← 블로그 목록
39 데이터·연동

안전한 CSV 가져오기와 검증·멱등성·대사 설계

CSV 업로드를 본 테이블에 바로 반영하지 않고 원본 보관, 단계별 검증, 멱등 확정, 수량·합계 대사로 운영하는 설계 기준을 예시와 함께 설명합니다.

안전한 CSV 가져오기와 검증·멱등성·대사 설계 대표 이미지

CSV 가져오기는 파일을 읽는 기능이 아니라, 같은 데이터가 여러 번 들어와도 결과가 예측 가능하고 오류 행을 되돌릴 수 있게 만드는 정합성 작업입니다. 안전한 기본값은 원본 파일을 보관하고, 헤더·인코딩·타입·업무 규칙을 검증한 뒤 임시 테이블에 적재하고, 행별 오류와 중복 키를 확정 전에 보여주는 것입니다.

CSV 문법의 인용·줄바꿈·구분자 처리 자체는RFC 4180의 관례를 참고하되, 업무 데이터의 필수값·참조 무결성·중복 허용 여부는 서비스 계약으로 별도 정의해야 합니다. 문법 파싱이 성공했다고 바로 본 테이블을 수정하지 말고, 파일 식별자와 행 번호를 가진 import batch를 만들어 검증·확정·재조정 단계의 증거를 연결합니다.

가져오기 경계를 임시 영역으로 나누기

한 번의 업로드는 batch_id, 원본 파일 해시, 업로더, 업로드 시각, 스키마 버전, 상태를 갖는 작업으로 시작합니다. 원본 파일은 재현성을 위해 접근 통제된 저장소에 보관하고, 처리 중 생성되는 임시 행에는 batch_id와 source_row를 반드시 붙입니다. 이렇게 해야 같은 파일을 다시 올렸을 때 파일 해시나 외부 idempotency key로 이미 처리된 배치를 식별하고, 특정 행만 오류가 났는지 재조정할 수 있습니다.

임시 테이블은 본 테이블의 모든 제약을 그대로 복사하기보다, 원문 값과 정규화 값, 오류 코드, 오류 메시지, 검증 상태를 함께 담는 구조가 실용적입니다. 금액을 문자열로 받은 뒤 통화와 소수 자릿수를 확인해 정수 최소 단위로 변환하고, 날짜는 허용 형식과 시간대를 명시합니다. 파서가 허용하지 않는 열 수, 깨진 인용, 예상 밖 인코딩은 행 단위 오류가 아니라 파일 단위 중단으로 처리해야 부분 성공을 오해하지 않습니다.

CSV 원본 보관부터 임시 적재, 검증, 확정, 재조정까지의 배치 처리 흐름도

검증 순서와 오류의 책임

검증은 값이 존재하는지부터 업무 의미가 맞는지까지 층을 나눕니다. 첫째 헤더와 열 수, 둘째 필수값·길이·문법, 셋째 타입·단위·허용 목록, 넷째 참조 키와 중복, 다섯째 현재 상태에서의 업무 규칙을 순서대로 확인합니다. 앞 단계가 실패한 행은 뒤 단계의 오류를 억지로 더하지 않아야 운영자가 원인을 하나씩 고칠 수 있습니다. 각 오류에는 stable code, 사람이 읽는 메시지, 원본 행 번호, 관련 열을 함께 기록합니다.

중복은 파일 안의 중복과 기존 본 테이블과의 충돌을 분리합니다. idempotency_key가 있는 경우 같은 키와 같은 payload hash는 재실행으로 간주하고 결과를 재사용할 수 있지만, 같은 키에 다른 payload가 오면 충돌로 중단합니다. 자연 키만 있는 데이터는 정규화 규칙을 먼저 합의하지 않으면 공백·대소문자·전화번호 형식 차이로 중복 판정이 흔들리므로, 비교용 canonical 값과 원본 표시값을 모두 보존합니다.

행별 원본값·정규화값·오류코드와 합계 비교를 담은 CSV 검증 기록 예시

확정과 재실행: 멱등성을 데이터로 증명하기

검증 통과 후 확정 단계는 batch 단위로 원자성을 확보합니다. 모든 행을 한 트랜잭션에 넣기 어렵다면 chunk마다 커밋하되 batch 상태를 validating, committing, committed, partially_failed처럼 분명히 기록하고 재시작 지점을 남깁니다. 확정 쿼리는 idempotency_key와 대상 테이블의 unique 제약을 함께 사용하고, 이미 committed인 batch를 다시 실행하면 새 레코드를 만들지 않고 기존 결과 요약을 반환해야 합니다.

부분 실패를 허용하는 정책이라면 성공 행과 실패 행의 범위를 사용자에게 미리 알리고, 실패 행만 수정해 같은 batch의 재조정 작업으로 다시 검증할 수 있게 합니다. 반대로 회계 합계처럼 한 행의 실패가 전체 의미를 깨는 파일은 all-or-nothing으로 중단합니다. 이 선택은 구현 편의가 아니라 데이터의 업무 단위와 복구 비용으로 결정하고, import 승인 화면에 표시합니다.

예시: 고객 상품 가격표를 한 번만 확정하기

예시의 입력은 1,000행 CSV이며 외부 상품 코드, 적용일, 통화, 가격, idempotency_key 열을 포함합니다. 결정은 파일 해시와 batch_id를 만든 뒤 임시 테이블에 적재하고, 상품 코드 존재 여부·ISO 통화·가격의 음수 여부·같은 키의 payload hash를 검증하는 것입니다. 중간 산출물은 987건 valid, 10건 missing_product, 2건 duplicate_key_conflict, 1건 malformed_row인 오류 리포트와 원본 파일 해시입니다. 예상 결과는 승인자가 충돌 2건을 수정한 새 파일을 올리고, valid 행만 확정하며, 기존 committed 배치를 재전송해도 건수가 변하지 않는 것입니다.

이 사례에서 재조정 화면은 원본 행 번호와 정규화 값을 나란히 보여주고, 성공·오류·제외 합계가 파일 행 수와 일치하는지 확인합니다. 확정 후에는 대상 테이블의 count와 가격 합계를 batch 기준으로 다시 집계해 임시 영역의 valid 합계와 비교하고, 차이가 있으면 완료가 아니라 보류 상태로 남깁니다.

실패 경로와 복구 경계

실패 경로는 검증 없이 본 테이블에 먼저 넣은 뒤 중복 오류를 만나는 경우입니다. 일부 행이 이미 커밋되어 재실행 시 이중 데이터가 생길 수 있으므로, 기존 데이터를 삭제해 맞추려 하지 말고 batch_id와 idempotency_key로 영향 범위를 조회한 뒤 보정 작업을 별도 기록합니다. 다음부터는 staging을 강제하고 본 테이블 쓰기를 확정 명령으로 제한합니다.

또 다른 경계는 파일이 너무 커서 처리 시간이 길어지는 경우입니다. 타임아웃에 맞춰 임의로 재시도하면 같은 chunk가 중복 실행될 수 있으므로, chunk offset과 처리 상태를 저장하고 워커가 재개하도록 합니다. 참조 데이터가 처리 중 바뀌면 스냅샷 버전이나 검증 시각을 기록해 어떤 기준으로 통과했는지 남기고, 기준이 바뀐 경우 전체 batch를 재검증할지 운영자가 결정합니다.

대사 보고서와 완료 조건

가져오기 완료는 ‘성공 메시지를 보냈다’가 아니라 원본·staging·본 테이블의 수량과 합계가 설명되는 상태입니다. batch 보고서에는 파일 해시, 총 행 수, valid·invalid·skipped 수, 확정된 레코드 수, 재조정 횟수, 마지막 처리 시각을 남깁니다. 숫자가 맞더라도 오류 행이 비어 있지 않으면 성공으로 표시하지 말고, 승인자가 오류 사유와 다음 조치를 확인한 뒤 상태를 닫습니다.

  1. 원본 파일 해시와 batch_id를 만들고 원본·스키마 버전을 보관한다.

  2. staging에서 문법·타입·참조·중복 검증을 순서대로 수행하고 행별 오류를 기록한다.

  3. 정책에 따라 all-or-nothing 또는 부분 성공을 승인받고 멱등 키로 확정한다.

  4. 원본 행 수와 staging·본 테이블 집계가 일치하는지 대사한 뒤 batch를 완료한다.

이 기록이 남아 있으면 같은 파일의 재업로드, 오류 행 수정, 참조 데이터 변경 같은 현실적인 사건도 데이터 손실 없이 설명할 수 있습니다. 완료 조건은 원본·staging·본 테이블의 행 수와 합계가 일치하는지 확인하고 batch 상태와 대사 결과를 기록하는 것입니다. CSV 형식이 바뀌거나 대량 처리 요구가 커질 때는 먼저 batch 계약과 재조정 증거가 유지되는지 확인하고, 그 다음에 전용 포맷이나 비동기 파이프라인을 검토하는 것이 안전합니다.