본문으로 건너뛰기

Connection Draining

2026-02-12 작성2026-09-17 수정2분 읽기
문서 도구
AI용 문서 안내

Pod 라이프사이클 개요 · 체크리스트와 참고 자료

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)