QuestDB — открытая колоночная база данных для временных рядов, которая говорит на SQL и принимает данные по InfluxDB Line Protocol. Она предсказуемо работает с метриками, показаниями датчиков и логами, где важны диапазоны времени и высокая скорость записи. Ниже — запуск, схема, загрузка данных и эксплуатация без лишней магии.
Чем QuestDB отличается
У таблицы есть designated timestamp — колонка, по которой строятся партиции и упорядочено хранение. Данные лежат в колонках, а не в строках, поэтому агрегаты по времени читают минимум лишних блоков. Вместо привычных индексов используется partition pruning: запрос с фильтром по времени открывает только нужные партиции.
Расширения SQL закрывают типовые задачи: SAMPLE BY делает downsampling, ASOF JOIN ищет ближайшую по времени запись, LATEST ON возвращает последнее состояние, а DEDUP UPSERT KEYS защищает от повторной вставки. Для транзакционной нагрузки, сложных многотабличных JOIN и частых UPDATE база не предназначена — это осознанный обмен возможностей на скорость анализа.
Запуск в Docker
Для знакомства достаточно одного контейнера с именованным томом. Порты: 9000 — HTTP и веб-консоль, 8812 — PostgreSQL wire, 9009 — ILP по TCP.
docker volume create questdb_data
docker run -d --name questdb -p 9000:9000 -p 8812:8812 -p 9009:9009 -v questdb_data:/var/lib/questdb questdb/questdb
docker ps --filter name=questdb
docker logs -f questdb
В продакшене фиксируйте конкретный тег образа вместо latest и храните conf/server.conf в системе управления конфигурацией. Для настройки ресурсов используйте параметры вида shared.worker.count и лимиты памяти, а не правку файлов внутри запущенного контейнера. Каталог данных содержит db, wal1, conf и log — резервную копию делайте с остановленным контейнером, чтобы получить согласованный снимок.
Схема и удержание данных
Создадим таблицу показаний с символьным тегом, двумя полями и партиционированием по дням. TTL удаляет старые партиции автоматически.
curl -s -G "http://localhost:9000/exec" --data-urlencode "query=CREATE TABLE IF NOT EXISTS sensors (ts TIMESTAMP, sensor SYMBOL, temp DOUBLE, hum DOUBLE) TIMESTAMP(ts) PARTITION BY DAY;"
curl -s -G "http://localhost:9000/exec" --data-urlencode "query=ALTER TABLE sensors SET TTL 30 DAYS;"
curl -s -G "http://localhost:9000/exec" --data-urlencode "query=SHOW COLUMNS FROM sensors;"
SYMBOL — словарный тип для повторяющихся строк вроде имени датчика, он экономит место по сравнению с обычным STRING. Партиция по дню подходит для равномерного потока; для очень больших объёмов имеет смысл PARTITION BY MONTH, а для редких данных — BY YEAR. Слишком мелкие партиции создают лишние файлы и замедляют обслуживание.
Загрузка по ILP и SQL-запросы
Проще всего писать данные через HTTP endpoint /write. Формат: измерение, теги, поля, время в наносекундах.
curl -s -X POST "http://localhost:9000/write?db=main" --data-binary "sensors,sensor=roof temp=21.4,hum=48.0 1735689600000000000"
curl -s -X POST "http://localhost:9000/write?db=main" --data-binary "sensors,sensor=roof temp=21.7,hum=47.9 1735689660000000000"
curl -s -G "http://localhost:9000/exec" --data-urlencode "query=SELECT count() FROM sensors;"
Для пакетной загрузки удобнее TCP-порт 9009 и официальный клиент: он буферизует строки и отправляет их пачками. После записи проверьте, что designated timestamp распознан: в выводе SELECT * FROM tables(); колонка designatedTimestamp должна указывать на ts.
Аналитика выглядит привычно, но с оговорками: фильтр по времени обязателен, иначе база просканирует все партиции.
SELECT sensor, avg(temp) AS avg_temp, max(hum) AS max_hum
FROM sensors
WHERE ts > dateadd('h', -6, now())
SAMPLE BY 5m FILL(NULL)
ALIGN TO CALENDAR;
SELECT s.ts, s.temp, t.limit_max
FROM sensors s
ASOF JOIN thresholds t ON s.sensor = t.sensor
WHERE s.ts > dateadd('h', -1, now());
SAMPLE BY 5m FILL(NULL) даёт ровные интервалы без пропусков, а ALIGN TO CALENDAR привязывает окна к календарной сетке. ASOF JOIN полезен, когда нужно подтянуть последний порог или последнюю конфигурацию на момент измерения; у второй таблицы тоже должен быть designated timestamp, иначе соединение не даст ожидаемого результата. Для защиты от дублей включите дедупликацию по времени и ключу:
ALTER TABLE sensors DEDUP ENABLE UPSERT KEYS(ts, sensor);
Резервное копирование и мониторинг
Согласованный бэкап проще всего получить остановкой контейнера и копированием тома. Для больших баз настройте snapshot на уровне файловой системы и проверяйте целостность на отдельном стенде. Храните не только данные, но и conf/server.conf: без него восстановление займёт больше времени.
Следите за тремя вещами: свободным местом, задержкой записи в WAL и количеством партиций. Рост каталога wal1 при остановленной записи обычно означает проблему с диском или нехватку памяти. Снимайте метрики HTTP-запросами к служебным таблицам и сравнивайте их с порогами, а не смотрите на график «когда-нибудь потом». Для регулярных проверок достаточно cron-задания, которое раз в минуту запрашивает количество строк и размер партиций и отправляет результат в вашу систему мониторинга.
Отдельно проверьте поведение при перезапуске: база должна подняться, принять данные и отдать запрос за разумное время. Если после старта WAL восстанавливается слишком долго, уменьшите объём необработанных данных и проверьте, успевает ли диск.
Диагностика и обслуживание
Смотрите не только на SQL, но и на состояние WAL и партиций. Если запись идёт, а чтение отстаёт, проверьте wal_tables и свободное место.
curl -s -G "http://localhost:9000/exec" --data-urlencode "query=SHOW TABLES;"
curl -s -G "http://localhost:9000/exec" --data-urlencode "query=SELECT * FROM wal_tables();"
curl -s -G "http://localhost:9000/exec" --data-urlencode "query=SHOW PARTITIONS FROM sensors;"
du -sh /var/lib/docker/volumes/questdb_data/_data
EXPLAIN показывает план и интервалы сканирования. Если план неожиданно широкий, проверьте, что фильтр использует designated timestamp и что в запросе нет приведения колонки времени к строке. Для административных задач подключайтесь через psql к порту 8812: это обычный PostgreSQL-протокол, но набор команд и типы отличаются от PostgreSQL.
- Ограничьте доступ к 9000 и 8812 доверенной сетью, смените пароль по умолчанию.
- Следите за размером партиций и не держите в базе данные дольше нужного срока.
- Проверяйте восстановление копии на отдельном стенде, а не только факт создания архива.
- Для тяжёлых агрегатов используйте материализованные представления или предварительный downsampling.
Итог
QuestDB закрывает нишу быстрых временных рядов без отдельного языка запросов: SQL, партиции по времени, ILP и понятное удержание данных. Схема с designated timestamp и дисциплина фильтров по времени дают предсказуемую скорость, а TTL и дедупликация упрощают эксплуатацию. Начните с одного контейнера и реального потока метрик, затем настройте резервное копирование и мониторинг размера партиций.