{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Yuriy Gavrilov: posts tagged Data Engineer",
    "_rss_description": "Welcome to my personal place for love, peace and happiness 🤖 Yuiry Gavrilov",
    "_rss_language": "en",
    "_itunes_email": "yvgavrilov@gmail.com",
    "_itunes_categories_xml": "",
    "_itunes_image": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic-square@2x.jpg?1643451008",
    "_itunes_explicit": "no",
    "home_page_url": "https:\/\/gavrilov.info\/tags\/data-engineer\/",
    "feed_url": "https:\/\/gavrilov.info\/tags\/data-engineer\/json\/",
    "icon": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic@2x.jpg?1643451008",
    "authors": [
        {
            "name": "Yuriy Gavrilov - B[u]g - for charity.gavrilov.eth",
            "url": "https:\/\/gavrilov.info\/",
            "avatar": "https:\/\/gavrilov.info\/pictures\/userpic\/userpic@2x.jpg?1643451008"
        }
    ],
    "items": [
        {
            "id": "331",
            "url": "https:\/\/gavrilov.info\/all\/utinye-istorii-chast-2-ekosistema-duckdb-v-2026-godu\/",
            "title": "Утиные истории: часть 2. Экосистема DuckDB в 2026 году",
            "content_html": "<p>В <a href=\"https:\/\/gavrilov.info\/all\/utinye-istorii-s-duckdb\/\">первой части Утиных историй<\/a> мы детально разбирали, как DuckDB переворачивает принципы локальной и встраиваемой аналитики. Сегодня на календаре 19 апреля 2026 года, и экосистема «утки» развивается с невероятной скоростью. На днях вышел юбилейный, 40-й выпуск информационного бюллетеня от команды MotherDuck.<\/p>\n<p>В этой статье мы разберем самые горячие новинки обновления: релиз DuckLake 1.0, нативную поддержку протокола PostgreSQL, векторный поиск и то, как DuckDB покоряет новые горизонты программирования (от Elixir к Rust).<\/p>\n<hr \/>\n<h3>🦆 DuckLake 1.0: Озерный формат (Lakehouse) готов к продакшену<\/h3>\n<p>Главная новость апреля — релиз <b>DuckLake 1.0<\/b>. Это lakehouse-формат, в котором <b>все метаданные хранятся непосредственно в каталоге базы данных<\/b> (в PostgreSQL, SQLite или самой DuckDB), а не в разрозненных файлах, как это сделано в Delta Lake или Apache Iceberg.<\/p>\n<h4>Что под капотом?<\/h4>\n<ul>\n<li><b>Сортированные таблицы и Bucket-партиционирование:<\/b> Оптимизируют чтение и ускоряют аналитику.<\/li>\n<li><b>Решение проблемы “маленьких файлов”:<\/b> Мелкие транзакции (где количество строк N≤10 по умолчанию) сохраняются напрямую (inlining) в каталог. Для сброса в объектное хранилище используется команда `CHECKPOINT`.<\/li>\n<li><b>Векторы удаления (Deletion vectors):<\/b> Поддержка совместимости с Iceberg.<\/li>\n<li><b>Новый тип Variant:<\/b> Позволяет работать с полуструктурированными данными, автоматически “раскладывая” их на примитивные типы для быстрого выполнения запросов.<\/li>\n<\/ul>\n<h4>Ускорение в цифрах<\/h4>\n<p>Отказ от чтения разрозненных файлов метаданных дает феноменальный прирост производительности базовых операций агрегации. Если сравнивать время выполнения запросов до оптимизации (T old) и с использованием чтения исключительного из каталога метаданных DuckLake (T new), то выигрыш в скорости можно выразить формулой:<\/p>\n<p>Speedup =T new \/ T old<\/p>\n<p>Для запросов вида `COUNT(*)` этот Speedup составляет от 8 до 258 раз! А вызов системной функции `duckdb_views()` ускорился примерно в 70 раз.<\/p>\n<p>Неудивительно, что DuckLake уже входит в топ-10 расширений по количеству скачиваний и поддерживается клиентами Apache DataFusion, Spark, Trino и Pandas. Издательство O’Reilly даже готовит книгу *“DuckLake: The Definitive Guide”*. (Фича доступна в DuckDB v1.5.2).<\/p>\n<hr \/>\n<h3>🐘 MotherDuck теперь говорит на языке Postgres<\/h3>\n<p>Чтобы внедрить мощь DuckDB в свою инфраструктуру разработчикам часто приходилось искать специальные драйверы и коннекторы. Это в прошлом!<\/p>\n<p>MotherDuck запустили <b>PostgreSQL wire-protocol endpoint<\/b>. Теперь вы можете выполнять аналитические SQL-запросы к DuckDB, используя совершенно любой клиент, пулер (pooler) или BI-инструмент, совместимый с Postgres. Устанавливать библиотеки DuckDB на клиент больше не нужно!<\/p>\n<p>Достаточно направить ваш текущий клиент по адресу:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">pg.us-east-1-aws.motherduck.com:5432<\/code><\/pre><p>Авторизация происходит с помощью токена MotherDuck. При этом диалект SQL остается утиным (хотя он в значительной степени и совместим с PostgreSQL). Миграция данных возможна через обычные ETL-утилиты или расширение `pg_duckdb`.<\/p>\n<hr \/>\n<h3>🦀 `quack-rs`: Пишем расширения на чистом Rust<\/h3>\n<p>Мощным толчком для развития комьюнити-плагинов стал релиз <b>`quack-rs`<\/b>. До сих пор написание расширений для DuckDB на Rust требовало создания слоев совместимости (C++ glue) и возни с CMake.<\/p>\n<p>`quack-rs` — это SDK на чистом Rust, который оборачивает *C Extension API* (v1.1+). Инструмент предоставляет безопасные абстракции и устраняет 16 задокументированных проблем с FFI (Foreign Function Interface), предотвращая “тихую” порчу данных через NULL и ошибки “double-free” в callback-функциях агрегации.<\/p>\n<p>Для старта нового расширения достаточно вызвать функцию:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">generate_scaffold();<\/code><\/pre><p>Она сгенерирует все 11 файлов, необходимых для подачи плагина в репозиторий сообщества. Теперь безопасность памяти Rust и скорость DuckDB идут рука об руку.<\/p>\n<hr \/>\n<p><summary><b>🛠️ Важные новости комьюнити и новые инструменты (Нажмите, чтобы развернуть)<\/b><\/summary><\/p>\n<h4>1. Lance Extension и векторный поиск<\/h4>\n<p>Открытый колоночный формат Lance, оптимизированный под ML и векторный поиск, теперь доступен и в DuckDB! Hao Ding реализовал поддержку чтения и записи таблиц Lance.<\/p>\n<p>Писать данные можно так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">COPY (...) TO 'path\/dataset.lance' (FORMAT lance, MODE 'overwrite');<\/code><\/pre><p>Для поиска доступны функции: `lance_vector_search()`, `lance_fts()` и `lance_hybrid_search()`.<\/p>\n<h4>2. Dux: Распределенные DataFrame для Elixir<\/h4>\n<p>Появилась библиотека `dux` — lazy-by-default (ленивые по умолчанию) датафреймы для Elixir поверх DuckDB. Конвейеры данных аккумулируются в AST структуре `%Dux{}` и компилируются в SQL CTE. Заявлено, что на тестах ($10$ млн строк, Apple M4 Max) Dux обгоняет Polars (Explorer) до 2.5 раз на операциях фильтрации.<\/p>\n<h4>3. eBPF трассировка с ИИ (`systing 1.0`)<\/h4>\n<p>Инструмент для трассировки ядра Linux `systing` (написанный Josef Bacik) перешел с сохранения логов Perfetto на прямую запись в DuckDB. А интеграция с Claude Code MCP (Model Context Protocol) позволяет ИИ динамически анализировать эти базы данных DuckDB в реальном времени.<\/p>\n<h4>4. Jupyter и DuckDB Kernel на Go<\/h4>\n<p>Создано полноценное Go-ядро DuckDB для Jupyter, которое напрямую отправляет поток данных (Arrow IPC) во встроенный WASM-просмотрщик `hugr-perspective-viewer`. На панели также агрегируются метрики без написания SQL: `approx_unique`, `avg`, `min`, `max`, `count`.<\/p>\n<h4>5. Web-framework, Neovim и игры<\/h4>\n<ul>\n<li><b>`neovim-web`<\/b>: Фреймворк для создания статических сайтов с горячими клавишами Vim. Фишка — встроенная консоль DuckDB-Wasm (команда `:sql`) прямо в браузере.<\/li>\n<li><b>`connections.duckdb`<\/b>: Аналог игры “Connections” от NYT, целиком реализованный на SQL макросах.<\/li>\n<\/ul>\n<hr \/>\n<h3>💻 Бенчмарки: Большие данные на самом дешевом MacBook Neo<\/h3>\n<p>Способен ли базовый ноутбук переваривать серьезную аналитику? Gábor проверил работу DuckDB на новом MacBook Neo с процессором Apple A18 Pro.<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">Бенчмарк<\/td>\n<td style=\"text-align: center\">Параметры<\/td>\n<td style=\"text-align: center\">Результат (медиана)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>ClickBench<\/b><\/td>\n<td style=\"text-align: center\">100M строк, лимит RAM: 5GB<\/td>\n<td style=\"text-align: center\">< 1 секунды (cold run)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>TPC-DS<\/b><\/td>\n<td style=\"text-align: center\">SF100<\/td>\n<td style=\"text-align: center\">1.63 секунды на запрос<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>TPC-DS<\/b><\/td>\n<td style=\"text-align: center\">SF300<\/td>\n<td style=\"text-align: center\">79 минут (высокий disk spill)<\/td>\n<\/tr>\n<\/table>\n<p>Даже при 5 гигабайтах оперативной памяти DuckDB демонстрирует субсекундные ответы, эффективно утилизируя NVMe-память, когда RAM исчерпан (disk spill).<\/p>\n<hr \/>\n<h3>🎓 Внедрение в Академическую Среду<\/h3>\n<p>Стоит отдельно отметить профессора Dr. Torsten Grust из Тюбингенского университета (Германия). Его исследовательская группа, стоящая на стыке баз данных и технологий языков программирования, недавно запустила открытый курс <b>DiDi<\/b> (*Design and Implementation of DuckDB Internals*).<\/p>\n<p>Курс использует DuckDB для обучения студентов архитектуре СУБД: от управления памятью и векторизованного исполнения до оптимизации запросов (включает около 50 рабочих примеров кода).<\/p>\n<hr \/>\n<h3>🗓 Ближайшие Мероприятия<\/h3>\n<ul>\n<li><b>21 апреля 2026 (Онлайн):<\/b> Стрим MotherDuck Now Speaks Postgres: Fast Analytics Without Changing Your Stack. Демонстрация нового PG wire-protocol.<\/li>\n<li><b>30 апреля 2026 (Сан-Франциско):<\/b> DuckDB + MotherDuck Meetup. Разговоры про DuckLake 1.0 и распределенный DuckDB (проект OpenDuck).<\/li>\n<\/ul>\n<p>Экосистема DuckDB перестала быть просто *“SQLite для аналитики”*. С релизом DuckLake, нативной интеграцией протокола Postgres и появлением SDK для Rust, “утка” окончательно закрепилась как основополагающий инструмент в стеке современных данных.<\/p>\n",
            "date_published": "2026-04-20T00:12:54+03:00",
            "date_modified": "2026-04-20T00:12:44+03:00",
            "tags": [
                "Data",
                "Data Engineer",
                "DuckDB"
            ],
            "_date_published_rfc2822": "Mon, 20 Apr 2026 00:12:54 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "331",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "330",
            "url": "https:\/\/gavrilov.info\/all\/sozdanie-pochtireal-time-data-lake-bystraya-migraciya-dannyh-v-a\/",
            "title": "🚀 Создание почтиReal-Time Data Lake: Быстрая миграция данных в Apache Iceberg или Parquet",
            "content_html": "<p>Сегодня Gemini 3.1 Pro Preview расскажет свое мненИИе))<\/p>\n<p>Связывание транзакционных баз (PostgreSQL) и аналитических хранилищ (ClickHouse) через прямые агрегации и `JOIN` часто приводит к жесточайшим блокировкам и деградации продакшена. Когда бизнес требует быстрый результат, а внедрение полноценного CDC (Debezium + Kafka) откладывается из-за сроков и сложности, лучшим решением становится пакетная и микро-пакетная выгрузка данных в озеро (в форматы Parquet и Apache Iceberg).<\/p>\n<p>С точки зрения архитектуры, наша главная цель — минимизировать время загрузки данных T load и усилия инженеров на развертывание E setup. Наша целевая функция: min(T load × E setup)<\/p>\n<p>В этой статье собраны исключительно рабочие, протестированные подходы для быстрой интеграции с озером данных (Data Lake) и аналитическим движком Trino.<\/p>\n<hr \/>\n<h3>🐘 1. Экспорт данных из PostgreSQL: Проверенные инструменты<\/h3>\n<p>Мы полностью исключаем создание и восстановление тяжелых дампов (`pg_dump`). Вся транзитная нагрузка ложится <b>исключительно на асинхронные реплики<\/b>.<\/p>\n<h4>🌟 Подход А: Движок OLake (Самый быстрый старт в Iceberg)<\/h4>\n<p>Для задачи “результат нужен вчера и без сложного стека” идеально подходит <b>OLake<\/b>. Это высокопроизводительный движок репликации баз данных напрямую в Apache Iceberg (или Parquet), минуя промежуточные шины сообщений.<\/p>\n<p><b>Шаг 1. Запуск сервиса (конфигурация `docker-compose.yml`):<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">version: '3.8'\nservices:\n  olake:\n    image: olakeio\/olake:latest\n    ports:\n      - &quot;8080:8080&quot;\n    environment:\n      # Настройки доступов к вашему S3\/MinIO\n      - AWS_ACCESS_KEY_ID=your_access_key\n      - AWS_SECRET_ACCESS_KEY=your_secret_key\n      - AWS_REGION=us-east-1<\/code><\/pre><p><b>Шаг 2. Запуск репликации:<\/b><br \/>\nВы отправляете JSON-манифест в OLake (через UI или REST API). Движок самостоятельно делает первоначальный слепок PostgreSQL (Full Load со скоростью до 580K RPS), а затем переключается на чтение инкрементов (CDC):<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\n  &quot;pipeline_name&quot;: &quot;pg_to_iceberg_fast&quot;,\n  &quot;source&quot;: {\n    &quot;type&quot;: &quot;postgres&quot;,\n    &quot;connection_url&quot;: &quot;postgresql:\/\/readonly_user:password@replica_host:5432\/prod_db&quot;,\n    &quot;tables&quot;: [&quot;public.customer&quot;, &quot;public.orders&quot;]\n  },\n  &quot;destination&quot;: {\n    &quot;type&quot;: &quot;iceberg&quot;,\n    &quot;catalog_type&quot;: &quot;rest&quot;,\n    &quot;catalog_uri&quot;: &quot;http:\/\/iceberg-rest:8181&quot;,\n    &quot;warehouse_path&quot;: &quot;s3:\/\/my-datalake\/warehouse\/&quot;\n  },\n  &quot;replication_mode&quot;: &quot;full_and_cdc&quot;\n}<\/code><\/pre><hr \/>\n<h4>🐍 Подход Б: DuckDB (Легковесная скриптовая выгрузка)<\/h4>\n<p>Если вы хотите управлять выгрузкой через свои `cron`-задачи или Airflow, идеальным инструментом выступает аналитическая in-memory СУБД DuckDB. Ниже приведен протестированный Python-скрипт, который напрямую подключается к реплике и потоково перегоняет данные в Parquet на S3.<\/p>\n<p><b>Рабочий скрипт на Python (`export_to_lake.py`):<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">import duckdb\n\n# Открываем in-memory соединение DuckDB\ncon = duckdb.connect()\n\n# 1. Устанавливаем и загружаем необходимые расширения\ncon.execute(&quot;INSTALL postgres;&quot;)\ncon.execute(&quot;INSTALL httpfs;&quot;)\ncon.execute(&quot;LOAD postgres;&quot;)\ncon.execute(&quot;LOAD httpfs;&quot;)\n\n# 2. Настраиваем подключение к объектному хранилищу\ncon.execute(&quot;&quot;&quot;\n    SET s3_region='us-east-1';\n    SET s3_access_key_id='YOUR_KEY';\n    SET s3_secret_access_key='YOUR_SECRET';\n    SET s3_endpoint='s3.your-domain.com';\n&quot;&quot;&quot;)\n\n# 3. Подключаемся к реплике PostgreSQL\n# Команда ATTACH монтирует Postgres прямо в DuckDB под именем 'pg'\ncon.execute(&quot;&quot;&quot;\n    ATTACH 'host=replica_host port=5432 dbname=postgres user=postgres password=password' \n    AS pg (TYPE postgres);\n&quot;&quot;&quot;)\n\n# 4. Копируем таблицу public.customer в S3 в сжатом формате Parquet\ncon.execute(&quot;&quot;&quot;\n    COPY pg.public.customer\n    TO 's3:\/\/my-datalake\/raw\/customer.parquet' \n    (FORMAT PARQUET, COMPRESSION ZSTD);\n&quot;&quot;&quot;)\n\nprint(&quot;Выгрузка в Data Lake успешно завершена!&quot;)<\/code><\/pre><hr \/>\n<h3>🖱️ 2. Унификация аналитики с ClickHouse<\/h3>\n<p>Данные из ClickHouse также необходимо перегружать в Озеро (для Trino), чтобы избежать дублирования логики таблиц и нагрузки на саму СУБД тяжелыми сторонними `JOIN`-ами.<\/p>\n<h4>🛠 Базовый подход: Нативная табличная функция S3<\/h4>\n<p>Самый простой и не требующий дополнительной инфраструктуры способ — использовать встроенную функцию `s3()`. Она позволяет в один SQL-запрос отправить результат выборки прямо в объектное хранилище в нужном формате.<\/p>\n<p><b>Пример выгрузки из ClickHouse в Parquet (выполняется в `clickhouse-client`):<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">-- Прямая вставка данных из локальной MergeTree таблицы в файл Parquet на S3\nINSERT INTO FUNCTION s3(\n    'https:\/\/s3.us-east-1.amazonaws.com\/my-datalake\/raw\/clickhouse_export\/events_{_partition_id}.parquet',\n    'YOUR_KEY',\n    'YOUR_SECRET',\n    'Parquet'\n)\nSELECT id, event_type, payload, event_date\nFROM local_events_mergetree\nWHERE event_date = today();<\/code><\/pre><p>*Совет: Используйте макрос `{_partition_id}` в пути файла для автоматического разбиения больших выгрузок.*<\/p>\n<h4>🌊 Продвинутый подход: Project Antalya (ClickHouse + Iceberg)<\/h4>\n<p>Для построения архитектуры на десятилетие вперед разработчики из Altinity создали сборку <b>Project Antalya<\/b>. Она позволяет использовать таблицы Iceberg в S3 как *полноценное разделяемое хранилище*, работающее со скоростью локального диска, но обходящееся в 10 раз дешевле.<\/p>\n<p><b>Пример прозрачного монтирования:<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">-- 1. Подключаем готовую Iceberg-таблицу прямо как движок ClickHouse\nCREATE TABLE iceberg_customer\nENGINE = Iceberg('s3:\/\/my-datalake\/warehouse\/customer', 'aws_key', 'aws_secret');\n\n-- 2. Запрашиваем данные. Теперь Trino и ClickHouse читают одни и те же Parquet-файлы!\nSELECT count(*) FROM iceberg_customer WHERE status = 'active';<\/code><\/pre><hr \/>\n<h3>⚠️ Решение частых проблем при транзите данных (Troubleshooting)<\/h3>\n<p><details><br \/>\n<summary><b>1. Управление оперативной памятью (OOM) в DuckDB<\/b><\/summary><br \/>\nПри скриптовой выгрузке гигантских таблиц in-memory движок может исчерпать RAM сервера.<br \/>\n<b>Решение:<\/b> Обязательно ограничивайте ресурсы сразу после<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">duckdb.connect()<\/code><\/pre><p>:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">con.execute(&quot;PRAGMA memory_limit='16GB'&quot;)\ncon.execute(&quot;PRAGMA threads=4&quot;)<\/code><\/pre><p><\/details><\/p>\n<p><details><br \/>\n<summary><b>2. Консолидация сложных типов данных PostgreSQL<\/b><\/summary><br \/>\nЕсли в вашей таблице есть<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">JSONB<\/code><\/pre><p>,<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">UUID<\/code><\/pre><p>или пользовательские массивы, Parquet может упасть с ошибкой соответствия типов.<br \/>\n<b>Решение:<\/b> Вместо<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">COPY pg.table<\/code><\/pre><p>напишите явный SQL-запрос с приведением к строке (<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">::VARCHAR<\/code><\/pre><p>):<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">con.execute(&quot;&quot;&quot;\n    COPY (\n        SELECT id, metadata::VARCHAR AS metadata \n        FROM pg.public.customer\n    )\n    TO 's3:\/\/my-datalake\/raw\/customer.parquet' (FORMAT PARQUET);\n&quot;&quot;&quot;)<\/code><\/pre><p>Внутри Trino эти строки легко парсятся функциями вроде<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">json_extract()<\/code><\/pre><p>.<br \/>\n<\/details><\/p>\n<p><details><br \/>\n<summary><b>3. Защита асинхронных реплик PostgreSQL от разрывов<\/b><\/summary><br \/>\nДлительный процесс<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">SELECT *<\/code><\/pre><p>(или<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">COPY<\/code><\/pre><p>) мешает мастеру применять WAL-логи на реплике (из-за очистки строк VACUUM-ом).<br \/>\n<b>Решение:<\/b> На аналитической реплике (в файле<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">postgresql.conf<\/code><\/pre><p>) обязательно пропишите:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">max_standby_streaming_delay = -1\nmax_standby_archive_delay = -1\nhot_standby_feedback = on<\/code><\/pre><p>Это позволит реплике “ставить на паузу” конфликтующие обновления и не обрывать ваш транзит данных.<br \/>\n<\/details><\/p>\n<hr \/>\n<h3>🎯 План внедрения (Roadmap)<\/h3>\n<ol start=\"1\">\n<li><b>Мгновенный результат (Первые 1-3 дня):<\/b> Используйте проверенный Python-скрипт на <b>DuckDB<\/b> для баз PostgreSQL и классическую функцию <b>`s3()`<\/b> для ClickHouse. Они перенесут исторические таблицы в Parquet на S3 без внесения изменений в инфраструктуру. Trino сразу увидит эти файлы.<\/li>\n<li><b>Системный подход (1-2 недели):<\/b> Разверните <b>OLake<\/b>. Потратив пару часов на конфигурацию манифестов, вы получите автоматический конвейер инкрементальной загрузки, который напрямую питает ваши Iceberg-каталоги.<\/li>\n<li><b>Объединение аналитики (2-4 недели):<\/b> Начните использовать <b>Project Antalya<\/b>, чтобы обогатить озеро горячими данными ClickHouse, избегая дублирования.<\/li>\n<li><b>Окончательная эволюция:<\/b> Когда бизнес-пожар потушен и аналитики получают данные в приемлемые сроки (T lag < 1 часа), вы можете спокойно внедрить **Debezium + Kafka**. Но делать это стоит только для узкого сегмента сверхкритичных таблиц, где аналитика требуется в строгом Real-Time.<\/li>\n<\/ol>\n<h3>Часть 2 – Интеграция PostgreSQL, Trino и Iceberg<\/h3>\n<h2>Эффективный ELT: Интеграция PostgreSQL, Trino и Iceberg (сравнение подходов Table Functions и pg_lake)<\/h2>\n<p>В современных data-архитектурах часто возникает задача переноса реляционных данных в озера данных (Data Lakes). Если ваш стек включает <b>PostgreSQL<\/b>, <b>Trino<\/b> и <b>Iceberg<\/b> (например, с REST-каталогом <b>Lakekeeper<\/b>), возникает архитектурный вопрос: как переносить данные и обращаться к ним максимально эффективно?<\/p>\n<p>В этой статье мы разберем два мощных подхода: использование “нативного” для Trino проталкивания через `system.query()` и применение расширения `pg_lake` на стороне базы данных.<\/p>\n<hr \/>\n<h4>Проблема: Почему Trino иногда “вытягивает” всю таблицу?<\/h4>\n<p>Обычно в Trino мы пишем простой федеративный запрос:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">SELECT * FROM postgres_catalog.public.customer WHERE acctbal &gt; 1000;<\/code><\/pre><p>В идеальном сценарии оптимизатор Trino считывает предикат (`acctbal > 1000`) и транслирует его в SQL-диалект PostgreSQL. Это называется <b>Pushdown<\/b> (проталкивание).<\/p>\n<p>Но на практике аналитические запросы гораздо сложнее. Если запрос содержит специфичную бизнес-логику, нестандартные оконные функции, сложные JOIN-ы или функции обработки строк, которых нет в базовом словаре коннектора Trino, оптимизатор не сможет транслировать этот кусок SQL. В результате Trino принимает решение <b>скачать всю таблицу в память своих воркеров<\/b> и применить фильтрацию уже там.<\/p>\n<p><summary><b>Как работает Dynamic Filtering в Trino и почему он может не сработать (Детали)<\/b><\/summary><\/p>\n<p>Особую роль при JOIN-ах играет механизм динамической фильтрации (Dynamic Filtering). Когда вы джоините большую таблицу из Postgres с маленькой таблицей (например, справочником из Hive\/Iceberg), Trino сначала читает справочник (Build side), извлекает ключи, формирует SQL-фильтр (например, `IN (1, 2, 3)`) и на лету отправляет его в Postgres (Probe side).<\/p>\n<p>Два критичных параметра в конфигурации коннектора управляют этим процессом:<\/p>\n<ul>\n<li>`dynamic-filtering.enabled`: Включает передачу динамических фильтров в JDBC-запросы (по умолчанию `true`).<\/li>\n<li>`dynamic-filtering.wait-timeout`: Максимальное время, которое Trino ждет сбора фильтров из Build-стороны JOIN-а перед тем, как запустить запрос в JDBC. По умолчанию это `20s`.<\/li>\n<\/ul>\n<p><b>В чем кроется опасность?<\/b><br \/>\nЕсли вычисление справочника на стороне Trino занимает больше времени, чем задано в `dynamic-filtering.wait-timeout` (например, 25 секунд против 20), координатор Trino прерывает ожидание. Чтобы не блокировать выполнение, он отправляет в Postgres “голый” запрос: `SELECT * FROM table`.<br \/>\nВместо пары тысяч строк по сети внезапно начинают передаваться миллионы. Если загрузка сети — B, а объем таблицы PostgreSQL — V total, то время выполнения стремится к: T pull = B V total<br \/>\nчто может привести к Out-of-Memory на воркерах Trino и падению кластера.<\/p>\n<hr \/>\n<h4>Решение 1: Полный Pushdown через `system.query` (Для ELT-оркестрации)<\/h4>\n<p>Чтобы гарантировать, что вычисления и фильтры 100% выполнятся на мощностях PostgreSQL, мы можем использовать специальную табличную функцию `system.query()`.<\/p>\n<p>Этот подход разделяет обязанности: <b>PostgreSQL<\/b> занимается фильтрацией и тяжелой математикой локально, а <b>Trino<\/b> просто оркестрирует запись результата в Parquet\/Iceberg.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">-- Создаем таблицу в Iceberg (Lakekeeper) и наполняем её результатами из Postgres\nCREATE TABLE iceberg_catalog.raw_data.customer_metrics WITH (\n    format = 'PARQUET',\n    partitioning = ARRAY['mktsegment']\n) AS \nSELECT\n    *\nFROM\n    TABLE(\n        postgres_catalog.system.query(\n            query =&gt; '\n                -- Этот SQL выполняется СТРОГО внутри PostgreSQL\n                SELECT \n                    custkey, \n                    name, \n                    mktsegment,\n                    acctbal,\n                    array_agg(acctbal) OVER (\n                        PARTITION BY mktsegment \n                        ORDER BY custkey \n                        ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING\n                        EXCLUDE GROUP\n                    ) AS rolling_bals\n                FROM public.customer\n                WHERE acctbal &gt; 1000 \n                  AND created_at &gt;= current_date - interval ''1 month''\n            '\n        )\n    );<\/code><\/pre><p><b>Преимущество:<\/b> Если селективность нашего фильтра S равна 0.05 (остается 5% строк), то объем передаваемых по сети данных составит строго V total \\ times S. Никакие таймауты Trino не заставят Postgres отдать лишние данные.<\/p>\n<hr \/>\n<h4>Решение 2: Использование `pg_lake` (Для концепции Lakehouse в PostgreSQL)<\/h4>\n<p>Если первый метод идеально подходит для использования Trino как движка трансформации, то зачем вообще существует проект `pg_lake`?<\/p>\n<p>`pg_lake` внедряет под капот PostgreSQL движок DuckDB через `pgduck_server`. Это позволяет базе данных <b>самостоятельно подключаться к S3 и читать\/писать формат Iceberg<\/b>, минуя Trino.<\/p>\n<p><summary><b>В чем выгода использования<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">pg_lake<\/code><\/pre><p>прямо в PostgreSQL?<\/b><\/summary><\/p>\n<ol start=\"1\">\n<li><b>Чтение ледяных архивов (Cold Data) без сторонних движков.<\/b><br \/>\nДопустим, вы переносите старые партиции данных из Postgres в Iceberg (S3) для экономии места. С `pg_lake` база Postgres “учится” читать эти архивы. Вы можете написать обычный запрос в вашем любимом клиенте (DBeaver, DataGrip, pgAdmin):<\/li>\n<\/ol>\n<pre class=\"e2-text-code\"><code class=\"\">-- Объединение горячих данных из кучи (heap) PG и холодных данных из Iceberg\nSELECT * FROM public.orders_current\nUNION ALL\nSELECT * FROM iceberg.orders_archive WHERE order_date &lt; '2023-01-01';<\/code><\/pre><ol start=\"2\">\n<li><b>Работа в родном диалекте PostgreSQL.<\/b><br \/>\nЕсли ваши аналитики и приложения жестко завязаны на специфические функции PostgreSQL (например, PostGIS для геоданных или сложные хранимые процедуры PL\/pgSQL), интеграция с `pg_lake` позволяет анализировать гигантские внешние Iceberg-файлы, используя всю мощь экосистемы PG, без необходимости переписывать SQL-код под диалект Trino.<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>Меньше точек отказа.<\/b><br \/>\nДля небольших команд, которым не нужна горизонтальная масштабируемость Trino, установка `pg_lake` позволяет построить Data Lake вообще без развертывания отдельного аналитического кластера. Postgres сам выполняет COPY-команды в S3.<\/li>\n<\/ol>\n<h4>Итог итогов<\/h4>\n<ul>\n<li><b>Используйте `system.query()` в Trino<\/b>, если ваша цель — построить надежный, масштабируемый процесс <b>выгрузки (ELT)<\/b>. Это самый безопасный паттерн: он разгружает сеть платформы данных, защищает от капризов динамической фильтрации и оставляет сервер БД свободным от сторонних плагинов.<\/li>\n<li><b>Используйте `pg_lake`<\/b>, если ваша бизнес-потребность — позволить самому <b>PostgreSQL прозрачно обращаться к Data Lake<\/b>. Это идеальное решение для архивации холодных данных прямо из СУБД или если ваши процессы глубоко интегрированы с инструментами, понимающими только нативный протокол Postgres.<\/li>\n<\/ul>\n",
            "date_published": "2026-04-16T01:33:39+03:00",
            "date_modified": "2026-04-17T00:12:12+03:00",
            "tags": [
                "big data",
                "Data Engineer"
            ],
            "_date_published_rfc2822": "Thu, 16 Apr 2026 01:33:39 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "330",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "329",
            "url": "https:\/\/gavrilov.info\/all\/opalya-i-hranitel-ozera-lakekeeper\/",
            "title": "OPA’ля :) и хранитель озера – Lakekeeper",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-22.01.32.png\" width=\"2130\" height=\"1474\" alt=\"\" \/>\n<\/div>\n<p><a href=\"https:\/\/github.com\/lakekeeper\/lakekeeper\/tree\/main\/authz\/opa-bridge\">https:\/\/github.com\/lakekeeper\/lakekeeper\/tree\/main\/authz\/opa-bridge<\/a><\/p>\n<p>или тут <a href=\"https:\/\/docs.lakekeeper.io\/docs\/nightly\/opa\/\">https:\/\/docs.lakekeeper.io\/docs\/nightly\/opa\/<\/a><\/p>\n<p>Много всего нового появилось у хранителя – роли, уточка и многое другое, статистика запросов<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-22.18.59.png\" width=\"1026\" height=\"692\" alt=\"\" \/>\n<\/div>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-22.05.20.png\" width=\"2134\" height=\"1210\" alt=\"\" \/>\n<\/div>\n",
            "date_published": "2026-04-14T22:06:41+03:00",
            "date_modified": "2026-04-14T22:19:19+03:00",
            "tags": [
                "big data",
                "Data Engineer",
                "Data Governance"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-22.01.32.png",
            "_date_published_rfc2822": "Tue, 14 Apr 2026 22:06:41 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "329",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-22.01.32.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-22.18.59.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-22.05.20.png"
                ]
            }
        },
        {
            "id": "327",
            "url": "https:\/\/gavrilov.info\/all\/arhitektura-client-spooling-kak-bystro-vygruzhat-gigantskie-data\/",
            "title": "Архитектура Client Spooling: Как быстро выгружать гигантские датасеты в Trino и Apache DataFusion",
            "content_html": "<p>Работа с Big Data часто упирается в классическое “узкое горлышко”: кластер может обработать терабайты данных за секунды, но передача результатов (Result Set) обратно на сторону клиента (например, в Jupyter или скрипт) занимает часы. На дворе апрель 2026 года, и современные аналитические движки предлагают эффективные методы обхода этой проблемы — концепцию <b>Spooling<\/b>.<\/p>\n<p>Немного душноты: <a href=\"https:\/\/www.starburst.io\/blog\/trino-spooling-protocol\/\">https:\/\/www.starburst.io\/blog\/trino-spooling-protocol\/<\/a><\/p>\n<p>Архитектура Client Spooling в Trino создавалась с параноидальным акцентом на безопасность, в S3 выкидываются куски сырых, возможно, чувствительных данных.<\/p>\n<p>Когда Trino решает сбросить данные в объектное хранилище, он всегда шифрует их на лету.<br \/>\nДля этого используется механизм S3 SSE-C (Server-Side Encryption with Customer-provided keys). Trino генерирует уникальный случайный AES-ключ для каждого запроса, отправляет его в MinIO вместе с данными, а клиенту (вашему Jupyter) отдает ссылку + этот же ключ для расшифровки.<br \/>\nЕсли мы используем локальный MinIO по адресу <a href=\"http:\/\/minio:9000\">http:\/\/minio:9000<\/a> (без SSL\/TLS), сервер MinIO видит, что ему пытаются передать секретный пароль (SSE-C ключ) по открытому незащищенному HTTP-каналу.<br \/>\nMinIO (как и настоящий AWS S3) строго запрещает это по спецификации. Он возвращает HTTP 400 Bad Request с ошибкой: “Requests specifying Server Side Encryption... must be made over a secure connection”. Поэтому тестировать лучше на реальном s3. И еще<\/p>\n<p>Мгновенное удаление (Сборка мусора)<\/p>\n<p>Главное правило Client Spooling: Trino удаляет файлы сразу же, как только они были прочитаны клиентом.<br \/>\nКак только ваш Python-скрипт или Jupyter получает ссылку на файл, скачивает его и отправляет координатору Trino HTTP-сигнал (ACK), что кусок получен, координатор дает команду немедленно удалить этот объект из S3.<br \/>\nЕсли запрос отменен или упал с ошибкой, Trino тоже моментально зачищает за собой fs.location. Вы просто не успеете их там увидеть.<\/p>\n<p>Данных слишком мало (Thresholds)<\/p>\n<p>Писать 10 строк в S3, генерировать для них Pre-signed URLs и отдавать клиенту — это дольше, чем просто плюнуть эти 10 строк текстом через координатор. Trino использует эвристику: если Result Set маленький, он отдается “инлайн” (внутри JSON-ответа самого координатора), и S3 не задействуется.<\/p>\n<p>В этой статье мы разберем, как передавать результаты запросов через промежуточное S3-хранилище, на примере движков Trino и Apache DataFusion.<\/p>\n<h4>Физика проблемы и математика Spooling<\/h4>\n<p>В классической архитектуре все воркеры кластера отправляют вычисленные строки на главный узел (Coordinator), а тот уже отдает их по одному каналу клиенту.<\/p>\n<p>Если D — это объем результирующей выборки, а B c — пропускная способность сети координатора, то время выгрузки данных клиенту без спулинга равно:<\/p>\n<p>T classic = B \/ Dc<\/p>\n<p>В режиме <b>Spooling<\/b> координатор не гоняет данные через себя. Воркеры напрямую, параллельно пишут куски результата в дешевое объектное хранилище (S3\/MinIO). Клиент получает лишь ссылки на эти файлы и скачивает их напрямую. Если у нас N файлов в S3, доступных для многопоточного скачивания с пропускной способностью клиента B client: T spooling ≈ min(N×B s3,B client)D<\/p>\n<p>Это позволяет ускорить выгрузку в десятки раз, так как $B_{client}$ и распределенный $B_{s3}$ обычно значительно больше ограничений одного координатора.<\/p>\n<hr \/>\n<h4>Подготовка минимальной инфраструктуры<\/h4>\n<p>Для демонстрации двух подходов мы убрали из нашего кластера все тяжелые клиентские среды (Jupyter, Spark) и оставили только “голое” ядро: хранилище S3, REST-каталог и SQL-движок.<\/p>\n<p><summary><b>минимальный<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">docker-compose.yml<\/code><\/pre><p><\/b><\/summary><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">version: '3.8'\n\nservices:\n  minio:\n    image: minio\/minio:latest\n    ports:\n      - &quot;19000:9000&quot;\n      - &quot;19001:9001&quot;\n    environment:\n      MINIO_ROOT_USER: &quot;minio-root-user&quot;\n      MINIO_ROOT_PASSWORD: &quot;minio-root-password&quot;\n    command: server \/data --console-address &quot;:9001&quot;\n\n  minio-setup:\n    image: minio\/mc:latest\n    depends_on:\n      - minio\n    entrypoint: &gt;\n      \/bin\/sh -c &quot;\n      sleep 5;\n      mc alias set myminio http:\/\/minio:9000 minio-root-user minio-root-password;\n      mc mb myminio\/warehouse || true;\n      &quot;\n\n  lakekeeper:\n    image: dalongrong\/lakekeeper:latest\n    ports:\n      - &quot;8181:8181&quot;\n    environment:\n      - S3_ENDPOINT=http:\/\/minio:9000\n      - S3_REGION=us-east-1\n      - S3_ACCESS_KEY_ID=minio-root-user\n      - S3_SECRET_ACCESS_KEY=minio-root-password\n    depends_on:\n      - minio-setup\n\n  trino:\n    image: trinodb\/trino:latest\n    ports:\n      - &quot;8080:8080&quot;<\/code><\/pre><p><summary><b>Шаг 1. Настройка каталога и генерация данных (Trino)<\/b><\/summary><br \/>\n<br><br \/>\nСначала мы генерируем данные в Trino. Запрос<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE CATALOG<\/code><\/pre><p>использует динамическое подключение к Lakekeeper REST API. Скрипт записывает файлы в формате Parquet в MinIO:<\/p>\n<p><b>config.properties<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">protocol.spooling.enabled=true\n# 256-битный ключ в формате base64. Вы можете сгенерировать свой с помощью команды `openssl rand -base64 32`\nprotocol.spooling.shared-secret-key=jxTKysfCBuMZtFqUf8UJDQ1w9ez8rynEJsJqgJf66u0=\n\ncatalog.management=dynamic<\/code><\/pre><p><b>spooling-manager.properties<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">spooling-manager.name=filesystem\n# Включаем чтение\/запись в S3 для Spooling\nfs.s3.enabled=true\n# Путь внутри MinIO (указываем через s3:\/\/)\nfs.location=s3:\/\/warehouse\/client-spooling\/\n\n# Системные настройки S3 (MinIO)\ns3.endpoint=http:\/\/minio:9000\ns3.region=us-east-1\ns3.aws-access-key=minio-root-user\ns3.aws-secret-key=minio-root-password\ns3.path-style-access=true<\/code><\/pre><p>-- 1. Подключение каталога Iceberg<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE CATALOG test_warehouse USING iceberg\nWITH (\n    &quot;iceberg.catalog.type&quot; = 'rest',\n    &quot;iceberg.rest-catalog.uri&quot; = 'http:\/\/lakekeeper:8181\/catalog\/',\n    &quot;iceberg.rest-catalog.warehouse&quot; = '00000000-0000-0000-0000-000000000000\/test_warehouse',\n    &quot;iceberg.rest-catalog.security&quot; = 'OAUTH2',\n    &quot;iceberg.rest-catalog.nested-namespace-enabled&quot; = 'true',\n    &quot;iceberg.rest-catalog.vended-credentials-enabled&quot; = 'true',\n    &quot;fs.native-s3.enabled&quot; = 'true',\n    &quot;s3.region&quot; = 'us-east-1',\n    &quot;s3.path-style-access&quot; = 'true',\n    &quot;s3.endpoint&quot; = 'http:\/\/minio:9000'\n);<\/code><\/pre><p>-- 2. Создание структуры<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE SCHEMA test_warehouse.test_schema;\n\nCREATE TABLE test_warehouse.test_schema.my_table (\n    id BIGINT,\n    data VARCHAR\n) WITH (format = 'PARQUET');<\/code><\/pre><p>-- 3. Запись данных<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">INSERT INTO test_warehouse.test_schema.my_table VALUES (1, 'hello'), (2, 'world');<\/code><\/pre><hr \/>\n<p>Если написать Select – должно быть как-то так<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-12-v-18.25.52.png\" width=\"490\" height=\"284\" alt=\"\" \/>\n<\/div>\n<h4>Аналог Spooling в Apache DataFusion (Через экспорт)<\/h4>\n<p>Trino поддерживает протокол *Client Spooling* “из коробки” — когда Python-клиент запрашивает огромный `SELECT`, Trino сам незаметно пишет куски в S3 и отдает клиенту готовые ссылки.<\/p>\n<p>В <b>Apache DataFusion<\/b> (который часто работает как локальный движок `datafusion-cli` или встраиваемая библиотка поверх S3) применяется более прозрачный паттерн делегирования (Explicit Spooling). Мы вручную инструктируем движок сохранить результаты агрегации в распределенное хранилище, чтобы позже забрать их в удобном формате — например, упаковав их в `JSON` и сжав алгоритмом `ZSTD`.<\/p>\n<h5>1. Подключение к S3 и маппинг исходной таблицы<\/h5>\n<p>Запускаем `datafusion-cli`, передав доступы как переменные среды (для предотвращения ошибок парсинга опций):<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">AWS_ACCESS_KEY_ID=&quot;minio-root-user&quot; \\\nAWS_SECRET_ACCESS_KEY=&quot;minio-root-password&quot; \\\nAWS_ENDPOINT=&quot;http:\/\/localhost:19000&quot; \\\nAWS_REGION=&quot;us-east-1&quot; \\\nAWS_ALLOW_HTTP=&quot;true&quot; \\\ndatafusion-cli<\/code><\/pre><p>Внутри консоли подключаем директорию с Parquet-файлами, сгенерированными Trino:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE EXTERNAL TABLE my_parquet_data \nSTORED AS PARQUET \nLOCATION 's3:\/\/warehouse\/019d81a3-c2d6-7ed2-ab15-070becf62582\/my_table-13e4b91a2b4e47d98f312b1384263880\/data\/';<\/code><\/pre><h5>2. Массовая конвертация и выгрузка (DataFusion COPY)<\/h5>\n<p>Вместо того чтобы тянуть миллионы строк на локальный терминал, мы просим DataFusion выполнить преобразование и записать итог запроса обратно в MinIO.<\/p>\n<p>Мы выбираем построчный JSON с экстремальным сжатием:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">COPY (\n    -- Тут может быть любая сложная агрегация:\n    -- SELECT id, count(data) FROM my_parquet_data GROUP BY id\n    SELECT * FROM my_parquet_data\n) \nTO 's3:\/\/warehouse\/019d81a3-c2d6-7ed2-ab15-070becf62582\/my_table-13e4b91a2b4e47d98f312b1384263880\/json_export\/' \nSTORED AS JSON\nOPTIONS (\n    'format.compression' 'zstd'\n);<\/code><\/pre><p><b>Результат:<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">+-------+\n| count |\n+-------+\n| 2     |\n+-------+\n1 row(s) fetched. \nElapsed 0.270 seconds.<\/code><\/pre><p>За миллисекунды (0.270 sec) DataFusion прочитал партиции, трансформировал бинарные столбцы в текст и сжал его.<\/p>\n<h4>В чем преимущество подхода DataFusion?<\/h4>\n<p>Описанный паттерн выполнения команды `COPY TO` с сохранением `.json.zst` в MinIO полностью воспроизводит механику Spooling:<\/p>\n<ol start=\"1\">\n<li><b>Отсутствие OOM (Out Of Memory):<\/b> Клиент получает только метаданные `count`, а не гигабайты сырых данных в оперативную память.<\/li>\n<li><b>Параллелизм:<\/b> Если исходных файлов много, DataFusion будет писать множество потоков `part-0.json.zst`, `part-1.json.zst` в бакет параллельно.<\/li>\n<li><b>Удаленное потребление:<\/b> Вы можете запустить легкий Python-скрипт (Pandas) на дешевой машине, который просто прочитает эти сжатые легковесные JSON объекты напрямую из MinIO, минуя дорогостоящие вычислительные кластеры.<\/li>\n<\/ol>\n<p>Еще немного про  Fault-Tolerant Execution (FTE), нужно провести важную границу между <b>архитектурой Trino<\/b> (готовый распределенный кластер) и <b>архитектурой DataFusion<\/b> (ядро\/библиотека выполнения запросов).<\/p>\n<p>В самом “голом” ядре DataFusion (которое вы запускаете в `datafusion-cli` или в Jupyter) <b>нет встроенного механизма Task Retries<\/b>, потому что процессы выполняются на одной машине в рамках одного приложения. Если сервер падает — запрос прерывается.<\/p>\n<p>Однако, в экосистеме DataFusion есть механизмы отказоустойчивости, которые делятся на два уровня: <b>локальный (Spilling)<\/b> и <b>распределенный (Apache Ballista \/ Ray)<\/b>.<\/p>\n<hr \/>\n<h4>1. Локальная отказоустойчивость (защита от OOM)<\/h4>\n<p>В Trino частой причиной падения задач является нехватка памяти (Out of Memory). В DataFusion реализован мощный механизм управления памятью.<\/p>\n<p>Если DataFusion понимает, что оперативной памяти для агрегации или JOIN’а не хватает, он не “роняет” задачу, а начинает сбрасывать промежуточные данные на диск (<b>Spill to Disk<\/b>).<\/p>\n<ul>\n<li>Это настраивается через конфигурацию `datafusion.execution.disk_manager`.<\/li>\n<li>Это аналог локального `spill-enabled = true` в Trino. Запрос замедлится, но выполнится до конца, не упав с ошибкой.<\/li>\n<\/ul>\n<h4>2. Распределенная отказоустойчивость (Аналог Trino FTE)<\/h4>\n<p>Trino использует архитектуру <b>Fault-Tolerant Execution (FTE)<\/b>, при которой промежуточные результаты (Shuffle Exchange) пишутся в S3, а упавшие воркеры заменяются, и их задачи (Tasks) перезапускаются координатором.<\/p>\n<p>В мире DataFusion эту задачу решает не само ядро, а <b>распределенные планировщики<\/b>, построенные поверх него:<\/p>\n<h5>А. Apache Ballista (Официальный распределенный DataFusion)<\/h5>\n<p>Ballista — это надстройка над DataFusion, превращающая его в полноценный кластер (с Coordinator и Executors), архитектурно очень похожая на Apache Spark и Trino.<\/p>\n<ul>\n<li><b>Task Retries:<\/b> Если один из Executor’ов теряется из-за сбоя сети или железа, Ballista Coordinator замечает это и <b>переназначает задачу (Task) другому воркеру<\/b>.<\/li>\n<li><b>Shuffle Spilling:<\/b> Промежуточные данные между стадиями (Stages) записываются во временные файлы. Следовательно, если упала только последняя стадия, кластеру не нужно пересчитывать весь запрос с нуля — он прочитает промежуточные Shuffle-файлы и повторит только упавший кусок.<\/li>\n<\/ul>\n<h5>Б. DataFusion on Ray (datafusion-ray)<\/h5>\n<p>Сейчас огромную популярность набирает запуск DataFusion поверх кластера <b>Ray<\/b>.<br \/>\nRay — это супер-устойчивый распределенный фреймворк. Интеграция `datafusion-ray` позволяет разбить SQL-запрос на граф задач прямо в Ray.<\/p>\n<ul>\n<li>За отказоустойчивость, Retry-логику и восстановление упавших узлов (Actor\/Task) здесь отвечает сам Ray, который делает это на уровне индустриального стандарта.<\/li>\n<li>Это максимально близко к концепции отказоустойчивого кластера.<\/li>\n<\/ul>\n<h4>Резюме: Как получить “Trino-like” Fault Tolerance в DataFusion?<\/h4>\n<ol start=\"1\">\n<li>Если вы используете <b>локальный DataFusion<\/b> (в Python или CLI): Отказоустойчивости уровня узлов нет, но есть защита от падений по памяти (Spill to Disk). Если упадет процесс — нужно перезапускать запрос руками.<\/li>\n<li>Если вам нужен настоящий <b>Task Repeat \/ Fault Tolerance<\/b> на сотнях серверов, где падение серверов — норма: вы используете движок DataFusion вместе с кластерным менеджером <b>Apache Ballista<\/b> или <b>Ray<\/b>, которые прозрачно обеспечат перезапуск задач (Retries) и сохранение промежуточных состояний (Shuffle), полностью повторяя логику Trino FTE.<\/li>\n<\/ol>\n<p>UPD: В локальном тестировании есть некоторые особенности. Когда контейнеры внутри имеют свою сеть, то трино посылает в dbeaver ссылки. А есть хост не знает что это за минива или localstack-spooling, то оно отдаст кусок данных, а остальные части просто не доедут. Квери упадет как отмененная, так как клиент получил не все результаты. Короче, надо просто так сделать<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">sudo nano \/etc\/hosts<\/code><\/pre><p>и вставить строку вашего s3 хоста.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">127.0.0.1       localstack-spooling<\/code><\/pre><p>то есть при спулинге клиент должен не только иметь сетевую связанность с s3 но различать dns имена корректно.<\/p>\n<p>Короче сравния строк пройдено, все сошлося :)<\/p>\n<p>со спулингом 2.2 сек<br \/>\nбез спулинга 4.4 сек<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-23.33.03.png\" width=\"1300\" height=\"136\" alt=\"\" \/>\n<\/div>\n<p>Питончик 2.16 сек с чанками<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-23.53.52.png\" width=\"880\" height=\"506\" alt=\"\" \/>\n<\/div>\n<p>в самом трино еще быстрее<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-23.57.40.png\" width=\"1190\" height=\"282\" alt=\"\" \/>\n<\/div>\n<p>все строки на месте: 150тыщъ<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-23.58.37.png\" width=\"614\" height=\"84\" alt=\"\" \/>\n<\/div>\n<p>код !!<\/p>\n<p><html><details><br \/>\nfrom trino.dbapi import connect<br \/>\nimport json<\/p>\n<h2>–<s> Конфигурация –<\/s><\/h2>\n<p>TRINO_HOST = “localhost”<br \/>\nTRINO_PORT = 9999<br \/>\nTRINO_USER = “trino”<br \/>\nTRINO_CATALOG = “test_warehouse”<br \/>\nTRINO_SCHEMA = “test_schema”<br \/>\nOUTPUT_FILE = “output.json”<br \/>\nCHUNK_SIZE = 10000  # Количество строк, обрабатываемых за один раз<\/p>\n<p>def export_to_json():<br \/>\nconn = connect(<br \/>\nhost=TRINO_HOST,<br \/>\nport=TRINO_PORT,<br \/>\nuser=TRINO_USER,<br \/>\ncatalog=TRINO_CATALOG,<br \/>\nschema=TRINO_SCHEMA,<br \/>\n)<br \/>\ncursor = conn.cursor()<\/p>\n<p>try:<\/p>\n<h2>Отключаем Fault-Tolerant Execution<\/h2>\n<p>cursor.execute(“SET SESSION retry_policy = ‘NONE’”)<br \/>\ncursor.execute(“SELECT * FROM my_table2”)<\/p>\n<p>column_names = [desc[0] for desc in cursor.description]<br \/>\nrow_count = 0<\/p>\n<p>with open(OUTPUT_FILE, “w”, encoding=“utf-8”) as f:<\/p>\n<h2>Используем fetchmany для чанков<\/h2>\n<p>while True:<br \/>\nrows = cursor.fetchmany(CHUNK_SIZE)<br \/>\nif not rows:<br \/>\nbreak<br \/>\nfor row in rows:<br \/>\nrow_dict = dict(zip(column_names, row))<br \/>\nf.write(json.dumps(row_dict, ensure_ascii=False, default=str) + “\\n”)<br \/>\nrow_count += len(rows)<br \/>\nprint(f“Processed {row_count} rows...”)<\/p>\n<p>print(f“Successfully exported {row_count} rows to {OUTPUT_FILE}”)<\/p>\n<p>finally:<br \/>\ncursor.close()<br \/>\nconn.close()<\/p>\n<p>if __name__ == “__main__”:<br \/>\nexport_to_json()<\/p>\n<p><\/details><\/html><\/p>\n<p>Вот еще с уточкой и чанками<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-15-v-01.12.49.png\" width=\"1064\" height=\"514\" alt=\"\" \/>\n<\/div>\n<p>код<\/p>\n<p><html><details><br \/>\nimport duckdb<br \/>\nimport json<\/p>\n<p>OUTPUT_FILE = “\/home\/jovyan\/examples\/output_duckdb.json”<br \/>\nCHUNK_SIZE = 10000<\/p>\n<p>conn = duckdb.connect()<\/p>\n<h2>расширения и настройки (как у вас)<\/h2>\n<p>conn.execute(“INSTALL httpfs; LOAD httpfs;”)<br \/>\nconn.execute(“INSTALL iceberg; LOAD iceberg;”)<br \/>\nconn.execute(“SET memory_limit = ‘4GB’;”)<br \/>\nconn.execute(“SET s3_region = ‘us-east-1’;”)<\/p>\n<p>conn.execute(“‘’<br \/>\nCREATE OR REPLACE SECRET minio_secret (<br \/>\nTYPE S3,<br \/>\nKEY_ID ‘minio-root-user’,<br \/>\nSECRET ‘minio-root-password’,<br \/>\nENDPOINT ‘minio:9000’,<br \/>\nUSE_SSL false,<br \/>\nURL_STYLE ‘path’<br \/>\n);<br \/>\n‘‘’)<\/p>\n<p>conn.execute(‘‘’<br \/>\nCREATE OR REPLACE SECRET iceberg_secret (<br \/>\nTYPE ICEBERG,<br \/>\nTOKEN ‘dummy’<br \/>\n);<br \/>\n‘‘’)<\/p>\n<p>conn.execute(‘‘’<br \/>\nATTACH ‘test_warehouse’ AS lakekeeper_db (<br \/>\nTYPE ICEBERG,<br \/>\nENDPOINT ’<a href=\"http:\/\/lakekeeper:8181\/catalog\/',\">http:\/\/lakekeeper:8181\/catalog\/',<\/a><br \/>\nACCESS_DELEGATION_MODE ‘none’,<br \/>\nSECRET iceberg_secret<br \/>\n);<br \/>\n‘‘’)<\/p>\n<h2>Используем cursor и fetchmany для чанков<\/h2>\n<p>cursor = conn.cursor()<br \/>\ncursor.execute(‘SELECT * FROM lakekeeper_db.test_schema.my_table2’)<\/p>\n<h2>Получаем имена колонок<\/h2>\n<p>col_names = [desc[0] for desc in cursor.description]<\/p>\n<p>total_rows = 0<br \/>\nwith open(OUTPUT_FILE, ‘w’, encoding=’utf-8’) as f:<br \/>\nwhile True:<br \/>\nrows = cursor.fetchmany(CHUNK_SIZE)<br \/>\nif not rows:<br \/>\nbreak<br \/>\nfor row in rows:<br \/>\nrow_dict = dict(zip(col_names, row))<br \/>\nf.write(json.dumps(row_dict, ensure_ascii=False, default=str) + ‘\\n’)<br \/>\ntotal_rows += len(rows)<br \/>\nprint(f’Обработано строк: {total_rows}’)<\/p>\n<p>print(f’✅ Загружено и сохранено строк: {total_rows}”)<br \/>\nprint(f“📁 Данные сохранены в {OUTPUT_FILE}”)<br \/>\nconn.close()<\/p>\n<p><\/details><\/html><\/p>\n<p>Можно даже так внутри уточки<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-15-v-01.19.57.png\" width=\"1100\" height=\"204\" alt=\"\" \/>\n<\/div>\n<p><html> <details><br \/>\nimport duckdb<\/p>\n<p>OUTPUT_FILE = “\/home\/jovyan\/examples\/output_duckdb_direct.json”<\/p>\n<p>conn = duckdb.connect()<\/p>\n<h2>Расширения и настройки<\/h2>\n<p>conn.execute(“INSTALL httpfs; LOAD httpfs;”)<br \/>\nconn.execute(“INSTALL iceberg; LOAD iceberg;”)<br \/>\nconn.execute(“SET memory_limit = ‘4GB’;”)<br \/>\nconn.execute(“SET s3_region = ‘us-east-1’;”)<\/p>\n<h2>Секрет для MinIO<\/h2>\n<p>conn.execute(“‘’<br \/>\nCREATE OR REPLACE SECRET minio_secret (<br \/>\nTYPE S3,<br \/>\nKEY_ID ‘minio-root-user’,<br \/>\nSECRET ‘minio-root-password’,<br \/>\nENDPOINT ‘minio:9000’,<br \/>\nUSE_SSL false,<br \/>\nURL_STYLE ‘path’<br \/>\n);<br \/>\n‘‘’)<\/p>\n<h2>Секрет для Iceberg REST<\/h2>\n<p>conn.execute(‘‘’<br \/>\nCREATE OR REPLACE SECRET iceberg_secret (<br \/>\nTYPE ICEBERG,<br \/>\nTOKEN ‘dummy’<br \/>\n);<br \/>\n‘‘’)<\/p>\n<h2>Подключение каталога Lakekeeper<\/h2>\n<p>conn.execute(‘‘’<br \/>\nATTACH ‘test_warehouse’ AS lakekeeper_db (<br \/>\nTYPE ICEBERG,<br \/>\nENDPOINT ’<a href=\"http:\/\/lakekeeper:8181\/catalog\/',\">http:\/\/lakekeeper:8181\/catalog\/',<\/a><br \/>\nACCESS_DELEGATION_MODE ‘none’,<br \/>\nSECRET iceberg_secret<br \/>\n);<br \/>\n‘‘’)<\/p>\n<h2>Экспорт в JSON (массив)<\/h2>\n<p>conn.execute(f’’’<br \/>\nCOPY (<br \/>\nSELECT * FROM lakekeeper_db.test_schema.my_table2<br \/>\n) TO ‘{OUTPUT_FILE}’ (FORMAT JSON);<br \/>\n‘‘’)<\/p>\n<p>print(f’✅ Данные сохранены в {OUTPUT_FILE}’)<br \/>\nconn.close()<br \/>\n<\/details><\/html><\/p>\n<p>К конце концов я использовал<\/p>\n<p>localstack-spooling<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">protocol.spooling.enabled=true\n# 256-битный ключ в формате base64. Вы можете сгенерировать свой с помощью команды `openssl rand -base64 32`\nprotocol.spooling.shared-secret-key=jxTKysfCBuMZtFqUf8UJDQ1w9ez8rynEJsJqgJf66u0=\ncatalog.management=dynamic<\/code><\/pre><p>так<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">spooling-manager.name=filesystem\nfs.s3.enabled=true\nfs.location=s3:\/\/spooling-bucket\/client-spooling\/\n\ns3.endpoint=http:\/\/localstack-spooling:4566\ns3.region=us-east-1\ns3.aws-access-key=test\ns3.aws-secret-key=test\ns3.path-style-access=true<\/code><\/pre><p>и так<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">services:\n\n  trino:\n    build: .\/trino\n    environment:\n      - CATALOG_MANAGEMENT=dynamic\n      - LANCE_ALLOW_HTTP=true\n      - AWS_ALLOW_HTTP=true\n      - AWS_ACCESS_KEY_ID=minio-root-user\n      - AWS_SECRET_ACCESS_KEY=minio-root-password\n      - AWS_REGION=us-east-1\n      - AWS_ENDPOINT_URL=http:\/\/minio:9000\n      - CATALOG_MANAGEMENT=dynamic\n      - JDK_JAVA_OPTIONS=--add-opens=java.base\/java.nio=ALL-UNNAMED --add-opens=java.base\/sun.nio.ch=ALL-UNNAMED --add-opens=java.base\/java.util=ALL-UNNAMED --add-opens=java.base\/java.lang=ALL-UNNAMED\n    healthcheck:\n      test: [&quot;CMD&quot;, &quot;curl&quot;, &quot;-I&quot;, &quot;http:\/\/localhost:8080\/v1\/status&quot;]\n      interval: 2s\n      timeout: 10s\n      retries: 2\n      start_period: 10s\n    ports:\n      - &quot;9999:8080&quot;\n    volumes:\n      - .\/lance5.properties:\/etc\/trino\/catalog\/lance5.properties\n      - .\/lance_rest.properties:\/etc\/trino\/catalog\/lance_rest.properties\n      - .\/lance_ice.properties:\/etc\/trino\/catalog\/lance_ice.properties\n      # --- ДОБАВЬТЕ ЭТУ СТРОКУ ---\n      - .\/spooling-manager.properties:\/etc\/trino\/spooling-manager.properties\n      # (При необходимости пробросьте и config.properties, если он не копируется при build: .\/trino)\n      - .\/config.properties:\/etc\/trino\/config.properties\n      - spooling-data:\/tmp\/spooling\n    networks:\n      - lakekeeper-network\n    depends_on:\n      localstack-setup:    # &lt;--- Trino ждет, пока AWS CLI не создаст бакет!\n        condition: service_completed_successfully\n\n  localstack-spooling:\n    image: localstack\/localstack:3.4.0    # Жестко фиксируем бесплатную рабочую версию!\n    container_name: localstack-spooling\n    ports:\n      - &quot;4566:4566&quot;\n    environment:\n      - SERVICES=s3\n      - AWS_DEFAULT_REGION=us-east-1\n    networks:\n      - lakekeeper-network\n\n  localstack-setup:\n    image: amazon\/aws-cli:latest\n    container_name: localstack-setup\n    depends_on:\n      - localstack-spooling\n    restart: &quot;no&quot;\n    environment:\n      - AWS_ACCESS_KEY_ID=test\n      - AWS_SECRET_ACCESS_KEY=test\n      - AWS_DEFAULT_REGION=us-east-1\n    entrypoint: &gt;\n      \/bin\/sh -c &quot;\n        echo 'Waiting for LocalStack to fully start...';\n        sleep 10;\n        aws --endpoint-url=http:\/\/localstack-spooling:4566 s3 mb s3:\/\/spooling-bucket;\n        echo 'LocalStack bucket created successfully!';\n      &quot;\n    networks:\n      - lakekeeper-network\n      \n  jupyter:\n    image: quay.io\/jupyter\/pyspark-notebook:2024-10-14\n    depends_on:\n      lakekeeper:\n        condition: service_healthy\n      # Исправлено: теперь зависим от рабочего setup сервиса\n      lakekeeper-setup:\n        condition: service_completed_successfully\n      trino:\n        condition: service_healthy\n      # Удалено: starrocks (сервис не описан в compose файле)\n    command: start-notebook.sh --NotebookApp.token=''\n    volumes:\n      - .\/notebooks:\/home\/jovyan\/examples\/\n      - spooling-data:\/tmp\/spooling\n    networks:\n      - lakekeeper-network\n    ports:\n      - &quot;8888:8888&quot;\n\n  # Сервис initialwarehouse УДАЛЕН, так как он дублировал lakekeeper-setup \n  # и ссылался на несуществующие сервисы (bootstrap, createbuckets).\n\n  postgres-lakekeeper:\n    image: postgres:17\n    container_name: postgres-lakekeeper\n    environment:\n      POSTGRES_USER: lakekeeper\n      POSTGRES_PASSWORD: lakekeeper\n      POSTGRES_DB: lakekeeper\n    ports:\n      - &quot;5435:5432&quot;\n    volumes:\n      - lakekeeper-postgres-data:\/var\/lib\/postgresql\/data\n    healthcheck:\n      test: [&quot;CMD-SHELL&quot;, &quot;pg_isready -U lakekeeper -d lakekeeper&quot;]\n      interval: 2s\n      timeout: 10s\n      retries: 5\n    networks:\n      - lakekeeper-network\n\n  minio:\n    image: minio\/minio:latest\n    container_name: minio-lakekeeper\n    environment:\n      MINIO_ROOT_USER: minio-root-user\n      MINIO_ROOT_PASSWORD: minio-root-password\n      # MINIO_DOMAIN: minio\n    command: server \/data --console-address &quot;:9001&quot;\n    ports:\n      - &quot;19000:9000&quot;\n      - &quot;19001:9001&quot;\n    volumes:\n      - lakekeeper-minio-data:\/data\n    healthcheck:\n      test: [&quot;CMD&quot;, &quot;mc&quot;, &quot;ready&quot;, &quot;local&quot;]\n      interval: 2s\n      timeout: 10s\n      retries: 5\n    networks:\n      - lakekeeper-network\n\n  minio-setup:\n    image: minio\/mc:latest\n    container_name: minio-setup\n    depends_on:\n      minio:\n        condition: service_healthy\n    entrypoint: &gt;\n      \/bin\/sh -c &quot;\n        mc alias set myminio http:\/\/minio:9000 minio-root-user minio-root-password &amp;&amp;\n        mc mb myminio\/warehouse --ignore-existing &amp;&amp;\n        echo 'MinIO bucket created'\n      &quot;\n    networks:\n      - lakekeeper-network\n\n  lakekeeper-migrate:\n    image: quay.io\/lakekeeper\/catalog:latest-main\n    container_name: lakekeeper-migrate\n    depends_on:\n      postgres-lakekeeper:\n        condition: service_healthy\n    environment:\n      - LAKEKEEPER__PG_ENCRYPTION_KEY=test-encryption-key-not-secure\n      - LAKEKEEPER__PG_DATABASE_URL_READ=postgresql:\/\/lakekeeper:lakekeeper@postgres-lakekeeper:5432\/lakekeeper\n      - LAKEKEEPER__PG_DATABASE_URL_WRITE=postgresql:\/\/lakekeeper:lakekeeper@postgres-lakekeeper:5432\/lakekeeper\n    restart: &quot;no&quot;\n    command: [&quot;migrate&quot;]\n    networks:\n      - lakekeeper-network\n\n  lakekeeper:\n    image: quay.io\/lakekeeper\/catalog:latest-main\n    container_name: lakekeeper\n    depends_on:\n      lakekeeper-migrate:\n        condition: service_completed_successfully\n      minio-setup:\n        condition: service_completed_successfully\n    environment:\n      - LAKEKEEPER__PG_ENCRYPTION_KEY=test-encryption-key-not-secure\n      - LAKEKEEPER__PG_DATABASE_URL_READ=postgresql:\/\/lakekeeper:lakekeeper@postgres-lakekeeper:5432\/lakekeeper\n      - LAKEKEEPER__PG_DATABASE_URL_WRITE=postgresql:\/\/lakekeeper:lakekeeper@postgres-lakekeeper:5432\/lakekeeper\n      - LAKEKEEPER__AUTHZ_BACKEND=allowall\n      - RUST_LOG=info\n    command: [&quot;serve&quot;]\n    healthcheck:\n      test: [&quot;CMD&quot;, &quot;\/home\/nonroot\/lakekeeper&quot;, &quot;healthcheck&quot;]\n      interval: 2s\n      timeout: 10s\n      retries: 5\n      start_period: 5s\n    ports:\n      - &quot;8282:8181&quot;\n    networks:\n      - lakekeeper-network\n\n  lakekeeper-bootstrap:\n    image: curlimages\/curl\n    container_name: lakekeeper-bootstrap\n    depends_on:\n      lakekeeper:\n        condition: service_healthy\n    restart: &quot;no&quot;\n    command:\n      - -w\n      - &quot;%{http_code}&quot;\n      - &quot;-X&quot;\n      - &quot;POST&quot;\n      - &quot;-v&quot;\n      - &quot;http:\/\/lakekeeper:8181\/management\/v1\/bootstrap&quot;\n      - &quot;-H&quot;\n      - &quot;Content-Type: application\/json&quot;\n      - &quot;--data&quot;\n      - '{&quot;accept-terms-of-use&quot;: true}'\n      - &quot;-o&quot;\n      - &quot;\/dev\/null&quot;\n    networks:\n      - lakekeeper-network\n\n  lakekeeper-setup:\n    image: curlimages\/curl\n    container_name: lakekeeper-setup\n    depends_on:\n      lakekeeper-bootstrap:\n        condition: service_completed_successfully\n    restart: &quot;no&quot;\n    entrypoint: [&quot;\/bin\/sh&quot;, &quot;-c&quot;]\n    command:\n      - |\n        echo &quot;Creating test_warehouse...&quot;\n        curl -sf -X POST &quot;http:\/\/lakekeeper:8181\/management\/v1\/warehouse&quot; \\\n          -H &quot;Content-Type: application\/json&quot; \\\n          -d '{\n            &quot;warehouse-name&quot;: &quot;test_warehouse&quot;,\n            &quot;project-id&quot;: &quot;00000000-0000-0000-0000-000000000000&quot;,\n            &quot;storage-profile&quot;: {\n              &quot;type&quot;: &quot;s3&quot;,\n              &quot;bucket&quot;: &quot;warehouse&quot;,\n              &quot;endpoint&quot;: &quot;http:\/\/minio:9000&quot;,\n              &quot;region&quot;: &quot;us-east-1&quot;,\n              &quot;path-style-access&quot;: true,\n              &quot;flavor&quot;: &quot;minio&quot;,\n              &quot;sts-enabled&quot;: false\n            },\n            &quot;storage-credential&quot;: {\n              &quot;type&quot;: &quot;s3&quot;,\n              &quot;credential-type&quot;: &quot;access-key&quot;,\n              &quot;aws-access-key-id&quot;: &quot;minio-root-user&quot;,\n              &quot;aws-secret-access-key&quot;: &quot;minio-root-password&quot;\n            }\n          }' &amp;&amp; echo &quot;Warehouse created successfully&quot; || echo &quot;Failed to create warehouse&quot;\n    networks:\n      - lakekeeper-network\n\nvolumes:\n  lakekeeper-postgres-data:\n  lakekeeper-minio-data:\n  spooling-data:\n  \nnetworks:\n  lakekeeper-network:\n    driver: bridge<\/code><\/pre>",
            "date_published": "2026-04-12T19:11:05+03:00",
            "date_modified": "2026-04-19T15:39:57+03:00",
            "tags": [
                "big data",
                "Data Engineer",
                "datafusion",
                "Trino"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-12-v-18.25.52.png",
            "_date_published_rfc2822": "Sun, 12 Apr 2026 19:11:05 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "327",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-12-v-18.25.52.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-23.33.03.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-23.53.52.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-23.57.40.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-14-v-23.58.37.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-15-v-01.12.49.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-04-15-v-01.19.57.png"
                ]
            }
        },
        {
            "id": "323",
            "url": "https:\/\/gavrilov.info\/all\/starrocks-arhitektura-praktika-i-mesto-v-sovremennom-data-stack\/",
            "title": "StarRocks: Архитектура, Практика и место в современном Data Stack",
            "content_html": "<p><b>StarRocks<\/b> — это аналитическая MPP-база данных нового поколения.<br \/>\nЕсли коротко, она пытается решить трилемму аналитики: объединить скорость <b>ClickHouse<\/b> (за счет векторизации и C++), гибкость <b>Trino<\/b> (поддержка сложных JOIN-ов) и простоту использования <b>MySQL<\/b> (совместимый протокол).<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/logo_starr.svg\" width=\"54\" height=\"62\" alt=\"\" \/>\n<\/div>\n<p>Это короткое руководство проведет вас от понимания архитектуры до построения простого конвейера загрузки данных (ETL) в домашнем продакшене.<\/p>\n<hr \/>\n<h3>Часть 1. Архитектура: FE и BE<\/h3>\n<p>В отличие от PostgreSQL (монолит) или ClickHouse (где узлы часто одноранговые), StarRocks имеет четкое разделение ролей. Это критически важно для понимания масштабирования и эксплуатации.<\/p>\n<h4>1. FE (Frontend) — “Мозг”<\/h4>\n<p>Написан на Java.<\/p>\n<ul>\n<li><b>Роль:<\/b> Управляющий слой.<\/li>\n<li><b>Функции:<\/b>\n<ul>\n  <li>Принимает подключения клиентов (по протоколу MySQL).<\/li>\n  <li>Хранит метаданные (схемы таблиц, права доступа).<\/li>\n  <li>Парсит SQL и строит план выполнения запроса (Query Plan).<\/li>\n  <li>Управляет транзакциями загрузки данных.<\/li>\n<\/ul>\n<\/li>\n<li><b>Масштабирование:<\/b> Обычно запускают 1 или 3 узла для обеспечения высокой доступности (HA).<\/li>\n<li><b>Важно:<\/b> Клиенты (DBeaver, BI, сurl) подключаются <b>только<\/b> к FE.<\/li>\n<\/ul>\n<h4>2. BE (Backend) — “Мускулы”<\/h4>\n<p>Написан на C++ (использует SIMD-инструкции процессора).<\/p>\n<ul>\n<li><b>Роль:<\/b> Слой хранения и вычислений.<\/li>\n<li><b>Функции:<\/b>\n<ul>\n  <li>Физически хранит данные (в колоночном формате).<\/li>\n  <li>Выполняет “тяжелую” работу: фильтрацию, агрегацию, JOIN-ы.<\/li>\n  <li>Управляет репликацией данных.<\/li>\n<\/ul>\n<\/li>\n<li><b>Масштабирование:<\/b> Можно добавлять узлы линейно. Чем больше BE, тем быстрее выполняются запросы и тем больше данных можно хранить.<\/li>\n<\/ul>\n<blockquote>\n<p><b>В Docker All-in-One:<\/b> Оба компонента упакованы в один контейнер для удобства, но слушают разные порты:<\/p>\n<ul>\n<li>`9030`: FE (SQL интерфейс, сюда идет DBeaver).<\/li>\n<li>`8030`: FE (HTTP API для загрузки Stream Load, сюда идет curl).<\/li>\n<li>`8040`: BE (HTTP API метрик и логов).<\/li>\n<\/ul>\n<\/blockquote>\n<hr \/>\n<h3>Часть 2. Быстрый старт (Docker Compose)<\/h3>\n<p>Мы поднимем стек StarRocks и MinIO (S3-совместимое хранилище), используя bridge-сеть для связности.<\/p>\n<p><b>Файл `docker-compose.yml`<\/b> (Полностью рабочий пример):<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">version: &quot;3.9&quot;\n\nnetworks:\n  starrocks-stack-network:\n    driver: bridge\n\nservices:\n  starrocks:\n    image: starrocks\/allin1-ubuntu:4.0-latest\n    container_name: starrocks\n    hostname: starrocks.local.com\n    platform: &quot;linux\/amd64&quot;\n    restart: unless-stopped\n    ports:\n      - &quot;9030:9030&quot; # MySQL Protocol (SQL клиенты)\n      - &quot;8030:8030&quot; # FE HTTP (Stream Load)\n      - &quot;8040:8040&quot; # BE HTTP (Logs\/Metrics)\n    environment:\n      - TZ=UTC\n    networks:\n      starrocks-stack-network:\n    volumes:\n      # Персистентность данных (чтобы данные не исчезли после рестарта)\n      - ${HOME}\/dv\/starrocks\/be\/storage:\/data\/deploy\/starrocks\/be\/storage\n      - ${HOME}\/dv\/starrocks\/be\/log:\/data\/deploy\/starrocks\/be\/log\n      - ${HOME}\/dv\/starrocks\/fe\/meta:\/data\/deploy\/starrocks\/fe\/meta\n      - ${HOME}\/dv\/starrocks\/fe\/log:\/data\/deploy\/starrocks\/fe\/log\n\n  minio:\n    image: quay.io\/minio\/minio\n    container_name: minio\n    platform: &quot;linux\/amd64&quot;\n    hostname: minio.local.com\n    restart: unless-stopped\n    ports:\n      - &quot;9000:9000&quot; # S3 API\n      - &quot;9001:9001&quot; # Web UI\n    networks:\n      starrocks-stack-network:\n    environment:\n      MINIO_ROOT_USER: root\n      MINIO_ROOT_PASSWORD: rootroot\n    volumes:\n      - ${HOME}\/dv\/minio\/data:\/data\n    command: server \/data --console-address &quot;:9001&quot;<\/code><\/pre><p>Запуск:<br \/>\n`docker-compose up -d`<\/p>\n<hr \/>\n<h3>Часть 3. Моделирование данных (Table Design)<\/h3>\n<p>В StarRocks нельзя просто “создать таблицу”. Нужно выбрать тип ключа (<b>Key Model<\/b>), который определит, как база будет хранить и обновлять данные.<\/p>\n<p>Подключение (DBeaver): `localhost:9030`, User: `root`, Password: (пусто).<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE DATABASE IF NOT EXISTS demo_db;\nUSE demo_db;<\/code><\/pre><h4>1. Primary Key Model (Для изменяемых данных)<\/h4>\n<p>Это “флагманская” возможность StarRocks. Она поддерживает быстрые <b>Upsert<\/b> (вставка новых или обновление старых записей по ID) в реальном времени.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE TABLE IF NOT EXISTS users (\n    user_id INT NOT NULL,\n    username VARCHAR(50),\n    email VARCHAR(100),\n    register_date DATE, \n    city VARCHAR(50)\n)\nPRIMARY KEY (user_id) -- Уникальный ключ\nDISTRIBUTED BY HASH(user_id) -- Распределение данных\nPROPERTIES (\n    &quot;replication_num&quot; = &quot;1&quot; -- Для локального теста ставим 1 реплику\n);<\/code><\/pre><h4>2. Aggregate Key Model (Для витрин данных)<\/h4>\n<p>База автоматически агрегирует данные при вставке. Если вы вставите новую продажу с *существующими* датой и категорией, StarRocks не создаст новую строку, а прибавит суммы к уже существующей строке. Это экономит место и ускоряет `GROUP BY`.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE TABLE IF NOT EXISTS daily_sales (\n    report_date DATE NOT NULL,\n    category VARCHAR(50) NOT NULL,\n    \n    -- Метрики с функцией агрегации:\n    total_amount BIGINT SUM DEFAULT &quot;0&quot;, \n    items_sold INT SUM DEFAULT &quot;0&quot;       \n)\nAGGREGATE KEY (report_date, category)\nDISTRIBUTED BY HASH(report_date) BUCKETS 3\nPROPERTIES (\n    &quot;replication_num&quot; = &quot;1&quot;\n);<\/code><\/pre><hr \/>\n<h3>Часть 4. загрузка данных users (Stream Load)<\/h3>\n<p>Для загрузки данных в продакшене мы используем <b>Service Account<\/b> (Техническую учетную запись). Это стандарт безопасности: мы не используем `root` и не используем токены в конфигах (так как они требуют перезагрузки кластера для смены).<\/p>\n<h4>Шаг 1. Создание сервисного пользователя (SQL)<\/h4>\n<p>Выполнять под `root`:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">-- 1. Создаем пользователя-бота\nCREATE USER IF NOT EXISTS 'etl_loader'@'%' IDENTIFIED BY 'SecretPass123!';\n\n-- 2. Даем права ТОЛЬКО на вставку и чтение в базе demo_db\nGRANT INSERT, SELECT ON demo_db.* TO 'etl_loader'@'%';\n\n-- Права применяются мгновенно.<\/code><\/pre><h4>Шаг 2. Загрузка сложного JSON через CURL<\/h4>\n<p>Stream Load — это самый быстрый способ загрузки (до 100 МБ\/сек на узел). Он поддерживает транзакционность (ACID).<\/p>\n<p><b>Пример файла `users.json`:<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\n  &quot;users&quot;: [\n    {&quot;user_id&quot;: 101, &quot;username&quot;: &quot;alex&quot;, &quot;email&quot;: &quot;a@test.com&quot;, &quot;city&quot;: &quot;NY&quot;},\n    {&quot;user_id&quot;: 102, &quot;username&quot;: &quot;bob&quot;, &quot;email&quot;: &quot;b@test.com&quot;, &quot;city&quot;: &quot;LA&quot;}\n  ]\n}<\/code><\/pre><p><b>Команда загрузки (Terminal):<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">curl --location-trusted \\\n    -u etl_loader:SecretPass123! \\\n    -H &quot;Expect: 100-continue&quot; \\\n    -H &quot;format: json&quot; \\\n    -H &quot;strip_outer_array: true&quot; \\\n    -H &quot;json_root: $.users&quot; \\\n    -H &quot;jsonpaths: [\\&quot;$.user_id\\&quot;, \\&quot;$.username\\&quot;, \\&quot;$.email\\&quot;, \\&quot;$.city\\&quot;]&quot; \\\n    -H &quot;columns: user_id, username, email, city&quot; \\\n    -T &quot;users.json&quot; \\\n    -XPUT http:\/\/localhost:8030\/api\/demo_db\/users\/_stream_load<\/code><\/pre><p>Ответ<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\n    &quot;TxnId&quot;: 9596,\n    &quot;Label&quot;: &quot;a9a37ab6-3678-4c08-95b7-2fd8b6ae973e&quot;,\n    &quot;Db&quot;: &quot;demo_db&quot;,\n    &quot;Table&quot;: &quot;users&quot;,\n    &quot;Status&quot;: &quot;Success&quot;,\n    &quot;Message&quot;: &quot;OK&quot;,\n    &quot;NumberTotalRows&quot;: 2,\n    &quot;NumberLoadedRows&quot;: 2,\n    &quot;NumberFilteredRows&quot;: 0,\n    &quot;NumberUnselectedRows&quot;: 0,\n    &quot;LoadBytes&quot;: 177,\n    &quot;LoadTimeMs&quot;: 153,\n    &quot;BeginTxnTimeMs&quot;: 2,\n    &quot;StreamLoadPlanTimeMs&quot;: 2,\n    &quot;ReadDataTimeMs&quot;: 0,\n    &quot;WriteDataTimeMs&quot;: 26,\n    &quot;CommitAndPublishTimeMs&quot;: 121\n}%<\/code><\/pre><h4>Шаг 3. Загрузка в Aggregate Table (Example)<\/h4>\n<p>Давайте “дольем” данные в таблицу продаж. Агрегация произойдет на лету.<br \/>\nФайл sales.json (простой список):<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">[\n    {&quot;dt&quot;: &quot;2023-11-01&quot;, &quot;cat&quot;: &quot;Electronics&quot;, &quot;amt&quot;: 100, &quot;qty&quot;: 1},\n    {&quot;dt&quot;: &quot;2023-11-01&quot;, &quot;cat&quot;: &quot;Electronics&quot;, &quot;amt&quot;: 50,  &quot;qty&quot;: 1}\n]\n\ncurl --location-trusted \\\n    -u etl_loader:SecretPass123! \\\n    -H &quot;format: json&quot; \\\n    -H &quot;Expect: 100-continue&quot; \\\n    -H &quot;strip_outer_array: true&quot; \\\n    -H &quot;jsonpaths: [\\&quot;$.dt\\&quot;, \\&quot;$.cat\\&quot;, \\&quot;$.amt\\&quot;, \\&quot;$.qty\\&quot;]&quot; \\\n    -H &quot;columns: report_date, category, total_amount, items_sold&quot; \\\n    -T &quot;sales.json&quot; \\\n    -XPUT http:\/\/localhost:8030\/api\/demo_db\/daily_sales\/_stream_load<\/code><\/pre><p>Ответ:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{\n    &quot;TxnId&quot;: 9613,\n    &quot;Label&quot;: &quot;bce0721a-dc2d-4927-be93-e0979a57873d&quot;,\n    &quot;Db&quot;: &quot;demo_db&quot;,\n    &quot;Table&quot;: &quot;daily_sales&quot;,\n    &quot;Status&quot;: &quot;Success&quot;,\n    &quot;Message&quot;: &quot;OK&quot;,\n    &quot;NumberTotalRows&quot;: 2,\n    &quot;NumberLoadedRows&quot;: 2,\n    &quot;NumberFilteredRows&quot;: 0,\n    &quot;NumberUnselectedRows&quot;: 0,\n    &quot;LoadBytes&quot;: 143,\n    &quot;LoadTimeMs&quot;: 52,\n    &quot;BeginTxnTimeMs&quot;: 3,\n    &quot;StreamLoadPlanTimeMs&quot;: 2,\n    &quot;ReadDataTimeMs&quot;: 0,\n    &quot;WriteDataTimeMs&quot;: 24,\n    &quot;CommitAndPublishTimeMs&quot;: 20\n}%<\/code><\/pre><p><b>Разбор заголовков:<\/b><\/p>\n<ul>\n<li>`-u ...`: Авторизация сервисным пользователем.<\/li>\n<li>`Expect: 100-continue`: Критически важно для надежности передачи больших файлов.<\/li>\n<li>`json_root: $.users`: Указывает базе, что данные лежат внутри ключа `users`.<\/li>\n<li>`strip_outer_array: true`: Говорит базе, что внутри лежит массив `[...]` и его нужно “развернуть” в отдельные строки.<\/li>\n<\/ul>\n<hr \/>\n<h3>Часть 5. Совместимость и Trino Dialect<\/h3>\n<p>Одна из сильных сторон StarRocks — способность “притворяться” другими базами данных для облегчения миграции.<\/p>\n<p>Если у вас есть дашборды, написанные на диалекте <b>Trino (Presto)<\/b>, вам не нужно переписывать все SQL-запросы.<\/p>\n<p><b>Пример трансляции функций:<\/b><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">-- Функция Trino, которой нет в StarRocks\nSELECT doy(date '2022-03-06'); \n-- Ошибка: No matching function...\n\n-- Проверяем, как StarRocks переведет этот запрос\nTRANSLATE TRINO select doy(date '2022-03-06');\n-- Результат: SELECT dayofyear('2022-03-06')\n\n-- Включаем режим автоматической трансляции в сессии\nSET sql_dialect = 'trino'; \n\n-- Теперь запрос выполняется корректно, но это не правда. а вот так SELECT dayofyear('2022-03-06') работает. Может бага или у меня версия не та. \nSELECT doy(date '2022-03-06');   \n\n-- Возвращаем нативный режим\nSET sql_dialect = 'starrocks';<\/code><\/pre><p>*(Примечание: Поддержка диалекта постоянно расширяется, но некоторые специфические функции могут требовать ручной замены).*<\/p>\n<hr \/>\n<h3>Итог: Сравнение и Выбор решения ( грубо )<\/h3>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">Характеристика<\/td>\n<td style=\"text-align: center\"><b>StarRocks<\/b><\/td>\n<td style=\"text-align: center\"><b>ClickHouse<\/b><\/td>\n<td style=\"text-align: center\"><b>Trino (Presto)<\/b><\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Основной сценарий<\/b><\/td>\n<td style=\"text-align: center\">OLAP-витрины с JOIN-ами и обновлениями данных<\/td>\n<td style=\"text-align: center\">Сбор логов, событий, метрик (Append-only)<\/td>\n<td style=\"text-align: center\">Федерация данных (запрос к S3 + Postgres + Kafka одновременно)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>JOIN производительность<\/b><\/td>\n<td style=\"text-align: center\">⭐⭐⭐ (Excellent, CBO оптимизатор)<\/td>\n<td style=\"text-align: center\">⭐ (Слабо, требует денормализации)<\/td>\n<td style=\"text-align: center\">⭐⭐⭐ (Excellent)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Обновление (UPDATE)<\/b><\/td>\n<td style=\"text-align: center\">⭐⭐⭐ (Работает как в OLTP, Primary Key)<\/td>\n<td style=\"text-align: center\">⭐ (Тяжелые асинхронные ALTER)<\/td>\n<td style=\"text-align: center\">❌ (Обычно только полная перезапись партиций), iceberg не в счёт :)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Язык Engine<\/b><\/td>\n<td style=\"text-align: center\">C++ (SIMD Vectorized)<\/td>\n<td style=\"text-align: center\">C++ (SIMD Vectorized)<\/td>\n<td style=\"text-align: center\">Java (JVM)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Место в стеке<\/b><\/td>\n<td style=\"text-align: center\"><b>Serving Layer<\/b> (Быстрый доступ для BI)<\/td>\n<td style=\"text-align: center\"><b>Storage Layer<\/b> (Хранение логов)<\/td>\n<td style=\"text-align: center\"><b>Query Engine<\/b> (Ad-hoc запросы к Data Lake)<\/td>\n<\/tr>\n<\/table>\n<p><b>Выбирайте StarRocks, если:<\/b><\/p>\n<ol start=\"1\">\n<li>Вам нужна “витрина” для BI (Superset\/Tableau), где данные должны быть всегда свежими (Real-time updates).<\/li>\n<li>Ваш бизнес требует сложных аналитических запросов с множеством JOIN-ов, и ClickHouse не справляется\/падает по памяти.<\/li>\n<li>Вы хотите использовать стандартный протокол MySQL без установки проприетарных драйверов.<\/li>\n<\/ol>\n",
            "date_published": "2026-03-15T19:06:01+03:00",
            "date_modified": "2026-03-15T19:05:56+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer",
                "SQL"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/logo_starr.svg",
            "_date_published_rfc2822": "Sun, 15 Mar 2026 19:06:01 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "323",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/logo_starr.svg"
                ]
            }
        },
        {
            "id": "320",
            "url": "https:\/\/gavrilov.info\/all\/bitva-titanov-analitiki-realnogo-vremeni-starrocks-protiv-clickh\/",
            "title": "Битва титанов аналитики реального времени: StarRocks против ClickHouse",
            "content_html": "<p>В мире больших данных, где счет идет на петабайты, а задержка измеряется миллисекундами, выбор правильного аналитического движка определяет успех продукта. Сегодня мы разберем восходящую звезду StarRocks и классического гиганта ClickHouse, а также посмотрим, как Netflix удалось укротить свои логи на экстремальных скоростях.<\/p>\n<h3>Часть 1: Обзор технологий и кейс Netflix<\/h3>\n<h4>StarRocks: Субсекундная аналитика нового поколения<\/h4>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image.png-1.jpg\" width=\"2560\" height=\"1078\" alt=\"\" \/>\n<\/div>\n<p><b>StarRocks<\/b> — это высокопроизводительный аналитический движок (MPP database) нового поколения, разработанный для сценариев, где скорость имеет решающее значение. Будучи проектом Linux Foundation, он позиционирует себя как самый быстрый открытый движок запросов для субсекундной аналитики как внутри собственного хранилища, так и поверх архитектуры Data Lakehouse.<\/p>\n<p><b>Ключевые особенности StarRocks:<\/b><\/p>\n<ul>\n<li>Универсальность:** Поддерживает почти любые сценарии — от многомерной OLAP-аналитики и realtime-дэшбордов до ad-hoc запросов аналитиков.<\/li>\n<li>Скорость:** Использует векторизованный движок исполнения, CBO (Cost-Based Optimizer) и пайплайновый параллелизм, что позволяет обгонять конкурентов на сложных запросах с JOIN-ами.<\/li>\n<li>Архитектура:** Native cloud-ready, легко масштабируется горизонтально. Умеет работать “on and off the lakehouse” — то есть быстро читать данные напрямую из S3\/HDFS (форматы Parquet, ORC, Iceberg, Hudi) без необходимости их обязательной загрузки внутрь базы.<\/li>\n<\/ul>\n<hr \/>\n<h4>Кейс Netflix: Как оптимизировать логирование петабайтного масштаба с ClickHouse<\/h4>\n<p>*( адаптация материала из блога ClickHouse)* <a href=\"https:\/\/clickhouse.com\/blog\/netflix-petabyte-scale-logging\">https:\/\/clickhouse.com\/blog\/netflix-petabyte-scale-logging<\/a><\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-03-06-01-15-33.png\" width=\"833\" height=\"380\" alt=\"\" \/>\n<\/div>\n<p>В Netflix масштаб диктует всё. Инженер Дэниел Муино поделился инсайтами о том, как их система логирования справляется с <b>5 петабайтами логов ежедневно<\/b>, обрабатывая в среднем 10.6 миллионов событий в секунду и отвечая на запросы быстрее, чем за секунду.<\/p>\n<p>Для достижения такой производительности потребовалось не просто выбрать правильную базу данных (ClickHouse), но и внедрить три критических инженерных оптимизации.<\/p>\n<h5>Архитектура: Горячее и холодное<\/h5>\n<p>Netflix использует гибридный подход:<\/p>\n<ul>\n<li>Горячий слой (ClickHouse):** Хранит недавние логи, где критична скорость для интерактивной отладки. Данные поступают через Kafka\/Kinesis в ClickHouse практически мгновенно.<\/li>\n<li>Холодный слой (Apache Iceberg):** Обеспечивает экономичное долговременное хранение исторических данных на S3.<\/li>\n<li>Единый API автоматически решает, к какому слою обращаться, скрывая сложность от инженеров.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image.png.jpg\" width=\"2560\" height=\"1403\" alt=\"\" \/>\n<\/div>\n<p>Результат: логи доступны для поиска через 20 секунд после генерации (при SLA в 5 минут), а сложные аналитические запросы выполняются почти мгновенно.<\/p>\n<h5>Три главные оптимизации<\/h5>\n<p><b>1. Ingestion: Свой лексер вместо Regex<\/b><br \/>\nИзначально Netflix использовал регулярные выражения для группировки похожих логов (fingerprinting). На скорости 10 млн событий\/сек это стало узким местом.<\/p>\n<ul>\n<li>Решение:* Команда переписала логику, создав <b>сгенерированный лексер<\/b> с помощью JFlex.<\/li>\n<li>Результат:* Рост пропускной способности в 8-10 раз. Время обработки одного события упало с 216 до 23 микросекунд.<\/li>\n<\/ul>\n<p><b>2. Сериализация: Отказ от JDBC<\/b><br \/>\nСтандартные JDBC-вставки через Java-клиент создавали оверхед на согласование схем. Переход на низкоуровневый формат `RowBinary` помог, но потребление CPU оставалось высоким.<\/p>\n<ul>\n<li>Решение:* Дэниел реверс-инжинирил протокол Go-клиента ClickHouse (который поддерживает нативный формат) и написал <b>собственный энкодер<\/b>. Он генерирует LZ4-сжатые блоки в нативном протоколе ClickHouse.<\/li>\n<li>Результат:* Снижение нагрузки на CPU и памяти при той же пропускной способности.<\/li>\n<\/ul>\n<p><b>3. Запросы: Шардирование карт тегов (Tag Maps)<\/b><br \/>\nИнженеры Netflix активно используют кастомные теги (фильтры по microservice_id, request_id). Изначально они хранились как `Map(String, String)`. В ClickHouse это реализовано как два параллельных массива, что требует линейного сканирования при поиске. При 25 000 уникальных ключей в час запросы тормозили.<\/p>\n<ul>\n<li>Решение:* Шардирование карты. Ключи тегов хешируются в 31 меньшую карту. Запрос сразу “прыгает” в нужный шард вместо перебора всех ключей.<\/li>\n<li>Результат:* Время фильтрующих запросов упало с 3 секунд до 1.3, а сложных проекций — с 3 секунд до <b>700 мс<\/b>.<\/li>\n<\/ul>\n<hr \/>\n<h3>Часть 2: ClickHouse vs StarRocks — Битва за Lakehouse<\/h3>\n<p>Обе системы являются лидерами в мире OLAP (On-Line Analytical Processing), используют MPP-архитектуру и колоночное хранение. Однако их философия и степень готовности к современной концепции <b>Lakehouse<\/b> (аналитика данных непосредственно в озере данных без копирования) различаются.<\/p>\n<h4>1. Архитектурные корни и специализация<\/h4>\n<ul>\n<li>ClickHouse:**\n<ul>\n  <li>ДНК:* Изначально создавался для Яндекс.Метрики. Король <b>единой широкой таблицы<\/b>.<\/li>\n  <li>Сильная сторона:* Непревзойденная скорость записи и чтения на одной таблице. Идеален для логов (как у Netflix), телеметрии, событийных данных.<\/li>\n  <li>Слабая сторона:* JOIN-ы (соединения таблиц). ClickHouse умеет их делать, но исторически это не его конек. Оптимизатор запросов долгое время был рудиментарным, требуя от пользователя ручной оптимизации порядка таблиц.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<ul>\n<li>StarRocks:**\n<ul>\n  <li>ДНК:* Эволюционировал из Apache Doris. Создавался с прицелом на сложные сценарии аналитики.<\/li>\n  <li>Сильная сторона:* <b>CBO (Cost-Based Optimizer)<\/b> уровня Oracle или Teradata. StarRocks блестяще справляется со сложными SQL-запросами, включая многотабличные JOIN-ы “звезда” и “снежинка”.<\/li>\n  <li>Специфика:* Ориентирован на обновление данных в реальном времени (Primary Key table engine) и векторизованную обработку сложных вычислений.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4>2. Степень готовности к Lakehouse (Работа с S3, HDFS, Iceberg)<\/h4>\n<p>Здесь наблюдается главное стратегическое расхождение.<\/p>\n<p><b>StarRocks: Native Lakehouse Engine<\/b><br \/>\nStarRocks позиционирует себя как движок, который может вообще <b>не хранить данные у себя<\/b>, а выступать только быстрым вычислительным слоем поверх S3\/MinIO.<\/p>\n<ul>\n<li>Кэширование:** Имеет продвинутый локальный кэш данных (Local Data Cache), который подтягивает горячие данные из S3 на диски воркеров, обеспечивая скорость, сравнимую с нативным хранением.<\/li>\n<li>Каталоги:** Бесшовная интеграция с Hive Metastore, AWS Glue, Iceberg, Hudi, Delta Lake. Вы просто подключаете каталог и пишете `SELECT` к таблицам в S3 без `CREATE TABLE`.<\/li>\n<li>Вердикт:<b> StarRocks **полностью готов<\/b> к Lakehouse. Это один из лучших выборов для сценария “данные лежат в S3 в формате Parquet\/Iceberg, а нам нужен быстрый SQL поверх них”.<\/li>\n<\/ul>\n<p><b>ClickHouse: Storage First, Lakehouse Second<\/b><br \/>\nClickHouse исторически — это система хранения. Хотя поддержка S3 и Data Lakes активно развивается (особенно в 2024-2025 годах), подход отличается.<\/p>\n<ul>\n<li>Интеграция:** ClickHouse может читать из S3 (`s3()` table function или S3 table engine). Поддерживает Iceberg и Hudi.<\/li>\n<li>Производительность:** Чтение “холодных” данных из S3 в ClickHouse часто медленнее, чем в StarRocks, из-за особенностей реализации сканирования и работы с метаданными внешних форматов.<\/li>\n<li>Кейс Netflix подтверждает:<b> Netflix использует ClickHouse **как горячее хранилище<\/b>, копируя туда данные. А для лекхоуса (Iceberg) они используют отдельные движки (вероятно, Trino или Spark), а ClickHouse выступает именно как акселератор для свежих данных.<\/li>\n<li>Вердикт:<b> ClickHouse движется в сторону Lakehouse (разделение Storage и Compute, S3-backed MergeTree), но его главная суперсила по-прежнему раскрывается, когда данные **импортированы<\/b> в его родной формат.<\/li>\n<\/ul>\n<h4>Пример использования ClickHouse (из статьи выше)<\/h4>\n<p>В примере Netflix мы видим классический паттерн использования ClickHouse, где он силен максимально:<\/p>\n<blockquote>\n<p>*“ClickHouse находится в сердце системы как горячий слой (hot tier). Он хранит недавние логи, где скорость критична... Для исторических данных Netflix использует Apache Iceberg.”*<\/p>\n<\/blockquote>\n<p>Это подтверждает тезис: ClickHouse идеален, когда вы загружаете данные в него (Ingest heavy). StarRocks же часто выигрывает там, где данные уже лежат в озере, и вы не хотите их никуда копировать, либо, когда вам нужны сложные JOIN-ы поверх этих данных.<\/p>\n<hr \/>\n<h4>Итог и рекомендации<\/h4>\n<p>Выбор между StarRocks и ClickHouse больше не стоит в плоскости “кто быстрее сканирует одну колонку”. Обе системы феноменально быстры. Вопрос в архитектуре ваших данных.<\/p>\n<p><b>Рекомендации:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Выбирайте ClickHouse, если:<\/b>\n<ul>\n  <li>Ваша главная задача — работа с логами, метриками, clickstream (как у Netflix).<\/li>\n  <li>У вас плоская структура данных (одна широкая таблица), и JOIN-ы редки.<\/li>\n  <li>Вам нужна максимальная скорость вставки (ingestion) и максимальное сжатие данных на диске.<\/li>\n  <li>У вас есть ресурсы на инженерию: ClickHouse гибок, но, как показал кейс Netflix, требует “прямых рук” для тонкой настройки (кастомные кодеки, шардирование тегов).<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Выбирайте StarRocks, если:<\/b>\n<ul>\n  <li>Вы строите <b>Data Lakehouse<\/b>: данные лежат в S3 (Iceberg\/Parquet), и вы хотите анализировать их без ETL\/копирования.<\/li>\n  <li>У вас сложная модель данных (схема “Звезда” или “Снежинка”) и много JOIN-ов в запросах.<\/li>\n  <li>Вам нужны обновления данных (UPSERT\/DELETE) в реальном времени с использованием Primary Keys.<\/li>\n  <li>Вы хотите упростить поддержку и получить оптимизатор запросов, который многое сделает за вас “из коробки”.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Приложение:<\/p>\n<p>Ниже представлен анализ списка компаний, использующих <b>StarRocks<\/b>. Они разделены по сферам деятельности, а также ранжированы по глубине использования технологии и вкладу в развитие проекта.<\/p>\n<h4>1. Сферы деятельности компаний<\/h4>\n<p>Вот краткое описание того, чем занимается каждая компания из вашего списка:<\/p>\n<p><b>Технологии, Интернет и E-commerce:<\/b><\/p>\n<ul>\n<li>Alibaba:** Крупнейший китайский холдинг электронной коммерции и облачных вычислений.<\/li>\n<li>Shopee:** Ведущая платформа электронной коммерции в Юго-Восточной Азии и Тайване.<\/li>\n<li>Trip.com:** Одно из крупнейших в мире онлайн-турагентств.<\/li>\n<li>Airbnb:** Онлайн-площадка для размещения, поиска и краткосрочной аренды жилья.<\/li>\n<li>Xiaohongshu (RedNote):** Китайская социальная сеть и платформа электронной коммерции (аналог Instagram + Pinterest).<\/li>\n<li>Zepto:** Сервис быстрой доставки продуктов (quick commerce) из Индии.<\/li>\n<li>Naver:** Ведущая южнокорейская интернет-компания (поисковик, карты и др.).<\/li>\n<\/ul>\n<p><b>Социальные сети и Медиа:<\/b><\/p>\n<ul>\n<li>Pinterest:** Фотохостинг, социальная сеть для обмена идеями.<\/li>\n<li>Tencent (Games & LLM):** Технологический гигант, владелец WeChat, крупнейший в мире издатель видеоигр.<\/li>\n<li>iQiyi:** Крупная китайская платформа онлайн-видео (аналог Netflix).<\/li>\n<li>SmartNews:** Агрегатор новостей (популярен в Японии и США).<\/li>\n<\/ul>\n<p><b>Финтех и Криптовалюты:<\/b><\/p>\n<ul>\n<li>Coinbase:** Крупнейшая американская криптовалютная биржа.<\/li>\n<li>Intuit:** Американская компания, разработчик финансового ПО (QuickBooks, TurboTax).<\/li>\n<li>TRM Labs:** Блокчейн-аналитика, порядочность в криптосфере и compliance.<\/li>\n<li>Yuno:** Финтех-оркестратор платежей.<\/li>\n<\/ul>\n<p><b>B2B SaaS и Корпоративное ПО:<\/b><\/p>\n<ul>\n<li>Airtable:** Облачный сервис для работы с базами данных и таблицами (no-code).<\/li>\n<li>Celonis:** Лидер в области Process Mining (анализ бизнес-процессов).<\/li>\n<li>Cisco:** Мировой лидер в области сетевых технологий и кибербезопасности.<\/li>\n<li>Demandbase:** Платформа для ABM-маркетинга (Account-Based Marketing).<\/li>\n<li>Eightfold.ai:** Платформа для управления талантами на базе ИИ.<\/li>\n<li>Freshа:** Платформа для бронирования услуг в сфере красоты и здоровья.<\/li>\n<li>SplitMetrics:** Платформа для A\/B тестирования и оптимизации мобильных приложений.<\/li>\n<li>Verisoul:** Платформа для выявления фейковых пользователей и ботов.<\/li>\n<\/ul>\n<p><b>Транспорт и Логистика:<\/b><\/p>\n<ul>\n<li>Didi:** Китайский агрегатор такси (аналог Uber).<\/li>\n<li>Grab:** Супер-приложение из Юго-Восточной Азии (такси, доставка еды, платежи).<\/li>\n<\/ul>\n<p><b>Игры:<\/b><\/p>\n<ul>\n<li>PlaySimple Games:** Разработчик мобильных словесных игр.<\/li>\n<\/ul>\n<p><b>Сельское хозяйство:<\/b><\/p>\n<ul>\n<li>HerdWatch:** ПО для управления фермерскими хозяйствами.<\/li>\n<\/ul>\n<p><b>Энергетика:<\/b><\/p>\n<ul>\n<li>Haezoom:** Южнокорейская платформа в сфере солнечной энергетики (Energy AI).<\/li>\n<\/ul>\n<p><b>Ритейл (Merchandise):<\/b><\/p>\n<ul>\n<li>Fanatics:** Мировой лидер по продаже лицензионной спортивной атрибутики.<\/li>\n<\/ul>\n<hr \/>\n<h4>2. Ранжирование по степени использования (Use Case Depth)<\/h4>\n<p>Это ранжирование основано на публично доступных кейсах (case studies), объемах данных и критичности систем, переведенных на StarRocks.<\/p>\n<p><b>Уровень 1: Heavy Users \/ Mission Critical (Ключевые внедрения)<\/b><\/p>\n<p>Эти компании заменили устаревшие хранилища данных (Snowflake, ClickHouse, Druid) на StarRocks для критически важных задач с огромными объемами данных.<\/p>\n<ol start=\"1\">\n<li><b>Airbnb:<\/b> Используют StarRocks для метрик реального времени и “умного” ценообразования (Minerva). Огромные объемы данных, строгие требования к задержке.<\/li>\n<li><b>Tencent (Games & LLM):<\/b> Один из самых масштабных пользователей. Унифицировали аналитику (заменив Hive\/Spark\/Druid), что позволило анализировать данные сотен игр в реальном времени.<\/li>\n<li><b>Trip.com:<\/b> Полностью отказались от ClickHouse и частично от Hive в пользу StarRocks для ускорения отчетов. Обрабатывают петабайты данных, высокая конкуренция запросов.<\/li>\n<li><b>Shopee:<\/b> Используют StarRocks для Data Service (API), ускорив запросы в 3 раза по сравнению с Presto. Критически важно для работы их E-commerce платформы.<\/li>\n<li><b>Didi:<\/b> Масштабное использование для логистики в реальном времени и анализа поездок.<\/li>\n<li><b>Fanatics:<\/b> Сократили расходы на 90%, перейдя с Snowflake на связку StarRocks + Iceberg.<\/li>\n<li><b>Coinbase:<\/b> Заменили Snowflake для аналитики, обращенной к клиенту (customer-facing). Требовались быстрые JOIN-ы на терабайтных масштабах, чего не давали другие системы.<\/li>\n<\/ol>\n<p><b>Уровень 2: Strategic Users (Важные продуктовые внедрения)<\/b><\/p>\n<p>Компании, использующие StarRocks для конкретных, высоконагруженных продуктов или функций.<\/p>\n<ol start=\"1\">\n<li><b>Pinterest:<\/b> Используют для аналитики, но акцент сделан на Lakehouse-архитектуре и join-ах больших таблиц.<\/li>\n<li><b>Xiaohongshu (RedNote):<\/b> Аналитика поведения пользователей в реальном времени (user behavior analysis) с высочайшей кардинальностью данных.<\/li>\n<li><b>Fresha:<\/b> Аналитика для партнеров (салонов красоты). Важна скорость отклика дэшбордов для тысяч внешних пользователей.<\/li>\n<li><b>Grab:<\/b> Аналитика для супер-приложения. Замена Druid\/Pinot для более гибких SQL-запросов.<\/li>\n<li><b>Celonis:<\/b> Использование в движке Process Mining, где требуются сложные JOIN-операции, с которыми StarRocks справляется лучше колоночных аналогов.<\/li>\n<\/ol>\n<p><b>Уровень 3: Adopters (Специфические сценарии)<\/b><\/p>\n<p>Компании, использующие StarRocks для внутренних BI-систем, маркетинговой аналитики или замены медленных компонентов.<\/p>\n<ul>\n<li>Airtable, Cisco, Intuit, Zepto, PlaySimple Games:** Вероятнее всего, использование для внутренней ускоренной аналитики и BI-отчетов, где традиционные DWH стали слишком медленными или дорогими.<\/li>\n<\/ul>\n<hr \/>\n<h4>3. Ранжирование по степени влияния на проект (Contribution & Influence)<\/h4>\n<p>StarRocks — это Open Source проект. Влияние оценивается по вкладу в код (Pull Requests), участию в техническом комитете (TSC) и архитектурном развитии.<\/p>\n<p><b>1. Лидеры (Архитекторы и основные контрибьюторы):<\/b><\/p>\n<ul>\n<li>Alibaba и Tencent:** Эти техногиганты не просто используют проект, они предоставляют огромное количество коммитов, тестируют его на экстремальных нагрузках и формируют roadmap развития. Многие фичи для “реального времени” и интеграции с Data Lake пришли благодаря требованиям и коду инженеров этих компаний.<\/li>\n<li>Didi:** Активные контрибьюторы в области стабильности и оптимизации планировщика запросов под высокие нагрузки.<\/li>\n<li>Airbnb:** Их вклад значителен в области интеграции с экосистемой данных (например, улучшения для Apache Iceberg и метрик), так как они строят сложные платформы данных (Minerva).<\/li>\n<\/ul>\n<p><b>2. Инноваторы (Драйверы конкретных фич):<\/b><\/p>\n<ul>\n<li>Trip.com:<b> Сильно повлияли на развитие функций для работы с **Data Lakehouse<\/b> (прямые запросы к Hive\/Iceberg без импорта данных), так как их основной кейс — отказ от миграции данных.<\/li>\n<li>Shopee:<b> Влияют на развитие функционала **Materialized Views<\/b> (материализованных представлений), так как активно используют их для ускорения API.<\/li>\n<li>Pinterest и Coinbase:** Их кейсы (быстрые JOIN-ы на S3) подталкивают развитие кеширования и оптимизатора для “холодных” данных.<\/li>\n<\/ul>\n<p><b>3. Евангелисты (Популяризаторы):<\/b><\/p>\n<ul>\n<li>Celonis, Fanatics, Grab:** Активно выступают на конференциях, пишут технические блоги о миграции с конкурентов (Snowflake, Druid), тем самым привлекая новых пользователей и валидируя технологию на западном рынке.<\/li>\n<\/ul>\n<hr \/>\n<p><b>ClickHouse<\/b> — это колоночная аналитическая СУБД с открытым кодом, позволяющая выполнять аналитические запросы в режиме реального времени на структурированных больших данных. Изначально разработанная в Яндексе для Яндекс.Метрики, она стала мировым стандартом для задач логирования, телеметрии и продуктовой аналитики благодаря феноменальной скорости вставки и сжатия данных.<\/p>\n<h4>1. Сферы деятельности компаний<\/h4>\n<p>Список компаний, использующих ClickHouse, охватывает почти все отрасли, где генерируются “Big Data”.<\/p>\n<p><b>Технологии, Интернет и Облачные сервисы:<\/b><\/p>\n<ul>\n<li>Yandex:** Родительская компания. Поисковик, такси, e-commerce, облачные сервисы.<\/li>\n<li>Cloudflare:** Глобальная сеть доставки контента (CDN) и защита от DDoS.<\/li>\n<li>Uber:** Мировой агрегатор такси и доставки.<\/li>\n<li>eBay:** Один из старейших и крупнейших аукционов и маркетплейсов в мире.<\/li>\n<li>VK (ВКонтакте):** Крупнейшая социальная сеть в СНГ.<\/li>\n<li>GitLab:** Платформа для DevOps и управления жизненным циклом ПО.<\/li>\n<\/ul>\n<p><b>Стриминг, Медиа и Развлечения:<\/b><\/p>\n<ul>\n<li>Spotify:** Глобальный аудио-стриминговый сервис.<\/li>\n<li>Netflix:** Крупнейший в мире онлайн-кинотеатр (стриминг видео).<\/li>\n<li>Twitch:** Видеостриминговый сервис, специализирующийся на компьютерных играх.<\/li>\n<li>Disney+ (Disney Streaming):** Стриминговая платформа медиа-конгломерата Disney.<\/li>\n<\/ul>\n<p><b>Финансы и Финтех:<\/b><\/p>\n<ul>\n<li>Bloomberg:** Поставщик финансовой информации для профессиональных участников рынков.<\/li>\n<li>Deutsche Bank:** Крупнейший банковский концерн Германии.<\/li>\n<li>Revolut:** Британский финтех-стартап и необанк.<\/li>\n<\/ul>\n<p><b>Мониторинг, Observability и SaaS:<\/b><\/p>\n<ul>\n<li>Datadog:** Платформа мониторинга и безопасности для облачных приложений.<\/li>\n<li>Grafana Labs:** Разработчик популярнейшей платформы визуализации данных.<\/li>\n<li>Sentry:** Платформа для отслеживания ошибок в приложениях.<\/li>\n<li>Segment (Twilio):** Платформа клиентских данных (CDP).<\/li>\n<\/ul>\n<p><b>Телеком:<\/b><\/p>\n<ul>\n<li>Comcast:** Крупнейшая телекоммуникационная компания США.<\/li>\n<li>Verizon:** Один из лидеров американского рынка мобильной связи.<\/li>\n<\/ul>\n<p>---<\/p>\n<h4>2. Ранжирование по степени использования (Use Case Depth)<\/h4>\n<p>Это ранжирование отражает масштаб данных, критичность системы для бизнеса и сложность архитектуры.<\/p>\n<p><b>Уровень 1: Heavy Users \/ Hyper-scale (Экстремальные нагрузки)<\/b><\/p>\n<p>Компании, обрабатывающие триллионы строк, где ClickHouse является ядром инфраструктуры.<\/p>\n<ol start=\"1\">\n<li><b>Cloudflare:<\/b> Пожалуй, один из самых впечатляющих кейсов в мире. Используют ClickHouse для аналитики HTTP-трафика и DNS-запросов. Обрабатывают <b>десятки миллионов событий в секунду<\/b> (более 100 млрд строк в день) для предоставления аналитики клиентам в личном кабинете.<\/li>\n<li><b>Yandex (Метрика):<\/b> Исторический “reference implementation”. Крупнейшая система веб-аналитики в Европе, работающая на кластерах из сотен серверов. Именно для этой нагрузки (>1 триллиона строк в базе) ClickHouse и был создан.<\/li>\n<li><b>Uber:<\/b> Используют ClickHouse для своей платформы логирования (более 4 петабайт данных), заменив Elasticsearch в ряде задач ради экономии ресурсов и скорости.<\/li>\n<li><b>Lyft:<\/b> Используют для аналитики поездок и Geo-данных в реальном времени, обрабатывая огромные потоки телеметрии с автомобилей и приложений.<\/li>\n<li><b>Bytedance (TikTok):<\/b> (До миграции части нагрузок на другие системы) Один из крупнейших пользователей в Китае, использовавший ClickHouse для анализа поведения пользователей (User Behavior Analysis) на гигантских масштабах.<\/li>\n<\/ol>\n<p><b>Уровень 2: Strategic Users (Ключевой компонент продукта)<\/b><\/p>\n<p>Компании, которые строят свой основной продукт или критически важные внутренние сервисы на базе ClickHouse.<\/p>\n<ol start=\"1\">\n<li><b>Sentry:<\/b> Вся аналитика ошибок и производительности в их SaaS-продукте построена на ClickHouse. Они хранят миллиарды событий ошибок, позволяя разработчикам мгновенно фильтровать их.<\/li>\n<li><b>GitLab:<\/b> Используют ClickHouse для feature “Observability” внутри своего продукта, предоставляя пользователям аналитику по их CI\/CD пайплайнам.<\/li>\n<li><b>Spotify:<\/b> Используют для внутренней аналитики экспериментов (A\/B тесты) и логов воспроизведения треков.<\/li>\n<li><b>eBay:<\/b> Используют для OLAP-аналитики логов приложений и мониторинга, добиваясь снижения затрат по сравнению с традиционными коммерческими решениями.<\/li>\n<li><b>Segment:<\/b> Платформа позволяет клиентам делать сложные выборки по аудитории, и ClickHouse здесь выступает в роли “движка” для мгновенной сегментации пользователей.<\/li>\n<\/ol>\n<p><b>Уровень 3: Adopters (Специализированные задачи)<\/b><\/p>\n<p>Использование для конкретных департаментов, внутренней бизнес-разведки (BI) или замены старых компонентов.<\/p>\n<ul>\n<li>Deutsche Bank:** Анализ рыночных тиков и высокочастотная финансовая аналитика.<\/li>\n<li>Comcast:** Мониторинг качества видеопотока и сети.<\/li>\n<li>Bloomberg:** Аналитика взаимодействия пользователей с терминалом Bloomberg.<\/li>\n<\/ul>\n<p>---<\/p>\n<h4>3. Ранжирование по степени влияния на проект (Contribution & Influence)<\/h4>\n<p>ClickHouse имеет огромное сообщество. Влияние оценивается не только по использованию, но и по вкладу в кодовую базу (PR), разработке драйверов и организации митапов.<\/p>\n<p><b>1. Создатели и Архитекторы:<\/b><\/p>\n<ul>\n<li>ClickHouse Inc:** После выделения в отдельную компанию в 2021 году, основные разработчики (включая Алексея Миловидова) работают здесь. Именно они определяют roadmap, развивают ClickHouse Cloud и ядро системы.<\/li>\n<li>Yandex:** Исторический создатель. До сих пор вносят огромный вклад, поддерживают свои форки и используют систему на пределе возможностей, что помогает выявлять баги производительности.<\/li>\n<\/ul>\n<p><b>2. Технологические Партнеры и Контрибьюторы:<\/b><\/p>\n<ul>\n<li>Cloudflare:** Внесли огромный вклад в оптимизацию работы с сетью, TLS и безопасность, так как их требования к защищенности и нагрузке экстремальны. Часто пишут глубокие технические статьи о внутренностях ClickHouse.<\/li>\n<li>Altinity:** Компания, оказывающая консалтинг и поддержку ClickHouse. Сделали огромный вклад в экосистему Kubernetes (ClickHouse Operator), драйверы и интеграцию с экосистемой Hadoop\/MySQL.<\/li>\n<li>Contentsquare:** Активно участвуют в оптимизации ядра для специфических аналитических функций (session analysis).<\/li>\n<\/ul>\n<p><b>3. Евангелисты Экосистемы:<\/b><\/p>\n<ul>\n<li>Uber и Lyft:** Публикуют детальные инженерные блоги о том, как переводить логирование с ELK стека на ClickHouse, чем вдохновили сотни других компаний на миграцию.<\/li>\n<li>Grafana Labs:** Разрабатывают и поддерживают официальный плагин ClickHouse для Grafana, делая СУБД доступной для визуализации миллионам пользователей.<\/li>\n<\/ul>\n",
            "date_published": "2026-03-06T01:26:35+03:00",
            "date_modified": "2026-03-13T21:32:49+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/image.png-1.jpg",
            "_date_published_rfc2822": "Fri, 06 Mar 2026 01:26:35 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "320",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/image.png-1.jpg",
                    "https:\/\/gavrilov.info\/pictures\/Screenshot-from-2026-03-06-01-15-33.png",
                    "https:\/\/gavrilov.info\/pictures\/image.png.jpg"
                ]
            }
        },
        {
            "id": "319",
            "url": "https:\/\/gavrilov.info\/all\/r2-sql-glubokoe-pogruzhenie-v-nash-novy-dvizhok-dlya-raspredelen\/",
            "title": "R2 SQL: Глубокое погружение в наш новый движок для распределенных запросов",
            "content_html": "<h4>Введение<\/h4>\n<p>В современном мире объемы данных растут экспоненциально, и хранение петабайтов информации в объектных хранилищах (как Amazon S3 или Cloudflare R2) стало стандартом. Однако просто хранить данные мало — их нужно анализировать. Традиционно для этого требовалось поднимать сложные кластеры (например, Spark или Trino), что долго и дорого.<\/p>\n<p>Компания Cloudflare представила <b>R2 SQL<\/b> — бессерверный (serverless) движок, который позволяет выполнять SQL-запросы прямо к данным, лежащим в объектном хранилище R2, без необходимости управлять инфраструктурой. Эта статья подробно описывает архитектуру этого решения: как они добились высокой скорости, используя формат таблиц Apache Iceberg, умное планирование запросов и свою глобальную сеть.<\/p>\n<p><a href=\"https:\/\/blog.cloudflare.com\/r2-sql-deep-dive\/\">Ссылка на оригинал статьи<\/a> А ранее я уже писал про их анонс тут <a href=\"https:\/\/gavrilov.info\/all\/cloudflare-anonsiruet-platformu-dannyh\/\">https:\/\/gavrilov.info\/all\/cloudflare-anonsiruet-platformu-dannyh\/<\/a><\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-18-v-21.51.06.png\" width=\"1582\" height=\"540\" alt=\"\" \/>\n<\/div>\n<hr \/>\n<h4>R2 SQL: Глубокое погружение в наш новый движок для распределенных запросов<\/h4>\n<p><b>Авторы:<\/b> Yevgen Safronov, Nikita Lapkov, Jérôme Schneider. ( Привет Никита и Евген :)<\/p>\n<p>Как выполнить SQL-запросы над петабайтами данных… без сервера?<br \/>\nУ нас есть ответ: <b>R2 SQL<\/b>, бессерверный движок запросов, который может просеивать огромные наборы данных и возвращать результаты за секунды.<\/p>\n<p>В этом посте подробно описывается архитектура и методы, которые делают это возможным. Мы пройдемся по нашему <b>Планировщику запросов<\/b> (Query Planner), который использует `R2 Data Catalog` для отсечения терабайтов данных еще до чтения первого байта, и объясним, как мы распределяем работу по глобальной сети Cloudflare, используя `Workers` и `R2` для массивного параллельного выполнения.<\/p>\n<h5>От каталога к запросу<\/h5>\n<p>Во время Developer Week 2025 мы запустили `R2 Data Catalog` — управляемый каталог `Apache Iceberg`, встроенный непосредственно в ваш бакет Cloudflare R2. Iceberg — это открытый формат таблиц, который предоставляет критически важные функции баз данных (такие как транзакции и эволюция схемы) для объектного хранилища петабайтного масштаба. Он дает вам надежный каталог ваших данных, но сам по себе не предоставляет способа их запрашивать.<\/p>\n<p>До сих пор чтение вашего каталога `R2 Data Catalog` требовало настройки отдельного сервиса, такого как `Apache Spark` или Trino. Эксплуатация этих движков в большом масштабе непроста: вам нужно создавать кластеры, управлять использованием ресурсов и отвечать за их доступность — ничто из этого не способствует главной цели: получению ценности из ваших данных.<\/p>\n<p>`R2 SQL` полностью устраняет этот этап. Это бессерверный движок запросов, который выполняет SQL-запросы на чтение (retrieval) к вашим таблицам Iceberg прямо там, где живут ваши данные.<\/p>\n<p><b>поясненИИе: Что такое Apache Iceberg?<\/b><\/summary><\/p>\n<p>Представьте, что у вас есть огромная куча файлов (CSV, Parquet, JSON) в облачном хранилище. Это “озеро данных”. Проблема в том, что если вы начнете менять один файл, пока кто-то другой его читает, все сломается. Трудно понять, какая версия данных актуальна.<\/p>\n<p><b>Apache Iceberg<\/b> — это слой управления поверх этих файлов. Он работает как библиотекарь: он не хранит сами книги (данные), но ведет идеальный учет (метаданные). Он точно знает: “Таблица ‘Пользователи’ сейчас состоит из вот этих 100 файлов”.<br \/>\nЭто позволяет делать с обычными файлами в облаке то, что раньше умели только дорогие базы данных:<\/p>\n<ol start=\"1\">\n<li><b>ACID-транзакции:<\/b> Гарантия того, что данные не запишутся “наполовину”.<\/li>\n<li><b>Time Travel:<\/b> Возможность сделать запрос “Как выглядела таблица вчера в 14:00?”.<\/li>\n<li><b>Ecosystem:<\/b> Единый стандарт, который понимают разные инструменты аналитики.<\/li>\n<\/ol>\n<h5>Проектирование движка запросов для петабайтов<\/h5>\n<p>Объектное хранилище фундаментально отличается от хранилища традиционной базы данных. База данных структурирована по своей природе; `R2 `— это океан объектов, где одна логическая таблица может состоять из миллионов отдельных файлов, больших и маленьких, и новые поступают каждую секунду.<\/p>\n<p>Apache Iceberg предоставляет мощный слой логической организации поверх этой реальности. Он работает, управляя состоянием таблицы как неизменяемой серией мгновенных снимков (snapshots), создавая надежное, структурированное представление таблицы путем манипулирования “легкими” файлами метаданных вместо перезаписи самих файлов данных.<\/p>\n<p>Однако эта логическая структура не меняет физической проблемы, лежащей в основе: эффективный движок запросов всё равно должен найти конкретные данные, необходимые ему, в этой огромной коллекции файлов. Это требует преодоления двух основных технических барьеров:<\/p>\n<ol start=\"1\">\n<li><b>Проблема ввода-вывода (I\/O problem):<\/b> Главная проблема эффективности запросов — минимизация объема данных, считываемых из хранилища. Подход “в лоб” с чтением каждого объекта просто нежизнеспособен. Основная цель — читать только те данные, которые абсолютно необходимы.<\/li>\n<li><b>Проблема вычислений (Compute problem):<\/b> Объем данных, которые *действительно* нужно прочитать, все равно может быть огромным. Нам нужен способ выделить запросу, который может быть массивным, необходимое количество вычислительной мощности всего на несколько секунд, а затем мгновенно снизить его до нуля, чтобы избежать лишних трат.<\/li>\n<\/ol>\n<p>Наша архитектура для `R2 SQL` разработана для решения этих двух проблем с помощью двухэтапного подхода: <b>Планировщик запросов<\/b> (Query Planner), который использует метаданные для интеллектуального отсечения (pruning) пространства поиска, и система <b>Выполнения запросов<\/b> (Query Execution), которая распределяет работу по глобальной сети Cloudflare для параллельной обработки данных.<\/p>\n<h5>Планировщик запросов (Query Planner)<\/h5>\n<p>Самый эффективный способ обработки данных — не читать их вовсе. Это ключевая стратегия планировщика `R2 SQL`. Вместо исчерпывающего сканирования каждого файла планировщик использует структуру метаданных, предоставляемую каталогом `R2 Data Catalog`, чтобы “подрезать” пространство поиска, то есть избежать чтения огромных массивов данных, не относящихся к запросу.<\/p>\n<p>Это расследование “сверху вниз”, где планировщик перемещается по иерархии слоев метаданных Iceberg, используя статистику (<b>stats<\/b>) на каждом уровне для построения быстрого плана, точно указывающего, какие диапазоны байтов должен прочитать движок.<\/p>\n<h6>Что мы подразумеваем под “статистикой”?<\/h6>\n<p>Когда мы говорим, что планировщик использует “статы”, мы имеем в виду сводные метаданные, которые Iceberg хранит о содержимом файлов данных. Эта статистика создает грубую карту данных, позволяя планировщику принимать решения о том, какие файлы читать, а какие игнорировать, даже не открывая их.<\/p>\n<p>Есть два основных уровня статистики, которые планировщик использует для отсечения (pruning):<\/p>\n<ol start=\"1\">\n<li><b>Статистика уровня раздела (Partition-level stats):<\/b> Хранится в списке манифестов (manifest list) Iceberg. Эти статы описывают диапазон значений разделов для всех данных в определенном файле манифеста Iceberg. Для раздела по `day(event_timestamp)` это будут самый ранний и самый поздний дни, присутствующие в файлах, отслеживаемых этим манифестом.<\/li>\n<li><b>Статистика уровня столбца (Column-level stats):<\/b> Хранится в файлах манифестов. Это более детальная статистика о каждом отдельном файле данных. Файлы данных в `R2 Data Catalog` отформатированы с использованием `Apache Parquet`. Для каждого столбца файла Parquet манифест хранит ключевую информацию, такую как:\n<ul>\n  <li>Минимальное и максимальное значения. Если запрос запрашивает `http_status = 500`, а статистика файла показывает, что в столбце `http_status` минимум 200 и максимум 404, этот файл можно пропустить целиком.<\/li>\n  <li>Количество null-значений. Это позволяет планировщику пропускать файлы, когда запрос ищет конкретно non-null значения (например, `WHERE error_code IS NOT NULL`), а метаданные файла сообщают, что все значения для `error_code` являются null.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<h6>Отсечение пространства поиска (Pruning)<\/h6>\n<p>Процесс отсечения — это расследование “сверху вниз”, которое происходит в три основных этапа:<\/p>\n<ol start=\"1\">\n<li><b>Метаданные таблицы и текущий снимок (snapshot):<\/b>  <br \/>\nПланировщик начинает с запроса к каталогу о местоположении текущих метаданных таблицы. Это JSON-файл, содержащий текущую схему таблицы, спецификации разделов и журнал всех исторических снимков. Затем планировщик выбирает последний снимок для работы.<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Список манифестов и отсечение разделов:<\/b>  <br \/>\nТекущий снимок указывает на единый *список манифестов* (manifest list) Iceberg. Планировщик читает этот файл и использует статистику уровня разделов для каждой записи, чтобы выполнить первый, самый мощный шаг отсечения, отбрасывая любые манифесты, чьи диапазоны значений разделов не удовлетворяют запросу. Например, для таблицы, партиционированной по дням, планировщик может отбросить манифесты за ненужные даты.<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>Манифесты и отсечение на уровне файлов:<\/b>  <br \/>\nДля оставшихся манифестов планировщик читает каждый из них, чтобы получить список фактических файлов данных Parquet. Эти файлы манифестов содержат более детальную статистику уровня столбцов. Это позволяет выполнить второй шаг отсечения, отбрасывая целые файлы данных, которые не могут содержать строки, соответствующие фильтрам запроса.<\/li>\n<\/ol>\n<ol start=\"4\">\n<li><b>Отсечение групп строк (Row-group pruning) внутри файла:<\/b>  <br \/>\nНаконец, для конкретных файлов данных, которые всё еще являются кандидатами, Планировщик использует статистику, хранящуюся внутри *футеров* (footers) файлов Parquet, чтобы пропускать целые группы строк (row groups).<\/li>\n<\/ol>\n<p>Результатом этого многослойного отсечения является точный список файлов Parquet и групп строк внутри этих файлов. Они становятся рабочими единицами (work units), которые отправляются в систему Выполнения запросов.<\/p>\n<p><b>поясненИИе: Формат Parquet и Row Groups<\/b><\/p>\n<p><b>Apache Parquet<\/b> — это колоночный формат хранения данных. В отличие от CSV, где данные хранятся строка за строкой, в Parquet данные хранятся столбец за столбцом. Это идеально для аналитики (когда вам нужно посчитать среднее по одной колонке, не читая остальные 50).<\/p>\n<p>Внутри себя файл Parquet делится на <b>Row Groups<\/b> (группы строк). Представьте файл на 1 миллион строк. Он может быть разбит на 10 групп по 100,000 строк. У каждой группы есть свой мини-заголовок со статистикой (min\/max значения).<\/p>\n<p><b>Пример:<\/b> Вы ищете `id = 950,000`.<br \/>\nДвижок читает футер файла и видит:<\/p>\n<ul>\n<li>Row Group 1: id 1-100,000 -> Пропускаем.<\/li>\n<li>...<\/li>\n<li>Row Group 10: id 900,001-1,000,000 -> <b>Читаем только эту часть файла<\/b>.<\/li>\n<\/ul>\n<p>Это называется “I\/O skipping” и экономит огромное количество времени и денег на трафике.<\/p>\n<h5>Конвейер планирования (The Planning pipeline)<\/h5>\n<p>В `R2 SQL` описанное выше многослойное отсечение не является монолитным процессом. Для таблицы с миллионами файлов метаданные могут быть слишком большими, чтобы обработать их полностью до начала реальной работы. Ожидание полного плана внесет значительную задержку (latency).<\/p>\n<p>Вместо этого `R2 SQL` рассматривает планирование и выполнение как единый конкурентный конвейер (pipeline). Работа планировщика — производить поток рабочих единиц (work units), которые исполнитель (executor) потребляет, как только они становятся доступны.<\/p>\n<h6>Начало выполнения как можно раньше<\/h6>\n<p>С этого момента запрос обрабатывается в потоковом режиме. По мере того как Планировщик читает файлы манифестов (и, следовательно, файлы данных, на которые они указывают) и отсекает их, он немедленно отправляет любые подходящие файлы данных\/группы строк как рабочие единицы в очередь выполнения.<\/p>\n<p>Такая конвейерная структура гарантирует, что вычислительные узлы могут начать дорогую работу по вводу-выводу данных практически мгновенно, задолго до того, как планировщик закончит свое полное расследование.<\/p>\n<p>На вершине этой модели конвейера планировщик добавляет критически важную оптимизацию: <b>преднамеренное упорядочивание<\/b> (deliberate ordering). Файлы манифестов не стримятся в случайной последовательности. Вместо этого планировщик обрабатывает их в порядке, соответствующем условию `ORDER BY` вашего запроса, руководствуясь статистикой метаданных. Это гарантирует, что данные, которые с наибольшей вероятностью содержат желаемые результаты, обрабатываются первыми.<\/p>\n<h6>Ранняя остановка: как закончить, не читая всё<\/h6>\n<p>Благодаря тому, что Планировщик передает рабочие единицы в порядке, соответствующем `ORDER BY`, система выполнения сначала обрабатывает данные, которые с наибольшей вероятностью попадут в итоговый набор результатов.<\/p>\n<p>Например, для запроса типа `... ORDER BY timestamp DESC LIMIT 5`: по мере того как движок выполнения обрабатывает рабочие единицы и отправляет результаты обратно, планировщик одновременно делает две вещи:<\/p>\n<ol start=\"1\">\n<li>Поддерживает ограниченную “кучу” (heap) из лучших 5 результатов, увиденных на данный момент.<\/li>\n<li>Следит за “ватерлинией” (high-water mark) самого потока. Благодаря метаданным он всегда знает абсолютно самый поздний `timestamp` любого файла данных, который *еще не был* обработан.<\/li>\n<\/ol>\n<p>В момент, когда самая старая временная метка в нашей “Топ-5 куче” оказывается новее, чем “ватерлиния” оставшегося потока (максимально возможная дата в еще не прочитанных файлах), <b>весь запрос может быть остановлен<\/b>.<\/p>\n<p>В этот момент мы можем доказать, что ни одна оставшаяся рабочая единица не может содержать результат, который попал бы в топ-5. Конвейер останавливается, и пользователю возвращается полный, корректный результат, часто после чтения лишь крошечной доли потенциально подходящих данных.<\/p>\n<hr \/>\n<h5>Выполнение запросов (Query Execution)<\/h5>\n<p>Планировщик передает работу кусочками, называемыми <b>Row Groups<\/b>. Сервер, который получает запрос пользователя, берет на себя роль <b>координатора запроса<\/b>. Он распределяет работу между <b>воркерами<\/b> (query workers) и агрегирует результаты.<\/p>\n<p>Сеть Cloudflare огромна. Координатор связывается с внутренним API Cloudflare, чтобы убедиться, что для выполнения выбираются только здоровые серверы. Соединения между координатором и воркерами проходят через `Cloudflare Argo Smart Routing` для обеспечения быстрой и надежной связи.<\/p>\n<p>Серверы, получающие задачи от координатора, становятся воркерами. Они служат точкой горизонтального масштабирования в `R2 SQL`. При большем количестве воркеров `R2 SQL` может обрабатывать запросы быстрее, распределяя работу между множеством серверов. Это особенно актуально для запросов, охватывающих большие объемы файлов.<\/p>\n<h6>Внутреннее устройство: Apache DataFusion<\/h6>\n<p>Внутри каждый воркер использует `Apache DataFusion` для выполнения SQL-запросов к группам строк. `DataFusion` — это аналитический движок запросов с открытым исходным кодом, написанный на <b>Rust<\/b>.<\/p>\n<p>Разделы (partitions) в `DataFusion` идеально ложатся на модель данных `R2 SQL`, поскольку каждая группа строк (row group) может рассматриваться как независимый раздел. Благодаря этому каждая группа строк обрабатывается параллельно.<br \/>\nПоскольку группы строк обычно содержат как минимум 1000 строк, `R2 SQL` выигрывает от <b>векторизованного выполнения<\/b>. Каждый поток DataFusion может выполнять SQL-запрос сразу на множестве строк за один проход, амортизируя накладные расходы на интерпретацию запроса.<\/p>\n<h6>Поддержка Parquet и Arrow<\/h6>\n<p>`DataFusion` имеет первоклассную поддержку Parquet. Используя ranged reads (чтение диапазонов) в R2, он способен считывать только части файлов Parquet, содержащие запрошенные столбцы, пропуская остальные.<\/p>\n<p>Оптимизатор `DataFusion` также позволяет нам “проталкивать” фильтры (push down filters) на самые низкие уровни плана запроса. Другими словами, мы можем применять фильтры прямо в момент чтения значений из файлов Parquet.<\/p>\n<p>Когда воркер заканчивает вычисления, он возвращает результаты координатору через протокол <b>gRPC<\/b>. `R2 SQL` использует `Apache Arrow` для внутреннего представления результатов. Это формат в оперативной памяти (in-memory), который эффективно представляет массивы структурированных данных. Arrow также определяет формат сериализации `Arrow IPC`, который идеально подходит для передачи данных между процессами по сети.<\/p>\n<p><b>поясненИИе: Векторизация и Apache Arrow<\/b><\/summary><br \/>\n<b>Векторизованное выполнение (Vectorized execution):<\/b> Традиционные базы данных обрабатывали одну строку за раз (Row-at-a-time). Это медленно, потому что процессор постоянно переключается. Векторизация означает обработку данных “пачками” (например, сложить сразу 1000 чисел из колонки А с 1000 чисел из колонки Б). Это использует современные возможности CPU (SIMD инструкции) и работает в разы быстрее.<\/p>\n<p><b>Apache Arrow:<\/b> Это стандарт того, как хранить эти “пачки” данных в оперативной памяти, чтобы процессору было максимально удобно их читать.<br \/>\nГлавный плюс Arrow: <b>Zero-copy<\/b>. Если один инструмент (DataFusion) передает данные другому (по сети координатору), и оба понимают Arrow, им не нужно тратить время на перекодирование (сериализацию\/десериализацию) данных. Они просто “передают указатель” или копируют сырые байты как есть.<\/p>\n<h5>Будущие планы<\/h5>\n<p>Хотя `R2 SQL` и так хорош в фильтрации, мы планируем быстро добавлять новые возможности:<\/p>\n<ul>\n<li>Поддержка сложных агрегаций (GROUP BY) в распределенном и масштабируемом виде.<\/li>\n<li>Инструменты для визуализации выполнения запросов (explain analyze), чтобы помочь разработчикам улучшать производительность.<\/li>\n<li>Поддержка многих конфигурационных опций Apache Iceberg.<\/li>\n<li>Возможность запрашивать каталоги прямо из панели управления Cloudflare (Dashboard).<\/li>\n<\/ul>\n<p>Мы также исследуем различные виды индексов, чтобы сделать запросы еще быстрее, и планируем добавить полнотекстовый поиск, геопространственные запросы и многое другое.<\/p>\n<h5>Попробуйте сейчас!<\/h5>\n<p>Это ранние дни для `R2 SQL`, но он уже доступен в открытой бете! Переходите к нашему руководству по началу работы, чтобы создать сквозной конвейер данных. Мы ждем вашей обратной связи в нашем Discord для разработчиков.<\/p>\n<p>***<\/p>\n<h4>Итог и СоображенИИя<\/h4>\n<p><b>Итог:<\/b> Cloudflare выпустила мощный инструмент, который превращает их объектное хранилище (R2) в полноценную аналитическую базу данных. Используя открытые стандарты (Iceberg, Parquet, Arrow, DataFusion) и свою глобальную сеть периферийных вычислений (Edge), они решили главную проблему Big Data — необходимость платить за простой серверов. Здесь вы платите только за время выполнения конкретного SQL-запроса.<\/p>\n<p><b>СоображенИИя:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Коммодитизация аналитики:<\/b> Cloudflare делает с Big Data то же, что ранее сделала с CDN и защитой от DDoS — делает сложные энтерпрайз-технологии доступными “по кнопке”. Использование открытого стека (Rust + Arrow + DataFusion) — это сейчас золотой стандарт построения современных СУБД (по этому пути идут такие гиганты как InfluxDB 3.0, LanceDB и др.). Cloudflare не изобретает велосипед, а собирает очень быструю ракету из лучших деталей.<\/li>\n<li><b>Убийца Snowflake\/Databricks для “бедных”?<\/b> Для огромных корпораций Snowflake и Databricks останутся стандартом из-за богатого функционала. Но для стартапов и среднего бизнеса, у которых данные лежат в R2 (чтобы не платить за egress трафик AWS), появление R2 SQL делает переезд на сторонние аналитические платформы бессмысленным. Зачем гонять данные туда-сюда, если можно выполнить SQL прямо “на месте”?<\/li>\n<li><b>Синергия с ИИ:<\/b> Упоминание планов на “индексы” и “геопространственные запросы” намекает на векторный поиск в будущем. Если Cloudflare добавит возможность делать векторный поиск по данным в R2 так же нативно, это станет киллер-фичей для всех, кто строит RAG (Retrieval-Augmented Generation) приложения на базе LLM. Хранишь документы в R2 -> R2 SQL ищет контекст -> Workers AI генерируют ответ. Весь цикл внутри одной экосистемы с минимальными задержками.<\/li>\n<\/ol>\n<p>Еще можно почитать про <a href=\"https:\/\/vegafusion.io\">https:\/\/vegafusion.io<\/a> и про формат <a href=\"https:\/\/lance.org\">https:\/\/lance.org<\/a> – он как раз и добавит векторочков.<\/p>\n",
            "date_published": "2026-02-18T21:56:56+03:00",
            "date_modified": "2026-02-18T21:57:57+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer",
                "Platform",
                "Serverless"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-18-v-21.51.06.png",
            "_date_published_rfc2822": "Wed, 18 Feb 2026 21:56:56 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "319",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-18-v-21.51.06.png"
                ]
            }
        },
        {
            "id": "317",
            "url": "https:\/\/gavrilov.info\/all\/data-stack-2-0-zakat-lambda-arhitektury-i-voshod-fluss-s-lance\/",
            "title": "Data Stack 2.0: Закат Lambda-архитектуры и восход Fluss с Lance",
            "content_html": "<h2>Data Stack 2.0: Закат Lambda-архитектуры и восход Fluss с Lance<\/h2>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-13-v-01.58.40.png\" width=\"1796\" height=\"340\" alt=\"\" \/>\n<\/div>\n<p>В мире инфраструктуры данных происходит “тектонический сдвиг”, описанный в отчетах <a href=\"https:\/\/a16z.com\/emerging-architectures-for-modern-data-infrastructure\/\">a16z.com<\/a>. Индустрия отходит от сложной Lambda-архитектуры (где batch и streaming живут отдельно) к унифицированным решениям, которые называют <b>Streamhouse<\/b>.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-13-v-01.59.13.png\" width=\"1476\" height=\"412\" alt=\"\" \/>\n<\/div>\n<p>Два ключевых игрока, меняющих правила игры в этом переходе:<\/p>\n<ol start=\"1\">\n<li><b>Apache Fluss<\/b> — управляемое хранилище для потоковой обработки (Streaming Storage).<\/li>\n<li><b>Lance<\/b> — формат данных нового поколения для AI и Data Lake.<\/li>\n<\/ol>\n<h3>1. Проблема: Почему одной Kafka больше недостаточно?<\/h3>\n<p>Долгое время Apache Kafka была стандартом де-факто для передачи данных. Однако, как отмечают эксперты Ververica в статье <a href=\"https:\/\/www.ververica.com\/blog\/a-world-without-kafka\">Мир без Kafka<\/a>, Kafka была спроектирована как *распределенный лог*, а не как база данных.<\/p>\n<p>Перевод есть тут, у меня: <a href=\"https:\/\/gavrilov.info\/all\/mir-bez-kafka-pochemu-kafka-ne-podhodit-dlya-analitiki-realnogo\/\">https:\/\/gavrilov.info\/all\/mir-bez-kafka-pochemu-kafka-ne-podhodit-dlya-analitiki-realnogo\/<\/a><\/p>\n<p><b>Фундаментальные ограничения брокеров сообщений (Kafka\/Pulsar) для аналитики:<\/b><\/p>\n<ul>\n<li><b>Слабая работа с обновлениями (Updates):<\/b> Kafka — это `append-only` система. Реализация `UPDATE` или `DELETE` требует использования *Compact Topics*, что не дает гарантий мгновенной консистентности и сложно в эксплуатации.<\/li>\n<li><b>Медленное чтение истории:<\/b> Чтобы найти запись годичной давности, вам часто нужно прочитать весь лог последовательно (Scan). Сложность операции — $O(N)$.<\/li>\n<li><b>Row-based природа:<\/b> Данные хранятся строками (Message bytes). Для аналитики (OLAP), где нам нужен средний чек по столбцу `price`, системе приходится распаковывать и читать *все* поля сообщения, что неэффективно.<\/li>\n<\/ul>\n<h3>2. Apache Fluss: Недостающее звено для Flink<\/h3>\n<p><a href=\"https:\/\/fluss.apache.org\">Apache Fluss<\/a> создан, чтобы решить проблему “разделения” между потоком и таблицей. Это нативное хранилище для Apache Flink, которое поддерживает <a href=\"https:\/\/www.ververica.com\/blog\/introducing-fluss\">концепцию Fluss<\/a>.<\/p>\n<h4>Архитектурные прорывы:<\/h4>\n<ol start=\"1\">\n<li><b>Гибридная модель чтения (Stream-Table Duality):<\/b> Fluss позволяет читать данные и как бесконечный поток (Log), и как изменяемую таблицу с первичными ключами (Primary Key Table). Это делает реализацию <b>CDC (Change Data Capture)<\/b> тривиальной: обновления перезаписывают старые значения по ключу.<\/li>\n<li><b>Колоночная проекция (Columnar Projection):<\/b> В отличие от Kafka, Fluss может отдавать аналитическому движку (Flink) только нужные колонки. Это снижает нагрузку на сеть (`I\/O`) в разы.<\/li>\n<li><b>Real-Time Lookups:<\/b> Fluss поддерживает точечные запросы (Point Lookup) по первичному ключу с задержкой порядка миллисекунд.  <br \/>\n$$Latency_{Fluss} \\ll Latency_{Kafka Scan}$$  <br \/>\nЭто позволяет использовать его как *Serverless State* для приложений, избавляясь от необходимости ставить рядом Redis или RocksDB.<\/li>\n<li><b>Tiered Storage в Data Lake:<\/b> Fluss работает в паре с <b>Apache Paimon<\/b> (ранее Flink Table Store). Горячие данные живут в Fluss (на быстрых дисках\/RAM), а по мере устаревания автоматически конвертируются в формат Lakehouse (Paimon\/Parquet\/ ну или Iceberg) и уходят в S3.<\/li>\n<\/ol>\n<h3>3. Lance: Новый стандарт для AI в Data Lake<\/h3>\n<p>Если Fluss отвечает за доставку и горячее состояние, то <b>Lance<\/b> меняет подход к хранению холодных данных для задач машинного обучения (ML).<\/p>\n<p>Традиционный формат <b>Parquet<\/b> великолепен для аналитики (сканирование больших диапазонов), но ужасен для AI, где требуется <b>случайный доступ (Random Access)<\/b> для формирования батчей обучения.<\/p>\n<p><a href=\"https:\/\/lance.org\">Lance<\/a> решает эти проблемы:<\/p>\n<ul>\n<li>Случайный доступ:** Lance позволяет извлекать строки по индексу в ~100 раз быстрее Parquet.<\/li>\n<li>Векторный поиск:** Это формат со встроенным векторным индексом (IVF-PQ). Вы можете хранить эмбеддинги прямо в файлах на S3 и выполнять поиск ближайших соседей (ANN) без отдельной VectorDB (вроде Pinecone или Milvus).<\/li>\n<li>Zero-Copy версионирование:** Эффективное управление версиями датасетов без дублирования данных.<\/li>\n<\/ul>\n<h3>4. Сборка пазла: Как это работает вместе<\/h3>\n<p>Современный <b>Streamhouse<\/b> (см. <a href=\"https:\/\/bigdataschool.ru\/blog\/news\/flink\/fluss-for-flink\/\">примеры архитектуры]<\/a><\/p>\n<p>выглядит как-то так:<\/p>\n<p>Схема потока данных (Workflow):<\/p>\n<ol start=\"1\">\n<li><b>Ingestion:<\/b>  <br \/>\nПриложения (на Go, Java, Python) пишут данные.<\/li>\n\n<ul>\n  <li>Важно:* Поскольку Fluss совместим с протоколом Kafka, можно использовать существующие <b>Kafka-клиенты<\/b> в Go-сервисах для записи в Fluss, не дожидаясь нативных библиотек. Но это пока только теория. Сходу я не нашел примеров быстро, но можно использовать GO и Arrow Flight SQL.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Streaming Storage (Fluss):<\/b>  <br \/>\nFluss принимает данные, индексирует первичные ключи и хранит “горячее” окно (например, 24 часа).<\/li>\n\n<ul>\n  <li>Flink* выполняет `JOIN` и агрегации прямо поверх Fluss, используя `Lookup Join` (обогащение данных без сохранения большого стейта внутри Flink).<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>Archiving & AI (Paimon\/Lance):<\/b>  <br \/>\nИсторические данные сбрасываются в S3.<\/li>\n\n<ul>\n  <li>Для классической BI-аналитики используется формат <b>Apache Paimon<\/b> или Iceberg.<\/li>\n  <li>Для ML-задач данные конвертируются или хранятся в <b>Lance<\/b>.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"4\">\n<li><b>Unified Analytics (Trino):<\/b>  <br \/>\nДвижок <a href=\"https:\/\/lance.org\/integrations\/trino\/config\/\">Trino<\/a> позволяет делать SQL-запросы ко всем слоям одновременно. Аналитик пишет один `SELECT`, а Trino забирает свежие данные из Fluss, а исторические — из S3 (Lance\/Parquet\/iceberg).<\/li>\n<\/ol>\n<h4>Пример интеграции (концептуальный)<\/h4>\n<p>Поскольку прямого клиента Go для Fluss нет, использование в микросервисах чаще всего выглядит как работа через Kafka-протокол или HTTP-прокси, а основная логика ложится на Flink (Java\/Python\/ или еще чего):<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">\/\/ Flink SQL example: Создание таблицы, управляемой Fluss\nCREATE TABLE user_behavior (\n    user_id BIGINT,\n    item_id BIGINT,\n    action STRING,\n    ts TIMESTAMP(3),\n    PRIMARY KEY (user_id) NOT ENFORCED\n) WITH (\n    'connector' = 'fluss',\n    'bootstrap.servers' = '...:9092', \/\/ Fluss совместим с Kafka-адресацией\n    'table.log.consistency' = 'eventual' \/\/ Оптимизация под высокую пропускную способность\n);<\/code><\/pre><p>Надо пробовать и тестировать... все таки еще инкубационный и это только теория.<\/p>\n<h3>5. Выводы и рекомендации<\/h3>\n<ol start=\"1\">\n<li><b>Не используйте Kafka как базу данных.<\/b> Если вашей архитектуре требуются частые обновления (`UPSERT`) и точечные запросы (`Lookup`), <a href=\"https:\/\/fluss.apache.org\">Apache Fluss<\/a> — это более подходящий инструмент в экосистеме Flink.<\/li>\n<li><b>Lance для AI.<\/b> Если вы строите RAG (Retrieval-Augmented Generation) или RecSys, рассмотрите формат Lance вместо связки “Parquet + внешняя VectorDB”. Это упростит инфраструктуру.<\/li>\n<li><b>Следите за совместимостью.<\/b> Интеграции Lance с Trino и Fluss с не-JVM языками (например, Go, Rust или еще чего) находятся в активной разработке. Используйте проверенные пути (Kafka Protocol для Ingestion, DataFusion\/Java\/Python для Querying).<\/li>\n<\/ol>\n<h4>Полезные ресурсы для изучения:<\/h4>\n<ul>\n<li><a href=\"https:\/\/a16z.com\/emerging-architectures-for-modern-data-infrastructure\/\">Emerging Architectures for Modern Data Infrastructure (a16z<\/a>)<\/li>\n<li><a href=\"https:\/\/www.ververica.com\/blog\/introducing-fluss\">Introducing Fluss (Ververica<\/a>)<\/li>\n<li><a href=\"https:\/\/bigdataschool.ru\/blog\/news\/flink\/fluss-for-flink\/\">Fluss for Flink (BigDataSchool<\/a>)<\/li>\n<li><a href=\"https:\/\/github.com\/lance-format\/lance-trino\/issues\/29#issuecomment-3893178604\">Lance & Trino Integration Issue (GitHub<\/a><\/li>\n<\/ul>\n",
            "date_published": "2026-02-13T01:59:35+03:00",
            "date_modified": "2026-02-15T13:51:52+03:00",
            "tags": [
                "AI",
                "big data",
                "Data",
                "Data Engineer",
                "MLOps"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-13-v-01.58.40.png",
            "_date_published_rfc2822": "Fri, 13 Feb 2026 01:59:35 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "317",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-13-v-01.58.40.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-13-v-01.59.13.png"
                ]
            }
        },
        {
            "id": "318",
            "url": "https:\/\/gavrilov.info\/all\/mir-bez-kafka-pochemu-kafka-ne-podhodit-dlya-analitiki-realnogo\/",
            "title": "Мир без Kafka: Почему Kafka не подходит для аналитики реального времени, что идет на смену)",
            "content_html": "<p>Статья описывает переход от традиционных систем обмена сообщениями, таких как Apache Kafka, к специализированным решениям для потоковой аналитики, таким как <b>Apache Fluss<\/b>.<\/p>\n<p>Основные тезисы:<\/p>\n<ol start=\"1\">\n<li><b>Проблема Kafka:<\/b> Kafka — это система хранения на основе *записей* (record-based), не имеющая нативной поддержки схем и аналитических возможностей. Это приводит к избыточному чтению данных и перегрузке сети при аналитических запросах, когда нужны только конкретные колонки, а не всё сообщение целиком.<\/li>\n<li><b>Эволюция требований:<\/b> Рынок перешел от простого перемещения данных (ingestion) к сложной аналитике реального времени и AI, что требует более эффективного хранения и доступа к данным.<\/li>\n<li><b>Решение (Apache Fluss):<\/b>\n<ul>\n  <li>Табличная структура:** Данные хранятся как таблицы (Log Tables для логов и PK Tables для изменяемых данных), что обеспечивает строгую типизацию.<\/li>\n  <li>Колоночное хранение:** Использование формата Apache Arrow позволяет читать только нужные колонки (projection pushdown) и эффективнее сжимать данные, что снижает нагрузку на диск и сеть.<\/li>\n  <li>Интеграция с Lakehouse:** Fluss нативно поддерживает многоуровневое хранение (горячие данные в Fluss, теплые\/холодные в S3\/Iceberg\/Paimon) без лишнего копирования, обеспечивая прозрачный доступ к историческим и оперативным данным.<\/li>\n<\/ul>\n<\/li>\n<li><b>Вывод:<\/b> Fluss в связке с Flink предлагает более дешевую, быструю и удобную архитектуру для современной аналитики реального времени, устраняя недостатки Kafka в этой области.<\/li>\n<\/ol>\n<p><b>Ссылка на оригинал:<\/b><br \/>\n<a href=\"https:\/\/www.ververica.com\/blog\/a-world-without-kafka\">Why Kafka Falls Short for Real-Time Analytics (and What Comes Next<\/a><\/p>\n<p>У Apache Kafka был замечательный период: она обеспечивала работу событийно-ориентированных архитектур более десяти лет. Но ландшафт изменился, обнажив явные <b>ограничения Kafka для аналитики в реальном времени<\/b> по мере того, как сценарии использования современной <b>потоковой аналитики<\/b> и принятия решений становятся всё более требовательными. Kafka все чаще пытаются заставить выполнять функции в <b>архитектуре аналитики реального времени<\/b>, для поддержки которых она никогда не проектировалась. Чтобы решить сегодняшние проблемы конвейеров потоковой передачи данных и аналитические требования, необходимы новые возможности. Пришло время для «новичка на районе».<\/p>\n<p>Во время перехода от пакетной обработки к <b>потоковой передаче данных в реальном времени<\/b> значительное внимание и импульс получил проект с открытым исходным кодом, разработанный внутри LinkedIn: <b>Apache Kafka<\/b>. Цель состояла в том, чтобы упростить перемещение данных из точки А в точку Б масштабируемым и устойчивым способом, используя модель издатель\/подписчик. Kafka позволила компаниям создавать ранние <b>конвейеры потоковой передачи данных<\/b> и открыть новый класс событийно-ориентированных сценариев использования. Постоянно растущая экосистема коннекторов и интеграций ускорила внедрение и утвердила Kafka в качестве предпочтительного <b>слоя потокового хранения<\/b>. Однако, по мере того как <b>архитектуры аналитики реального времени<\/b> эволюционировали за пределы простого приема данных (ingestion), ограничения Kafka для аналитических нагрузок становились всё более очевидными.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-233.png\" width=\"1200\" height=\"674\" alt=\"\" \/>\n<\/div>\n<p>С архитектурной точки зрения Kafka — это не аналитический движок. Это устойчивая и масштабируемая <b>система хранения на основе записей (record-based storage system)<\/b> для свежих данных в реальном времени — часто называемая «горячим слоем». Следовательно, аналитические нагрузки должны выполняться за пределами кластера Kafka, постоянно перемещая данные между системами хранения и обработки, что увеличивает сетевой трафик и накладные операционные расходы. Кроме того, Kafka нативно не обеспечивает соблюдение схем для данных, публикуемых в топиках.<\/p>\n<p>Хотя эта гибкость была приемлема для ранних сценариев использования потоковой передачи, современные <b>платформы аналитики реального времени<\/b> требуют схем для обеспечения согласованности, управления и качества данных. В качестве компенсации появились реестры схем (Schema Registries) для обеспечения контрактов между издателями и подписчиками, добавляя сложности аналитическим архитектурам на основе Kafka.<\/p>\n<p>И последнее, но не менее важное (и, возможно, самый важный аспект): Kafka — это система хранения на основе записей. Это хорошо подходит для использования в качестве очереди сообщений, например, для приема данных в реальном времени или событийно-ориентированных архитектур, но имеет значительные ограничения при решении текущих и будущих задач проектов реального времени. Движки обработки, такие как Spark и Flink, должны потреблять все данные топика, даже если требуется только часть данных события (столбцы). Результатом является ненужный сетевой трафик, снижение производительности обработки и чрезмерные требования к хранилищу.<\/p>\n<p>Компоненты потокового хранения на основе записей по-прежнему будут занимать свое место в архитектуре данных. Такие решения, как Kafka и Pulsar, хорошо подходят для случаев, требующих чтения полных записей. Архитектурные паттерны, основанные на микросервисах, могут использовать вышеуказанные решения для обмена данными, отделяя функции от транспортировки сообщений для повышения производительности, надежности и масштабируемости. Чтение полных записей также полезно для конвейеров приема данных (ingestion pipelines), в которых данные будут храниться в системах долгосрочного хранения, таких как объектное хранилище (Object Storage), для исторических и архивных целей. Узкие места и ограничения возникают, когда они используются для аналитических нагрузок, требующих возможностей, выходящих за рамки простого слоя транспорта данных.<\/p>\n<h3>Эволюция потоковых данных<\/h3>\n<p>Сегодняшний разговор движим единственным аспектом: Эволюция. Другими словами, новые потребности требуют новых подходов к управлению данными. Kafka удовлетворила первоначальные потребности в потоковой передаче данных. В этой первой волне в основном доминировали конвейеры приема данных в реальном времени и дискретная (SEP, Simple Event Processing) аналитика. По сути, способность перемещать данные из точки А в точку Б и, в некоторых случаях, выполнять простую подготовку и обработку данных между ними. Kafka, в сочетании со Spark Streaming или специальными коннекторами, справлялась с этими ранними сценариями использования.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-234.png\" width=\"1200\" height=\"674\" alt=\"\" \/>\n<\/div>\n<p>Перенесемся вперед: вторая волна привнесла сложность в потоковый конвейер. Помимо дискретной подготовки данных, сценарии использования на этом этапе требовали расширенных аналитических функций, таких как агрегация, обогащение и сложная обработка событий (CEP). Микро-батчинг (micro-batching) оказался недостаточным. Требуется новый архитектурный подход, основанный на колоночном хранении с эффективным проталкиванием проекций (projection pushdown) и прозрачным многоуровневым хранением данных (data tiering), в сочетании с движками обработки с задержкой менее секунды. `Apache Fluss` и `Apache Flink` могут выполнить это обещание и вместе составляют будущее и третью волну по шкале зрелости.<\/p>\n<p>Каждая техническая статья сегодня упоминает AI\/ML. Эта эволюция «третьей волны» позволяет компаниям создавать AI-конвейеры реального времени, которые внедряют передовые аналитические методы (такие как Generative AI) в потоковые данные. Это увеличивает потребность в современных системах хранения данных в реальном времени с расширенными функциями, которые распределяют данные как по быстрым потоковым, так и по историческим слоям, обеспечивая интегрированный, унифицированный доступ к бизнес-данным.<\/p>\n<h3>Новичок на районе<\/h3>\n<p>`Apache Fluss` — это современная система хранения потоковых данных в реальном времени для аналитики. Она консолидирует многолетний опыт и уроки, извлеченные из предшественников, отвечая текущим и будущим потребностям организаций. Fluss родился в эпоху, когда для питания моделей машинного обучения требуется больше данных, Лейкхаусы (Lakehouses) являются частью корпоративной экосистемы, а облачная инфраструктура является предпочтительной стратегией для компаний.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-15-v-13.48.31.png\" width=\"696\" height=\"382\" alt=\"\" \/>\n<\/div>\n<p>Но хранение данных — это лишь часть архитектурной головоломки. `Apache Flink` предоставляет возможности и устойчивость для обработки огромных объемов данных в реальном времени с задержкой менее секунды, обеспечивая скорость, необходимую для будущих потоковых приложений. Не ограничиваясь Flink, дополнительные движки обработки и библиотеки разрабатывают интеграции с Fluss, тем самым укрепляя экосистему.<\/p>\n<p>Ниже приведены основные функции современной аналитики реального времени.<\/p>\n<h4>Поток как таблица (Stream as Table)<\/h4>\n<p>Fluss хранит данные как схематизированные таблицы. Этот подход подходит для большинства сценариев использования в реальном времени, включая те, которые опираются как на структурированные, так и на полуструктурированные данные. Структурируя потоковые данные, компании могут улучшить управление, повысить качество данных и гарантировать, что издатели и потребители используют общий язык. Fluss определяет два типа таблиц:<\/p>\n<ul>\n<li>Log Tables (Лог-таблицы)** работают только на добавление (append-only), аналогично топикам Kafka. Такие сценарии использования, как мониторинг логов, кликстримы (clickstreams), показания датчиков, журналы транзакций и другие, являются хорошими примерами данных только для добавления. События неизменяемы и не должны изменяться или обновляться.<\/li>\n<li>Primary Key (PK) Tables (Таблицы с первичным ключом)** — это изменяемые таблицы, определенные ключом. Записи сначала вставляются, а затем обновляются или удаляются с течением времени в соответствии с журналом изменений (changelog), который они представляют. Таблица PK хранит последние изменения всей таблицы, обеспечивая паттерн доступа «поиск записи» (record lookup). Сценарии использования журнала изменений, такие как балансы счетов, корзина покупок и управление запасами, могут извлечь выгоду из этого подхода. Kafka не может выполнять такое поведение, требуя внешних баз данных типа «ключ-значение» или NoSQL для отслеживания текущего статуса записи, что приводит к сложным и трудным в обслуживании решениям.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-235.png\" width=\"1200\" height=\"556\" alt=\"\" \/>\n<\/div>\n<p>Вкратце, PK Tables обеспечивают уникальность записей на основе первичного ключа, операций `INSERT`, `UPDATE` и `DELETE`, а также предоставляют широкие возможности изменения записей. С другой стороны, Log Tables работают только на добавление; обновления записей не требуются.<\/p>\n<h4>Колоночное хранение (Columnar Storage)<\/h4>\n<p>То, как Fluss хранит данные на диске, возможно, является наиболее фундаментальным архитектурным сдвигом по сравнению с другими решениями. В отличие от Kafka, Fluss использует формат `Apache Arrow` для хранения данных в колоночном формате, что дает следующие преимущества:<\/p>\n<ul>\n<li>Улучшенное использование хранилища**, так как хранение данных в колоночном формате требует меньше дискового пространства. Степень сжатия зависит от множества характеристик данных, но первоначальные тесты показывают многообещающее улучшение в 5 раз при использовании Apache Arrow в качестве базового формата хранения. Меньше хранилища = меньше затрат. Kafka предоставляет лишь несколько вариантов сжатия данных, которые не сравнимы с теми, что доступны в Apache Arrow «из коробки».<\/li>\n<li>Эффективные запросы с использованием обрезки столбцов (column pruning).** В общем случае запрашивается или доступно менее половины атрибутов данного бизнес-события, т.е. только те имена столбцов, которые вы добавляете в ваше выражение `SELECT FROM`. Проталкивание проекции (projection pushdown) — это метод, который удаляет ненужные атрибуты (также известный как column pruning) при извлечении данных из системы хранения. Kafka работает по принципу «все или ничего» из-за своего формата хранения на основе записей.<\/li>\n<li>И колоночное сжатие, и проталкивание проекции улучшат сетевой трафик — перемещение меньшего количества данных приведет к тому, что сетевые администраторы станут счастливее. С Kafka компании постоянно сталкиваются с перегрузкой сети и потенциально высокими расходами на исходящий трафик (egress costs).<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-236.png\" width=\"1200\" height=\"674\" alt=\"\" \/>\n<\/div>\n<h4>Унификация с Lakehouse<\/h4>\n<p>Kafka была создана в эпоху Data Lake (Озер данных). С самого начала проектирования Fluss создавался для Lakehouse. Это создает большую разницу. Компании поняли, что Озера данных (или во многих случаях «Болота данных» — Data Swamps) трудно поддерживать в рабочем состоянии и окупать инвестиции в лицензии, оборудование и персонал для создания решений больших данных. К счастью, Лейкхаусы преодолевают эти проблемы. Лейкхаусы утверждают, что данные должны быть широко и легко доступны независимо от их возраста. Пакетные события и события реального времени перекрываются, и движки обработки должны иметь возможность прозрачно обращаться к обоим слоям.<\/p>\n<p>Вот возможности тиринга данных (распределения по уровням) и унифицированного просмотра, которые может предоставить Fluss, в дополнение к слою горячих\/свежих данных:<\/p>\n<ul>\n<li>Теплый слой (Warm layer):** для данных возрастом от минут до часов, в основном хранящихся в решениях объектного хранения (Object Storage).<\/li>\n<li>Холодный слой (Cold layer):** для данных возрастом от дней до лет. Решения Lakehouse, такие как `Apache Paimon` и `Iceberg`, являются предпочтительными платформами для этих исторических данных, питающих модели ML, ретроспективную аналитику и комплаенс.<\/li>\n<li>Zero-copy data tiering (Тиринг данных без копирования):** старение данных из горячего слоя (таблицы Fluss) в теплые\/холодные слои (Object Storage и Lakehouse). Это означает, что доступна единственная копия единицы данных, либо в слое реального времени, либо в историческом слое. Fluss управляет переключением между слоями, облегчая запросы и доступ. Подход Kafka опирается на дублирование данных с помощью задания потребителя\/издателя, что приводит к увеличению затрат на хранение и необходимости конвертировать топики Kafka в табличный формат Lakehouse.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-237.png\" width=\"1200\" height=\"674\" alt=\"\" \/>\n<\/div>\n<h3>Светлое будущее впереди<\/h3>\n<p>Аналитика данных в реальном времени становится краеугольным камнем современных компаний. Цифровые бизнес-модели должны обеспечивать лучший пользовательский опыт и своевременные ответы на взаимодействия с клиентами, что заставляет компании создавать системы для использования и управления данными в реальном времени, создавая увлекательный и впечатляющий («wow») опыт. Действовать сейчас — это не просто вопрос технической осуществимости; для большинства предприятий это становится уникальным преимуществом для выживания в высококонкурентной глобальной рыночной среде.<\/p>\n<p>Fluss помогает компаниям преодолеть разрыв между мирами реального времени и аналитики, предлагая унифицированный доступ как к свежим данным в реальном времени, так и к историческим, холодным данным. Вкратце, Fluss обеспечивает беспрепятственный доступ к данным независимо от возраста набора данных и упрощает сложные архитектуры аналитики данных, которые тянулись годами, в основном из-за отсутствия наиболее подходящих компонентов и фреймворков.<\/p>\n<p>В то время как Fluss служит слоем хранения в реальном времени для аналитики, Лейкхаусу предоставляется управление, простота и масштабируемость, которые защищают современные архитектуры в будущем.<\/p>\n<p>С операционной стороны он предлагает значительные преимущества за счет снижения сложности управления, хранения и обслуживания как данных реального времени, так и пакетных данных. Эта эффективность трансформируется в прямую экономию средств, достигаемую в первую очередь за счет оптимизированного формата таблиц Fluss, двухуровневой системы хранения, основанной на температуре данных, и, наконец, минимизации общего использования ЦП конвейера с помощью проталкивания предикатов (predicate pushdown) и обрезки столбцов. В совокупности эти архитектурные элементы снижают накладные операционные расходы, связанные с обслуживанием платформы, ускоряют внедрение новых сценариев использования и облегчают бесшовную интеграцию с существующей ИТ-инфраструктурой предприятия.<\/p>\n",
            "date_published": "2026-02-12T13:50:00+03:00",
            "date_modified": "2026-02-15T13:51:18+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer",
                "Data Governance",
                "Streaming"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/image-233.png",
            "_date_published_rfc2822": "Thu, 12 Feb 2026 13:50:00 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "318",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/image-233.png",
                    "https:\/\/gavrilov.info\/pictures\/image-234.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2026-02-15-v-13.48.31.png",
                    "https:\/\/gavrilov.info\/pictures\/image-235.png",
                    "https:\/\/gavrilov.info\/pictures\/image-236.png",
                    "https:\/\/gavrilov.info\/pictures\/image-237.png"
                ]
            }
        },
        {
            "id": "305",
            "url": "https:\/\/gavrilov.info\/all\/reyting-open-source-grafovyh-subd-dlya-adtech\/",
            "title": "Рейтинг Open Source Графовых СУБД для AdTech",
            "content_html": "<p>Для задач <b>AdTech сегментации<\/b> (профилирование пользователей, identity resolution, поиск look-alike аудиторий) набор требований к графовой базе данных специфичен: нужна высокая скорость операций чтения\/записи (real-time bidding\/serving) и горизонтальная масштабируемость (миллиарды событий и связей).<\/p>\n<p>Учитывая популярность текущего стека (<b>ClickHouse, Trino, Qdrant<\/b>), идеальная графовая база должна уметь интегрироваться в аналитический контур (через Trino или прямые коннекторы) и дополнять ClickHouse (который хранит логи событий), взяв на себя хранение топологии связей.<\/p>\n<p>Ниже представлен небольшой обзор и рейтинг Open Source решений на 2024-2025 год с фокусом на масштабируемость.<\/p>\n<hr \/>\n<h4>Рейтинг Open Source Графовых СУБД для AdTech<\/h4>\n<p>Разделим 12 решений на 3 эшелона по пригодности для высоконагруженной сегментации.<\/p>\n<h5>1 эшелон: Лидеры производительности и масштабирования (Native Distributed)<\/h5>\n<p>Эти базы изначально создавались для кластеров и больших объемов данных.<\/p>\n<p><b>1. NebulaGraph<\/b><\/p>\n<ul>\n<li><b>Тип:<\/b> Native Distributed Graph Database.<\/li>\n<li><b>Язык запросов:<\/b> nGQL (SQL-подобный).<\/li>\n<li><b>Архитектура:<\/b> Разделение Compute (GraphD) и Storage (StorageD). Shared-nothing.<\/li>\n<li><b>Плюсы для вас:<\/b> Это топ-1 выбор для AdTech масштаба Tencent или Meituan. Спокойно переваривает сотни миллиардов вершин и триллионы ребер. Обеспечивает миллисекундный отклик при обходе графа (hops) на большую глубину.<\/li>\n<li><b>Минусы:<\/b> Более крутая кривая обучения, чем у Neo4j. Сообщество меньше, но растет.<\/li>\n<li><b>Связь со стеком:<\/b> Отлично дополнит ClickHouse (CH хранит атрибуты, Nebula — связи). Есть коннекторы для Spark\/Flink. А через Spark можно дойти до Trino.<\/li>\n<\/ul>\n<p><b>2. Dgraph<\/b><\/p>\n<ul>\n<li><b>Тип:<\/b> Native Distributed Graph.<\/li>\n<li><b>Язык запросов:<\/b> GraphQL (модифицированный DQL).<\/li>\n<li><b>Архитектура:<\/b> Распределенная, использует BadgerDB (KV store) под капотом. Поддерживает шардинг и репликацию “из коробки” в open source версии.<\/li>\n<li><b>Плюсы:<\/b> Горизонтальное масштабирование. Очень удобна для фронтенд-разработчиков благодаря GraphQL. Высокая пропускная способность.<\/li>\n<li><b>Минусы:<\/b> Специфичный язык запросов, если вы привыкли к SQL\/Cypher. В последние годы темпы разработки ядра немного снизились относительно конкурентов.<\/li>\n<\/ul>\n<p><b>3. Memgraph<\/b><\/p>\n<ul>\n<li><b>Тип:<\/b> In-Memory Graph Database (написана на C++).<\/li>\n<li><b>Язык запросов:<\/b> Cypher (совместим с Neo4j).<\/li>\n<li><b>Архитектура:<\/b> Работает в оперативной памяти (с возможностью сброса на диск).<\/li>\n<li><b>Плюсы:<\/b> <b>Самая быстрая<\/b> для задач реального времени (вычисление фичей для RTB). Полная совместимость с экосистемой Neo4j (драйверы, протокол Bolt). Поддерживает Python\/Rust процедуры. Отличная работа с Streaming данными (Kafka).<\/li>\n<li><b>Минусы:<\/b> Ограничена объемом RAM (хотя есть disk-spill, это снижает скорость).<\/li>\n<li><b>Связь со стеком:<\/b> Отлично стыкуется с моделями AI (Qdrant), так как позиционируется для “Graph AI”.<\/li>\n<\/ul>\n<h5>2 эшелон: Классика и Универсалы<\/h5>\n<p><b>4. Neo4j (Community Edition)<\/b><\/p>\n<ul>\n<li><b>Тип:<\/b> Native Graph.<\/li>\n<li><b>Язык:<\/b> Cypher (стандарт индустрии).<\/li>\n<li><b>Плюсы:<\/b> Огромное сообщество, лучшая документация, куча плагинов (APOC).<\/li>\n<li><b>Главный минус для AdTech:<\/b> Open Source версия (Community) ограничена <b>одним узлом<\/b>. Нет встроенного кластеризации и шардинга (доступно только в Enterprise за большие деньги). Для “технического задела на вырост” в Open Source варианте — это бутылочное горлышко.<\/li>\n<\/ul>\n<p><b>5. ArangoDB<\/b><\/p>\n<ul>\n<li><b>Тип:<\/b> Multi-model (Graph, Document, Key\/Value).<\/li>\n<li><b>Язык:<\/b> AQL (похож на SQL).<\/li>\n<li><b>Плюсы:<\/b> Гибкость. Можно хранить сложные JSON-документы (как в Mongo) и связывать их.<\/li>\n<li><b>Минусы:<\/b> При глубоких обходах графа (“друзья друзей друзей”) проигрывает специализированным Native Graph базам по скорости. Это компромиссное решение.<\/li>\n<\/ul>\n<p><b>6. JanusGraph<\/b><\/p>\n<ul>\n<li><b>Тип:<\/b> Layered Graph Database.<\/li>\n<li><b>Плюсы:<\/b> Работает поверх мощных бэкендов (Cassandra, HBase, ScyllaDB) и использует Elasticsearch для индексации. Масштабируемость ограничена только бэкендом.<\/li>\n<li><b>Минусы:<\/b> Очень “тяжелая” инфраструктура (JVM based). Сложна в настройке и эксплуатации. Медленнее на простых запросах из-за сетевых хопов между слоями. Часто считается “устаревающей” архитектурой по сравнению с Nebula\/Dgraph.<\/li>\n<\/ul>\n<p><b>7. Apache AGE (PostgreSQL Extension)<\/b><\/p>\n<ul>\n<li><b>Тип:<\/b> Extension.<\/li>\n<li><b>Суть:<\/b> Превращает PostgreSQL в графовую БД с поддержкой Cypher.<\/li>\n<li><b>Плюсы:<\/b> Если вы знаете Postgres, вы знаете AGE. Не нужно новой инфраструктуры.<\/li>\n<li><b>Минусы:<\/b> Производительность ограничена движком Postgres. Сложно масштабировать горизонтально на запись (проблема шардинга PG).<\/li>\n<\/ul>\n<h5>3 эшелон: Нишевые и Новые игроки<\/h5>\n<p><b>8. HugeGraph<\/b> (Baidu) — аналог JanusGraph, популярен в Китае, очень мощный, но документация местами страдает.<br \/>\n<b>9. OrientDB<\/b> — мультимодельная, была популярна, но сейчас развитие замедлилось.<br \/>\n<b>10. FalkorDB<\/b> — форк закрывшегося RedisGraph (Redis module). Очень быстрый, использует разреженные матрицы. Интересен, если уже есть Redis.<br \/>\n<b>11. Cayley<\/b> — написана на Go (Google), простая, работает с триплетами (Linked Data), но для сложной AdTech логики может не хватить функционала.<br \/>\n<b>12. TerminusDB<\/b> — интересная база с концепцией “Git для данных”, но специфична для версионирования знаний, а не высоконагруженной сегментации.<\/p>\n<h4>Сравнительная таблица (ТОП-7 для выбора)<\/h4>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">СУБД<\/td>\n<td style=\"text-align: center\">Язык запросов<\/td>\n<td style=\"text-align: center\">Архитектура<\/td>\n<td style=\"text-align: center\">Масштабирование (Open Source)<\/td>\n<td style=\"text-align: center\">Скорость (Read\/Traverse)<\/td>\n<td style=\"text-align: center\">Сложность эксплуатации<\/td>\n<td style=\"text-align: center\">Идеально для<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>NebulaGraph<\/b><\/td>\n<td style=\"text-align: center\">nGQL (SQL-like)<\/td>\n<td style=\"text-align: center\">Distributed Native<\/td>\n<td style=\"text-align: center\"><b>Отличное<\/b> (Sharding+Replication)<\/td>\n<td style=\"text-align: center\">🔥 Очень высокая<\/td>\n<td style=\"text-align: center\">Средняя\/Высокая<\/td>\n<td style=\"text-align: center\">Big Data, AdTech, Fraud<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Memgraph<\/b><\/td>\n<td style=\"text-align: center\">Cypher<\/td>\n<td style=\"text-align: center\">In-Memory (C++)<\/td>\n<td style=\"text-align: center\">Вертикальное \/ Репликация<\/td>\n<td style=\"text-align: center\">🚀 <b>Топ-1 (Low Latency)<\/b><\/td>\n<td style=\"text-align: center\">Низкая (как Docker)<\/td>\n<td style=\"text-align: center\">Real-time features, Streaming<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Dgraph<\/b><\/td>\n<td style=\"text-align: center\">GraphQL<\/td>\n<td style=\"text-align: center\">Distributed Native<\/td>\n<td style=\"text-align: center\"><b>Отличное<\/b><\/td>\n<td style=\"text-align: center\">Высокая<\/td>\n<td style=\"text-align: center\">Средняя<\/td>\n<td style=\"text-align: center\">App Backend, 360 Customer View<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Neo4j (CE)<\/b><\/td>\n<td style=\"text-align: center\">Cypher<\/td>\n<td style=\"text-align: center\">Native<\/td>\n<td style=\"text-align: center\"><b>Нет<\/b> (только 1 нода)<\/td>\n<td style=\"text-align: center\">Высокая (локально)<\/td>\n<td style=\"text-align: center\">Низкая<\/td>\n<td style=\"text-align: center\">R&D, малые проекты<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>ArangoDB<\/b><\/td>\n<td style=\"text-align: center\">AQL<\/td>\n<td style=\"text-align: center\">Multi-model<\/td>\n<td style=\"text-align: center\">Хорошее (Cluster mode)<\/td>\n<td style=\"text-align: center\">Средняя<\/td>\n<td style=\"text-align: center\">Средняя<\/td>\n<td style=\"text-align: center\">Гибридные данные (Docs+Graph)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>JanusGraph<\/b><\/td>\n<td style=\"text-align: center\">Gremlin<\/td>\n<td style=\"text-align: center\">Layered (over NoSQL)<\/td>\n<td style=\"text-align: center\">Бесконечное (зависит от Backend)<\/td>\n<td style=\"text-align: center\">Низкая\/Средняя<\/td>\n<td style=\"text-align: center\">☠️ Высокая<\/td>\n<td style=\"text-align: center\">Если уже есть HBase\/Cassandra<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Apache AGE<\/b><\/td>\n<td style=\"text-align: center\">Cypher<\/td>\n<td style=\"text-align: center\">Postgres Ext<\/td>\n<td style=\"text-align: center\">Только Read Replicas<\/td>\n<td style=\"text-align: center\">Средняя<\/td>\n<td style=\"text-align: center\">Низкая (если знают PG)<\/td>\n<td style=\"text-align: center\">Гибрид SQL + Graph<\/td>\n<\/tr>\n<\/table>\n<h4>Интеграция с текущим стеком (Qdrant, Trino или ClickHouse)<\/h4>\n<ol start=\"1\">\n<li><b>Qdrant + Graph DB = GraphRAG \/ Semantic Search:<\/b>\n<ul>\n  <li>Сегментация пользователей часто требует поиска не только по связям (“кто кликал то же, что и я”), но и по похожести векторов (“чей профиль похож на мой”).<\/li>\n  <li>Memgraph<b> и **Neo4j<\/b> имеют встроенные модули для работы с векторами, но так как у вас уже есть <b>Qdrant<\/b>, вам нужна база, которая *не пытается заменить Qdrant*, а позволяет хранить ID векторов в узлах графа.<\/li>\n  <li>NebulaGraph** позволяет хранить embedding в свойствах узла, но поиск лучше делегировать Qdrant.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Trino:<\/b>\n<ul>\n  <li>Вам захочется делать SQL-запросы сразу к ClickHouse (события) и Графу (профиль).<\/li>\n  <li>У <b>Neo4j<\/b> и <b>NebulaGraph<\/b> есть коннекторы, позволяющие Trino (через JDBC или нативные коннекторы) запрашивать данные. Это мощнейшая связка для аналитиков. Отдельно нативного конектора к Trino пока не найти, но скоро может появится поддержка iceberg <a href=\"https:\/\/github.com\/vesoft-inc\/nebula\/discussions\/5902\">https:\/\/github.com\/vesoft-inc\/nebula\/discussions\/5902<\/a> или пока можно использоваться связку через Spark.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>ClickHouse:<\/b>\n<ul>\n  <li>Паттерн: ClickHouse хранит “сырые” логи (миллиарды строк). Агрегаты и связи (User Graph) пересчитываются и заливаются в Графовую БД для быстрого lookup.<\/li>\n  <li>NebulaGraph** имеет Exchange (инструмент на основе Spark) для массовой заливки данных из Warehouse.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<hr \/>\n<h4>Итоговая рекомендация<\/h4>\n<p>Учитывая, что вы хотите <b>Open Source<\/b> и вам нужен <b>технический задел (масштабирование)<\/b> для AdTech:<\/p>\n<h5>🏆 Выбор №1: NebulaGraph<\/h5>\n<p>Это наиболее близкий аналог “ClickHouse в мире графов”.<\/p>\n<ul>\n<li>Почему:** Он создан для хранения миллиардов вершин (пользователей\/устройств) и работы в кластере. У него shared-nothing архитектура, которая необходима для роста. Язык nGQL будет понятен вашим аналитикам, знающим SQL (ClickHouse\/Trino).<\/li>\n<li>Для AdTech:** Идеально решает проблемы *Identity Resolution* (склеивание cookie, device_id, user_id и других атрибутов в единый граф) на больших объемах.<\/li>\n<\/ul>\n<h5>🥈 Выбор №2: Memgraph<\/h5>\n<p>Если ваши графы помещаются в память (сотни миллионов узлов, но не десятки миллиардов) и критична задержка (latency) менее 10 мс для *real-time* принятия решений.<\/p>\n<ul>\n<li>Почему:** Он безумно быстр. Он совместим с Cypher (легко нанимать людей или переезжать с Neo4j). Написан на C++, очень эффективен.<\/li>\n<li>Интеграция:** Идеально, если вы планируете стримить данные из Kafka, обновлять граф и сразу выдавать сегменты.<\/li>\n<\/ul>\n<h5>🥉 Выбор №3: Apache AGE (или ArangoDB)<\/h5>\n<p>Только если объем графа невелик, и вы хотите минимизировать зоопарк технологий, оставаясь в рамках “почти SQL” решений. Но для серьезного AdTech они не рекомендуется как *основное* хранилище графа пользователей.<\/p>\n<p><b>Совет:<\/b> Начните пилот (PoC) с <b>NebulaGraph<\/b>. Попробуйте загрузить туда выгрузку из ClickHouse и сравнить скорость выполнения запросов “найти всех пользователей, связанных через устройство X на глубину 3 шага” с тем, как это делается сейчас (вероятно, через JOINs в реляционке или CH). Если сложность эксплуатации Nebula покажется высокой, можно посмотреть в сторону <b>Memgraph<\/b> как более легкой альтернативы и применять их не на одном большом графе например, а на нескольких малых в реальном времени, а готовые расчеты уже хранить в привычных местах.<\/p>\n<p><b>Еще можно почитать:<\/b><\/p>\n<ul>\n<li><a href=\"https:\/\/bigdataschool.ru\/blog\/memgraph-vs-neo4j\/\">Сравнение Memgraph и Neo4j bigdataschool.ru<\/a><\/li>\n<li><a href=\"https:\/\/bigdataschool.ru\/blog\/neo4j-vs-tigergraph-what-to-choose.html\">Сравнение Neo4j и TigerGraph (для понимания коммерческого рынка bigdataschool.ru<\/a><\/li>\n<li><a href=\"https:\/\/wiki.merionet.ru\/articles\/10-lucsix-resenii-dlia-raboty-s-grafovymi-bazami-dannyx\">Обзор графовых БД wiki.merionet.ru<\/a><\/li>\n<\/ul>\n<p>Вот еще мысль и про языки немного. Если проект большой с единым графом для разных нужд, то NebulaGraph выглядит лучшим решением, но архитектурно можно выбрать много средних и малых графов. Для второго подхода хорошо Memgraph с его языком Cypher<\/p>\n<hr \/>\n<h4>1. Семейство Cypher (OpenCypher \/ ISO GQL)<\/h4>\n<p><b>Базы:<\/b> *Neo4j, Memgraph, FalkorDB, Apache AGE.*<\/p>\n<p>Cypher — это «SQL для графов». Это декларативный язык, использующий ASCII-арт для визуализации связей в коде (например, `(User)-[:CLICKS]->(Ad)`).<\/p>\n<ul>\n<li><b>Функциональность:<\/b> Очень богатая. Поддерживает сложные паттерны (Pattern Matching), агрегации, пути переменной длины. В апреле 2024 года ISO утвердила стандарт <b>GQL<\/b> (Graph Query Language), который во многом основан на Cypher.<\/li>\n<li><b>Плюсы:<\/b>\n<ul>\n  <li><b>Интуитивность:<\/b> Код читается как предложение на английском. Самая низкая кривая входа.<\/li>\n  <li><b>Экосистема:<\/b> Стандарт де-факто. Если вы знаете Cypher, вы можете переключаться между Neo4j, Memgraph и AGE без переобучения.<\/li>\n  <li><b>Выразительность:<\/b> Идеален для глубокой аналитики и поиска сложных паттернов (Fraud Detection).<\/li>\n<\/ul>\n<\/li>\n<li><b>Минусы:<\/b>\n<ul>\n  <li>Изначально создавался для одноузловых систем. В распределенных системах (шардинг) некоторые конструкции Cypher могут быть сложны для оптимизации движком.<\/li>\n<\/ul>\n<\/li>\n<li><b>Оценка для стека:<\/b>\n<ul>\n  <li><b>Memgraph\/Neo4j:<\/b> Работает идеально.<\/li>\n  <li><b>Apache AGE:<\/b> Cypher оборачивается внутри SQL запросов Postgres, что немного громоздко, но функционально.<\/li>\n  <li><b>FalkorDB:<\/b> Реализует подмножество Cypher, очень быстро благодаря Redis, но функционал беднее, чем у Neo4j.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4>2. Семейство Gremlin (Apache TinkerPop)<\/h4>\n<p><b>Базы:<\/b> *JanusGraph, HugeGraph, OrientDB (частично), Azure CosmosDB.*<\/p>\n<p>Gremlin — это императивный язык обхода графа (Traversals). Вы пишете не «что найти» (как в SQL\/Cypher), а «куда идти» шаг за шагом.<\/p>\n<ul>\n<li><b>Функциональность:<\/b> Тьюринговская полнота. Можно написать алгоритм любой сложности прямо внутри запроса. Это скорее язык программирования потоков данных, чем язык запросов.<\/li>\n<li><b>Плюсы:<\/b>\n<ul>\n  <li><b>Контроль:<\/b> Вы точно указываете базе, как обходить граф. Это важно для сверхбольших графов (как в JanusGraph\/HugeGraph), где неверный план запроса может “положить” кластер.<\/li>\n  <li><b>Абстракция:<\/b> Работает поверх любой БД, поддерживающей TinkerPop.<\/li>\n<\/ul>\n<\/li>\n<li><b>Минусы:<\/b>\n<ul>\n  <li><b>Сложность:<\/b> Кривая обучения очень крутая. Код получается вербозным и сложным для отладки («write once, read never»).<\/li>\n  <li><b>Устаревание:<\/b> С появлением стандарта ISO GQL популярность Gremlin падает. Для новых проектов в 2025 году его выбирают редко, если только не привязаны к JanusGraph.<\/li>\n<\/ul>\n<\/li>\n<li><b>Пример AdTech:<\/b> «Найти всех пользователей, кликнувших на этот баннер» на Gremlin будет длинной цепочкой вызовов методов (`g.V().has(‘Banner’...).out(‘CLICKS’)...`).<\/li>\n<\/ul>\n<h4>3. nGQL (NebulaGraph Query Language)<\/h4>\n<p><b>Базы:<\/b> *NebulaGraph.*<\/p>\n<p>Собственный язык Nebula, который синтаксически мимикрирует под SQL, но логически работает с графами.<\/p>\n<ul>\n<li><b>Функциональность:<\/b> Заточена под распределенный Massive Parallel Processing (MPP).<\/li>\n<li><b>Плюсы:<\/b>\n<ul>\n  <li><b>SQL-подход:<\/b> Разработчикам, привыкшим к MySQL\/ClickHouse, синтаксис `GO FROM ... OVER ...` будет понятнее, чем Gremlin.<\/li>\n  <li><b>Скорость:<\/b> Спроектирован так, чтобы не позволять писать «плохие» запросы, которые убивают распределенный кластер. Вынуждает думать о том, где лежат данные (VID).<\/li>\n  <li><b>Пайпы:<\/b> Удобный синтаксис передачи результата одного шага в другой через `|` (как в Bash).<\/li>\n<\/ul>\n<\/li>\n<li><b>Минусы:<\/b>\n<ul>\n  <li><b>Vendor Lock-in:<\/b> Это не стандарт. Переехать с Nebula на другую базу потребует переписывания всех запросов.<\/li>\n  <li>Не поддерживает полную гибкость Pattern Matching, как Cypher (хотя добавили поддержку `MATCH`, она менее производительна, чем нативный `GO`).<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4>4. DQL (ранее GraphQL+-)<\/h4>\n<p><b>Базы:<\/b> *Dgraph.*<\/p>\n<p>Это модифицированный GraphQL.<\/p>\n<ul>\n<li><b>Функциональность:<\/b> Идеальна для API. Вы запрашиваете данные в формате JSON-дерева, и база возвращает JSON.<\/li>\n<li><b>Плюсы:<\/b>\n<ul>\n  <li><b>Frontend-first:<\/b> Фронтендерам не нужен бэкенд-прослойка, они могут (теоретически) ходить в базу почти напрямую.<\/li>\n  <li><b>Работа с атрибутами:<\/b> Поскольку Dgraph — это по сути распределенный Key-Value, DQL очень быстро достает атрибуты нод.<\/li>\n<\/ul>\n<\/li>\n<li><b>Минусы:<\/b>\n<ul>\n  <li><b>Слабая аналитика:<\/b> Графовые алгоритмы и сложные обходы (traversals) на DQL писать сложнее и менее эффективно, чем на Cypher\/nGQL. Это язык выборки данных, а не язык аналитики графов.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4>5. AQL (ArangoDB Query Language)<\/h4>\n<p><b>Базы:<\/b> *ArangoDB.*<\/p>\n<p>Гибридный язык, объединяющий возможности SQL (JOINs), работы с JSON (как в Mongo) и графовых обходов.<\/p>\n<ul>\n<li><b>Функциональность:<\/b> Одна из самых мощных среди “универсалов”. Позволяет в одном запросе сделать JOIN трех коллекций, отфильтровать JSON и пройтись по графу друзей.<\/li>\n<li><b>Плюсы:<\/b> Гибкость.<\/li>\n<li><b>Минусы:<\/b> Синтаксис `FOR u IN users FILTER ...` специфичен и многословен. Для чистых графовых задач (deep hopping) он медленнее нативных решений [ArangoDB vs Native Graph].<\/li>\n<\/ul>\n<h4>6. Другие \/ Устаревающие<\/h4>\n<ul>\n<li><b>OrientDB (SQL-extended):<\/b> Пытались расширить SQL для графов. Сейчас проект стагнирует, язык считается тупиковой ветвью эволюции по сравнению с Cypher\/GQL.<\/li>\n<li><b>SQL Graph (MS SQL \/ PG SQL):<\/b> В [статье про SQL Server](<a href=\"https:\/\/learn.microsoft.com\/ru-ru\/sql\/relational-databases\/graphs\/sql-graph-sample?view=sql-server-ver17)\">https:\/\/learn.microsoft.com\/ru-ru\/sql\/relational-databases\/graphs\/sql-graph-sample?view=sql-server-ver17)<\/a> показан синтаксис `MATCH`, который Microsoft внедрила в T-SQL. Это попытка “догнать” Cypher, оставаясь в рамках реляционной модели. Удобно, если вы намертво привязаны к MS SQL, но неудобно для сложной аналитики.<\/li>\n<li><b>Cayley (Gizmo\/MQL):<\/b> Очень нишевый язык на базе Go или JS. Для AdTech продакшена слишком экзотичен.<\/li>\n<\/ul>\n<hr \/>\n<h4>Сводная таблица сравнения<\/h4>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">Язык<\/td>\n<td style=\"text-align: center\">Базы данных<\/td>\n<td style=\"text-align: center\">Порог входа<\/td>\n<td style=\"text-align: center\">Для AdTech\/High-load<\/td>\n<td style=\"text-align: center\">Стандартность (2025)<\/td>\n<td style=\"text-align: center\">Примечание<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>nGQL<\/b><\/td>\n<td style=\"text-align: center\"><b>NebulaGraph<\/b><\/td>\n<td style=\"text-align: center\">Средний<\/td>\n<td style=\"text-align: center\"><b>Идеально<\/b> (Tencent scale)<\/td>\n<td style=\"text-align: center\">Низкая (Vendor specific)<\/td>\n<td style=\"text-align: center\">Топ для сотен млрд связей и кластерной архитектуры.<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Cypher<\/b><\/td>\n<td style=\"text-align: center\"><b>Memgraph<\/b>, Neo4j, AGE<\/td>\n<td style=\"text-align: center\"><b>Низкий<\/b><\/td>\n<td style=\"text-align: center\">Хорошо (Memgraph) \/ Средне (Neo4j)<\/td>\n<td style=\"text-align: center\"><b>Высокая<\/b> (основа ISO GQL)<\/td>\n<td style=\"text-align: center\">Самый удобный для аналитиков и Data Science.<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>DQL<\/b><\/td>\n<td style=\"text-align: center\"><b>Dgraph<\/b><\/td>\n<td style=\"text-align: center\">Низкий (для Web-dev)<\/td>\n<td style=\"text-align: center\">Хорошо (для OLTP)<\/td>\n<td style=\"text-align: center\">Низкая<\/td>\n<td style=\"text-align: center\">Лучший выбор, если граф — это бэкенд для UI.<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: right\"><b>Gremlin<\/b><\/td>\n<td style=\"text-align: center\">JanusGraph, HugeGraph<\/td>\n<td style=\"text-align: center\">Высокий<\/td>\n<td style=\"text-align: center\">Отлично (если настроить)<\/td>\n<td style=\"text-align: center\">Падает (Legacy)<\/td>\n<td style=\"text-align: center\">Слишком сложен в поддержке, проигрывает современным языкам.<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>AQL<\/b><\/td>\n<td style=\"text-align: center\">ArangoDB<\/td>\n<td style=\"text-align: center\">Средний<\/td>\n<td style=\"text-align: center\">Средне<\/td>\n<td style=\"text-align: center\">Низкая<\/td>\n<td style=\"text-align: center\">Хорош, если нужна “Document Store + Graph” в одном.<\/td>\n<\/tr>\n<\/table>\n<h4>Итоговая рекомендация<\/h4>\n<ol start=\"1\">\n<li><b>Если приоритет — производительность на масштабе (AdTech, сегментация 100M+ пользователей):<\/b>  <br \/>\nВам нужен <b>NebulaGraph<\/b> и его <b>nGQL<\/b>.<\/li>\n\n<ul>\n  <li>*Почему:* В AdTech сценариях (как у Meituan\/Tencent) критичны latency на “хопах” (hops). nGQL архитектурно заставляет писать запросы так, чтобы они эффективно параллелились. Он менее удобен, чем Cypher, но более предсказуем в нагрузке.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Если приоритет — Real-time аналитика, ML-фичи и скорость разработки:<\/b>  <br \/>\nВам нужен <b>Memgraph<\/b> на <b>Cypher<\/b>.<\/li>\n\n<ul>\n  <li>*Почему:* Вы получаете совместимость с самой популярной экосистемой (Neo4j), стандартный язык Cypher (легко найти специалистов) и скорость C++ in-memory движка.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>Если приоритет — дешевое горизонтальное масштабирование “для бедных” (в хорошем смысле):<\/b>  <br \/>\nВам нужен <b>Dgraph<\/b> (DQL) или <b>NebulaGraph<\/b>.<\/li>\n\n<ul>\n  <li>У <b>Dgraph<\/b> отличный шардинг из коробки и DQL закрывает 90% задач продуктовой разработки, но может буксовать на тяжелой аналитике.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p><b>От чего стоит отказаться:<\/b><\/p>\n<ul>\n<li><b>Neo4j Community:<\/b> Язык Cypher прекрасен, но ограничения лицензии (отсутствие кластера) убьют проект на росте.<\/li>\n<li><b>JanusGraph\/HugeGraph (Gremlin):<\/b> В 2025 году начинать проект на Gremlin — это создавать себе технический долг, так как индустрия движется в сторону ISO GQL (Cypher Style).<\/li>\n<li><b>Apache AGE:<\/b> Пока слишком сыро для High-load, проблемы с горизонтальным масштабированием Postgres никуда не деваются.<\/li>\n<\/ul>\n",
            "date_published": "2025-12-14T14:24:45+03:00",
            "date_modified": "2025-12-18T23:42:57+03:00",
            "tags": [
                "Data",
                "Data Engineer",
                "Database",
                "Graph"
            ],
            "_date_published_rfc2822": "Sun, 14 Dec 2025 14:24:45 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "305",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "302",
            "url": "https:\/\/gavrilov.info\/all\/obzor-pg-clickhouse-kak-obedinit-mosch-clickhouse-i-udobstvo-pos\/",
            "title": "Обзор pg_clickhouse: Как объединить мощь ClickHouse и удобство PostgreSQL",
            "content_html": "<p>Недавно компания ClickHouse представила новый инструмент — расширение <b>pg_clickhouse<\/b>. Это событие стало ответом на одну из самых частых болей разработчиков: сложность миграции аналитических запросов из классических реляционных баз данных в колоночные аналитические СУБД.<\/p>\n<p>Оригинал статьи: <a href=\"https:\/\/clickhouse.com\/blog\/introducing-pg_clickhouse\">A Postgres extension for querying ClickHouse<\/a><\/p>\n<p>или берем сразу тут: <a href=\"https:\/\/github.com\/ClickHouse\/pg_clickhouse\/releases\">https:\/\/github.com\/ClickHouse\/pg_clickhouse\/releases<\/a><\/p>\n<p>В этой статье мы разберем, что представляет собой этот инструмент, в чем его фундаментальный смысл для архитектуры приложений и куда проект хочет двигаться дальше.<\/p>\n<h3>Проблема: Данные переехали, а запросы остались<\/h3>\n<p>Типичный сценарий роста стартапа выглядит так: приложение строится на PostgreSQL. В какой-то момент данных (логов, метрик, транзакций) становится так много, что аналитические отчеты начинают тормозить. Обычные реплики для чтения (read replicas) перестают спасать.<\/p>\n<p>Команда принимает решение внедрить ClickHouse. Перенос данных сейчас решается просто (например, с помощью ClickPipes), но возникает другая проблема:<br \/>\n<b>Как быть с тысячами строк SQL-кода в ORM, дашбордах и скриптах, которые написаны под синтаксис Postgres?<\/b><\/p>\n<p>Переписывание всей логики приложения под диалект ClickHouse — это месяцы работы и риск новых багов. Именно эту проблему решает `pg_clickhouse`.<\/p>\n<h3>Что такое pg_clickhouse?<\/h3>\n<p><b>pg_clickhouse<\/b> — это расширение для PostgreSQL (Foreign Data Wrapper — FDW), которое позволяет создавать в Postgres «внешние таблицы», фактически ссылающиеся на таблицы в ClickHouse.<\/p>\n<p><b>Суть технологии:<\/b> Вы пишете запросы на привычном SQL диалекте PostgreSQL, обращаясь к этим таблицам. Расширение на лету транслирует запрос в диалект ClickHouse, отправляет его на исполнение в аналитическую базу и возвращает результат обратно в Postgres.<\/p>\n<p>Для приложения это выглядит прозрачно: таблицы ClickHouse могут находиться просто в отдельной схеме (schema). Достаточно изменить путь поиска (`search_path`), и старые запросы начнут работать с данными, лежащими в ClickHouse.<\/p>\n<h3>В чем «соль»: Технология Pushdown<\/h3>\n<p>Главная ценность и сложность такого расширения заключается не просто в соединении двух баз, а в эффективности этого соединения. Этот механизм называется <b>Pushdown<\/b> (спуск или делегирование вычислений).<\/p>\n<p>Если вы делаете запрос `SELECT sum(price) FROM orders`, есть два пути его выполнения:<\/p>\n<ol start=\"1\">\n<li><b>Плохой путь:<\/b> Postgres выкачивает *все* миллионы строк из ClickHouse и сам считает сумму. Это уничтожает весь смысл аналитической базы.<\/li>\n<li><b>Путь pg_clickhouse:<\/b> Расширение понимает, что это агрегация, и отправляет в ClickHouse команду «посчитай сумму». Обратно по сети возвращается только одна цифра.<\/li>\n<\/ol>\n<h4>Умная трансляция функций<\/h4>\n<p>Авторы `pg_clickhouse` пошли дальше простой трансляции. Они научили расширение переводить специфические функции Postgres в аналоги ClickHouse, даже если синтаксис кардинально отличается.<\/p>\n<p>*Пример:*<br \/>\nВ Postgres есть функция для расчета медианы: `percentile_cont(0.5) WITHIN GROUP (ORDER BY price)`.<br \/>\nВ ClickHouse такой синтаксис не поддерживается.<br \/>\n`pg_clickhouse` автоматически переписывает это в нативную функцию ClickHouse: `quantile(0.5)(price)`.<\/p>\n<p>Также поддерживается трансляция конструкции `FILTER (WHERE ...)` в специфичные для ClickHouse комбинаторы `-If` (например, `sumIf`).<\/p>\n<h4>Ускорение подзапросов (Semi-Join)<\/h4>\n<p>В версии 0.1.0 была реализована поддержка <b>SEMI JOIN Pushdown<\/b>. Это критически важно для запросов с конструкцией `WHERE ... IN (SELECT ...)` или `EXISTS`. Тесты на бенчмарке TPC-H показали, что благодаря этому время выполнения сложных запросов сократилось с нескольких секунд (или даже минут) до миллисекунд, так как фильтрация теперь происходит на стороне ClickHouse.<\/p>\n<h3>Планы развития (Roadmap)<\/h3>\n<p>Проект находится в стадии активной разработки (версия 0.1.0), и команда ClickHouse нацелена на полное покрытие аналитических сценариев.<\/p>\n<p><b>Ключевые пункты плана:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Полное покрытие TPC-H и ClickBench:<\/b> Оптимизация планировщика, чтобы все стандартные аналитические бенчмарки выполнялись с максимальным pushdown-ом.<\/li>\n<li><b>Расширенная поддержка функций:<\/b> Трансляция *всех* агрегатных и обычных функций PostgreSQL в их эквиваленты в ClickHouse.<\/li>\n<li><b>DML операции:<\/b> Поддержка легковесных удалений (`DELETE`) и обновлений (`UPDATE`), а также пакетной вставки данных через `COPY`.<\/li>\n<li><b>Управление настройками:<\/b> Возможность передавать настройки ClickHouse (settings) через команды создания пользователей или серверов в Postgres.<\/li>\n<li><b>Passthrough-режим:<\/b> Возможность отправить произвольный SQL-запрос (на диалекте ClickHouse) и получить результат в виде таблицы, обходя парсер Postgres.<\/li>\n<\/ol>\n<h3>Заключение<\/h3>\n<p>`pg_clickhouse` — это попытка построить «лучшее из двух миров»: взять скорость колоночной СУБД и объединить её с богатой экосистемой и инструментарием PostgreSQL. Это позволяет разработчикам плавно мигрировать нагрузку, не переписывая приложение с нуля, и оставляет Postgres в качестве единой точки входа для данных.<\/p>\n",
            "date_published": "2025-12-12T23:27:54+03:00",
            "date_modified": "2025-12-12T23:32:31+03:00",
            "tags": [
                "Data",
                "Data Engineer",
                "Database",
                "Dev"
            ],
            "_date_published_rfc2822": "Fri, 12 Dec 2025 23:27:54 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "302",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "299",
            "url": "https:\/\/gavrilov.info\/all\/sravnitelny-analiz-self-hosted-s3-sovmestimyh-hranilisch\/",
            "title": "Сравнительный анализ self-hosted S3-совместимых хранилищ",
            "content_html": "<p>Четкое сравнение семи self-hosted S3-совместимых решений для хранения данных.<\/p>\n<p>Оригинал тут: <a href=\"https:\/\/www.repoflow.io\/blog\/benchmarking-self-hosted-s3-compatible-storage-a-practical-performance-comparison\">Команда RepoFlow. 9 августа 2025 г.<\/a><\/p>\n<p>Локальное (self-hosted) объектное хранилище — это отличный выбор для разработчиков и команд, которые хотят иметь полный контроль над хранением и доступом к своим данным. Независимо от того, заменяете ли вы Amazon S3, размещаете внутренние файлы, создаете CI-конвейер или обслуживаете репозитории пакетов, уровень хранения может значительно повлиять на скорость и стабильность.<\/p>\n<p>Мы протестировали семь популярных решений для объектного хранения, поддерживающих протокол S3. Цель состояла в том, чтобы сравнить их производительность в идентичных условиях, используя реальные операции загрузки и скачивания.<\/p>\n<h3>Тестируемые решения<\/h3>\n<p>Каждое из следующих решений было развернуто с помощью Docker на одном и том же сервере без монтирования томов и без специальной настройки:<\/p>\n<ol start=\"1\">\n<li>`MinIO`<\/li>\n<li>`Ceph`<\/li>\n<li>`SeaweedFS`<\/li>\n<li>`Garage`<\/li>\n<li>`Zenko` (Scality Cloudserver)<\/li>\n<li>`LocalStack`<\/li>\n<li>`RustFS`<\/li>\n<\/ol>\n<h3>Скорость последовательного скачивания<\/h3>\n<p>Средняя скорость скачивания одного файла разного размера.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.42.26.png\" width=\"1270\" height=\"656\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости последовательного скачивания для малых файлов размером 50 КБ и 200 КБ. По оси Y — скорость в МБ\/с, по оси X — размер файла. Сравниваются Garage, Localstack, Minio, Zenko, Ceph, RustFS, SeaweedFS.]<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.42.57.png\" width=\"1264\" height=\"650\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости последовательного скачивания для больших файлов размером 10 МБ, 50 МБ, 100 МБ и 1 ГБ. По оси Y — скорость в МБ\/с, по оси X — размер файла. Сравниваются те же решения.]<\/p>\n<h3>Скорость последовательной загрузки<\/h3>\n<p>Средняя скорость загрузки одного файла разного размера.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.43.18.png\" width=\"1278\" height=\"648\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости последовательной загрузки для малых файлов размером 50 КБ и 200 КБ. По оси Y — скорость в МБ\/с, по оси X — размер файла. Сравниваются те же решения.]<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.43.34.png\" width=\"1252\" height=\"636\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости последовательной загрузки для больших файлов размером 10 МБ, 50 МБ, 100 МБ и 1 ГБ. По оси Y — скорость в МБ\/с, по оси X — размер файла. Сравниваются те же решения.]<\/p>\n<h3>Производительность листинга<\/h3>\n<p>Измеряет время, необходимое для получения списка всех 2000 тестовых объектов в бакете с использованием разных размеров страницы (100, 500 и 1000 результатов на запрос).<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.44.02.png\" width=\"1280\" height=\"648\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График производительности листинга. По оси Y — время в мс, по оси X — количество результатов на страницу (100, 500, 1000). Сравниваются те же решения.]<\/p>\n<h3>Скорость параллельной загрузки<\/h3>\n<p>Измеряет время, необходимое для параллельной загрузки нескольких файлов одинакового размера. Скорость загрузки рассчитывается по формуле:<\/p>\n<p>(number of files × file size) ÷ total time<\/p>\n<h4>Скорость параллельной загрузки – файлы 1 МБ<\/h4>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.44.22.png\" width=\"1304\" height=\"748\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости параллельной загрузки файлов размером 1 МБ. По оси Y — скорость в МБ\/с, по оси X — количество параллельных потоков (5, 10, 20). Сравниваются те же решения.]<\/p>\n<h4>Скорость параллельной загрузки – файлы 10 МБ<\/h4>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.45.06.png\" width=\"1254\" height=\"650\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости параллельной загрузки файлов размером 10 МБ. По оси Y — скорость в МБ\/с, по оси X — количество параллельных потоков (5, 10, 20). Сравниваются те же решения.]<\/p>\n<h4>Скорость параллельной загрузки – файлы 100 МБ<\/h4>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.45.23.png\" width=\"1278\" height=\"652\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости параллельной загрузки файлов размером 100 МБ. По оси Y — скорость в МБ\/с, по оси X — количество параллельных потоков (5, 10, 20). Сравниваются те же решения.]<\/p>\n<h3>Скорость параллельного скачивания<\/h3>\n<p>Измеряет время, необходимое для параллельного скачивания нескольких файлов одинакового размера. Скорость скачивания рассчитывается по формуле:<\/p>\n<p>(number of files × file size) ÷ total time<\/p>\n<h4>Скорость параллельного скачивания – файлы 1 МБ<\/h4>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.45.45.png\" width=\"1278\" height=\"650\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости параллельного скачивания файлов размером 1 МБ. По оси Y — скорость в МБ\/с, по оси X — количество параллельных потоков (5, 10, 20). Сравниваются те же решения.]<\/p>\n<h4>Скорость параллельного скачивания – файлы 10 МБ<\/h4>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.46.02.png\" width=\"1280\" height=\"644\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости параллельного скачивания файлов размером 10 МБ. По оси Y — скорость в МБ\/с, по оси X — количество параллельных потоков (5, 10, 20). Сравниваются те же решения.]<\/p>\n<h4>Скорость параллельного скачивания – файлы 100 МБ<\/h4>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.46.17.png\" width=\"1284\" height=\"670\" alt=\"\" \/>\n<\/div>\n<p>[Изображение: График скорости параллельного скачивания файлов размером 100 МБ. По оси Y — скорость в МБ\/с, по оси X — количество параллельных потоков (5, 10, 20). Сравниваются те же решения.]<\/p>\n<h3>Как проводились тесты<\/h3>\n<p>Для каждого решения мы:<\/p>\n<ol start=\"1\">\n<li>Загружали и скачивали файлы 7 различных размеров: 50 КБ, 200 КБ, 1 МБ, 10 МБ, 50 МБ, 100 МБ и 1 ГБ.<\/li>\n<li>Повторяли каждую загрузку и скачивание 20 раз для получения стабильных средних значений.<\/li>\n<li>Измеряли среднюю скорость загрузки и скачивания в мегабайтах в секунду (МБ\/с).<\/li>\n<li>Выполняли все тесты на одной и той же машине, используя стандартный Docker-контейнер для каждой системы хранения, без внешних томов, монтирования или кешей.<\/li>\n<\/ol>\n<p>Все решения тестировались в одноузловой конфигурации для обеспечения согласованности. Хотя некоторые системы (например, `Ceph`) спроектированы для лучшей производительности в кластерной среде, мы использовали одинаковые условия для всех решений, чтобы гарантировать справедливое сравнение.<\/p>\n<h3>Заключительные мысли<\/h3>\n<p>Эти результаты показывают, как каждое решение вело себя в нашей конкретной тестовой среде с одним узлом. Их следует рассматривать как относительное сравнение соотношений производительности, а не как абсолютные жесткие значения, которые будут применимы в любой конфигурации.<\/p>\n<p>При выборе подходящего решения для хранения данных учитывайте типичные размеры файлов, которые вы будете хранить, поскольку одни системы лучше справляются с маленькими файлами, а другие преуспевают с большими. Также подумайте об основных возможностях, которые вам требуются, таких как масштабируемость, репликация, долговечность или встроенный графический интерфейс. Наконец, помните, что производительность может сильно отличаться между одноузловыми и многоузловыми кластерами.<\/p>\n<p>Наши тесты предоставляют базовый уровень для понимания того, как эти системы соотносятся в идентичных условиях, но ваша реальная производительность будет зависеть от вашего конкретного оборудования, рабочей нагрузки и конфигурации.<\/p>\n",
            "date_published": "2025-12-08T00:50:17+03:00",
            "date_modified": "2025-12-08T00:50:03+03:00",
            "tags": [
                "big data",
                "Data Engineer",
                "Dev",
                "k3s",
                "Programming",
                "s3"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.42.26.png",
            "_date_published_rfc2822": "Mon, 08 Dec 2025 00:50:17 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "299",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.42.26.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.42.57.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.43.18.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.43.34.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.44.02.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.44.22.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.45.06.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.45.23.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.45.45.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.46.02.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-12-08-v-00.46.17.png"
                ]
            }
        },
        {
            "id": "296",
            "url": "https:\/\/gavrilov.info\/all\/prosto-duckdb-nu-krasota-zhe\/",
            "title": "Просто duckdb 🦆 ну красота же 😍",
            "content_html": "<pre class=\"e2-text-code\"><code class=\"\">echo &quot;cnt\\n1\\n2\\n3&quot; | duckdb -c &quot;SELECT count(distinct cnt) FROM read_csv('\/dev\/stdin')&quot;\n┌─────────────────────┐\n│ count(DISTINCT cnt) │\n│        int64        │\n├─────────────────────┤\n│          3          │\n└─────────────────────┘\n\necho &quot;cnt\\n1\\n2\\n3&quot; | duckdb -c &quot;SELECT sum(cnt) FROM read_csv('\/dev\/stdin')&quot; \n┌──────────┐\n│ sum(cnt) │\n│  int128  │\n├──────────┤\n│    6     │\n└──────────┘<\/code><\/pre><p>А тут еще много всякой дополнительно утиной косметики <a href=\"https:\/\/query.farm\/duckdb_extensions.html\">https:\/\/query.farm\/duckdb_extensions.html<\/a><\/p>\n",
            "date_published": "2025-11-24T21:21:17+03:00",
            "date_modified": "2025-11-24T21:22:12+03:00",
            "tags": [
                "Data",
                "Data Engineer"
            ],
            "_date_published_rfc2822": "Mon, 24 Nov 2025 21:21:17 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "296",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        },
        {
            "id": "295",
            "url": "https:\/\/gavrilov.info\/all\/obrabotka-logov-trino-iz-kafka-s-pomoschyu-vector-dlya-udaleniya\/",
            "title": "Обработка логов Trino из Kafka с помощью Vector для удаления полей",
            "content_html": "<p>В современных архитектурах данных, построенных на Kafka, часто возникает задача обработки или фильтрации потока событий “на лету”. Один из распространенных кейсов — удаление чувствительной информации из логов перед их передачей в следующую систему (например, в SIEM или систему долгосрочного хранения).<\/p>\n<p>Kafka: <a href=\"https:\/\/hub.docker.com\/r\/apache\/kafka\">https:\/\/hub.docker.com\/r\/apache\/kafka<\/a><br \/>\nVector: <a href=\"https:\/\/vector.dev\/docs\">https:\/\/vector.dev\/docs<\/a><\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-11-21-v-00.28.37.png\" width=\"1662\" height=\"720\" alt=\"\" \/>\n<\/div>\n<p>Рассмотрим реальный пример:<\/p>\n<ul>\n<li>Кластер <b>Trino<\/b> (или Presto) пишет подробные логи о каждом выполненном запросе в топик Kafka.<\/li>\n<li>Эти логи содержат как полезные метаданные (пользователь, время, объем данных), так и полную <b>текстовую версию самого SQL-запроса<\/b> в поле, например, `query`.<\/li>\n<li>Задача<b>: Переложить эти логи в другой топик Kafka, но уже <\/b>без** поля `query`, чтобы система-подписчик не имела доступа к потенциально конфиденциальной информации в текстах запросов.<\/li>\n<\/ul>\n<p>Для решения этой задачи мы воспользуемся <b>Vector<\/b> — легковесным и сверхбыстрым инструментом для обработки данных.<\/p>\n<h4>План действий<\/h4>\n<ol start=\"1\">\n<li>Создадим два топика в Kafka: `trino-logs-raw` (для сырых логов) и `trino-logs-cleaned` (для очищенных).<\/li>\n<li>Настроим Vector для чтения из первого топика, удаления поля `query` и всех служебных метаданных.<\/li>\n<li>Настроим Vector на запись результата во второй топик.<\/li>\n<li>Запустим всю цепочку в Docker и протестируем.<\/li>\n<\/ol>\n<h4>Шаг 1: Подготовка Kafka<\/h4>\n<p>Предполагается, что у вас уже запущен Kafka-брокер в Docker. На основе нашего примера, у вас есть контейнер с именем `broker1`, который является частью Docker-сети `minimal_iceberg_net`.<\/p>\n<p>Откройте терминал и подключитесь к контейнеру Kafka, чтобы создать топики:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">Создадим сеть \n\ndocker network create my_net \n\nЗапускаем брокер broker:\n\ndocker run -d \\\n  --name broker3 \\\n  --network=my_net \\\n  -p 8893:9092 \\\n  -e KAFKA_NODE_ID=3 \\\n  -e KAFKA_PROCESS_ROLES='broker,controller' \\\n  -e KAFKA_CONTROLLER_QUORUM_VOTERS='3@broker3:9093' \\\n  -e KAFKA_LISTENERS='INTERNAL:\/\/0.0.0.0:29092,EXTERNAL:\/\/0.0.0.0:9092,CONTROLLER:\/\/broker3:9093' \\\n  -e KAFKA_ADVERTISED_LISTENERS='INTERNAL:\/\/broker3:29092,EXTERNAL:\/\/localhost:8893' \\\n  -e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP='INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT' \\\n  -e KAFKA_INTER_BROKER_LISTENER_NAME='INTERNAL' \\\n  -e KAFKA_CONTROLLER_LISTENER_NAMES='CONTROLLER' \\\n  -e KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1 \\\n  -e KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1 \\\n  -e KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1 \\\n  apache\/kafka:latest\n\n\ndocker exec --workdir \/opt\/kafka\/bin\/ -it broker3 sh<\/code><\/pre><p>Теперь, находясь внутри контейнера, выполните команды:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\"># Создаем &quot;сырой&quot; топик для входящих логов Trino\n.\/kafka-topics.sh --create --topic trino-logs-raw --bootstrap-server localhost:29092 --partitions 1 --replication-factor 1\n\n# Создаем &quot;чистый&quot; топик для обработанных логов\n.\/kafka-topics.sh --create --topic trino-logs-cleaned --bootstrap-server localhost:29092 --partitions 1 --replication-factor 1<\/code><\/pre><p>*Обратите внимание: я использую внутренний порт брокера `29092`, который узнали ранее.*<\/p>\n<p>Выйдите из контейнера командой `exit`.<\/p>\n<h4>Шаг 2: Конфигурация Vector<\/h4>\n<p>На вашей локальной машине создайте структуру папок:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">vector-trino-processor\/\n└── config\/\n    └── vector.toml<\/code><\/pre><p>Поместите в файл `vector.toml` следующую конфигурацию. Это сердце нашего решения.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\"># vector-trino-processor\/config\/vector.toml\n\n# ==================================\n#          ИСТОЧНИК ДАННЫХ\n# ==================================\n# Читаем сырые логи из Kafka\n[sources.trino_raw_logs]\n  type = &quot;kafka&quot;\n  # Подключаемся к брокеру по имени контейнера и внутреннему порту\n  bootstrap_servers = &quot;broker3:29092&quot;\n  # Указываем, какой топик слушать\n  topics = [&quot;trino-logs-raw&quot;]\n  group_id = &quot;vector-trino-cleaner&quot;\n  # Vector автоматически распарсит входящие сообщения как JSON\n  decoding.codec = &quot;json&quot;\n\n# ==================================\n#             ТРАНСФОРМАЦИЯ\n# ==================================\n# Удаляем поле `query` и служебные метаданные Vector\n[transforms.clean_trino_log]\n  type = &quot;remap&quot;\n  # Получаем данные от нашего источника\n  inputs = [&quot;trino_raw_logs&quot;]\n  # Скрипт на языке Vector Remap Language (VRL)\n  source = '''\n  # 1. Удаляем чувствительное поле &quot;query&quot; из лога.\n  del(.query)\n\n  # 2. Удаляем все служебные поля, которые Vector добавляет\n  #    при чтении из Kafka, чтобы на выходе был чистый JSON.\n  del(.headers)\n  del(.message_key)\n  del(.offset)\n  del(.partition)\n  del(.source_type)\n  del(.timestamp)\n  del(.topic)\n  '''\n\n# ==================================\n#           ПРИЕМНИК ДАННЫХ\n# ==================================\n# Пишем очищенные логи в новый топик Kafka\n[sinks.trino_cleaned_logs]\n  type = &quot;kafka&quot;\n  # Принимаем на вход данные, прошедшие трансформацию\n  inputs = [&quot;clean_trino_log&quot;]\n  bootstrap_servers = &quot;broker3:29092&quot;\n  # Указываем топик для записи\n  topic = &quot;trino-logs-cleaned&quot;\n  # Кодируем итоговое событие обратно в JSON\n  encoding.codec = &quot;json&quot;<\/code><\/pre><h4>Шаг 3: Запуск и Тестирование<\/h4>\n<p>Нам понадобится три терминала.<\/p>\n<p><b>В Терминале №1 — Запустим Vector<\/b><\/p>\n<p>Перейдите в папку `vector-trino-processor` и выполните команду:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">docker run \\\n  -d \\\n  --name vector-processor \\\n  -v &quot;$(pwd)\/config:\/etc\/vector\/&quot; \\\n  --network=my_net \\\n  --rm \\\n  timberio\/vector:latest-alpine --config \/etc\/vector\/vector.toml<\/code><\/pre><p>Эта команда:<\/p>\n<ul>\n<li>Запускает контейнер Vector в фоновом режиме (`-d`).<\/li>\n<li>Дает ему имя `vector-processor`.<\/li>\n<li>Монтирует ваш локальный конфиг (`-v`).<\/li>\n<li>Подключает его к той же сети, что и Kafka (`--network`).<\/li>\n<li>Явно указывает, какой файл конфигурации использовать (`--config`).<\/li>\n<\/ul>\n<p><b>В Терминале №2 — Симулируем отправку лога Trino<\/b><\/p>\n<p>Запустим интерактивный Kafka-продюсер.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">docker exec --workdir \/opt\/kafka\/bin -it broker3 .\/kafka-console-producer.sh --topic trino-logs-raw --bootstrap-server localhost:29092<\/code><\/pre><p>Теперь вставьте в этот терминал JSON, имитирующий лог от Trino, и нажмите Enter:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{&quot;user&quot;:&quot;yuriy&quot;,&quot;source&quot;:&quot;trino-cli&quot;,&quot;queryId&quot;:&quot;20231120_123456_00001_abcde&quot;,&quot;query&quot;:&quot;SELECT * FROM sensitive_table a JOIN other_table b ON a.id = b.id WHERE a.credit_card = '1234-5678-9012-3456'&quot;,&quot;state&quot;:&quot;FINISHED&quot;}<\/code><\/pre><p><b>В Терминале №3 — Проверяем результат<\/b><\/p>\n<p>Запустим Kafka-консьюмер, который будет слушать <b>очищенный<\/b> топик `trino-logs-cleaned`.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">docker exec --workdir \/opt\/kafka\/bin -it broker3 .\/kafka-console-consumer.sh --topic trino-logs-cleaned --bootstrap-server localhost:29092 --from-beginning<\/code><\/pre><p>Вы практически мгновенно увидите результат работы Vector — тот же самый лог, но уже <b>без поля `query`<\/b>:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">{&quot;user&quot;:&quot;yuriy&quot;,&quot;source&quot;:&quot;trino-cli&quot;,&quot;queryId&quot;:&quot;20231120_123456_00001_abcde&quot;,&quot;state&quot;:&quot;FINISHED&quot;}<\/code><\/pre><p>Мы построили простой, но мощный конвейер для обработки данных в режиме реального времени, решив поставленную задачу с минимальными усилиями.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-11-21-v-01.25.17.png\" width=\"1652\" height=\"470\" alt=\"\" \/>\n<\/div>\n",
            "date_published": "2025-11-21T01:27:16+03:00",
            "date_modified": "2025-11-21T01:27:11+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer",
                "Security"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-11-21-v-00.28.37.png",
            "_date_published_rfc2822": "Fri, 21 Nov 2025 01:27:16 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "295",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-11-21-v-00.28.37.png",
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-11-21-v-01.25.17.png"
                ]
            }
        },
        {
            "id": "292",
            "url": "https:\/\/gavrilov.info\/all\/dbt-otkryvaet-ishodny-kod-metricflow-upravlyaemye-metriki-dlya-a\/",
            "title": "dbt открывает исходный код MetricFlow: Управляемые метрики для AI и аналитики",
            "content_html": "<p>Компания dbt Labs объявила о важном изменении в своей стратегии: `MetricFlow`, ключевая технология, лежащая в основе `dbt Semantic Layer`, становится полностью открытой. Проект переводится под лицензию Apache 2.0, что позволяет любому использовать, изменять и встраивать его в свои продукты. Это стратегический шаг, направленный на создание единого отраслевого стандарта для определения бизнес-метрик, особенно в свете бурного развития AI-систем.<\/p>\n<p>Оригинал тут: <a href=\"https:\/\/www.getdbt.com\/blog\/open-source-metricflow-governed-metrics\">https:\/\/www.getdbt.com\/blog\/open-source-metricflow-governed-metrics<\/a><br \/>\nА гит тут: <a href=\"https:\/\/github.com\/dbt-labs\/metricflow\">https:\/\/github.com\/dbt-labs\/metricflow<\/a><\/p>\n<p>Еще кстати есть <a href=\"https:\/\/github.com\/memiiso\/opendbt\">https:\/\/github.com\/memiiso\/opendbt<\/a> ( Make dbt great again! :) Может они сольются с метриками, интересно.<\/p>\n<h3>Проблема: почему семантический слой стал критически важен<\/h3>\n<p>Концепция семантического слоя, который служит промежуточным слоем для определения бизнес-логики (метрик, измерений, связей), не нова. Она уже много лет используется в BI-системах для обеспечения согласованности отчетов. Однако с появлением больших языковых моделей (LLM) и инструментов в стиле “Chat with your data” проблема вышла на новый уровень.<\/p>\n<p>Когда AI-агент или LLM пытается ответить на вопрос, обращаясь напрямую к базе данных, он вынужден самостоятельно генерировать SQL-запрос. При этом модель “угадывает”, какие таблицы нужно соединить (`JOIN`), как правильно отфильтровать данные, какую использовать гранулярность по времени и какие оконные функции применить.<\/p>\n<p><b>Проблемы такого подхода:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Несогласованность:<\/b> Две разные модели (или даже одна и та же, но с другим запросом) могут сгенерировать разный SQL для расчета, казалось бы, одной и той же метрики. Это приводит к разным цифрам в отчетах.<\/li>\n<li><b>Ошибки:<\/b> LLM может не знать о тонкостях бизнес-логики, например, о том, что при расчете выручки нужно учитывать возвраты или использовать специальный финансовый календарь.<\/li>\n<li><b>Потеря доверия:<\/b> Когда пользователи получают противоречивые или неверные данные, доверие ко всей системе аналитики быстро падает.<\/li>\n<\/ol>\n<blockquote>\n<p>Метрики не должны быть вероятностными, зависящими от “догадок” LLM при каждом вызове. <b>Они должны быть детерминированными.<\/b><\/p>\n<\/blockquote>\n<p>`MetricFlow` решает именно эту задачу.<\/p>\n<h3>Что такое MetricFlow и как он работает<\/h3>\n<p>`MetricFlow` — это движок, который преобразует семантические определения бизнес-понятий в готовый к выполнению и оптимизированный SQL-код. Аналитик один раз определяет метрику “Валовая маржа” на языке `MetricFlow`, и после этого любая система (BI-инструмент, AI-агент, Python-скрипт) может запросить эту метрику по имени, будучи уверенной, что получит корректный и одинаковый результат.<\/p>\n<h3>Ключевые изменения и их значение<\/h3>\n<ol start=\"1\">\n<li><b>Лицензия Apache 2.0:<\/b> Это одно из главных нововведений. Apache 2.0 — это разрешительная лицензия, которая позволяет другим компаниям свободно встраивать `MetricFlow` в свои коммерческие и открытые продукты. Это снимает барьеры для принятия технологии и способствует ее распространению как стандарта.<\/li>\n<li><b>Сотрудничество с Open Semantic Interchange (OSI):<\/b> dbt Labs будет развивать `MetricFlow` совместно с такими партнерами, как Snowflake и Salesforce, в рамках инициативы OSI. Цель — создать единый стандарт для семантической совместимости между разными платформами, чтобы метрики, определенные один раз, одинаково работали во всех инструментах.<\/li>\n<\/ol>\n<h3>Как MetricFlow обеспечивает надежность AI<\/h3>\n<p>`MetricFlow` предоставляет открытый стандарт для метаданных и расширяемый движок, который превращает намерение (“покажи валовую маржу”) в SQL-запрос для хранилища данных.<\/p>\n<p><b>Пример работы:<\/b><\/p>\n<p>Предположим, пользователь задает AI-агенту вопрос:<\/p>\n<blockquote>\n<p>“Покажи валовую маржу (%) по месяцам за прошлый квартал для Северной Америки (за вычетом скидок и возвратов, по финансовому календарю).”<\/p>\n<\/blockquote>\n<p>Без семантического слоя LLM пришлось бы конструировать сложный запрос с нуля. С `MetricFlow` процесс выглядит так:<\/p>\n<ol start=\"1\">\n<li>Агент распознает намерение и запрашивает у `MetricFlow` метрику `gross_margin_pct` с нужными измерениями (`region`, `fiscal_month`) и фильтрами.<\/li>\n<li>`MetricFlow`, на основе заранее созданных определений, строит план запроса:\n<ul>\n  <li>Находит нужные таблицы: `orders`, `discounts`, `returns`, `cogs` (себестоимость).<\/li>\n  <li>Применяет правильные `JOIN` между ними.<\/li>\n  <li>Применяет фильтр по региону (`North America`).<\/li>\n  <li>Группирует данные по месяцам финансового, а не календарного, года.<\/li>\n  <li>Рассчитывает числитель (выручка) и знаменатель (себестоимость) с учетом того, что популяция данных для них должна быть одинаковой.<\/li>\n  <li>Вычисляет итоговое соотношение.<\/li>\n<\/ul>\n<\/li>\n<li>`MetricFlow` компилирует этот план в оптимизированный SQL-запрос, специфичный для диалекта конкретного хранилища (Snowflake, BigQuery, Databricks и т.д.).<\/li>\n<li>Запрос выполняется в хранилище, и результат возвращается пользователю.<\/li>\n<\/ol>\n<p>При этом весь сгенерированный SQL доступен для проверки, что обеспечивает <b>прозрачность и объяснимость<\/b> вычислений.<\/p>\n<h3>Основные возможности движка:<\/h3>\n<ul>\n<li><b>Единое определение, выполнение где угодно:<\/b> Метрики и измерения определяются один раз, а `MetricFlow` компилирует их в SQL для разных диалектов.<\/li>\n<li><b>Оптимизация производительности:<\/b> Движок строит эффективные запросы, чтобы избежать лишних сканирований и снизить нагрузку на хранилище данных.<\/li>\n<li><b>Поддержка сложных вычислений:<\/b> `MetricFlow` из коробки обрабатывает сложные соединения, оконные функции, расчеты по когортам и полуаддитивные метрики (например, остатки на счетах, которые нельзя просто суммировать по времени).<\/li>\n<\/ul>\n<h3>`MetricFlow` vs. `dbt Semantic Layer`<\/h3>\n<p>Важно понимать различие между двумя компонентами:<\/p>\n<ul>\n<li><b>`MetricFlow`<\/b> — это движок с открытым исходным кодом для <b>определения и вычисления<\/b> метрик. Это “сердце” системы, которое выполняет всю сложную работу по генерации SQL.<\/li>\n<li><b>`dbt Semantic Layer`<\/b> — это коммерческий продукт dbt Labs, построенный *поверх* `MetricFlow`. Он добавляет функциональность корпоративного уровня:\n<ul>\n  <li>Управление доступом (`RBAC`).<\/li>\n  <li>Версионирование определений метрик.<\/li>\n  <li>Аудит и отслеживание происхождения данных (`lineage`).<\/li>\n  <li>Надежные API и коннекторы для интеграции с BI- и AI-инструментами.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Таким образом, `MetricFlow` становится общедоступным строительным блоком, а `dbt Semantic Layer` — готовым решением для его безопасного и управляемого внедрения в компаниях.<\/p>\n<h3>Итог<\/h3>\n<ol start=\"1\">\n<li><b>dbt Labs сделала `MetricFlow` (движок для расчета метрик) полностью открытым под лицензией Apache 2.0.<\/b> Это позволяет всем желающим использовать его без ограничений.<\/li>\n<li><b>Главная цель — создать открытый стандарт для определения бизнес-метрик.<\/b> Это особенно актуально для AI-систем, которые часто ошибаются при самостоятельной генерации SQL.<\/li>\n<li><b>`MetricFlow` позволяет AI и BI-инструментам запрашивать данные по имени метрики (например, `revenue`), получая детерминированный и корректный SQL-запрос.<\/b> Это повышает надежность и согласованность данных.<\/li>\n<li><b>Этот шаг способствует совместимости инструментов (`interoperability`) и снижает зависимость от конкретного вендора (`vendor lock-in`).<\/b> Метрики, определенные один раз, будут работать одинаково в разных системах.<\/li>\n<li><b>Коммерческий продукт `dbt Semantic Layer` продолжит развиваться<\/b> как решение для управления жизненным циклом метрик в корпоративной среде (безопасность, контроль версий, аудит).<\/li>\n<\/ol>\n",
            "date_published": "2025-11-01T01:03:55+03:00",
            "date_modified": "2025-11-01T01:04:30+03:00",
            "tags": [
                "Data",
                "Data Engineer",
                "dbt",
                "ETL"
            ],
            "_date_published_rfc2822": "Sat, 01 Nov 2025 01:03:55 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "292",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "291",
            "url": "https:\/\/gavrilov.info\/all\/sravnenie-apache-iceberg-delta-lake-i-apache-hudi-glubokiy-anali\/",
            "title": "Сравнение Apache Iceberg, Delta Lake и Apache Hudi: Глубокий анализ (2025)",
            "content_html": "<p>С ростом популярности архитектуры <b>Data Lakehouse<\/b> усилился интерес к трём основным открытым проектам в этой области: <b>Apache Hudi<\/b>, <b>Delta Lake<\/b> и <b>Apache Iceberg<\/b>. Все три технологии продолжают активно развиваться, и в этой статье представлено актуальное сравнение их возможностей по состоянию на октябрь 2025 года.<\/p>\n<p>Оригинал тут: <a href=\"https:\/\/www.onehouse.ai\/blog\/apache-hudi-vs-delta-lake-vs-apache-iceberg-lakehouse-feature-comparison\">https:\/\/www.onehouse.ai\/blog\/apache-hudi-vs-delta-lake-vs-apache-iceberg-lakehouse-feature-comparison<\/a><\/p>\n<blockquote>\n<p><b>Примечание:<\/b> Если выбор формата вызывает сложности, обратите внимание на проект <b>Apache XTable<\/b> (Incubating), который обеспечивает интероперабельность между Hudi, Delta и Iceberg, позволяя использовать несколько форматов одновременно.<\/p>\n<\/blockquote>\n<h3>Сравнение возможностей<\/h3>\n<h4>Функциональность записи<\/h4>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td>Функция<\/td>\n<td>Apache Hudi (v1.0.2)<\/td>\n<td>Delta Lake (v4.0.0)<\/td>\n<td>Apache Iceberg (v1.10.0)<\/td>\n<\/tr>\n<tr>\n<td><b>ACID-транзакции<\/b><\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<\/tr>\n<tr>\n<td><b>Copy-on-Write<\/b><\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<\/tr>\n<tr>\n<td><b>Merge-on-Read<\/b><\/td>\n<td>✅ Полнофункциональный<\/td>\n<td>❌ Векторы удалений (эксперимент.)<\/td>\n<td>❌ Векторы удалений (огранич.)<\/td>\n<\/tr>\n<tr>\n<td><b>Эффективная bulk-загрузка<\/b><\/td>\n<td>✅ Bulk_Insert<\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<\/tr>\n<tr>\n<td><b>Индексирование<\/b><\/td>\n<td>✅ 8+ типов индексов<\/td>\n<td style=\"text-align: left\">❌ Bloom-фильтр проприетарный<\/td>\n<td>✅ Метаданные для статистики<\/td>\n<\/tr>\n<tr>\n<td><b>Частичные обновления<\/b><\/td>\n<td>✅ Partial Updates<\/td>\n<td>❌<\/td>\n<td>❌<\/td>\n<\/tr>\n<tr>\n<td><b>Миграция таблиц<\/b><\/td>\n<td>✅ Bootstrap<\/td>\n<td>✅ Convert to Delta<\/td>\n<td>❌<\/td>\n<\/tr>\n<tr>\n<td><b>Управление конкуренцией<\/b><\/td>\n<td>✅ OCC, MVCC, NBCC<\/td>\n<td>✅ OCC<\/td>\n<td>✅ OCC<\/td>\n<\/tr>\n<tr>\n<td><b>Неблокирующая конкуренция<\/b><\/td>\n<td>✅ NBCC<\/td>\n<td>❌ OCC с перезапуском<\/td>\n<td>❌ OCC с перезапуском<\/td>\n<\/tr>\n<tr>\n<td><b>Менеджеры блокировок<\/b><\/td>\n<td>✅ ФС, DynamoDB, Hive, Zookeeper<\/td>\n<td>✅ Только внешний DynamoDB<\/td>\n<td>✅ Каталог или внешние провайдеры<\/td>\n<\/tr>\n<tr>\n<td><b>Дедупликация<\/b><\/td>\n<td>✅ Ключи, Precombine<\/td>\n<td>❌ Нет первичных ключей<\/td>\n<td>❌ Нет первичных ключей<\/td>\n<\/tr>\n<tr>\n<td><b>Зависимость от каталога<\/b><\/td>\n<td>❌ Не требуется<\/td>\n<td>❌ Не требуется<\/td>\n<td>✅ Обязателен<\/td>\n<\/tr>\n<\/table>\n<p><b>Ключевые отличия:<\/b><\/p>\n<ul>\n<li><b>Hudi<\/b> предлагает наиболее продвинутые механизмы управления конкуренцией, включая неблокирующий контроль (NBCC)<\/li>\n<li>Только <b>Hudi<\/b> поддерживает настоящий Merge-on-Read без компромиссов производительности<\/li>\n<li><b>Hudi<\/b> предоставляет встроенные инструменты для дедупликации через первичные ключи<\/li>\n<\/ul>\n<h3>Метаданные таблиц<\/h3>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td>Функция<\/td>\n<td>Apache Hudi<\/td>\n<td>Delta Lake<\/td>\n<td>Apache Iceberg<\/td>\n<\/tr>\n<tr>\n<td><b>Масштабируемость метаданных<\/b><\/td>\n<td>✅ LSM-дерево + HFile (100x ускорение)<\/td>\n<td>❌ Parquet чекпойнты (медленно)<\/td>\n<td>❌ Avro манифесты (медленно)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: left\"><b>Управление индексами<\/b><\/td>\n<td>✅ Асинхронное многомодальное<\/td>\n<td>❌<\/td>\n<td>❌<\/td>\n<\/tr>\n<tr>\n<td><b>Эволюция схемы<\/b><\/td>\n<td>✅ Добавление, переупоряд., удаление<\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<\/tr>\n<tr>\n<td><b>Эволюция партиций<\/b><\/td>\n<td>✅ Кластеризация + индексы выражений<\/td>\n<td style=\"text-align: left\">✅ Эволюция партиций<\/td>\n<td style=\"text-align: center\">❌<\/td>\n<\/tr>\n<tr>\n<td><b>Первичные ключи<\/b><\/td>\n<td>✅<\/td>\n<td style=\"text-align: right\">❌ Только в проприетарной версии<\/td>\n<td>❌<\/td>\n<\/tr>\n<tr>\n<td><b>Статистика столбцов<\/b><\/td>\n<td>✅ HFile (до 50x ускорение)<\/td>\n<td>✅ Parquet чекпойнт<\/td>\n<td>✅ Avro манифест<\/td>\n<\/tr>\n<\/table>\n<p><b>Важные особенности:<\/b><\/p>\n<ul>\n<li><b>Hudi<\/b> использует оптимизированный формат HFile для метаданных, что значительно ускоряет поиск<\/li>\n<li>Только <b>Hudi<\/b> поддерживает настоящие первичные ключи как в реляционных БД<\/li>\n<li><b>Hudi<\/b> предлагает более гибкий подход к партиционированию через кластеризацию<\/li>\n<\/ul>\n<h3>Функциональность чтения<\/h3>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td>Функция<\/td>\n<td>Apache Hudi<\/td>\n<td>Delta Lake<\/td>\n<td>Apache Iceberg<\/td>\n<\/tr>\n<tr>\n<td><b>Time Travel<\/b><\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<\/tr>\n<tr>\n<td><b>Merge-on-Read запросы<\/b><\/td>\n<td>✅ Snapshot Query<\/td>\n<td>❌ Сложная поддержка<\/td>\n<td>✅ Все запросы мержат векторы удалений<\/td>\n<\/tr>\n<tr>\n<td><b>Инкрементальные запросы<\/b><\/td>\n<td>✅ + CDC запросы<\/td>\n<td>✅ CDF (эксперимент.)<\/td>\n<td>❌ Только аппенды<\/td>\n<\/tr>\n<tr>\n<td><b>CDC запросы<\/b><\/td>\n<td>✅ + before\/after images<\/td>\n<td>❌<\/td>\n<td>❌<\/td>\n<\/tr>\n<tr>\n<td><b>Вторичные индексы<\/b><\/td>\n<td>✅<\/td>\n<td>❌<\/td>\n<td>❌<\/td>\n<\/tr>\n<tr>\n<td><b>Предикаты для пропуска данных<\/b><\/td>\n<td>✅ Индексы выражений<\/td>\n<td>✅ Логические предикаты<\/td>\n<td>✅ Трансформации таблиц<\/td>\n<\/tr>\n<\/table>\n<h3>Сервисы таблиц<\/h3>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td>Функция<\/td>\n<td>Apache Hudi<\/td>\n<td>Delta Lake<\/td>\n<td>Apache Iceberg<\/td>\n<\/tr>\n<tr>\n<td><b>Авторазмер файлов<\/b><\/td>\n<td>✅<\/td>\n<td>✅<\/td>\n<td>❌ Ручное управление<\/td>\n<\/tr>\n<tr>\n<td><b>Компактизация<\/b><\/td>\n<td>✅ Управляемая<\/td>\n<td>❌ 2-этапное обслуживание<\/td>\n<td>❌ Ручное обслуживание<\/td>\n<\/tr>\n<tr>\n<td><b>Очистка<\/b><\/td>\n<td>✅ Управляемая<\/td>\n<td>❌ VACUUM вручную<\/td>\n<td>❌ Ручное удаление снапшотов<\/td>\n<\/tr>\n<tr>\n<td><b>Кластеризация<\/b><\/td>\n<td>✅ Авто + Z-order\/Hilbert<\/td>\n<td>❌ Z-order в OSS, авто – проприетар.<\/td>\n<td>❌ Z-order вручную<\/td>\n<\/tr>\n<\/table>\n<h3>Поддержка экосистемы<\/h3>\n<p>Все три формата имеют широкую поддержку в экосистеме данных:<\/p>\n<ul>\n<li><b>Apache Spark, Flink, Trino, DBT<\/b> – полная поддержка чтения\/записи во всех форматах<\/li>\n<li><b>Kafka Connect<\/b> – Hudi и Iceberg имеют нативную поддержку, Delta – только проприетарную<\/li>\n<li><b>Облачные платформы<\/b> (AWS, GCP, Azure) – все три формата поддерживаются с некоторыми ограничениями<\/li>\n<li><b>Snowflake<\/b> – нативная поддержка Iceberg, Hudi через XTable<\/li>\n<\/ul>\n<h3>Производительность: TPC-DS бенчмарки<\/h3>\n<p>Согласно независимым тестам:<\/p>\n<ul>\n<li><b>Hudi<\/b> и <b>Delta<\/b> показывают сопоставимую производительность<\/li>\n<li><b>Iceberg<\/b> consistently отстаёт по скорости выполнения запросов<\/li>\n<\/ul>\n<blockquote>\n<p><b>Важно:<\/b> При сравнении производительности учитывайте, что Hudi по умолчанию оптимизирован для mutable-нагрузок (upsert), в то время как Delta и Iceberg – для append-only. Для честного сравнения используйте `bulk-insert` режим в Hudi.<\/p>\n<\/blockquote>\n<h3>Ключевые дифференцирующие возможности<\/h3>\n<h3>Инкрементальные пайплайн<\/h3>\n<p><b>Hudi<\/b> предлагает наиболее зрелую поддержку инкрементальной обработки с трекингом всех изменений (вставки, обновления, удаления) и предоставлением их в виде change streams. Это позволяет строить эффективные ETL-пайплайны без перевычисления полных наборов данных.<\/p>\n<h3>Управление конкуренцией<\/h3>\n<p>В то время как все три системы поддерживают оптимистический контроль конкуренции (OCC), только <b>Hudi<\/b> предлагает:<\/p>\n<ul>\n<li>Неблокирующий контроль конкуренции (NBCC)<\/li>\n<li>Файл-уровневую гранулярность блокировок<\/li>\n<li>Возможность работы с асинхронными сервисами таблиц без остановки записи<\/li>\n<\/ul>\n<h3>Merge-on-Read<\/h3>\n<p>Только <b>Hudi<\/b> предоставляет полнофункциональный Merge-on-Read, который позволяет:<\/p>\n<ul>\n<li>Балансировать между производительностью записи и чтения<\/li>\n<li>Использовать row-ориентированные форматы для стриминга и column-ориентированные для аналитики<\/li>\n<li>Выполнять компактизацию асинхронно<\/li>\n<\/ul>\n<h3>Кластеризация vs Эволюция партиций<\/h3>\n<ul>\n<li><b>Iceberg<\/b>: Partition Evolution – изменение схемы партиционирования для новых данных<\/li>\n<li><b>Hudi<\/b>: Гибридный подход – coarse-grained партиционирование + fine-grained кластеризация с возможностью эволюции без перезаписи данных<\/li>\n<\/ul>\n<h3>Многомодальное индексирование<\/h3>\n<p>Только <b>Hudi<\/b> предлагает асинхронную подсистему индексирования, поддерживающую:<\/p>\n<ul>\n<li>Bloom, hash, bitmap, R-tree индексы<\/li>\n<li>10-100x ускорение point lookup запросов<\/li>\n<li>10-30x общее ускорение запросов в реальных нагрузках<\/li>\n<\/ul>\n<h3>Реальные кейсы использования<\/h3>\n<h3>Peloton<\/h3>\n<ul>\n<li>Увеличение частоты ингестии с 1 раза в день до каждых 10 минут<\/li>\n<li>Снижение времени выполнения снапшот-заданий с 1 часа до 15 минут<\/li>\n<li>Экономия затрат через оптимизацию использования EMR-кластеров<\/li>\n<\/ul>\n<h3>ByteDance\/TikTok<\/h3>\n<ul>\n<li>Обработка таблиц объемом 400+ PB<\/li>\n<li>Ежедневный прирост данных на уровне PB<\/li>\n<li>Пропускная способность >100 GB\/s на таблицу<\/li>\n<li>Выбор Hudi из-за открытости экосистемы и поддержки глобальных индексов<\/li>\n<\/ul>\n<h3>Walmart<\/h3>\n<ul>\n<li>Использование Merge-on-Read для снижения задержек<\/li>\n<li>Нативная поддержка удалений для GDPR\/CCPA compliance<\/li>\n<li>Row versioning для обработки out-of-order данных<\/li>\n<\/ul>\n<h3>Инновации сообщества<\/h3>\n<p>Многие ключевые функции data lakehouse были впервые реализованы в Hudi:<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td>Инновация Hudi<\/td>\n<td>Год<\/td>\n<td>Аналог в других проектах<\/td>\n<\/tr>\n<tr>\n<td>Транзакционные обновления<\/td>\n<td>2017<\/td>\n<td>Delta OSS (2019)<\/td>\n<\/tr>\n<tr>\n<td>Merge-on-Read<\/td>\n<td>2017<\/td>\n<td>Iceberg (2021)<\/td>\n<\/tr>\n<tr>\n<td>Инкрементальные запросы<\/td>\n<td>2017<\/td>\n<td>Delta Change Feed (2022)<\/td>\n<\/tr>\n<tr>\n<td>Z-order\/Hilbert кривые<\/td>\n<td>2021<\/td>\n<td>Delta OSS (2022)<\/td>\n<\/tr>\n<tr>\n<td>Многомодальное индексирование<\/td>\n<td>2022<\/td>\n<td>❌ Нет аналогов<\/td>\n<\/tr>\n<tr>\n<td>Контроль конкуренции без блокировок<\/td>\n<td>2024<\/td>\n<td>❌ Нет аналогов<\/td>\n<\/tr>\n<\/table>\n<h3>Заключение<\/h3>\n<h3>Критерии выбора<\/h3>\n<p><b>Выбирайте Apache Hudi если:<\/b><\/p>\n<ul>\n<li>Ваши workload’ы содержат значительное количество обновлений и удалений<\/li>\n<li>Требуется низкая задержка от конца в конец<\/li>\n<li>Нужны продвинутые возможности управления конкуренцией<\/li>\n<li>Важна производительность point lookup запросов<\/li>\n<li>Требуется гибкое управление layout данных через кластеризацию<\/li>\n<\/ul>\n<p><b>Рассмотрите Delta Lake если:<\/b><\/p>\n<ul>\n<li>Вы используете экосистему Databricks<\/li>\n<li>Workload’ы преимущественно append-only<\/li>\n<li>Достаточно базовых возможностей управления конкуренцией<\/li>\n<\/ul>\n<p><b>Apache Iceberg может подойти если:<\/b><\/p>\n<ul>\n<li>Основная задача – работа с очень большими объемами данных в cloud storage<\/li>\n<li>Требуется скрытое партиционирование с эволюцией<\/li>\n<li>Workload’ы в основном аналитические с минимальными обновлениями<\/li>\n<\/ul>\n<h3>Итоговые рекомендации<\/h3>\n<ol start=\"1\">\n<li><b>Для зрелых production-нагрузок<\/b> с frequent updates, high concurrency и low latency требованиями <b>Apache Hudi<\/b> предлагает наиболее полный набор возможностей.<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Не ограничивайтесь сравнением “галочек”<\/b> – оценивайте производительность на своих данных и workload’ах.<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>Рассмотрите Apache XTable<\/b> если невозможно определиться с одним форматом или требуется интероперабельность между системами.<\/li>\n<\/ol>\n<ol start=\"4\">\n<li><b>Учитывайте roadmap проекта<\/b> – Hudi продолжает лидировать в инновациях, что может быть важно для долгосрочных инвестиций.<\/li>\n<\/ol>\n<p>Технологии data lakehouse продолжают быстро развиваться, и выбор должен основываться на конкретных требованиях ваших use cases, а не только на текущем состоянии функциональности.<\/p>\n",
            "date_published": "2025-11-01T00:53:55+03:00",
            "date_modified": "2025-11-01T00:53:37+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer",
                "Iceberg"
            ],
            "_date_published_rfc2822": "Sat, 01 Nov 2025 00:53:55 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "291",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        },
        {
            "id": "279",
            "url": "https:\/\/gavrilov.info\/all\/iskusstvo-skorosti-rukovodstvo-po-optimizacii-dlya-analitiki-v-d\/",
            "title": "Искусство скорости: Руководство по оптимизации для аналитики в Data Lakehouse с DuckDB",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-09-09-v-01.35.35.png\" width=\"1174\" height=\"108\" alt=\"\" \/>\n<\/div>\n<p>DuckDB завоевал огромную популярность как “SQLite для аналитики”. Это невероятно быстрый, встраиваемый, колоночный движок, который не требует отдельного сервера. Однако его мощь по-настоящему раскрывается, когда он получает доступ к данным эффективно. Просто натравить DuckDB на петабайтный дата-лейк без подготовки — это рецепт для медленных запросов и высоких затрат.<\/p>\n<p>Как же построить мост между огромным хранилищем данных и молниеносной интерактивной аналитикой, которую обещает DuckDB?<\/p>\n<div class=\"e2-text-video\">\n<video src=\"https:\/\/gavrilov.info\/video\/-5436782684090959843.mp4#t=0.001\" width=\"640\" height=\"480\" controls alt=\"\" \/>\n\n<\/div>\n<p>В этой статье рассмотрим три фундаментальных архитектурных подхода к организации доступа к данным для DuckDB. Но прежде чем мы погрузимся в то, как *читать* данные, давайте поговорим о том, как их *готовить*.<\/p>\n<h4>Большая картина: Подготовка данных с помощью Trino<\/h4>\n<p>Данные в вашем Lakehouse не появляются из ниоткуда. Они поступают из операционных баз данных, потоков событий (Kafka), логов и десятков других источников. Прежде чем DuckDB сможет их эффективно запросить, эти данные нужно собрать, очистить, трансформировать и, что самое важное, организовать в надежный и производительный формат.<\/p>\n<p>Здесь на сцену выходит <b>Trino<\/b> (ранее известный как PrestoSQL).<\/p>\n<p><b>Что такое Trino?<\/b> Это мощный распределенный SQL-движок, созданный для выполнения запросов к гетерогенным источникам данных. Его суперсила — способность “на лету” объединять данные из PostgreSQL, Kafka, Hive, MySQL и многих других систем.<\/p>\n<p><b>Роль Trino в Lakehouse:<\/b> В современной архитектуре Trino часто выступает в роли “фабрики данных”. Он выполняет тяжелую работу по <b>ETL\/ELT<\/b> (Extract, Transform, Load), подготавливая данные для аналитических инструментов вроде DuckDB.<\/p>\n<p><b>Типичный сценарий использования:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Источники:<\/b> У вас есть события о прослушивании треков в Kafka, а информация о пользователях — в базе данных PostgreSQL.<\/li>\n<li><b>Задача:<\/b> Создать единую, денормализованную таблицу Iceberg для аналитики.<\/li>\n<li><b>Решение с Trino:<\/b> Вы настраиваете в Trino коннекторы к Kafka и PostgreSQL. Затем вы запускаете периодический SQL-запрос, который читает данные из обоих источников, объединяет их и записывает результат в новую или существующую таблицу Iceberg.<\/li>\n<\/ol>\n<pre class=\"e2-text-code\"><code class=\"\">-- Этот запрос выполняется в Trino, а не в DuckDB!\n    INSERT INTO iceberg_catalog.analytics.daily_user_activity\n    SELECT\n        u.user_id,\n        u.country,\n        e.event_timestamp,\n        e.track_id,\n        e.duration_ms\n    FROM\n        postgres_catalog.public.users u\n    JOIN\n        kafka_catalog.raw_data.listen_events e ON u.user_id = e.user_id\n    WHERE\n        e.event_date = CURRENT_DATE;<\/code><\/pre><p>Как отмечается в одном из руководств, именно такой `INSERT INTO ... SELECT ...` является типичным способом перемещения данных в Iceberg с помощью Trino.<\/p>\n<p><b>Итог:<\/b> Trino работает “глубоко в машинном отделении” вашего Lakehouse. Он берет на себя тяжелые, распределенные задачи по преобразованию данных, а DuckDB получает на вход уже чистые, структурированные и оптимизированные для чтения таблицы Iceberg.<\/p>\n<p>Теперь, когда данные готовы, давайте рассмотрим, как их лучше всего потреблять.<\/p>\n<h4>Подход 1: Табличные форматы (Iceberg) — Читайте только то, что нужно<\/h4>\n<p>Это самый продвинутый и рекомендуемый подход для серьезной аналитики, особенно в serverless-архитектуре.<\/p>\n<ul>\n<li><b>Как это работает:<\/b> Вместо того чтобы работать с “россыпью” файлов Parquet, вы работаете с логической таблицей, управляемой Apache Iceberg. Расширение `iceberg` в DuckDB использует метаданные Iceberg для интеллектуального отсечения ненужных файлов (partition pruning) и блоков данных (predicate pushdown), читая с диска минимально необходимый объем информации.<\/li>\n<li><b>Архитектура:<\/b> `Данные на S3 -> Trino (ETL) -> Таблица Iceberg -> DuckDB (Аналитика)`<\/li>\n<\/ul>\n<h5>Назначение и сценарии использования:<\/h5>\n<ul>\n<li><b>Serverless-аналитика:<\/b> Основной кейс. AWS Lambda или Google Cloud Function, оснащенная DuckDB, выполняет SQL-запрос к озеру данных. Благодаря Iceberg, функция читает всего несколько мегабайт вместо гигабайт, что делает ее выполнение быстрым (<1 сек) и дешевым.<\/li>\n<li><b>Локальная разработка и BI:<\/b> Аналитик данных или инженер открывает Jupyter Notebook на своем ноутбуке. С помощью DuckDB он подключается напрямую к производственному Lakehouse и выполняет исследовательский анализ, не создавая копий данных и не перегружая кластеры.<\/li>\n<li><b>Встраиваемая аналитика:<\/b> Backend-сервис на Python или Node.js, которому нужно быстро отвечать на аналитические вопросы (например, “показать статистику пользователя за последний месяц”). Он использует DuckDB для прямого запроса к Lakehouse без обращения к промежуточной базе данных.<\/li>\n<\/ul>\n<h4>Подход 2: RPC-стриминг (Apache Arrow Flight) — Прямой канал к данным<\/h4>\n<p>Иногда вам не нужна вся мощь Iceberg, а нужно просто эффективно выполнить запрос на удаленном экземпляре DuckDB и получить результат.<\/p>\n<ul>\n<li><b>Как это работает:<\/b> Вы запускаете сервер, который инкапсулирует DuckDB. Клиент и сервер общаются по протоколу Arrow Flight — высокопроизводительному фреймворку для стриминга колоночных данных в формате Apache Arrow без затрат на сериализацию.<\/li>\n<li><b>Архитектура:<\/b> `Клиент -> Arrow Flight RPC -> Сервер с DuckDB -> Данные (любой источник)`<\/li>\n<\/ul>\n<h5>Назначение и сценарии использования:<\/h5>\n<ul>\n<li><b>Интерактивные дашборды:<\/b> Веб-интерфейс (React, Vue) должен строить графики в реальном времени. Он отправляет SQL-запросы на Flight-сервер и получает данные для отрисовки практически мгновенно, без “тяжести” HTTP\/JSON.<\/li>\n<li><b>API-шлюз для данных:<\/b> Централизация доступа к данным для множества внутренних микросервисов. Вместо того чтобы каждый сервис имел свои креды и логику подключения к БД, они обращаются к единому, стабильному Flight API.<\/li>\n<li><b>Кросс-языковое взаимодействие:<\/b> Сервис на Java должен получить результаты вычислений из BI-системы, построенной на Python и DuckDB. Arrow Flight обеспечивает эффективный и стандартизированный мост между ними.<\/li>\n<\/ul>\n<h4>Подход 3: “API поверх данных” (ROAPI & DataFusion) — Декларативная альтернатива<\/h4>\n<p>Что, если вам не нужна вся гибкость SQL, а нужен стандартный REST или GraphQL API поверх ваших данных без строчки кода? Здесь на сцену выходит <b>ROAPI<\/b>.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-212.png\" width=\"1612\" height=\"1435\" alt=\"\" \/>\n<\/div>\n<ul>\n<li><b>Как это работает:<\/b> ROAPI — это инструмент, который автоматически создает API, читая конфигурационный YAML-файл, где вы описываете ваши данные (Parquet, CSV и т.д.). Под капотом он использует Apache Arrow DataFusion, движок запросов, написанный на Rust, являющийся идейным братом DuckDB.<\/li>\n<li><b>Архитектура:<\/b> `Клиент (HTTP\/GraphQL) -> ROAPI Server -> Данные (файлы)`<\/li>\n<\/ul>\n<h5>Назначение и сценарии использования:<\/h5>\n<ul>\n<li><b>Быстрое прототипирование:<\/b> Вам нужно за 5 минут предоставить команде фронтенда API для нового набора данных. Вы пишете 10 строк в YAML, запускаете ROAPI — и API готов.<\/li>\n<li><b>Простые микросервисы данных:<\/b> Сервис, единственная задача которого — раздавать данные из файла с поддержкой фильтрации и пагинации. ROAPI делает это из коробки, избавляя вас от написания рутинного кода на FastAPI или Express.js.<\/li>\n<li><b>Дата-фиды для внешних систем:<\/b> Предоставление стандартизированного API для партнерской системы, которая умеет работать с REST, но не умеет читать Parquet.<\/li>\n<\/ul>\n<h4>и еще немного про DuckDB<\/h4>\n<h5>1. Читайте меньше данных (Золотое правило)<\/h5>\n<ul>\n<li><b>Используйте Iceberg:<\/b> Это лучший способ.<\/li>\n<li><b>Проекция колонок (`SELECT col1, col2...`):<\/b> <b>Никогда не используйте `SELECT *`<\/b>.<\/li>\n<li><b>Проталкивание предикатов (`WHERE`):<\/b> Пишите максимально конкретные фильтры. DuckDB автоматически проталкивает их в сканеры Parquet и Iceberg. Используйте `EXPLAIN` для проверки того, что фильтры применяются на этапе сканирования.<\/li>\n<\/ul>\n<h5>2. Оптимизация SQL-запросов<\/h5>\n<ul>\n<li><b>Материализация промежуточных результатов:<\/b> Если вы делаете несколько агрегаций над одним и тем же отфильтрованным срезом, сохраните его во временную таблицу с помощью `CREATE TEMP TABLE ... AS`.<\/li>\n<li><b>Используйте `COPY` для массовой загрузки:<\/b> При загрузке данных в DuckDB `COPY` на порядки быстрее, чем `INSERT`.<\/li>\n<li><b>Предварительная агрегация:<\/b> Для сверхбольших данных создавайте “витрины” с помощью Trino (см. выше) или DuckDB, а запросы стройте уже по ним.<\/li>\n<\/ul>\n<h5>3. Настройка окружения DuckDB<\/h5>\n<ul>\n<li><b>Управление памятью:<\/b> `SET memory_limit = ‘1GB’;` — обязательная настройка в Lambda и контейнерах.<\/li>\n<li><b>Параллелизм:<\/b> `SET threads = 4;` — адаптируйте количество потоков под vCPU вашего окружения.<\/li>\n<li><b>Настройка `httpfs` для S3:<\/b> Настройте регион (`s3_region`), креды и включите кэширование метаданных, чтобы не перечитывать их при каждом запуске. ( Это комьюнити дополнение -<a href=\"https:\/\/duckdb.org\/community_extensions\/extensions\/cache_httpfs.html\">cache_httpfs<\/a>, см. ниже “Проблема Шторм” )<\/li>\n<\/ul>\n<p>Еще вот тут можно почитать: <a href=\"https:\/\/duckdb.org\/docs\/stable\/guides\/performance\/how_to_tune_workloads\">https:\/\/duckdb.org\/docs\/stable\/guides\/performance\/how_to_tune_workloads<\/a><\/p>\n<h4>Заключение: Какой подход выбрать?<\/h4>\n<p>Выбор архитектуры зависит от вашей задачи. Каждая из них занимает свою нишу в стеке современной инженерии данных.<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">Подход<\/td>\n<td style=\"text-align: center\">Ключевая технология<\/td>\n<td style=\"text-align: center\">Когда использовать<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Табличный формат<\/b><\/td>\n<td style=\"text-align: center\"><b>Trino (Подготовка) + DuckDB\/Iceberg (Потребление)<\/b><\/td>\n<td style=\"text-align: center\"><b>Стандарт для Lakehouse.<\/b> Нужна строгая структура, надежность и максимальная производительность для аналитических SQL-запросов от различных инструментов.<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>RPC-стриминг<\/b><\/td>\n<td style=\"text-align: center\"><b>DuckDB + Arrow Flight<\/b><\/td>\n<td style=\"text-align: center\">Нужен <b>быстрый интерактивный SQL-доступ<\/b> к удаленному экземпляру DuckDB, например, для дашборда или кастомного клиента.<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>API поверх данных<\/b><\/td>\n<td style=\"text-align: center\"><b>ROAPI + DataFusion<\/b><\/td>\n<td style=\"text-align: center\">Нужно <b>быстро и без кода<\/b> поднять стандартный `REST`\/`GraphQL` API поверх наборов данных для прототипирования или простых микросервисов.<\/td>\n<\/tr>\n<\/table>\n<hr \/>\n<h4>Проблема Шторм из GET-запросов к S3<\/h4>\n<p>Давайте представим, что вы выполняете запрос к таблице Iceberg или просто к набору из 1000 файлов Parquet на S3:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">SELECT count(*)\nFROM read_parquet('s3:\/\/my-bucket\/data\/*.parquet')\nWHERE event_type = 'click';<\/code><\/pre><p>Чтобы выполнить этот запрос с максимальной эффективностью (с “проталкиванием предиката”), DuckDB должен сделать следующее, *прежде чем* читать основные данные:<\/p>\n<ol start=\"1\">\n<li>Получить список всех 1000 файлов.<\/li>\n<li>Для <b>каждого<\/b> из 1000 файлов прочитать его <b>метаданные (футер)<\/b>. Футер Parquet-файла — это небольшой блок в конце файла, содержащий схему и, что самое важное, статистику по колонкам (min\/max значения).<\/li>\n<li>Проанализировав футер, DuckDB понимает, может ли в этом файле вообще содержаться `event_type = ‘click’`. Если статистика говорит, что в файле есть только типы `’view’` и `’purchase’`, утка его пропустит.<\/li>\n<\/ol>\n<p>Проблема в том, что для чтения футера каждого файла DuckDB должен отправить отдельный HTTP `GET` запрос с указанием диапазона байт (range request) к S3. То есть, один SQL-запрос порождает <b>1000+ мелких HTTP-запросов<\/b>. Это может быть медленно и может быть дорого, так как в S3 вы платите за каждый `GET` запрос.<\/p>\n<p><b>Кэширование метаданных решает именно эту проблему:<\/b> оно сохраняет результаты этих мелких запросов на локальный диск, чтобы при повторном обращении к тем же файлам DuckDB брал их из локального кэша, а не летел снова в S3.<\/p>\n<h4>Решение: Комьюнити-расширение `cache_httpfs`<\/h4>\n<p>Для реализации постоянного, дискового кэширования в DuckDB используется специальное комьюнити-расширение `cache_httpfs`. Оно работает как “обертка” над стандартным `httpfs`.<\/p>\n<p><b>Основная идея:<\/b> Вы говорите DuckDB использовать `cache_httpfs` в качестве клиента для HTTP-запросов. Этот клиент сначала проверяет, нет ли уже нужного блока данных (например, футера Parquet-файла) в локальном кэше. Если есть — отдает его мгновенно. Если нет — идет в S3, скачивает блок, сохраняет его в кэш и отдает DuckDB.<\/p>\n<p>Вот как это настроить:<\/p>\n<h5>Шаг 1: Установка и загрузка расширений<\/h5>\n<p>Вам понадобятся три расширения: `httpfs` (для работы с S3), `cache_httpfs` (для кэширования) и, если вы работаете с Iceberg, то и `iceberg`.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">INSTALL httpfs;\nINSTALL cache_httpfs;\nLOAD httpfs;\nLOAD cache_httpfs;<\/code><\/pre><h5>Шаг 2: Активация кэширующего клиента<\/h5>\n<p>Это ключевой шаг. Вы должны указать DuckDB использовать `cache_httpfs` для всех HTTP-операций.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">SET httpfs_client = 'cached_httpfs';<\/code><\/pre><h5>Шаг 3: Настройка пути к кэшу (критически важно для Serverless)<\/h5>\n<p>По умолчанию `cache_httpfs` сохраняет кэш в директорию `~\/.cache\/duckdb\/`. Это хорошо работает на локальной машине, но в serverless-окружениях (AWS Lambda, Cloud Functions) эта папка либо недоступна для записи, либо является эфемерной.<\/p>\n<p>В serverless-среде единственное гарантированно доступное для записи место — это директория `\/tmp`.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">SET cache_httpfs_cache_path = '\/tmp\/duckdb_cache';<\/code><\/pre><p>Этот кэш в `\/tmp` будет “жить” между “теплыми” вызовами вашей Lambda-функции. Если одна и та же функция вызывается несколько раз подряд, второй и последующие вызовы будут использовать уже заполненный кэш, что кардинально ускорит выполнение запросов к одним и тем же данным.<\/p>\n<h4>Полный пример конфигурации (Python)<\/h4>\n<pre class=\"e2-text-code\"><code class=\"\">import duckdb\n\n# Подключаемся к базе данных\ncon = duckdb.connect()\n\n# Устанавливаем и загружаем расширения\ncon.execute(&quot;INSTALL httpfs;&quot;)\ncon.execute(&quot;INSTALL cache_httpfs;&quot;)\ncon.execute(&quot;LOAD httpfs;&quot;)\ncon.execute(&quot;LOAD cache_httpfs;&quot;)\n\n# --- Настройка S3 и кэша ---\n\n# 1. Настройте креды для S3 (если не используются IAM-роли)\n# con.execute(&quot;SET s3_access_key_id='YOUR_KEY';&quot;)\n# con.execute(&quot;SET s3_secret_access_key='YOUR_SECRET';&quot;)\ncon.execute(&quot;SET s3_region='us-east-1';&quot;)\n\n# 2. Активируем кэширующий http-клиент\ncon.execute(&quot;SET httpfs_client = 'cached_httpfs';&quot;)\n\n# 3. Указываем путь к директории кэша (обязательно для serverless)\ncon.execute(&quot;SET cache_httpfs_cache_path = '\/tmp\/duckdb_http_cache';&quot;)\n\n# --- Выполняем запрос ---\n\n# Первый запуск этого запроса будет медленнее,\n# так как он заполнит кэш метаданными файлов.\nresult1 = con.execute(&quot;SELECT count(*) FROM 's3:\/\/my-bucket\/data\/*.parquet'&quot;).fetchone()\nprint(f&quot;Первый запуск: {result1[0]}&quot;)\n\n# Второй запуск будет на порядки быстрее,\n# так как все метаданные будут прочитаны из локального кэша в \/tmp.\nresult2 = con.execute(&quot;SELECT count(*) FROM 's3:\/\/my-bucket\/data\/*.parquet'&quot;).fetchone()\nprint(f&quot;Второй запуск (с кэшем): {result2[0]}&quot;)<\/code><\/pre><h4>Сравнение: Встроенный кэш vs `cache_httpfs`<\/h4>\n<p>Стоит отметить, что стандартный `httpfs` тоже имеет небольшой *внутренний, оперативный кэш*, но его возможности ограничены.<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">Параметр<\/td>\n<td style=\"text-align: center\">Встроенный кэш `httpfs`<\/td>\n<td style=\"text-align: center\">Расширение `cache_httpfs`<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Тип<\/b><\/td>\n<td style=\"text-align: center\">Внутренний, в памяти<\/td>\n<td style=\"text-align: center\">Явный, на диске<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Жизненный цикл<\/b><\/td>\n<td style=\"text-align: center\">Живет <b>в рамках одного соединения<\/b> (connection). При переподключении кэш пуст.<\/td>\n<td style=\"text-align: center\">Живет <b>между сессиями и процессами<\/b>. Сохраняется на диске до очистки.<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Назначение<\/b><\/td>\n<td style=\"text-align: center\">Ускорение повторных запросов в одной и той же длительной сессии.<\/td>\n<td style=\"text-align: center\"><b>Радикальное ускорение<\/b> для любых повторных запросов, особенно в serverless (warm starts) и при локальной разработке.<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Активация<\/b><\/td>\n<td style=\"text-align: center\">Включен по умолчанию<\/td>\n<td style=\"text-align: center\">Требует `SET httpfs_client = ‘cached_httpfs’;`<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Настройка<\/b><\/td>\n<td style=\"text-align: center\">Не настраивается<\/td>\n<td style=\"text-align: center\">Настраивается путь (`cache_httpfs_cache_path`) и максимальный размер.<\/td>\n<\/tr>\n<\/table>\n<p>Для серьезной работы с данными на S3, особенно в serverless-архитектуре, использование расширения `cache_httpfs` является приятным дополнением и зачастую обязательным. Это та самая “серебряная пуля”, которая убирает узкое место в виде задержек сети и большого количества API-вызовов к облачному хранилищу.<\/p>\n<p>Начиная с тяжелых ETL-процессов на Trino и заканчивая быстрыми запросами в DuckDB, современный стек данных предлагает невероятную гибкость и производительность. Выбрав правильный инструмент или их комбинацию для каждой задачи, можно построить по-настоящему эффективную и масштабируемую аналитическую платформу.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/duck.png\" width=\"198\" height=\"149\" alt=\"\" \/>\n<div class=\"e2-text-caption\">pic. Krenskiy Dmitriy<\/div>\n<\/div>\n",
            "date_published": "2025-09-09T01:48:12+03:00",
            "date_modified": "2025-09-09T09:14:51+03:00",
            "tags": [
                "big data",
                "Data Engineer",
                "Data Visualization",
                "datafusion",
                "Programming"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-09-09-v-01.35.35.png",
            "_date_published_rfc2822": "Tue, 09 Sep 2025 01:48:12 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "279",
            "_rss_enclosures": [
                {
                    "url": "https:\/\/gavrilov.info\/video\/-5436782684090959843.mp4",
                    "type": "video\/mp4",
                    "length": 2206521
                }
            ],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-09-09-v-01.35.35.png",
                    "https:\/\/gavrilov.info\/pictures\/image-212.png",
                    "https:\/\/gavrilov.info\/pictures\/duck.png"
                ]
            }
        },
        {
            "id": "272",
            "url": "https:\/\/gavrilov.info\/all\/novaya-era-transformacii-dannyh-dbt-protiv-bruin-i-aac\/",
            "title": "Новая эра трансформации данных: dbt против Bruin и aaC",
            "content_html": "<p>В мире данных произошла тихая, но фундаментальная революция. На смену традиционному подходу <b>ETL (Extract, Transform, Load)<\/b>, где данные преобразовывались до загрузки в хранилище, пришла новая парадигма — <b>ELT (Extract, Load, Transform)<\/b>. Благодаря мощности современных облачных хранилищ (таких как Snowflake, BigQuery, Databricks, Starburst\\Trino) стало выгоднее сначала загружать сырые данные, а уже затем трансформировать их непосредственно в хранилище.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/demo.gif\" width=\"1200\" height=\"900\" alt=\"\" \/>\n<\/div>\n<p>Этот сдвиг породил потребность в инструментах, которые специализируются на последнем шаге — трансформации (T). Именно в этой нише dbt (data build tool) стал безоговорочным лидером, но на его поле появляются и новые сильные игроки, такие как Bruin. Давайте разберемся, что это за инструменты, какой подход они олицетворяют и в чем их ключевые различия.<\/p>\n<h4>Подход «Аналитика как код»<\/h4>\n<p>И dbt, и Bruin являются яркими представителями движения <b>“Analytics as Code”<\/b> (аналитика как код). Это не просто инструменты, а целая философия, которая переносит лучшие практики разработки программного обеспечения в мир аналитики данных.<\/p>\n<p><b>Основные принципы и идеи:<\/b><\/p>\n<ol start=\"1\">\n<li><b>Версионирование:<\/b> Все трансформации данных описываются в виде кода (в основном SQL), который хранится в системе контроля версий, такой как Git. Это позволяет отслеживать изменения, совместно работать и откатываться к предыдущим версиям.<\/li>\n<li><b>Модульность и переиспользование (DRY – Don’t Repeat Yourself):<\/b> Сложные трансформации разбиваются на небольшие, логически завершенные модели, которые могут ссылаться друг на друга. Это делает код чище, понятнее и позволяет повторно использовать уже написанную логику.<\/li>\n<li><b>Тестирование:<\/b> Код трансформаций должен быть протестирован. Инструменты позволяют автоматически проверять качество данных после преобразований: на уникальность ключей, отсутствие `NULL` значений, соответствие заданным условиям и т.д.<\/li>\n<li><b>Документация и прозрачность:<\/b> Процесс трансформации становится самодокументируемым. Инструменты могут автоматически генерировать документацию и строить графы зависимостей моделей (data lineage), показывая, как данные текут и преобразуются от источника к конечному виду. <a href=\"https:\/\/www.element61.be\/en\/resource\/when-use-dbt-or-delta-live-tables\">element61.be<\/a><\/li>\n<li><b>CI\/CD (Continuous Integration \/ Continuous Deployment):<\/b> Изменения в коде трансформаций могут автоматически тестироваться и разворачиваться в продуктивную среду, что значительно ускоряет циклы разработки.<\/li>\n<\/ol>\n<p><b>Решаемые проблемы:<\/b><\/p>\n<ul>\n<li><b>“Черные ящики” ETL:<\/b> Заменяют сложные, трудноподдерживаемые и непрозрачные ETL-процессы на понятный и документированный код.<\/li>\n<li><b>Рассинхронизация команд:<\/b> Стирают границы между инженерами данных и аналитиками, позволяя аналитикам, владеющим SQL, самостоятельно создавать надежные модели данных.<\/li>\n<li><b>Низкое качество данных:<\/b> Встроенные механизмы тестирования помогают обеспечить надежность и согласованность данных.<\/li>\n<\/ul>\n<p>---<\/p>\n<h4>dbt (data build tool): Золотой стандарт трансформации<\/h4>\n<p><b>dbt<\/b> — это инструмент с открытым исходным кодом, который позволяет аналитикам и инженерам трансформировать данные в их хранилищах с помощью простых SQL-запросов. Важно понимать, что dbt <b>не извлекает и не загружает данные<\/b>. Он специализируется исключительно на шаге <b>“T”<\/b> в ELT  <a href=\"https:\/\/vutr.substack.com\/p\/why-is-dbt-so-popular\">vutr.substack.com<\/a>. <a href=\"https:\/\/github.com\/dbt-labs\/dbt-core\">dbt git<\/a>.<\/p>\n<p>Он работает как компилятор и исполнитель: вы пишете модели данных в `.sql` файлах, используя шаблонизатор Jinja для добавления логики (циклы, условия, макросы). Затем dbt компилирует этот код в чистый SQL и выполняет его в вашем хранилище данных <a href=\"https:\/\/www.element61.be\/en\/resource\/when-use-dbt-or-delta-live-tables\">element61.be<\/a>.<\/p>\n<h5>Плюсы dbt<\/h5>\n<ul>\n<li><b>Огромное сообщество и экосистема:<\/b> dbt стал де-факто стандартом индустрии. Существует огромное количество статей, курсов, готовых пакетов (библиотек) и экспертов.<\/li>\n<li><b>Фокус на SQL:<\/b> Низкий порог входа для аналитиков, которые уже знают SQL. Это демократизирует процесс трансформации данных.<\/li>\n<li><b>Мощное тестирование и документирование:<\/b> Встроенные команды для тестирования данных и автоматической генерации проектной документации с графом зависимостей.<\/li>\n<li><b>Зрелость и надежность:<\/b> Инструмент проверен временем и используется тысячами компаний по всему миру.<\/li>\n<li><b>Гибкость:<\/b> Благодаря шаблонизатору Jinja можно создавать очень сложные и переиспользуемые макросы, адаптируя dbt под любые нужды.<\/li>\n<\/ul>\n<h5>Минусы dbt<\/h5>\n<ul>\n<li><b>Только трансформация:<\/b> dbt не занимается извлечением (E) и загрузкой (L). Для этого вам понадобятся отдельные инструменты (например, Fivetran, Airbyte), что усложняет стек технологий.<\/li>\n<li><b>Кривая обучения:<\/b> Хотя основы просты, освоение продвинутых возможностей Jinja, макросов и структуры проекта требует времени.<\/li>\n<li><b>Зависимость от Python-моделей:<\/b> Хотя недавно появилась поддержка моделей на Python, она все еще не так нативна и проста, как основной SQL-подход, и требует дополнительных настроек.<\/li>\n<\/ul>\n<p>---<\/p>\n<h4>Bruin Data: Универсальный боец<\/h4>\n<p><b>Bruin<\/b> — это более новый игрок на рынке, который позиционирует себя как инструмент для создания “end-to-end” пайплайнов данных. В отличие от dbt, он не ограничивается только трансформацией, а стремится охватить больше этапов работы с данными, включая их загрузку (ingestion) <a href=\"https:\/\/github.com\/bruin-data\/bruin\">https:\/\/github.com\/bruin-data\/bruin<\/a>.<\/p>\n<p>Bruin разделяет ту же философию “Analytics as Code”, но предлагает более интегрированный опыт, где SQL и Python являются равноправными гражданами.<\/p>\n<h5>Плюсы Bruin<\/h5>\n<ul>\n<li><b>Универсальность:<\/b> Один инструмент для определения всего пайплайна: от загрузки из источников до финальных витрин данных. Это может упростить стек технологий.<\/li>\n<li><b>Нативная поддержка SQL и Python:<\/b> Позволяет легко комбинировать задачи на разных языках в одном пайплайне без дополнительных настроек. Это идеально для задач, где чистый SQL громоздок (например, работа с API, машинное обучение).<\/li>\n<li><b>Простота конфигурации:<\/b> Зачастую требует меньше шаблонного кода (boilerplate) для определения ассетов и пайплайнов по сравнению с dbt.<\/li>\n<li><b>Встроенное качество данных:<\/b> Как и dbt, делает акцент на проверках качества на каждом шаге.<\/li>\n<\/ul>\n<h5>Минусы Bruin<\/h5>\n<ul>\n<li><b> Пока маленькое сообщество:<\/b> Как у нового инструмента, у Bruin гораздо меньше пользователей, готовых решений и обсуждений на форумах по сравнению с dbt. Найти помощь или готовый пакет для решения специфической задачи сложнее.<\/li>\n<li><b>Незрелость:<\/b> Инструмент моложе, а значит, наверное, потенциально менее стабилен и может иметь меньше интеграций по сравнению с проверенным dbt. Пока нет облачных функция за деньги. Я так думал, но все же есть <a href=\"https:\/\/getbruin.com.\">https:\/\/getbruin.com.<\/a><\/li>\n<li><b>“Мастер на все руки — эксперт ни в чем?”:<\/b> Стремление охватить все этапы (E, L, T) может означать, что в каждом отдельном компоненте Bruin может уступать лучшим в своем классе специализированным инструментам (например, Fivetran в загрузке, dbt в трансформации), но это конечно субъективно.<\/li>\n<\/ul>\n<h4>Сводное сравнение<\/h4>\n<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" class=\"e2-text-table\">\n<tr>\n<td style=\"text-align: center\">Характеристика<\/td>\n<td style=\"text-align: center\">dbt (data build tool)<\/td>\n<td style=\"text-align: center\">Bruin Data<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Основная задача<\/b><\/td>\n<td style=\"text-align: center\">Трансформация (T в ELT)<\/td>\n<td style=\"text-align: center\">Весь пайплайн (E, L, T)<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Ключевые языки<\/b><\/td>\n<td style=\"text-align: center\">SQL с шаблонизатором Jinja<\/td>\n<td style=\"text-align: center\">SQL и Python как равноправные<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Экосистема<\/b><\/td>\n<td style=\"text-align: center\">Огромная, стандарт индустрии<\/td>\n<td style=\"text-align: center\">Маленькая, развивающаяся<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Зрелость<\/b><\/td>\n<td style=\"text-align: center\">Высокая, проверен временем<\/td>\n<td style=\"text-align: center\">Низкая\/Средняя<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center\"><b>Стек инструментов<\/b><\/td>\n<td style=\"text-align: center\">Требует отдельных E\/L инструментов<\/td>\n<td style=\"text-align: center\">Стремится быть самодостаточным<\/td>\n<\/tr>\n<\/table>\n<h4>Итого<\/h4>\n<p>Выбор между dbt и Bruin — это выбор между двумя стратегиями построения современного стека данных.<\/p>\n<p><b>Выбирайте dbt, если:<\/b><\/p>\n<ul>\n<li>Вы строите гибкий стек из лучших в своем классе инструментов (“best-of-breed”): один для загрузки, другой для хранения, третий для трансформации.<\/li>\n<li>Ваша команда в основном состоит из аналитиков, сильных в SQL.<\/li>\n<li>Для вас критически важны поддержка сообщества, стабильность и наличие готовых решений.<\/li>\n<li>Вы работаете в большой организации, где принятие отраслевых стандартов является преимуществом.<\/li>\n<li>Вы готовы переехать к ним в платное облако, когда нибудь. Большая часть функционала доступна там.<\/li>\n<\/ul>\n<p><b>Выбирайте Bruin, если:<\/b><\/p>\n<ul>\n<li>Вы предпочитаете единый, интегрированный инструмент для управления всеми пайплайнами, чтобы упростить архитектуру<\/li>\n<li>Вы любите open source и End-to-end дата framework: фор data ingestion + transformations + кволити. :)<\/li>\n<li>Ваши пайплайны требуют тесной связки SQL и Python для трансформаций (например, обогащение данных через вызовы API или модели ML).<\/li>\n<li>Вы начинаете новый проект или работаете в небольшой команде и цените скорость настройки и меньшее количество движущихся частей.<\/li>\n<li>Вы Go’шник :) –  Bruin написан на Go почти на 100%.<\/li>\n<\/ul>\n<p>И dbt, и Bruin — мощные инструменты, воплощающие современные подходы к инженерии данных. dbt предлагает проверенный, сфокусированный и невероятно мощный движок для трансформаций, ставший стандартом. Bruin же предлагает более универсальный и интегрированный подход, который может быть привлекателен для команд, стремящихся к простоте и нативной поддержке Python.<\/p>\n<h4>А что такое “Аналитика как код” (Analytics as Code, AaC)?<\/h4>\n<p><b>Аналитика как код<\/b> — это подход к управлению аналитическими процессами, при котором все компоненты аналитики — от моделей данных и метрик до отчетов и правил доступа — определяются в виде кода в человекочитаемых файлах. Эти файлы затем управляются так же, как исходный код любого другого программного обеспечения: с помощью систем контроля версий, автоматизированного тестирования и развертывания <a href=\"https:\/\/medium.com\/gooddata-developers\/analytics-as-code-managing-analytics-solutions-like-any-other-software-504372ba6a61\">medium.com<\/a>.<\/p>\n<p>Самая близкая и известная аналогия — это <b>Infrastructure as Code (IaC)<\/b>. Как IaC (например, с помощью Terraform) позволил инженерам описывать серверы, сети и базы данных в коде вместо ручной настройки через веб-интерфейсы, так и AaC позволяет описывать в коде всё, что связано с данными <a href=\"https:\/\/medium.com\/@terezacihelkova\/analytics-as-code-what-is-it-and-how-does-it-help-you-93e9a3c179ee\">medium.com<\/a>.<\/p>\n<blockquote>\n<p>Идея проста и убедительна: “настройте свои системы один раз, выразите это в виде кода, а затем поместите в систему контроля версий” <a href=\"https:\/\/www.holistics.io\/blog\/analytics-as-code\/\">holistics.io<\/a>.<\/p>\n<\/blockquote>\n<h4>Проблема: Как было раньше?<\/h4>\n<p>Чтобы понять ценность AaC, нужно посмотреть на проблемы, которые он решает. В традиционном подходе аналитика часто была разрозненной и хрупкой:<\/p>\n<ul>\n<li><b>Логика в “черных ящиках”:<\/b> Сложные преобразования данных были скрыты внутри GUI-интерфейсов старых ETL-инструментов или непосредственно в настройках BI-платформы (например, Tableau, Power BI). Никто, кроме автора, не мог легко понять, как рассчитывается та или иная метрика.<\/li>\n<li><b>Разрозненные SQL-скрипты:<\/b> Аналитики хранили важные SQL-запросы на своих локальных машинах, в общих папках или на wiki-страницах. Не было единой версии правды, код дублировался и быстро устаревал.<\/li>\n<li><b>Отсутствие контроля версий:<\/b> Невозможно было отследить, кто, когда и почему изменил логику расчета ключевого показателя. Откат к предыдущей работающей версии был настоящей головной болью.<\/li>\n<li><b>“Ручное” тестирование:<\/b> Проверка качества данных после изменений была ручным, подверженным ошибкам процессом. Часто о проблемах узнавали уже от бизнес-пользователей, которые видели неверные цифры в отчетах.<\/li>\n<li><b>Рассинхронизация:<\/b> Инженеры данных готовили сырые таблицы, а аналитики строили свою логику поверх них. Любые изменения с одной стороны могли сломать всю цепочку, не будучи замеченными вовремя.<\/li>\n<\/ul>\n<p>Этот хаос приводил к главному — <b>недоверию к данным<\/b>. Никто не мог быть уверен, что цифры в дашборде верны.<\/p>\n<h4>Ключевые принципы “Аналитики как код”<\/h4>\n<p>AaC решает эти проблемы, внедряя практики из мира разработки ПО.<\/p>\n<ol start=\"1\">\n<li><b>Декларативное определение:<\/b> Все аналитические артефакты описываются в файлах.\n<ul>\n  <li>Модели данных:** `SELECT * FROM ...` в `.sql` файлах.<\/li>\n  <li>Тесты:** `not_null`, `unique` в `.yml` файлах.<\/li>\n  <li>Документация:** Описания таблиц и полей в `.yml` файлах.<\/li>\n  <li>Метрики и дашборды:** Определения в `.yml` или специализированных файлах <a href=\"https:\/\/medium.com\/gooddata-developers\/analytics-as-code-managing-analytics-solutions-like-any-other-software-504372ba6a61\">medium.com<\/a>.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Контроль версий (Git):<\/b> Весь код хранится в репозитории (например, на GitHub или GitLab).\n<ul>\n  <li>Прозрачность:** Каждое изменение — это `commit` с понятным описанием.<\/li>\n  <li>Совместная работа:** Аналитики работают в отдельных ветках, а изменения вносятся через `Pull Request` (или `Merge Request`), что позволяет проводить ревью кода (code review).<\/li>\n  <li>Восстанавливаемость:** Если что-то пошло не так, можно легко откатиться к предыдущей версии.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"3\">\n<li><b>Автоматизированное тестирование:<\/b> Тесты являются неотъемлемой частью кода. Они запускаются автоматически при каждом изменении, чтобы гарантировать, что данные по-прежнему соответствуют ожиданиям (например, `user_id` всегда уникален и не равен `NULL`).<\/li>\n<\/ol>\n<ol start=\"4\">\n<li><b>CI\/CD (Непрерывная интеграция и развертывание):<\/b> Процессы полностью автоматизированы.\n<ul>\n  <li>Когда аналитик вносит изменения в `Pull Request`, автоматически запускаются тесты.<\/li>\n  <li>После одобрения и слияния ветки изменения автоматически развертываются в продуктивной среде (например, dbt Cloud или Jenkins запускает команду `dbt run`).<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"5\">\n<li><b>Модульность и переиспользование (DRY – Don’t Repeat Yourself):<\/b> Сложные потоки данных разбиваются на небольшие, логичные и переиспользуемые модели. Одна модель может ссылаться на другую, создавая четкий граф зависимостей (lineage), который можно визуализировать.<\/li>\n<\/ol>\n<h4>Преимущества подхода AaC<\/h4>\n<p>Принятие этой философии дает компании ощутимые выгоды:<\/p>\n<ul>\n<li><b>Надежность и доверие:<\/b> Благодаря автоматическому тестированию и ревью кода значительно повышается качество данных, а вместе с ним и доверие бизнеса к аналитике.<\/li>\n<li><b>Скорость и гибкость:<\/b> Аналитики могут вносить изменения гораздо быстрее. Цикл от идеи до готового отчета сокращается с недель до дней или даже часов.<\/li>\n<li><b>Масштабируемость:<\/b> Кодовая база легко поддерживается и расширяется. Новые члены команды могут быстро разобраться в проекте благодаря документации и прозрачности.<\/li>\n<li><b>Прозрачность и обнаруживаемость:<\/b> Автоматически сгенерированная документация и графы зависимостей позволяют любому сотруднику понять, откуда берутся данные и как они рассчитываются.<\/li>\n<li><b>Демократизация:<\/b> AaC дает возможность аналитикам, владеющим SQL, самостоятельно создавать надежные и протестированные модели данных, не дожидаясь инженеров данных. Это стирает барьеры между командами.<\/li>\n<\/ul>\n<p>В конечном итоге, “Аналитика как код” — это культурный сдвиг, который превращает аналитику из ремесленного занятия в зрелую инженерную дисциплину, обеспечивая скорость, надежность и масштабируемость, необходимые современному бизнесу.<\/p>\n",
            "date_published": "2025-08-23T16:04:02+03:00",
            "date_modified": "2025-08-24T11:51:29+03:00",
            "tags": [
                "BI",
                "Data Engineer",
                "Data Quality",
                "dbt",
                "ETL"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-08-23-v-15.37.26.png",
            "_date_published_rfc2822": "Sat, 23 Aug 2025 16:04:02 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "272",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/Snimok-ekrana-2025-08-23-v-15.37.26.png",
                    "https:\/\/gavrilov.info\/pictures\/demo.gif"
                ]
            }
        },
        {
            "id": "270",
            "url": "https:\/\/gavrilov.info\/all\/apache-seatunnel-dvizhenie-k-multimodalnoy-integracii-dannyh\/",
            "title": "Apache SeaTunnel – Движение к мультимодальной интеграции данных",
            "content_html": "<h4>Новое позиционирование Apache SeaTunnel. Движение к унифицированному инструменту для мультимодальной интеграции данных<\/h4>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-209.png\" width=\"1456\" height=\"840\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Оригинал тут: <a href=\"https:\/\/apacheseatunnel.substack.com\/p\/apache-seatunnel-new-positioning\">https:\/\/apacheseatunnel.substack.com\/p\/apache-seatunnel-new-positioning<\/a><\/div>\n<\/div>\n<h4>Введение<\/h4>\n<p>В постоянно меняющемся мире больших данных эффективная и надежная интеграция данных является ключевым фактором для успеха любого предприятия. Apache SeaTunnel (ранее известный как Waterdrop) зарекомендовал себя как мощный инструмент для синхронизации данных. Однако с развитием технологий и появлением новых вызовов, таких как интеграция разнородных типов данных (структурированных, полуструктурированных и неструктурированных), проект пересматривает свое позиционирование. Цель — превратиться из простого инструмента синхронизации в комплексную, унифицированную платформу для мультимодальной интеграции данных.<\/p>\n<h4>Проблемы предыдущей архитектуры<\/h4>\n<p>Изначально Apache SeaTunnel был разработан как плагин, работающий поверх вычислительных движков, таких как Apache Spark и Apache Flink. Такой подход имел свои преимущества, позволяя использовать мощность этих движков, но также порождал ряд проблем:<\/p>\n<ol start=\"1\">\n<li><b>Зависимость от сторонних движков:<\/b> Для выполнения даже самых простых задач по пересылке данных требовалось развертывание и поддержка тяжеловесных кластеров Spark или Flink. Это увеличивало накладные расходы, усложняло настройку и повышало порог входа для новых пользователей.<\/li>\n<li><b>Сложность конфигурации:<\/b> Пользователям приходилось разбираться не только в конфигурации самого SeaTunnel, но и в настройках Spark\/Flink, что часто приводило к так называемому “конфигурационному аду”.<\/li>\n<li><b>Ограничения коннекторов:<\/b> Разработка коннекторов была тесно связана с API Spark и Flink, что затрудняло создание универсальных коннекторов, работающих в обеих средах без изменений.<\/li>\n<li><b>Низкая производительность для простых задач:<\/b> Использование мощных, но громоздких движков для элементарных задач ETL (Extract, Transform, Load) было избыточным и неэффективным с точки зрения ресурсов и времени запуска.<\/li>\n<\/ol>\n<h4>Новое видение: унифицированная платформа с собственным движком<\/h4>\n<p>Чтобы решить эти проблемы и соответствовать современным требованиям, сообщество Apache SeaTunnel представило новую архитектуру, в основе которой лежит собственный вычислительный движок — <b>SeaTunnel Engine<\/b>.<\/p>\n<p>Этот стратегический шаг позволил отделить SeaTunnel от обязательной зависимости от Spark и Flink. Теперь SeaTunnel может работать в самостоятельном режиме, что обеспечивает следующие ключевые преимущества:<\/p>\n<ul>\n<li><b>Легковесность и быстрота:<\/b> `SeaTunnel Engine` специально оптимизирован для задач интеграции данных. Он запускается быстрее и потребляет значительно меньше ресурсов, чем полноценные кластеры Spark или Flink, что делает его идеальным для широкого круга задач.<\/li>\n<li><b>Унификация пакетной и потоковой обработки:<\/b> Новая архитектура изначально спроектирована для бесшовной работы как с пакетными (batch), так и с потоковыми (streaming) данными. Пользователям больше не нужно поддерживать два разных стека для разных типов задач — SeaTunnel предоставляет единый интерфейс и модель выполнения.<\/li>\n<li><b>Упрощенная разработка коннекторов:<\/b> С введением унифицированного API коннекторов (`Connector API`), разработчикам стало проще создавать новые интеграции. Коннектор, написанный для `SeaTunnel Engine`, будет работать одинаково для всех сценариев, что ускоряет расширение экосистемы.<\/li>\n<\/ul>\n<h4>Мультимодальная интеграция данных<\/h4>\n<p>Ключевой аспект нового позиционирования — это поддержка <b>мультимодальных данных<\/b>. Это означает способность работать с данными различных форматов и из различных источников в рамках единого конвейера.<\/p>\n<ol start=\"1\">\n<li><b>Структурированные данные:<\/b> Традиционная область для SeaTunnel. Поддерживается множество реляционных баз данных (MySQL, PostgreSQL), аналитических СУБД (ClickHouse, Doris) и хранилищ данных.<\/li>\n<li><b>Полуструктурированные данные:<\/b> Эффективная работа с NoSQL базами данных (MongoDB, Elasticsearch) и потоками событий (Kafka, Pulsar).<\/li>\n<li><b>Неструктурированные данные:<\/b> Расширение поддержки для озер данных (Data Lakes) и файловых систем (HDFS, S3, OSS). Это включает интеграцию с форматами вроде Apache Hudi, Iceberg и Delta Lake.<\/li>\n<\/ol>\n<p>Особое внимание уделяется критически важным функциям, таким как <b>Захват изменяемых данных (CDC)<\/b> и <b>синхронизация всей базы данных целиком<\/b>. SeaTunnel теперь может считывать журналы транзакций (например, binlog в MySQL) для захвата изменений в реальном времени и применять их к целевой системе. Функция полной синхронизации позволяет в одной задаче перенести схему и все данные из одной базы в другую, что значительно упрощает миграцию.<\/p>\n<h4>Будущее развитие<\/h4>\n<p>Дорожная карта проекта включает в себя:<\/p>\n<ul>\n<li><b>Расширение экосистемы коннекторов:<\/b> Добавление поддержки еще большего числа источников и приемников, включая современные SaaS-платформы и векторные базы данных для задач ИИ.<\/li>\n<li><b>Улучшенная поддержка озер данных:<\/b> Углубление интеграции с форматами Hudi и Iceberg, поддержка эволюции схем и транзакционных операций.<\/li>\n<li><b>Пользовательский интерфейс:<\/b> Разработка визуального интерфейса для создания и мониторинга заданий, что сделает инструмент более доступным для широкого круга пользователей.<\/li>\n<li><b>Повышение производительности и стабильности:<\/b> Непрерывная оптимизация `SeaTunnel Engine` для еще более быстрой и надежной обработки данных.<\/li>\n<\/ul>\n<h4>Заключение<\/h4>\n<p>Apache SeaTunnel совершает важный переход от зависимого инструмента к самостоятельной, легковесной и унифицированной платформе для интеграции данных. Отказ от обязательной привязки к Spark\/Flink и внедрение собственного `SeaTunnel Engine` открывают новые возможности для пользователей, которым нужно простое, но мощное решение для пакетной и потоковой обработки разнородных данных. Новое позиционирование делает SeaTunnel сильным конкурентом в мире современных ETL\/ELT инструментов.<\/p>\n<p>---<\/p>\n<h5>Выводы<\/h5>\n<p>Проанализировав направление развитие Apache SeaTunnel, можно сделать несколько ключевых выводов:<\/p>\n<ol start=\"1\">\n<li><b>Стратегическая зрелость:<\/b> Переход на собственный движок (`SeaTunnel Engine`) — это признак зрелости проекта. Команда осознала, что зависимость от универсальных, но тяжеловесных движков (Spark\/Flink) является узким местом для основного сценария использования — интеграции данных. Создание специализированного движка позволяет оптимизировать производительность и снизить накладные расходы именно для этих задач.<\/li>\n<li><b>Соответствие трендам:<\/b> Этот шаг полностью соответствует общему тренду в индустрии данных — движению от монолитных, “умеющих все” платформ к более легковесным и специализированным инструментам. Для многих задач по перемещению и простой трансформации данных запуск Spark-кластера является избыточным. SeaTunnel теперь предлагает “золотую середину”.<\/li>\n<li><b>Конкурентное позиционирование:<\/b>\n<ul>\n  <li><b>Против коммерческих SaaS ETL (Fivetran, Airbyte):<\/b> SeaTunnel является мощной open-source альтернативой. Он привлекателен для компаний, которые хотят полного контроля над своей инфраструктурой, стремятся избежать зависимости от поставщика (vendor lock-in) и имеют техническую экспертизу для самостоятельного развертывания и поддержки.<\/li>\n  <li><b>Против специализированных CDC-инструментов (Debezium):<\/b> SeaTunnel не просто захватывает изменения (CDC), а встраивает эту функциональность в полноценный конвейер интеграции. Это решение “все в одном”, которое позволяет не только извлечь данные, но и доставить их в целевую систему (например, озеро данных или хранилище) в рамках одного инструмента.<\/li>\n<\/ul>\n<\/li>\n<li><b>Фокус на “мультимодальности” — это задел на будущее.<\/b> Поддержка не только реляционных баз и Kafka, но и озер данных (Hudi, Iceberg) и, в перспективе, векторных баз, говорит о том, что проект нацелен на обслуживание современных стеков данных, включая аналитику в реальном времени и конвейеры для машинного обучения (MLOps).<\/li>\n<\/ol>\n<h5>Рекомендации<\/h5>\n<p>Исходя из этого, можно дать следующие рекомендации:<\/p>\n<ol start=\"1\">\n<li><b>Кому стоит обратить внимание на Apache SeaTunnel:<\/b>\n<ul>\n  <li><b>Командам, для которых Spark\/Flink избыточны.<\/b> Если ваша основная задача — это синхронизация данных между различными источниками (например, из MySQL в ClickHouse или из Kafka в HDFS) без сложных вычислений, `SeaTunnel Engine` может оказаться значительно более эффективным и простым в эксплуатации решением.<\/li>\n  <li><b>Компаниям, ищущим open-source замену коммерческим ETL-инструментам.<\/b> Если у вас есть экспертиза для управления Java-приложениями и вы хотите построить гибкую, масштабируемую и экономичную платформу интеграции данных, SeaTunnel — отличный кандидат.<\/li>\n  <li><b>Пользователям экосистемы Apache.<\/b> Проект тесно интегрируется с другими популярными проектами Apache (Doris, Hudi, Flink, Spark), что делает его естественным выбором для тех, кто уже использует эти технологии.<\/li>\n  <li><b>Инженерам, которым нужна унификация.<\/b> Если вы устали поддерживать отдельные скрипты или инструменты для пакетной и потоковой обработки, SeaTunnel предлагает единый подход к обоим сценариям.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<ol start=\"2\">\n<li><b>Что нужно проверить перед внедрением:<\/b>\n<ul>\n  <li><b>Экосистему коннекторов:<\/b> Самое важное — убедиться, что в SeaTunnel есть готовые, стабильные коннекторы для всех ваших источников и приемников данных. Хотя сообщество активно их добавляет, покрытие может быть не таким широким, как у коммерческих лидеров рынка.<\/li>\n  <li><b>Функциональность CDC:<\/b> Если вам нужен захват изменений в реальном времени, детально изучите поддержку вашей СУБД. Проверьте, насколько стабильно работает коннектор и какие гарантии доставки (exactly-once, at-least-once) он предоставляет.<\/li>\n  <li><b>Операционная сложность:<\/b> Несмотря на то, что SeaTunnel стал проще, это все еще open-source инструмент, требующий мониторинга, настройки и периодических обновлений. Убедитесь, что у вашей команды есть ресурсы для его поддержки.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Apache SeaTunnel трансформируется в мощный и современный инструмент, который заслуживает внимания со стороны инженеров данных. Его новое позиционирование как легковесной, унифицированной платформы делает его сильным игроком на поле интеграции данных.<\/p>\n",
            "date_published": "2025-08-17T11:32:24+03:00",
            "date_modified": "2025-08-17T11:32:43+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/image-209.png",
            "_date_published_rfc2822": "Sun, 17 Aug 2025 11:32:24 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "270",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/image-209.png"
                ]
            }
        },
        {
            "id": "268",
            "url": "https:\/\/gavrilov.info\/all\/iceberg-kafka-connect\/",
            "title": "iceberg-kafka-connect",
            "content_html": "<p>Крутой блог по всей экостистеме кафка, примеры по iceberg которые разобраны ниже<\/p>\n<p><a href=\"https:\/\/rmoff.net\/2025\/07\/04\/writing-to-apache-iceberg-on-s3-using-kafka-connect-with-glue-catalog\/\">https:\/\/rmoff.net\/2025\/07\/04\/writing-to-apache-iceberg-on-s3-using-kafka-connect-with-glue-catalog\/<\/a><\/p>\n<p>небольшой пост про CDC от автора книги гроккаем конкурентность<\/p>\n<p><a href=\"https:\/\/luminousmen.com\/post\/change-data-capture\">https:\/\/luminousmen.com\/post\/change-data-capture<\/a><\/p>\n<p>дока по iceberg-sink connector<\/p>\n<p><a href=\"https:\/\/github.com\/databricks\/iceberg-kafka-connect\">https:\/\/github.com\/databricks\/iceberg-kafka-connect<\/a><\/p>\n<p>kafka vizualizer<\/p>\n<p><a href=\"https:\/\/softwaremill.com\/kafka-visualisation\/\">https:\/\/softwaremill.com\/kafka-visualisation\/<\/a><\/p>\n<p>А тут видосик:<\/p>\n<p><video controls style=\"width: 100%; max-width: 740px; height: auto;\"><br \/>\n<source src=\"http:\/\/a.gavrilov.info\/data\/posts\/debezium+iceberg_s.mp4\" type=\"video\/mp4\"><br \/>\nВаш браузер не поддерживает видео.<br \/>\n<\/video><\/p>\n",
            "date_published": "2025-08-17T11:07:00+03:00",
            "date_modified": "2025-08-17T11:09:28+03:00",
            "tags": [
                "Data Engineer"
            ],
            "_date_published_rfc2822": "Sun, 17 Aug 2025 11:07:00 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "268",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": []
            }
        }
    ],
    "_e2_version": 4171,
    "_e2_ua_string": "Aegea 11.4 (v4171e)"
}