Отчёт сравнивает производительность механизма Lightweight Transactions (LWT) в Sirin 1.2.0 и Apache Cassandra 5.0.6.
Окружение
| Параметр | Значение |
|---|---|
| Sirin | 1.2.0 на Picodata 26.1.2 |
| Apache Cassandra | 5.0.6 |
| Нагрузчик | latte |
| Конфигурация стенда | 16 ядер / 32ГБ RAM / 1 ТБ SSD |
Кластер Sirin
Узлы запущены как сервис systemd. Размер кластера зависит от replication factor: при RF=1 — 8 узлов без репликации, при RF=3 — 24 узла с тройной репликацией данных.
Кластер Cassandra
Три узла Apache Cassandra 5.0.6, запущены через Docker.
Схема
Создание keyspace
Фактор репликации задаётся параметром replication_factor.
Тесты проводились при replication_factor=1 и replication_factor=3.
CREATE KEYSPACE IF NOT EXISTS latte
WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': <replication_factor> };
Таблица для одиночных LWT-запросов (write_lwt.rn)
CREATE TABLE latte.lwt (
partition_key bigint,
clustering_key bigint,
data blob,
PRIMARY KEY ((partition_key), clustering_key)
);
Таблица для батчевых LWT-запросов (write_batch_lwt.rn)
CREATE TABLE latte.lwt_batch (
partition_key bigint,
clustering_key bigint,
data blob,
PRIMARY KEY ((partition_key), clustering_key)
);
Тесты
Все тесты проводятся с partitions=1: все запросы направляются в один
партишен, что максимизирует нагрузку на механизм LWT.
Одиночные LWT-запросы (write_lwt.rn)
Каждая итерация выполняет один INSERT … IF NOT EXISTS. Clustering key
уникален для каждой итерации.
INSERT INTO latte.lwt (partition_key, clustering_key, data)
VALUES (?, ?, ?) IF NOT EXISTS;
Параметры запуска:
latte run write_lwt.rn -P partitions=1 --threads 5 -p 50 --connections 5 --duration 15m
Батчевые LWT-запросы (write_batch_lwt.rn)
Каждая итерация отправляет один BATCH из batch_size записей в один
партишен. LWT-операции внутри батча не могут пересекать границу партишена —
это ограничение Cassandra и Sirin.
BEGIN BATCH
INSERT INTO latte.lwt_batch (partition_key, clustering_key, data)
VALUES (?, ?, ?) IF NOT EXISTS;
-- … ещё (batch_size - 1) строк в тот же partition_key
APPLY BATCH;
Параметры запуска:
latte run write_batch_lwt.rn -P batch_size=50 --threads 5 -p 10 --connections 1 --duration 15m
latte run write_batch_lwt.rn -P batch_size=1000 --threads 5 -p 10 --connections 1 --duration 15m
Результаты
Replication factor = 1
Параметр запуска: -P replication_factor=1 (передаётся в latte schema).
Кластер Sirin: 8 узлов, репликация отключена — каждая партиция хранится на одном узле.
Одиночные LWT-запросы
| Система | RPS | P50 | P95 | P99 | CPU, % |
|---|---|---|---|---|---|
| Sirin | 64239 | 3.656 | 6.816 | 8.569 | 190 |
| Cassandra | 2633 | 49.578 | 301.203 | 500.433 | 155 |
Батчевые LWT-запросы, batch_size=50
| Система | RPS | P50 | P95 | P99 | CPU, % |
|---|---|---|---|---|---|
| Sirin | 1598 | 46.563 | 173.539 | 246.284 | 150 |
| Cassandra | 795 | 32.309 | 520.618 | 858.784 | 180 |
Батчевые LWT-запросы, batch_size=1000
| Система | RPS | P50 | P95 | P99 | CPU, % |
|---|---|---|---|---|---|
| Sirin | 74 | 273.154 | 771.752 | 1085.276 | 180 |
| Cassandra | 11 | 5519.704 | 5679.088 | 5737.808 | 800 |
Replication factor = 3
Параметр запуска: -P replication_factor=3 (передаётся в latte schema).
Кластер Sirin: 24 узла, каждая партиция хранится на трёх независимых узлах — данные реплицируются
с тройной избыточностью.
Одиночные LWT-запросы
| Система | RPS | P50 | P95 | P99 | CPU, % |
|---|---|---|---|---|---|
| Sirin | 25995 | 4.772 | 34.472 | 51.446 | 600 |
| Cassandra | 465 | 302.514 | 1923.088 | 3445.621 | 570 |
Батчевые LWT-запросы, batch_size=50
| Система | RPS | P50 | P95 | P99 | CPU, % |
|---|---|---|---|---|---|
| Sirin | 1520 | 24.510 | 86.770 | 104.006 | 800 |
| Cassandra | 318 | 162.529 | 1203.765 | 2065.695 | 500 |
Батчевые LWT-запросы, batch_size=1000
| Система | RPS | P50 | P95 | P99 | CPU, % |
|---|---|---|---|---|---|
| Sirin | 70 | 481.296 | 1836.057 | 2116.026 | 810 |
| Cassandra | 13 | 4773.118 | 5637.145 | 5712.642 | 1000 |
Выводы
Почему LWT в Cassandra значительно медленнее
LWT в Cassandra реализован поверх протокола Paxos. Для выполнения одного
INSERT … IF NOT EXISTS координатор проводит до четырёх фаз:
- Prepare — координатор рассылает prepare-запросы репликам;
- Promise — реплики отвечают, возвращая последнее принятое значение;
- Accept — координатор предлагает значение, реплики соглашаются;
- Commit — значение фиксируется.
При RF=3 кворум требует ответа минимум от двух из трёх реплик на каждой фазе. Одна LWT-операция — от двух до четырёх сетевых round-trip’ов.
Sirin реализует LWT через механизмы транзакций Picodata внутри конкретной партиции без использования Paxos, что устраняет эти накладные расходы.
Replication factor = 1
Одиночные запросы. Разрыв в пропускной способности составляет 24× (64 239 vs 2 633 rps) при сопоставимой загрузке CPU — 190% против 155%. CPU у Cassandra уходит на Paxos, а не на запись. Медиана латентности — 49 мс против 3.6 мс у Sirin (14×), P99 — 500 мс против 8.6 мс (58×).
Батч из 50 записей. Cassandra выполняет один Paxos-раунд на весь BATCH, независимо от числа строк в нём, поэтому стоимость Paxos на строку в 50 раз ниже, чем при одиночных запросах — отсюда и меньший разрыв в RPS: 2× (1 598 vs 795). Тем не менее хвостовая латентность P99 у Cassandra в 3.5 раза выше (858 мс vs 246 мс).
Батч из 1000 записей. Cassandra уходит в CPU-насыщение: 800% при 16 доступных ядрах. Несмотря на это, пропускная способность — лишь 11 rps против 74 у Sirin (6.7×), P99 достигает 5 737 мс против 1 085 мс (5.3×).
Replication factor = 3
При включённой репликации стоимость Paxos в Cassandra возрастает: каждая из четырёх фаз теперь требует кворума от реальных реплик, что добавляет сетевые round-trip’ы.
Одиночные запросы. Разрыв вырастает до 56× по RPS (25 995 vs 465). P50 у Cassandra — 302 мс против 4.8 мс у Sirin (63×); P99 — 3 445 мс против 51 мс (67×). Sirin задействует больше CPU (600%) на обслуживание трёх реплик, однако пропускная способность снижается лишь в 2.5× относительно RF=1. Cassandra при сопоставимой загрузке CPU (570%) даёт в 5.7× меньше RPS, чем при RF=1.
Батч из 50 записей. Разрыв по RPS — 4.8× (1 520 vs 318), по P99 — 20× (2 065 мс vs 104 мс). Sirin при RF=3 сохраняет почти ту же пропускную способность, что при RF=1 (1 520 vs 1 598 rps) — просадка в пределах погрешности. Cassandra деградирует в 2.5× относительно RF=1.
Батч из 1000 записей. Cassandra упирается в потолок: 1 000% CPU — более 60% вычислительной мощности машины, — и при этом выдаёт лишь 13 rps. Sirin при 810% CPU даёт 70 rps — разрыв 5.4×. P99 у Cassandra — 5 712 мс против 2 116 мс у Sirin.
Сводка
| Сценарий | RF | Sirin, rps | Cassandra, rps | Sirin / Cassandra |
|---|---|---|---|---|
| Одиночные запросы | 1 | 64 239 | 2 633 | 24× |
| Батч, 50 строк | 1 | 1 598 | 795 | 2× |
| Батч, 1000 строк | 1 | 74 | 11 | 6.7× |
| Одиночные запросы | 3 | 25 995 | 465 | 56× |
| Батч, 50 строк | 3 | 1 520 | 318 | 4.8× |
| Батч, 1000 строк | 3 | 70 | 13 | 5.4× |
Заключение
Для LWT-нагрузки Sirin — очевидный выбор. Cassandra при каждой условной записи прогоняет Paxos между узлами-репликами; Sirin этого не делает, и сетевые round-trip’ы просто выпадают из критического пути. При переходе с RF=1 на RF=3 разрыв по RPS на одиночных запросах вырастает с 24× до 56×: Cassandra платит за кворум на каждой из четырёх Paxos-фаз, Sirin — нет.
