GridFS로 큰 파일 다루기

GridFS로 큰 파일 다루기

MongoDB에는 BSON 문서의 최대 크기 제한이 16 MiB라는 사실을 아시나요? 그보다 큰 파일을 저장해야 할 때가 있는데, 이때 사용하는 명세가 바로 GridFS예요. 파일을 여러 조각으로 나눠 저장하고 필요할 때 다시 조립해 주는 방식이에요.

출처: GridFS for Self-Managed Deployments (공식 문서)

본문

GridFS가 파일을 저장하는 방식

GridFS는 파일 하나를 단일 문서에 넣는 대신 파일을 부분(chunk)으로 나눠서 각 청크를 별도 문서로 저장해요. 기본 청크 크기는 255 KiB예요. 마지막 청크만 필요한 만큼만 크고, 청크 크기보다 작은 파일은 필요한 공간과 약간의 메타데이터만 쓰는 마지막 청크 하나만 가져요.

GridFS는 파일을 저장하는 데 두 컬렉션을 사용해요. 하나는 파일 청크를, 다른 하나는 파일 메타데이터를 저장해요.

GridFS로 파일을 쿼리하면 드라이버가 필요할 때 청크를 다시 조립해요. 저장된 파일에 범위 쿼리도 할 수 있고, 비디오나 오디오 파일의 중간으로 '건너뛰는' 것처럼 파일의 임의 구간 정보에 접근할 수도 있어요.

GridFS는 16 MiB를 넘는 파일뿐 아니라, 전체 파일을 메모리에 올리지 않고 접근하고 싶은 모든 파일에 유용해요.

언제 쓰면 좋을까요

GridFS는 16 MiB보다 큰 파일을 저장할 때 사용해요. 어떤 상황에서는 MongoDB가 시스템 파일시스템보다 더 효율적일 수 있어요.

  • 디렉토리당 파일 수에 제한이 있는 파일시스템이라면, GridFS로 필요한 만큼 파일을 저장할 수 있어요.
  • 큰 파일의 일부만 메모리에 올리지 않고 접근하고 싶을 때.
  • 파일과 메타데이터를 여러 시스템과 시설에 걸쳐 동기화하고 배포하고 싶을 때. 지리적으로 분산된 복제 세트를 쓰면 파일과 메타데이터를 여러 시설의 mongod 인스턴스에 배포할 수 있어요.

전체 파일의 내용을 원자적으로 업데이트해야 한다면 GridFS를 쓰지 마세요. 대안으로 각 파일의 여러 버전을 저장하고 현재 버전을 메타데이터로 지정할 수 있어요.

만약 모든 파일이 16 MiB BSON 문서 크기 제한보다 작다면, GridFS 대신 각 파일을 단일 문서에 BinData 타입으로 저장하는 방법을 고려해 보세요.

GridFS 컬렉션

GridFS는 파일을 두 컬렉션에 저장해요. 두 컬렉션은 공통 버킷에 배치되는데, 기본 버킷 이름은 fs예요.

  • fs.files
  • fs.chunks

버킷 이름은 바꿀 수 있고, 한 데이터베이스에 여러 버킷을 만들 수도 있어요. 버킷 이름을 포함한 전체 컬렉션 이름은 네임스페이스 길이 제한을 받아요.

chunks 컬렉션

chunks 컬렉션의 각 문서는 파일의 청크 하나를 나타내요. 문서 형태는 이래요.

{ "_id" : <ObjectId>, "files_id" : <ObjectId>, "n" : <num>, "data" : <binary> }
  • chunks._id — 청크의 고유 ObjectId예요.
  • chunks.files_idfiles 컬렉션에 지정된 '부모' 문서의 _id예요.
  • chunks.n — 청크의 시퀀스 번호예요. GridFS는 모든 청크에 0부터 번호를 매겨요.
  • chunks.data — BSON Binary 타입의 청크 페이로드예요.

files 컬렉션

files 컬렉션의 각 문서는 파일 하나를 나타내요.

{ "_id" : <ObjectId>, "length" : <num>, "chunkSize" : <num>, "uploadDate" : <timestamp>, "md5" : <hash>, "filename" : <string>, "contentType" : <string>, "aliases" : <string array>, "metadata" : <any> }

주요 필드 몇 가지를 짚어볼게요.

  • files.length — 문서의 바이트 단위 크기.
  • files.chunkSize — 각 청크의 바이트 단위 크기. 마지막 청크만 필요한 만큼만. 기본값은 255 KiB.
  • files.uploadDate — GridFS가 파일을 처음 저장한 날짜. Date 타입.
  • files.md5Deprecated. MD5 알고리즘은 FIPS 140-2에서 금지됐어요. 파일 다이제스트가 필요하면 GridFS 밖에서 구현하고 files.metadata에 저장하세요.
  • files.contentTypeDeprecated. MIME 타입 정보는 files.metadata에 저장하세요.
  • files.aliasesDeprecated. 별칭 정보는 files.metadata에 저장하세요.
  • files.metadata — 어떤 데이터 타입이든 담을 수 있는 메타데이터 필드.

GridFS 인덱스

GridFS 사양을 따르는 드라이버는 효율성을 위해 chunksfiles 컬렉션 각각에 인덱스를 자동으로 만들어요.

chunks 컬렉션에는 files_idn 필드로 고유 복합 인덱스를 만들어요. 청크를 효율적으로 조회할 수 있게 되죠.

db.fs.chunks.find({ files_id: myFileID }).sort({ n: 1 })

인덱스가 없다면 mongosh에서 직접 만들 수 있어요.

db.fs.chunks.createIndex({ files_id: 1, n: 1 }, { unique: true });

files 컬렉션에는 filenameuploadDate 필드로 인덱스를 만들어요.

db.fs.files.find({ filename: myFileName }).sort({ uploadDate: 1 })
db.fs.files.createIndex({ filename: 1, uploadDate: 1 });

GridFS 샤딩

두 컬렉션 모두 샤딩 대상이에요.

chunks 컬렉션을 샤딩하려면 { files_id: 1, n: 1 } 또는 { files_id: 1 }을 샤드 키 인덱스로 써요. files_id는 ObjectId라 단조 증가해요. filemd5를 실행하지 않는 드라이버라면 chunks 컬렉션에 해시 샤딩을 쓸 수 있지만, filemd5를 실행하는 드라이버는 쓸 수 없어요.

files 컬렉션은 작고 메타데이터만 담아요. 필수 키들이 샤딩 환경에서 고르게 분배되기 어려워서, files를 샤딩하지 않으면 모든 파일 메타데이터 문서가 한 샤드에 사는 게 좋아요. 꼭 샤딩해야 한다면 _id 필드를, 가능하면 애플리케이션 필드와 함께 사용하세요.

더 알아보기