직접 업로드로 올리면 쓰기가 두 곳으로 갈라집니다. 파일은 스토리지에, 목록에 보일 정보는 데이터베이스에 들어갑니다. 두 쓰기를 한 트랜잭션으로 묶을 수 없다는 것이 이 개념의 전부입니다.
순서를 바꿔도 문제는 남는다
어느 쪽을 먼저 하든 사이에서 죽으면 어긋난 상태가 남습니다.
| 순서 | 사이에서 죽으면 | 사용자가 보는 것 |
|---|---|---|
| 파일 먼저 | 고아 객체. 스토리지에만 있다 | 아무 일도 없었다. 요금만 나간다 |
| 기록 먼저 | 깨진 참조. 목록에만 있다 | 목록에 보이는데 열면 실패한다 |
깨진 참조가 더 나쁩니다. 사용자가 파일이 있다고 믿게 만들기 때문입니다. 고아 객체는 사용자에게 보이지 않고 요금만 씁니다. 그래서 파일을 먼저 쓰는 편을 고릅니다.
중간 상태를 만든다
한 번에 못 하면 단계를 드러냅니다. 기록을 먼저 만들되 아직 완성이 아니라고 표시합니다.
1) 업로드 시작 시 메타데이터를 pending 으로 만든다
2) 클라이언트가 스토리지에 올린다
3) 완료 통보를 받으면 committed 로 바꾼다
목록은 committed 만 보여줍니다. 2번에서 죽으면 pending 레코드와 파일 조각이 남지만 사용자에게는 보이지 않습니다. 어긋난 상태가 사용자에게 새지 않는 것이 핵심입니다.
그래도 정리는 필요하다
pending 은 계속 쌓입니다. 일정 시간 지난 pending 을 찾아 스토리지를 확인하는 배치를 돕니다.
| 확인 결과 | 조치 |
|---|---|
| 파일이 있다 | 완료 통보가 유실된 것이다. committed 로 올린다 |
| 파일이 없다 | 업로드가 중단된 것이다. pending 을 지운다 |
이 배치가 완료 통보 유실의 안전망이기도 합니다. 통보 경로만 믿으면 성공한 업로드가 영구히 pending 으로 남습니다.
요구사항이 이 선택을 허락한다
가용성을 우선한다고 했으므로, 방금 올린 파일이 목록에 몇 초 늦게 나타나는 것은 받아들일 수 있습니다. 결제나 재고라면 이 설계를 못 씁니다. 어긋난 상태를 잠시 허용하는 대신 쓰기를 막지 않는 것이고, 그 교환을 요구사항이 승인한 것입니다.