Пять месяцев назад мы перенесли наше хранилище данных на DuckDB вместо ClickHouse. Мы сделали это, потому что общий кластер ClickHouse не справлялся с многочасовыми запросами моделирования, а в нем отсутствовал оптимизатор запросов на основе стоимости. DuckDB предоставляла каждому клиенту собственный экземпляр, а такие инструменты, как dbt, могли подключаться по протоколу Postgres. Мы по-прежнему являемся ее фанатами.
Но мы недооценили то, насколько масштабными и сложными станут рабочие нагрузки наших клиентов. Мы хотели, чтобы они получали не просто хороший опыт, а лучший опыт, который можно получить, платя кому-то другому за запуск вашего хранилища. Оно должно работать в любом масштабе и по справедливой цене.
Поэтому последние три месяца мы занимались реархитектурой, на этот раз без DuckDB.
Мы любим DuckDB. Это, пожалуй, лучший инструмент для работы с одним узлом для анализа массивов данных, причем этим узлом может быть ваш ноутбук. Она надежна. Она почти всегда завершает ваш запрос, а если ей не хватает памяти, происходит сброс на диск.
Те же функции, которые так хороши локально, доставили нам проблемы в облаке. Клиенты запускали более сложные запросы, чем мы ожидали, поэтому нам приходилось добавлять огромные инстансы. Добавление дисков было вариантом, но запросы работали бы слишком медленно и дорого, чтобы это имело смысл.
Чтобы безопасно запускать одноузловой движок в облаке, нам нужно подбирать размер воркера до того, как поступит запрос. Эргономика здесь странная. Мы не смогли найти хороший способ подсказать клиентам, какие ресурсы понадобятся их запросам. Предсказать процессор и память для конкретного запроса было еще сложнее.
Пользователи не должны беспокоиться об этом. У них должна быть возможность запускать все необходимое для выполнения своей работы. наша задача — справиться со всем остальным и сделать это максимально предсказуемым.
Мы столкнулись и с другими узкими местами, например с выгрузкой больших объемов данных из S3 без асинхронной загрузки. DuckDB добавила эту возможность только недавно.
Чтобы использовать DuckDB в качестве полноценного хранилища данных, мы по умолчанию установили расширение DuckLake. Это новый формат lakehouse от тех же создателей, который следует тем же принципам, что и DuckDB: сохранять простоту и работать практически на любой конфигурации. До определенного момента это работает хорошо, но в масштабах PostHog нам потребовалось что-то изначально созданное для распределенного масштабирования.
Схема каталога DuckLake должна оставаться переносимой между множеством баз данных SQL, что ограничивает оптимизации, специфичные для движка. В нашем масштабе запись в одну таблицу могла занимать более трех минут, потому что каждый коммит заново считывал статистику для всего каталога.
Мы оптимизировали DuckLake с помощью индексов таблиц и подобных правок. Но чтобы продолжать предлагать DuckLake, мы должны были оставаться на 100% совместимыми с ее существующей «официальной» схемой. Это оставляло нам меньше возможностей для оптимизации.
Другая проблема заключалась в том, что более 99% строк со статистикой в каталоге принадлежали таблицам, которых больше не существовало. Их очистка ускоряла запись в 30–50 раз, но эти строки продолжали появляться снова.
Мы также потратили много времени на количество соединений, которые DuckLake открывает к базам данных метаданных. Это бьет по общим инстансам Postgres, где каждое соединение имеет реальную стоимость.
DuckLake в PostHog не начинала с малого с постепенным наращиванием. Мы полностью вложились в проект и перенесли весь PostHog в DuckLake, чтобы протестировать его на себе в качестве первого клиента.
Когда мы отказались от нее, в ней хранилось около 2 ПБ: примерно 1 триллион строк и 15,3 миллиона снимков. Она ломалась самыми разными способами, которые было трудно исправить в рамках возможностей DuckLake.
Мы бы предпочли внести свой вклад в существующее сообщество и помочь ему расти. Но мы не могли внедрять изменения так быстро, как нам было нужно, поэтому в итоге мы использовали патчи в собственном форке, что сводит на нет одно из главных преимуществ использования DuckLake: сообщество.
Разговаривать на языке Postgres сложнее, чем кажется
Предложение прямых соединений с Postgres через порт 5432 звучит как отличная идея. На практике масштабировать это оказалось гораздо сложнее, чем HTTP.
Пропуск переподключений, управление TTL и простаивающими соединениями в больших масштабах — это сложно, а долгоживущие соединения стоят дорого. Кроме того, для балансировки нагрузки pgwire существует очень мало инструментов по сравнению с HTTP. Сравните эргономику AWS NLB с ALB, и вы поймете, что мы имеем в виду. Заголовки HTTP также делают маршрутизацию в Kubernetes гораздо более гибкой.
Существует также бесконечный список клиентов Postgres, и каждый ожидает своего специфического поведения Postgres. Клиенты постоянно просили нас починить их клиент или библиотеку. Мы потратили много времени на выпуск множества таких исправление, но покрытие так и не стало достаточным. Мы бы предпочли потратить время на создание отличного хранилища, чем гоняться за длинным хвостом совместимости с Postgres.
Мы усвоили, что не стоит так сильно раздувать хайп вокруг технологий, лежащих в основе хранилища. Имеет значение только тот опыт, который оно вам дает.
Тем не менее, мы рады поделиться тем, что готовим дальше. Мы перестроили все вокруг собственного варианта Trino и создали собственный открытый формат таблиц — HogLake. И да, сначала мы тестируем его в петабайтном масштабе PostHog. Мы сделали его надежным, прежде чем передавать дизайн-партнерам.
Если ваше текущее хранилище работает медленно или стоит дорого, давайте поговорим, особенно если у вас много данных для запросов. Мы всегда ищем дизайн-партнеров.












