Лучший способ хранения большого количества данных пользователей

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

/username/file01.jpg
/username/file02.mp4
/username/file03.mp3

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

Что может быть лучшим способом сделать это. У меня есть одно решение, но вы хотите спросить сообщество, это лучший способ.

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

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

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

Edit:

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

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

Ответ 1

Я обрабатываю метаданные файлов в базе данных и извлекаю файлы с помощью UUID. Что я делаю:

  • Идентификация на основе контента
    • MD5 из содержимого файла
    • Namespaced UUID: v5 для генерации уникального идентификатора на основе пользовательского uuid и файла md5.
    • Пользовательская функция для генерации пути на основе "реального имени".
    • Сохранить в базе данных: uuid, originalname (загруженное имя), realname (сгенерированное имя), размер файла и mime. (необязательный dateAdded и md5)
  • Восстановление файла.
    • UUID для извлечения метаданных.
    • восстановить путь к файлу на основе имени.
    • Исходное имя используется для отображения знакомого имени пользователю, загружающему файл.

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

$fileInfo = pathinfo($_FILE['file']['name']);
$extension = (isset($fileInfo['extension']))?".".$fileInfo['extension']:"";

$md5Name = md5_file($_FILE['file']['tmp_name']); //you could use other hash algorithms if you are so inclined.

$realName = UUID::v5($user->uuid, $md5Name) . $extension; //UUID::v5(namespace, value).

Я использую функцию для создания пути к файлу на основе некоторых настраиваемых параметров, вы можете использовать $username и $realname. Это полезно, если вы реализуете структуру распределенных папок, которую вы могли бы разделить на схему именования файлов или любую пользовательскую схему.

function generateBasePath($realname, $customArgsArray){
    //Process Args as your requirements.
    //might as well be  "$FirstThreeCharsFromRealname/"
    //or a checksum that helps you decide which drive/volume/mountpoint to use.
    //like some files on the local disk and some other from an Amazon::S3 mountpoint.
    return $mountpoint.'/'.$generatedPath; 
}

В качестве дополнительного бонуса это также:

  • поможет вам поддерживать репозиторий с версиями файлов, если вы добавите в файл записи какой-либо файл (uuid), который он заменил.
  • создать приложение Access Control List, если вы добавите атрибуты "владелец" и/или "группа"
  • также работает с единой структурой папок.

Примечание. Я использовал php $_FILE в качестве примера источника файла на основе этих тегов вопроса. Это может быть любой источник файла или сгенерированный контент.

Ответ 2

Не могли бы вы создать реляционные таблицы MySQL? например:.

A users и таблица files.

Таблица ваших пользователей будет отслеживать все, что вы (я предполагаю) уже отслеживает:

id, name, email и т.д.

Затем таблица файлов сохранит что-то вроде:

id, fileExtension, fileSize, userID < ---- userID будет внешним ключом, указывающим на поле id в таблице files.

то при сохранении файла вы можете сохранить его как id. fileExtension и использовать запрос, чтобы вытащить пользователя, связанного с этим файлом, или всех файлов, связанных с пользователем.

например:.

SELECT users.name, files.id, files.extension
FROM `users`
INNER JOIN `files` on users.id = files.userID;

Ответ 3

Поскольку вы уже используете MongoDB, я бы предложил проверить GridFS. Это спецификация, которая позволяет хранить файлы (даже если они больше 16 МБ) в коллекциях MongoDB.

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

Ответ 4

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

В качестве примера возьмем пользователя Daniel Steiner (me), который загружает файл с именем resume.doc 23 апреля 2013 года в 37 последних двенадцати часов на ваш сервер. это даст базовую ценность Daniel_Steiner + 2013/23/04 + 00: 37 + resume.doc, который тогда будет как хеш MD5 05c2d2f501e738b930885d991d136f1e. чтобы файл был открыт в правой программе, мы потом добавим нужный файл и, таким образом, получим что-то вроде http://link.to/your/site/05c2d2f501e738b930885d991d136f1e.doc Если ваши учетные данные уже имеют идентификатор пользователя, вы можете добавить их к URL-адресу, например, если мой идентификатор пользователя будет 123145, URL будет http://link.to/your/site/123145/05c2d2f501e738b930885d991d136f1e.doc

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

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

Если вы хотите, я мог бы придумать пример PHP - не должен быть слишком много кода.

Ответ 5

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

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

Ответ 6

Другая тактика - создать двумерную структуру, где первый уровень каталогов - первые 2 символа имени пользователя, тогда второй уровень - остальные символы (подобно тому, как Git хранит свои идентификаторы объектов SHA-1). Например:

/files/jr/andomuser/456.jpg

для пользователя jrandomuser.

Обратите внимание, что поскольку имена пользователей, скорее всего, не будут распространяться так же, как и значения SHA-1, возможно, вам придется добавить еще один уровень позже. Однако, сомневайтесь.

Ответ 7

Я предлагаю использовать следующую структуру базы данных:

enter image description here

Где File таблица имеет как минимум:

enter image description here

IDFile - это столбец auto_increment/первичный ключ. UserID является nullable внешним ключом.

Для FK_File_User я предлагаю:

ON UPDATE NO ACTION -- IDUser is auto_increment too. No changes need to be tracked.
ON DELETE SET NULL  -- If user deleted, then File is not owned. Might be deleted
                    -- with CRON job or something else.

Тем не менее, в таблицу File можно добавить другие столбцы:

  • Фактическая дата и время загрузки
  • Фактический тип mime
  • Фактическое место хранения (для распределенных систем хранения)
  • Количество загрузок (другая таблица может быть лучшим решением)

и т.д...

Некоторые преимущества:

  • Вам не нужно вычислять размер файла, хеш, расширение или метафайл файла, поскольку вы можете получить его с помощью одной операции с базой данных.
  • Вы можете получить статистику для каждого пользователя используемого количества файлов/пробелов/того, что вы написали в таблице File, одним оператором SELECT ... GROUP BY ... WITH ROLLUP, и это будет быстрее, чем анализ фактических файлов, которые могут быть распространены несколько устройств хранения.
  • Вы можете применять разрешения доступа к файлам для разных пользователей. Это будет стоить незначительное изменение базы данных структур таблиц.

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

  • Файл может иметь имя, которое неправильно поддерживается файловой системой OS сервера, например, кириллицей.
  • Два разных файла могут иметь полностью идентичные имена, поэтому один из них может быть перезаписан другим.

Итак, есть решение:

1) Переименуйте файлы, когда они загружены в IDFile из INSERT в таблицу File. Это безопасно, и нет дубликатов.

2) Восстановите имя файла, когда он понадобится/скачан, например:

// peform query to "File" table by given ID

list($name, $ext, $size, $md5) = $result->fetch_row();

$result->free();

header('Content-Length: ' . $size);
header('Content-MD5: ' . $md5);
header('Accept-Ranges: bytes');
header('Connection: close');
header('Content-Type: application/force-download');
header('Content-Disposition: attachment; filename="' . $name . '.' . $ext . '"');

// flush file content

3) Фактические файлы могут храниться в одной директории (поскольку IDFile является безопасным) и подкаталог IDUser -named - зависит от ситуации.

4) Поскольку IDFile является прямой последовательностью, если некоторые из файлов пропали без вести, вы можете получить их метаданные базы данных, оценив недостающие сегменты фактической последовательности имен файлов. Затем вы можете "сообщить владельцам", "удалить метафайл файла" или оба этих действия.


Я против идеи хранения больших фактических файлов в СУБД как бинарного содержимого.

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

Ответ 8

Вы можете установить LDAP-сервер. Поиск LDAP выполняется очень быстро, так как он оптимизирован для работы с большим количеством операций чтения. Вы даже можете запросить данные

LDAP организует данные в дереве, как мода.

Вы можете организовать данные в следующем примере: "user- > IP address- > folder- > имя файла". Этот файл может быть физически/географически распространен, и вы можете быстро найти местоположение.

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

Ответ 9

  • Mongodb для хранения фактического имени файла (например: myImage.jpg) и других атрибутов (например: MIME-типы), плюс $random-text.jpg от 2. и 3. ниже

  • Сгенерируйте несколько $random-text, например: base_convert(mt_rand(), 10, 36) или uniqid($username, true);

  • Физически сохранить файл как $random-text.jpg - всегда полезно поддерживать такое же расширение

  • ПРИМЕЧАНИЕ. Используйте filter_var(), чтобы гарантировать, что входное имя файла не создает угрозу безопасности для Mongodb.

Amazon S3 является надежным и дешевым, знайте "Eventual Concurrency" с S3.

Ответ 10

Предполагая, что пользователи имеют уникальный идентификатор (первичный ключ) в базе данных, если пользователь с идентификатором 73 загружает файл, сохраните его следующим образом:

"загрузки/$userid_ $имя_файла. $Внутр"

Например, 73_resume.doc, 73_myphoto.jpg

Теперь, при извлечении файлов, используйте этот код:

foreach (glob("uploads/$userid_*.*") as $filename) {
    echo $filename;
}

Это можно объединить с хэш-решениями (хранимыми в БД), так что пользователь, который получает путь загрузки, как 73_photo.jpg, не случайно пытается 74_photo.jpg в адресной строке браузера.