홈 › 실무 배치 › 04 / 6

트랜잭션: 커밋과 롤백 시뮬레이션

섹션 7진행 0 / 6

5. 자주 하는 실수 (Tip)

❌ 실수 1: 완료 마킹을 별도 트랜잭션에서 한다

java
db.begin(); transfer(t); db.commit();          // 잔액 변경 커밋
db.begin(); markDone(t); db.commit();          // 마킹 커밋 ← 사이에 죽으면?

첫 커밋 후 죽으면 잔액은 바뀌었는데 마킹이 없어 재시작 시 또 이체됩니다. 반대 순서면 마킹만 있고 이체는 안 된 채 영원히 건너뜁니다.

✅ 데이터 변경과 마킹을 같은 트랜잭션에 넣습니다.

java
db.begin();
transfer(t);
db.put("done:" + t.txId(), 1L);
db.commit();                                   // 둘 다 반영되거나 둘 다 취소

❌ 실수 2: 체크포인트를 커밋 전에 저장한다

java
checkpoint.save(lineNo);   // 먼저 저장
db.commit();               // 여기서 죽으면 체크포인트는 앞서 있고 데이터는 없음 → 청크 유실

✅ 커밋 직후에 저장합니다. 그래도 창이 남으므로 멱등성을 갖춥니다. 같은 DB 라면 체크포인트 테이블 UPDATE 를 같은 트랜잭션에 넣어 창을 없앱니다.

❌ 실수 3: 전체를 하나의 트랜잭션으로

java
conn.setAutoCommit(false);
for (Row r : million) update(r);
conn.commit();             // 100만 건 뒤 한 번

undo 로그가 수 GB, 배치 내내 행 락 유지로 온라인 서비스가 멈춤, 마지막 한 건 실패로 전부 롤백, 재시작은 처음부터. "커밋을 줄이면 빠르다"는 말은 청크 수준까지만 맞습니다.

✅ 청크(100~1000)마다 커밋합니다.

❌ 실수 4: 재시작인데 결과 파일에 append

java
Files.newBufferedWriter(report, APPEND);   // 재시작하면 앞 실행분 + 이번 실행분이 겹쳐 씀

첫 실행이 300줄 쓰고 죽었고 재시작이 처음부터 다시 쓰면 파일에 600줄. 앞 300줄은 중복입니다.

✅ 재생성(임시 파일 → 이동) 하거나, 체크포인트에 "몇 줄까지 썼는지" 기록하고 재시작 시 그 지점으로 truncate 후 append.

❌ 실수 5: 외부 API 호출 후 DB 커밋 실패를 고려하지 않음

java
db.begin();
paymentApi.charge(order);   // 외부 결제 성공 (되돌릴 수 없음)
db.update(order);           // 여기서 실패 → rollback → DB 에는 미결제, 실제로는 결제됨
db.commit();

✅ 되돌릴 수 있는 것을 먼저, 없는 것을 마지막에. API 에는 멱등 키를 붙여 재시작 시 중복 결제를 서버가 거부하게 하고, 실패 시 보상(취소 API)을 준비합니다.

java
db.begin();
db.update(order, PENDING);
db.commit();
paymentApi.charge(order, idempotencyKey = order.id());   // 재실행 시 서버가 같은 키를 거부
db.begin(); db.update(order, PAID); db.commit();

❌ 실수 6: rollback 후 같은 커넥션 상태를 정리하지 않음

java
try { ... } catch (Exception e) { log(e); }   // rollback 없이 다음 청크 begin

시뮬레이션에서는 begin() 이 "이미 트랜잭션 진행 중"으로 막아 주지만, 실제 JDBC 는 막지 않습니다. 실패한 청크의 절반이 다음 청크와 함께 커밋됩니다.

✅ 예외 시 반드시 rollback(). finally 에서 inTransaction() 이면 롤백하는 안전망을 둡니다.