유한 상태 머신(FSM) 기본 개념과 적용 포인트

유한 상태 머신(FSM, Finite State Machine)은 시스템이 가질 수 있는 상태와 그 사이 전이를 명확히 정의하는 모델 복잡한 비즈니스 로직을 단순화하고 예측 가능하게 만들어 결제나 주문처럼 순서와 무결성이 중요한 도메인에 적합 개념/배경 상태와 규칙을 명시적으로 모델링해 허용되지 않은 동작을 구조적으로 차단하는 접근 요구사항이 늘어도 상태 전이 규칙을 중심으로 변경 범위를 제한해 안정성 확보에 유리 핵심 개념 상태 State 시스템이 존재할 수 있는 유한한 조건 집합 예 로그인 전, 로그인 후, 결제 대기, 결제 완료 이벤트 또는 입력 Event/Input 상태 변화를 유발하는 외부 행위나 신호 예 버튼 클릭, 네트워크 끊김, 시간 초과 전이 또는 규칙 Transition/Rule 어떤 상태에서 어떤 이벤트가 발생하면 다음 상태로 이동한다는 정의 규칙에 없는 전이는 거부 또는 무시 동작 원리 현재 상태와 입력을 받아 다음 상태를 결정하는 전이 함수 관점으로 해석 가능 f(state, event) -> nextState 형태 전이에는 가드 조건과 부수효과가 수반될 수 있음 가드는 전이 허용 여부 판단, 부수효과는 알림 전송이나 로그 기록 등 외부 행동을 명시적으로 수행 정의되지 않은 전이를 막아 비정상 흐름 차단 재진입이나 중복 이벤트 처리 시에도 일관된 행동을 보장 예시 자판기 모델 상태 대기 중, 이벤트 동전 투입 -> 다음 상태 금액 충족 돈이 들어와야만 다음 상태로 이동 상태 금액 충족, 이벤트 상품 버튼 누름 -> 다음 상태 상품 배출 중 상품 선택 가능 상태 상품 배출 중, 이벤트 배출 완료 -> 다음 상태 대기 중 초기 상태로 복귀 상태 대기 중, 이벤트 상품 버튼 누름 -> 상태 변화 없음 금액 미충족 시 입력 무시 장점과 효과 정합성 보장 정의된 전이 외 동작 불가하므로 데이터 꼬임 예방 결제가 완료 상태에서 결제 요청 이벤트가 다시 들어오면 규칙에 없는 전이로 처리되어 거부 또는 무시되어 중복 결제 차단에 기여 멱등 처리 전략과 결합 시 효과적 예측 가능성 향상 몇 개의 상태와 전이 규칙으로 복잡도를 축소해 로직 이해와 디버깅 용이 테스트 용이성 상태 x 이벤트 조합별로 기대 결과가 명확해 단위 테스트 케이스 설계가 단순 주의와 베스트 프랙티스 상태 폭발 주의 의미 있는 상태만 유지하고 파생 속성은 별도 데이터로 관리 전이 정의의 단일 소스 유지 가드와 부수효과를 전이 정의 근처에 모아 산재된 분기 로직을 제거 잘못된 전이 로깅과 모니터링 활성화 무시된 입력을 계측해 모델 누락이나 외부 시스템 이상 조기 탐지 멱등성 고려 중복 이벤트 재수신 가능 환경에서는 전이와 부수효과를 멱등하게 설계 분산 환경에서는 상태 영속화와 재시도 정책을 명시 크래시 후 재기동 시 일관성 있는 상태 복원 필요 마무리 FSM은 상태와 전이를 중심으로 시스템 동작을 제한하고 드러내며, 규칙 밖 동작을 원천 차단해 안정성을 끌어올림 결제나 주문 같은 순서 기반 도메인에 특히 유용하며, 상태 정의와 전이 규칙만 명확히 유지하면 변경에도 견고하게 대응 가능 ...

January 4, 2026

API 에러 응답 설계 가이드 — HTTP Status는 대분류, 비즈니스 의미는 바디

개요 HTTP Status Code만으로는 서비스 로직의 원인을 전달하기 부족함 400대와 500대는 네트워크 관점의 대분류 신호에 가깝고 실제로 필요한 것은 비즈니스 맥락의 구체적 사유임 결론은 단순함 HTTP Status는 대분류 신호로 두고 실제 의미와 추가 컨텍스트는 Response Body에 싣는 구조가 현실적 해법임 핵심 원칙 HTTP Status는 큰 범주 신호등 역할 2xx 성공 4xx 클라이언트 오류 5xx 서버 오류 비즈니스 의미는 Response Body의 커스텀 에러 구조로 표현 클라이언트가 코드 기반으로 분기하고 UI를 결정할 수 있어야 함 운영 관측을 위해 traceId 제공 업계에서 검증된 기본 구조 아래 형태가 가장 보편적으로 쓰이는 패턴임 ...

December 31, 2025

API ErrorCode Enum 설계 베스트 프랙티스

개요 API 에러 규격의 핵심은 에러를 안정적으로 식별할 수 있는 Code 체계 확보임 HTTP Status만으로는 부족하고 메시지는 언어·맥락에 따라 바뀔 수 있음 실제로 계약으로서 신뢰할 수 있는 값은 error.code 임 아래는 다수의 글로벌 서비스에서 공통적으로 쓰는 Error Code 설계 원칙 정리 설계 원칙 error.code 는 서비스 전반의 안정적인 식별자여야 함 메시지나 HTTP Status는 변경 가능하지만 error.code 는 변경 불가 구버전 클라이언트도 동일 코드를 신뢰해야 하므로 호환성 보장 필수 숫자형 대신 의미가 드러나는 문자열 기반 Enum 권장 숫자형은 의미 파악 어려움, 매뉴얼 의존, 협업 비용 증가 문자열 Enum은 가독성, 검색성, 커뮤니케이션 효율 우수 예시 ...

December 25, 2025

NestJS forRoot()의 동작 원리와 싱글톤에 대한 오해

개요 NestJS을 다루다 보면 ConfigModule.forRoot(), TypeOrmModule.forRoot() 같은 코드를 보게 됨 보통 “이건 전역 설정이니까 한 번만 하면 끝이고 알아서 싱글톤 유지되겠지?“라고 생각하기 쉬움 하지만 forRoot()를 호출한다고 프레임워크가 알아서 물리적인 싱글톤 인스턴스를 강제하는 건 아님 특히 ScheduleModule처럼 사이드 이펙트(이벤트 리스너, 타이머 등)를 유발하는 모듈을 잘못 다루면, 기능이 중복 실행되는 심각한 버그가 터질 수 있음 이 글에서는 forRoot()의 진짜 의미와 내부 동작, 그리고 ScheduleModule 중복 실행 문제가 왜 생기는지 코드로 뜯어보겠음 forRoot()란 무엇인가 forRoot()는 NestJS의 동적 모듈(Dynamic Module)을 생성하기 위해 관례적으로 쓰는 메서드 이름임 ...

December 7, 2025

백오프 Backoff 재시도 전략 정리 고정·선형·지수·지터

개요 백오프는 실패한 작업을 즉시 재시도하지 않고 일정 시간 대기 후 다시 시도하는 전략을 말함 연속 실패 시 대기 시간을 점진적으로 늘려 서비스와 네트워크에 가해지는 부하를 낮추는 목적 RPC, 데이터베이스, 외부 API 호출, 트랜잭션 재시도 등에서 일반적으로 사용 왜 쓰는가 과부하 방지 실패 직후 동시 재시도를 막아 서버와 네트워크 폭주를 예방 일시적 장애 흡수 순간적인 지연이나 혼잡이 해소될 시간을 벌어 성공 확률을 높임 비용 절감 불필요한 재시도 횟수와 리소스 낭비 감소 대표 백오프 패턴 고정 백오프 Fixed 매번 동일 대기 시간 사용 예) 1초 → 1초 → 1초 선형 백오프 Linear 시도할 때마다 일정 간격으로 증가 예) 1초 → 2초 → 3초 → 4초 지수 백오프 Exponential 보통 2배로 증가하는 지수 증가 사용, 실무에서 기본값으로 가장 흔함 예) 1초 → 2초 → 4초 → 8초 → 16초 지수 백오프 + 지터 Jitter 지수 증가에 무작위성을 섞어 동시 재시도 동기화를 깨뜨림 예) 1초 → 2.3초 → 4.1초 → 8.6초 실전 포인트 동시성 환경에서는 지터 필수 동일한 주기 백오프만으로는 다수 클라이언트가 같은 타이밍에 몰려 서버를 다시 두들김 상한 설정 최대 대기 시간과 최대 재시도 횟수 캡을 두어 꼬리 길어짐 방지 실패 예산과 타임아웃 연계 전체 호출 타임아웃 내에서 재시도 예산을 배분, 1회 호출과 재시도들이 전체 SLA를 초과하지 않도록 관리 멱등성 보장 재시도 가능한 작업은 멱등성을 만족해야 안전, 아니면 보상 로직 필요 서버 힌트 활용 Retry-After 등 서버가 제시하는 대기 힌트가 있으면 우선 적용 지터 방식 선택 전체 구간 무작위 분포 Full jitter가 단순하고 효과적이라는 보고가 많음 간단 예시 아래는 지수 백오프의 최소 구현 예시이며, 지터와 최대 대기 시간 제한은 상황에 맞게 추가 권장 ...

November 28, 2025

Prisma cursor 기반 페이지네이션 동작 원리와 skip: 1의 의미

개요 Prisma에서 cursor는 특정 레코드 지점부터 결과를 읽기 시작하는 기준점으로 동작함 skip: 1은 해당 cursor 레코드를 결과에서 제외하기 위한 옵션으로, 페이지 간 중복을 제거하는 데 사용함 핵심 동작 cursor는 그 지점부터 시작 await prisma.user.findMany({ cursor: { id: 100 }, take: 5, orderBy: { id: "asc" }, }); // 결과: 100부터 시작해 5개 반환 skip: 1은 cursor에 해당하는 레코드를 건너뜀 await prisma.user.findMany({ cursor: { id: 100 }, skip: 1, take: 5, orderBy: { id: "asc" }, }); // 결과: 101부터 5개 반환 예시로 보는 차이 데이터가 아래와 같다고 가정 ...

October 29, 2025

Node.js 환경에서 디버깅하기

개요 Node.js에서 디버깅은 문제를 재현 가능한 최소 단위로 축소하고, 실행 흐름과 상태를 관찰해 원인을 단정하는 과정임. 이 글은 디버깅 기본 원리와 함께 Node.js 환경에서 자주 쓰는 세 가지 방법인 Chrome DevTools, node-inspect CLI, VS Code 디버거 사용법을 정리함 문제를 명확히 하기 작성한 코드의 기대 동작 정의 실제 관측된 동작과의 차이 정리 실패 조건과 재현 절차 고정 문제 정의가 모호하면 디버깅 범위가 불필요하게 커짐. 입력, 환경 변수, 의존성 버전, 네트워크 상태 등 외부 요인도 고정하는 편이 좋음 ...

October 23, 2025