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

Соблазнительная ловушка «Мы можем сделать это сами»

Дело в том, что файловые системы обманчиво просты в теории. Сохранять данные. Извлекать данные. Что может пойти не так? Как выясняется, всё. И всё же каждый год команды по всей отрасли убеждают себя, что их вариант использования достаточно особенный, чтобы оправдать индивидуальную реализацию. Спойлер: это редко так.

Привлекательность понятна. Вы устали от ограничений текущего решения. Видите возможности для оптимизации. Думаете: «Если мы просто будем контролировать весь стек, то сможем создать что-то красивое и производительное». Затем реальность ударяет вас как SIGSEGV без трассировки стека.

Скрытый айсберг сложности

Когда вы говорите о «написании файловой системы», большинство разработчиков представляют себе организацию данных в файлы и каталоги. Достаточно просто, верно? Вы уже представляете себе счастливый путь, когда всё отлично работает на вашем компьютере с тестовыми данными.

Что вы не представляете — и что потребует 80% ваших усилий при разработке — это всё остальное:

Управление параллелизмом: два процесса должны одновременно записать данные в один и тот же файл. Что произойдёт? В наивной реализации вы получите повреждение или потерю данных. В профессиональной системе вам нужны механизмы блокировки, журналы транзакций, возможности отката и тщательная оркестровка операций чтения/записи. Это не небольшая функция, которую можно добавить; это архитектурная ДНК.

Целостность и согласованность данных: ваша система вышла из строя во время записи. Находятся ли ваши данные в согласованном состоянии? Как вы это узнаете? Реляционные базы данных решили эту проблему через свойства ACID — годы исследований и тысячи исправленных ошибок. Когда вы реализуете собственную систему, вы заново изобретаете эти колёса с нуля, за исключением того, что ваши колёса восьмиугольные и одно из них в огне.

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

Безопасность и контроль доступа: кто может читать что? Кто может писать куда? Может ли кто-то использовать ваш формат для повышения привилегий? Это проблемы, над решением которых проектировщики операционных систем тратят карьеры, и даже они иногда ошибаются. Ваш проект выходного дня почти наверняка ошибётся с первого дня.

Техническое минное поле: пример из реальной жизни

Давайте представим сценарий. Вы создаёте уровень сохраняемости данных для своего приложения. Вы решаете использовать плоскую структуру файлов — просто JSON-файлы в каталогах. Просто! Быстро! Что может пойти не так?

// Ваша первоначальная реализация
function saveUser(userId, userData) {
  const path = `./users/${userId}.json`;
  fs.writeFileSync(path, JSON.stringify(userData));
}
function getUser(userId) {
  const path = `./users/${userId}.json`;
  const data = fs.readFileSync(path, 'utf8');
  return JSON.parse(data);
}

Поздравляю, вы только что создали нечто, что:

  • Бесшумно терпит неудачу, если запись прерывается
  • Оставляет повреждённые файлы, если два процесса записывают одновременно
  • Не имеет транзакций — если ваше приложение вылетает во время сохранения, запись становится мусором
  • Обеспечивает нулевой контроль доступа — прочитали исходный код? У вас есть доступ
  • Плохо масштабируется, когда у вас тысячи записей
  • Требует полного сканирования файлов для поиска чего-либо
  • Не может обрабатывать связи между данными без ручного соединения
  • Ломается, если ваша структура JSON неожиданно меняется
  • Не предоставляет журнал аудита или версионирование

Теперь вы можете сказать: «Но я добавлю эти функции!» Конечно. Вы потратите следующие шесть месяцев на реализацию того, над чем SQLite совершенствовал пятнадцать лет, и всё равно упустите крайние случаи, которые исследователь безопасности найдёт за тридцать минут.

Иллюзия производительности

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

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

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

// Ваша оптимизированная функция запроса
function findUsersByCity(city) {
  const files = fs.readdirSync('./users');
  return files
    .map(file => {
      const data = fs.readFileSync(`./users/${file}`, 'utf8');
      return JSON.parse(data);
    })
    .filter(user => user.city === city);
}
// Это читает КАЖДЫЙ файл. С миллионом пользователей это займёт минуты.
// Реальная база данных использует индексы. Она возвращает данные мгновенно.

Архитектурная сложность, которую вы не учли

┌─────────────────────────────────────────────────────────┐
│ Требования к вашей файловой системе                     │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ Организация данных                                   │ │
│  │ • Индексирование                                      │ │
│  │ • Партиционирование                                  │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                         │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ Управление параллелизмом                            │ │
│  │ • Механизмы блокировки                               │ │
│  │ • Управление транзакциями                           │ │
│  │ • Уровни изоляции                                    │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                         │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ Надёжность                                           │ │
│  │ • Восстановление после сбоя                          │ │
│  │ • Валидация данных                                   │ │
│  │ • Резервное копирование/репликация                   │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                         │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ Безопасность                                         │ │
│  │ • Аутентификация                                     │ │
│  │ • Авторизация                                        │ │
│  │ • Шифрование                                         │ │
│  │ • Журналирование аудита                              │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                         │
│  ┌─────────────────────────────────────────────────────┐ │
│  │ Производительность                                   │ │
│  │ • Оптимизация запросов                               │ │
│  │ • Стратегии кэширования                              │ │
│  │ • Управление памятью                                 │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                         │
└─────────────────────────────────────────────────────────┘

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

Реальные затраты помимо времени на разработку

Давайте поговорим о фактическом экономическом воздействии этого решения:

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

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

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