ServiceFlow

FastAPI로 만든 주문·지원 티켓 API의 조회 속도와 오류 처리, 데이터 검증을 점검한 프로젝트입니다. API 응답, 요청 로그, 데이터베이스를 대조해 문제의 원인을 찾고 회귀 테스트로 수정 결과를 확인했습니다.

공개 저장소 · 생성 데이터로 로컬 검증 · 온라인 데모 없음

PythonFastAPISQLitepytest애플리케이션 지원

로컬 검증 결과

서비스 계층 집계 시간

90.26 ms → 2.11 ms

이번 측정에서 42.8배 · 워밍업 1회 후 5회 측정한 중앙값

생성한 SQLite 테스트 데이터

고객 500명 / 주문 2,000건

주문 항목 5,966개

회귀 테스트

28개 pytest 테스트

같은 테스트를 정상 모드와 장애 모드에서 각각 실행해 모두 통과

문제 분석과 해결

테스트용 데이터를 생성하고 의도적으로 오류가 발생하도록 구성해 분석했습니다. 각 문서에서 재현 방법, 관련 코드와 테스트를 확인할 수 있습니다.

INC-001 주문 보고서 조회 지연

확인한 문제
HTTP 200과 예상한 500개 행은 반환됐지만, 고객마다 쿼리를 반복하는 N+1 구조와 고객 인덱스 누락으로 조회가 느렸습니다.
수정 내용
고객 인덱스를 추가하고 반복 쿼리를 하나의 JOIN … GROUP BY 쿼리로 바꿨습니다.

INC-002 결제 실패 처리

확인한 문제
결제 클라이언트에 타임아웃이 없었고, 결제가 실패해도 주문 상태가 paid로 남았습니다.
수정 내용
클라이언트 타임아웃을 3초로 설정하고 결제가 성공한 뒤에만 주문을 paid로 표시하도록 했습니다. 실패 시 내부 정보 없이 502를 반환하며 자동 재시도는 하지 않습니다.

INC-003 주문 데이터 검증

확인한 문제
수량이 음수이거나 가격이 없는 항목을 API가 받아들여 잘못된 주문 합계를 저장했습니다.
수정 내용
주문 저장 전에 qty가 1 이상인지, 가격이 입력됐고 0 이상인지 검사합니다. 조건을 충족하지 못한 요청은 주문을 저장하지 않고 거부합니다.

주문 보고서가 느렸던 이유

  1. 먼저 HTTP 200과 예상한 500개 행이 반환되는지 확인했습니다. 결과 누락이 아닌 조회 시간의 문제로 범위를 좁혔습니다.
  2. 서비스 코드를 확인하니 고객 목록을 한 번 조회한 뒤 고객마다 SUM과 COUNT를 따로 실행해 총 1 + 2N개의 쿼리가 필요했습니다. 장애 설정에서는 orders.customer_id 인덱스도 없었습니다.
  3. orders.customer_id 인덱스를 추가하고 한 번의 그룹 집계로 바꿔 두 원인을 함께 수정했습니다. 회귀 테스트는 계산 결과와 각 모드의 인덱스 존재 여부를 확인합니다.
인덱스 확인: 고객별 SUM 쿼리

SCAN orders
SEARCH orders USING INDEX ix_orders_customer_id

고객 한 명의 SUM을 구하는 별도 쿼리에 EXPLAIN QUERY PLAN을 적용해 인덱스가 조회 방식을 어떻게 바꾸는지 확인했습니다. 전체 JOIN … GROUP BY 보고서 쿼리의 실행 계획은 저장소에 따로 있습니다.

워밍업 1회 후 5회 측정한 서비스 함수 실행 시간의 중앙값은 90.26 ms에서 2.11 ms로 줄었습니다. 같은 프로세스 안에서 측정했으므로 HTTP 요청 처리 시간은 포함하지 않습니다. 벤치마크 파일에 개별 측정값이 있으며, 실행하는 컴퓨터에 따라 시간은 달라집니다.

범위와 한계

  • 생성한 데이터로 로컬 환경에서 오류를 재현한 결과입니다. 실제 운영 장애 대응, SLA 달성 실적이나 당직 경험을 나타내지 않습니다.
  • 측정과 테스트에는 SQLite를 사용했습니다. PostgreSQL 설정은 가능하지만 검증하지 않았습니다. API에는 인증이나 역할별 접근 제어가 없습니다.
  • 결제 흐름에는 로컬 모의 결제 API가 필요합니다. 테스트는 httpx 타임아웃 설정과 TimeoutException 발생 시 처리를 확인했으며, 전체 요청의 타임아웃 시간은 측정하지 않았습니다. 결제를 재시도하려면 멱등성 키를 먼저 설계해야 합니다.