EBS 외부 API 연동 및 대용량 데이터 동기화 시스템 구축, 2023.10 ~ 2023.12 설계와 구현
단계를 나누고 원본을 보존해 외부 장애에도 재처리 가능한 동기화 구조 설계
외부 호출, 검증, 내부 반영을 단계별 트랜잭션으로 나누고(3단계는 1만 건 묶음마다 커밋) 외부 응답 원본을 남겨, 실패한 단계만 외부를 다시 부르지 않고 다시 실행하도록 설계
문제와 배경
- 교육과정이 바뀌면 사용자가 이전 커리큘럼 기반 콘텐츠를 보게 되는 문제가 있었습니다.
- 별도 배치 서버와 메시지 브로커 없이, 외부 콘텐츠 10만+ 건을 다시 처리할 수 있게 반영하는 파이프라인이 필요했습니다.
- EBS는 콘텐츠마다 생성, 수정, 삭제 지시를 API로 내려 줬습니다. 연동은 EBS가 제공한 API 명세서를 따랐습니다.
결과
- 외부 호출, 검증, 내부 반영을 단계별 트랜잭션으로 나눠(3단계는 1만 건 묶음마다 커밋), 실패한 단계만 다시 실행하고 2~3단계는 외부 재호출 없이 보존한 원본으로 재처리할 수 있게 했습니다.
- 교육과정 개편 2회와 EBS API 버전 변경 1회에도 같은 동기화 구조로 외부 콘텐츠 10만+ 건을 반영했습니다. 그 결과 현행 콘텐츠를 제공하고 이전 커리큘럼 노출 위험을 줄였습니다.
- 정합성 검증으로 원천 데이터 오류를 검출해 EBS 측 원본 보정 3~4회로 연결했습니다.
설계와 구현
단계별 트랜잭션 분리
- 공통 AOP 설정이 서비스 메서드마다
REQUIRED트랜잭션을 걸었습니다. 컨트롤러에는 트랜잭션이 없고, 단계별 실행 API가 서비스 메서드를 각각 호출하도록 나눴습니다. 그래서 단계마다 트랜잭션이 따로 커밋되고, 뒤 단계가 실패해도 앞 단계의 원본 적재와 검증 결과는 남습니다.
| 단계 | 커밋 단위 | 실패한 뒤 같은 파라미터로 다시 실행하면 |
|---|---|---|
| 1단계 원천 적재 | 실행 API 호출마다 | 정상 호출만 있으면 다시 부르지 않고, 오류가 섞이면 그 범위를 모두 다시 받음 |
| 2단계 검증 | 실행 API 호출마다 | 보존한 원본으로 다시 검증. EBS를 다시 부르지 않음 |
| 3단계 내부 반영 | 1만 건 묶음마다 | 반영 여부가 N인 행만 처리. EBS를 다시 부르지 않음 |
- 묶음 안의 오류는 종류에 따라 결과가 달랐습니다. Java 쪽 오류(JSON 변환 등)는 오류 전 행까지 커밋되고 오류 행만 표시됩니다(코드 기준). DB 오류는 PostgreSQL이 이후 SQL을 모두 거부해 묶음 전체가 오류 표시와 함께 롤백됩니다(재현 환경에서 확인).
- 청크별 진행 상태 추적 대신 실패한 실행 API를 같은 파라미터로 다시 실행해, 추적 구조를 단순하게 두었습니다. 다시 실행하는 범위가 넓어지는 비용은 감수했습니다.
원본 보존과 1단계 재호출 판단
- EBS API를 한 번 호출할 때마다 호출 기록 한 행을 남기고, 그 호출로 받은 원본 행을 호출 기록 번호로 연결했습니다.
- 호출 기록에는 요청 파라미터(대상 테이블, 작업 지시, 날짜), 처리 상태, 오류 메시지를 남겼습니다. 응답 원본은 원본 테이블에 JSON 그대로 보존했습니다. 그래서 2~3단계가 실패해도 EBS를 다시 부르지 않고 다시 처리합니다.
- 1단계를 다시 실행하면 EBS를 부르기 전에 같은 파라미터(대상 테이블, 작업 지시, 날짜)의 이전 호출 기록을 조회합니다.
- 기록이 없으면 호출해 적재합니다.
- 오류가 난 호출이 섞여 있으면 같은 파라미터의 호출 기록과 원본 행을 모두 무효로 표시한 뒤 범위 전체를 다시 호출합니다. 데이터 자체의 오류는 EBS가 해당 API에서 보정해 두었기 때문에, 같은 파라미터로 그대로 다시 호출했습니다.
- 정상 호출만 있으면 이미 등록된 파라미터로 보고 다시 부르지 않습니다.
2단계 검증 규칙
작업 지시마다 내부 테이블을 기본 키로 조회해 정합성 플래그를 정했습니다.
| 내부 데이터 상태 | 생성 지시 | 수정·삭제 지시 |
|---|---|---|
| 있음 | 작업 지시를 수정으로 바꾸고 통과 | 통과 |
| 없음 | 통과 | 보류 |
| 있지만 삭제 표시됨 | 보류 | 보류 |
- 이미 있는 키로 온 생성 지시는 실패로 처리하지 않고 수정 지시로 바꿔, 같은 생성 지시가 다시 와도 결과가 같게 했습니다. 원본 JSON은 그대로 두고 작업 행의 지시 컬럼만 바꿨습니다.
- 검증에 실패한 건도 원본과 함께 보관해 보정이나 내부 수정으로 이어지게 했습니다.
3단계 반영 방식
보존한 원본 JSON을 Jackson으로 VO로 바꾼 뒤, 작업 지시와 검증 결과에 따라 반영 구문을 나눴습니다.
| 작업 지시 | 통과 | 보류 |
|---|---|---|
| 생성 | INSERT … ON CONFLICT … DO UPDATE (Upsert) | 통과와 같이 삭제 표시를 푼 상태로 Upsert |
| 수정 | UPDATE | 대상이 없거나 삭제 표시된 행이므로 삭제 표시를 푼 상태로 Upsert |
| 삭제 | 행을 지우지 않고 삭제 표시만 바꿈 (논리 삭제) | 삭제 표시된 행으로 Upsert |
- Upsert와 UPDATE에는 값이 달라졌는지 비교하는 조건이 없습니다. 그래서 같은 범위를 다시 반영하면 값이 같은 행도 다시 쓰이고 동기화 시각이 갱신됩니다. 결과 데이터는 같지만, 다시 반영한 흔적은 남는 구조입니다.
오픈 후 판별 쿼리로 검증
- 오픈 후 판별 쿼리로 EBS 제공 건수, 기존 내부 건수, 실제 연동 건수를 대조했고, 교육과정별 문항 수와 단원 수 일치를 통과 기준으로 삼았습니다.
- 불일치는 바로 EBS에 요청하지 않고 내부 원인부터 확인했습니다. 원본 오류면 EBS에 보정을 요청했고, 아니면 연동 뒤 만든 뷰의 쿼리나 내부 로직을 고쳤습니다.