Post

소켓 기반 파일 전송 시스템 설계 - 이벤트 드리븐 아키텍처

소켓 기반 파일 전송 시스템 설계 - 이벤트 드리븐 아키텍처

배경

금융 기관과 파일 전문(전자문서)을 주고받는 시스템을 새로 설계했다. 기존에는 Windows 전용 프로그램을 통해 담당자가 수동으로 파일을 전송/수신하고 있었는데, 이를 24시간 자동화된 소켓 기반 시스템으로 전환하는 프로젝트였다.

요구사항과 제약조건

  • 3가지 양방향 프로세스: 변동 데이터 수신, 선택적 수신 요청, 집중 데이터 수신
  • 순차 처리 필수: 초기화 → 전송 → 보고 단계가 반드시 순서대로 진행되어야 함
  • 동시 소켓 연결 1개 제한: 한 번에 하나의 파일만 전송 가능
  • 비동기 응답: 상대 기관의 응답이 2-3시간 뒤 별도 포트로 도착

이 제약조건들이 설계를 흥미롭게 만든 핵심 요인이다.


설계 결정 1: 비즈니스 로직과 통신의 분리

graph LR
    subgraph "메인 애플리케이션"
        A[요청 처리] --> B[전문 생성/파싱]
        B --> C[비즈니스 규칙]
        C --> D[DB 영속화]
    end
    subgraph "소켓 서버"
        E[소켓 연결 유지]
        F[바이트 송수신]
        G[포트 관리]
    end
    D -->|S3 경유| E

메인 애플리케이션은 “무엇을 보낼지” 결정하고, 소켓 서버는 “어떻게 보낼지”만 담당한다. 왜 이렇게 분리했냐면:

  • 소켓 서버는 특정 포트에서 장시간 연결을 유지해야 하는 반면, 비즈니스 로직은 요청-응답 패턴이다
  • 각각 독립적으로 배포하고 장애를 격리할 수 있다
  • 소켓 서버에 문제가 생겨도 비즈니스 로직은 계속 동작하고, 복구 후 재전송하면 된다

설계 결정 2: DB 기반 이벤트 아키텍처

메시지 큐(RabbitMQ, SQS 등) 대신 DB 테이블을 이벤트 저장소로 활용했다.

stateDiagram-v2
    [*] --> INIT: 전송 요청 생성
    INIT --> SENDING: Detector가 감지
    SENDING --> SENT: 전송 완료
    SENT --> WAITING_ACK: 응답 대기
    WAITING_ACK --> COMPLETED: 2-3시간 후 응답 도착
    WAITING_ACK --> RETRY: 타임아웃
    RETRY --> SENDING: 재시도
    COMPLETED --> [*]

Detector라는 프로세스가 DB를 주기적으로 폴링하여 특정 상태의 레코드를 발견하면 해당 함수를 실행하고, 상태를 업데이트한다.

왜 MQ 대신 DB를 선택했나?

비교 항목메시지 큐DB 기반 이벤트
순차 처리순서 보장 설정 필요상태 머신으로 자연스럽게 보장
비동기 응답 (2-3h)메시지 TTL 관리 필요DB에 영속화되어 시간 무관
중단/재개별도 체크포인트 구현상태만 보면 어디서든 재개 가능
추가 인프라MQ 서버 필요기존 DB 활용

“폴링은 안티패턴”이라는 편견을 버리면 설계 선택지가 넓어진다. 이 경우처럼 순차 처리 제약이 강하고 비동기 응답을 기다려야 할 때, DB 상태 머신이 MQ보다 단순하고 안정적이었다.


설계 결정 3: S3 이벤트 파이프라인

메인 앱에서 소켓 서버로 데이터를 전달하는 방식으로 직접 API 호출 대신 S3 이벤트 트리거를 선택했다.

flowchart LR
    A[메인 앱] -->|파일 업로드| B[S3]
    B -->|이벤트 트리거| C[Lambda]
    C -->|전송 요청| D[소켓 서버]
    D -->|TCP/IP| E[상대 기관]

직접 API 호출 대비 장점:

  • 장애 복구 단순화: API 호출 실패 시 재시도 로직이 복잡해지지만, S3에 파일이 남아있으므로 실패한 이벤트만 재처리하면 된다
  • 결합도 감소: 메인 앱은 S3에 파일만 올리면 끝. 소켓 서버의 존재를 알 필요 없다
  • 자동 로깅: S3 이벤트와 Lambda 실행 로그가 자동으로 남는다

“파일이 있으면 처리한다”는 단순한 계약만으로 두 시스템이 느슨하게 연결된다.


6단계 메시지 검증 체계

소켓 통신은 HTTP와 달리 프레임 경계가 없다. 바이트 스트림을 직접 파싱해야 하므로 다층 검증이 필수다.

flowchart TD
    A[수신된 바이트 스트림] --> B{1. 시퀀스 검증}
    B -->|OK| C{2. 타임아웃 검증}
    C -->|OK| D{3. 공통 헤더 검증}
    D -->|OK| E{4. 전문 타입 검증}
    E -->|OK| F{5. 상세 필드 검증}
    F -->|OK| G{6. 메시지 무결성}
    G -->|OK| H[비즈니스 로직 처리]
    B -->|FAIL| I[에러 처리]
    C -->|FAIL| I
    D -->|FAIL| I
    E -->|FAIL| I
    F -->|FAIL| I
    G -->|FAIL| I

각 단계가 독립적으로 실패를 감지하므로, 문제 발생 시 어느 레벨에서 실패했는지 즉시 파악할 수 있다.


파일 수신 프로세스

파일 수신은 7단계로 구성되며, 누락 데이터 재요청과 파일 연속 수신을 처리한다.

flowchart TD
    A[1. 업무개시지시] --> B[완료보고]
    B --> C[2. 헤더레코드 수신]
    C --> D[수신보고]
    D --> E[3. 데이터레코드 x N 수신]
    E --> F[4. 누락 체크]
    F -->|누락 있음| G[재요청]
    G --> H[재전송 수신]
    H --> F
    F -->|누락 없음| I[5. 트레일러레코드]
    I --> J[수신보고]
    J --> K{6. 후속 파일 있음?}
    K -->|Yes| C
    K -->|No| L[7. 업무종료지시]
    L --> M[완료보고]

송신 재시도 로직

60초 무응답 시 재시도하며 최대 3회 시도한다. 이벤트 큐 기반으로 관리하여 부분 실패 시에도 성공한 전송은 보존된다.

flowchart TD
    A[데이터 전송] --> B{60초 내 응답?}
    B -->|Yes| C[성공 - 다음 레코드]
    B -->|No| D{재시도 횟수 < 3?}
    D -->|Yes| A
    D -->|No| E[에러 보고]

전체 아키텍처 요약

flowchart TB
    subgraph main["메인 애플리케이션"]
        A[전문 생성/파싱] --> B[6단계 Validator]
        B --> C[DB 상태 관리]
        C --> D[S3 업로드]
    end
    subgraph pipeline["이벤트 파이프라인"]
        D --> E[S3]
        E -->|이벤트| F[Lambda]
    end
    subgraph socket["소켓 서버"]
        F --> G[송신 포트]
        H[수신 포트 - 24시간 유지]
    end
    G <-->|TCP/IP| I[상대 기관]
    I <-->|TCP/IP| H

느낀 점

  • DB 기반 상태 머신은 MQ 없이도 이벤트 드리븐 패턴을 구현할 수 있는 실용적인 방법이다. 순차 처리 제약이 있고 비동기 응답을 기다려야 할 때 특히 유용하다.

  • S3 이벤트 트리거는 시스템 간 결합도를 낮추면서 장애 복구를 단순화한다. 복잡한 재시도 로직 대신 “파일이 있으면 처리한다”는 단순한 계약이면 충분하다.

  • 소켓 통신은 HTTP와 다르다. 프레임 경계 없음, 바이트 파싱, 연결 상태 관리 등 저수준 문제가 많다. 검증 레이어를 충분히 쌓는 것이 장기 운영의 핵심이다.

  • 수동 작업에서 자동화 시스템으로의 전환은 기술적 도전뿐 아니라 실패 모드를 미리 정의하고 각각에 대한 복구 전략을 설계하는 것이 핵심이다.

This post is licensed under CC BY 4.0 by the author.