~/brunosong
← 프로젝트 목록

Pipeline

대량 문서 변환 처리 파이프라인

어드민이 변환 솔루션을 동기로 호출하던 구조였습니다. 500건을 한 번에 요청하면 게이트웨이 타임아웃이 나고, 그 시점의 변환 결과는 사라졌습니다. 처리를 별도 서비스로 분리하면서 생긴 문제들을 어떻게 다뤘는지 정리했습니다.

실무 프로젝트Java · Spring Boot · MySQL

접수와 처리를 끊었습니다

변경 전

어드민 ──▶ 변환 솔루션
       동기 호출 · 500건

응답이 끊기면 → 결과 유실

변경 후

어드민 ──▶ 접수 ──▶ 작업 테이블
                      │ SKIP LOCKED
                      ▼
                   워커 N ──▶ 변환 솔루션

응답이 끊겨도 → 작업은 계속

요청자가 기다리는 시간과 작업이 끝나는 시간을 분리했습니다. 타임아웃을 없앤 게 아니라, 결과를 잃지 않는 위치로 옮긴 것에 가깝습니다.

응답을 못 받은 것과 실패한 것은 다릅니다

504를 받았다고 해서 그 변환이 실패했다는 뜻은 아닙니다. 솔루션 쪽에서는 이미 끝났을 수도 있습니다. 그래서 실패로 단정하지 않고 판정을 미루는 상태를 하나 두었습니다.

변환 요청의 결과 판정 상태 전이접수처리 중응답 받음504 · 응답 없음결과 미확정응답 없음 ≠ 실패솔루션 캐시 재조회확인 한도 초과 시성공실패추적 포기

둘을 같게 다루면 이미 끝난 작업을 다시 돌리거나, 끝나지 않은 작업을 끝난 것으로 처리하게 됩니다. 미확정 상태는 솔루션 캐시를 반복 조회해 성공·실패로 수렴하고, 확인 한도를 넘기면 추적을 포기한 것으로 남깁니다.

부딪힌 문제와 선택

01

어드민이 변환 솔루션을 동기로 호출했습니다. 500건을 한 번에 요청하면 게이트웨이 타임아웃(504)이 나고, 그 시점에 진행 중이던 변환 결과는 그대로 사라졌습니다.

변환 처리를 별도 서비스로 떼어내고 접수와 처리를 끊었습니다. 요청은 접수만 하고 즉시 응답하며, 실제 변환은 워커가 가져가 처리합니다.

요청자가 기다리는 시간과 작업이 끝나는 시간을 분리하면, 응답이 끊겨도 작업은 계속됩니다. 타임아웃은 사라진 게 아니라 결과를 잃지 않는 위치로 옮긴 것입니다.

02

같은 자료가 중복으로 변환되는 문제가 있었습니다. 조회 후 등록하는 방식만으로는 동시에 들어온 요청을 막을 수 없었습니다.

활성 상태일 때만 값이 붙는 유니크 키를 두고, 선조회와 DB 제약을 2단계로 걸었습니다.

선조회는 대부분의 중복을 싸게 걸러내고, DB 제약은 그 사이를 비집고 들어온 동시 요청을 막습니다. 애플리케이션 검사만으로는 경합을 막을 수 없고, DB 제약만 두면 정상 흐름에서도 예외로 처리하게 됩니다. 완료된 건은 키에서 빠지므로 재변환은 허용됩니다.

03

504를 받았을 때 그 작업이 실패한 것인지 알 수 없었습니다. 응답을 못 받았을 뿐 솔루션 쪽에서는 변환이 끝났을 수도 있습니다.

504를 실패로 단정하지 않고 결과 미확정 상태로 따로 두었습니다. 이후 솔루션 캐시를 반복 조회해 성공·실패·추적 포기 중 하나로 수렴시킵니다.

응답을 받지 못한 것과 작업이 실패한 것은 다릅니다. 둘을 같게 다루면 이미 끝난 작업을 다시 돌리거나, 끝나지 않은 작업을 끝난 것으로 처리하게 됩니다. 판정을 미루는 상태를 하나 두는 편이 정확합니다.

04

워커를 여러 개 띄우면 같은 작업을 동시에 집어갈 수 있고, 솔루션에 동시 호출이 몰릴 수도 있었습니다.

SELECT ... FOR UPDATE SKIP LOCKED 로 워커가 작업을 선점하게 했습니다. 잠긴 행은 건너뛰므로 서로 기다리지 않습니다.

큐를 따로 두지 않고도 다중 인스턴스에서 중복 없이 나눠 가질 수 있습니다. 한 번에 가져가는 개수로 솔루션 쪽 동시 호출 수도 함께 조절됩니다.

05

접수·처리·조회가 한 덩어리에 있으면 조회 경로에서 상태를 바꾸는 코드가 언제든 섞여 들어올 수 있습니다.

포트/어댑터로 세 경계를 나누고, 각 경계가 쓸 수 있는 포트를 분리했습니다.

규칙을 문서로 적어두면 지켜지지 않습니다. 조회 어댑터에 상태를 바꾸는 포트를 주지 않으면, 잘못된 호출은 리뷰가 아니라 컴파일 단계에서 막힙니다.

남은 생각

이 작업에서 가장 오래 붙잡은 건 성능이 아니라 모르는 상태를 어떻게 다룰 것인가였습니다. 분산된 시스템에서는 상대의 결과를 확인하지 못하는 순간이 반드시 생기고, 그때 임의로 성공이나 실패로 밀어버리면 데이터가 어긋납니다. 판정을 미루는 상태를 하나 두는 비용이, 잘못 판정한 데이터를 나중에 되돌리는 비용보다 훨씬 쌉니다.