Уважаемые читатели. Это третья статья из цикла по базам данных.

Оглавление:

  1. Как сделать разный часовой пояс в разных базах данных на одном сервере.
  2. Как вести логи изменений данных пользователями в базе данных, сохраняя их в другой базе данных (чтобы база основная база данных не забивалась мусором и не росла)
  3. Как создать свою файловую систему на основе blob полей в базе данных. Почему это удобно. Вопросы эффективности хранения файлов (как получить максимальное быстродействие и при этом минимальное занимаемое место)

Данный способ – способ реализации хранения файлов, приложенных пользователем на сайте, через веб интерфейс. Это не будет “файловой системой” в том понимании как это организовано в операционной системе.

Пример, описанный в этой статье, будет решать задачу стоявшую когда-то у меня: “Выделить раздел внутри аккаунта компании на веб сайте, где сотрудники компании смогут хранить свои файлы, создавать папки (назовем его “Диск”). Диск должен быть изолированным от аккаунтов других компаний и должен интегрироваться в процессы работы аккаунта (организация хранения файлов, прикладываемых к задачам, проектам, карточкам контрагентов, отчетов и т.п.) ”.

Сразу замечу, что данный способ, с точки зрения быстродействия, менее эффективен простого сохранения файлов на веб-сервере. Но он имеет определенные преимущества, которые в моем случае перевесили недостатки потери скорости загрузки.

Недостатки: Увеличенное время загрузки файла

Преимущества

  1. Распределенная структура хранения. Т.е. не обязательно хранить файлы клиента на самом веб-сервере. Можно хранить их где угодно, на любых серверах своей сети. Легко перемещать их с сервера на сервер в случае необходимости.
  2. Удобство резервного копирования файлов клиента. Можно все делать стандартными средствами бекапа.
  3. Безопасность. Данный способ лишен основных уязвимостей веб приложений при загрузке файлов (ознакомиться с ними можно например здесь). Так же способ осуществляет физическую изоляцию данных клиента, от данных других клиентов, ибо у каждой компании своя БД.

Основным аргументом реализации данной системы хранения файлов послужила возможность распределённого хранения. В принципе можно использовать решения типа cifs и samba, примонтировать сетевые диски от других машин и там хранить файлы клиентов. Но в то время мне пришло вот такое вот, не совсем стандартное решение, и я им полностью доволен.

В данной статье мы будем рассматривать процесс реализации от простого к сложному:

  1. Общая организация структуры хранения.
  2. Сохранение файлов в blob поля. Прямое извлечение.
  3. Сохранение файлов с промежуточной архивацией в blob поля. Извлечение с промежуточной разархивацией.
  4. Сохранение файлов с отложенной и выборочной архивацией (не все файлы имеет смысл архивировать, в каких случаях овчинка не стоит выделки, а так же не всегда имеет смысл архивировать сразу).
  5. Потом мы рассмотрим организацию структуры каталогов, организацию прав доступа, операции с файлами, некоторые частные случаи и т.п.
    Итак начнем.

Итак начнем.

В качестве базы данных, используется firebird 3

1. Общая организация структуры хранения.

Как и в предыдущей статье (по хранению логов), не стоит в основной рабочей базе хранить файлы, это будет много мусора, проблемы с бекапами и т.д. Для этого лучше выделить отдельную базу данных. Назовем ее “Файловая БД”. В основной базе данных должна храниться структура каталогов и ссылки на файлы, а сами бинарные данные из файлов, будут храниться в файловой БД.

В этом случае вы получите удобство бекапов, и быструю базу данных. Например, я в своей системе даю пользователям возможность самостоятельно планировать график своих бекапов, в том числе и бекапов файловой базы данных. Более того, даже есть возможность автоматического закидывания архива бекапа пользователю на его FTP (например, если он бекапы желает хранить на своем оборудовании и не платить за аренду места в облаке). Такая реализация возможна как раз благодаря хранению файлов в отдельной базе и организации распределенной системы хранения.

Так же благодаря такой организации, веб-сервер выполняет только функции веб-сервера, а не файлового хранилища. Можно более грамотно организовать распределение ресурсов в сети. При огромном количестве клиентов (огромном количестве баз данных и файловых структур) нераспределенная организация хранения данных будет просто неприемлема. Структура должна быть неограниченно масштабируемой, а так же простой.

Схему можно представить следующим образом:

image

При этом каждый из элементов может быть (и лучше сделать так) на своем отдельном сервере. Структура хранения данных может быть организована следующим образом. В основной базе данных создается структура с данными о приложенном файле

CREATE TABLE FILES_ (
    ID                  BIGINT,
    DATA                TIMESTAMP,  --Дата добавления файла
    DATA_DEL            TIMESTAMP,  --Дата удаления файла   
    USER_               INTEGER,  --Юзер, добавивший файл
    USER_DEL            INTEGER,   --Юзер, удаливший файл
    ID_FILE             INTEGER,   --ID файла в файловой БД
    FILE_NAME           VARCHAR(256),   --Название файла
    FILE_NAME_TMP       VARCHAR(256),   --Название файла при закачке
    CONTENT_TYPE        VARCHAR(100),   --Тип контента файла
    STATUS              SMALLINT,   --Статус файла (есть или удален)
    SIZE                BIGINT,    --Размер файла
    SIZE_ZIP            BIGINT,    --Размер архива файла
    SIZE_ZIP_2          BIGINT,   --Тестовый прогон архивации
    ZIP_USE             SMALLINT,   --Какого типа архиватор используется
    ID_FOLDER           INTEGER,      --Идентификатор каталога, где лежит файл
    FLAG_ZIP            SMALLINT    --Флаг использования архивации
);

Структура файловой базе данных может быть такой (минимум данных о файле и сам файл в blob поле).

CREATE TABLE FILES_ (
    ID              BIGINT,
    DATA            TIMESTAMP,
    USER_           INTEGER,  --Юзер, добавивший файл
    FILE_NAME       VARCHAR(256),  --Название файла
    CONTENT_TYPE    VARCHAR(100),  --Тип контента файла 
    FILE_DATA       BLOB SUB_TYPE 0 SEGMENT SIZE 80,  --Бинарная последовательность файла
    STATUS          SMALLINT,   --Статус (есть или удален)
    SIZE            INTEGER,   --Размер файла
    SIZE_ZIP        INTEGER,  --Размер архива файла
    ZIP_USE         SMALLINT   --Какого типа архиватор используется
);

Тут есть некоторая избыточность данных в этих двух таблицах относительно друг друга, но в последствии она пригодиться, например, при операции отложенной архивации, при отладке, при последующих анализах данных и т.п. Не городить же единый запрос в несколько БД.

Начнем рассмотрение с простой записи файла в blob поле.

2.1. Сохранение файлов в blob поля.

Веб формы и процесс закачки файла в каталог на веб-сервере мы рассматривать тут не будем. Предполагается что читатель с этим знаком.

Итак, пользователь нажал в веб форме кнопку, файл загрузился и сохранился в каталоге веб-сервера (пусть будет например tmp).

Данные файла в этот момент у нас есть в глобальном массиве FILES.
(примеры буду приводить на PHP)

PS: Для простоты изложения, я сознательно опускаю преобразование опасных спецсимволов к мнемоникам, принудительное приведение числовых данных к числу и т.п. Предполагается, что у читателя есть свои процедуры для этого и он осознает опасность sql-инъекций. Если этого нет, то очень рекомендую познакомится с данной темой и делать соответствующие преобразования.

//Коннект к файловой базе данных.
$dbh_file = ibase_connect(…);

//Открываем наш файл для чтения
$fd = fopen($_FILES[…]['tmp_name'], 'r');

//Добавляем данные файла в переменную
$blob = ibase_blob_import($dbh_file, $fd);

//Закрываем файл 
fclose($fd);

//Если все успешно, то производим данных файла в файловую БД.
if (!is_string($blob)) {
} else {

$query = 'INSERT INTO FILES_ (
	USER_,
	NAME_FILE,
	CONTENT_TYPE,
	FILE_DATA,
	SIZE,
	SIZE_ZIP,
	ZIP_USE)
VALUES (
	'.$USER_.',
	'.$_FILES[...]['name'].',
	'.$file_type.',
	?,
	'.$_FILES[...]['size'].',
	'.$_FILES[...]['size'].',
	0)   
RETURNING ID';

$prepared = ibase_prepare($dbh_file, $query);
$res_query = ibase_execute($prepared, $blob);
$prom_query = ibase_fetch_row($res_query);

В $prom_query[0] – будет значение ID записанного файла. После успешной записи данных файла в файловую БД надо записать данные о файле в нашу основную БД.


//Делает проверку, что все ок, ID нового файла есть.
if (isset($prom_query[0]))

//Делаем коннект к основной базе 
$dbh_osn = ibase_connect(…);
$query2 = '
INSERT INTO FILES (
	USER_,
	ID_FILE,
	FILE_NAME,
	SIZE,
	SIZE_ZIP,
	CONTENT_TYPE,
	FILE_ NAME_TMP,
	ZIP_USE)
VALUES (
'.$USER_.',
'.$prom_query[0].',
'. $_FILES[…]['name']).',
'. $_FILES[…]['size'].',
'. $_FILES[…]['size'].',
'.$_FILES[…]['type'].',
'.basename($_FILES[…]['tmp_name']).',
0)';

$res_query2 = ibase_query($dbh_osn, $query2);

//Последним шагом удаляем оригинал файлов в папке tmp.
unlink($_FILES[…]['tmp_name']);

Все, у нас файл записан в файловую БД. На веб-сервере его нет. В основной БД есть информация об этом файле, его статусе, типе, размере и идентификаторе в файловой БД.

2.2. Чтение файла из БД.

Когда пользователь кликает на ссылку с именем файла, нам надо осуществить обратный процесс. Извлечь файл из БД и преподнести браузеру пользователя эту последовательность данных, сказав какого типа этот файл.

В ссылке в качестве GET параметров должен быть указан идентификатор файла (либо хэш идентификатора с солью – зависит от требований к безопасности системы, мы будем рассматривать пока простую ситуацию). Например, ссылка может выглядеть следующим образом Файл.

На этапе простого чтения из БД (без архивирования данных), нам достаточно обращения в файловую БД.

//Делаем коннект к базе данных 
$dbh_file = ibase_connect(…)

$query="select 
				p.file_data, 
				p.CONTENT_TYPE, 
				p.FILE_NAME, 
				p.size
 
			from FILES p where p.id=".$_GET['id'];
$res = ibase_query($dbh_file, $query);    
$data  = ibase_fetch_row($res);

При указании типа данных в header, выявлена индивидуальная проблема с браузером Chrome. Ему принципиально необходимо чтобы filename в header было с одинарными кавычками, другим же браузерам принципиально необходимо без кавычек. Для этого например, можно использовать следующее решение.

preg_match("/(MSIE|Opera|Firefox|Chrome|Version)(?:\/| )([0-9.]+)/", $_SERVER['HTTP_USER_AGENT'], $browser_info);
list(,$browser,$version) = $browser_info;

if ($browser=='Chrome')
header("Content-Disposition: attachment; filename='".str_replace(' ','_',$data[2])."'");
else
header("Content-Disposition: attachment; filename=".str_replace(' ','_',$data[2]));

Замена пробела на "_" осуществляется для корректного отражения имени файла при скачивании, ибо скорее всего по пробелу имя обрежется.

После этого выводим бинарные данные файла в тело скрипта.

echo ibase_blob_echo($data[0]);

PS: Тут хочу обратить внимание на типовую ошибку. Перед <?php и после ?> не должно быть никаких символов – иначе ничего не получится.

Теперь при клике на ссылку file_b.php?id=123, у пользователя загрузится окошко для скачивания его файла (с его именем и нужного типа).

3.1. Загрузка файла с промежуточным архивированием

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

Немного статистики:
В первый раз эта мысль мне пришла около 1.5 лет назад. В тот момент я как раз разрабатывал модуль согласования счетов для одной компании и решил экспериментально интегрировать им данную функцию. В данный модуль прикладываются всевозможные файлы с информацией по счетам и договорам, pdf, xls, doc и т.п.

За 1.5 года в средне-типовой компании со штатом 50-70 человек, к одному модулю было приложено(на текущий момент) 4085 файлов общим объемом 1526 мб, при этом на диске это все занимает 1240 мб. Т.е. архивация zip архивом, дала экономию около 20%. Это довольно неплохо.
В те времена архивация реализовывалась мной через библиотеку zip.lib.php прямо в скрипте. Позже я пришел к выводу, что этот способ не оптимален и по сжатию и по быстродействию. В текущий момент на практике используется архиватор 7zip.

Загрузка файла с архивированием проблем особых не вызывает. Необходимо только перед тем, как сохранить blob последовательность файла в базу данных, выполнить его архивирование, например так:

exec('7z a <ваш файл> <файл архива>);

А потом в конце помимо оригинала, удалить еще и его архив с веб-сервера.

А вот процесс извлечения такого файла пользователю уже гораздо интереснее.

3.2. Чтение файла из БД с извлечением его из архива

Просто так, как в прямом извлечении, поступить не получится. После того, как пользователь кликнет по ссылке, сначала надо считать файл, сохранить его на веб-сервере, разархивировать, а потом передать пользователю.

Т.е. когда пользователь кликает на ссылку file_b.php?id=123, скрипт должен дернуть другой скрипт, который считает в себя данные из blob файла (абсолютно аналогично с п.2.2) после сохранить этот файл на диск сервера, потом запустить его разархивацию, и данные из полученного файла вывести в себя, подставив нужный header, чтобы пользователю вылезло окошко – что он скачивает файл.

Для этих целей используем CURL.

После определения типа браузера и подстановки header, делаем следующее.

//Генерируем случайное имя файла и открываем файл для чтения
 
//Открываем файл
$fp = fopen($path, 'w');

//Дергаем curl-ом ссылку на скрипт, который считывает в себя данные из blob файла, и выводим их в открытый файл.

$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_USERAGENT, $agent);
	curl_setopt($ch, CURLOPT_FILE, $fp);
	curl_exec($ch);
curl_close($ch);

fclose($fp);

Таким образом, в файле с именем $path, оказывается наш заархивированный файл. Производим его разархивацию.

exec('7z e '.$path.' –o <каталог разархивации> -y');

После этого считываем файл и интегрируем его данные уже в наш скрипт при помощи fpassthru

$stream = fopen(<имя файла>,'r');
fpassthru($stream);
fclose($stream);

Теперь все должно получиться.

4. Сохранение файлов с промежуточной архивацией в blob поля.

Теперь рассмотрим вопросы быстродействия. Место то мы конечно экономим. Но вместо того, чтобы пользователь ждал только время загрузки файла на сервер, мы его заставляем ожидать еще и время архивации, и время добавления файла в blob поле. Для пользователя время загрузки файла зачастую увеличивается даже более чем в 2 раза. При извлечении аналогично, вместо того, чтобы считать данные напрямую, мы начинаем их сначала сохранять, разархивировать, а потом уже предоставлять пользователю. Все это не очень хорошо. Что же можно сделать в данном случае?

1) Во первых, можно разнести процесс загрузки файла и процесс его архивации. Т.е. пользователь загружает файл в оригинальном виде. А уже потом, скрипт запускающийся с какой-то периодичностью, будет смотреть на вновь загруженные файлы и их архивировать. В этом случае для пользователя загрузка файлов будет происходить намного быстрее, а мы при этом будем экономить место. Более того, работу данного скрипта можно организовать на другом сервере, и он не будет потреблять ресурсов веб-сервера (зачем нам высоконагруженный сервер еще и заставлять заниматься архивированием?), этот процесс может происходить в стороне, не спеша.

2) Во вторых, далеко не все типы файлов имеет смысл сжимать. Например, если разница между сжатым и несжатым файлом будет менее 10%, то игра точно не стоит свеч, экономия места минимальна – напряги процессора и время ожидания пользователя – максимальны. Для себя я отобрал следующие типы плохо сжимаемых файлов, которые архиватор даже не трогает, а просто ставит галочку (обработано – больше не трогать). RAR, GZ, ZIP, JAR, TAR, ARJ, UC2, GZ, UUE, LHA, CAB, LZH, ACE, TGZ, 7Z, AVI, MPG, 3GP, WMV, ASF, FLV, MP3, AAC, WMA, AMR, TIF, JPG, JP2, GIF, PNG.

PS: информация от ivanbolhovitinov. Правильный XLSX,DOCX — это всегда ZIP-архив. Первые 2 байта «PK». Проверить их можно так: file_get_contents($filename, NULL, NULL, 0,2)

3) Т.к. при извлечении файла пользователю, мы тоже проводим работу по разархивированию, то даже если файл не является типом из п.2 – все равно не факт что его имеет смысл архивировать. Пустая архивация – съедание своих ресурсов и что более страшно – времени ожидания пользователя. Поэтому система, перед тем как заархивировать файл – должна проверить, если в этом смысл. Для этого пришлось сделать систему превентивной архивации, т.е. скрипт сначала “пробует” — он скачивает файл, архивирует его, но перед тем как оригинал заменить архивом – происходит проверка, на сколько процентов отличается размер архива от размера оригинала. Если эта величина опять же, менее 10% — то архивация смысла никакого не имеет. Такие файлы отмечаются как обработанные и не архивируются.

Типичные представители таких файлов xlsx. Формат уже сам по себе сжатый, но по факту встречаются очень отличные файлы, какие-то можно сжать на 50%, а какие-то дай бог на 5%. Зависит от начинки.

4) Так же не имеет смысла архивировать файлы размером менее 250 байт, архив получится больше оригинала.

5) Не нужно архивировать файлы большого размера. Эмпирически выведено, что это в районе 15 мб и более. Даже, если он хорошо сжимается – на его разархивацию придется потратить определенное время (помимо того, что его надо извлечь из blobа) – такие временные ожидания пользователей уже могут напрягать.

5. Организация структуры каталогов, организация прав доступа, операции с файлами, некоторые частные случаи.

Пока мы рассмотрели только систему хранения файлов. Это базовая вещь, на которой основывается все остальное. Но для полноценной “Файловой системы” еще необходимо как-то эти файлы структурировать, назначать им права доступа и т.д.

Возможно, эта глава не всем будет интересна, т.к. реализация данной структуры будет очень сильно зависеть от стоящей задачи. Я реализовывал данную структуру для облачного сервиса erp-platforma.com, поэтому в этой главе рассмотрю задачи, которые должна решать файловая система для сервиса организации работы компании:

1) Классика – Дерево каталогов, файлы. Механизм приложения файлов, редактирования их метаданных, удаления.

2) К этому пункту необходимо небольшое вступление. Рассмотрим не для всех очевидную вещь в работе систем автоматизации: когда вы прикладываете файл к задаче, он физически хранится не в задаче, а на диске. В задаче находится только ссылка на этот файл.

Когда место на диске заканчивается, системному администратору возможно понадобиться его очистить. И перебирать ему все задачи за дцать лет, удаляя из них файлы – ну это просто глупо.
Т.е. должна быть некая файловая структура, например, служебный каталог “Задачи” на диске, где будут храниться все файлы, прикладываемые к задачам, и сисадмин старые файлы может просто почистить.

Но что получится, если он их удалит? В задачах останутся ссылки, которые никуда не ведут! Тоже не есть гуд. Следовательно, в задачах должна быть не ссылка, а некое “окно” в Диск, в котором задача будет видеть только свои файлы.

Но просто “окна” мало. Пользователь может “помнить” что приложил файл, а “злой” сисадмин его тихо удалит с диска и скажет пользователю что он сумасшедший. Поэтому, данные файла можно удалить, но вот запись что файл был, должна остаться где-то в закромах и в нашем “окне” ссылка таки должна выводится каким-нибудь серым, неактивным цветом.

“Задачу” я привел для примера, аналогично файлы могут прикладываться к проектам, контрагентам, сотрудникам, объектам и т.п. Т.е. система должна быть универсальная.

Но и это еще не все. Например, заказчик, может запросто захотеть, чтобы у контрагентов было 2 области приложения файлов, например область где прикладываются счета и область где прикладываются документы. Т.е. эти “окна” должны привязываться не просто к странице и ее входным данным, а к конкретным элементам страницы.

Подведем итог пункта: файловая система должна поддерживать некие “окна” в нее из внешней системы. Каждое “окно” должно иметь функции операции с файлами в пределах своих рамок.

3) Пользователю может потребоваться найти в папке файлы, которые были приложены к такому-то контрагенту, или такому-то проекту или еще по каким-то неведомым нам признакам, при этом не важно как эти файлы могут называться. Т.е. нужна некая система поиска не только по названиям файлов, но и по их принадлежности внешним структурам.

PS: На мой взгляд такие вещи оптимально реализовывать при помощи хэштегов. Для этих целей, в файлах, можно ввести еще пару свойств: “системные хэштеги” и ”пользовательские хэштеги”. В системных пишутся системные названия, например ФИО контрагента. В пользовательские – произвольно пользователем, при приложении файла.

4) Права доступа. Само собой кто-то должен иметь права просматривать те или иные папки, кто-то нет. Права на добавление, удаление. А может так получиться, что у пользователя должен быть доступ к задаче и файлам приложенным в ней, а к папке в которой эти файлы доступа не быть. Т.е. “окно” должно иметь свои права. У системного администратора должен быть механизм для назначения или удаления прав доступа тем или иным пользователям.

Вот такие получились требования к структуре файловой системы.

Реализация всех этих вещей – достаточно сложный и ветвистый код в скриптах и в структуре БД, поэтому в рамках статьи такое не привести. И так уже очень большая статья получается.
Я опишу базовые принципы реализации каждой из задач.

Структуру каталогов вести достаточно просто. Это простая таблица в БД компании, у которой есть:

1) Название папки
2) Создатель папки
3) Узел папки в котором находится данная папка
4) Статус папки
5) Идентификатор папки
6) Общие права на папку по умолчанию (что могут все пользователи делать в папке, если им не выданы специальные права)

Подробнее по каждому пункту:

1) Название папки может дублироваться на разных уровнях дерева, в разных узлах. В одном узле название папки дублироваться не может. Данный механизм реализовать очень просто, достаточно поставить уникальный индекс на поля 1 и 3 таблицы. При дублировании имени в одном узле – система выдаст ошибку.

2) Создателя папки надо записать. Создатель папки имеет на нее всегда полные права, если иное не прописано администратором в роли пользователя. Например, даже если пользователь создал папку – администратор системы может все же закрыть ему доступ к ней, сделав соответствующую запись в роли.

Создатель папки может настроить права по умолчанию для остальных пользователей. Т.е. видят ли остальные папку, могут ли они смотреть файлы только на чтение, или могут их удалять, изменять пользовательские хэштеги и т.п. Опять же, данные настройки могут перекрываться системным администратором.

3) В папке прописывается узел другой папки, в которой она находится. Это нужно для построения древовидной системы папок.

4) Статус папки. Папка может быть рабочей, или ее можно удалить. Вопрос на самом деле не простой. Тут каждый разработчик может для себя решать что делать, можно например, организовать служебную папку “Корзина”, в которой будут отображаться папки со статусом удалено. Можно их реально удалять – но надо тогда проработать, что делать с файлами в папке, а так же со связями папки в структуре системы. Правильное решение, на мой взгляд, не давать физически удалять запись папки, пока на нее есть ссылки в системе и в ней есть какие-то файлы. Чистим файлы, чистим связи, и после этого – пожалуйста, удаляйте. Иначе можно получить глюки в зависимых модулях.

5) Каждая папка должна иметь свой идентификатор. Названия разных папок в разных узлах может дублироваться, а идентификаторы — нет. При программировании системы, когда пользователь на веб странице создает элемент Файл, в этом элементе он всегда указывает идентификатор папки, и именно в этой папке система будет сохранять файлы, прикладываемые в этом модуле, через этот элемент.

6) Права папки по умолчанию. При просмотре пользователем папки система сначала должна проверить записи на эту папку в роли пользователя, если стоят хоть какие-то настройки прав (будь то права на все, или наоборот запрет всех действий) то применяться должны эти правила, если же не написано ничего – то должны вступать правила, выставленные создателем папки.
В своей разработке, систему прав на папки, я интегрировал в общую систему прав и у администратора компании добавление в роль пользователя папки вызвать проблем не должна (как пример, реализацию прав доступа к элементам системы, можно почитать здесь).

Для связки файла с элементом страницы есть изящное решение.
Надо вычислить хэш сумму, в которой должен быть уникальный идентификатор элемента страницы, и данные страницы (например, номер проекта или задачи). Этот хэш будет являться всегда уникальным и должен храниться в записи данных файла. При загрузке страницы в момент отображения “окна”, должен вычисляться этот связующих хэш и по нему производиться поиск файлов и вывод на страницу их списка.

Также “окно” должно знать каталог, в который складывать файлы новые файлы.

Записи хэштегов к файлам. С пользовательскими (произвольными) хэштегами проблем нет, надо сделать пользователю при добавлении файла (или при его редактировании) возможность внесения пометок к файлу.

Сложнее с системными хэштегами. Например, в каждый добавляемый файл к задаче надо ставить тег “#Задача №…#”. Потом в папке Задачи, пользователь, введя в строку поиска, например “№…#“, получит все файлы интересующей задачи. У меня данный функционал реализован на уровне языка программирования, в свойствах элемента формы “Файл” (в статье я это называл “окном”). В его свойствах можно задавать строку с элементами идентификаторов, и связывать данные элементы с необходимым источником данных. Остальное система построит автоматически.

Еще, в качестве приятного бонуса, можно рассмотреть частный случай файловой игры с blob полями – Систему хранения изображений.

В какой-то момент, в разработке внутреннего языка программирования, я столкнулся с необходимостью введения нестандартного типа данных. Есть разные типы данных, integer, varchar, timestamp и т.д. Но вот типа image в базах данных нет. А нужен. Например, очень удобно взять и запросом вывести таблицу, в которой будут изображения, например стрелки вверх, вниз для перемещения данных, удаления данных и т.п. Чтобы это уже все было в базе и обрабатывалось на уровне базы, а не городить каждый раз что-то в интерфейсе юзера. Например, вот такие вещи у меня можно выводить одним запросом:

image

Это как раз возможно благодаря использованию blob полей. В файловой базе клиента, помимо таблицы хранения файлов, можно создать к примеру таблицу IMG и там организовать хранение изображений. На физическом уровне, в базе данных с этим типом, будет храниться только идентификатор изображения. При выводе данных на страницу, скрипт интерпретатора, встретив тип данных image, по этому идентификатору обратится в файловую базу данных и выведет изображение из blob поля. При этом пользователь, не будет видеть всей этой внутренней кухни. Он просто укажет в выходных данных процедуры параметр с типом image и сделает в процедуре запрос, выводящий поле таблицы с типом image в этот параметр. Очень удобный механизм.

На этом цикл по базам данных пока заканчиваю.

Если есть предложения по улучшению механизмов описанных в статье, или альтернативной организации хранения файлов с аналогичными возможностями, пишите комментарии.
Поделиться с друзьями
-->

Комментарии (73)


  1. elve
    12.01.2017 20:20
    +9

    Я может не совсем компетентен, но всегда интересовал вопрос, что курят люди делающие такое:

    Как создать свою файловую систему на основе blob полей в базе данных. Почему это удобно.

    Создание файловой системы в БД, которая сама является набором файлов в нормальной файловой системе.


    1. equand
      12.01.2017 20:33
      +2

      Чтобы будущий разработчик принявший вымерший проект для рефакторинга застрелился предварительно перерезав семью?


      1. Kanedias
        13.01.2017 10:15
        +2

        «Застрелился» можно опустить. Хансу 6 лет осталось всего.


    1. napa3um
      12.01.2017 20:36
      +1

      https://docs.mongodb.com/manual/core/gridfs/ — там описание кейсов, в которых подобное может быть полезным (но да, лепить это руками я бы не стал, лучше взять одно из многих готовых решений).


    1. barantaran
      13.01.2017 12:52
      +1

      Получил как-то «в наследство» CMS с такой парадигмой, много лет после этого задаюсь этим вопросом и видимо настал момент узнать ответ.


  1. alexxxst
    12.01.2017 20:28
    +5

    Но зачем?


  1. alexkunin
    12.01.2017 20:50
    +4

    О, какие у вас вкусные SQL-injections… Вижу, вы предупреждаете, но раз уж вы и плейсхолдеры используете, сделали бы все единообразно. Ведь кто-то так и скопирует…

    А вот функция ibase_blob_import не ограничивает ли максимальный размер файла объемом оперативки, доступной PHP? Никогда не работал с нею, но на php.net написано: «This function creates a BLOB, reads an entire file into it, closes it and returns the assigned BLOB id.»

    И разжимать все же может на лету, а не в отдельный файл? А если использовать zlib, и если браузер примет сжатый поток, то можно прямо так отдавать, без двойной компрессии.


  1. kafeman
    13.01.2017 04:18

    CREATE TABLE FILES_ (
        ID                  BIGINT,
        DATA                TIMESTAMP,  --Дата добавления файла
        DATA_DEL            TIMESTAMP,  --Дата удаления файла
    

    IMHO, было бы логичнее использовать слово «date», а не «data».

    И вообще именование полей очень спорное. Например, вместо «USER» и «USER_DEL» я бы использовал «created_by» и «deleted_by» — тогда бы сразу отпала необходимость в комментариях.

    Кроме того, для хранения файлов лучше использовать решения на базе NoSQL, например, упомянутый выше GridFS или HDFS.


    1. mail-online
      13.01.2017 10:28

      Да можно как угодно обозвать. Это просто пример. Но вообще я всегда пишу «Data» (тут автоматом тоже так обозвал) ибо «Date» — зарезервированное слово, оно в любом случае с приставкой какой-то должно идти. Можно конечно с кавычками, но это просто неудобно.

      GridFS — как вариант. Но когда мне пришлось все это делать, я просто не знал о ней. Возможно я бы тогда и не заморачивался своей системой.
      Но видите, во всем своя польза, получилась альтернативная структура, которую можно сделать самостоятельно. В свое время я никакой подробной статьи по использованию blob полей для хранения файлов не нашел. Вот написал ее, пусть будет.


      1. ostapbender
        13.01.2017 13:50

        Можно конечно с кавычками, но это просто неудобно.

        Фу таким быть. Это вот после таких "неудобно" приходится каждому новичку объяснять, что "в Date у нас дата, Customar_ID — это по опечатка оплошности, и хранится там не Customer.ID, а LegalEntity.ID" и т.д.


        1. kafeman
          13.01.2017 14:15

          в Date у нас дата
          Ну правильно же :-)


          1. ostapbender
            13.01.2017 15:05

            Вот сами руки написали, клянусь макаронным монстром!


            1. AlexPu
              13.01.2017 15:07
              -1

              Не чешите чашего макаронного монстра! А лучге застегните ширинку!
              (звиняйте — не удержался :) )


  1. michael_vostrikov
    13.01.2017 06:58

    Данный способ лишен основных уязвимостей веб приложений при загрузке файлов

    Эти уязвимости есть, если вы загружаете файл на тот же сервер, где работает интерпретатор PHP, и к тому же есть прямой доступ из интернета к загруженным файлам. У вас отдельный сервер для файлов с доступом через скрипт. Так что тут нет разницы, хранить их в базе или на диске. Если я правильно понимаю, из преимуществ остается только удобство бекапа средствами БД.


    Не городить же единый запрос в несколько БД.
    Тут есть некоторая избыточность данных в этих двух таблицах

    В основной базе в таблице files хранится путь к файлу. В таком варианте ничего городить не надо, запрос идет только в основную БД. После проверки прав содержимое так же отдается через stream. Для удобства переноса можно адрес сервера в отдельном поле хранить.


    Все, у нас файл записан в файловую БД. На веб-сервере его нет.

    "Все, у нас файл записан в файловое хранилище на другом сервере. На веб-сервере его нет."
    В чем принципиальная разница?


    Тут хочу обратить внимание на типовую ошибку. Перед <?php и после ?> не должно быть никаких символов – иначе ничего не получится.

    Рекомендуется не закрывать тег, если в файле есть только PHP-код. Это так, к сведению.


    Т.е. когда пользователь кликает на ссылку file_b.php?id=123, скрипт должен дернуть другой скрипт, который считает в себя данные из blob файла

    Зачем? Что мешает разархивировать в этом же скрипте?


    Рассмотрим не для всех очевидную вещь в работе систем автоматизации: когда вы прикладываете файл к задаче, он физически хранится не в задаче, а на диске. В задаче находится только ссылка на этот файл.

    Да ну? Вы для программистов статью написали или для менеджеров?)


    Следовательно, в задачах должна быть не ссылка, а некое “окно” в Диск, в котором задача будет видеть только свои файлы.

    Эмм. Чем это отличается от таблицы tasks__files (task_id, file_id, ...)?


    нужна некая система поиска не только по названиям файлов, но и по их принадлежности внешним структурам.
    На мой взгляд такие вещи оптимально реализовывать при помощи хэштегов.

    По-моему, промежуточные таблицы для связи для связи многие-ко-многим больше подходят. Они явно показывают принадлежность файла к сущности.


    Для связки файла с элементом страницы есть изящное решение.
    Надо вычислить хэш сумму, в которой должен быть уникальный идентификатор элемента страницы

    comments__files


    Например, очень удобно взять и запросом вывести таблицу, в которой будут изображения, например стрелки вверх, вниз для перемещения данных, удаления данных и т.п.

    FontAwesome, glyphicons


    1. kafeman
      13.01.2017 07:23
      +1

      Если я правильно понимаю, из преимуществ остается только удобство бекапа средствами БД.
      Не оправдываю автора, но если разбить файлы на блоки и настроить репликацию, то может получиться достаточно гибкое и отказоустойчивое хранилище, справляющееся с падением целого датацентра.

      Кроме того, такой подход может пригодиться в облаках, где принято делать stateless-контейнеры.


    1. mail-online
      13.01.2017 11:06

      Эти уязвимости есть, если вы загружаете файл на тот же сервер, где работает интерпретатор PHP, и к тому же есть прямой доступ из интернета к загруженным файлам. У вас отдельный сервер для файлов с доступом через скрипт. Так что тут нет разницы, хранить их в базе или на диске. Если я правильно понимаю, из преимуществ остается только удобство бекапа средствами БД.

      1) Неизвестно имя файла, т.е. ссылкой его дернуть не получится
      2) Во вторых можно грузить в каталог ниже каталога веб сервера
      3) В третьих он грузится на миг, т.е. только на время работы скрипта, скрипт этот файл после загрузки в БД с веб сервера удалит.

      При извлечении аналогично, он только миг присутствует на веб сервере.

      В основной базе в таблице files хранится путь к файлу. В таком варианте ничего городить не надо, запрос идет только в основную БД. После проверки прав содержимое так же отдается через stream. Для удобства переноса можно адрес сервера в отдельном поле хранить.

      Примерно так и сделано. Данные в файловой базе — избыточны. Но они удобны для анализа. Конечно можно без них сделать. С ними просто удобнее в случае работы именно с данным файлов, не надо дергать основную базу.

      «Все, у нас файл записан в файловое хранилище на другом сервере. На веб-сервере его нет.»
      В чем принципиальная разница?

      Веб сервер не «резиновый». В него не влезут файлы 1000 клиентов… Масштабируемость нужна. При необходимости покупается новый сервер, его адрес прописывается в настройках и на нем создаются базы новых клиентов и туда все пишется.

      Зачем? Что мешает разархивировать в этом же скрипте?

      Смотря чем архивирован. Для 7z очень сомневаюсь. Можно только zip насколько я знаю. Это одна из реализаций.
      Система в данном случае модульная, в файлах ставится признак чем архивирован, и в зависимости от этого можно уже делать действия, если имеет смысл zip то можно делать так. Наверное для больших файлов zip-ом в будущем и сделаю. Тут везде должен быть вопрос целесообразности.

      Да ну? Вы для программистов статью написали или для менеджеров?)

      Да, вы правы, это я погорячился )

      Эмм. Чем это отличается от таблицы tasks__files (task_id, file_id, ...)?

      По-моему, промежуточные таблицы для связи для связи многие-ко-многим больше подходят. Они явно показывают принадлежность файла к сущности.

      И да и нет. Можно и так, но в моем случае например лучше через хэши. У меня встроенный язык программирования и файлы могут быть приложены где угодно в системе, в любом месте страницы любого раздела, даже в нескольких местах на одной странице + должны учитываться входные данные в страницу, например у одной задачи одни файлы, у другой дургие. Входные данные — может быть тоже не один параметр а 2-а,3-и...,10-ять. Поэтому удобнее было через хэши. Он всегда будет уникальный у уникальных данных. Да и в смысле быстродействия на мой взгляд лучше, индекс по одному полю с уникальными значениями.
      Но реализация через таблицы многие-ко-многим — тоже вполне может иметь место.


      1. oxidmod
        13.01.2017 11:38

        Масштабируемость и доступность легко решается за счет использования специализированных ФС. Стоило просто чуть больше времени уделить изучению существующих решений.


        Зы. К примеру nginx имеет модуль для работы с MogileFS. в бд нужно хранить только метаинформацию (по минимуму — иди файла в ФС, по желанию когда и кем загружен/удален/изменен и все что нужно для счастья)


      1. michael_vostrikov
        13.01.2017 12:01

        1) Неизвестно имя файла, т.е. ссылкой его дернуть не получится

        Если не разрешать доступ к файловому серверу из интернета, то тоже ссылкой его дернуть будет нельзя. Вы же к базе не даете открытый доступ.


        2) Во вторых можно грузить в каталог ниже каталога веб сервера

        Ну так и если хранить в файлах, можно так же грузить. Доступ все равно через скрипт, а не по прямой ссылке.


        3) В третьих он грузится на миг, т.е. только на время работы скрипта, скрипт этот файл после загрузки в БД с веб сервера удалит.

        Если файлы грузятся на отдельный сервер, то будет точно то же самое.


        С ними просто удобнее в случае работы именно с данным файлов, не надо дергать основную базу.

        А как же проверка прав доступа?


        Веб сервер не «резиновый». В него не влезут файлы 1000 клиентов

        "Все, у нас файл записан в файловое хранилище на другом сервере. На веб-сервере его нет". И да, при необходимости покупается новый сервер, и файлы пишутся в его файловую систему. Так в чем принципиальная разница?


        Смотря чем архивирован. Для 7z очень сомневаюсь. Можно только zip насколько я знаю. Это одна из реализаций.

        Нет, что мешает вызвать exec('7z ...') в этом же скрипте, без использования CURL?


        1. mail-online
          13.01.2017 12:18

          1) Неизвестно имя файла, т.е. ссылкой его дернуть не получится

          Если не разрешать доступ к файловому серверу из интернета, то тоже ссылкой его дернуть будет нельзя. Вы же к базе не даете открытый доступ.

          Возможно не совсем корректно выразился. Имею ввиду что при загрузке файлы загружается не с тем именем которое было на компьютере заружающего, а со случайным, какое имя файла будет при загрузке — неизвестно заранее. Т.е. ссылкой его дернуть не получится, т.к. имя низвестно.

          2) Во вторых можно грузить в каталог ниже каталога веб сервера

          Ну так и если хранить в файлах, можно так же грузить. Доступ все равно через скрипт, а не по прямой ссылке.

          3) В третьих он грузится на миг, т.е. только на время работы скрипта, скрипт этот файл после загрузки в БД с веб сервера удалит.

          Если файлы грузятся на отдельный сервер, то будет точно то же самое.


          Да, можно так же грузить. Только на веб сервер нужно монтировать сетевые диски. Ставить самба сервер. Это как варинат да, но на мой взгляд менее удобно. В принципе можно заниматься монтированием и писать пути.

          Смотря чем архивирован. Для 7z очень сомневаюсь. Можно только zip насколько я знаю. Это одна из реализаций.

          Нет, что мешает вызвать exec('7z ...') в этом же скрипте, без использования CURL?

          Как что мешает? А какой файл вы собрались делать exec('7z ...')? Нужно файл добыть сначала. Это делается CURL.


          1. michael_vostrikov
            13.01.2017 13:07

            а со случайным, какое имя файла будет при загрузке — неизвестно заранее

            Почему неизвестно? В таблице files делается поле filename (или filepath). Информация о файле в базе есть, разница в том, что контент файла там не хранится.


            Только на веб сервер нужно монтировать сетевые диски.

            Зачем? Если например использовать FTP, то достаточно вызова ftp_put().


            Как что мешает? А какой файл вы собрались делать exec('7z ...')?

            Вызывается скрипт file_b.php?id=123. В нем написано примерно такое:


            $fileRow = getFileRowById($_GET['id']);
            $randomName = 'random_name.zip';
            file_put_contents($randomName, $fileRow['FILE_DATA']);
            exec('7z ' . $randomName . ' ...')
            
            // ... отправка разархивированного файла


            1. kafeman
              13.01.2017 13:17

              7z не умеет STDIN?


              1. michael_vostrikov
                13.01.2017 13:52

                Может и умеет, я просто пример автора преобразовал)


            1. mail-online
              13.01.2017 14:06

              а со случайным, какое имя файла будет при загрузке — неизвестно заранее

              Почему неизвестно? В таблице files делается поле filename (или filepath). Информация о файле в базе есть, разница в том, что контент файла там не хранится.

              Ну мы же рассматриваем безопасность, т.е вариант со стороны нападающего. Понятно что серверу известно имя файла, но нападающему нет. Он не сможет его по ссылке выполнить.

              Только на веб сервер нужно монтировать сетевые диски.

              Зачем? Если например использовать FTP, то достаточно вызова ftp_put().

              Эта мысль приходила, так конечно можно но… время! ФТП не самый быстрый протокол. Пользователь все это время будет ждать загрузку. Хотя что быстрее: взять файл по фтп или из блоба, вопрос на самом деле открытый.

              Как что мешает? А какой файл вы собрались делать exec('7z ...')?

              Вызывается скрипт file_b.php?id=123. В нем написано примерно такое:

              Да, тут вы правы! Действительно с file_put_contents может получиться. Не приходило в голову. Потестирую, если все ок будет обновлю статью.


              1. oxidmod
                13.01.2017 14:18

                Никто не запрещает в фс сохранять файлы с рандомным именем


              1. michael_vostrikov
                13.01.2017 14:41

                Понятно что серверу известно имя файла, но нападающему нет. Он не сможет его по ссылке выполнить.

                Тогда я все правильно сказал в прошлый раз. "Если не разрешать доступ к файловому серверу из интернета, то тоже ссылкой его дернуть будет нельзя.". А если на файловом сервере нет PHP, то и с известной ссылкой нападающий его не выполнит.


  1. oxidmod
    13.01.2017 08:25

    Но зачем??? Взяли бы gaufrette


  1. AlexPu
    13.01.2017 11:47
    +2

    Тоже не понял насчет «Как создать свою файловую систему на основе blob полей в базе данных. Почему это удобно»
    Ибо это во первых НЕ удобно, а во вторых все перечисленные «преимущества» просто напросто являются следствием незнания автором современных реалий…

    Я кстати тоже занимался в свое время чем-то вроде этого… лет двадцать назад наверное… ну файловую систему на основе реляционной БД творил… но это всетакие было чем-то вроде прикола… еще раньше, участь в школе я прикалывался созданием альтернативных теорий в математике и в физике путем подменты одного послулата на что-нибудь совершенное нелепое… зарядка для ума не более того…


    1. TerraV
      13.01.2017 12:28
      +3

      если вы почитаете статьи автора по ссылкам в начале, вы увидите что автор живет в своей, уникальной вселенной. В частности работа с датами это же просто лютый треш!


      1. mail-online
        13.01.2017 12:51
        -2

        TerraV — улыбнуло :)
        Но вообще это просто нестандартный подход. Он имеет свои плюсы при использовании в определенных задачах. Просто никто не может на него посмотреть с другой стороны, не как привыкли.


        1. oxidmod
          13.01.2017 13:00
          +2

          Ну вы же привыкли есть суп ложкой? Почему бы не посмотреть с другой тсороны и не есть его вилкой. Удобно выбирать овощи/мясо, а бульйон потом вообще выпить можно. Нестандартный подход же, да?


          В даном случае — это излишний оверхед на дополнительный сервер бд. Оверхед на хранение файлов внутри файлов. А еще индексы по всему этому (когда там пара сотен файлов то фигня, но представьте размеры этой бд с миллионами файлов?)
          Со временем вылезут пробелм как это все распихать по шардам и прочие прелести распределнных систем, которые придется решать вручную.


          Задача РСУБД — хранение связанных данных в консистентном виде, а уж никак не файлов. Да и отдавать файлы через пых — то еще извращение и излишняя нагрузка на сервер. В бд достаточно хранить метаинформаци о файлах и тогда пых только будет проверять права и отдавать заголовки редиректа.


          зы. Ешьте суп ложкой)


          1. mail-online
            14.01.2017 15:30
            -1

            Вы хотите индексы по блобам делать?..
            Хранение файла вне веб-сервера, в любом случае оверхед.


            1. Fortop
              16.01.2017 17:02

              Отдача файла посредством того же nginx в разы более легкая задача в сравнении с отдачей этого же файла из БД да еще и через приложение.


              И Oracle и MSSQL Server для этих целей сделали отдельное легковесное апи, которое позволяет отдавать файл напрямую из БД минуя приложение.
              В случае MySQL и FireBird такая возможность отсутствует.


        1. defecator
          13.01.2017 14:08
          +1

          Хранение файлов в блобах — это очень дорогой в финансовом отношении способ.
          Обычно СУБД работают на серверах на быстрых винчестерах 10K, 15K, и такие винчестеры,
          особенно фирменные, стоят как половина самого сервера, а ёмкость имеют небольшую — 300, 600 гигов.

          В то время как для хранения файлов достаточно винтов 5400 или 7200 в дисковой полке.


  1. time2rfc
    13.01.2017 14:08

    Готов поставить пиво, что такого рода извращения нужны в случаях написания 'велосипедов'.


    1. AlexPu
      13.01.2017 15:02

      в случаях написания 'извращенных велосипедов для извращенцев', действительно необходимы, но за этим нужет строгий контроль со стороны государства как и за производством любых других наркотических средств


  1. Fortop
    16.01.2017 16:54

    Боже, какая все же печаль....


    Итак, по пунктам:


    • Хранить файлы в БД нужно в достаточно редких и исключительных случаях (или тогда когда у вас ну очень простой бложик и вы хотите иметь абсолютно весь контент в одном месте — БД). Поэтому для большинства случаев правильный ответ — не хранить.
      Тогда когда вам это потребуется — вы будете знать достаточно чтобы это сделать. Чаще всего это моменты связанные с шардингом и репликацией данных между шардами. И интенсивной работой с самими файлами.
    • В обычном случае в БД храним метаинформацию о файле
      — реальное имя файла загруженного пользователем
      — размер
      — дату
      — путь где хранится файл (включая служебное имя сгенерированное вами) и возможно имя файлсервера
    • На файлсервере вы встраиваете дерево с учетом того чтобы не хранить более 1000-2000 файлов в одном каталоге. Для этого можете именовать файлы при помощи хеширующей функции и разбивать на группы по части имени.
    • Сам файлсервер может быть подключен к CDN

    Все.


    1. mail-online
      16.01.2017 23:14

      Извиняюсь, ниже коммент, не в то поле вставил.


    1. AlexPu
      17.01.2017 12:05
      -1

      >>Итак, по пунктам:

      пункты… как бы помягче… я конечно понимаю, что вам хочется найти оправлания но…
      Впрочем у вас это пройдет… повторяю — вы не первый (и не последний) из тех кого осенила подобная гениальная идея…


      1. Fortop
        17.01.2017 12:44

        Оправдание чему? :-D


        1. AlexPu
          17.01.2017 14:16
          -1

          Оправдание попытки изобретения велосипеда с квадратными колесами


          1. Fortop
            17.01.2017 14:19

            Мне жаль вас огорчать, но вам к окулисту и в первый класс школы.
            Для изучения чтения


            1. AlexPu
              17.01.2017 14:24
              -1

              Мне не жаль вас огорчать — в вашем случае медицина бессильна


              1. Fortop
                17.01.2017 14:50

                К счастью здоровым вроде меня она и не нужна.

                А вам я крайне рекомендую перечитать и вникнуть в текст.
                Ну и определиться с тем, что же вы считаете велосипедами…


  1. mail-online
    16.01.2017 23:12

    Ок, Fortop.

    Давайте по полочкам. Рассматриваем мы бесплатную СУБД.
    Oracle и MSSQL Server нет. Денег на них нет, а есть только бесплатный Firebird. У стартапов такое бывает весьма часто. Надо при минимуме бабла, выжать максимум производительности.

    Решить следующее:

    Файлы пользователей должны храниться вне веб сервера. Система должна быть распределенной и неограниченно масштабируемой. Файловых серверов может быть сколько угодно и они могут добавляться в процессе работы. Нужно иметь возможность достаточно просто организовать миграцию файлов клиента на другой сервер при необходимости (например заполнено 70% места, добавили еще сервер, 35% оставить на старом, 35% переместить на новый так чтобы никто ничего не заметил, и дальше равномерно раскидывать новых клиентов по двум серверам. Потом следующий сервер и т.д.).

    Файлы аккаунта должны бекапится (отдельный файл архива со всеми файлами). С возможностью передачи архива на оборудование клиенту по графику (если он настроит свой ftp в системе планирования бекапов)

    Какие варианты я вижу для решения этой задачи:
    а) Samba-server и cifs, класть файлы локально вне каталога веб сервера
    б) Загружать их на файловый сервер по ftp
    в) Хранить их в базе данных (каждому аккаунту своя база данных с которой он работает)

    GridFS я никогда не использовал и возможно мое мнение будет субъективное, но я не вижу никаких преимуществ, так же надо файл туда положить, так же его взять оттуда и произвести над ним операции. Разве что возможно чуть быстрее будет, чем с реляционной БД

    Отдача файлов.
    1) В любом случае это не прямая ссылка, это скрипт с header. Как минимум соображения безопасности это диктуют.
    2) Файл может быть сжатым. На мой взгляд для сжатия лучше использовать стандартные архиваторы (они работают эффективно). Перед отдачей пользователю, его надо разархивировать.
    В случаях б) и в) файл надо стащить на веб-сервер чтобы сделать эту операцию.
    В случае а) можно сразу архиватором читать файл по сети, а оригинал сохранять на веб сервере.
    3) Как отдавать.
    Самый простой (но возможно действительно не самый эффективный) вариант — как описан в статье. Делаем нужный header и выводим в скрипт данные файла. После файл стираем.

    Возможно вы правильно советуете через nginx, я этого не пробовал, но мне стало интересно, обязательно попробую. Почитал, можно через X-Accel. Он даже может взять их с другого сервера, насколько я понял. В документации сказано что так

    location / files {
    internal;
    proxy_pass http://127.0.0.2;
    }


    Да, соглашусь, неплохо все. Но как же масштабируемость? А если много серверов откуда надо брать? Т.е. надо скриптом их грузить на этот сервер с которого потом будет отдача и header(«X-Accel-Redirect: /files/». $path);
    Вообще предполагаю что можно сделать location / files1{}, location / files2{} и т.д. и в зависимости от того на каком файловом сервере и изымать по нужному пути, но описания этого я сходу не нашел, поэкспериментировать придется.
    А вообще именно база данных для хранения файла была выбрана из соображений удобства. Просто бекапить, просто мигрировать на другой сервер, просто накатить новый. Купил железку, накатил проксмокс, сделал копию виртуалки, поменял IP, почистил, прописал ее настройки в базе распределения – все подхватилось и все работает. Так же файловая база в моем случае решает проблему хранения типа данных image в моем внутреннем языке программирования, но это мой индивидуальный случай.

    Но вообще, если что подскажите, что решит описанные проблемы и будет удобно в эксплуатации, буду благодарен и обязательно изучу если не знаю. Но пока получилось решить так.


    1. oxidmod
      16.01.2017 23:23

      В вашем комменте слишком много "не знаю" и "не пробовал". А стоило просто поискать. Не думаете же вы, что вы первый кто столкнулся с распределенным хранением документов?


      вот к примеру http://www.grid.net.ru/nginx/mogilefs.ru.html модуль для nginx позволяющий отдавать файлы с mogileFS.
      при этом сама mogileFS решает большинство ваших проблем. Она сама распределяет файлы по нодах, хранит файлы в нескольких местах, так что при падении ноды файлы всеравно доступны. Вам вообще не нужно никуда ничего копировать, вы просто запрашиваете хеш, который вам даст фс при сохранении файла и она по хешу найдет на какой ноде он лежит и отдаст через nginx. и это лишь один из обкатанных вариантов


    1. lair
      17.01.2017 00:39

      Файлы пользователей должны храниться вне веб сервера. Система должна быть распределенной и неограниченно масштабируемой.

      Берете, значит, облачное хранилище (Azure Blob или S3) — и все, конец разговора.


      1. Fortop
        17.01.2017 00:47

        Там исходные две посылки их режут

        Денег на них нет…
        … Надо при минимуме бабла, выжать максимум производительности.


        1. lair
          17.01.2017 00:57

          Ну да, а место, на котором Firebird развернут, бесплатное. А если посчитать, то разное может выясниться.


          1. Fortop
            17.01.2017 01:03

            Ну да, а место, на котором Firebird развернут, бесплатное

            Без понятия :D
            Одно я знаю точно Амазон и Азур жестоко дорого для более-менее равномерной нагрузки и без использования основной массы их фич.

            Вот такое
            RAM 64 GB DDR4 RAM
            Hard Drive 2 x 4 TB SATA 6 Gb/s 7200 rpm

            Перекрывает потребности практически любого стартапа, который в состоянии поднять один человек за 2-4 месяца


            1. lair
              17.01.2017 01:05

              Масштабируемость? Распределенность?


              (другое дело, что не факт, что они стартапам нужны, но автор же попросил)


              1. Fortop
                17.01.2017 01:08

                Масштабируемость? Распределенность?

                1. путем поднятия еще такого же сервера
                2. в рамках цодов хетцнера или любого другого крупного хостера с аналогичными же тарифами

                И, да, не из коробки.


                1. lair
                  17.01.2017 01:09

                  Вот-вот. А когда начинаешь это реализовывать, в какой-то момент так задалбываешься, что дешевле купить у Амазона/МС. Мы вот даже не стали думать свое делать, а просто взяли облако, ибо намного предсказуемее.


                  1. Fortop
                    17.01.2017 01:15

                    Мы вот даже не стали думать свое делать, а просто взяли облако, ибо намного предсказуемее.

                    При наличии хорошего админа об этом не задумываешься.
                    В свое время на проектах команды которую я вел использовалось свыше 200 виртуалок.
                    А сколько уж реальных серверов было — нас, как разработчиков, не сильно интересовало.

                    На проект я просил типовые инстансы в количестве 4-5 штук. (2 бек, 2 сфинкс, отдельно мемкеш, отдельно бд, отдельно статика)
                    И масштабировалось все путем клонирования оных и подброса под haproxy и nginx

                    На пике нагрузки на одном проекте могло быть 22 бека, 18 сфинксов, 1 БД, 2 с демонами, 2 мемкеша.


                    1. lair
                      17.01.2017 01:18

                      Вопрос в том, сколько стоит такая админская команда, опять-таки.


                      1. Fortop
                        17.01.2017 01:22

                        Вопрос в том, сколько стоит такая админская команда, опять-таки.

                        Я сильно удивлю, если скажу что админ был один? :)
                        И вел он не только нашу команду, но и еще как минимум 2 о которых я знал… :D

                        По его ЗП — без понятия.
                        Железо на пиках обходилось порядка 8 000 $ в месяц, это из того что краем уха слышал.


                        1. lair
                          17.01.2017 01:23

                          Да нет… но стоимость работы (а так же что делать, если он попал под автобус) — это все равно фактор.


                          1. Fortop
                            17.01.2017 01:33

                            Это вопросы не тимлида в общем случае, а техлида.

                            Но админ масштабируется примерно так же как и сервера. И как прочие разработчики.
                            Главное лишнюю бюрократию не разводить, которая крайне замедляет процесс реакции.

                            Вопрос цены актуален, но эффективность таких специалистов на порядок выше чем швецов-жнецов-дудецов из разработчиков.
                            А они в свою очередь могут отлично сосредоточиться на разработке.

                            К сожалению двух команд под рукой не было, чтобы сравнить стоимость и уровень рисков между командой dash-специалистов и командой T-специалистов.


                            1. lair
                              17.01.2017 01:37

                              Это вопросы не тимлида в общем случае, а техлида.

                              Но в стартапах на 2-4 человека, как мы знаем, регулярно это один и то же человек… и на админа там тоже ресурсов нет.


                              В общем, каждый балансирует сам для себя.


      1. mail-online
        17.01.2017 09:44

        Сейчас так нельзя по российскому законодательству. Хранить персональные данные можно только на территории РФ. 152-ФЗ, 242-ФЗ.

        Стоимость же аренды виртуалок например на M8 впечатлит любого. За несколько месяцев аренды можно просто купить аналогичные б\у сервера и разместить их в датацентре по цене 2.5-3 тыс в месяц за 1U. Да и неизвестно какие мощности тебе на самом деле выделяют, не факт что как написано, а тут ты знаешь что у тебя есть.

        В третьих. Нет поддержки второго уровня виртуализации. Я одно время продумывал вариант аренды со спящими виртуалками на случай физического выхода из строя оборудования, но вот просто взять и свою конфигурацию перенести не получится, нельзя у них сделать виртуалку например с проксмоксом и туда скопировать свою структуру. В общем этот вопрос в итоге решается дополнительным железом, где лежат копии виртуалок, которые можно быстро поднять. Это же железо можно юзать для хранения бекапов, логов и т.д.


        1. lair
          17.01.2017 11:54

          Сейчас так нельзя по российскому законодательству. Хранить персональные данные можно только на территории РФ. 152-ФЗ, 242-ФЗ.

          Речь вроде про хранение файлов шла. Ну и да, если вы храните персданные… а вы как оператор зарегистрированы? А система у вас сертифицирована?


          За несколько месяцев аренды можно просто купить аналогичные б\у сервера и разместить их в датацентре по цене 2.5-3 тыс в месяц за 1U.

          … и мы снова возвращаемся к вопросу о (запрошенных вами) неограниченной масштабируемости и распределенном хранилище.


          Нет поддержки второго уровня виртуализации.

          Что такое "второй уровень виртуализации", и зачем он вам нужен при хранении файлов?


          1. mail-online
            17.01.2017 13:00

            1) Причем тут оператор. Этот закон ко всем относится. Если у вас на сайте регистрируется пользователь и он может оставлять какие-то свои персональные данные или данные по своим клиентам, все, только РФ.
            У меня все в комплексе, в том числе файлы. Глупо хранить именно файлы за рубежом отдельно от всего остального…

            Слушал недавно по бфм интервью кого-то из битрикса (не помню уже кто), по этому поводу. У них все на амазоне было и весьма непросто было переехать в РФ. Издержки сейчас выросли на треть вроде из-за этого.

            У операторов по Яровой голова болит…

            2) А что вас в данном случае смущает? Покупайте оборудование и ставьте дальше в датацентре.

            3) Нельзя в облаке делать виртуалки внутри виртуальных машин. Проксмокс например требует от процессора поддержки аппаратной виртуализации, это у хостеров не поддерживается. Т.е. грубо говоря беря в облаке виртуальный сервер, вы не сможете на нем поставить гипервизор и внутри него сделать виртуалки.
            А строить все в облаке с нуля вместо того чтобы скопировать структуру, на мой взгляд это просто мартышкин труд, тем более что это надо сделать просто на всякий случай.


            1. lair
              17.01.2017 13:05

              Причем тут оператор. Этот закон ко всем относится.

              При том, что если вы храните персданные, вы должны соответствовать не только требованию "только в РФ".


              Глупо хранить именно файлы за рубежом отдельно от всего остального…

              "Глупо" изобретать свои велосипеды.


              А что вас в данном случае смущает? Покупайте оборудование и ставьте дальше в датацентре.

              … датацентр бесконечный? Я уж молчу про географическую распределенность.


              Нельзя в облаке делать виртуалки внутри виртуальных машин.

              А зачем для хранения файлов виртуалка? Я больше скажу, для хранения файлов нужен только сервис, который обеспечивает… хранение файлов. Он есть, он работает.


              А строить все в облаке с нуля вместо того чтобы скопировать структуру, на мой взгляд это просто мартышкин труд, тем более что это надо сделать просто на всякий случай.

              "С нуля" — это ваше решение "давайте положим файлы сюда, давайте напишем такой-то код, блаблабла", вместо того, чтобы просто взять готовое решение "заливайте файлы такой-то HTTP-операций, читайте файлы такой-то HTTP-операцией".


              1. mail-online
                17.01.2017 13:19

                О боже, lair… Если вы способны забить своим оборудованием датацентр… я думаю вы просто свой датацентр сделаете, 2 датацентра, 3, 4…

                Хранение файлов это «одно из»…
                Виртуалки затем чтобы не делать двойную работу. В данном примере с облаком — это резервный вариант. У вас поменялась структура и что, вам вместо того чтобы сделать копию надо менять структуру в облаке на аналогичную… это путь в никуда. Резервный вариант должен делаться копией, а не силами разработчиков переписываться.
                Вообще как сказал один мой знакомый сисадмин: «виртуализация, это наше все».


                1. lair
                  17.01.2017 13:22

                  Если вы способны забить своим оборудованием датацентр… я думаю вы просто свой датацентр сделаете, 2 датацентра, 3, 4…

                  Но зачем?


                  У вас поменялась структура и что, вам вместо того чтобы сделать копию надо менять структуру в облаке на аналогичную…

                  Эээ, зачем? Зачем мне вообще менять какую-то "структуру" в облаке?


                  Вообще как сказал один мой знакомый сисадмин: «виртуализация, это наше все».

                  Для него, возможно, это так. Но не для разработчиков.


  1. Fortop
    17.01.2017 00:08
    +1

    1. сколько угодно файловых серверов.
      • Можно разруливать через приложение, которое по функции определяет к какой шарде обращаться
      • Можно разруливать через nginx, где функцию определения шарды реализовать при помощи того же lua
      • Что из себя представляет функция? В простейшем виде, например, остаток от деления айди на количество серверов, или маппинг по регулярным выражениям от хеша имени файла и т.п.
    2. Отдача и проверка прав через скрипт и X-Accel-redirect заголовок
      Техника стара как мир, если погуглить, то можно найти даже решения с expired токенами в ссылке на скачивание
    3. Миграции при добавлении новых шард.
      Аналогично легко гуглится. В простейшем случае это несколько скриптов которые производят вычисление шарды по старому и новому методу (добавление или удаление шарды влечет за собой изменение функции).
      И затем выполняют копирование файлов на новый сервер (как и чем? Например, rsync)
      Процесс верификации может быть или одновременно с копированием (мы проверяем идентичность и доступность файла в новом месте), или отдельной задачей по завершению переноса.
      Если есть ошибки, то повторить копирование и/или выдать сообщение в лог для разбора.
      Процесс удаления старых файлов выполняется только при успешной верификации и так же может идти для каждого файла сразу при копировании, или пакетно для всех по завершении верификации.
    4. бекап выполняется для всей системы сразу и отдельно администратором.
      Бекап отдельного аккаунта иногда целесообразен, если подразумевается активное удаление/добавление файлов самим пользователем и приложение хочет предоставить возможность отката действий самого пользователя.
      В общем случае вместе с отправкой этого бекапа куда-либо это функция экспорта информации. Выполняется отдельным скриптом/модулем приложения.
    5. Компрессия/декомпрессия файлов не относится к общему случаю.
      Объём диска обычно дешевле процессорного времени, если вы не файл/фото хостинг
      Поэтому сжимать изначально — нет смысла, поскольку это относится к оптимизации.
      Лучше потратить время разработки на более критичные фичи.
    6. Компрессия/декомпрессия, и Linux и Windows умеют сжатие разделов целиком. То есть это можно настраивать вне вашего приложения и абсолютно прозрачно для него.
    7. Отдача, nginx умеет кешировать соответственно буде мы решили сжимать файлы самостоятельно и поштучно, то можно настраивать кеширование для распакованных файлов. Удалять nginx будет самостоятельно, следить нам не надо, только выставить нужные сроки инвалидации
    8. Загрузка файлов.
      При наличии множества шард нам придётся реализовать загрузку на выбранную шарду
      Механизм похож на скачивание. Сначала определить на какую шарду поместить хотим, затем залить.
      Можно генерировать служебное имя файла заранее, тогда адрес загрузки вместе с нужным именем шарды отдавать сразу с формой клиенту и он при отправке будет грузить сразу в нужное место.
      Но можно и изначально грузить на сервер приложения и переносить на нужную шарду.
    9. Не вижу проблемы хранения картинок в виде файлов.

    Что касается простоты.
    Да, хранить все в одной БД — проще.
    Это пока единственный аргумент "за".
    Но для случаев, когда "проще" имеет смысл — о масштабировании проще забыть.
    Или то, или другое.


    Более конкретные рецепты реализации зависят от того что именно пишется.
    Какие задачи выполняет приложение и т.д.
    Ну и для почитать http://ruhighload.com/server


    1. mail-online
      17.01.2017 09:59
      -1

      Спасибо за ссылку. Действительно все вместе и по полочкам. Много интересного.

      Да, хранить все в одной БД — проще.
      Это пока единственный аргумент «за».
      Но для случаев, когда «проще» имеет смысл — о масштабировании проще забыть.
      Или то, или другое.


      У меня не в одной БД. Структура такая, каждой компании выделяются аккаунт, каждый сотрудник компании может войти в этот аккаунт (со своими правами конечно). Каждому аккаунту выделяется своя рабочая база данных, файловая база данных, база данных для хранения логов и т.д. Есть база данных со структурой, где хранится у какой компании какая БД и на каком сервере она находится. При авторизации пользователя из базы данных со структурой получаем адресацию хранения его БД, и после он работает уже только с ними.
      На серверах с БД есть скрипты и исходник БД с типовой конфигурацией. При регистрации происходит копирование типовой БД и запись в базу структуры ее адреса нахождения.
      Масштабируемость неограниченная получается.

      По бекапам. Рабочую базу все равно надо бекапить. Бекапить заодно и его файловую базу в данном случае не сложно, фактически один механизм. Ничего дополнительно городить не надо.
      У меня реализован механизм управления бекапом от пользователя, он сам указывает в какие дни, в какой временной диапазон и какие базы бекапить, за сколько дней хранить копии и т.п. Технически — обновляется cron на нужном сервере в соответствии с планом бекапов. На сервере для бекапов работает скрипт, который просматриает по ftp папочки на этих серверах с определенной периодичностью и тянет к себе файлы, удаляет оригиналы бекапов освобождая место.


      1. lair
        17.01.2017 11:55

        Масштабируемость неограниченная получается.

        … и что случится, когда мощности одного сервера перестанет хватать для поддержки аккаунта?


        1. mail-online
          17.01.2017 12:43
          -1

          Такое сложно представить в SaaS концепции. Но если вдруг будет такой совсем «жирный» клиент. Я ему просто выделю отдельное оборудование. Но вообще обычно в этом случае «коробочные версии» делают.


          1. lair
            17.01.2017 12:45

            Такое сложно представить в SaaS концепции.

            Да ладно, легко и непринужденно.


            Но если вдруг будет такой совсем «жирный» клиент. Я ему просто выделю отдельное оборудование.

            … а когда и этого "отдельного оборудования" перестанет хватать?


            1. mail-online
              17.01.2017 13:02
              -1

              … а когда и этого «отдельного оборудования» перестанет хватать?


              Тогда буду решать вопрос в индивидуальном порядке


              1. lair
                17.01.2017 13:06

                Вот и нет никакой "неограниченной масштабируемости".


      1. Fortop
        17.01.2017 13:29

        У меня не в одной БД.

        Подразумевалось «все связанные данные и ресурсы в одной БД»

        Если у вас пользователи не имеют связей между собой, то естественно БД может быть больше одной


    1. mail-online
      17.01.2017 10:17
      -1

      По этому вопросу еще озвучу свои мысли.

      Компрессия/декомпрессия файлов не относится к общему случаю.
      Объём диска обычно дешевле процессорного времени, если вы не файл/фото хостинг
      Поэтому сжимать изначально — нет смысла, поскольку это относится к оптимизации.


      Изначально файл грузится в оригинальном виде. Потом в фоновом режиме проверяется имеет ли смысл вообще его трогать. Для проверки и сжатия можно выделить виртуалку с одним ядром (ограничим влияние на остальные ресурсы), тут нам не важно чтобы все летало, т.к. работа в фоне. Если его можно сжать неплохо то имеет, если сжатие кпеечное, то конечно нет.
      И сжимать только маленькие файлы, извлечение которых процесс очень быстрый и не заметен для пользователя.
      В общем на мой взгляд тут должен быть вопрос целесообразности. Как показывает практика, в процессе работы офиса в основном прикладывается куча небольших файлов (xls, doc, pdf) они неплохо сжимаются, имеют небольшой объем и их отдача из сжатого вида получается весьма быстрой.
      Посмотрим конечно что будет еще при возрастании объемов, если что данный механизм всегда можно отключить и далее хранить в оригинальном виде все.