Я написал серверную SQLite +14


Меня зовут Бен Джонсон, и я написал встраиваемую базу данных, которая служит бэкендом систем вроде etcd, — это BoltDB. Сегодня я работаю над Open Source проектом Litestream в компании Fly.io. Благодаря репликации Litestream делает SQLite приемлемым для фулстек‐приложений. Если вы можете установить SQLite, то Litestream заставите работать за 10 минут.


Фулстек‐приложения обладают настолько привычной многослойной архитектурой, что легко забыть, что она вообще как‐то называется. Эта архитектура возникает, когда вы запускаете сервер приложений типа Rails, Django или Remix вместе с сервером БД, например Postgres. 

По общепринятым представлениям SQLite в архитектуре занимает своё место, это место — юнит‐тесты. Представления пора дополнить. Думаю, во многих приложениях с большим числом пользователей и высокими требованиями к доступности у SQLite есть место лучше: в центре стека, в качестве ядра данных слоя хранения. Притязания большие, и они могут не подходить приложению, но вы должны подумать об SQLite. Я здесь, чтобы рассказать, зачем.

Краткая история баз данных

50 лет — недолгий срок, за который мы увидели много поразительных изменений в том, как программы управляют данными. В 70‐е, в начале истории баз данных, появились правила Кодда, давшие определение тому, что сейчас называется реляционными базами данных и базами данных вообще. 

Эти правила вы знаете: все данные располагаются в таблицах, таблицы имеют колонки, а строки адресуются ключами. Есть CRUD, схемы данных и, конечно, SQL — буквальный язык передачи всех этих понятий и причина кембрийского взрыва баз данных в 80–90‐х: от DB2 и Oracle до Postgres и MySQL. 

Не всё шло хорошо. 2000‐е принесли нам базы данных XML, хотя за это время индустрия искупила свою вину несколькими колоночными БД. В 2010‐х мы увидели десятки крупномасштабных распределённых баз данных Open Source — и теперь создать кластер и запрашивать терабайты данных может каждый. 

Вместе с базами развивались стратегии их подключения к приложениям. Почти со времён Кодда мы разделили приложения на слои, и первым из них стали именно базы данных. С появлением memcached и Redis мы получили слои кеширования и фоновых задач, слои маршрутизации и распределения. 

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

Наши учёные сильно озабочены тем, смогут ли они сделать нечто. За те же полвека процессоры, диски и память стали в сотни раз быстрее и дешевле, а инновации 2010‐х на практике определил термин «большие данные». 

Но развитие аппаратной части десять лет спустя сделало это понятие скользким: управлять базой на гигабайт в 1996 — большое дело. А в 2022? Просто запустите её на ноутбуке или t3.micro.

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

Долгожданная БД

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

SQLite — встраиваемая БД. В слое архитектуры вы её не найдёте. Это просто библиотека, связанная с процессом сервера приложений, стандартная подпорка «приложений с одним процессом». И это сервер, который выполняется сам по себе, не полагаясь на ещё девять. 

Подобные приложения заинтересовали меня, поэтому я разработал BoltDB — популярное в экосистеме Go хранилище данных типа ключ‐значение. BoltDB надёжна и бегает, как раскрашенный спорткар на азоте, а именно этого мы ждём от базы внутри процесса. 

Но схема данных BoltDB определяется кодом на Go, так что на неё трудно мигрировать, и нужно написать всю обвязку, ведь нет даже REPL. Если вы будете внимательны, такая база может дать большую производительность. Но запускать базу для универсального применения, как спорткар без выхлопной системы, не хочется. 

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

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

Знакомьтесь, Litestream

По двум серьёзным причинам SQLite не используется по умолчанию. Первая — это неустойчивость к сбоям, а вторая — масштабирование конкурентности. И Litestream есть что сказать по этому поводу: он берёт на себя WAL‐журналирование SQLite. 

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

Litestream вступает в середине: он открывает бесконечную транзакцию чтения, поэтому SQLite не создаёт контрольных точек. Мы перехватываем обновления WAL, реплицируем их и запускаем создание контрольной точки. Самое важное — понять, что Litestream — это просто SQLite. 

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

Код по большей части может забыть о Litestream. Вы можете создать приложение Remix, которое поддерживается Litestream‐репликацией, а во время работы приложения взломать базу, изменив её стандартным REPL sqlite3. Прочитать об этом больше можно здесь

Это звучит сложно, но на практике невероятно просто. Поиграв с кодом, вы увидите, что он просто работает. На сервере базы в режиме «репликации» вы запускаете бинарник Litestream:

litestream replicate fruits.db s3://my-bukkit:9000/fruits.db

И восстанавливаете репликацию в другом месте:

litestream restore -o fruits-replica.db s3://my-bukkit:9000/fruits.db

Зафиксируйте изменения. Вы увидите их после восстановления в новой копии.

Реплицировать можно почти везде: на S3 или Minio, Azure, или Backblaze B2, на Digital Ocean, Google Cloud, или какой‐нибудь SFTP‐сервер. 

Сегодня люди обычно используют Litestream, чтобы реплицировать базу на S3: для большинства баз данных SQLite репликация в реальном времени на S3 обходится совсем недорого, что само по себе — большая операционная победа: база устойчива настолько, насколько вы попросите, а перемещать её, мигрировать и работать с ней легко. 

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

Отнеситесь к этому варианту серьёзнее

Одна из первых моих работ в 2000‐x — администратор баз данных Oracle9i. Помню, как часами корпел над документацией и книгами, изучая Oracle снаружи и внутри. Руководство по администрированию почти в тысячу страниц — одно на более чем сотню руководств. 

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

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

Современная Postgres — это чудо, и за годы чтения её кода я многому научился. У этой базы много функций: генетическая оптимизация запросов, политики безопасности для строк и полдюжины типов индексов. Если эти функции нужны вам, то они нужны. Но, наверное, они не нужны большинству, а ненужные функции мешают. 

Даже если вы не используете несколько аккаунтов, придётся настроить и отладить аутентификацию по имени хоста, а ещё отключить брандмауэр от сервера Postgres. Документация Postgres содержит около 3000 страниц. Больше функций — больше документации, затрудняющей понимание инструментов, с которыми вы работаете.

SQLite обладает частью набора функций Postgres, эта часть покрывает 99,9% ваших нужд: прекрасная поддержка SQL, оконные функции, общие табличные выражения, полнотекстовый поиск, JSON. А если какой‐то функции нет, данные лежат совсем рядом с приложением, так что вытащить и обработать их в коде — это немного накладных расходов.

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

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

Свет чертовски медленный

Вы столкнётесь с теоретическими пределами. В вакууме свет за тысячную долю секунды преодолевает 186 миль, — расстояние от Филадельфии до Нью‐Йорка и обратно. Задержку увеличивают слои сетевых коммутаторов, брандмауэры и протоколы приложений. Каждый запрос Postgres в одном регионе AWS может задерживаться на время до 1 мс. Не потому, что Postgres медленная — а потому, что мы упираемся в предел скорости передачи данных. 

Теперь обработайте HTTP‐запрос в современном приложении. Десяток запросов к базе данных — и ещё до бизнес‐логики и рендеринга вы сожгли 10 мс, а мгновенными кажутся ответы не позже 100 мс. 

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

Насколько близко? SQLite не просто делит с приложением одну машину, она на самом деле встроена в процесс приложения. Расположив данные рядом с приложением, вы увидите сокращение задержки каждого запроса на 10–20 мс — микросекунд, с приставкой μ. Это в 50–100 раз быстрее запроса Postgres в регионе AWS.

Но это ещё не всё. Эффективно устраняется задержка каждого запроса. Приложение становится быстрее и проще. Большие запросы можно разбить на много небольших, более управляемых, а время, которое мы тратили на поиск паттернов N+1 в крайних случаях, потратить на разработку новых функций. 

Сводить задержку к наименьшей нужно не только на продакшене. Интеграционное тестирование с традиционной клиент‐серверной базой данных локально легко возрастает до минуты, и задержка не исчезнет, когда вы перейдёте в CI. 

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

Маленькая, быстрая, надёжная, глобально распределяемая: выберите любые четыре

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

Разумно было не замечать этот вариант 170 лет назад, когда было написано первое руководство по запуску блога на Rails, но сегодня SQLite может справиться с нагрузкой записи большинства приложений, а реплики — масштабировать чтение до выбранного количества экземпляров для балансировки нагрузки.

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

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

Пришло время сделать его более быстрым и цельным — в этом заключается вся моя работа в Fly.io. В Litestream появятся улучшения, никак не связанные с Fly.io, — и мне волнительно поделиться ими. У Litestream на Fly.io появился новый дом, но он всегда будет проектом с открытым исходным кодом. 

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

А мы поможем прокачать ваши навыки или с самого начала освоить профессию, актуальную в любое время:

Выбрать другую востребованную профессию.

Краткий список курсов и профессий




Комментарии (19):

  1. saipr
    /#24343268 / +2

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

    Я тоже сторонник SQLite. Именн SQLite используется в удостоверяющем центре CAFL63.

    • nuclight
      /#24343302 / +3

      Местами она даже лучше других баз, и на самом деле поддерживает не один процесс, а несколько (просто писателем будет только один).

      • randomsimplenumber
        /#24343902 / +2

        писателем будет только один

        Да. Достаточно длинная транзакция в одном процессе блокирует всех. Для Web-приложений это критично.

  2. fquantum
    /#24343304 / +17

    Изучать, какие ручки повернуть для оптимизации запросов или улучшения записи, — это могло быть важно 20 лет назад

    Должен разочаровать автора - сегодня это так же важно, как и 20 лет назад

    • edo1h
      /#24345282

      и аргумент про «ну так база же всего в гигабайт, она скоро в l3 процессора будет помещаться» не работает, достаточно нескольких join, чтобы запросы даже к гигабайтной базе выполнялись секунды.
      да что там гигабайтная, недавно наблюдал, как в постгресовской таблице на 100 мегабайт включили полнотекстовый поиск, а индексы забыли, полнотекстовый поиск через seq scan внезапно стал занимать полсекунды.

  3. siarheiblr
    /#24343336 / +4

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

    • sukhe
      /#24343480 / +6

      Значит, пришло время изобрести NoSQLite

      • IvaYan
        /#24343552 / +1

        Все уже украдено придумано до нас. Есть unqlite. Правда, не знаю, как она в плане использования.

    • Format-X22
      /#24344804 / +1

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

      А вот эндорфиновая часть другая - дело в том что разработчик становится счастлив от отсутствия боли с заданием схем, всяких там реляций, унылых миграций, не нужно 100500 типов данных, не нужно даже таблицы создавать - отправил туда данные и оно само там разобралось. Ну и так как там нет оверхедов на всякое - и всякие сессии и прочее, что уходит в редисы обычно, тоже там же. А так как много всяких разных стартапов где цель может поменяться через две недели - вообще идеально. Запилил МВП, пару версий ещё, если взлетает стартап, ну а далее по классике - миграция на постгрес или что-то подобное, а также установка редиса и прочего.

      В итоге монга то до сих пор классный инструмент - если ты конечно понимаешь зачем.

      • impwx
        /#24345632 / +1

        Эйфория от легкости записи в монгу быстро омрачается гемором при чтении — теперь у каждой записи потенциально свой собственный формат, любое обращение к базе нужно обмазывать увеличивающимся числом if'ов. Эйфория от скорости работы также быстро омрачается пониманием, что eventual consistency позволяет легко потерять данные в случае failover'а, а ненавистные join'ы теперь нужно делать вручную в коде приложения (или дублировать данные).


        Поэтому за последние 10 лет граница между SQL и NoSQL сильно размылась, и современные базы позволяют использовать преимущества общих подходов (a.k.a. NewSQL).

        • Format-X22
          /#24345748

          Ну собственно ваш комментарий особо не противоречит моему - аналитика и стартапы всё ещё целевая аудитория ????

    • alan008
      /#24349078

      Э, нет, сейчас говорят не так. Сейчас говорят, что NoSQL уже тоже умер и мы возвращаемся обратно к реляционным СУБД, т.к. они круче :-D

  4. me21
    /#24343430

    Я не понял, почему автор говорит, что SQLite неустойчива к сбоям.

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

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

    • debagger
      /#24343462

      Из предыдущего контекста вроде выходит, что автор имел в виду отсутствие механизмов репликации в SQlite.

      ...приложение с одним процессом имеет одну точку отказа: потерян сервер — и потеряна вся база.

  5. slonopotamus
    /#24345168 / +1

    Это ведь автор того самого BoltDB, репозиторий которого заархивирован "потому что оно в идеальном состоянии и доделывать уже нечего", и из-за которого у Roblox был даунтайм на три дня, вызванный x500 write amplification в BoltDB?

  6. GerrAlt
    /#24345446

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

  7. vadim100
    /#24345548

    Насчёт пользовательских приложений SQLite это огонь DB и musthave.
    А вот серверная переделанная SQLite из статьи я вообще не понял, что автор имел ввиду. Какое-то журналирование ввёл своё - как это влияет на параллельность транзакций в базе я вообще не пойму.

    • alan008
      /#24349082

      Если честно, перевод статьи полное говно, пардон муа. Невозможно понять, что хотели сказать и как это работает, какой-то набор слов из гугл транслейт.
      На самом деле смысл тут:
      litestream.io/how-it-works
      Они изобрели фоновую репликацию для SQLite, вот и всё.