{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Yuriy Gavrilov: posts tagged datafusion",
    "_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\/datafusion\/",
    "feed_url": "https:\/\/gavrilov.info\/tags\/datafusion\/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": "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>Немного душноты:<\/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",
            "date_published": "2026-04-12T19:11:05+03:00",
            "date_modified": "2026-04-13T00:36:14+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"
                ]
            }
        },
        {
            "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": "195",
            "url": "https:\/\/gavrilov.info\/all\/rascvet-odnouzlovoy-obrabotki-brosaya-vyzov-podhodu-raspredelyon\/",
            "title": "Расцвет одноузловой обработки: Бросая вызов подходу – распределённое решение в первую очередь",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-140.png\" width=\"1002\" height=\"708\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Перевод: <a href=\"https:\/\/www.pracdata.io\/p\/the-rise-of-single-node-processing\">https:\/\/www.pracdata.io\/p\/the-rise-of-single-node-processing<\/a><\/div>\n<\/div>\n<p><b>Введение<\/b><\/p>\n<p>В 2024 году наблюдается растущий интерес к одноузловым системам обработки данных. Инструменты вроде DuckDB, Apache DataFusion и Polars привлекли внимание сообщества и стали невероятно популярными. Этот тренд — не просто технологический прогресс, а переосмысление подходов к аналитике данных.<\/p>\n<p>По мере отказа от парадигмы «распределённые системы прежде всего», доминировавшей в эпоху «больших данных», компании обнаруживают, что одноузловые решения часто эффективнее, экономичнее и проще в управлении, особенно при работе с данными умеренного объёма.<\/p>\n<p>Недавний пост «Почему одноузловые системы набирают обороты в обработке данных» в LinkedIn вызвал неожиданно живой отклик сообщества, что подчеркнуло возросший интерес к теме. В этой статье мы рассмотрим её подробнее.<\/p>\n<p>---<\/p>\n<p><b>Переосмысление «больших данных»<\/b><\/p>\n<p>Последнее десятилетие компании активно внедряли стратегии big data, инвестируя в распределённые системы вроде Hadoop и Spark. Однако исследования показывают, что большинству компаний «большие данные» не нужны.<\/p>\n<p>Итоги анализа:<\/p>\n<ul>\n<li><b>Jordan Tigani<\/b> (основатель Google BigQuery): медианный объём данных у активных пользователей BigQuery — менее 100 ГБ.<\/li>\n<li>Исследование 500 млн запросов Amazon Redshift:\n<ul>\n  <li>99% обрабатывали менее 10 ТБ данных;<\/li>\n  <li>90% сессий работали с менее чем 1 ТБ;<\/li>\n  <li>98% таблиц содержат меньше миллиарда строк.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><b>Вывод:<\/b> Для 90% запросов достаточно одноузловых систем вместо распределённых (Spark, Trino, Athena).<\/p>\n<p>---<\/p>\n<p><b>Паттерны рабочих нагрузок и старение данных<\/b><\/p>\n<p><b>1. Эффект старения данных<\/b><br \/>\nДоступ к данным резко сокращается со временем:<\/p>\n<ul>\n<li><b>Горячие данные<\/b> (0–48 часов): обработка ETL-пайплайнами.<\/li>\n<li><b>Тёплые данные<\/b> (2–30 дней): основа аналитических запросов.<\/li>\n<li><b>Холодные данные<\/b> (30+ дней): редко запрашиваются (сохранены для истории или аудита).<\/li>\n<\/ul>\n<p>Исследование Meta и eBay подтверждает: 95% обращений к данным происходят в первые 48 часов. В «золотой» аналитической зоне 95% запросов выполняются в течение 30 дней.<\/p>\n<p><b>2. Правило 90\/10<\/b><br \/>\n90% рабочих нагрузок приходится на 10% данных (за 30 дней). Даже при хранении данных год, аналитики в основном работают с последними 30 днями.<\/p>\n<p>---<\/p>\n<p><b>Эволюция оборудования: масштабирование вверх вместо распределения<\/b><\/p>\n<p>Рост возможностей одноузловых систем:<\/p>\n<ul>\n<li>В 2006 году (эпоха Hadoop) серверы имели 1 CPU и 2 ГБ RAM.<\/li>\n<li>Сегодня облачные инстансы (например, AWS EC2) предлагают 64+ ядер и 256+ ГБ RAM.<\/li>\n<\/ul>\n<p><b>Экономика масштабирования:<\/b><\/p>\n<ul>\n<li>Стоимость крупных инстансов (например, m5.16xlarge) сопоставима с расходами на несколько мелких узлов (например, 8 × m5.2xlarge) при одинаковой мощности.<\/li>\n<\/ul>\n<p><b>Итог:<\/b> Современные одноузловые системы справляются с задачами, которые раньше требовали распределения, но с меньшей сложностью.<\/p>\n<p>---<\/p>\n<p><b>Производительность одноузловых систем<\/b><br \/>\nDuckDB, Apache DataFusion и другие движки используют:<\/p>\n<ul>\n<li>Векторизованное выполнение запросов;<\/li>\n<li>Параллелизм;<\/li>\n<li>Оптимизацию использования памяти.<\/li>\n<\/ul>\n<p>Примеры роста скорости:<\/p>\n<ul>\n<li>Переход с Postgres на DuckDB дал ускорение в 4–200 раз (Vantage).<\/li>\n<li>DuckDB превзошёл коммерческие хранилища на TPC-DS до 300 ГБ (Fivetran).<\/li>\n<\/ul>\n<p>---<\/p>\n<p><b>Причины выбрать одноузловую обработку<\/b><\/p>\n<ol start=\"1\">\n<li><b>Простота:<\/b> Меньше сложности, чем в распределённых системах.<\/li>\n<li><b>Эффективность:<\/b> Реализация до 80% кода на C\/C++ (против 10% в распределённых движках).<\/li>\n<li><b>Совместимость:<\/b> Интеграция с облачными хранилищами, языками программирования, BI-инструментами.<\/li>\n<\/ol>\n<p>---<\/p>\n<p><b>Ограничения<\/b><\/p>\n<ul>\n<li>Не все движки эффективно используют многоядерные CPU.<\/li>\n<li>Пропускная способность RAM\/CPU может стать узким местом.<\/li>\n<li>Очень большие наборы данных (>1 ТБ) всё ещё требуют распределения.<\/li>\n<\/ul>\n<p>---<\/p>\n<p><b>Заключение<\/b><br \/>\nОдноузловая обработка — прагматичный ответ на реальные потребности бизнеса. С развитием оборудования и оптимизацией движков необходимость в распределённых системах будет снижаться.<\/p>\n<p><b>Главный вывод:<\/b> Выбирайте инструмент под конкретную задачу, а не следуйте трендам. Будущее — за балансом между мощью одноузловых систем и гибкостью распределённых решений.<\/p>\n<p>---<\/p>\n<p><b>Полный дословный перевод<\/b>:<\/p>\n<p><b>Введение<\/b><\/p>\n<p>В 2024 году наблюдался растущий интерес к фреймворкам одноузловой обработки, при этом такие инструменты, как DuckDB, Apache DataFusion и Polars, привлекли повышенное внимание и завоевали беспрецедентную популярность в сообществе специалистов по данным.<\/p>\n<p>Эта тенденция представляет собой не просто технологический прогресс — она знаменует собой фундаментальную переоценку подхода к анализу данных.<\/p>\n<p>По мере того, как мы отходим от подхода “распределённое решение в первую очередь” эпохи “больших данных”, многие предприятия обнаруживают, что решения одноузловой обработки зачастую обеспечивают более эффективный, экономичный и управляемый подход к своим аналитическим потребностям, когда размер их данных не так велик.<\/p>\n<p>Когда я недавно опубликовал небольшую заметку в LinkedIn под названием “Почему одноузловые движки набирают обороты в обработке данных”, я не ожидал, что она привлечет такое значительное внимание со стороны сообщества специалистов по данным в LinkedIn. Этот отклик подчеркнул растущий интерес отрасли к этой теме.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-141.png\" width=\"1054\" height=\"1144\" alt=\"\" \/>\n<\/div>\n<p>В этой статье я углублюсь в эту тему, изучив её более детально и предоставив дополнительные сведения.<\/p>\n<p><b>Переосмысление больших данных<\/b><\/p>\n<p>Последнее десятилетие многие предприятия изо всех сил пытались внедрить стратегии больших данных, при этом многие компании вкладывали значительные средства в фреймворки распределенной обработки, такие как Hadoop и Spark.<\/p>\n<p>Однако недавние анализы выявили удивительную правду: у большинства компаний на самом деле нет “больших данных”.<\/p>\n<p>Значительному большинству компаний не требуются крупные платформы данных для удовлетворения своих потребностей в анализе данных. Часто эти компании поддаются маркетинговой шумихе и делают значительные инвестиции в эти платформы, которые могут неэффективно решать их фактические проблемы с данными.<\/p>\n<p>Джордан Тигани, один из инженеров-основателей Google BigQuery, проанализировал шаблоны использования и обнаружил, что медианный размер хранилища данных среди активных пользователей BigQuery составляет менее 100 ГБ.<\/p>\n<p>Ещё более показательным является анализ полумиллиарда запросов, выполненных в Amazon Redshift и опубликованных в статье:<\/p>\n<ul>\n<li>Более 99% запросов обработали менее 10 ТБ данных.<\/li>\n<li>Более 90% сеансов обработали менее 1 ТБ.<\/li>\n<\/ul>\n<p>В статье также говорится, что:<\/p>\n<ul>\n<li>Большинство таблиц содержит менее миллиона строк, и подавляющее большинство (98%) — менее миллиарда строк. Большая часть этих данных достаточно мала, чтобы её можно было кэшировать или реплицировать.<\/li>\n<\/ul>\n<p>Этот анализ показывает, что при пороговом значении для больших данных в 1 ТБ более 90% запросов находятся ниже этого порога.<\/p>\n<p>В результате, одноузловые движки обработки потенциально способны обрабатывать рабочие нагрузки, которые ранее требовали распределенных систем, таких как Spark, Trino или Amazon Athena, для обработки на нескольких машинах.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-142.png\" width=\"1201\" height=\"812\" alt=\"\" \/>\n<\/div>\n<p>Эта реальность ставит под сомнение распространенное представление о том, что инфраструктура больших данных является необходимостью для всех современных предприятий.<\/p>\n<p><b>Шаблоны рабочей нагрузки и быстрое устаревание данных<\/b><\/p>\n<p>Аргументы в пользу одноузловой обработки становятся ещё более убедительными, когда мы изучаем, как организации в действительности используют свои данные.<\/p>\n<p>Выявляются два ключевых шаблона: эффект устаревания данных и правило 90\/10 для аналитических рабочих нагрузок.<\/p>\n<p><b>Эффект устаревания данных<\/b><\/p>\n<p>По мере устаревания данных частота доступа к ним резко снижается. Для большинства компаний шаблоны доступа к данным следуют предсказуемому жизненному циклу:<\/p>\n<ul>\n<li>Активные данные (0-48 часов): в основном из конвейеров ETL.<\/li>\n<li>Теплые данные (2-30 дней): составляют большую часть аналитических запросов.<\/li>\n<li>Холодные данные (30+ дней): редко используются, но часто хранятся для соответствия требованиям или исторического анализа.<\/li>\n<\/ul>\n<p>Исследование шаблонов доступа к данным Meta и eBay выявило резкое снижение доступа после первых нескольких дней, причем данные обычно становились холодными через месяц.<\/p>\n<p>В нашем анализе озера данных петабайтного масштаба мы обнаружили, что необработанные данные остаются активными только в течение 48 часов, причем 95% доступа приходится на это время, в основном со стороны нисходящих конвейеров ETL. В зоне Analytics (Gold) активный период длится около 7 дней, и 95% запросов выполняются только в течение 30 дней.<\/p>\n<p><b>Правило 90\/10 для аналитических рабочих нагрузок<\/b><\/p>\n<p>Этот эффект устаревания приводит к правилу 90\/10 в аналитических рабочих нагрузках:<\/p>\n<ul>\n<li>Если общий активный и теплый период составляет 30 дней и приходится на 90% рабочих нагрузок, то, при годовом сроке хранения, более 90% рабочих нагрузок получают доступ менее чем к 10% данных.<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-143.png\" width=\"1041\" height=\"1340\" alt=\"\" \/>\n<\/div>\n<p>Этот шаблон остается удивительно постоянным в разных отраслях и вариантах использования. Даже в организациях с большими наборами данных большинство аналитических рабочих нагрузок работает с последними, агрегированными данными, которые легко помещаются в возможности одноузловой обработки.<\/p>\n<p><b>Эволюция оборудования и переосмысление масштабирования вверх<\/b><\/p>\n<p>Возможности одноузловых систем экспоненциально выросли со времен зарождения больших данных.<\/p>\n<p>Обоснование и мотивация стратегии масштабирования по горизонтали (scale-out), которая стала популярной с появлением Hadoop в середине 2000-х годов в области обработки данных, заключаются в необходимости объединения нескольких машин для решения проблем масштабирования, что позволяет эффективно обрабатывать большие наборы данных в разумные сроки и с приемлемым уровнем производительности.<\/p>\n<p>Интегрируя несколько машин в распределенные системы, мы фактически создаем единый большой блок, объединяя ресурсы, такие как ОЗУ, ЦП, дисковое пространство и пропускную способность, в одну большую виртуальную машину.<\/p>\n<p>Однако нам необходимо переоценить наши предположения о распределенной обработке и проблемах масштабирования, с которыми мы столкнулись в 2000-х годах, чтобы увидеть, остаются ли они актуальными сегодня.<\/p>\n<p>В 2006 году, когда появился Hadoop MapReduce, первые инстансы AWS EC2 (m1.small) имели всего 1 ЦП и менее 2 ГБ ОЗУ. Сегодня облачные провайдеры предлагают инстансы с 64+ ядрами и 256 ГБ+ ОЗУ, что кардинально меняет ситуацию с возможностями одноузловой обработки.<\/p>\n<p>Изучение эволюции сбалансированных инстансов EC2 с точки зрения памяти и ЦП (с соотношением 1:4) на протяжении многих лет выявляет экспоненциальный рост, поскольку эти инстансы со временем становятся все более мощными.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-144.png\" width=\"1456\" height=\"448\" alt=\"\" \/>\n<\/div>\n<p><b>Экономика масштабирования вверх (Scale-Up) против масштабирования по горизонтали (Scale-Out)<\/b><\/p>\n<p>Можно предположить, что масштабирование по горизонтали на нескольких небольших инстансах более рентабельно, чем использование более крупных инстансов. Однако модели облачного ценообразования говорят об обратном.<\/p>\n<p>Стоимость за вычислительную единицу в облаке является постоянной, независимо от того, используете ли вы меньший или больший инстанс, поскольку стоимость увеличивается линейно.<\/p>\n<p>То есть стоимость более крупных вычислительных инстансов в облаке увеличивается линейно, и общая цена остается той же, независимо от того, используете ли вы один более крупный инстанс или несколько небольших инстансов, при условии, что общее количество ядер и памяти одинаково.<\/p>\n<p>Используя семейство инстансов m5 от AWS в качестве примера, независимо от того, масштабируетесь ли вы вверх с помощью одного инстанса m5.16xlarge или масштабируетесь по горизонтали с помощью восьми инстансов m5.2xlarge, цена за час останется той же.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-145.png\" width=\"882\" height=\"694\" alt=\"\" \/>\n<\/div>\n<p>Эта эволюция оборудования имеет важные последствия для решений по архитектуре системы, поскольку:<\/p>\n<ul>\n<li>Современные инстансы могут обрабатывать рабочие нагрузки, которые ранее требовали десятков небольших узлов, и делают это с меньшей сложностью и накладными расходами.<\/li>\n<\/ul>\n<p>Это поднимает критический вопрос:<\/p>\n<ul>\n<li>С точки зрения соотношения цены и производительности, если одноузловой движок запросов может эффективно обрабатывать большинство рабочих нагрузок, есть ли еще выгода от распределения обработки по нескольким узлам?<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/gavrilov.info\/pictures\/image-146.png\" width=\"1156\" height=\"742\" alt=\"\" \/>\n<\/div>\n<p><b>Аргумент производительности для одноузловой обработки<\/b><\/p>\n<p>Современные одноузловые движки обработки используют передовые методы для достижения впечатляющей производительности.<\/p>\n<p>Движки, такие как DuckDB и Apache DataFusion, достигают превосходной производительности благодаря сложным методам оптимизации, включая векторизованное выполнение, параллельную обработку и эффективное управление памятью.<\/p>\n<p>Многочисленные тесты показывают эти улучшения производительности:<\/p>\n<ul>\n<li>Vantage сообщила, что при переходе с Postgres на DuckDB для анализа затрат на облако они увидели улучшение производительности в диапазоне от 4X до 200X.<\/li>\n<li>Тесты генерального директора Fivetran с использованием наборов данных TPC-DS показали, что DuckDB превосходит коммерческие хранилища данных для наборов данных размером менее 300 ГБ.<\/li>\n<li>Эксперимент с 1 миллиардом строк поддельных данных о заказах, сравнивающий DuckDB с Amazon Athena.<\/li>\n<\/ul>\n<p><b>Почему стоит выбрать одноузловую обработку?<\/b><\/p>\n<p>Аргументы в пользу одноузловой обработки выходят за рамки простой производительности. Для большинства предприятий современные одноузловые движки предлагают несколько веских преимуществ:<\/p>\n<ul>\n<li>Они значительно упрощают архитектуру системы, устраняя сложность распределенных систем. Это упрощение снижает эксплуатационные расходы, облегчает отладку и снижает порог входа для команд, работающих с данными.<\/li>\n<li>Они часто обеспечивают лучшее использование ресурсов. Без накладных расходов на сетевую связь и распределенную координацию больше вычислительной мощности можно выделить для фактической обработки данных. Эта эффективность напрямую приводит к экономии затрат и повышению производительности.<\/li>\n<li>Они предлагают отличную интеграцию с современными рабочими процессами обработки данных. Такие движки, как chDB и DuckDB, могут напрямую запрашивать данные из облачного хранилища, бесперебойно работать с популярными языками программирования и органично вписываться в существующие конвейеры обработки данных.<\/li>\n<li>Встраиваемая природа некоторых из этих движков обеспечивает бесшовную интеграцию с существующими системами — от расширений PostgreSQL, таких как pg_analytics и pg_duckdb, до различных современных инструментов Business Intelligence — расширяя аналитические возможности без нарушения установленных рабочих процессов.<\/li>\n<\/ul>\n<p><b>Проблемы и ограничения<\/b><\/p>\n<p>Хотя одноузловая обработка предлагает много преимуществ, важно признать её ограничения.<\/p>\n<ul>\n<li>Некоторые движки по-прежнему сталкиваются с проблемами полного использования всех доступных ядер ЦП на больших машинах, особенно по мере увеличения количества ядер. Пропускная способность иерархии памяти между ОЗУ и ЦП может стать узким местом для определенных рабочих нагрузок.<\/li>\n<li>При чтении из облачного хранилища, такого как S3, скорость передачи данных через одно соединение может быть ограничена, хотя это часто можно смягчить с помощью параллельных соединений и интеллектуальных стратегий кэширования. И, естественно, остаются рабочие нагрузки, включающие очень большие наборы данных, которые превышают доступную память и хранилище, требующие распределенной обработки.<\/li>\n<\/ul>\n<p><b>Заключение<\/b><\/p>\n<p>Расцвет одноузловых движков обработки представляет собой прагматичный сдвиг в анализе данных. Поскольку возможности оборудования продолжают развиваться, а одноузловые движки становятся все более сложными, потребность в распределенной обработке, вероятно, продолжит снижаться для большинства организаций.<\/p>\n<p>Для подавляющего большинства компаний фреймворки одноузловой обработки предлагают более эффективное, экономичное и управляемое решение для их потребностей в анализе данных. По мере продвижения вперед главное — не автоматически тянуться к распределенным решениям, а тщательно оценивать фактические требования к рабочей нагрузке и выбирать правильный инструмент для работы.<\/p>\n<p>Будущее обработки данных вполне может быть менее связано с управлением кластерами и больше с использованием впечатляющих возможностей современных одноузловых систем.<\/p>\n<p>Спасибо автору ALIREZA SADEGHI и оригиналу: <a href=\"https:\/\/www.pracdata.io\/p\/the-rise-of-single-node-processing\">https:\/\/www.pracdata.io\/p\/the-rise-of-single-node-processing<\/a><\/p>\n",
            "date_published": "2025-02-23T21:05:19+03:00",
            "date_modified": "2025-02-23T21:08:08+03:00",
            "tags": [
                "big data",
                "Data",
                "Data Engineer",
                "Data Governance",
                "datafusion"
            ],
            "image": "https:\/\/gavrilov.info\/pictures\/image-140.png",
            "_date_published_rfc2822": "Sun, 23 Feb 2025 21:05:19 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "195",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [],
                "og_images": [
                    "https:\/\/gavrilov.info\/pictures\/image-140.png",
                    "https:\/\/gavrilov.info\/pictures\/image-141.png",
                    "https:\/\/gavrilov.info\/pictures\/image-142.png",
                    "https:\/\/gavrilov.info\/pictures\/image-143.png",
                    "https:\/\/gavrilov.info\/pictures\/image-144.png",
                    "https:\/\/gavrilov.info\/pictures\/image-145.png",
                    "https:\/\/gavrilov.info\/pictures\/image-146.png"
                ]
            }
        },
        {
            "id": "73",
            "url": "https:\/\/gavrilov.info\/all\/polezny-freymvork-datafusion-sql-engine-na-baze-arrow\/",
            "title": "Полезный фрэймворк datafusion – SQL Engine на базе Arrow",
            "content_html": "<p><a href=\"https:\/\/arrow.apache.org\/datafusion\/\">https:\/\/arrow.apache.org\/datafusion\/<\/a><\/p>\n<pre class=\"e2-text-code\"><code class=\"\">$ datafusion-cli\nDataFusion CLI v17.0.0\n❯ select * from 'data.csv';\n+---+---+\n| a | b |\n+---+---+\n| 1 | 2 |\n+---+---+\n1 row in set. Query took 0.007 seconds.<\/code><\/pre><p>А можно даже с s3 напрямую читать:<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">CREATE EXTERNAL TABLE test\nSTORED AS PARQUET\nOPTIONS(\n    'access_key_id' '******',\n    'secret_access_key' '******',\n    'region' 'us-east-2'\n)\nLOCATION 's3:\/\/bucket\/path\/file.parquet';<\/code><\/pre>",
            "date_published": "2023-10-18T21:20:09+03:00",
            "date_modified": "2023-10-18T21:31:30+03:00",
            "tags": [
                "Apache",
                "Arrow",
                "big data",
                "datafusion"
            ],
            "_date_published_rfc2822": "Wed, 18 Oct 2023 21:20:09 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "73",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "highlight\/highlight.js",
                    "highlight\/highlight.css"
                ],
                "og_images": []
            }
        }
    ],
    "_e2_version": 4171,
    "_e2_ua_string": "Aegea 11.4 (v4171e)"
}