Welcome to my personal place for love, peace and happiness 🤖

StarRocks: Архитектура, Практика и место в современном Data Stack

StarRocks — это аналитическая MPP-база данных нового поколения.
Если коротко, она пытается решить трилемму аналитики: объединить скорость ClickHouse (за счет векторизации и C++), гибкость Trino (поддержка сложных JOIN-ов) и простоту использования MySQL (совместимый протокол).

Это короткое руководство проведет вас от понимания архитектуры до построения простого конвейера загрузки данных (ETL) в домашнем продакшене.


Часть 1. Архитектура: FE и BE

В отличие от PostgreSQL (монолит) или ClickHouse (где узлы часто одноранговые), StarRocks имеет четкое разделение ролей. Это критически важно для понимания масштабирования и эксплуатации.

1. FE (Frontend) — “Мозг”

Написан на Java.

  • Роль: Управляющий слой.
  • Функции:
    • Принимает подключения клиентов (по протоколу MySQL).
    • Хранит метаданные (схемы таблиц, права доступа).
    • Парсит SQL и строит план выполнения запроса (Query Plan).
    • Управляет транзакциями загрузки данных.
  • Масштабирование: Обычно запускают 1 или 3 узла для обеспечения высокой доступности (HA).
  • Важно: Клиенты (DBeaver, BI, сurl) подключаются только к FE.

2. BE (Backend) — “Мускулы”

Написан на C++ (использует SIMD-инструкции процессора).

  • Роль: Слой хранения и вычислений.
  • Функции:
    • Физически хранит данные (в колоночном формате).
    • Выполняет “тяжелую” работу: фильтрацию, агрегацию, JOIN-ы.
    • Управляет репликацией данных.
  • Масштабирование: Можно добавлять узлы линейно. Чем больше BE, тем быстрее выполняются запросы и тем больше данных можно хранить.

В Docker All-in-One: Оба компонента упакованы в один контейнер для удобства, но слушают разные порты:

  • `9030`: FE (SQL интерфейс, сюда идет DBeaver).
  • `8030`: FE (HTTP API для загрузки Stream Load, сюда идет curl).
  • `8040`: BE (HTTP API метрик и логов).

Часть 2. Быстрый старт (Docker Compose)

Мы поднимем стек StarRocks и MinIO (S3-совместимое хранилище), используя bridge-сеть для связности.

Файл `docker-compose.yml` (Полностью рабочий пример):

version: "3.9"

networks:
  starrocks-stack-network:
    driver: bridge

services:
  starrocks:
    image: starrocks/allin1-ubuntu:4.0-latest
    container_name: starrocks
    hostname: starrocks.local.com
    platform: "linux/amd64"
    restart: unless-stopped
    ports:
      - "9030:9030" # MySQL Protocol (SQL клиенты)
      - "8030:8030" # FE HTTP (Stream Load)
      - "8040:8040" # BE HTTP (Logs/Metrics)
    environment:
      - TZ=UTC
    networks:
      starrocks-stack-network:
    volumes:
      # Персистентность данных (чтобы данные не исчезли после рестарта)
      - ${HOME}/dv/starrocks/be/storage:/data/deploy/starrocks/be/storage
      - ${HOME}/dv/starrocks/be/log:/data/deploy/starrocks/be/log
      - ${HOME}/dv/starrocks/fe/meta:/data/deploy/starrocks/fe/meta
      - ${HOME}/dv/starrocks/fe/log:/data/deploy/starrocks/fe/log

  minio:
    image: quay.io/minio/minio
    container_name: minio
    platform: "linux/amd64"
    hostname: minio.local.com
    restart: unless-stopped
    ports:
      - "9000:9000" # S3 API
      - "9001:9001" # Web UI
    networks:
      starrocks-stack-network:
    environment:
      MINIO_ROOT_USER: root
      MINIO_ROOT_PASSWORD: rootroot
    volumes:
      - ${HOME}/dv/minio/data:/data
    command: server /data --console-address ":9001"

Запуск:
`docker-compose up -d`


Часть 3. Моделирование данных (Table Design)

В StarRocks нельзя просто “создать таблицу”. Нужно выбрать тип ключа (Key Model), который определит, как база будет хранить и обновлять данные.

Подключение (DBeaver): `localhost:9030`, User: `root`, Password: (пусто).

CREATE DATABASE IF NOT EXISTS demo_db;
USE demo_db;

1. Primary Key Model (Для изменяемых данных)

Это “флагманская” возможность StarRocks. Она поддерживает быстрые Upsert (вставка новых или обновление старых записей по ID) в реальном времени.

CREATE TABLE IF NOT EXISTS users (
    user_id INT NOT NULL,
    username VARCHAR(50),
    email VARCHAR(100),
    register_date DATE, 
    city VARCHAR(50)
)
PRIMARY KEY (user_id) -- Уникальный ключ
DISTRIBUTED BY HASH(user_id) -- Распределение данных
PROPERTIES (
    "replication_num" = "1" -- Для локального теста ставим 1 реплику
);

2. Aggregate Key Model (Для витрин данных)

База автоматически агрегирует данные при вставке. Если вы вставите новую продажу с *существующими* датой и категорией, StarRocks не создаст новую строку, а прибавит суммы к уже существующей строке. Это экономит место и ускоряет `GROUP BY`.

CREATE TABLE IF NOT EXISTS daily_sales (
    report_date DATE NOT NULL,
    category VARCHAR(50) NOT NULL,
    
    -- Метрики с функцией агрегации:
    total_amount BIGINT SUM DEFAULT "0", 
    items_sold INT SUM DEFAULT "0"       
)
AGGREGATE KEY (report_date, category)
DISTRIBUTED BY HASH(report_date) BUCKETS 3
PROPERTIES (
    "replication_num" = "1"
);

Часть 4. загрузка данных users (Stream Load)

Для загрузки данных в продакшене мы используем Service Account (Техническую учетную запись). Это стандарт безопасности: мы не используем `root` и не используем токены в конфигах (так как они требуют перезагрузки кластера для смены).

Шаг 1. Создание сервисного пользователя (SQL)

Выполнять под `root`:

-- 1. Создаем пользователя-бота
CREATE USER IF NOT EXISTS 'etl_loader'@'%' IDENTIFIED BY 'SecretPass123!';

-- 2. Даем права ТОЛЬКО на вставку и чтение в базе demo_db
GRANT INSERT, SELECT ON demo_db.* TO 'etl_loader'@'%';

-- Права применяются мгновенно.

Шаг 2. Загрузка сложного JSON через CURL

Stream Load — это самый быстрый способ загрузки (до 100 МБ/сек на узел). Он поддерживает транзакционность (ACID).

Пример файла `users.json`:

{
  "users": [
    {"user_id": 101, "username": "alex", "email": "a@test.com", "city": "NY"},
    {"user_id": 102, "username": "bob", "email": "b@test.com", "city": "LA"}
  ]
}

Команда загрузки (Terminal):

curl --location-trusted \
    -u etl_loader:SecretPass123! \
    -H "Expect: 100-continue" \
    -H "format: json" \
    -H "strip_outer_array: true" \
    -H "json_root: $.users" \
    -H "jsonpaths: [\"$.user_id\", \"$.username\", \"$.email\", \"$.city\"]" \
    -H "columns: user_id, username, email, city" \
    -T "users.json" \
    -XPUT http://localhost:8030/api/demo_db/users/_stream_load

Ответ

{
    "TxnId": 9596,
    "Label": "a9a37ab6-3678-4c08-95b7-2fd8b6ae973e",
    "Db": "demo_db",
    "Table": "users",
    "Status": "Success",
    "Message": "OK",
    "NumberTotalRows": 2,
    "NumberLoadedRows": 2,
    "NumberFilteredRows": 0,
    "NumberUnselectedRows": 0,
    "LoadBytes": 177,
    "LoadTimeMs": 153,
    "BeginTxnTimeMs": 2,
    "StreamLoadPlanTimeMs": 2,
    "ReadDataTimeMs": 0,
    "WriteDataTimeMs": 26,
    "CommitAndPublishTimeMs": 121
}%

Шаг 3. Загрузка в Aggregate Table (Example)

Давайте “дольем” данные в таблицу продаж. Агрегация произойдет на лету.
Файл sales.json (простой список):

[
    {"dt": "2023-11-01", "cat": "Electronics", "amt": 100, "qty": 1},
    {"dt": "2023-11-01", "cat": "Electronics", "amt": 50,  "qty": 1}
]

curl --location-trusted \
    -u etl_loader:SecretPass123! \
    -H "format: json" \
    -H "Expect: 100-continue" \
    -H "strip_outer_array: true" \
    -H "jsonpaths: [\"$.dt\", \"$.cat\", \"$.amt\", \"$.qty\"]" \
    -H "columns: report_date, category, total_amount, items_sold" \
    -T "sales.json" \
    -XPUT http://localhost:8030/api/demo_db/daily_sales/_stream_load

Ответ:

{
    "TxnId": 9613,
    "Label": "bce0721a-dc2d-4927-be93-e0979a57873d",
    "Db": "demo_db",
    "Table": "daily_sales",
    "Status": "Success",
    "Message": "OK",
    "NumberTotalRows": 2,
    "NumberLoadedRows": 2,
    "NumberFilteredRows": 0,
    "NumberUnselectedRows": 0,
    "LoadBytes": 143,
    "LoadTimeMs": 52,
    "BeginTxnTimeMs": 3,
    "StreamLoadPlanTimeMs": 2,
    "ReadDataTimeMs": 0,
    "WriteDataTimeMs": 24,
    "CommitAndPublishTimeMs": 20
}%

Разбор заголовков:

  • `-u ...`: Авторизация сервисным пользователем.
  • `Expect: 100-continue`: Критически важно для надежности передачи больших файлов.
  • `json_root: $.users`: Указывает базе, что данные лежат внутри ключа `users`.
  • `strip_outer_array: true`: Говорит базе, что внутри лежит массив `[...]` и его нужно “развернуть” в отдельные строки.

Часть 5. Совместимость и Trino Dialect

Одна из сильных сторон StarRocks — способность “притворяться” другими базами данных для облегчения миграции.

Если у вас есть дашборды, написанные на диалекте Trino (Presto), вам не нужно переписывать все SQL-запросы.

Пример трансляции функций:

-- Функция Trino, которой нет в StarRocks
SELECT doy(date '2022-03-06'); 
-- Ошибка: No matching function...

-- Проверяем, как StarRocks переведет этот запрос
TRANSLATE TRINO select doy(date '2022-03-06');
-- Результат: SELECT dayofyear('2022-03-06')

-- Включаем режим автоматической трансляции в сессии
SET sql_dialect = 'trino'; 

-- Теперь запрос выполняется корректно, но это не правда. а вот так SELECT dayofyear('2022-03-06') работает. Может бага или у меня версия не та. 
SELECT doy(date '2022-03-06');   

-- Возвращаем нативный режим
SET sql_dialect = 'starrocks';

*(Примечание: Поддержка диалекта постоянно расширяется, но некоторые специфические функции могут требовать ручной замены).*


Итог: Сравнение и Выбор решения ( грубо )

Характеристика StarRocks ClickHouse Trino (Presto)
Основной сценарий OLAP-витрины с JOIN-ами и обновлениями данных Сбор логов, событий, метрик (Append-only) Федерация данных (запрос к S3 + Postgres + Kafka одновременно)
JOIN производительность ⭐⭐⭐ (Excellent, CBO оптимизатор) ⭐ (Слабо, требует денормализации) ⭐⭐⭐ (Excellent)
Обновление (UPDATE) ⭐⭐⭐ (Работает как в OLTP, Primary Key) ⭐ (Тяжелые асинхронные ALTER) ❌ (Обычно только полная перезапись партиций), iceberg не в счёт :)
Язык Engine C++ (SIMD Vectorized) C++ (SIMD Vectorized) Java (JVM)
Место в стеке Serving Layer (Быстрый доступ для BI) Storage Layer (Хранение логов) Query Engine (Ad-hoc запросы к Data Lake)

Выбирайте StarRocks, если:

  1. Вам нужна “витрина” для BI (Superset/Tableau), где данные должны быть всегда свежими (Real-time updates).
  2. Ваш бизнес требует сложных аналитических запросов с множеством JOIN-ов, и ClickHouse не справляется/падает по памяти.
  3. Вы хотите использовать стандартный протокол MySQL без установки проприетарных драйверов.
Follow this blog
Send
Share
Tweet
Pin