Foundry
파일 업로드 시스템 설계
심화
핵심

메타데이터와 저장소 정합성

쓰기가 두 곳으로 갈라지면 사이에서 죽는다

직접 업로드로 올리면 쓰기가 두 곳으로 갈라집니다. 파일은 스토리지에, 목록에 보일 정보는 데이터베이스에 들어갑니다. 두 쓰기를 한 트랜잭션으로 묶을 수 없다는 것이 이 개념의 전부입니다.

파일 저장과 메타데이터 기록 사이의 틈에서 고아 객체와 깨진 참조가 생긴다 여기서 죽으면 스토리지에 저장 메타데이터 기록 고아 객체가 남는다 메타데이터 기록 스토리지에 저장 깨진 참조가 남는다 순서를 바꿔도

순서를 바꿔도 문제는 남는다

어느 쪽을 먼저 하든 사이에서 죽으면 어긋난 상태가 남습니다.

순서사이에서 죽으면사용자가 보는 것
파일 먼저고아 객체. 스토리지에만 있다아무 일도 없었다. 요금만 나간다
기록 먼저깨진 참조. 목록에만 있다목록에 보이는데 열면 실패한다

깨진 참조가 더 나쁩니다. 사용자가 파일이 있다고 믿게 만들기 때문입니다. 고아 객체는 사용자에게 보이지 않고 요금만 씁니다. 그래서 파일을 먼저 쓰는 편을 고릅니다.

중간 상태를 만든다

한 번에 못 하면 단계를 드러냅니다. 기록을 먼저 만들되 아직 완성이 아니라고 표시합니다.

1) 업로드 시작 시 메타데이터를 pending 으로 만든다
2) 클라이언트가 스토리지에 올린다
3) 완료 통보를 받으면 committed 로 바꾼다

목록은 committed 만 보여줍니다. 2번에서 죽으면 pending 레코드와 파일 조각이 남지만 사용자에게는 보이지 않습니다. 어긋난 상태가 사용자에게 새지 않는 것이 핵심입니다.

그래도 정리는 필요하다

pending 은 계속 쌓입니다. 일정 시간 지난 pending 을 찾아 스토리지를 확인하는 배치를 돕니다.

확인 결과조치
파일이 있다완료 통보가 유실된 것이다. committed 로 올린다
파일이 없다업로드가 중단된 것이다. pending 을 지운다

이 배치가 완료 통보 유실의 안전망이기도 합니다. 통보 경로만 믿으면 성공한 업로드가 영구히 pending 으로 남습니다.

요구사항이 이 선택을 허락한다

가용성을 우선한다고 했으므로, 방금 올린 파일이 목록에 몇 초 늦게 나타나는 것은 받아들일 수 있습니다. 결제나 재고라면 이 설계를 못 씁니다. 어긋난 상태를 잠시 허용하는 대신 쓰기를 막지 않는 것이고, 그 교환을 요구사항이 승인한 것입니다.

면접에서 이렇게 나옵니다

Q.직접 업로드에서 파일과 메타데이터를 어떻게 함께 저장하나요

함께 저장할 수 없습니다. 스토리지와 데이터베이스는 한 트랜잭션으로 묶이지 않습니다.

그래서 순서를 정하고, 사이에서 죽었을 때 남는 것을 감당할 수 있는 쪽을 고릅니다.

순서사이에서 죽으면심각도
파일 먼저고아 객체. 스토리지에만 있다사용자에게 안 보인다. 요금만
기록 먼저깨진 참조. 목록에만 있다목록에 보이는데 열면 실패

깨진 참조가 더 나쁘므로 파일을 먼저 쓰는 쪽을 고릅니다. 그다음 pending 과 committed 같은 중간 상태로 사용자에게 새지 않게 막습니다.

흔한 실수: 두 쓰기를 트랜잭션으로 묶겠다고 답하는 것. 스토리지는 그 트랜잭션에 참여하지 않습니다. 분산 트랜잭션을 붙이는 방법이 있지만 가용성을 크게 깎아서, 파일 업로드에서는 거의 쓰지 않습니다.

Q.고아 객체와 깨진 참조는 각각 어떻게 생기나요

두 쓰기 사이의 에서 생깁니다. 어느 쪽을 먼저 했느냐가 어느 쪽이 남는지를 정합니다.

파일 저장 성공 -> (여기서 죽음) -> 메타데이터 기록
  결과: 스토리지에 파일이 있는데 아무도 가리키지 않는다. 고아 객체

메타데이터 기록 성공 -> (여기서 죽음) -> 파일 저장
  결과: 목록에 있는데 실제 파일이 없다. 깨진 참조

고아 객체는 조용합니다. 사용자에게 보이지 않고 저장 요금만 씁니다. 그래서 배치로 찾아 지웁니다.

깨진 참조는 사용자가 만납니다. 목록에서 클릭하면 실패하고, 그것이 서비스 신뢰를 깎습니다.

흔한 실수: 둘을 같은 심각도로 다루는 것. 조치 우선순위가 다릅니다. 깨진 참조는 애초에 생기지 않게 순서와 중간 상태로 막고, 고아 객체는 생기는 것을 받아들이고 정리로 해결합니다.

Q.pending 과 committed 같은 중간 상태를 왜 두나요

어긋난 상태를 사용자에게 새지 않게 막기 위해서입니다.

1) 업로드 시작 시 메타데이터를 pending 으로 만든다
2) 클라이언트가 스토리지에 올린다
3) 완료 통보를 받으면 committed 로 바꾼다

목록 조회는 committed 만 봅니다. 2번에서 죽으면 pending 레코드와 파일 조각이 남지만 사용자 화면에는 나타나지 않습니다.

이 구조가 주는 것이 하나 더 있습니다. pending 레코드가 업로드가 시작됐다는 기록이라서, 나중에 정리 배치가 무엇을 확인해야 하는지 알 수 있습니다. 기록이 없으면 스토리지 전체를 훑어야 합니다.

흔한 실수: 중간 상태를 두었으니 정리가 필요 없다고 답하는 것. pending 은 계속 쌓입니다. 일정 시간 지난 pending 을 찾아 스토리지를 확인하고, 파일이 있으면 committed 로 올리고 없으면 지우는 배치가 필요합니다.

Q.완료 통보가 유실되면 어떻게 복구되나요

정리 배치가 안전망입니다. 통보 경로만 믿으면 성공한 업로드가 영구히 pending 으로 남습니다.

일정 시간 지난 pending 을 찾아 스토리지에 실제로 파일이 있는지 확인합니다.

확인 결과판정조치
파일이 있다업로드는 성공했고 통보만 유실됐다committed 로 올린다
파일이 없다업로드가 중단됐다pending 을 지운다

같은 배치가 두 가지 문제를 함께 처리합니다. 통보 유실 복구와 고아 레코드 정리입니다.

이 배치가 있으면 통보 경로를 완벽하게 만들 필요가 없어집니다. 클라이언트 통보와 스토리지 이벤트 둘 다 유실될 수 있다고 전제하고, 최종적으로 맞춰지는 경로를 하나 두는 편이 단순합니다.

흔한 실수: 통보를 재시도로 보장하려는 것. 통보를 보내는 쪽이 죽으면 재시도할 주체가 없습니다. 안전망은 상태를 보고 스스로 판단하는 쪽이어야 합니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

파일 업로드 시스템 설계 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.