RethinkDB появилась как база документов с необычной идеей: клиент может подписаться на изменения запроса и получать обновления сразу, без опроса. Для дашбордов, чатов и домашней телеметрии это удобнее, чем периодически дергать SELECT. Сегодня проект развивается сообществом, но сама модель остаётся интересной и вполне рабочей.
Чем RethinkDB отличается
Это документная база с языком запросов ReQL. Запросы собираются из цепочки методов и выполняются на сервере, а не на клиенте. Данные хранятся в JSON-документах, схема не фиксирована, но первичный ключ и индексы задаются явно. Встроенный веб-интерфейс на порту 8080 показывает таблицы, запросы и состояние кластера.
Главная особенность — changefeed. Вместо polling клиент открывает поток и получает вставки, обновления и удаления по мере их появления. Это снимает нагрузку с базы и уменьшает задержку. Поток можно фильтровать, объединять с другими таблицами и ограничивать по времени.
- Горизонтальное шардирование и репликация на уровне таблиц.
- Атомарные операции с одним документом.
- Геопространственные индексы и операции.
- HTTP-интерфейс администратора.
Запуск в контейнере
Самый предсказуемый способ попробовать — Docker. Том сохраняет данные между перезапусками, а порты публикуются только на localhost: драйверный порт 28015 не должен смотреть в интернет. Порт 29015 нужен для связи узлов кластера и на одиночной машине не требуется.
docker volume create rethink_data
docker run -d --name rethinkdb \
--restart unless-stopped \
-p 127.0.0.1:8080:8080 \
-p 127.0.0.1:28015:28015 \
-v rethink_data:/data \
rethinkdb:latest
После старта откройте http://127.0.0.1:8080 и убедитесь, что узел виден в разделе Servers. Если страница не открывается, смотрите docker logs: чаще всего не хватает прав на том или занят порт.
Подключение и первые запросы
Официальный драйвер для Python ставится в виртуальное окружение. Подключение указывает хост, порт и базу; для учебного примера достаточно localhost. На самых новых версиях Python драйвер иногда требует сборки из исходников, поэтому смотрите вывод pip при установке.
python3 -m venv .venv
. .venv/bin/activate
pip install rethinkdb
python -c "from rethinkdb import r; print(r.db_list().run(r.connect(host='127.0.0.1')))"
from rethinkdb import r
conn = r.connect(host="127.0.0.1", port=28015)
r.db_create("home").run(conn)
conn.use("home")
r.table_create("readings", primary_key="id").run(conn)
r.table("readings").insert({
"id": "kitchen-2026-11-06T12:00",
"sensor": "kitchen",
"temperature": 21.4,
"ts": r.now()
}).run(conn)
Идентификатор лучше формировать самому: это упрощает повторную запись и делает операцию идемпотентной. Поле ts заполняется серверным временем, поэтому часы клиента не влияют на сортировку.
Живые запросы
Changefeed открывается один раз и живёт до закрытия соединения. В примере отбрасываются служебные события состояния, чтобы обрабатывать только реальные изменения. Флаг squash объединяет частые правки одного документа, снижая поток.
feed = r.table("readings").changes(
squash=True, include_states=True
).run(conn)
for change in feed:
if change["type"] == "state":
continue
print(change["new_val"])
Каждый поток занимает соединение и очередь на сервере. Не открывайте changefeed на каждый HTTP-запрос: держите один фоновый процесс и рассылайте данные подписчикам через WebSocket или SSE.
Индексы и выборки
Фильтр по полю без индекса читает всю таблицу. Для временных рядов создайте индекс по ts и используйте between — он работает по диапазону и не требует полного перебора. После создания индекса дождитесь его готовности.
r.table("readings").index_create("ts").run(conn)
r.table("readings").index_wait("ts").run(conn)
rows = (r.table("readings")
.between(r.time(2026, 11, 6, "Z"), r.time(2026, 11, 7, "Z"), index="ts")
.order_by(index="ts")
.limit(100)
.run(conn))
Составной индекс задаётся списком полей и ускоряет выборки по паре «устройство + время». Для агрегатов используйте group и avg прямо в запросе: сервер посчитает результат без выгрузки всех документов.
(r.table("readings")
.group("sensor")
.avg("temperature")
.run(conn))
Кластер и обслуживание
Таблицу можно разбить на шарды и держать несколько реплик. Для домашнего стенда это избыточно, но для нескольких узлов полезно: при потере машины данные останутся доступны. Состояние смотрите через служебные таблицы, а не только через веб-интерфейс.
r.db("rethinkdb").table("server_status").run(conn)
r.db("rethinkdb").table("current_issues").run(conn)
r.table("readings").index_list().run(conn)
Резервная копия делается утилитой rethinkdb внутри контейнера. Дамп включает данные и конфигурацию таблиц; restore возвращает их в чистый кластер. Проверяйте восстановление на тестовом узле, иначе копия останется теоретической.
docker exec rethinkdb rethinkdb dump -c 127.0.0.1:28015 -f /data/backup.tar.gz
docker cp rethinkdb:/data/backup.tar.gz ./backup.tar.gz
docker exec rethinkdb rethinkdb restore -c 127.0.0.1:28015 /data/backup.tar.gz
Ограничения и диагностика
RethinkDB не заменяет реляционную базу: межтабличных транзакций и привычного SQL здесь нет, а джойны ограничены eqJoin, innerJoin и outerJoin. Сложную аналитику лучше выгружать в специализированное хранилище. Также помните, что база не предназначена для прямого доступа из интернета: закройте порты файрволом.
docker logs --tail 100 rethinkdb
curl -s http://127.0.0.1:8080 | head -n 5
docker stats --no-stream rethinkdb
df -h /var/lib/docker
Если запрос «висит», проверьте, не открыт ли changefeed без читателя: буфер заполняется и сервер начинает тормозить. Если диск заполнен, смотрите docker system df и старые тома. Для наблюдения за задержкой добавьте в дашборд график по числу документов и времени ответа: рост без нагрузки обычно означает нехватку индекса, а не проблему с сетью.
Чек-лист
- Порты 28015 и 8080 закрыты снаружи.
- Индексы созданы под реальные запросы.
- Changefeed один на приложение, а не на запрос.
- Дампы снимаются по расписанию и проверяются.
- Для одного узла не включайте лишние реплики.
- Перед обновлением образа сделан дамп.
Итог
RethinkDB стоит выбирать ради changefeed и простой модели документов, а не как универсальную замену PostgreSQL. Запуск в контейнере занимает минуты, ReQL читается почти как обычный код, а живые запросы действительно упрощают интерфейсы. Держите базу в закрытом сегменте, индексируйте поля заранее и проверяйте восстановление из дампа — тогда эксперимент не превратится в потерю данных.