Пора заменить MySQL и PostgreSQL на SQLite

Источник: «Is it time to ditch MySQL and PostgreSQL for SQLite?»
В последнее время SQLite вызывает всеобщий ажиотаж, но действительно ли он может конкурировать с MySQL и PostgreSQL в продакшне веб-приложений?

Оглавление

Хорошо, я слышу вас: "SQLite — это игрушечная база данных", "SQLite слишком медленный", "SQLite не масштабируется", и так далее, и тому подобное.

На дворе 2024 год, и всё изменилось. SQLite в тренде, его продвигают Kent C. Dodds и DHH, нативная поддержка добавлена в Bun, и если вы следите за новостями хакеров, то наверняка заметили увеличение количества постов с восхвалением SQLite за последние несколько лет.

Это не новость, SQLite существует уже более 20 лет и является самым распространённым движком баз данных, но его игнорируют для веб-приложений в пользу клиент-серверных баз данных, лучше работающих с приложениями, требующими интенсивной записи.

Стоит ли следовать за шумихой и начинать использовать SQLite в продакшне? И если да, то как правильно это сделать? Давайте выясним это вместе.

Зачем выбирать SQLite вместо MySQL или PostgreSQL

Усталость от DevOps реальна, от разработчиков ожидают всё больше знаний об инфраструктуре, CI/CD, Docker, сетевых технологиях, Kubernetes, кластеризации, шардинге, и список можно продолжать. Но реальность такова, что большинству проектов не нужна и половина всего этого. В современной веб-разработке наблюдается откат от сложности, сопровождающийся возвращением монолита и неуклонным ростом более простых фронтенд-фреймворков, таких, как Alpine.js и htmx.

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

Так почему же вы можете выбрать SQLite, а не MySQL или PostgreSQL? Вот некоторые из основных причин:

SQLite проще

SQLite уже входит в стандартную поставку PHP и Python, и если вы используете другой язык бэкенда, есть все шансы, что он поддерживается им.

По своей природе это встроенная библиотека на языке C, вам не нужно управлять процессом, сервером или сокетом. Её можно рассматривать как расширение языка, которое пишет/читает файл .sqlite, когда вы его об этом попросите, и всё.

Поскольку она уже поставляется в комплекте с любимыми нами языками, это значительно упрощает процесс релиза. Не нужно беспокоиться об установке и поддержке сервера базы данных в локальном окружении разработчиков, вы получаете более простые конвейеры CI/CD, если у вас установлен PHP/Python, то у вас есть работающая база данных.

Для сравнения, MySQL и PostgreSQL потребуют от вас установки, обычно из менеджера пакетов, управления доступом пользователей, добавления в качестве зависимости в конвейеры CI/CD и, наконец, повторения всего этого на новом рабочем сервере, которым вам нужно будет управлять и обеспечивать безопасность, — в общем, вы поняли.

SQLite переносим

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

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

# Просто переместить
$ rsync database.sqlite user@server:/path/to/database.sqlite

# Легко создать копию
$ cp database.sqlite database.backup.sqlite

# Легко удалить
$ rm database.sqlite

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

Перенесите его с Windows на macOS, на CI/CD конвейеры Linux на Docker, на Raspberry Pi, на VPS, в бессерверную функцию — она будет работать без каких-либо изменений.

Интересный факт: создатели SQLite описали его как бессерверную базу данных в 2007 году, что было за 7 лет до выхода AWS Lambda.

SQLite быстр

Да, бенчмарки всегда следует воспринимать с долей сомнения, но вывод таков: SQLite достаточно быстр для большинства малых и средних веб-приложений. Более того, в надлежащих обстоятельствах он может быть даже быстрее, чем MySQL или PostgreSQL.

Я сделал упрощённый бенчмарк на базовом приложении Laravel, с локальной базой данных MySQL по умолчанию и слегка настроенной базой данных SQLite со следующими тестами:

Бенчмарк выполняется на таблице из 5 столбцов со следующей структурой:

Я выполнил этот бенчмарк на своём ноутбуке, ThinkPad extreme gen 3 с процессором Intel i7-10850H и 32 Гб оперативной памяти. Но вы можете попробовать сами, используя код в этом репозитории GitHub.

Сравнение производительности MySQL и SQLite
Сравнение производительности MySQL и SQLite

Как видите, SQLite быстрее MySQL, за исключением массовых записей. Это упрощённый подход с небольшим объёмом данных. Суть в том, что в большинстве случаев SQLite работает достаточно быстро, и мы только потрогали поверхность того, что можно сделать для его оптимизации.

SQLite надёжен

Высокая надёжность — один из главных аргументов в пользу SQLite. У вас наверняка есть сотни баз данных SQLite на ноутбуке и телефоне, они также используются в авиации.

Что ещё более важно, SQLite тщательно протестирован: впечатляющие 100% Modified Condition/Decision Coverage (MC/DC) и более 2 миллионов тестов с соотношением 590 тестовых строк на каждую строку кода.

Разработчики SQLite создали собственную систему контроля версий под названием Fossil для управления разработкой самого SQLite.

SQLite экономически эффективен

Как уже говорилось, SQLite уже поставляется в комплекте с распространёнными бэкенд-языками, поэтому вам не нужно платить за отдельный сервер баз данных. Вы также можете установить MySQL или PostgreSQL на сервер, на котором размещён ваш код, но это не совсем то, что следует делать.

Благодаря своей простоте вам также потребуется меньше времени DevOps для управления / обеспечения безопасности / масштабирования = меньше денег, потраченных на DevOps.

SQLite легко защитить

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

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

Может ли SQLite подойти как база данных для проекта в продакшене

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

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

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

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

Этот пункт не относится к SQLite, но является дружеским напоминанием о переходе на любую новую технологию. Наступит момент, когда вы застрянете и нужно будет выяснить, как исправить ошибку или что-то сделать. Несмотря на то, что SQLite довольно близка к MySQL и PostgreSQL, есть различия в поведении, в основном в транзакциях и миграциях, о которых нужно помнить, чтобы не загубить своё приложение.

Если вы умеете работать с клиент-серверными базами данных, у вас уже есть CI/CD конвейеры, резервное копирование и инфраструктура, вы получите незначительные преимущества от SQLite.

Не масштабируется по горизонтали

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

База данных SQLite может содержать до 281 ТБ данных, но, как советует документация, если вы планируете увеличивать свою базу данных до терабайтов, лучше использовать централизованную клиент-серверную базу данных.

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

LiteFS использует файловую систему FUSE для перехвата запросов SQLite, отправляемых вашим приложением. Затем она реплицирует изменения между вашими экземплярами через HTTP-сервер.

Более подробное описание работы LiteFS можно найти в документации по архитектуре проекта.

Хотя это прекрасно работает и обеспечивает невероятную производительность в приложениях с интенсивным чтением, это также лишает SQLite многих преимуществ. Вам нужно позаботиться о процессе LiteFS на ваших серверах и защитить порты, используемые им для связи между репликами. Использование FUSE также означает, что транзакции записи ограничены ~100 в секунду, что может оказаться решающим фактором для приложений, интенсивно работающих с записью.

Упрощённая схема LiteFS
Упрощённая схема LiteFS

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

LiteFS стабильна и используется в производстве, в частности, на Fly.io, но всё ещё находится в стадии бета-версии. Поэтому вы можете столкнуться с ошибками или критическими изменениями в API.

Ограничение параллелизма

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

Хотя по умолчанию это так, но благодаря режиму журнала Write-Ahead Logging (WAL) это не такое большое ограничение, как вы думаете.

Что такое режим журнала, спросите вы? Не бойтесь, я объясню вам это простым языком.

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

Это делается для гарантии того, что если что-то пойдёт не так во время операции записи, база данных может быть восстановлена в прежнее состояние, что обеспечивает соблюдение ACID свойств SQLite.

Когда запрос на запись будет полностью выполнен, SQLite удалит ранее созданный файл журнала.

Режим журнала SQLite по умолчанию
Полный процесс немного сложнее с 3 инкрементными механизмами блокировки, но суть такова.

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

Вход в режим журнала Write-Ahead Logging (WAL). В этом режиме SQLite выполняет обратную операцию, записывая запрашиваемое изменение в файл журнала первым, избегая блокировки таблицы. Таким образом, одновременные запросы на чтение могут выполняться над основными данными, пока выполняется транзакция записи. Затем выполняется задача согласования для объединения данных в журнальном файле с основной базой данных, которая выполняется SQLite автоматически.

Режим журнала SQLite WAL
Режим WAL не лишён недостатков, но это лучший выбор по умолчанию для большинства веб-приложений.

Режим журнала может быть включён одной инструкцией PRAGMA и сохраняется после установки в базе данных:

sqlite3 mydb.sqlite "PRAGMA journal_mode = wal"

Ограниченная поддержка миграции

Модифицировать схему приложения в SQLite очень сложно: существует всего четыре команды, которые могут изменить таблицу:

Если вам нужно сделать что-то большее, например, изменить тип столбца или добавить внешний ключ в существующую таблицу, вам придётся проявить изобретательность.

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

Существуют также отдельные инструменты, такие как sqlite-utils или golang-migrate, позволяющие создавать плавные сценарии миграции.

Ограничения типов данных

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

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

Этого достаточно для хранения чего угодно, например, если вам нужно хранить дату, вы можете поместить её в формате ISO 8601 в столбец TEXT или в виде timestamp в столбец INTEGER.

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

sqlite> CREATE TABLE test(id INT, datetime TEXT);
sqlite> INSERT INTO test(id, datetime) VALUES(1, '2024-01-01 01:01:01');
sqlite> SELECT date(datetime) FROM test;
2024-01-01

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

Использование SQLite в продакшене, в реальном мире

SQLite используется на миллиардах устройств, но не так популярен для веб-приложений по указанным выше причинам. Есть несколько заметных крупных компаний, использующих его в качестве основной базы данных, например Expensify и недавно появившаяся Tailscale.

Я не буду перечислять все веб-приложения для малого и среднего бизнеса, работающие на SQLite, но отличным примером могут служить Nomadlist и Remoteok Питера Левелса, которые работают на SQLite на VPS, обрабатывая 50M+ запросов в месяц всего за $40. Так что если вы считаете, что ваше приложение не будет масштабироваться вертикально, подумайте ещё раз.

Заключение

Как и в случае с любым другим решением в области разработки программного обеспечения, универсального решения не существует, и вам придётся тщательно оценить все плюсы и минусы SQLite для ваших конкретных нужд. Большинство разработчиков склонны переоценивать количество ресурсов, необходимых для работы проекта, и недооценивать производительность и преимущества простых "менее блестящих" решений, таких как SQLite.

Многие веб-приложения выиграют от использования встроенной базы данных, и если вы не планируете горизонтальное масштабирование или хранение данных объёмом более 1 ТБ, то лучше выбрать SQLite.

А если возникнет необходимость, миграция с SQLite на MySQL или PostgreSQL не будет слишком болезненной.

Дополнительные материалы

Предыдущая Статья

Валидация в Laravel стала проще

Следующая Статья

TypeScript vs JavaScript — Детальное сравнение