11import app from '@/app' ;
22import logger from '@/configs/logger.config' ;
3- import { closeCache } from './configs/cache.config' ;
3+ import { closeCache , initCache } from './configs/cache.config' ;
44import { closeDatabase , initializeDatabase } from './configs/db.config' ;
55import { Server } from 'http' ;
6+ import { initSentry } from './configs/sentry.config' ;
67
7- const port = parseInt ( process . env . PORT || '8080' , 10 ) ;
8+ interface ShutdownHandler {
9+ cleanup ( ) : Promise < void > ;
10+ }
811
9- async function startServer ( ) {
10- try {
11- // 데이터베이스 초기화
12- await initializeDatabase ( ) ;
13- logger . info ( '데이터베이스 초기화 완료' ) ;
12+ /**
13+ * 서버의 graceful shutdown을 담당하는 매니저 클래스
14+ */
15+ class GracefulShutdownManager implements ShutdownHandler {
16+ private isShuttingDown = false ;
17+ private readonly shutdownTimeout = 10000 ; // 10초 강제 종료 타이머
1418
15- // 서버 시작
16- const server = app . listen ( port , ( ) => {
17- logger . info ( `Server running on port ${ port } ` ) ;
18- logger . info ( `Environment: ${ process . env . NODE_ENV } ` ) ;
19- if ( process . env . NODE_ENV !== 'production' ) {
20- logger . info ( `API Docs: http://localhost:${ port } /api-docs` ) ;
21- }
22- logger . info ( `Health Check: http://localhost:${ port } /health` ) ;
23- } ) ;
19+ constructor ( private server : Server ) { }
2420
25- // Graceful shutdown 핸들러 설정
26- setupGracefulShutdown ( server ) ;
27- } catch ( error ) {
28- logger . error ( '서버 시작 중 오류 발생:' , error ) ;
29- process . exit ( 1 ) ;
30- }
31- }
21+ /**
22+ * 모든 연결을 안전하게 정리하고 서버를 종료
23+ */
24+ async cleanup ( ) : Promise < void > {
25+ if ( this . isShuttingDown ) {
26+ return ;
27+ }
3228
33- function setupGracefulShutdown ( server : Server ) {
34- // 기본적인 graceful shutdown 추가
35- const gracefulShutdown = async ( signal : string ) => {
36- logger . info ( `${ signal } received, shutting down gracefully` ) ;
29+ this . isShuttingDown = true ;
30+
31+ // 강제 종료 타이머 설정 (데드락 방지)
32+ const forceExitTimer = setTimeout ( ( ) => {
33+ logger . error ( 'Could not close connections in time, forcefully shutting down' ) ;
34+ process . exit ( 1 ) ;
35+ } , this . shutdownTimeout ) ;
3736
3837 try {
3938 // HTTP 서버 종료
4039 await new Promise < void > ( ( resolve ) => {
41- server . close ( ( ) => {
40+ this . server . close ( ( ) => {
4241 logger . info ( 'HTTP server closed' ) ;
4342 resolve ( ) ;
4443 } ) ;
4544 } ) ;
4645
4746 // 데이터베이스 연결 종료
4847 await closeDatabase ( ) ;
48+ logger . info ( 'Database connections closed' ) ;
4949
5050 // 캐시 연결 종료
5151 await closeCache ( ) ;
52+ logger . info ( 'Cache connections closed' ) ;
5253
54+ clearTimeout ( forceExitTimer ) ; // 정상 종료 시 강제 타이머 해제
5355 logger . info ( 'Graceful shutdown completed' ) ;
54- process . exit ( 0 ) ;
5556 } catch ( error ) {
56- logger . error ( 'Graceful shutdown 중 오류 발생:' , error ) ;
57+ clearTimeout ( forceExitTimer ) ;
58+ throw error ;
59+ }
60+ }
61+
62+ /**
63+ * 시그널을 받아 graceful shutdown을 시작
64+ */
65+ handleShutdown ( signal : string ) : void {
66+ if ( this . isShuttingDown ) {
67+ logger . info ( `Already shutting down, ignoring ${ signal } ` ) ;
68+ return ;
69+ }
70+
71+ logger . info ( `${ signal } received, shutting down gracefully` ) ;
72+
73+ // 비동기 cleanup 실행 후 프로세스 종료
74+ this . cleanup ( )
75+ . then ( ( ) => process . exit ( 0 ) )
76+ . catch ( ( error ) => {
77+ logger . error ( 'Error during graceful shutdown:' , error ) ;
78+ process . exit ( 1 ) ;
79+ } ) ;
80+ }
81+ }
82+
83+ /**
84+ * Express 서버의 시작과 lifecycle을 관리하는 메인 클래스
85+ */
86+ class ServerManager {
87+ private server ?: Server ;
88+ private shutdownManager ?: GracefulShutdownManager ;
89+ private readonly port = parseInt ( process . env . PORT || '8080' , 10 ) ;
90+
91+ /**
92+ * 서버를 초기화하고 시작
93+ */
94+ async start ( ) : Promise < void > {
95+ try {
96+ await this . initializeServices ( ) ;
97+ this . server = this . createServer ( ) ;
98+ this . setupShutdownHandlers ( ) ; // 시그널 핸들러 등록
99+
100+ logger . info ( 'Server started successfully' ) ;
101+ } catch ( error ) {
102+ logger . error ( 'Failed to start server:' , error ) ;
57103 process . exit ( 1 ) ;
58104 }
59- } ;
60-
61- // 시그널 핸들러 등록
62- process . on ( 'SIGTERM' , async ( ) => gracefulShutdown ( 'SIGTERM' ) ) ;
63- process . on ( 'SIGINT' , async ( ) => gracefulShutdown ( 'SIGINT' ) ) ;
64-
65- // 예상치 못한 에러 처리
66- process . on ( 'uncaughtException' , async ( error ) => {
67- logger . error ( 'Uncaught Exception:' , error ) ;
68- await gracefulShutdown ( 'UNCAUGHT_EXCEPTION' ) ;
69- } ) ;
70-
71- process . on ( 'unhandledRejection' , async ( reason , promise ) => {
72- logger . error ( 'Unhandled Rejection at:' , promise , 'reason:' , reason ) ;
73- await gracefulShutdown ( 'UNHANDLED_REJECTION' ) ;
74- } ) ;
75-
76- // 강제 종료 타이머 (10초)
77- const forceExitTimer = setTimeout ( ( ) => {
78- logger . error ( 'Could not close connections in time, forcefully shutting down' ) ;
79- process . exit ( 1 ) ;
80- } , 10000 ) ;
81-
82- // graceful shutdown이 완료되면 타이머 해제
83- process . on ( 'exit' , ( ) => {
84- clearTimeout ( forceExitTimer ) ;
85- } ) ;
105+ }
106+
107+ /**
108+ * 데이터베이스 등 필요한 서비스들을 초기화
109+ */
110+ private async initializeServices ( ) : Promise < void > {
111+ // Sentry 초기화 (에러 모니터링을 위해 가장 먼저)
112+ initSentry ( ) ;
113+ logger . info ( 'Sentry initialized successfully' ) ;
114+
115+ // Cache 초기화
116+ initCache ( ) ;
117+ logger . info ( 'Cache initialized successfully' ) ;
118+
119+ // 데이터베이스 초기화
120+ await initializeDatabase ( ) ;
121+ logger . info ( 'Database initialized successfully' ) ;
122+ }
123+
124+ /**
125+ * Express 서버 인스턴스를 생성하고 시작
126+ */
127+ private createServer ( ) : Server {
128+ const server = app . listen ( this . port , ( ) => {
129+ logger . info ( `Server running on port ${ this . port } ` ) ;
130+ logger . info ( `Environment: ${ process . env . NODE_ENV } ` ) ;
131+
132+ // 개발 환경에서만 API 문서 URL 표시
133+ if ( process . env . NODE_ENV !== 'production' ) {
134+ logger . info ( `API Docs: http://localhost:${ this . port } /api-docs` ) ;
135+ }
136+
137+ logger . info ( `Health Check: http://localhost:${ this . port } /health` ) ;
138+ } ) ;
139+
140+ return server ;
141+ }
142+
143+ /**
144+ * 프로세스 시그널 핸들러와 에러 핸들러를 설정
145+ */
146+ private setupShutdownHandlers ( ) : void {
147+ if ( ! this . server ) {
148+ throw new Error ( 'Server not initialized' ) ;
149+ }
150+
151+ // shutdown manager 인스턴스 생성
152+ this . shutdownManager = new GracefulShutdownManager ( this . server ) ;
153+
154+ // Graceful shutdown 시그널 핸들러 등록
155+ process . on ( 'SIGTERM' , ( ) => {
156+ if ( this . shutdownManager ) {
157+ this . shutdownManager . handleShutdown ( 'SIGTERM' ) ;
158+ }
159+ } ) ;
160+
161+ process . on ( 'SIGINT' , ( ) => {
162+ if ( this . shutdownManager ) {
163+ this . shutdownManager . handleShutdown ( 'SIGINT' ) ;
164+ }
165+ } ) ;
166+
167+ // 치명적 에러 발생 시 즉시 종료
168+ process . on ( 'uncaughtException' , ( error ) => {
169+ logger . error ( 'Uncaught Exception:' , error ) ;
170+ process . exit ( 1 ) ; // graceful shutdown 없이 즉시 종료
171+ } ) ;
172+
173+ process . on ( 'unhandledRejection' , ( reason , promise ) => {
174+ logger . error ( 'Unhandled Rejection at:' , promise , 'reason:' , reason ) ;
175+
176+ // 개발 환경에서는 더 엄격하게 처리
177+ if ( process . env . NODE_ENV === 'development' ) {
178+ process . exit ( 1 ) ;
179+ }
180+ } ) ;
181+ }
86182}
87183
88- // 서버 시작
89- startServer ( ) ;
184+ // 애플리케이션 진입점
185+ const serverManager = new ServerManager ( ) ;
186+ serverManager . start ( ) . catch ( ( error ) => {
187+ logger . error ( 'Fatal error during server startup:' , error ) ;
188+ process . exit ( 1 ) ;
189+ } ) ;
0 commit comments