Connection Draining
2026-02-12 작성2026-09-17 수정2분 읽기
문서 도구
Connection Draining 패턴
Connection Draining은 종료 시 기존 연결을 안전하게 정리하는 패턴입니다.
HTTP Keep-Alive 연결 처리
// Node.js Express with Connection Draining
const express = require('express');
const app = express();
const server = app.listen(8080);
let isShuttingDown = false;
const activeConnections = new Set();
// 연결 추적
server.on('connection', (conn) => {
activeConnections.add(conn);
conn.on('close', () => {
activeConnections.delete(conn);
});
});
function gracefulShutdown(signal) {
console.log(`${signal} received`);
isShuttingDown = true;
// 새 연결 거부
server.close(() => {
console.log('Server closed, no new connections');
});
// 기존 연결 종료
console.log(`Closing ${activeConnections.size} active connections`);
activeConnections.forEach((conn) => {
conn.destroy(); // 강제 종료 (또는 conn.end()로 graceful)
});
// 정리 작업 후 종료
setTimeout(() => {
console.log('Graceful shutdown complete');
process.exit(0);
}, 5000);
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
WebSocket 연결 정리
// WebSocket graceful shutdown
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('close', () => {
clients.delete(ws);
});
ws.on('message', (message) => {
// 메시지 처리
});
});
function gracefulShutdown() {
console.log(`Closing ${clients.size} WebSocket connections`);
clients.forEach((ws) => {
// 클라이언트에게 종료 알림
ws.send(JSON.stringify({ type: 'server_shutdown' }));
ws.close(1001, 'Server shutting down');
});
wss.close(() => {
console.log('WebSocket server closed');
process.exit(0);
});
}
process.on('SIGTERM', gracefulShutdown);
gRPC Graceful Shutdown
package main
import (
"context"
"log"
"net"
"os"
"os/signal"
"syscall"
"time"
"google.golang.org/grpc"
pb "myapp/proto"
)
type server struct {
pb.UnimplementedMyServiceServer
}
func main() {
lis, err := net.Listen("tcp", ":9090")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterMyServiceServer(s, &server{})
go func() {
log.Println("gRPC server starting on :9090")
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}()
// SIGTERM 대기
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
<-quit
log.Println("Graceful shutdown initiated")
// GracefulStop: 진행 중인 RPC 완료 대기
done := make(chan struct{})
go func() {
s.GracefulStop()
close(done)
}()
// Timeout 처리
select {
case <-done:
log.Println("gRPC server stopped gracefully")
case <-time.After(50 * time.Second):
log.Println("Graceful stop timeout, forcing stop")
s.Stop() // 강제 종료
}
}
데이터베이스 연결 풀 정리
# Python with psycopg2 connection pool
import psycopg2
from psycopg2 import pool
import signal
import sys
# Connection pool
db_pool = psycopg2.pool.SimpleConnectionPool(
minconn=1,
maxconn=10,
host='db.example.com',
database='mydb',
user='user',
password='password'
)
def graceful_shutdown(signum, frame):
print("Closing database connections...")
# 모든 연결 종료
db_pool.closeall()
print("Database connections closed")
sys.exit(0)
signal.signal(signal.SIGTERM, graceful_shutdown)
# 애플리케이션 로직
def query_database():
conn = db_pool.getconn()
try:
cur = conn.cursor()
cur.execute("SELECT * FROM users")
return cur.fetchall()
finally:
db_pool.putconn(conn)