Привет, Хабр! Меня зовут Александр, я DevRel команды Selena Lakehouse. Пишу про СУБД StarRocks, архитектуры Lakehouse и Streamhouse в Telegram-канале @starrocks_selena (https://t.me/starrocks_selena).

Полгода назад термин Streamhouse начал всплывать на конференциях и в блогах. При этом многие русскоязычные источники, которые я читал, сводят его к «замене Iceberg на Paimon и обновлению Flink» или путают с обычным Lakehouse. На самом деле за Streamhouse стоит интересная архитектурная логика. Конкретный набор компонентов, где каждый решает свою задачу: Apache Flink для вычислений, Apache Fluss как горячий потоковый слой, Apache Paimon как холодное хранилище. Вместе они дают потоковый Lakehouse с задержкой в секунды вместо минут.

Мне стало интересно: можно ли на этом стеке построить полноценный аналитический конвейер? Не на слайдах, а руками в Docker Compose, с SQL и реальным сценарием.

В этой статье:

  • Разбираем архитектуру Streamhouse. Какие компоненты, зачем каждый из них нужен и как они связаны.

  • Собираю конвейер для телеком-оператора. В Docker Compose, с SQL, и скоростью генерации событий — 2000 событий/сек.

  • Упираюсь в ограничение. Конвейер работает, данные есть, а быстро анализировать их нечем.

  • Нахожу решение и добавляю недостающий компонент (спойлер: StarRocks) и показываю результат с бенчмарками.

Для кого: дата-инженеры и архитекторы, которые строят конвейеры аналитики реального времени и хотят понять, стоит ли смотреть в сторону Streamhouse, и что там работает, а что пока нет.

1. Проблема Lakehouse: минутный потолок

Lakehouse-форматы (Iceberg, Hudi, Delta Lake) построены на модели неизменяемых файлов (Parquet/ORC) на объектном хранилище плюс слой метаданных. Эта модель хорошо работает для пакетной аналитики, но при потоковой записи возникают конкретные ограничения.

Четыре источника задержки:

  1. Накладные расходы фиксации. Каждая фиксация — это запись Parquet-файла + обновление манифеста + атомарная замена метаданных. При потоковой записи через Flink каждый чекпоинт (обычно раз в 10–30 секунд) инициирует коммит в Lakehouse-формат. Это создаёт постоянную нагрузку на объектное хранилище.

  2. Мелкие файлы. Частые фиксации порождают тысячи мелких Parquet-файлов. При фиксации каждые 30 секунд за сутки накопится ~2880 коммитов на одну таблицу. Но каждый writer (параллельный поток) создаёт свой файл, при параллелизме 8 это уже ~23 000 файлов в сутки. И каждый файл несёт метаданные, footer, схему.

  3. Compaction. Для борьбы с мелкими файлами нужен отдельный процесс, периодическая перезапись в крупные файлы. Это дополнительные ресурсы, сложность и окна обслуживания.

  4.  Обновления по ключу. Модель неизменяемых файлов не поддерживает обновления на месте. Нужны файлы удаления (delete files), слияние при чтении, всё это деградирует пропорционально доле обновлений.

Когда я в первый раз столкнулся с этим на практике, это была подготовка к пилоту по Iceberg и Lakehouse. Заказчик ожидал относительно быстрое выполнение запросов, а получил десятки минут ожидания — тысячи мелких файлов после потоковой записи, а compaction только добавлял нагрузку вместо того чтобы помогать. Тогда я понял одну вещь: если в задаче не нужны транзакции и time travel, Lakehouse-формат может оказаться не ускорителем, а проблемой. И мы предложили другие архитектуры и компоненты для решения его задачи.

Уточнение: это не означает, что Lakehouse плох. Для аналитики с задержкой 5-15 минут — это отличная архитектура. Но при потоковой записи с частыми обновлениями модель неизменяемых файлов ставит перед выбором: свежесть данных против стоимости эксплуатации.

В итоге компании строят двойную инфраструктуру: Kafka + Flink для потоковой обработки плюс Iceberg для аналитики. Данные живут в двух местах, схемы рассинхронизируются, а инженеры тратят время на синхронизацию.

 Ververica (компания, основанная создателями Apache Flink) предложила модель LSR-треугольника (Lakehouse — Streamhouse — Real Time), которая описывает баланс (https://www.ververica.com/blog/streamhouse-unveiled) между задержкой и стоимостью:

  •  Lakehouse (Iceberg/Delta/Hudi): минуты-часы задержки, дёшево ($)

  • Streamhouse (Flink+Fluss+Paimon): секунды, умеренно ($$)

  • Потоковая обработка (Kafka/Fluss напрямую): миллисекунды, дорого ($$$$)

Модель LSR-треугольника (перерисована для наглядности по оригиналу Ververica (https://www.ververica.com/blog/streamhouse-unveiled)): чем ниже задержка, тем дороже инфраструктура. Streamhouse это золотая середина.
Модель LSR-треугольника (перерисована для наглядности по оригиналу Ververica (https://www.ververica.com/blog/streamhouse-unveiled)): чем ниже задержка, тем дороже инфраструктура. Streamhouse это золотая середина.

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

2. Стек: Flink + Fluss + Paimon

Изначально Ververica (компания, основанная создателями Flink) определила Streamhouse (https://www.ververica.com/blog/streamhouse-unveiled) как комбинацию трёх компонентов: Flink CDC (ingestion) + Apache Flink (вычисления) + Apache Paimon (хранение). Эта комбинация покрывала потоковый ETL, но не решала проблему горячего слоя. Свежие данные становились доступны для запросов только после очередного чекпоинта Flink (обычно раз в 10–30 секунд), а до этого момента они просто висели в памяти движка.

В конце 2024 году Ververica представила Apache Fluss (https://www.ververica.com/blog/introducing-fluss) колоночное потоковое хранилище, которое встаёт между Flink и Paimon как горячий слой. Fluss обеспечивает субсекундный доступ к свежим данным, а встроенный тиринг-сервис автоматически переносит их в Paimon. С появлением Fluss стек обновился, именно его мы проверим на тестовом стенде дальше в статье:

Архитектура Streamhouse (перерисована по [Introducing Fluss Ververica] (https://www.ververica.com/blog/introducing-fluss)): Flink(вычисления) → Fluss (горячий слой) → Paimon (холодный слой).
Архитектура Streamhouse (перерисована по [Introducing Fluss Ververica] (https://www.ververica.com/blog/introducing-fluss)): Flink(вычисления) → Fluss (горячий слой) → Paimon (холодный слой).

Три компонента, каждый со своей ролью.

Apache Flink - вычислительный движок

Flink это единственный движок, который нативно работает и с Fluss, и с Paimon через SQL (возможно когда вы читаете эту статью Spark тоже уже подтянулся). В Streamhouse он выполняет три роли:

  • Потоковые вычисления — обработка, обогащение, соединения (JOIN) в реальном времени.

  • Пакетные запросы — аналитика по историческим данным в том же движке.

  • Управление конвейером — координация записи, тиринга и компактации

В марте 2025 года вышел Flink 2.0 (https://flink.apache.org/2025/03/24/apache-flink-2.0.0-a-new-era-of-real-time-data-processing/), для Streamhouse это важно: разделённое хранение состояния (ForSt) позволяет держать состояние JOIN-операций на диске, а не в памяти, что критично для обогащения потоков по большим справочникам. Асинхронная модель выполнения снижает задержку чекпоинтов, а оптимизированные многосторонние JOIN ускоряют именно те операции, на которых строится потоковый конвейер. На тестовом стенде, который мы развернём в третьей главе, будет Flink 1.20 и Fluss 0.8 (пока предоставляет quickstart-образ только для этой версии).

Apache Fluss - горячий слой

Fluss — это колоночное потоковое хранилище для аналитики реального времени. Проект в инкубаторе Apache с июня 2025, обкатан в Alibaba при пиковой нагрузке 40 ГБ/с на кластере объёмом 3 ПБ (https://fluss.apache.org/blog/fluss-joins-asf/).

Kafka проектировался для передачи событий, а не для аналитических запросов. Fluss закрывает конкретные пробелы:

Kafka

Fluss

Формат хранения

Строковый (byte array)

Колоночный (Arrow IPC)

Отсечение колонок

Нет

Да, серверное (до 10× прироста при отсечении 90% колонок)

Обновления по ключу

Нет (append-only)

Нативные (PK-таблицы)

Тиринг в Lakehouse

Kafka Connect (внешний)

Встроенный тиринг-сервис

Аналитические запросы

Не предназначен

Column pruning + SIMD

Fluss в переводе с немецкого — «река». В Apache-проектах часто неравнодушны к воде: streams, data lakes, Lakehouse, теперь река. Осталось дождаться Apache Ozean — и круговорот данных в природе замкнётся.

Подробно различия и сходства Kafka и Fluss разобраны в статье «Why Fluss?» на официальном сайте проекта (https://fluss.apache.org/blog/why-fluss/).

Главная интеграция — это тиринг-сервис: отдельный Flink-джоб, поставляемый в комплекте с Fluss, который переносит данные из Fluss в Paimon (Arrow IPC -> Parquet) напрямую, без промежуточной сериализации. Тиринг управляется параметром при создании Fluss-таблицы через Flink SQL:

'table.datalake.enabled' = 'true',
'table.datalake.freshness' = '30s'

Каждые 30 секунд данные автоматически перемещаются в холодный слой.

Объединённое чтение (Union Read)

Архитектурная возможность Streamhouse это Union Read. Один SQL-запрос через Flink прозрачно объединяет данные из горячего (Fluss) и холодного (Paimon) слоёв. Для пользователя это выглядит как одна таблица, а внутри Flink автоматически:

  1. Читает свежие данные из Fluss (те, что ещё не перенеслись в Paimon).

  2. Читает исторические данные из Paimon (Parquet-файлы).

  3. Объединяет результат и дедуплицирует по первичному ключу.

Union Read: прозрачное объединение горячего и холодного слоёв в одном запросе.
Union Read: прозрачное объединение горячего и холодного слоёв в одном запросе.

Apache Paimon - холодный слой

Apache Paimon — это формат таблиц для потокового Lakehouse (Top-Level Project Apache с апреля 2024). Ключевое отличие от Iceberg: вместо модели неизменяемых файлов Paimon использует LSM-деревья (Log-Structured Merge Tree), ту же структуру данных, что и в RocksDB и Cassandra.

 Почему это важно для Streamhouse:

  • Запись всегда быстрая. Данные буферизуются в MemTable в памяти, периодически сбрасываются на диск как отсортированные файлы. Никакой перезаписи существующих файлов.

  • Обновления по ключу нативные. Не нужны файлы удаления (delete files). LSM-дерево при чтении объединяет версии записи по ключу, отдавая последнюю.

  • Compaction встроенный. Фоновый процесс автоматически объединяет мелкие файлы в крупные, не блокируя запись.

  • Двойная природа. Одна и та же таблица работает и как обычная таблица (пакетный режим с полным снимком), и как очередь сообщений (потоковый режим с журналом изменений). Отдельная инфраструктура Kafka не нужна.

Paimon поддерживается Flink, Spark, Trino, Presto и рядом других движков. Paimon может генерировать метаданные Iceberg (V2 для обычных таблиц, V3 для таблиц с deletion vectors), позволяя читать данные через любой движок с поддержкой Iceberg.

А можно ли просто взять Iceberg вместо Paimon? Формально - да, Fluss с версии 0.7 поддерживает тиринг и в Iceberg. Но в модели неизменяемых файлов (типа Iceberg) обновления по ключу требуют delete files и merge-on-read, те самые проблемы, которые мы разобрали в главе 1. Paimon с его LSM-деревом обновляет записи нативно, без delete files и без деградации при большой доле обновлений. Для Streamhouse-конвейера с частыми обновлениями справочников (PK-таблицы) и потоковым обогащением Paimon подходит лучше.

Обратите внимание: в документации Paimon (https://paimon.apache.org/docs/1.0/engines/overview/) явно перечислены рекомендуемые аналитические движки для запросов -  StarRocks, Trino и другие. Paimon сам по себе это формат хранения, а не движок запросов. Запомните этот момент.

3. Телеком-кейс: строим конвейер

Красивые архитектурные схемы — это одно, а работающий конвейер с реальной нагрузкой, совсем другое. Мне хотелось понять, как Streamhouse ведёт себя на практике: сколько данных проходит без потерь, какая реальная свежесть, где начинаются ограничения. Единственный способ выяснить — это развернуть стек на виртуальной машине и эмулировать работу оператора связи (мой любимый кейс для NRT).

Сервер: VM, 8 vCPU (AMD EPYC), 32 ГБ ОЗУ.

Docker Compose (7 контейнеров):

  • ZooKeeper 3.9.2

  • Fluss 0.8.0 CoordinatorServer

  • Fluss 0.8.0 TabletServer

  • Flink 1.20.1 JobManager

  • Flink 1.20.1 TaskManager

  • MinIO (S3-совместимое хранилище)

  • Генератор данных

Топология стенда: 7 контейнеров в Docker Compose.
Топология стенда: 7 контейнеров в Docker Compose.

Сценарий

~500 базовых станций по 12 регионам. ~100 000 абонентов с тарифами (Базовый, Стандарт, Премиум, VIP, Бизнес). Поток CDR-событий (Call Detail Records) со скоростью 2000 событий/сек — звонки, SMS, передача данных, обрывы (CALL_DROP), ошибки хэндовера (HANDOVER_FAIL). Это сопоставимо с нагрузкой оператора на 5—10 млн абонентов по голосу и SMS; крупные операторы с data-трафиком генерируют на порядок больше.

«Золотая тысяча» — это VIP-абоненты (subscriber_id 1-1000), для которых обрывы и инциденты нужно обнаруживать в секунды, а не в минуты. У этих абонентов высокий ARPU (средний доход на абонента) и персональный SLA. Один пропущенный инцидент может стоить оператору контракта. Для остальных абонентов допустим агрегированный мониторинг с задержкой в минуты.

Примечание: кстати, я рассказывал про этот кейс с другой стороны на прошлогоднем SQL-ниндзя вот запись(https://www.youtube.com/watch?v=OY0CUGNdxGs&t=492s), там подробнее про real-time аналитику. Забавный факт: в том видео я упоминаю, что познакомился со StarRocks случайно именно в этом кейсе, а теперь работаю с ним на full-time.

Конвейер: SQL-команды, ноль кода

Flink Dashboard: 2 непрерывных джоба, тиринг-сервис и обогащение (3 загрузочных джоба завершились ранее).
Flink Dashboard: 2 непрерывных джоба, тиринг-сервис и обогащение (3 загрузочных джоба завершились ранее).

Шаг 1. Одной CREATE CATALOG подключаем Flink к Fluss. Дальше создаём три таблицы обычным CREATE TABLE, никаких 'connector' = '...' указывать не нужно, потому что каталог Fluss уже знает, куда и как писать. Запускаем три потока загрузки данных (каждый это отдельный Flink-джоб): справочник станций, справочник абонентов, поток CDR-событий.

Все SQL-скрипты для всех таблиц (CREATE CATALOG, CREATE TABLE, INSERT INTO) в комментариях к статье.

Шаг 2. Обогащение «3-way» Lookup JOIN. Lookup JOIN — это механизм Flink, при котором для каждой записи из потока выполняется точечный запрос (lookup) в справочную таблицу по первичному ключу. В отличие от обычного JOIN, который требует хранить обе стороны в памяти, Lookup JOIN обращается к справочнику на лету — это эффективно для обогащения потока данными, которые меняются редко. В нашем случае каждое CDR-событие обогащается данными абонента (имя, тариф) и станции (регион) через два Lookup JOIN по PK-таблицам Fluss, отсюда «3-way». Ключевой параметр — table.datalake.freshness = 30s, который запускает автоматический тиринг в Paimon каждые 30 секунд.

Итого: 5 потоковых Flink-джобов, запущенных SQL-командами (ни строчки Java/Python):

  1. Загрузка справочника станций.

  2. Загрузка справочника абонентов.

  3. Загрузка потока CDR-событий.

  4. Тиринг-сервис (Fluss → Paimon).

  5. Обогащение с 3-way Lookup JOIN.

Результат: работает!

Flink Overview: 2 непрерывных джоба работают, 6 завершённых загрузок, 0 ошибок.
Flink Overview: 2 непрерывных джоба работают, 6 завершённых загрузок, 0 ошибок.

Конвейер запущен, данные текут. За пару минут в Fluss накапливаются сотни тысяч обогащённых событий. Тиринг-сервис каждые 30 секунд переносит их в Paimon, Parquet-файлы аккуратно формируются в S3. Flink UI показывает стабильный throughput без backpressure.

Streamhouse на этапе ETL без нареканий. Данные исправно текут от генератора через Flink в Fluss и дальше в Paimon. Следующий логичный вопрос: А как аналитик может использовать эти данные для принятия решений?

4. А теперь – аналитика! А Flink SQL упирается в стену

Конвейер работает: 2000 CDR-событий в секунду обогащаются, переносятся в Paimon, за несколько минут накапливается более полутора миллионов строк. Данные есть. Но складировать данные — не цель. Их нужно использовать для принятия решений.

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

  1. Сколько событий обработано прямо сейчас?

  2. Какие VIP-абоненты теряют связь?

  3. В каких регионах больше всего инцидентов?

  4. Как меняется ситуация по часам?

У нас есть Flink SQL, который умеет работать и в пакетном режиме. Почему не в потоковом? Потому что потоковый режим Flink предназначен для непрерывных вычислений (INSERT INTO, Lookup JOIN, обогащение). А нам нужен разовый ответ на конкретный вопрос: «покажи TOP-10 прямо сейчас». Для этого переключаем execution.runtime-mode на batch и пробуем.

Запрос первый: Сколько событий?

SET 'execution.runtime-mode' = 'batch';
SELECT COUNT(*) AS total_events FROM enriched_events$lake;

Примечание: суффикс $lake (https://fluss.apache.org/docs/streaming-lakehouse/integrate-data-lakes/paimon/) задокументированный механизм Fluss, который указывает Flink читать только холодный слой Paimon. Таблица ведёт себя как обычная Paimon-таблица.

Ответ: 1 647 099 строк. Но пришёл через 712 секунд. Простой COUNT и больше 7 секунд? Но дело в том, что Flink не хранит агрегаты наготове. Он запускает полноценный batch-джоб: планирует граф выполнения, поднимает задачи, последовательно читает все Parquet-файлы Paimon, считает строки и возвращает результат. Это пакетный процесс, а не мгновенный ответ от OLAP-движка.

Для разового запроса – терпимо. Но вспомним требование: дашборд обновляется каждые 30 секунд. Виджет с COUNT должен обновиться быстрее, чем придёт ответ на предыдущий запрос, а он не успевает.

Запрос второй: Кто из VIP теряет связь?

Настоящие вопросы бизнеса сложнее COUNT. Для дашборда оператора нужны: TOP-10 VIP-абонентов по инцидентам (кому звонить прямо сейчас), тепловая карта по регионам (где проблемы), часовой тренд (как меняется ситуация). Запросы это стандартные GROUP BY + ORDER BY + агрегации по таблице enriched_events$lake.

Все SQL-запросы аналитики (с полными колонками и метриками) и их результаты в комментариях к статье.

Результат: 15-25 секунд на 1.6M строк за каждый запрос. Flink честно сканирует все Parquet-файлы, строит хэш-таблицу группировки, сортирует. Для batch-движка — ожидаемый результат. Для интерактивного дашборда — нет. И это только один запрос.

Проблема: один запрос за раз

Дашборд — это не один запрос. Это 5–8 виджетов, каждый обновляется каждые 30 секунд. Плюс несколько аналитиков одновременно.

Flink SQL в batch-режиме выполняет запросы последовательно. У него нет конкурентных сессий, нет пула соединений, нет cost-based оптимизатора для ad-hoc запросов. Flink создавался для долгоживущих потоковых задач, а не для интерактивной аналитики.

Математика: 5 виджетов × 15 секунд = 75 секунд на один цикл обновления дашборда. При требуемом интервале 30 секунд запросы не успевают завершиться до начала следующего цикла. 

Итого, что мы выяснили

Streamhouse отлично решает задачу доставки данных: Flink обогащает поток, Fluss обеспечивает горячий слой, Paimon принимает в LSM-дерево. Как ETL-конвейер — работает безупречно.

Но между хранилищем и экраном аналитика, руководителя есть разрыв. Paimon — это формат хранения, не движок запросов. Flink — это потоковый вычислитель, не OLAP-система. Документация Fluss прямо указывает (https://fluss.apache.org/docs/maintenance/tiered-storage/lakehouse-storage/), что для аналитики по холодному слою рекомендуются внешние движки запросов. Документация Paimon перечисляет (https://paimon.apache.org/docs/1.0/engines/overview/) StarRocks, Trino, Spark и другие.

 Нам нужен MPP-движок!

5. Добавляем StarRocks

Streamhouse + StarRocks: Flink (вычисления) → Fluss (горячий слой) → Paimon (холодный слой) → StarRocks (аналитика).
Streamhouse + StarRocks: Flink (вычисления) → Fluss (горячий слой) → Paimon (холодный слой) → StarRocks (аналитика).

Раз архитектура сама подсказывает, что нужен аналитический движок, попробуем. Документация Paimon перечисляет несколько вариантов: StarRocks, Trino, Spark и другие. Все они умеют читать Paimon-таблицы через внешние каталоги. Я выбрал StarRocks, во-первых, потому что работаю в компании, которая делает из него Enterprise-решение и достаточно по нему экспертизы, во-вторых, потому что MPP-архитектура StarRocks оптимизирована именно для интерактивных запросов с низкой задержкой. На стенде, StarRocks 4.0.6 в одном Docker-контейнере (поддержка Paimon через внешний каталог доступна с версии 3.1).

Подключение, одна SQL-команда:

CREATE EXTERNAL CATALOG paimon_lake
PROPERTIES (
    "type" = "paimon",
    "paimon.catalog.type" = "filesystem",
    "paimon.catalog.warehouse" = "s3://paimon/warehouse",
    "aws.s3.endpoint" = "http://minio:9000",
    "aws.s3.access_key" = "admin",
    "aws.s3.secret_key" = "password",
    "aws.s3.enable_path_style_access" = "true"
);

Одна SQL-команда, и StarRocks видит все таблицы Paimon. Никакого ETL, никакого копирования данных, никакого импорта. StarRocks читает Parquet-файлы Paimon напрямую из S3 (MinIO), те самые файлы, которые создаёт тиринг-сервис Fluss. Данные существуют в одном экземпляре, просто два движка обращаются к ним для разных задач.

Те же три запроса, а результат другой

Запускаем точно те же запросы, что работали медленно через Flink SQL. Streaming-джобы в Flink продолжают работать, конвейер обогащения и тиринга не останавливаем. StarRocks обращается к Paimon через собственный каталог, полностью независимо от Flink. Единственное изменение в SQL, вместо enriched_events$lake пишем paimon_lake.telecom.enriched_events.

  1.  COUNT(): 1,2 сек (было 7–12 сек через Flink SQL) - ускорение в 6–10 раз.

  2. TOP-10 VIP по инцидентам: 2,4 сек (было 15–25 сек через Flink SQL) – 6–10 раз.

  3. Тепловая карта по регионам: 3.0 сек на 1.6M строк (было 15–25 сек) – 5–8 раз.

Уточнение: 1-3 секунды — это режим External Catalog: прямое чтение из S3 одной SQL-командой, без копирования данных и без дополнительного тюнинга параметров. Если для дашборда нужны миллисекунды, достаточно создать Materialized View с автообновлением — данные обновляются из Paimon каждые 60 секунд, а те же запросы ускоряются в 30 раз минимум: COUNT за 10 мс, TOP-10 VIP за 30 мс, тепловая карта за 25 мс.

Скорость серии запросов

Запускаем пять запросов последовательно к StarRocks, один за другим, без пауз. Все пять выполняются за ~20 секунд (~4 секунды на запрос). Для сравнения: через Flink SQL те же пять запросов заняли бы 75+ секунд последовательно. На нашем стенде это один контейнер StarRocks, читающий из S3 (MinIO), в продакшен-кластере с локальным SSD и несколькими BE-нодами эти цифры будут лучше.

Тут надо оговориться

StarRocks — это не потоковый движок обработки данных. Он не конкурирует с Flink или Fluss. В этой архитектуре StarRocks читает только холодный слой (Paimon). Свежие данные, которые ещё находятся в Fluss и не перенеслись в Paimon, для StarRocks недоступны. Свежесть данных для аналитики ограничена частотой тиринга, на нашем стенде это 2–37 секунд (в среднем ~17 секунд).

Для Union Read (горячий + холодный) по-прежнему нужен Flink SQL. 

Хорошая новость: прямая интеграция StarRocks с Fluss уже в планах. В Roadmap StarRocks 2026 (https://github.com/StarRocks/starrocks/issues/67632) среди приоритетов это нативная поддержка Fluss как источника данных. Со стороны Fluss roadmap (https://fluss.apache.org/roadmap/) тоже включает Union Read для внешних движков, включая StarRocks. Когда это будет реализовано, StarRocks сможет читать и горячий, и холодный слой напрямую.

6. Результаты стенда

Сервер: VM, 8 vCPU (AMD EPYC), 32 ГБ ОЗУ. Docker Compose: 7 контейнеров (ZooKeeper 3.9.2, Fluss 0.8.0, Flink 1.20.1, StarRocks 4.0.6, MinIO). Сценарий: 500 станций, 100K абонентов, 2 000 CDR/сек, тиринг каждые 30 секунд. S3 (MinIO).

 Flink SQL против StarRocks: сравнение

Запрос

Flink SQL (batch)

StarRocks

Ускорение

COUNT(*) по 1.6M строк

7-12 сек

1.2 сек

6-10×

TOP-10 VIP (GROUP BY + filter + LIMIT)

15-25 сек

2.4 сек

6-10×

Тепловая карта (GROUP BY + CASE)

15-25 сек

3.0 сек

5-8×

Часовой тренд (DATE_FORMAT + GROUP BY)

15-25 сек

2.0 сек

7-12×

5 запросов подряд

75+ сек

~20 сек

Работа во время стриминга

Да, но делит ресурсы

Да, независимо

-

Свежесть данных: 2-37 сек

Ключевая метрика Streamhouse — End-to-End Freshness: время от записи события до его доступности для аналитических запросов (NOW() - MAX(source_commit_ts)). Серия из пяти замеров через StarRocks при работающих streaming-джобах, хранилище S3 (MinIO):

Замер

Строк в Paimon

Freshness (сек)

1

1 647 099

2

2

1 647 099

34

3

1 647 099

5

4

1 647 099

37

5

1 647 099

8

Средняя свежесть (freshness): ~17 сек. (диапазон 2–37 сек). Разброс зависит от того, когда вы отправили запрос: сразу после тиринга – значит, данные свежие (2–5 сек), незадолго до следующего – максимальная задержка (до 37 сек).

Объём и стабильность

За несколько сессий тестирования в Paimon накопилось 1 585 699 обогащённых CDR-событий (lookup JOIN по 100K абонентов и 500 станций). Пять Flink-джобов работали стабильно без деградации на обоих хранилищах. StarRocks читал данные параллельно с работающим конвейером без конфликтов.

SQL-only конвейер

Весь конвейер от загрузки до аналитики без единой строки Java/Python:

  • 8 SQL-команд в Flink: CREATE CATALOG + CREATE DATABASE + 3 CREATE TABLE + 3 INSERT INTO.

  • 1 SQL-��оманда в StarRocks: CREATE EXTERNAL CATALOG.

Порог входа для инженера, знающего SQL, минимален.

 Тут надо указать ограничения: стенд был на одной машине (не кластер), генератор CDR с равномерным распределением (не реальный трафик). Это доказательство архитектуры, а не нагрузочный тест!

7. Когда нужен Streamhouse

Нужен, если:

  • Информационные панели реального времени. Логистика, e-commerce, телеком, финансы, где данные должны обновляться в секунды.

  • Обнаружение мошенничества. Секунды задержки = пропущенная мошенническая транзакция.

  • IoT и телеметрия. Потоки с датчиков, GPS, RFID с немедленной реакцией на аномалии.

  • Хранилища признаков для ML. Модели требуют свежие признаки без задержки пакетно��о конвейера

Не нужен (Lakehouse достаточно), если:

  • Задержка 5-15 минут устраивает.

  • Нагрузка пакетно-ориентированная (ночные отчёты, еженедельный ETL).

  • Нет требований к обновлениям справочников в реальном времени.

  • Бюджет ограничен, а сложность Flink + Fluss + Paimon избыточна для задачи.

Что учесть перед стартом

Fluss - молодой проект (0.8.0-incubating). Мало документации, нет веб-интерфейса, отладка через журналы. Всё ещё зависит от ZooKeeper. Для production нужны дополнительные тесты отказоустойчивости.

Paimon - растущая экосистема. Поддержка уже широкая (StarRocks, Trino, Spark и другие), но еще не такая, как у Iceberg. Зато есть совместимость с Iceberg V3 - лучшее из двух миров.

8. Заключение

Streamhouse — это отличный потоковый ETL-конвейер. Flink обогащает данные в реальном времени, Fluss обеспечивает колоночное горячее хранение с субсекундной задержкой, Paimon принимает всё это в LSM-дерево с автоматической компактацией. Три компонента, SQL-only, ни строчки Java-кода — все работает.

 Но без аналитического движка данные остаются в Parquet-файлах, к которым никто не может быстро обратиться. Flink SQL спроектирован для потоковых вычислений, а не для интерактивных дашбордов. Это не недостаток Flink, это просто не его задача. Данные доставлены, но не доступны для бизнес-пользователя.

StarRocks закрывает этот разрыв: одна SQL-команда для подключения к Paimon, MPP-архитектура для быстрых агрегаций, конкурентность для десятков одновременных запросов. Не замена Flink, а дополнение к нему. Каждый компонент делает то, для чего спроектирован, и не пытается заменить другие.

Полная формула, к которой мы пришли эмпирически:

Streamhouse + аналитический движок =
  Flink (вычисления) + Fluss (горячий слой) + Paimon (холодный слой) + StarRocks (аналитика)

А какой стек для real-time аналитики используете вы? Если пробовали Fluss, Paimon или другие потоковые Lakehouse-решения, поделитесь опытом в комментариях. Конфигурацию Docker Compose со стендом выложу по запросу.

Хотите больше деталей по этой статье? Готовлю расширенную версию с архитектурным разбором Iceberg против Paimon, внутренностями Flink 2.0, полным SQL-кодом стенда и доработанным нашей командой для этого PoC коннектора Fluss к Flink 2.2. Подписывайтесь на Telegram-канал @starrocks_selena(https://t.me/starrocks_selena) опубликую там, как только будет готово.