KYC 증빙 서류를 저장할 때 이름이 겹치는지만 보고 겹치면 이름 뒤에 (1) 을 붙여서 넣었다. 이름이 안 겹치는데도 같은 파일이 여러 벌씩 쌓이는 문제가 생겼다.
Table of contents
Open Table of contents
증상 — 이름이 안 겹쳐도 겹치는 내용
신분증_v2.jpg 와 신분증_최종.jpg 처럼 버전이 붙은 이름과 최종이라는 이름으로 같은 서류가 여러 번 올라오고 있었다. 이름이 다르니 충돌 검사에 안 걸리고 전부 별개 파일로 들어갔다.
이름은 회원이 정하는 값이고 같은 내용에 다른 이름을 붙이는 것을 막을 방법이 없다. 이름의 충돌과 내용의 중복은 서로 다른 문제였다.
조치 — 내용 해시와 참조 분리
MessageDigest 로 SHA-256 해시를 계산해서 같은 것이 이미 있으면 새로 넣지 않고 기존 것을 가리키게 했다. 처음 돌렸을 때 4만 개 중 8천 개가 같은 내용이었다.
두 가지 방법이 있었다. 같은 해시를 전부 하나로 합치는 것과, 실체 file_blobs 는 하나만 두고 회원별 참조 file_refs 를 여럿 두는 것이다. 뒤쪽을 골랐다. 이유는 서로 다른 회원이 같은 서류를 올린 자리에서 한쪽이 지우면 다른 쪽 서류가 사라져서다. 대신 마지막 참조가 사라질 때 실체를 지우는 참조 계수를 관리해야 하는 것을 감수했다.
주의 — 확장자와 실제 내용의 불일치
내용 말고도 크기와 형식과 안전성을 봐야 했다. 특히 확장자가 pdf 인데 앞부분을 읽어 보면 MZ 로 시작하는 실행 파일인 것이 섞여 있었다.
파일 앞부분의 서명을 읽어 확장자와 맞는지 비교하니 그런 것이 걸렸다. 이름이 무엇을 주장하는지와 내용이 무엇인지는 별개이므로 이름만 믿을 수 없었다.
판단 기준 — 싼 검사부터 두는 순서
검사를 크기와 확장자와 서명과 해시 중복 순서로 배치했다. 앞의 둘은 Content-Length 와 이름만 보면 되고 서명은 앞 몇 바이트만 읽으면 되지만 중복 판정은 파일 전체를 읽어야 한다.
싼 것에서 걸리면 비싼 것을 하지 않아도 되므로 큰 파일에서 시간이 크게 갈렸다. 검사 순서를 비용순으로 두는 것만으로 전체 처리 시간이 눈에 띄게 줄었다.
옮겼다는 것과 저장됐다는 것은 다르다
Files.move() 하고 나서 실제로 존재하는지와 Files.size() 가 기대값과 맞는지를 다시 확인하게 했다. 디스크가 찼을 때 예외가 나지 않고 파일이 잘린 채로 옮겨진 적이 있었기 때문이다.
실패했을 때 임시 파일이 남아 며칠 만에 /tmp 가 차는 문제도 있었다. 예외 처리에서 지우게 하되 프로세스가 죽으면 그것도 안 돌므로 find /tmp -mtime +1 로 오래된 임시 파일을 지우는 정리를 따로 뒀다.
저장 이름은 해시로 정하고 회원이 올린 이름은 original_name 에 따로 담았다. 저장 이름을 우리가 정하니 경로 문자나 운영체제가 못 다루는 글자나 지나치게 긴 이름이 들어와도 문제가 되지 않았다.
정리
- 이름의 충돌과 내용의 중복은 서로 다른 문제다
SHA-256해시로 보면 같은 파일이 여러 이름으로 들어온 것이 드러난다- 소유가 다르면
file_blobs하나에file_refs여럿으로 둔다 - 마지막 참조가 사라질 때 실체를 지운다
- 확장자가 주장하는 형식과 실제 서명이 다를 수 있다
- 검사는 싼 것부터 두고 전체를 읽는 것을 마지막에 둔다
- 옮긴 뒤 존재와
Files.size()를 확인한다 - 저장 이름을 우리가 정하면 이름에 든 것 때문에 나던 문제가 사라진다