Як зберігати мільйони файлів з контролем доступу: огляд рішень

Всім привіт! Мене звати Павло. У розробці 25+ років, починав з Object Pascal, потім Unix + C, потім по похилій: Delphi, PHP, HTML5, трошки Java, Go, Rust. Працював практично з усіма СУБД, іноді досить великих розмірів (>10 TB). Останні 8 років — на посаді архітектора в компанії InBase. Один з продуктів компанії — система електронного документообігу " Megapolis Doc.Net (розробляється з 1998 року). Моїм завданням була міграція цієї системи на веб-технології. Власне, про вирішення однієї з проблем, з якою ми зіткнулися, а саме — про пошук оптимального способу зберігання неструктурованої інформації та доступу до неї з урахуванням прав користувачів — я і хочу розповісти.

Стаття може бути корисною як технічним фахівцям компаній-замовників, перед якими стоїть проблема вибору продукту з оптимальною архітектурою сховища документів, так і розробникам інформаційних систем.

Розв'язувані задачі

Наприклад, такі:

Загалом, це — типові завдання, які стоять перед розробниками більшості корпоративних додатків. Найпростіше вирішувати їх як? Правильно, за допомогою бази даних. Але виявилося, що коли документів стає дуже багато, виникають складності. Далі про них детальніше.

Бази даних. У всього є межа

Це, здавалося б, очевидно. Якщо є база даних, то можна спокійно користуватися всіма її благами: цілісністю, транзакционностью, бэкапами і так далі. Тобто архітектура досить проста: є додаток, є база даних, ми в неї пишемо файли у BLOB - поля, і у нас все добре. Треба відкотити транзакцію — відкотили. Треба забэкапиться — забэкапились. І це працює. До пори До часу. Поки є 100 користувачів і 100К документів — все добре. Але коли документів стає більше або кількість користувачів збільшується, починають виникати проблеми (незалежно від обраної СУБД). Розглянемо їх.

Проблема № 1

Чим більше база даних, тим складніше її бэкапить. Зрозуміло, що можна виділити якісь окремі табличні простору для BLOBов, бэкапить частково, але все одно — це складно. І коли ми опиняємося перед фактом, що у нас є табличка, і в ній, припустимо, 100 млн рядків з BLOBами за 200к кожен (при цьому вона не партиционирована), то бэкапить її доведеться всю. Втім, навіть якщо є partition, періодично бэкапить таблицю повністю все одно доведеться. І коли ця табличка «доросте» до пари десятків терабайт, її бекап буде тривати дуже довго.

Проблема № 2

Ще одна проблема баз даних — масштабування. Найчастіше можна легко масштабувати сервери додатків горизонтально: поставити десять або двадцять серверів додатків. Але сервер бази даних горизонтально масштабується дуже погано. Його можна масштабувати тільки вертикально, нарощуючи потужність, додаючи пам'ять, встановивши більш потужний процесор. І рано чи пізно ми зіткнемося в стелю. Після цього починається експонентний ріст ціни питання, або виявиться, що досить потужного обладнання для рішення задачі в природі ще не існує. Додавання шардирования (якщо архітектура спочатку його не передбачала) — теж завдання не з простих.

Таким чином, якщо система буде продовжувати писати все в базу, то врешті-решт це закінчиться тим, що саме база стане найслабшою ланкою. А коли база стає слабкою ланкою, це — дуже погано. З цим практично нічого не можна зробити, і залишиться тільки одне — кинути все і поїхати в Ладакх. Цей варіант вирішення проблеми ми обговоримо пізніше. Коли-небудь. А зараз розглянемо інші опції.

Не зберігай у базі — зберігай у файлах

У результаті виникла ідея винести з бази даних все, що можна винести. Тобто: прибираємо BLOBы з БД, замість них залишаємо текстове поле з інформацією про те, де збережений вміст файлу, сам файл пишемо в файлову систему. Транзакція БД підтвердилася — файл буде доступний користувачам, транзакція відкотилася — файл залишиться у файловій системі як «сміття».

У другому поколінні UnityBase (платформа, на якій працює наш документообіг) так і зробили. Але виявилося, що не все так просто.

Не клади всі файли в одну корзину

Поки було 1000 файлів або 10 000 файлів, проблем не виникало. Все чудово працювало. Файли лежали в одній папці файлової системи, з ними можна було легко працювати, а бэкапить з допомогою robocopy/fsync. Але коли файлів стало більше, дала про себе знати проблема файлових систем — система починає працювати дуже повільно, коли в одній папці багато файлів. Цю проблему потрібно було якось вирішувати.

Очевидне рішення — створювати підпапки. Розкладаємо файлики з теки, шлях до файлів зберігаємо в поле БД.

Схема хороша, але свої підводні камені в ній теж є. Один з них — організація процесу створення папок і розподілу файлів за ним. У першій версії алгоритму ми створювали відразу X папок, далі по колу поміщали в них файли: перший файл поміщаємо в першу папку, другий — в другу і так далі. Біг по колу. Кажуть, це корисно для здоров'я... Чому створювали папки відразу? Тому що цей процес теж займає час. Спочатку треба перевірити, чи є така папка чи ні, і якщо ні, нам треба її створити на рівні файлової системи.

Виявилося, що це — погана ідея, оскільки виникають складності з резервних копій. Для того щоб забекапіть таку структуру, потрібно визначити, які файли змінилися, а для цього необхідно пробігти по всій структурі каталогів. Причому кількість папок може бути досить великим, так як якщо файлів у вас мільйон, то розклавши їх у сто папок, ми отримаємо за 10 000 файлів у кожній. А це — дуже багато. Тому всередині папок першого рівня треба створити ще X підпапок, щоб обійти всі ці ліміти (на платформі UnityBase є побудовані рішення, в яких зберігається близько 70 млн файлів, а це — близько 40 терабайт). Але тоді час бекапа збільшується до непристойності.

Папка останнього дня

В залежності від апетиту замовника, обсягу даних і кількості користувачів можна застосовувати різні стратегії створення папок. Найбільш ефективною виявилася та, яка отримала внутрішню назву «стратегія Daily». Щодня створюється нова папка. В ній, при необхідності, можна створити ряд підпапок, але їх буде не дуже багато. Навіть якщо за день з'явиться 100 000 файлів, достатньо буде створити всього 100 папок, щоб у кожної з них було по 1000. Таким чином, можна легко бэкапить цю одноденну папку. Навіть якщо виникне необхідність бэкапить частіше, досить створювати резервну копію тільки однієї цієї папки, а все решта вже не міняються, вони в архіві.

У такої стратегії є ще один бонус: можна легко переносити старі папки з швидких дисків на повільні. Навіщо це потрібно? Все просто: SSD коштують дорого, а SATA хоч і повільніше, але коштують дешевше, і на них можна перемістити старі файли, якими користуються рідко.

Профіт

Віддача файлів

Віддавати файли можна по-різному. У Linux є можливість використовувати функцію ядра sendfile. В Windows, на рівні API HTTP.SYS можна віддати файл за файловим дескриптору. Можна самому читати файл і писати в сокет (до речі, при зберіганні файлів у БД це, напевно, єдиний варіант). Варіантів багато, і, здавалося б, можна з цим не перейматися. Але не все так просто. Клієнт може підтримувати докачку файлів, стиснення, часткове завантаження і т. д., і все це імплементувати досить складно.

Оскільки в нашому випадку віддача файлів йде по HTTP, є й інший шлях: поставити зворотний проксі перед сервером додатків і використовувати прекрасну фічу: X-Accel-Redirect (nginx)/x-sendfile(Apache). Схема стає простою і крос-платформної: клієнт просить файл, сервер додатків перевіряє права доступу (може бути складна логіка, може бути простий RLS), і якщо все ОК — віддає шлях до файлу в спеціальному заголовку HTTP-відповіді. Як NGINX/Apache віддасть файл — це вже не наша проблема: він його віддасть. Що це дає? Те, що сервер додатків взагалі не витрачає ресурси на передачу файлу.

Дана схема дозволяє обслуговувати досить багато людей. На звичайних впровадження навряд чи виникне питання продуктивності цієї схеми — швидше, продуктивності мережі. У практиці InBase бували випадки, коли 300-мегабітний мережу виявлялася забита намертво з-за великої кількості користувачів і файлів. Багато — це тисячі одночасних користувачів. При цьому сервер додатків був не особливо навантажений. Проксі-сервер. Завантажена мережу.

Хмарні зберігання. Добре, але не завжди

Звичайно, можна користуватися Amazon S3 (Microsoft, Google etc). Прекрасні сервіси! Але є, знову-таки, нюанси, при яких зберігати дані в хмарі неможливо або не раціонально:

Стиснення, реплікація та дедупликация

Чому це важливо? Зі стисненням зрозуміло — стискати засобами файлової системи не так вже складно, але можна добре зекономити на обсязі дисків. З дедупликацией не зовсім очевидно. Візьмемо, приміром, PDF. Жати його не дуже ефективно. Але кожен PDF-A містить у собі файл шрифту. Сам файл не стискається, але коли файлова система підтримує дедупликацию, всі ці шматочки зі шрифтом будуть лежати на диску в одному примірнику. І тоді кожен з цих PDF буде займати на 20 Кб менше. Так що якщо файлова система підтримує дедупликацию, гріх цим не скористатися. При цьому ми практично не втрачаємо в продуктивності.

Що стосується реплікації, то дуже багато файлові системи її підтримують, і в деяких випадках ми можемо взагалі не паритися з бэкапами, а просто використовувати, наприклад, Btrfs/CephFS/NTFS etc — всі вони підтримують можливість відразу перекинути тільки що створений файл на віддалений хост і зробити там його копію. Профіт: у нас вийде автоматичний фейловер. Бэкапить все одно потрібно. Якщо прийде якийсь Petya і зашифрує файли на майстра, вони успішно реплікуються на підлеглий вузол, і тоді врятує тільки бекап. Але можна бэкапить рідше. Ну і так — CephFS. приміром, розрахована на петабайтные сховища.

У сухому залишку

Використання файлової системи для зберігання неструктурованої інформації замість BLOBов бази даних дає нам безліч переваг. Особливо у випадку, коли кількість файлів постійно зростає. Але якщо раптом нам захочеться використовувати, наприклад, Amazon S3, існують рішення у вигляді проксі, які перетворять виклики до файлової системи в виклики API S3. Можна поставити такий проксі і, фактично, працювати зі своєю файловою системою, а з іншого боку, це буде S3 сховище.

Залишається тільки побажати всім терабайти добра і безхмарного зберігання (хоч це і звучить дещо двозначно).

Опубліковано: 03/04/20 @ 07:00
Розділ Різне

Рекомендуємо:

Здоров'я ІТ-спеціаліста: синдром сухого ока, спазм акомодації і короткозорість
Огляд ІТ-ринку праці: Запоріжжя + ВІДЕО
Як менеджеру справлятися зі страхом і невпевненістю
Go дайджест #13: реліз Go 1.14, нове API для Protobuf
Jadi Raja Judi Poker Dunia, Pria Asal Medan Ini Bawa serta Pulang Uang 28 Miliar dari Amerika