Статьи

Вкус GridFS, файловых систем MongoDB

В некоторых предыдущих статьях о mongodb, python и pymongo я представил базу данных NoSQL MongoDB и ее использование в Python. Этот пост выходит за рамки основ MongoDB и pymongo, чтобы дать вам представление о взгляде MongoDB на файловые системы GridFS .

Почему файловая система?

Если вы некоторое время работали с MongoDB, возможно, вы слышали об ограничении размера документа в 16 МБ. Когда я начал использовать MongoDB (около версии 0.8), предел был на самом деле 4 МБ. Что это означает, что все работает нормально, ваша система кричит быстро, пока вы пытаетесь создать документ , что это 4.001 MB и бум ничего не работает больше. Для нас в SourceForge это означало, что мы должны были реструктурировать нашу схему и использовать меньше вложений.

Но что, если это не то, что можно реструктурировать? Возможно, ваш сайт позволяет пользователям загружать большие вложения неизвестного размера. В таких случаях вы, вероятно, можете избежать использования поля типа Binary и скрестить пальцы, но, на мой взгляд, лучшим решением является сохранение содержимого, которое вы загружаете, в серии документов (назовем их «кусками») ограниченный размер. Затем вы можете связать их все вместе с другим документом, в котором указаны все метаданные файла.

GridFS на помощь

Что ж, это именно то, что делает GridFS, но делает это с хорошим API с несколькими дополнительными возможностями, чем вы, вероятно, построите самостоятельно. Важно отметить, что GridFS, реализованная во всех языковых драйверах MongoDB, является соглашением и API , а не тем, что изначально предоставляется сервером. Что касается сервера, то это всего лишь коллекции и документы.

Схема GridFS

GridFS на самом деле хранит ваши файлы в двух коллекциях, названных по умолчанию fs.files и fs.chunks, хотя вы можете изменить fs на что-то другое, если хотите. В коллекции fs.files начинается чтение или запись файла. Типичный документ fs.files выглядит следующим образом ( кредит ):

{
  // unique ID for this file
  "_id" : <unspecified>,
  // size of the file in bytes
  "length" : data_number,
  // size of each of the chunks.  Default is 256k
  "chunkSize" : data_number,
  // date when object first stored
  "uploadDate" : data_date,
  // result of running the "filemd5" command on this file's chunks
  "md5" : data_string
}

Коллекция fs.chunks содержит все данные для ваших файлов:

{
  // object id of the chunk in the _chunks collection
  "_id" : <unspecified>,
  // _id of the corresponding files collection entry
  "files_id" : <unspecified>,
  // chunks are numbered in order, starting with 0
  "n" : chunk_number,
  // the chunk's payload as a BSON binary type
  "data" : data_binary,
}

В пакете Python gridfs (входит в состав драйвера pymongo) также добавлено несколько других полей:


имя файла

Это «человеческое» имя файла, которое может быть разделено путем для имитации каталогов.

Тип содержимого

Это MIME-тип файла

кодирование

Это кодировка Unicode, используемая для текстовых файлов.

Вы также можете добавить свои собственные атрибуты к файлам. В SourceForge мы использовали такие вещи, как project_id или forum_id, чтобы разрешить загрузку одного и того же имени файла в несколько мест на сайте, не беспокоясь о коллизиях пространства имен. Чтобы сохранить ваш код в будущем, вы должны поместить любые пользовательские атрибуты во встроенный документ метаданных на случай, если спецификация gridfs расширится и включит больше полей.

Использование GridFS

Итак, со всем этим, как вам на самом деле использовать GridFS? Это на самом деле довольно просто. Первое, что вам нужно, это ссылка на файловую систему GridFS:

>>> import pymongo
>>> import gridfs
>>> conn = pymongo.Connection()
>>> db = conn.gridfs_test
>>> fs = gridfs.GridFS(db)

Основное чтение и письмо

Когда у вас есть файловая система, вы можете начинать помещать в нее вещи:

>>> with fs.new_file() as fp:
...     fp.write('This is my new file. It is teh awezum!')

Давайте рассмотрим основные коллекции, чтобы увидеть, что на самом деле произошло

>>> list(db.fs.files.find())
[{u'length': 38,
  u'_id': ObjectId('4fbfa7b9fb72f096bd000000'),
  u'uploadDate': datetime.datetime(2012, 5, 25, 15, 39, 37, 55000),
  u'md5': u'332de5ca08b73218a8777da69293576a',
  u'chunkSize': 262144}]
>>> list(db.fs.chunks.find())
[{u'files_id': ObjectId('4fbfa7b9fb72f096bd000000'),
  u'_id': ObjectId('4fbfa7b9fb72f096bd000001'),
  u'data': Binary('This is my new file. It is teh awezum!', 0),
  u'n': 0}]

Вы можете видеть, что там действительно нет ничего удивительного или таинственного; это просто отображение метафоры файловой системы на документы MongoDB. В этом случае наш файл был достаточно маленьким, чтобы его не нужно было разбивать на куски. Мы можем принудительно разделить его, указав небольшой chunkSize при создании файла:

>>> with fs.new_file(chunkSize=10) as fp:
...     fp.write('This is file number 2. It should be split into several chunks')
...
>>> fp
<gridfs.grid_file.GridIn object at 0x1010f5950>
>>> fp._id
ObjectId('4fbfa8ddfb72f0971c000000')
>>> list(db.fs.chunks.find(dict(files_id=fp._id)))
[{... u'data': Binary('This is fi', 0), u'n': 0},
 {... u'data': Binary('le number ', 0), u'n': 1},
 {... u'data': Binary('2. It shou', 0), u'n': 2},
 {... u'data': Binary('ld be spli', 0), u'n': 3},
 {... u'data': Binary('t into sev', 0), u'n': 4},
 {... u'data': Binary('eral chunk', 0), u'n': 5},
 {... u'data': Binary('s', 0), u'n': 6}]

Теперь, если мы действительно хотим прочитать файл как файл , нам нужно использовать api gridfs:

>>> with fs.get(fp._id) as fp_read:
...     print fp_read.read()
...
This is file number 2. It should be split into several chunks

Рассматривая это больше как файловую систему

Есть несколько других удобных методов, связанных с объектом GridFS, чтобы дать больше поведения, похожего на файловую систему. Например, new_file () принимает любое количество аргументов ключевого слова, которые будут добавлены в создаваемый документ fs.files:

>>> with fs.new_file(
...     filename='file.txt', 
...     content_type='text/plain', 
...     my_other_attribute=42) as fp:
...     fp.write('New file')
...
>>> fp
<gridfs.grid_file.GridIn object at 0x1010f59d0>
>>> db.fs.files.find_one(dict(_id=fp._id))
{u'contentType': u'text/plain',
 u'chunkSize': 262144,
 u'my_other_attribute': 42,
 u'filename': u'file.txt',
 u'length': 8,
 u'uploadDate': datetime.datetime(2012, 5, 25, 15, 53, 1, 973000),
 u'_id': ObjectId('4fbfaaddfb72f0971c000008'), u'md5':
 u'681e10aecbafd7dd385fa51798ca0fd6'}

Лучше было бы инкапсулировать my_other_attribute в свойство метаданных:

>>> with fs.new_file(
...     filename='file2.txt', 
...     content_type='text/plain', 
...     metadata=dict(my_other_attribute=42)) as fp:
...     fp.write('New file 2')
...
>>> db.fs.files.find_one(dict(_id=fp._id))
{u'contentType': u'text/plain',
 u'chunkSize': 262144,
 u'metadata': {u'my_other_attribute': 42},
 u'filename': u'file2.txt',
 u'length': 10,
 u'uploadDate': datetime.datetime(2012, 5, 25, 15, 54, 5, 67000),
 u'_id':ObjectId('4fbfab1dfb72f0971c00000a'),
 u'md5': u'9e4eea3dec28d8346b52f18086437ac7'}

Мы также можем «перезаписать» файлы по имени файла, но поскольку GridFS фактически индексирует файлы по _id, он не избавляется от старого файла, он просто делает его версию :

>>> with fs.new_file(filename='file.txt', content_type='text/plain') as fp:
...     fp.write('Overwrite the so-called "New file"')
...

Теперь, если мы хотим получить файл по имени файла, мы можем использовать get_version или get_last_version:

>>> fs.get_last_version('file.txt').read()
'Overwrite the so-called "New file"'
>>> fs.get_version('file.txt', 0).read()
'New file'

Так как мы загружаем файлы со свойством filename, мы также можем перечислить файлы в gridfs:

>>> fs.list()
[u'file.txt', u'file2.txt']

Мы также можем удалить файлы, конечно:

>>> fp = fs.get_last_version('file.txt')
>>> fs.delete(fp._id)
>>> fs.list()
[u'file.txt', u'file2.txt']
>>> fs.get_last_version('file.txt').read()
'New file'

Обратите внимание, что, поскольку была удалена только одна версия «file.txt», у нас все еще есть файл с именем «file.txt» в файловой системе.

Наконец, gridfs также предоставляет удобные методы для определения, существует ли файл и для быстрой записи короткого файла в grif:

>>> fs.exists(fp._id)
False
>>> fs.exists(filename='file.txt')
True
>>> fs.exists({'filename': 'file.txt'}) # equivalent to above
True
>>> fs.put('The quick brown fox', filename='typingtest.txt')
ObjectId('4fbfad74fb72f0971c00000e')
>>> fs.get_last_version('typingtest.txt').read()
'The quick brown fox'

Так что это вихрь тур по GridFS. Мне бы очень хотелось услышать, как вы используете GridFS в своем проекте, или, если вы считаете, что он подходит, так что, пожалуйста, напишите мне в комментариях.