В интернетах бытует мнение, что хранить файлы в БД — школоло. Разрушаем стереотип с Mongo GridFS.
Введение:
MongoDB предоставляет возможность хранить файлы любого размера в Mongo GridFS
Для драйвера PHP — это обычная коллекция, в которой есть поле для хранения бинарных данных.
Бинарные данные в свою очередь разбиваются на чунки(небольшие куски).
Бинарные данные можно извлечь как целиком, так и почунково(это экономит память).
Плюсы хранения данных в БД MongoDB:
MongoDB может хранить миллионы файлов в БД, хранение такого объёма данных в файловой системе может вызвать проблемы(начиная с IO проблем, заканчивая проблемами доступа к данным из разных серверов).
MongoDB реплицирует данные на все сервера, что упрощает доступ к данным на отдельно взятом сервере.
Нет проблем с бекапами и целостностью данных на разных серверах. надёжность обеспечивается MongoDB.
К загруженному файлу можно прикрепить метаданные (описание, комментарии, лайки, и.т.д.)
Т.к. файлы хранятся в чунках — можно работать с любой частью файла.
Минусы:
Быстродействие ниже, чем nginx + файловая истема. Сравним:
nginx + файловая система, получаем 6559.31 операций в секунду
apache + файловая система: получаем 2625.37 операций в секунду
nginx + модуль nginx-gridfs, получаем 1083.88 операций в секунду
Вывод: nginx + fs на высоте, но это решение не даёт масштабируемости и гибкости.
nginx + nginx-gridfs даёт приличные результаты, которые существенно возрастут, при использовании нескольких серверов MongoDB.
Благодарность, http://tokarchuk.ru за тесты.
решение:
location / ( error_page 404 @fallback; ) location @fallback ( proxy_pass http://backend; )
В двух словах: эта штуковина позволит на слабом серваке иметь отдачу под 200 страниц в секунду, если правильно настроить бэкенд (кешировать, временно статические страницы в файлуху(ramfs)).
subj лечится добавлением в конфиг нгинкса пары строчек в http секцию:
proxy_buffers 8 16k;
proxy_buffer_size 32k;
thanks to: http://phpsuxx.blogspot.com/2009/11/upstream-sent-too-big-header-while.html
Тестируем WordPress сайт утилитой ab:
ab -c 20 -n 200 http://lenta.iadlab.ru/
Что эквивалентно 20 запросам в секунду. Общее число запросов 200. Перед Apache стоит nginx, который принимает запросы и проксирует на apache в очередь.
Принцип работы XCache: предварительно компилируем PHP файлы в объектный код и сохраняем в памяти. При выполнении запроса линкуем нужные файлы из памяти и выполняем запрос.
Результаты без XCache:
Concurrency Level: 20 Time taken for tests: 51.226 seconds Complete requests: 200 Failed requests: 0 Write errors: 0 Total transferred: 10737600 bytes HTML transferred: 10688600 bytes Requests per second: 3.90 [#/sec] (mean) Time per request: 5122.616 [ms] (mean) Time per request: 256.131 [ms] (mean, across all concurrent requests) Transfer rate: 204.70 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 1 Processing: 473 4935 1205.1 4900 10384 Waiting: 272 4544 1024.4 4588 9127 Total: 473 4935 1205.1 4900 10384
Результаты с XCache:
Concurrency Level: 20 Time taken for tests: 35.107 seconds Complete requests: 200 Failed requests: 0 Write errors: 0 Total transferred: 10737600 bytes HTML transferred: 10688600 bytes Requests per second: 5.70 [#/sec] (mean) Time per request: 3510.692 [ms] (mean) Time per request: 175.535 [ms] (mean, across all concurrent requests) Transfer rate: 298.69 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.6 0 3 Processing: 418 3366 791.7 3310 6196 Waiting: 324 3015 625.8 3056 4991 Total: 421 3366 791.4 3310 6196
Вывод: время выполнения упало ~ вдвое.
В продолжение решения задачи: Реляционка или не реляционка
Пройдя первый этап: MySQL vs MongoDB. Сравнение скорости insert Создания пользователей.
Пройдя второй этап:MySQL vs MongoDB. Более сложный insert (для mongodb) Создания друзей.
Пройдя третий этап: MySQL vs MongoDB. Более сложный insert (для mongodb) часть 2. Создания фотографий у каждого пользователя
Теперь пометим у каждого френда (100 френдов у каждого из 1000 пользователей) по 50 фотографий из 100 просмотренными. Получается 1000*100*50=5000000 (пять миллионов записей):
Для MySQL это вставка в таблицу who_view_photo записи с id_photo и id_user просмотревшем его.

Для MongoDB это изменение существующих объектов в коллекции пользователей. Причём мы меняем 3-й уровень объекта в глубину:
Посмотрим насколько шустро отработает наш MongoDB. Ему уже приходится в каждом объекте содержать информацию о 100 друзьях и 100 фотографиях (1000 записей).
По оси Х-время, по оси Y-количество случаев с данным временем. На примере вставки первых 50000 элементов (просмотров фотографий):
Вывод: Объекты MongoDB разжирнев, обрабатываются дольше, чем банальная вставка значения в таблицу MySQL. Но даже содержа 1000 значений в объекте, выполняя фактически операцию update к объекту (меняя его содержимое) мы получаем все-лишь двукратное замедление, что радует.
В продолжение решения задачи: Реляционка или не реляционка
Пройдя первый этап: MySQL vs MongoDB. Сравнение скорости insert Создания пользователей.
Пройдя второй этап:MySQL vs MongoDB. Более сложный insert (для mongodb) Создания друзей.
Создадим фотографии пользователей. Каждому пользователю по 100 фотографий.
Для MySQL это обычное создание записи в таблице:
Для NoSQL это добавление в коллекцию пользователей из MySQL vs MongoDB. Более сложный insert (для mongodb) ещё одного поля photos, являющегося множеством элементов.
И сравниваем производительность выполнения вставки 100 фотографий каждому из 1000 пользователей (100000 элементов).
По оси Х-время в секундах, по оси Y-количество случаев с данным временем выполнения(основной кусок увеличен):
Вывод: нужно понимать, что для MySQL — создание фотографии лишь вставка новой записи в таблицу фотографий, а для MongoDB — изменение существующего объекта, в котором уже содержится 100 друзей, созданных в предыдущем этапе, и ещё добавляется 100 элементов фотографий. Но при всём этом MongoDB половину операций (~50000) отработало за 0.0001 секунды, а MySQL редко ранее 0.0005 секунды. И MongoDB пришлось проделать гораздо большую работу (обновить существующие объекты).
В продолжение решения задачи: Реляционка или не реляционка
Пройдя первый этап: MySQL vs MongoDB. Сравнение скорости insert
Создадим для 1000 пользователей системы по 100 друзей каждому (1000*100=100000 записей).
В MySQL мы имеем структуру:

Таким образом для MySQL это будет вставка записи в таблицу friends.
В MongoDB мы имеем структуру сложнее. т.к. вся информация представлена в виде большого дерева.
Мы имеем коллекцию пользователей. В которой все наши 1000 пользователей как документы.
Каждый документ имеет структуру:
Понять сложно, если вы до этого ни разу не делали nosql проект. Обязательно изучите.
Ну так вот, операция создания френдов, по времени (по оси Х-время в секундах, по оси Y-количество операций с этим временем выполнения):
Вывод: тут нужно понимать, что в mysql — обычная вставка данных в таблицу, а в MongoDB — обновление существующего объекта — создание в его поле ifriend ещё одного элемента, хоть и с одним значением id_friend, но всё-же это update. Ну и мы видим, что выполнив 100000 таких операций мы имеет сравнимо одинаковые затраты по времени.
Для решения задачи Реляционка или не реляционка
Вставим 1000 записей в таблицу. Для чистоты эксперимента в таблицах уже по 10000 записей.
UPD: переделал сбор статистики и запустил на нормальном сервере. Результаты разительно другие (по Х-время в секундах, по Y-количество случаев):
Вывод: видим явно более быструю работу insert в mongodb. Нужно понимать что операции происходят в памяти, никакого обращения к диску не происходит. В обоих случаях. =)
Попробуем решить интересную задачу двумя способами (реляционным и не реляционным).
Допустим есть социальная сеть.
В ней есть пользователи, которые помечают других пользователей френдами.
Пользователи выкладывают какие-либо фотографии.
Задача: Для текущего пользователя вывести список всех фотографий френдов, которые этот пользователь ещё не просмотрел. О_о
Бум использовать:
php фреймворк CodeIgniter
mysql для реляционки
MongoDB для NoSql
Начальные данные:
1. создадим 1000 пользователей
2. для каждого пользователя зададим 100 друзей
3. для каждого пользователя зададим 100 фотографий (получится всего 100,000 фотографий О_о)
4. каждым пользователем посетим случайные 50 фото каждого своего друга (50*100*1000=5,000,000 записей)
5. каждым пользователем посетим случайные 1000 фото (1,000*1,000=1,000,000 ненужных записей дополнительно)
Ну и область наших интересов — быстродействие
Пока в процессе проектирования — пишите что-нить =)