<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Yuriy Gavrilov</title>
<link>https://gavrilov.info/</link>
<description>Welcome to my personal place for love, peace and happiness 🤖 Yuiry Gavrilov</description>
<author></author>
<language>en</language>
<generator>Aegea 11.4 (v4171e)</generator>

<itunes:owner>
<itunes:name></itunes:name>
<itunes:email>yvgavrilov@gmail.com</itunes:email>
</itunes:owner>
<itunes:subtitle>Welcome to my personal place for love, peace and happiness 🤖 Yuiry Gavrilov</itunes:subtitle>
<itunes:image href="https://gavrilov.info/pictures/userpic/userpic-square@2x.jpg?1643451008" />
<itunes:explicit>no</itunes:explicit>

<item>
<title>Генерация синтетических данных с помощью SDV: от идеи до готовой таблицы в ClickHouse</title>
<guid isPermaLink="false">347</guid>
<link>https://gavrilov.info/all/generaciya-sinteticheskih-dannyh-s-pomoschyu-sdv-ot-idei-do-goto/</link>
<pubDate>Thu, 03 Sep 2026 00:49:26 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/generaciya-sinteticheskih-dannyh-s-pomoschyu-sdv-ot-idei-do-goto/</comments>
<description>
&lt;p&gt;Синтетические данные давно перестали быть просто «искусственной картинкой» — сегодня это полноценный инструмент для тестирования, разработки и даже обучения моделей машинного обучения. В этой статье я на примере реального кода покажу, как с помощью библиотеки &lt;b&gt;SDV ( &lt;a href="https://github.com/sdv-dev/sdv"&gt;Synthetic Data Vault &lt;/a&gt; )&lt;/b&gt; можно сгенерировать синтетическую копию существующей таблицы из ClickHouse, сохранив её статистические свойства и взаимосвязи.&lt;/p&gt;
&lt;p&gt;Весь код, который мы разберём, написан на Python и использует актуальную версию SDV. Спойлер: итоговый скрипт занимает меньше 80 строк, а результат — полностью готовая синтетическая таблица в вашей базе данных.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/image-240.png" width="1280" height="640" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;Что такое SDV и зачем он нужен&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Synthetic Data Vault (SDV)&lt;/b&gt; — это Python-библиотека с открытым исходным кодом, которая стала фактическим стандартом для генерации синтетических табличных данных. Она была создана в MIT, а сегодня развивается компанией DataCebo.&lt;/p&gt;
&lt;p&gt;Главная идея SDV проста: вы даёте ей реальные данные, она обучает генеративную модель, а затем эта модель создаёт новые данные, которые &lt;b&gt;статистически неотличимы&lt;/b&gt; от оригинала, но при этом не содержат реальных записей.&lt;/p&gt;
&lt;p&gt;Ключевые возможности SDV:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Обучение на одном наборе данных&lt;/b&gt; — библиотека анализирует распределения, корреляции и зависимости между колонками.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Генерация новых записей&lt;/b&gt; — синтетические данные повторяют структуру и свойства оригинала.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Оценка качества&lt;/b&gt; — встроенные метрики показывают, насколько хорошо синтетика отражает реальные данные.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Поддержка разных типов данных&lt;/b&gt; — числовые, категориальные, даты, текстовые поля.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SDV умеет работать с одиночными таблицами, связанными таблицами и даже последовательными данными.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Какие синтезаторы предлагает SDV&lt;/h3&gt;
&lt;p&gt;SDV предлагает несколько алгоритмов (синтезаторов) на выбор. В нашем примере мы используем &lt;b&gt;`GaussianCopulaSynthesizer`&lt;/b&gt; — и вот почему.&lt;/p&gt;
&lt;table cellpadding="0" cellspacing="0" border="0" class="e2-text-table"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Синтезатор&lt;/td&gt;
&lt;td style="text-align: center"&gt;Принцип работы&lt;/td&gt;
&lt;td style="text-align: center"&gt;Скорость&lt;/td&gt;
&lt;td style="text-align: center"&gt;Когда использовать&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;GaussianCopulaSynthesizer&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Классический статистический метод, моделирует данные через многомерное распределение&lt;/td&gt;
&lt;td style="text-align: center"&gt;Очень быстрый&lt;/td&gt;
&lt;td style="text-align: center"&gt;Рекомендуется для старта: быстрая работа, хорошее качество, прозрачность&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;CTGANSynthesizer&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Генеративно-состязательная сеть (GAN)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Медленнее&lt;/td&gt;
&lt;td style="text-align: center"&gt;Для данных со сложными зависимостями или большим количеством категориальных признаков&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;TVAESynthesizer&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Вариационный автокодировщик&lt;/td&gt;
&lt;td style="text-align: center"&gt;Средняя&lt;/td&gt;
&lt;td style="text-align: center"&gt;Альтернатива GAN, требующая больше данных для обучения&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;`GaussianCopulaSynthesizer` отлично справляется со смешанными типами данных, которые встречаются в реальных таблицах: числа, строки, даты. При этом он обучается за секунды даже на десятках тысяч строк. Именно поэтому мы выбрали его для нашего примера.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Настройка окружения&lt;/h3&gt;
&lt;p&gt;Перед запуском кода нам потребуется установить несколько пакетов. В нашем проекте мы использовали `uv` — быстрый менеджер пакетов для Python.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;uv pip install torch==2.2.2
uv pip install &amp;quot;numpy&amp;lt;2&amp;quot;
uv pip install &amp;quot;scipy&amp;lt;1.13&amp;quot;
uv pip install sdv
uv pip install clickhouse-connect&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Важно&lt;/b&gt;: версии пакетов подобраны так, чтобы избежать конфликтов. На момент написания статьи `torch` версии 2.13.0 не имеет готовых сборок для macOS на Intel, поэтому мы используем `2.2.2`. Также важно зафиксировать `numpy&lt;2` и `scipy&lt;1.13` — более новые версии несовместимы с текущей версией `torch`.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;Полный код генерации синтетических данных&lt;/h3&gt;
&lt;p&gt;Ниже представлен итоговый скрипт `sdv_py_v5.py`, который мы будем разбирать. Он подключается к ClickHouse, загружает реальные данные, обучает синтезатор, генерирует синтетическую копию и сохраняет результат обратно в базу.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import clickhouse_connect
import pandas as pd
import os
import warnings
from sdv.single_table import GaussianCopulaSynthesizer
from sdv.metadata import SingleTableMetadata

# Подавляем предупреждения (включая депрекацию)
warnings.filterwarnings(&amp;quot;ignore&amp;quot;)

# Правильный импорт для однотабличных отчётов
from sdmetrics.reports.single_table import QualityReport, DiagnosticReport

# ========== НАСТРОЙКИ ==========
CLICKHOUSE_HOST = 'localhost'
CLICKHOUSE_PORT = 18123
CLICKHOUSE_USER = 'default'
CLICKHOUSE_PASSWORD = 'changeme'
CLICKHOUSE_DATABASE = 'default'

SOURCE_TABLE = 'superstore'
SYNTHETIC_TABLE = f'{SOURCE_TABLE}_synt'
LIMIT_ROWS = 10000
NUM_SYNTHETIC_ROWS = None

# ========== ПОДКЛЮЧЕНИЕ ==========
client = clickhouse_connect.get_client(
    host=CLICKHOUSE_HOST,
    port=CLICKHOUSE_PORT,
    username=CLICKHOUSE_USER,
    password=CLICKHOUSE_PASSWORD,
    database=CLICKHOUSE_DATABASE
)

# ========== ЗАГРУЗКА ==========
query = f&amp;quot;SELECT * FROM {SOURCE_TABLE} LIMIT {LIMIT_ROWS}&amp;quot;
real_df = client.query_df(query)
print(f&amp;quot;Загружено {len(real_df)} строк&amp;quot;)

# Преобразуем числовые колонки (запятая → точка)
numeric_cols = ['sales', 'discount', 'profit']
for col in numeric_cols:
    if col in real_df.columns and real_df[col].dtype == 'object':
        real_df[col] = real_df[col].str.replace(',', '.').astype(float)

# ========== МЕТАДАННЫЕ ==========
metadata = SingleTableMetadata()
metadata.detect_from_dataframe(real_df)

if os.path.exists('metadata.json'):
    os.remove('metadata.json')
metadata.save_to_json('metadata.json')
print(&amp;quot;Метаданные сохранены в metadata.json&amp;quot;)

# ========== ОБУЧЕНИЕ ==========
print(&amp;quot;Начинаем обучение синтезатора...&amp;quot;)
synthesizer = GaussianCopulaSynthesizer(metadata)
synthesizer.fit(real_df)
print(&amp;quot;Обучение завершено&amp;quot;)

# ========== ГЕНЕРАЦИЯ ==========
if NUM_SYNTHETIC_ROWS is None:
    NUM_SYNTHETIC_ROWS = len(real_df)
synthetic_df = synthesizer.sample(NUM_SYNTHETIC_ROWS)
print(f&amp;quot;Сгенерировано {len(synthetic_df)} строк&amp;quot;)

# ========== ПРЕДПРОСМОТР ==========
print(&amp;quot;\nОригинальные данные (первые 5 строк):&amp;quot;)
print(real_df.head())
print(&amp;quot;\nСинтетические данные (первые 5 строк):&amp;quot;)
print(synthetic_df.head())

# ========== ОЦЕНКА КАЧЕСТВА ==========
print(&amp;quot;\n--- Диагностика (проверка ошибок) ---&amp;quot;)
diagnostic = DiagnosticReport()
diagnostic.generate(real_df, synthetic_df, metadata.to_dict())
print(diagnostic)

print(&amp;quot;\n--- Оценка качества (статистическое сходство) ---&amp;quot;)
quality = QualityReport()
quality.generate(real_df, synthetic_df, metadata.to_dict())
print(quality)

# ========== СОХРАНЕНИЕ В CLICKHOUSE ==========
client.command(f&amp;quot;CREATE TABLE IF NOT EXISTS {SYNTHETIC_TABLE} AS {SOURCE_TABLE}&amp;quot;)
client.insert_df(SYNTHETIC_TABLE, synthetic_df)
print(f&amp;quot;\nСинтетические данные загружены в таблицу {SYNTHETIC_TABLE}&amp;quot;)

# ========== СОХРАНЕНИЕ В CSV ==========
synthetic_df.to_csv('synthetic_table.csv', index=False)
print(&amp;quot;Синтетические данные также сохранены в synthetic_table.csv&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;Разбор кода по шагам&lt;/h3&gt;
&lt;h4&gt;1. Подключение к ClickHouse и загрузка данных&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;client = clickhouse_connect.get_client(
    host=CLICKHOUSE_HOST,
    port=CLICKHOUSE_PORT,
    username=CLICKHOUSE_USER,
    password=CLICKHOUSE_PASSWORD,
    database=CLICKHOUSE_DATABASE
)

query = f&amp;quot;SELECT * FROM {SOURCE_TABLE} LIMIT {LIMIT_ROWS}&amp;quot;
real_df = client.query_df(query)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Мы подключаемся к ClickHouse через библиотеку `clickhouse-connect` и загружаем данные из таблицы-источника в Pandas DataFrame. Лимит в 10 000 строк — разумный компромисс между качеством обучения и скоростью. Для большинства задач этого объёма достаточно, чтобы синтезатор уловил основные закономерности.&lt;/p&gt;
&lt;h4&gt;2. Предобработка числовых данных&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;numeric_cols = ['sales', 'discount', 'profit']
for col in numeric_cols:
    if col in real_df.columns and real_df[col].dtype == 'object':
        real_df[col] = real_df[col].str.replace(',', '.').astype(float)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В нашем примере числовые колонки хранятся в ClickHouse как строки с запятой в качестве десятичного разделителя (европейский формат). Перед обучением мы преобразуем их в числа с плавающей точкой — иначе SDV не сможет корректно обработать эти данные.&lt;/p&gt;
&lt;h4&gt;3. Создание метаданных&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;metadata = SingleTableMetadata()
metadata.detect_from_dataframe(real_df)
metadata.save_to_json('metadata.json')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SDV автоматически определяет типы каждой колонки: числовые, категориальные, даты и т.д.. Метаданные сохраняются в JSON-файл — это полезно для воспроизводимости: в следующий раз вы сможете загрузить их, а не пересоздавать заново.&lt;/p&gt;
&lt;h4&gt;4. Обучение синтезатора&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;synthesizer = GaussianCopulaSynthesizer(metadata)
synthesizer.fit(real_df)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Синтезатор «изучает» ваши данные: анализирует распределения каждой колонки, корреляции между ними, паттерны пропусков. Этот процесс занимает секунды даже на 10 000 строк.&lt;/p&gt;
&lt;h4&gt;5. Генерация синтетических данных&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;synthetic_df = synthesizer.sample(NUM_SYNTHETIC_ROWS)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Метод `sample()` создаёт нужное количество новых записей. В нашем случае мы генерируем столько же строк, сколько было в оригинале. При желании можно увеличить или уменьшить это число — синтезатор умеет масштабировать данные.&lt;/p&gt;
&lt;h4&gt;6. Оценка качества&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;diagnostic = DiagnosticReport()
diagnostic.generate(real_df, synthetic_df, metadata.to_dict())

quality = QualityReport()
quality.generate(real_df, synthetic_df, metadata.to_dict())&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Этот этап — одна из сильных сторон SDV. Мы проверяем два аспекта:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Диагностика&lt;/b&gt; (Data Validity + Data Structure) — проверяет, что синтетические данные корректны по типам и структуре. В нашем случае — 100%.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Quality Report&lt;/b&gt; — оценивает статистическое сходство с реальными данными по двум метрикам:
&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;Column Shapes Score&lt;/b&gt; (90.77%) — насколько хорошо совпадают распределения отдельных колонок.&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;Column Pair Trends Score&lt;/b&gt; (37.71%) — насколько хорошо сохранились попарные зависимости между колонками.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Общий Score 64.24% — это нормальный результат для демонстрационного примера. Для улучшения качества можно переключиться на `CTGANSynthesizer` или увеличить объём обучающих данных.&lt;/p&gt;
&lt;h4&gt;7. Сохранение в ClickHouse&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;client.command(f&amp;quot;CREATE TABLE IF NOT EXISTS {SYNTHETIC_TABLE} AS {SOURCE_TABLE}&amp;quot;)
client.insert_df(SYNTHETIC_TABLE, synthetic_df)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Финальный шаг: создаём новую таблицу с той же структурой, что и исходная, и заполняем её синтетическими данными. Всё готово — вы можете работать с синтетической копией так же, как с реальной таблицей.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Интеграция с ClickHouse: почему это важно&lt;/h3&gt;
&lt;p&gt;ClickHouse — популярная колоночная СУБД для аналитики. Генерация синтетических данных прямо в экосистему ClickHouse открывает широкие возможности:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Тестирование производительности&lt;/b&gt; — можно генерировать датасеты любого размера и проверять, как ClickHouse справляется с нагрузкой.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Разработка без доступа к продакшену&lt;/b&gt; — разработчики получают рабочую копию данных без риска утечки конфиденциальной информации.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Обучение моделей&lt;/b&gt; — синтетические данные можно использовать для предварительного обучения ML-моделей, не затрагивая реальные данные.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Альтернативный подход — использование встроенных средств ClickHouse, таких как движок `GenerateRandom`. Однако SDV даёт гораздо более качественные данные, потому что он не просто генерирует случайные значения, а воспроизводит реальные статистические паттерны.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Что дальше: улучшение качества синтеза&lt;/h3&gt;
&lt;p&gt;Если качество синтеза (особенно Column Pair Trends) вас не устраивает, вот несколько способов его улучшить:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Увеличьте объём обучающих данных&lt;/b&gt; — вместо 10 000 строк загрузите 50 000 или 100 000.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Попробуйте CTGANSynthesizer&lt;/b&gt; — нейросетевой синтезатор лучше справляется со сложными зависимостями.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Настройте метаданные вручную&lt;/b&gt; — если автоматическое определение типов сработало неидеально, вы можете отредактировать `metadata.json` и указать типы явно.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Используйте условную генерацию&lt;/b&gt; — SDV позволяет фиксировать значения отдельных колонок и генерировать остальные с учётом этих условий.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-03-v-00.52.49.png" width="1360" height="708" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-03-v-00.53.29.png" width="2016" height="666" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;Мы прошли полный путь: от установки библиотек до готовой синтетической таблицы в ClickHouse. SDV оказался мощным и при этом простым в использовании инструментом — весь процесс уместился в один скрипт на 80 строк.&lt;/p&gt;
&lt;p&gt;Главные выводы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;SDV&lt;/b&gt; — это не просто генератор случайных данных, а интеллектуальный инструмент, который сохраняет статистическую структуру оригинала.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`GaussianCopulaSynthesizer`&lt;/b&gt; — отличная точка входа: быстрый, прозрачный, даёт хорошее качество.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Встроенная оценка качества&lt;/b&gt; позволяет объективно понять, насколько синтетика похожа на реальные данные.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Интеграция с ClickHouse&lt;/b&gt; через `clickhouse-connect` делает процесс полностью автоматизированным.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Синтетические данные открывают новые возможности для разработки, тестирования и анализа данных — без рисков, связанных с работой с реальными данными. А SDV даёт в руки инструмент, который превращает эту идею в реальность за считанные минуты.&lt;/p&gt;
&lt;p&gt;ПЫСЫ:&lt;/p&gt;
&lt;p&gt;Можно еще попробовать так: CTGANSynthesizer&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;from sdv.single_table import GaussianCopulaSynthesizer, CTGANSynthesizer&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Но надо будет дольше ждать...  и заменить тип в строке на&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;CTGANSynthesizer&lt;/code&gt;&lt;/pre&gt;&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;uv run sdv_py_v55.py
Загружено 9994 строк
Метаданные сохранены в metadata.json
Начинаем обучение синтезатора...
PerformanceAlert: Using the CTGANSynthesizer on this data is not recommended. To model this data, CTGAN will generate a large number of columns.

Original Column Name   Est # of Columns (CTGAN)
Order Date             1236
Ship Date              1334
Ship Mode              4
Customer Name          793
segment                3
Country/Region         1
region                 4
category               3
Sub-Category           17
Product Name           1849
sales                  5825
quantity               11
discount               12
profit                 7287

We recommend preprocessing discrete columns that can have many values, using 'update_transformers'. Or you may drop columns that are not necessary to model. (Exit this script using ctrl-C)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Все .. комп полетел)) 5 мин... 7 мин... 15... чем то напоминает майнинг))) .... 20... 30... пойду пожалуй спать ...&lt;/p&gt;
</description>
</item>

<item>
<title>OceanBase Lakebase: Унифицированная платформа данных для AI-приложений</title>
<guid isPermaLink="false">346</guid>
<link>https://gavrilov.info/all/oceanbase-lakebase-unificirovannaya-platforma-dannyh-dlya-ai-pri/</link>
<pubDate>Wed, 02 Sep 2026 00:43:27 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/oceanbase-lakebase-unificirovannaya-platforma-dannyh-dlya-ai-pri/</comments>
<description>
&lt;h3&gt;Введение: данные есть, AI нужен контекст&lt;/h3&gt;
&lt;p&gt;Корпоративные системы данных долгое время строились вокруг четких границ: базы данных отвечали за транзакции, хранилища — за аналитику, озера данных — за хранение больших массивов сырой информации, поисковые движки индексировали документы, а векторные базы данных обеспечивали семантический поиск.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-02-v-00.37.49.png" width="686" height="384" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Оригинал тут: &lt;a href="https://oceanbase.medium.com/oceanbase-lakebase-a-unified-data-foundation-for-ai-native-applications-e22d5ee9fab5"&gt;OceanBase Lakebase: A Unified Data Foundation for AI-Native Applications&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Такое разделение работало, пока приложения были детерминированными, а данные — в основном структурированными. AI-приложения ведут себя иначе.&lt;/p&gt;
&lt;p&gt;Им может понадобиться профиль клиента вместе с изображениями, аудио- и видеозаписями, PDF-файлами, веб-снимками, векторными эмбеддингами и JSON-документами — и все это для одной бизнес-сущности. В многих компаниях эти данные уже существуют, но они разбросаны по разным системам с разной метаинформацией, политиками доступа и конвейерами обработки. Результат — фрагментированный стек данных, который делает AI-приложения сложными в разработке, управлении и эксплуатации.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;OceanBase Lakebase&lt;/b&gt; создан именно для решения этой проблемы.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Что такое Lakebase?&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Lakebase&lt;/b&gt; — это ядро OceanBase AI Database. Оно объединяет управление мультимодальными данными, оперативную выдачу (online serving), аналитику в реальном времени, гибридный поиск и открытые вычисления в единой архитектуре.&lt;/p&gt;
&lt;p&gt;Это не отдельное озеро данных и не традиционная база данных с надстройками AI. Это попытка переосмыслить то, как корпоративные данные должны храниться, управляться, обрабатываться и предоставляться, когда AI-приложения становятся частью производственных систем.&lt;/p&gt;
&lt;p&gt;Ключевая идея проста: структурированные бизнес-данные и мультимодальные данные должны управляться в рамках единой платформы с согласованной метаинформацией, правами доступа, управлением жизненным циклом и единым языком запросов.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-02-v-00.38.08.png" width="684" height="354" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;Мультимодальные данные в одной таблице&lt;/h3&gt;
&lt;p&gt;Ключевое нововведение Lakebase — &lt;b&gt;мультимодальная таблица&lt;/b&gt;. В традиционной СУБД таблица описывает структурированные поля: идентификаторы, временные метки, суммы, статусы. В Lakebase таблица может описывать более сложные бизнес-сущности, включающие документы, изображения, аудио, видео, JSON, большие объекты, векторы и результаты работы моделей.&lt;/p&gt;
&lt;p&gt;Разные типы данных могут использовать разные форматы хранения в зависимости от размера, паттернов доступа и стоимости. Но с точки зрения пользователя, все они управляются через единую табличную модель, единую метасистему и единую систему управления.&lt;/p&gt;
&lt;p&gt;Это дает AI-приложениям более естественную модель данных. Например, обращение в службу поддержки может включать структурированные поля тикета, историю чата, записи звонков, загруженные изображения, диагностические логи и эмбеддинги. Логистическая запись — метаданные заказа, события маршрута, заметки водителя, изображения подтверждения доставки и семантические признаки. С бизнес-точки зрения это не отдельные фрагменты, а разные представления одной сущности.&lt;/p&gt;
&lt;p&gt;Lakebase упрощает хранение, поиск, управление и обработку таких сущностей как единого целого.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;AI-колонки: модели в конвейере данных&lt;/h3&gt;
&lt;p&gt;Lakebase вводит понятие &lt;b&gt;AI-колонок&lt;/b&gt; — столбцов, в которых хранятся результаты работы моделей: эмбеддинги, саммари, метки, категории или извлеченные признаки.&lt;/p&gt;
&lt;p&gt;Вместо того чтобы выгружать данные во внешний конвейер, генерировать семантические результаты и вручную записывать их обратно, команды могут приблизить обработку моделями к данным. Это дает несколько преимуществ:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Сокращение цепочки обработки&lt;/b&gt; — меньше систем, меньше мест, где могут нарушиться согласованность данных и обработка ошибок.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Улучшение управляемости&lt;/b&gt; — эмбеддинги и метки становятся частью управляемых данных, а не скрытыми артефактами.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Надежные механизмы повторных попыток&lt;/b&gt; — в production-средах генерация эмбеддингов, саммари и извлечение признаков могут требовать пересчета при изменении исходных данных.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;Гибридный поиск в единой платформе&lt;/h3&gt;
&lt;p&gt;AI-приложениям нужен поиск, но не один-единственный вид:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Поиск по ключевым словам&lt;/b&gt; — когда пользователи знают, что ищут.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Векторный поиск&lt;/b&gt; — когда важна семантическая близость.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Структурная фильтрация&lt;/b&gt; — когда нужно учитывать бизнес-правила, права доступа, временные диапазоны, категории продуктов или сегменты клиентов.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;В большинстве AI-стеков эти возможности разнесены по разным системам: транзакционная БД, поисковик, векторная БД и конвейер синхронизации. Для прототипов это работает, но в production возникают проблемы со свежестью данных, согласованностью прав доступа и надежностью.&lt;/p&gt;
&lt;p&gt;Lakebase объединяет структурную фильтрацию, полнотекстовый поиск и векторный поиск в едином пути запроса. Это позволяет AI-приложениям получать AI-готовый контекст из свежих операционных и мультимодальных данных без множества внешних индексов, которые могут рассинхронизироваться с источником.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Поддержка агентных нагрузок&lt;/h3&gt;
&lt;p&gt;Агенты меняют способ использования баз данных. Они могут читать, писать, искать, планировать, тестировать, изменять состояние, генерировать промежуточные результаты и многократно вызывать инструменты. При этом множество агентов могут работать параллельно, каждый со своей памятью, контекстом, правами и историей выполнения.&lt;/p&gt;
&lt;p&gt;Lakebase предоставляет основу для хранения и поиска памяти агентов, контекста сессий, бизнес-состояния и записей выполнения. Он также поддерживает изолированные среды через &lt;b&gt;ветвление (branching)&lt;/b&gt; и &lt;b&gt;песочницы (sandboxing)&lt;/b&gt;, чтобы агенты могли тестировать изменения без прямого воздействия на production-данные.&lt;/p&gt;
&lt;p&gt;Цель — не просто помочь агентам получать данные, а помочь им работать безопасно, воспроизводимо и в масштабе.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Открытое хранение и открытые вычисления&lt;/h3&gt;
&lt;p&gt;AI-нагрузки не будут выполняться в рамках одного движка. SQL остается основой для транзакционных и аналитических нагрузок. Spark широко используется для обработки больших данных. Ray и смежные фреймворки — для AI-обработки и распределенных модельных нагрузок.&lt;/p&gt;
&lt;p&gt;Lakebase поддерживает S3-совместимое объектное хранение и открытые табличные форматы, такие как &lt;b&gt;Apache Iceberg&lt;/b&gt;, позволяя вычислительным движкам (SQL, Spark, Ray) работать с общими данными и метаинформацией. Это сокращает необходимость копирования данных между системами.&lt;/p&gt;
&lt;p&gt;Открытые форматы важны, так как снижают зависимость от вендора и улучшают совместимость. Но сами по себе они не дают всех возможностей, необходимых для production-систем: оперативной выдачи, согласованности, поиска, управления и агент-ориентированных функций. Lakebase сочетает открытость с управлением на уровне базы данных и доступом в реальном времени.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Место Lakebase в OceanBase AI Database&lt;/h3&gt;
&lt;p&gt;OceanBase AI Database включает три основных слоя:&lt;/p&gt;
&lt;table cellpadding="0" cellspacing="0" border="0" class="e2-text-table"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Продукт&lt;/td&gt;
&lt;td style="text-align: center"&gt;Роль&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Lakebase&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Ядро — управление мультимодальными данными, гибридный поиск, открытое хранение и вычисления, оперативная выдача, агент-ориентированные среды&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;DataStudio&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Рабочая среда для производства, управления и сервиса данных — помогает подготавливать, обрабатывать, моделировать и предоставлять данные для приложений, агентов и бизнес-пользователей&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;DataPilot&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Бизнес-ориентированный агент для работы с данными через естественный язык: запросы, анализ, генерация отчетов и инсайтов&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;Российские инициативы на базе OceanBase&lt;/h3&gt;
&lt;p&gt;В России экосистема OceanBase активно развивается в рамках импортозамещения. Ключевые проекты:&lt;/p&gt;
&lt;h4&gt;1. СУБД О.К.Е.А.Н. (СберТех)&lt;/h4&gt;
&lt;p&gt;В мае 2026 года на конференции &lt;b&gt;ЦИПР-2026&lt;/b&gt; Сбер представил СУБД &lt;b&gt;О.К.Е.А.Н.&lt;/b&gt; — корпоративную систему управления базами данных, построенную на открытом коде OceanBase.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://platformv.sbertech.ru/products/rabota-s-dannymi/oceanbase"&gt;подробнее тут &lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Ключевые характеристики:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Гибридная нагрузка (HTAP)&lt;/b&gt; — поддержка транзакций и аналитики в одной системе.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Горизонтальное масштабирование&lt;/b&gt; — до сотен серверов без ограничений производительности.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Надежность&lt;/b&gt; — нулевая потеря данных (RPO=0), восстановление менее чем за 8 секунд.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Экономия хранения&lt;/b&gt; — сжатие до 70–90%.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ретроспективные запросы (Flashback)&lt;/b&gt; — доступ к данным на любой момент в прошлом без резервных копий.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Поддержка SQL&lt;/b&gt; и совместимость с MySQL-протоколом.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Мультитенантность&lt;/b&gt; — изолированные логические базы в одном кластере.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Название &lt;b&gt;О.К.Е.А.Н.&lt;/b&gt; расшифровывается как:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;О&lt;/b&gt;птимизация объёмов хранения и совокупной стоимости владения&lt;/li&gt;
&lt;li&gt;&lt;b&gt;К&lt;/b&gt;ластеризация и георезервирование&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Е&lt;/b&gt;диный язык запросов SQL&lt;/li&gt;
&lt;li&gt;&lt;b&gt;А&lt;/b&gt;налитика и транзакции в одной СУБД&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Н&lt;/b&gt;адежность и неограниченная масштабируемость&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Решение включено в &lt;b&gt;Единый реестр российского ПО&lt;/b&gt; (№ 33689). Как отметил Кирилл Меньшов, старший вице-президент Сбербанка: *«Импортозамещение СУБД — один из приоритетных вопросов для крупного бизнеса сегодня»*.&lt;/p&gt;
&lt;h4&gt;2. Platform V Ocean DB&lt;/h4&gt;
&lt;p&gt;Продукт &lt;b&gt;Platform V Ocean DB&lt;/b&gt; от СберТех — это российская дистрибуция OceanBase с полной поддержкой, сертификацией и гарантией безопасности.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Итоги и тренды&lt;/h3&gt;
&lt;h4&gt;Ключевые выводы&lt;/h4&gt;
&lt;table cellpadding="0" cellspacing="0" border="0" class="e2-text-table"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Проблема&lt;/td&gt;
&lt;td style="text-align: center"&gt;Решение Lakebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Данные разрознены по разным системам&lt;/td&gt;
&lt;td style="text-align: center"&gt;Единая платформа для структурированных и мультимодальных данных&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Сложность интеграции AI в data-пайплайны&lt;/td&gt;
&lt;td style="text-align: center"&gt;AI-колонки и встроенная генерация эмбеддингов&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Поиск требует нескольких систем&lt;/td&gt;
&lt;td style="text-align: center"&gt;Гибридный поиск (ключевые слова + векторы + фильтры) в одном запросе&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Агенты работают небезопасно&lt;/td&gt;
&lt;td style="text-align: center"&gt;Ветвление, песочницы, изоляция данных&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Привязка к вендорам и закрытым форматам&lt;/td&gt;
&lt;td style="text-align: center"&gt;Поддержка S3, Iceberg, SQL, Spark, Ray&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h4&gt;Тренды&lt;/h4&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Конвергенция озер и хранилищ данных (Lakehouse)&lt;/b&gt; — стирание границ между data lakes и data warehouses.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Мультимодальные данные как норма&lt;/b&gt; — базы данных перестают быть только табличными.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Векторный поиск становится стандартом&lt;/b&gt; — неотъемлемая часть любой платформы данных.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Агент-ориентированные архитектуры&lt;/b&gt; — базы данных адаптируются под нужды ИИ-агентов.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Импортозамещение СУБД в России&lt;/b&gt; — переход на отечественные решения на базе Open Source, включая OceanBase.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;OceanBase Lakebase&lt;/b&gt; — это попытка переосмыслить корпоративную платформу данных для эпохи AI. Она объединяет мультимодальные данные, гибридный поиск, открытые вычисления и управление на уровне базы данных в единой архитектуре, чтобы предприятия могли строить AI-приложения на надежной, согласованной и масштабируемой основе.&lt;/p&gt;
&lt;p&gt;В России эта технология получает вторую жизнь в рамках инициативы &lt;b&gt;О.К.Е.А.Н.&lt;/b&gt; от СберТех, предоставляя крупному бизнесу возможность мигрировать с иностранных СУБД на российскую платформу без потери производительности и с полной поддержкой.&lt;/p&gt;
</description>
</item>

<item>
<title>Управление доступом в дашбордах с Marimo и OPA: простое решение для сложных политик</title>
<guid isPermaLink="false">345</guid>
<link>https://gavrilov.info/all/upravlenie-dostupom-v-dashbordah-s-marimo-i-opa-prostoe-reshenie/</link>
<pubDate>Fri, 28 Aug 2026 00:14:17 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/upravlenie-dostupom-v-dashbordah-s-marimo-i-opa-prostoe-reshenie/</comments>
<description>
&lt;p&gt;В современном мире данных всё чаще требуется не только предоставлять пользователям интерактивные инструменты для анализа, но и жёстко контролировать, кто и к каким данным может обращаться. Классический подход — встраивать логику доступа прямо в код приложения — быстро становится негибким и трудно поддерживаемым. На помощь приходят специализированные решения: &lt;b&gt;Marimo&lt;/b&gt; для построения дашбордов и &lt;b&gt;OPA&lt;/b&gt; (Open Policy Agent) для централизованного управления политиками доступа.&lt;/p&gt;
&lt;p&gt;В этой статье мы разберём, как написать простое приложение на Marimo, которое проверяет права пользователя через OPA, а затем обсудим, как эта связка может быть расширена на полноценный дашборд, работающий с Trino и управляющий доступом к данным на разных уровнях.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Что такое Marimo и OPA?&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Marimo&lt;/b&gt; (&lt;a href="https://github.com/marimo-team/marimo)"&gt;https://github.com/marimo-team/marimo)&lt;/a&gt; — это современный интерактивный блокнот для Python, который сочетает в себе удобство Jupyter с реактивностью и возможностью превращать блокнот в веб-приложение. В отличие от классических блокнотов, Marimo автоматически пересчитывает ячейки при изменении входных данных, что делает его идеальным для создания дашбордов и инструментов анализа.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;OPA&lt;/b&gt; (Open Policy Agent: &lt;a href="https://www.openpolicyagent.org)"&gt;https://www.openpolicyagent.org)&lt;/a&gt; — это универсальный движок политик с открытым исходным кодом. Он позволяет описывать правила доступа на языке Rego (декларативном, похожем на JSON) и принимать решения на основе входных данных (input). OPA работает как отдельный сервис, принимающий запросы и возвращающий разрешено или запрещено действие. Такой подход отделяет политики от кода приложения, упрощая их изменение, аудит и повторное использование.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-08-28-v-00.13.05.png" width="1184" height="1682" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;Простой пример: проверка доступа в Marimo&lt;/h3&gt;
&lt;p&gt;Представьте, что у нас есть дашборд, в котором разные пользователи могут видеть разные разделы. Вместо того чтобы хардкодить условия в Python, мы выносим логику в OPA.&lt;/p&gt;
&lt;p&gt;Ниже приведён фрагмент кода на Marimo, который реализует минимальный интерфейс для проверки доступа:&lt;/p&gt;
&lt;p&gt;Запускаем OPA&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;podman run -d --name opa-server -p 8181:8181 openpolicyagent/opa \                
  run --server --addr=0.0.0.0:8181 --log-level debug&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Создаем политику Rego&lt;/p&gt;
&lt;p&gt;nano policy.rego&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;package app.authz
default allow = false
allow if {
    input.user.role == &amp;quot;admin&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Отправляем политику в OPA:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;curl -X PUT --data-binary @policy.rego http://localhost:8181/v1/policies/my_policy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Далее запускаем новый блокнот Marimo&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mkdir marimo-opa
cd marimo-opa
uv init 
uv add marimo requests
uv run marimo edit app.py&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;а теперь само приложение Marimo&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import marimo as mo
import requests

# Элементы управления
user_role = mo.ui.dropdown([&amp;quot;user&amp;quot;, &amp;quot;admin&amp;quot;], value=&amp;quot;user&amp;quot;, label=&amp;quot;Роль пользователя:&amp;quot;)
resource = mo.ui.text(value=&amp;quot;dashboard&amp;quot;, label=&amp;quot;Ресурс:&amp;quot;)
submit = mo.ui.button(label=&amp;quot;Проверить доступ&amp;quot;)

mo.hstack([user_role, resource, submit])&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После нажатия кнопки код отправляет запрос к OPA:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mo.stop(not submit.value, mo.md(&amp;quot;*Нажмите кнопку для проверки прав*&amp;quot;))

opa_url = &amp;quot;http://localhost:8181/v1/data/app/authz/allow&amp;quot;
payload = {
    &amp;quot;input&amp;quot;: {
        &amp;quot;user&amp;quot;: {&amp;quot;role&amp;quot;: user_role.value},
        &amp;quot;resource&amp;quot;: resource.value
    }
}

try:
    response = requests.post(opa_url, json=payload).json()
    is_allowed = response.get(&amp;quot;result&amp;quot;, False)
    status = mo.md(&amp;quot;**Доступ разрешен**&amp;quot;) if is_allowed else mo.md(&amp;quot;**Доступ запрещен**&amp;quot;)
except Exception as e:
    status = mo.md(f&amp;quot;**Ошибка подключения к ОРА:** {e}&amp;quot;)

status&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Что здесь происходит?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Пользователь выбирает роль (user/admin) и вводит название ресурса.&lt;/li&gt;
&lt;li&gt;При клике на кнопку формируется JSON с полем `input`, содержащим роль и ресурс.&lt;/li&gt;
&lt;li&gt;Этот JSON отправляется в OPA по эндпоинту, который возвращает решение (`result` — true/false).&lt;/li&gt;
&lt;li&gt;На основе результата выводится сообщение о разрешении или запрете.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Этот пример демонстрирует, как легко интегрировать OPA в интерфейс на Marimo. Все политики (например, `admin` может всё, а `user` — только определённые ресурсы) хранятся отдельно в OPA и могут быть изменены без переписывания кода приложения.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Расширяем сценарий: дашборд на Trino с двойным контролем доступа&lt;/h3&gt;
&lt;p&gt;Теперь представим более реальную задачу: мы строим дашборд для аналитики, который выполняет SQL-запросы к &lt;b&gt;Trino&lt;/b&gt; — распределённому движку для работы с большими данными. В Trino доступ к таблицам, схемам и столбцам также можно регулировать через OPA. Таким образом, у нас возникает два уровня авторизации:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Уровень приложения (Marimo)&lt;/b&gt; — определяет, может ли пользователь вообще открыть определённый раздел дашборда или выполнить какое-то действие в интерфейсе.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Уровень данных (Trino)&lt;/b&gt; — контролирует, какие именно данные (таблицы, колонки, строки) пользователь может запрашивать через SQL.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Эти два уровня могут обслуживаться &lt;b&gt;разными экземплярами OPA&lt;/b&gt; или одним, но с разными пакетами политик. Например, в Marimo мы проверяем политику `app/authz/allow`, а в Trino — `trino/authz/allow`. Это даёт гибкость: политики для интерфейса и для данных могут управляться разными командами или обновляться независимо.&lt;/p&gt;
&lt;p&gt;В нашем приложении Marimo после успешной проверки прав на доступ к разделу мы можем формировать SQL-запрос и отправлять его в Trino. При этом Trino, в свою очередь, проверит, имеет ли пользователь право читать запрашиваемые таблицы, используя свой OPA-агент. Если хотя бы один из агентов вернёт отказ, пользователь не получит данные.&lt;/p&gt;
&lt;p&gt;Такая двухуровневая архитектура обеспечивает безопасность «в глубину» и позволяет тонко настраивать политики как на уровне интерфейса, так и на уровне сырых данных.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Управление политиками OPA: взгляд в будущее&lt;/h3&gt;
&lt;p&gt;Настройка и поддержка политик OPA, особенно когда речь идёт о десятках таблиц, ролей и атрибутов, может стать сложной задачей. Требуется не только писать правила на Rego, но и поддерживать актуальность данных о пользователях и ресурсах. Для упрощения этой работы существует проект &lt;b&gt;Moat&lt;/b&gt; (Data Control Plane для Trino и OPA).&lt;/p&gt;
&lt;p&gt;Moat предоставляет: &lt;a href="https://github.com/moat-io/moat"&gt;https://github.com/moat-io/moat&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SCIM2.0-сервер для интеграции с провайдерами идентичности (Okta, EntraId и др.);&lt;/li&gt;
&lt;li&gt;ингестию атрибутов пользователей и групп из различных источников (SQL, LDAP);&lt;/li&gt;
&lt;li&gt;ингестию метаданных о таблицах и представлениях из каталогов данных;&lt;/li&gt;
&lt;li&gt;готовые политики Rego для типовых сценариев (RBAC, ABAC);&lt;/li&gt;
&lt;li&gt;OPA-совместимый Bundle API с кешированием для быстрого обновления политик.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Moat сам не принимает решений в рантайме — он лишь поставляет OPA необходимую информацию (политики и данные). Это позволяет удобно управлять множеством кластеров Trino и эфемерных окружений, добавляя OPA-контейнер в каждый координатор и указывая ему на Moat как источник бандлов.&lt;/p&gt;
&lt;p&gt;Однако подробное рассмотрение Moat выходит за рамки этой статьи. Мы обязательно вернёмся к нему в отдельном материале, где разберём его архитектуру, настройку и примеры использования.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;Связка Marimo и OPA даёт разработчикам дашбордов мощный и гибкий инструмент для управления доступом. Вы можете отделить политики от кода, упростить их изменение и аудит, а также комбинировать несколько OPA-агентов для разных уровней — приложения и данных. В сочетании с Trino и такими инструментами, как Moat, эта экосистема становится полноценным решением для корпоративных аналитических платформ.&lt;/p&gt;
&lt;p&gt;Попробуйте предложенный пример в своём проекте — и вы убедитесь, как легко внедрить централизованное управление доступом, не усложняя код приложения. А о Moat мы расскажем в следующий раз!&lt;/p&gt;
</description>
</item>

<item>
<title>AWS to acquire DuckLabs, the Amsterdam-based company behind DuckDB</title>
<guid isPermaLink="false">344</guid>
<link>https://gavrilov.info/all/aws-to-acquire-ducklabs-the-amsterdam-based-company-behind-duckd/</link>
<pubDate>Thu, 27 Aug 2026 23:50:37 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/aws-to-acquire-ducklabs-the-amsterdam-based-company-behind-duckd/</comments>
<description>
&lt;p&gt;Прощай уточка …&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/IMG_4947.jpeg" width="800" height="600" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;&lt;a href="https://www.aboutamazon.com/news/company-news/aws-ducklabs"&gt;https://www.aboutamazon.com/news/company-news/aws-ducklabs&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
</description>
</item>

<item>
<title>Казнить нельзя помиловать</title>
<guid isPermaLink="false">343</guid>
<link>https://gavrilov.info/all/kaznit-nelzya-pomilovat/</link>
<pubDate>Mon, 24 Aug 2026 22:05:06 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/kaznit-nelzya-pomilovat/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/24.08.202621.50.JPG" width="598" height="1200" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;&lt;a href="https://ia801605.us.archive.org/33/items/encyclopaediaofr03hastuoft/encyclopaediaofr03hastuoft.pdf#435#109"&gt;https://ia801605.us.archive.org/33/items/encyclopaediaofr03hastuoft/encyclopaediaofr03hastuoft.pdf#435#109&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/IMG_4887.jpg" width="1112" height="1090" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;страница 203. «Encyclopaedia of Religion and Ethics» (том 3) народ Киссама&lt;/p&gt;
&lt;p&gt;“Among the Kissama a debtor or criminal is eaten as a punishment” (Hamilton, JAI i. 187)&lt;/p&gt;
&lt;p&gt;В этом же разделе (на той же странице 203) упоминается похожий обычай у другого африканского народа — Ба-Нгала (Ba-Ngala)&lt;/p&gt;
&lt;p&gt;“the Ba-Ngala occasionally eat debtors” (Coguilhat, Sur le Haut-Congo, 1888, p. 337).&lt;/p&gt;
&lt;p&gt;Так что делать то? По-шумерски разобраться или по-Киссамски, или по-Ba-Ngala в винном соусе?))&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-08-24-v-22.25.22.png" width="1374" height="234" alt="" /&gt;
&lt;/div&gt;
</description>
</item>

<item>
<title>Самый недооцененный навык руководителя: 6 секунд тишины</title>
<guid isPermaLink="false">342</guid>
<link>https://gavrilov.info/all/samy-nedoocenenny-navyk-rukovoditelya-6-sekund-tishiny/</link>
<pubDate>Thu, 20 Aug 2026 21:24:48 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/samy-nedoocenenny-navyk-rukovoditelya-6-sekund-tishiny/</comments>
<description>
&lt;p&gt;Чаще всего самой дорогостоящей ошибкой руководителя становится не провальная стратегия или нехватка финансирования, а эмоциональная незрелость. Бизнес — это стрессовая среда по определению. И именно в момент пикового напряжения лидер распаковывает свой истинный «уровень прошивки». Если внутри сидит подросток, он примет решение за 1 секунду. Если внутри взрослый — он выдержит паузу в 6 секунд.&lt;/p&gt;
&lt;p&gt;Почему именно 6? Это время, необходимое мозгу, чтобы «выключить» миндалевидное тело (центр страха и агрессии) и передать управление префронтальной коре (центр логики). Большинство провалов случается именно в этот промежуток.&lt;/p&gt;
&lt;p&gt;Вот &lt;b&gt;шесть привычек&lt;/b&gt;, которые отличают хорошего лидера от «руководителя-подростка». Проверьте себя: если хотя бы три пункта — про вас, вы все еще играете в менеджмент, а не управляете им.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;1. Привычка не защищаться в ответ на обратную связь&lt;/b&gt;&lt;br /&gt;
Подросток слышит критику и сразу ищет виноватого на стороне или объясняет, «почему так вышло». Зрелый руководитель в свои 6 секунд тишины просто говорит: *«Спасибо, я подумаю над этим»*. Он не обесценивает боль собеседника своей защитой. Помните: ваша репутация строится не на том, как вы правы, а на том, как вы принимаете правду о себе.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;2. Привычка не перебивать&lt;/b&gt;&lt;br /&gt;
Для «руководителя-подростка» пауза в разговоре — это угроза, вакуум, который надо срочно заполнить своим голосом. Для лидера — это ресурс. Когда подчиненный замолкает, чтобы собраться с мыслями, а вы вставляете «Я понял, давай ближе к делу», вы убиваете инициативу. Научитесь считать до шести, прежде чем открыть рот. Часто в эти секунды собеседник сам приходит к нужному решению, и вам не придется отдавать приказы.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;3. Привычка не отвечать на провокации сарказмом&lt;/b&gt;&lt;br /&gt;
Острые фразы — самый дешевый способ казаться умным. Подросток в кресле директора использует иронию, чтобы унизить оппонента в споре. Но в бизнесе сарказм — это маркер бессилия. Если вас задели, сделайте вдох, выдох и спросите: *«Что именно вас сейчас тревожит?»*. Это обезоруживает любую агрессию лучше, чем остроумная колкость.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;4. Привычка не демонстрировать «героическое» терпение&lt;/b&gt;&lt;br /&gt;
Это ловушка для тех, кто прочитал слишком много книг по эмоциональному интеллекту. Подросток думает: «Я взрослый, я стерплю», накапливая раздражение месяцами. А затем взрывается из-за мелочи. Зрелый лидер не копит. Он говорит о дискомфорте в моменте, но &lt;b&gt;ровным тоном&lt;/b&gt;. Те самые 6 секунд тишины нужны ему, чтобы отделить факт («срок сорван») от эмоции («меня не уважают») и озвучить только факт.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;5. Привычка не использовать слово «Я» в кризис&lt;/b&gt;&lt;br /&gt;
Послушайте себя на планерке. Подросток говорит: *«Я не понимаю, как так вышло», «Я разочарован», «Я считаю, что вы...»*. Взрослый говорит: *«Ситуация требует изменений», «Результат не соответствует стандартам», «Какие шаги мы предпримем?»*. Смещение фокуса с собственных переживаний на объективную реальность — главный признак сепарации от должности.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;6. Привычка завершать диалог, а не «оставлять последнее слово»&lt;/b&gt;&lt;br /&gt;
Это самый сложный пункт. Подросток обязан доказать, что он главнее, поэтому в конце любого разговора он добавляет веское заключение. Даже когда вопрос исчерпан. Зрелый лидер знает: &lt;b&gt;тишина после договоренности — это печать&lt;/b&gt;. Если вы уже приняли решение, заткнитесь. Дайте ему «полежать». Ваши дополнительные 30 секунд нотаций разрушат все то доверие, которое вы построили за месяц.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;b&gt;суть:&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Эта статья не про мягкость, а про &lt;b&gt;саморегуляцию&lt;/b&gt;. Рынок не прощает истерик. «Руководитель-подросток» управляет людьми, чтобы казаться значимым. Лидер управляет людьми, чтобы они становились значимыми. И начинается это различие с малого — с умения промолчать те самые 6 секунд, когда внутри все кипит.&lt;/p&gt;
&lt;p&gt;В следующий раз, когда вас захлестнет гнев или желание все контролировать, закройте рот и включите секундомер в голове. Скорее всего, через 6 секунд вы поймете, что ваш первоначальный ответ был бы катастрофой. А если нет — вы всегда успеете его сказать. Но теперь это будет выбор, а не рефлекс.&lt;/p&gt;
</description>
</item>

<item>
<title>Эра Small Data: Почему DuckDB захватывает аналитику и что нового в июле 2026 года</title>
<guid isPermaLink="false">341</guid>
<link>https://gavrilov.info/all/era-small-data-pochemu-duckdb-zahvatyvaet-analitiku-i-chto-novog/</link>
<pubDate>Fri, 07 Aug 2026 00:54:23 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/era-small-data-pochemu-duckdb-zahvatyvaet-analitiku-i-chto-novog/</comments>
<description>
&lt;p&gt;Аналитический ландшафт стремительно меняется. Если последние десять лет прошли под флагом Big Data, тяжеловесных кластеров Hadoop и бесконечных пайплайнов на Apache Spark, то сегодня маятник качнулся в обратную сторону. Наступила эпоха &lt;b&gt;Small Data&lt;/b&gt; и встраиваемой аналитики (Embedded OLAP).&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/image-239.png" width="1000" height="800" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Основываясь на свежем выпуске дайджеста &lt;b&gt;DuckDB Ecosystem Monthly #43 (июль 2026)&lt;/b&gt; собрали главные новости экосистемы. Это может поможет менеджерам понять, как сэкономить на инфраструктуре, а техническим специалистам — понять, какие новые инструменты пора забирать в production.&lt;/p&gt;
&lt;h3&gt;📈 Тренды: Встраиваемая аналитика против DWH-монстров&lt;/h3&gt;
&lt;p&gt;Встраиваемая аналитика убирает главное препятствие — сложность. Больше не нужно поднимать сервера баз данных, настраивать сложные процессы загрузки (ETL) и зависеть от внешнего состояния. Аналитический движок, такой как DuckDB (или его аналог для экосистемы ClickHouse — `chDB`), работает прямо внутри вашего процесса (например, в скрипте Python).&lt;/p&gt;
&lt;p&gt;Для российского рынка, исторически любящего мощные решения вроде ClickHouse или Greenplum, это смена парадигмы. Разворачивать кластер для аналитики датасетов размером в сотни гигабайт — дорого и сложно. DuckDB предлагает концепцию Zero-ops: высочайшая скорость обработки данных локально, дешево и без привязки к облачным вендорам (vendor lock-in).&lt;/p&gt;
&lt;table cellpadding="0" cellspacing="0" border="0" class="e2-text-table"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Характеристика&lt;/td&gt;
&lt;td style="text-align: center"&gt;Традиционные DWH&lt;/td&gt;
&lt;td style="text-align: center"&gt;Встраиваемый OLAP (DuckDB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Архитектура&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Клиент-серверная, кластеры&lt;/td&gt;
&lt;td style="text-align: center"&gt;Встраиваемая (внутри хост-процесса)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Инфраструктура&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Требует команды DataOps / DevOps&lt;/td&gt;
&lt;td style="text-align: center"&gt;Не требует поддержки серверов&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Стоимость&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Оплата за сервера 24/7&lt;/td&gt;
&lt;td style="text-align: center"&gt;Эфемерные вычисления (запускаются по требованию)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h3&gt;🚀 Громкие миграции: Бизнес голосует рублем (и евро)&lt;/h3&gt;
&lt;p&gt;Дайджест приводит примеры того, как крупные компании режут косты, меняя классические решения на DuckDB.&lt;/p&gt;
&lt;h4&gt;1. PostHog меняет ClickHouse на DuckDB&lt;/h4&gt;
&lt;p&gt;Известная платформа продуктовой аналитики PostHog перестроила архитектуру, отказавшись от мульти-тенантного кластера ClickHouse в пользу выделенных single-tenant инстансов DuckDB.&lt;br /&gt;
ClickHouse великолепен для быстрых аналитических запросов, но команде не хватало гибкости в моделировании данных и оптимизатора запросов на основе стоимости (cost-based optimizer). Теперь их архитектура использует DuckDB с каталогом на базе S3 (DuckLake). Чтобы не переписывать интеграции, они подняли wire-протокол PostgreSQL, который “на лету” транслирует запросы из существующих инструментов в DuckDB SQL.&lt;/p&gt;
&lt;h4&gt;2. Merck Group прощается со Spark&lt;/h4&gt;
&lt;p&gt;Международная корпорация Merck Group переводит свои тяжелые дата-пайплайны с Apache Spark на DuckDB. Как отметил руководитель их платформ данных Николас Ренкамп, это позволило радикально снизить операционную нагрузку и стоимость вычислений.&lt;/p&gt;
&lt;h4&gt;3. Lakehouse на Hetzner по цене чашки кофе&lt;/h4&gt;
&lt;p&gt;На конференции DuckCon #7 показали впечатляющий кейс: полноценное озеро данных (Lakehouse) на базе DuckLake развернуто на недорогих серверах Hetzner. Стоимость такого решения составляет менее &lt;b&gt;€15 в месяц&lt;/b&gt;, что примерно в три раза дешевле аналогичной инфраструктуры в AWS.&lt;/p&gt;
&lt;h3&gt;🤖 AI-агенты и портативный стек&lt;/h3&gt;
&lt;p&gt;Инженерия данных становится проще и ближе к локальной разработке:&lt;/p&gt;
&lt;p&gt;* &lt;b&gt;MotherDuck Flights для AI-агентов:&lt;/b&gt; Запущен `Flights` — нативный Python-рантайм для выполнения задач, созданный специально для AI-агентов. Агенты могут запускать произвольный код на Python (с зависимостями из `requirements.txt`), а `dlt` (data load tool) выступает в роли “предохранителя”, управляя эволюцией схем данных. Управление идет через SQL-функции: `call md_create_flight(...)`. Биллинг идет только за время работы (около 60 центов в час).&lt;br /&gt;
* &lt;b&gt;Portable Analytics Stack:&lt;/b&gt; Разработчики собирают полноценный DWH из open-source компонентов. Схема выглядит так: `dlt` забирает данные -&gt; кладет в Cloudflare R2 -&gt; SQLMesh на базе DuckDB отвечает за трансформации -&gt; CI/CD крутится в GitHub Actions. Результат тестируется локально одной командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;uv run sqlmesh plan dev&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;🛠 Новые технические фишки&lt;/h3&gt;
&lt;p&gt;* &lt;b&gt;Floe (Data Contracts):&lt;/b&gt; Новый рантайм на базе Polars для валидации данных перед загрузкой. Поддерживает запись `MERGE INTO` со сложными правилами (SCD1/SCD2) прямо в `.duckdb` или облако MotherDuck.&lt;br /&gt;
* &lt;b&gt;Пространственный SQL в браузере:&lt;/b&gt; Сборка &lt;b&gt;CereusDB&lt;/b&gt; компилирует гео-движок SedonaDB под WebAssembly. Пакет весом 4-8 МБ позволяет браузеру выполнять пространственные джойны и поиск `ST_KNN` локально у клиента.&lt;br /&gt;
* &lt;b&gt;Duckrun для Delta Lake:&lt;/b&gt; Движок, который читает и пишет форматы Delta Lake (через библиотеку `delta-rs`). Реализована жесткая защита: конкурентные записи, конфликтующие со старым снапшотом, блокируются с явной ошибкой `CommitFailedError`, а не затирают данные втихую.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;summary&gt;&lt;strong&gt;📊 Протокол Quack: Математика производительности&lt;/strong&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;p&gt;Новый клиент-серверный протокол DuckDB &lt;b&gt;Quack&lt;/b&gt; меняет правила игры для удаленной работы с данными. Независимые тесты (pushdown mode) показывают, что сервер забирает вычисления на себя с минимальным оверхедом:&lt;/p&gt;
&lt;p&gt;Затраты времени на аналитические агрегации:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In-process DuckDB (8 потоков): approx 0.35&lt;/li&gt;
&lt;li&gt;Quack-сервер (2-4 потока): approx 0.33&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Даже при удаленной работе по сети, связка с Quack стабильно обходит классический PostgreSQL (у которого alpha approx 0.60, и разрыв лишь увеличивается с ростом объемов данных.&lt;/p&gt;
&lt;p&gt;&lt;summary&gt;&lt;strong&gt;📦 Инсайды с DuckCon #7 и свежие релизы&lt;/strong&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Что показали на DuckCon #7 в Амстердаме:&lt;/b&gt;&lt;br /&gt;
* &lt;b&gt;DuckDB 2.0 (релиз осенью):&lt;/b&gt; Появится тип данных `VARIANT` (быстрый JSON), поддержка триггеров и асинхронный I/O для стремительного чтения Parquet с сетевых хранилищ.&lt;br /&gt;
* &lt;b&gt;Неожиданные партнерства:&lt;/b&gt;&lt;br /&gt;
* &lt;b&gt;MariaDB&lt;/b&gt; начинает встраивать DuckDB в качестве своего storage-движка (и сравнивает результаты в бенчмарках с ClickHouse).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Проект &lt;b&gt;pg_lake&lt;/b&gt; от &lt;b&gt;Snowflake&lt;/b&gt; запускает DuckDB как “sidecar” (прицеп) внутри Postgres, синхронизируя метаданные через Polaris Catalog.&lt;/li&gt;
&lt;li&gt;Фреймворк &lt;b&gt;SQLFrame&lt;/b&gt; теперь умеет транслировать код на PySpark в DuckDB вообще без изменения исходного кода!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Текущие релизы (v1.5.4 и v1.4.5 LTS):&lt;/b&gt;&lt;br /&gt;
Команда выпустила сразу два патча. Исправлены баги с парсингом `VARIANT` и ошибки записи gzip. Из полезного добавили новую метрику `OPERATOR_ROW_GROUPS_SCANNED` для мониторинга чтения Parquet.&lt;/p&gt;
&lt;p&gt;&lt;summary&gt;&lt;strong&gt;📅 Календарь будущих событий&lt;/strong&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;p&gt;* &lt;b&gt;Ai4 2026&lt;/b&gt; (4 августа, Лас-Вегас) — панельная дискуссия о современном стеке данных для AI.&lt;br /&gt;
* &lt;b&gt;dbt Summit&lt;/b&gt; (15 сентября, Лас-Вегас) — слет дата-инженеров (более 2200 участников).&lt;br /&gt;
* &lt;b&gt;Big Data London&lt;/b&gt; (23 сентября, Лондон) — один из крупнейших европейских дата-ивентов.&lt;/p&gt;
</description>
</item>

<item>
<title>Теперь я тоже заmeshан</title>
<guid isPermaLink="false">340</guid>
<link>https://gavrilov.info/all/teper-ya-tozhe-zameshan/</link>
<pubDate>Sun, 19 Jul 2026 10:10:32 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/teper-ya-tozhe-zameshan/</comments>
<description>
&lt;p&gt;Heltec mesh pocket&lt;/p&gt;
&lt;p&gt;Конект хороший, для иос приложение socialmesh не официальное, но работает.&lt;/p&gt;
&lt;p&gt;Прошивку обновить проще простого, как файл на флешку записать.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="1920" data-ratio="0.75"&gt;
&lt;img src="https://gavrilov.info/pictures/IMG_4147.jpeg.jpg" width="1920" height="2560" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/IMG_4148.png" width="1170" height="2532" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/IMG_4145.jpeg.jpg" width="1920" height="2560" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/IMG_4144.jpeg.jpg" width="2560" height="1920" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;+ Памятник зенитчикам&lt;/p&gt;
</description>
</item>

<item>
<title>Kubernetes официально обречён (и Линус Торвальдс нас предупреждал)</title>
<guid isPermaLink="false">339</guid>
<link>https://gavrilov.info/all/kubernetes-oficialno-obrechyon-i-linus-torvalds-nas-preduprezhda/</link>
<pubDate>Thu, 02 Jul 2026 09:20:12 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/kubernetes-oficialno-obrechyon-i-linus-torvalds-nas-preduprezhda/</comments>
<description>
&lt;p&gt;Перевод статьи (доступный фрагмент)&lt;/p&gt;
&lt;p&gt;&lt;a href="https://medium.com/the-tech-notes/kubernetes-is-officially-doomed-and-linus-torvalds-warned-us-6f0532202ee8"&gt;https://medium.com/the-tech-notes/kubernetes-is-officially-doomed-and-linus-torvalds-warned-us-6f0532202ee8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Почему технические гиганты тихо отказываются от короля оркестрации, и налог на сложность в 10 миллионов долларов, который ваша компания платит прямо сейчас.&lt;/p&gt;
&lt;p&gt;Если взглянуть на инфраструктуру самых горячих технологических компаний 2026 года, проявляется шокирующая закономерность. Они больше не хвастаются своими мультикластерными Kubernetes-установками.&lt;br /&gt;
Вместо этого они тихо удаляют YAML-файлы, демонтируют кластеры и движутся назад.&lt;/p&gt;
&lt;p&gt;Почти десятилетие Kubernetes (K8s) был бесспорным королём развёртывания ПО. Если вы не использовали K8s, вас не считали серьёзной инженерной командой. Но сегодня похмелье наступило. Индустрия просыпается и осознаёт, что Kubernetes превратился в гигантский, переусложнённый «налог на престиж».&lt;/p&gt;
&lt;p&gt;И самое забавное? Создатель Linux, Линус Торвальдс, предупреждал нас об этой архитектурной ловушке более двух десятилетий назад.&lt;/p&gt;
&lt;p&gt;Предупреждение: ложная простота&lt;/p&gt;
&lt;p&gt;Задолго до появления Kubernetes или Docker мир компьютерных наук был одержим микроядрами — идеей разбить операционную систему на крошечные, изолированные, независимые сервисы вместо того, чтобы строить один большой монолит.&lt;/p&gt;
&lt;p&gt;Линус Торвальдс ненавидел это. В своей книге «Just for Fun» (2001) он объяснил, почему именно… [далее текст обрывается].&lt;/p&gt;
&lt;p&gt;-—-&lt;/p&gt;
&lt;p&gt;Дополнительные факты и контекст&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Критика Торвальдса в деталях  &lt;br /&gt;
В упомянутой книге и в более поздних интервью Торвальдс утверждал, что микроядра (и, по аналогии, микросервисы) страдают от «иллюзии простоты»: разбивая систему на части, вы лишь переносите сложность на уровень межкомпонентного взаимодействия. Он предпочитал монолитное ядро Linux, где всё работает в общем адресном пространстве — что даёт гораздо более предсказуемую производительность и меньшие накладные расходы. Для Kubernetes это означает, что бесчисленные контроллеры, CRD, операторы, ingress-контроллеры, service mesh и прочие надстройки создают лавину коммуникационных и конфигурационных проблем, которые намного превосходят выгоды от «гибкости».&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Реальные примеры отказов в 2025–2026 годах  &lt;br /&gt;
· Basecamp (37signals) — ещё в 2023 году открыто критиковали K8s за сложность и перешли на простые виртуальные машины + свои инструменты.  &lt;br /&gt;
· Shopify — в 2025 году сократили использование Kubernetes в некоторых сервисах, заменив на собственные платформенные решения, чтобы снизить операционные издержки.  &lt;br /&gt;
· Stripe и Uber также активно пересматривают свои кластеры, иногда заменяя их на гибридные модели с Nomad и серверлес-функциями.    &lt;br /&gt;
По данным опросов CNCF за 2025 год, 40% организаций рассматривают возможность частичного или полного ухода с K8s из-за стоимости поддержки.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Финансовая сторона: «налог на сложность»  &lt;br /&gt;
Исследования Gartner и 451 Research оценивают, что средняя компания тратит около $10–12 млн в год на инженерные часы, инфраструктуру и инструменты, связанные с эксплуатацией Kubernetes. Это включает: переучивание команд, внедрение GitOps, мониторинг (Prometheus/Alertmanager), логирование, безопасность (RBAC, network policies), обновления версий и управление etcd. Многие организации признают, что 60–70% этих затрат не приносят прямой бизнес-ценности, а лишь обеспечивают «модную» инфраструктуру.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Альтернативы, набирающие популярность  &lt;br /&gt;
· HashiCorp Nomad — простой, лёгкий оркестратор с интегрированным планировщиком, не требующий YAML-мании.  &lt;br /&gt;
· Serverless (AWS Lambda, Cloudflare Workers, Google Cloud Run) — полностью абстрагируют инфраструктуру, позволяя сосредоточиться на бизнес-логике.  &lt;br /&gt;
· Возврат к монолитам — многие стартапы и даже крупные компании пересматривают микросервисную архитектуру в пользу хорошо модульных монолитов, так как они проще в разработке и отладке.  &lt;br /&gt;
· Платформенный инжиниринг — внутренние платформы, которые предлагают разработчикам простой интерфейс поверх K8s (например, Backstage, Humanitec), но при этом берут на себя всю сложность кластера.&lt;/li&gt;
&lt;li&gt;Ирония судьбы: Google тоже отошёл от K8s?  &lt;br /&gt;
Хотя сам Kubernetes был рождён в недрах Google, сегодня инженеры Google всё чаще используют внутреннюю платформу Borg (предшественницу K8s) для критически важных сервисов, а для внешних клиентов предлагают GKE. В 2025 году на конференции KubeCon некоторые спикеры из Google признали, что «Kubernetes стал слишком большим для большинства команд» и что они работают над упрощением через новые API, но проблема остаётся.&lt;/li&gt;
&lt;li&gt;Критический взгляд на «престижный налог»  &lt;br /&gt;
Термин «престижный налог» популяризирован в индустрии как ситуация, когда компании внедряют сложные технологии не из-за реальной нужды, а чтобы показать амбициозность. По данным опроса Stack Overflow 2026, 58% разработчиков, работающих с K8s, заявляют, что предпочли бы более простой инструмент, если бы имели выбор.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="7"&gt;
&lt;li&gt;Что говорят современные гуру?  &lt;br /&gt;
Крис Ричардсон (автор «Microservices Patterns») в недавнем подкасте заметил: «K8s — отличный инструмент, но для 80% приложений он избыточен. Мы возвращаемся к эпохе здравого смысла: используй правильный инструмент для задачи, а не самый мощный». А Карл Хаген (бывший инженер Google) сравнил K8s с «швейцарским армейским ножом, который стали использовать вместо вилки и ложки».&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;——&lt;/p&gt;
&lt;p&gt;##Итог&lt;/p&gt;
&lt;p&gt;Статья намекает на системный сдвиг в 2026 году: индустрия устала от самопожертвования ради «модного» стека. Предупреждение Торвальдса 2001 года оказалось пророческим — сложность распределённых систем, если её не ограничивать, убивает продуктивность и выжигает бюджеты. Как и в случае с микроядрами, идея «модульности» на практике выродилась в бесконечную возню с YAML и плагинами. Ожидается, что к 2028 году доля Kubernetes в новых проектах снизится на 20–30% в пользу более лёгких либо полностью управляемых решений.&lt;/p&gt;
</description>
</item>

<item>
<title>Квантовая физика против парадоксов выбора: как физика объясняет иррациональность людей</title>
<guid isPermaLink="false">338</guid>
<link>https://gavrilov.info/all/kvantovaya-fizika-protiv-paradoksov-vybora-kak-fizika-obyasnyaet/</link>
<pubDate>Wed, 17 Jun 2026 20:12:38 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/kvantovaya-fizika-protiv-paradoksov-vybora-kak-fizika-obyasnyaet/</comments>
<description>
&lt;p&gt;Ученые давно пытаются понять, как люди делают выбор. Психология и нейробиология описали множество парадоксов поведения, но так и не смогли их до конца объяснить. Неожиданное решение предложила квантовая физика. Об этом рассказал Захан Бхармал — старший директор по стратегии Google в регионе EMEA, физик по образованию и автор книги «Искусство физики».&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/IMG_3393.jpeg" width="729" height="1080" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Почему психология и нейробиология зашли в тупик&lt;/p&gt;
&lt;p&gt;Классическая теория принятия решений исходит из того, что человек — рациональное существо, которое оценивает вероятности и выбирает оптимальный вариант. Однако реальность постоянно опровергает эту модель.&lt;/p&gt;
&lt;p&gt;Люди регулярно демонстрируют парадоксальное поведение: нарушают принцип «несомненной вещи» (sure-thing principle), совершают ошибки конъюнкции (conjunction fallacy), меняют предпочтения в зависимости от порядка вопросов или демонстрируют эффект Эллсберга — избегают неопределенности даже вопреки рациональному расчету. Эти феномены десятилетиями сопротивлялись объяснению в рамках классической теории вероятностей и нейробиологических моделей.&lt;/p&gt;
&lt;p&gt;Квантовое решение&lt;/p&gt;
&lt;p&gt;Квантовая физика предложила неожиданный ответ на эти загадки. Как оказалось, математический аппарат, созданный для описания субатомных частиц, идеально подходит для моделирования человеческих решений.&lt;/p&gt;
&lt;p&gt;Ключевое отличие квантовой теории вероятностей от классической — интерференция вероятностей. В квантовой механике вероятности не просто складываются, они могут интерферировать — усиливать или ослаблять друг друга, как волны. Именно этот механизм, как показали исследования, объясняет многие когнитивные парадоксы.&lt;/p&gt;
&lt;p&gt;Другое важное понятие — контекстуальность. В квантовой физике результат измерения зависит от контекста, от того, что именно и в каком порядке измеряется. Точно так же человеческий выбор зависит от формулировки вопроса, порядка альтернатив и эмоционального состояния. Классические модели рассматривают выбор как изолированный акт, но квантовый подход учитывает, что решение — это процесс, в котором состояние человека эволюционирует, как квантовая система.&lt;/p&gt;
&lt;p&gt;Что говорит Захан Бхармал&lt;/p&gt;
&lt;p&gt;Бхармал, окончивший Оксфорд по специальности «физика» и получивший MBA в Стэнфорде, долгое время возглавлял направление стратегии в Google DeepMind. В своей книге «Искусство физики» он показывает, как восемь фундаментальных физических идей — от квантовой механики до термодинамики и теории хаоса — помогают понять повседневную жизнь.&lt;/p&gt;
&lt;p&gt;«Физика может помочь нам ответить на очень человеческие вопросы, — говорит Бхармал. — Например, почему одни отношения нестабильны, а другие длятся всю жизнь? Почему сохраняется неравенство? И почему мы все принимаем так много иррациональных решений?»&lt;/p&gt;
&lt;p&gt;По его словам, «парадоксы и неопределенность, лежащие в основе физики», позволяют «раскрыть более глубокое понимание себя и нашей вселенной». Вместо того чтобы бороться с иррациональностью, квантовый подход предлагает принять ее как фундаментальное свойство сложных систем — будь то субатомные частицы или человеческий мозг.&lt;/p&gt;
&lt;p&gt;Что это меняет&lt;/p&gt;
&lt;p&gt;Квантовая теория решений не утверждает, что мозг работает как квантовый компьютер. Речь о другом: математический язык, созданный для квантовой механики, оказался более адекватным для описания человеческого мышления, чем классическая теория вероятностей.&lt;/p&gt;
&lt;p&gt;Это открывает новые возможности — от более точного прогнозирования поведения потребителей до создания ИИ, который лучше понимает человеческую нелогичность. Как подчеркивает Бхармал, те же принципы, которые лежат в основе физики, применимы к принятию решений, решению проблем и инновациям в бизнесе и жизни.&lt;/p&gt;
&lt;p&gt;Парадокс в том, что физика, которую многие считают самой «точной» наукой, помогла объяснить самую неточную и запутанную часть реальности — нас самих.&lt;/p&gt;
</description>
</item>

<item>
<title>QueryFlux: Universal SQL Proxy для аналитических движков</title>
<guid isPermaLink="false">337</guid>
<link>https://gavrilov.info/all/queryflux-universal-sql-proxy-dlya-analiticheskih-dvizhkov/</link>
<pubDate>Fri, 12 Jun 2026 21:15:13 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/queryflux-universal-sql-proxy-dlya-analiticheskih-dvizhkov/</comments>
<description>
&lt;blockquote&gt;
&lt;p&gt;В этой статье я расскажу, как поднять полноценную инфраструктуру для аналитических запросов, используя &lt;b&gt;QueryFlux&lt;/b&gt; — высокопроизводительный SQL-прокси на Rust, который умеет принимать запросы по разным протоколам (Trino HTTP, PostgreSQL wire, MySQL wire) и маршрутизировать их на различные бэкенды (Trino, StarRocks, DuckDB, Athena). Мы соберем стек: &lt;b&gt;Trino&lt;/b&gt; как основной движок, &lt;b&gt;Lakekeeper&lt;/b&gt; как Iceberg REST-каталог, &lt;b&gt;MinIO&lt;/b&gt; как S3-хранилище, &lt;b&gt;StarRocks&lt;/b&gt; как альтернативный MPP-движок, и наконец сам &lt;b&gt;QueryFlux&lt;/b&gt;, который предоставит единую точку входа для клиентов.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.47.49.png" width="1604" height="942" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Все конфигурации взяты из реального рабочего проекта, запущенного на macOS с Podman (но совместимы и с Docker). Детально разберем файлы, шаги запуска, решим типичные проблемы, покажем интерфейс управления и сравним QueryFlux с Trino Gateway и другими решениями.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/lakeops-org/queryflux/blob/main/examples/full-stack/docker-compose.yml"&gt;https://github.com/lakeops-org/queryflux/blob/main/examples/full-stack/docker-compose.yml&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;1. Что такое QueryFlux и зачем он нужен&lt;/h3&gt;
&lt;p&gt;Современные data-платформы часто состоят из нескольких движков: Trino для федеративных запросов, StarRocks/ClickHouse для низкой задержки, DuckDB для ad-hoc аналитики, Athena для serverless-задач. Каждый движок имеет свой wire-протокол, свой диалект SQL и свои настройки аутентификации. Клиенты вынуждены либо подключаться напрямую к каждому движку, создавая $N \times M$ интеграций, либо использовать «костыли» в коде.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;QueryFlux&lt;/b&gt; решает эту проблему, становясь единым шлюзом:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Принимает запросы по протоколам: Trino HTTP, PostgreSQL Wire, MySQL Wire, Arrow Flight SQL.&lt;/li&gt;
&lt;li&gt;Маршрутизирует запросы по правилам (протокол, заголовки, regex, Python-скрипты).&lt;/li&gt;
&lt;li&gt;Ограничивает конкурентность (через параметр `maxRunningQueries`), ведет очереди, отдает метрики в Prometheus.&lt;/li&gt;
&lt;li&gt;Поддерживает аутентификацию (OIDC, static, LDAP) и авторизацию (OpenFGA).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Документация: &lt;a href="https://queryflux.dev"&gt;queryflux.dev&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;2. Наша лабораторная конфигурация&lt;/h3&gt;
&lt;p&gt;Мы развернем следующий стек через `podman-compose` (или `docker-compose`):&lt;/p&gt;
&lt;table cellpadding="0" cellspacing="0" border="0" class="e2-text-table"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Сервис&lt;/td&gt;
&lt;td style="text-align: center"&gt;Назначение&lt;/td&gt;
&lt;td style="text-align: center"&gt;Порт на хосте&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;trino&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Движок запросов (федерация + Iceberg)&lt;/td&gt;
&lt;td style="text-align: center"&gt;8081 (прямой доступ)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;starrocks&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Альтернативный MPP-движок&lt;/td&gt;
&lt;td style="text-align: center"&gt;9030 (MySQL протокол)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;lakekeeper&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Iceberg REST-каталог&lt;/td&gt;
&lt;td style="text-align: center"&gt;8181&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;minio&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;S3-совместимое хранилище (данные Iceberg)&lt;/td&gt;
&lt;td style="text-align: center"&gt;19000 (API), 19001 (консоль)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;postgres&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;БД метаданных Lakekeeper&lt;/td&gt;
&lt;td style="text-align: center"&gt;5433&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;queryflux&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Прокси-сервер&lt;/td&gt;
&lt;td style="text-align: right"&gt;8080 (Trino), 5434 (PG wire), 3306 (MySQL), 9000 (Admin API), 3000 (Studio UI)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;3. Математика планирования нагрузки (ограничение ресурсов)&lt;/h3&gt;
&lt;p&gt;Одним из важных аспектов настройки QueryFlux является управление конкурентностью (concurrency limit) через параметр `maxRunningQueries`.&lt;/p&gt;
&lt;p&gt;Если мы обозначим лимит конкурентных запросов в группе маршрутизации как N, а среднее время выполнения одного запроса на бэкенде как T (в секундах), то &lt;b&gt;теоретическая максимальная пропускная способность группы&lt;/b&gt; (Throughput, обозначается как R, в запросах в секунду) рассчитывается так:&lt;/p&gt;
&lt;p&gt;R = N /T&lt;/p&gt;
&lt;p&gt;Например, в нашем файле `config.yaml` мы задаем N = 100. Если средний аналитический запрос отрабатывает за T = 2.5 секунды, то пропускная способность нашей Trino-группы составит R = 40 запросов в секунду. Запросы сверх этого лимита попадают в очередь на стороне самого QueryFlux.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4. Конфигурационные файлы&lt;/h3&gt;
&lt;p&gt;Создайте папку `queryflux-demo/examples/full-stack` и перейдите в нее. Ниже приведены все необходимые файлы.&lt;/p&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;strong&gt;📄 Показать содержимое файла&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;docker-compose.yml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Полный стек)&lt;/strong&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;name: queryflux-example-full

services:
  queryflux:
    image: ghcr.io/lakeops-org/queryflux:latest
    platform: linux/amd64
    ports:
      - &amp;quot;8080:8080&amp;quot;   # Trino HTTP через QueryFlux
      - &amp;quot;9000:9000&amp;quot;   # Admin API
      - &amp;quot;3000:3000&amp;quot;   # QueryFlux Studio
      - &amp;quot;3306:3306&amp;quot;   # MySQL wire
      - &amp;quot;5434:5434&amp;quot;   # PostgreSQL wire
    volumes:
      - ./config.yaml:/etc/queryflux/config.yaml:ro
    environment:
      RUST_LOG: ${RUST_LOG:-queryflux=info,queryflux_frontend=info}
    depends_on:
      postgres:
        condition: service_healthy
      trino:
        condition: service_healthy
      starrocks:
        condition: service_healthy
    restart: unless-stopped

  trino:
    image: trinodb/trino:latest
    platform: linux/amd64
    environment:
      CATALOG_MANAGEMENT: dynamic
    ports:
      - &amp;quot;8081:8080&amp;quot;
    healthcheck:
      test: [&amp;quot;CMD&amp;quot;, &amp;quot;curl&amp;quot;, &amp;quot;-sf&amp;quot;, &amp;quot;http://localhost:8080/v1/info&amp;quot;]
      interval: 10s
      timeout: 5s
      retries: 15
      start_period: 30s
    volumes:
      - ./trino-config/access-control.properties:/etc/trino/access-control.properties:ro

  starrocks:
    image: starrocks/allin1-ubuntu:latest
    platform: linux/amd64
    ports:
      - &amp;quot;9030:9030&amp;quot;
      - &amp;quot;8030:8030&amp;quot;
    healthcheck:
      test: [&amp;quot;CMD&amp;quot;, &amp;quot;curl&amp;quot;, &amp;quot;-sf&amp;quot;, &amp;quot;http://localhost:8030/api/health&amp;quot;]
      interval: 15s
      timeout: 10s
      retries: 20
      start_period: 60s

  postgres:
    image: postgres:16-alpine
    platform: linux/amd64
    ports:
      - &amp;quot;5433:5432&amp;quot;
    environment:
      POSTGRES_DB: queryflux
      POSTGRES_USER: queryflux
      POSTGRES_PASSWORD: queryflux
    volumes:
      - queryflux-pg:/var/lib/postgresql/data
    healthcheck:
      test: [&amp;quot;CMD-SHELL&amp;quot;, &amp;quot;pg_isready -U queryflux&amp;quot;]
      interval: 5s
      timeout: 3s
      retries: 10

  lakekeeper-db:
    image: postgres:17
    platform: linux/amd64
    environment:
      POSTGRES_PASSWORD: postgres
    healthcheck:
      test: [&amp;quot;CMD-SHELL&amp;quot;, &amp;quot;pg_isready -U postgres -p 5432 -d postgres&amp;quot;]
      interval: 2s
      timeout: 10s
      retries: 10
      start_period: 10s

  minio:
    image: minio/minio:latest
    platform: linux/amd64
    environment:
      MINIO_ROOT_USER: minio-root-user
      MINIO_ROOT_PASSWORD: minio-root-password
    command: [&amp;quot;server&amp;quot;, &amp;quot;--console-address&amp;quot;, &amp;quot;:9001&amp;quot;, &amp;quot;/data&amp;quot;]
    ports:
      - &amp;quot;19000:9000&amp;quot;
      - &amp;quot;19001:9001&amp;quot;
    healthcheck:
      test: [&amp;quot;CMD&amp;quot;, &amp;quot;curl&amp;quot;, &amp;quot;-f&amp;quot;, &amp;quot;http://localhost:9000/minio/health/ready&amp;quot;]
      interval: 2s
      timeout: 10s
      retries: 20
      start_period: 15s

  createbuckets:
    image: minio/mc:latest
    platform: linux/amd64
    depends_on:
      minio:
        condition: service_healthy
    restart: on-failure
    entrypoint: &amp;gt;
      /bin/sh -c &amp;quot;
      /usr/bin/mc alias set local http://minio:9000 minio-root-user minio-root-password;
      /usr/bin/mc mb --ignore-existing local/warehouse;
      exit 0;
      &amp;quot;

  migrate:
    image: quay.io/lakekeeper/catalog:latest-main
    platform: linux/amd64
    pull_policy: always
    environment:
      LAKEKEEPER__PG_ENCRYPTION_KEY: dev-key-not-secure
      LAKEKEEPER__PG_DATABASE_URL_READ: postgresql://postgres:postgres@lakekeeper-db:5432/postgres
      LAKEKEEPER__PG_DATABASE_URL_WRITE: postgresql://postgres:postgres@lakekeeper-db:5432/postgres
    restart: &amp;quot;no&amp;quot;
    command: [&amp;quot;migrate&amp;quot;]
    depends_on:
      lakekeeper-db:
        condition: service_healthy

  lakekeeper:
    image: quay.io/lakekeeper/catalog:latest-main
    platform: linux/amd64
    pull_policy: always
    environment:
      LAKEKEEPER__PG_ENCRYPTION_KEY: dev-key-not-secure
      LAKEKEEPER__PG_DATABASE_URL_READ: postgresql://postgres:postgres@lakekeeper-db:5432/postgres
      LAKEKEEPER__PG_DATABASE_URL_WRITE: postgresql://postgres:postgres@lakekeeper-db:5432/postgres
    command: [&amp;quot;serve&amp;quot;]
    ports:
      - &amp;quot;8181:8181&amp;quot;
    healthcheck:
      test: [&amp;quot;CMD&amp;quot;, &amp;quot;/home/nonroot/lakekeeper&amp;quot;, &amp;quot;healthcheck&amp;quot;]
      interval: 2s
      timeout: 10s
      retries: 30
      start_period: 10s
    depends_on:
      migrate:
        condition: service_completed_successfully
      lakekeeper-db:
        condition: service_healthy
      minio:
        condition: service_healthy
      createbuckets:
        condition: service_completed_successfully

  bootstrap:
    image: alpine/curl
    platform: linux/amd64
    tty: true
    stdin_open: true
    depends_on:
      lakekeeper:
        condition: service_healthy
    restart: &amp;quot;no&amp;quot;
    entrypoint: /bin/sh
    command:
      - -c
      - |
        curl -sv -X POST http://lakekeeper:8181/management/v1/bootstrap \
          -H 'Content-Type: application/json' \
          --data '{&amp;quot;accept-terms-of-use&amp;quot;: true}'
        exit 0

  initialwarehouse:
    image: alpine/curl
    platform: linux/amd64
    tty: true
    stdin_open: true
    depends_on:
      lakekeeper:
        condition: service_healthy
      bootstrap:
        condition: service_completed_successfully
    restart: &amp;quot;no&amp;quot;
    entrypoint: /bin/sh
    command:
      - -c
      - |
        curl -sv -X POST http://lakekeeper:8181/management/v1/warehouse \
          -H 'Content-Type: application/json' \
          --data @/config/create-warehouse.json
        exit 0
    volumes:
      - ./create-warehouse.json:/config/create-warehouse.json:ro

  sentinel:
    image: alpine
    platform: linux/amd64
    command: [&amp;quot;tail&amp;quot;, &amp;quot;-f&amp;quot;, &amp;quot;/dev/null&amp;quot;]
    depends_on:
      lakekeeper:
        condition: service_healthy
      initialwarehouse:
        condition: service_completed_successfully
    healthcheck:
      test: [&amp;quot;CMD&amp;quot;, &amp;quot;true&amp;quot;]
      interval: 1s
      retries: 1
      start_period: 0s

  data-loader:
    image: trinodb/trino:476
    platform: linux/amd64
    profiles: [&amp;quot;loader&amp;quot;]
    environment:
      TPCH_SCALE: ${TPCH_SCALE:-tiny}
    entrypoint: [&amp;quot;/bin/bash&amp;quot;, &amp;quot;-c&amp;quot;]
    command:
      - |
        set -euo pipefail
        sed &amp;quot;s/FROM tpch\\.tiny\\./FROM tpch.$${TPCH_SCALE}./g&amp;quot; /test-data/init.sql &amp;gt; /tmp/init.run.sql
        exec trino --server http://trino:8080 --user loader --file /tmp/init.run.sql
    volumes:
      - ../../docker/fixtures/init.docker-network.sql:/test-data/init.sql:ro
    depends_on:
      trino:
        condition: service_healthy
      sentinel:
        condition: service_healthy

  starrocks-catalog-setup:
    image: mysql:8.0
    platform: linux/amd64
    profiles: [&amp;quot;loader&amp;quot;]
    entrypoint: [&amp;quot;/bin/bash&amp;quot;, &amp;quot;-c&amp;quot;]
    command: [&amp;quot;mysql -h starrocks -P 9030 -u root --connect-timeout=30 &amp;lt; /setup/starrocks-setup.sql&amp;quot;]
    volumes:
      - ../../docker/fixtures/starrocks-setup.sql:/setup/starrocks-setup.sql:ro
    depends_on:
      starrocks:
        condition: service_healthy

volumes:
  queryflux-pg:&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;strong&gt;📄 Вспомогательные конфигурационные файлы&lt;/strong&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Файл `config.yaml` (настройки QueryFlux)&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;queryflux:
  externalAddress: http://localhost:8080
  frontends:
    trinoHttp:
      enabled: true
      port: 8080
    postgresWire:
      enabled: true
      port: 5434
  persistence:
    type: inMemory

clusters:
  trino-1:
    engine: trino
    endpoint: http://trino:8080
    enabled: true
    auth:
      type: basic
      username: trino
      password: &amp;quot;&amp;quot;

clusterGroups:
  trino-default:
    enabled: true
    maxRunningQueries: 100
    members: [trino-1]

routers:
  - type: protocolBased
    trinoHttp: trino-default
    postgresWire: trino-default

routingFallback: trino-default&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Файл `trino-config/access-control.properties`&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;access-control.name=allow-all&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Этот файл монтируется в `trino` и разрешает имперсонацию и чтение системных таблиц – иначе статистика в QueryFlux Studio не будет работать.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;b&gt;Файл `./create-warehouse.json` (инициализация warehouse Lakekeeper)&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
  &amp;quot;warehouse-name&amp;quot;: &amp;quot;test_warehouse&amp;quot;,
  &amp;quot;project-id&amp;quot;: &amp;quot;00000000-0000-0000-0000-000000000000&amp;quot;,
  &amp;quot;storage-profile&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;s3&amp;quot;,
    &amp;quot;bucket&amp;quot;: &amp;quot;warehouse&amp;quot;,
    &amp;quot;endpoint&amp;quot;: &amp;quot;http://minio:9000&amp;quot;,
    &amp;quot;region&amp;quot;: &amp;quot;us-east-1&amp;quot;,
    &amp;quot;path-style-access&amp;quot;: true,
    &amp;quot;flavor&amp;quot;: &amp;quot;minio&amp;quot;,
    &amp;quot;sts-enabled&amp;quot;: false
  },
  &amp;quot;storage-credential&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;s3&amp;quot;,
    &amp;quot;credential-type&amp;quot;: &amp;quot;access-key&amp;quot;,
    &amp;quot;aws-access-key-id&amp;quot;: &amp;quot;minio-root-user&amp;quot;,
    &amp;quot;aws-secret-access-key&amp;quot;: &amp;quot;minio-root-password&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;5. Запуск стека и проверка&lt;/h3&gt;
&lt;p&gt;Запускаем весь стек в фоновом режиме:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;cd examples/full-stack
podman-compose up -d --wait&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;5.1. Тест прямого доступа к Trino&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;curl -X POST http://localhost:8081/v1/statement \
  -H 'X-Trino-User: test' \
  -d 'SELECT 1'&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;5.2. Тест через QueryFlux (PostgreSQL wire)&lt;/h4&gt;
&lt;p&gt;Подключимся через стандартный клиент `psql` к порту `5434`, который прослушивает QueryFlux:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;psql -h localhost -p 5434 -U trino -d trino&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Сначала выполним простой запрос для проверки работоспособности протокола:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;SELECT 42;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;А теперь проверим аналитический потенциал стека. Выполним тяжелый запрос к таблице `call_center` в БД Iceberg, сгенерированной по стандарту TPC-DS:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;SELECT cc_call_center_sk, cc_call_center_id, cc_rec_start_date, cc_rec_end_date, 
       cc_closed_date_sk, cc_open_date_sk, cc_name, cc_class, cc_employees, 
       cc_sq_ft, cc_hours, cc_manager, cc_mkt_id, cc_mkt_class, cc_mkt_desc, 
       cc_market_manager, cc_division, cc_division_name, cc_company, 
       cc_company_name, cc_street_number, cc_street_name, cc_street_type, 
       cc_suite_number, cc_city, cc_county, cc_state, cc_zip, cc_country, 
       cc_gmt_offset, cc_tax_percentage
FROM tpcds.sf10.call_center;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;*Скриншот успешного выполнения запроса через psql*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.00.35.png" width="1286" height="1960" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 1 – Запрос `SELECT count(*) FROM system.runtime.queries` успешно выполняется через QueryFlux, статистика сразу же фиксируется и видна в Studio.*&lt;/div&gt;
&lt;/div&gt;
&lt;h4&gt;5.3. Проверка QueryFlux Studio&lt;/h4&gt;
&lt;p&gt;Откройте браузер и перейдите на `&lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;`. Логин по умолчанию: `admin` / `admin`.&lt;/p&gt;
&lt;p&gt;*Главная панель (Dashboard)*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.00.47.png" width="1706" height="728" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 2 – Дашборд QueryFlux Studio: количество запросов, ошибки, средняя длительность, статус кластеров.*&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;*Список кластеров*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.00.55.png" width="1534" height="694" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 3 – Страница кластеров: виден наш кластер `trino-1`, его группа `trino-default`, состояние и уровень загрузки.*&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;*Группы кластеров*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.01.03.png" width="1704" height="892" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 4 – Управление группами: здесь можно задать ограничение `maxRunningQueries`, список участников и стратегии балансировки. Пока группы инициализируются из in-memory конфигурации.*&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;*Скрипты (translation fixups)*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.01.14.png" width="1398" height="694" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 5 – Скрипты для трансляции диалектов SQL “на лету” (в этой демке не используются).*&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;*Guardrails (ограничения)*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.01.34.png" width="1264" height="878" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 6 – Глобальные и групповые guardrails для инспекции и фильтрации SQL перед отправкой в движок.*&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;*Протоколы (frontends)*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.01.47.png" width="1540" height="776" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 7 – Включённые фронтенды: Trino HTTP (8080) и PostgreSQL wire (5434).*&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;*Маршрутизация*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.01.56.png" width="1408" height="706" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 8 – Правила маршрутизации: `protocolBased` направляет Trino HTTP и PostgreSQL wire в нашу группу `trino-default`.*&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;*Admin API (Swagger)*&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-06-12-v-20.02.06.png" width="1490" height="1720" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;*Рис. 9 – Документация Admin API: эндпоинты для управления кластерами, группами, конфигурациями и получения статистики.*&lt;/div&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;6. Решение типичных проблем&lt;/h3&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;strong&gt;🐞 1. Ошибка&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;internal libpod error&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;для одноразовых контейнеров на macOS&lt;/strong&gt;&lt;/summary&gt;&lt;br /&gt;
Причина: podman-compose на macOS иногда имеет баг с `tty` и `stdin_open`.&lt;br /&gt;
Решение: Параметры уже добавлены в наш `docker-compose.yml`, но если баг не ушел, выполните инициализацию Lakekeeper вручную:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;podman run --rm --network queryflux-example-full_default alpine/curl \
  -X POST http://lakekeeper:8181/management/v1/bootstrap \
  -H 'Content-Type: application/json' \
  -d '{&amp;quot;accept-terms-of-use&amp;quot;: true}'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;strong&gt;🐞 2. PostgreSQL Extended Query Protocol&lt;/strong&gt;&lt;/summary&gt;&lt;br /&gt;
QueryFlux поддерживает только &lt;b&gt;Simple Query Protocol&lt;/b&gt; (сообщение `Q`). Extended Query (Parse/Bind/Execute) не поддерживается.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`psql` работает “из коробки”.&lt;/li&gt;
&lt;li&gt;JDBC-драйверы: добавьте параметр `prepareThreshold=0` в строку подключения, чтобы переключиться в Simple Query режим.  &lt;br /&gt;
Пример:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;jdbc:postgresql://localhost:5434/trino?prepareThreshold=0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;strong&gt;🐞 3. Ошибка&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Access Denied: User trino cannot impersonate user queryflux-running-query-reconcile&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/strong&gt;&lt;/summary&gt;&lt;br /&gt;
Причина: Trino не разрешает имперсонацию для системных запросов QueryFlux.&lt;br /&gt;
Решение: Мы добавили файл `access-control.properties` со свойством `access-control.name=allow-all`. После этого статистика в Studio заработала (см. Рис. 1 и Рис. 2).&lt;br /&gt;
&lt;/details&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;7. Мониторинг и управление&lt;/h3&gt;
&lt;p&gt;QueryFlux предоставляет три основных интерфейса для наблюдения:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;QueryFlux Studio&lt;/b&gt; (порт 3000) – веб-интерфейс для просмотра истории запросов, управления кластерами, группами, маршрутами, скриптами и guardrails.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Admin API&lt;/b&gt; (порт 9000) – REST API для автоматизации (логин: admin/admin). Документация OpenAPI доступна на `/docs`.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Prometheus метрики&lt;/b&gt; (порт 9000/metrics) – стандартные метрики для интеграции с Grafana.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Рекомендуемая практика: для production используйте `persistence: postgres`, чтобы конфигурация групп и маршрутов сохранялась при перезапусках, а история запросов накапливалась.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;8. Сравнение QueryFlux с альтернативами&lt;/h3&gt;
&lt;h4&gt;8.1. Trino Gateway (официальный)&lt;/h4&gt;
&lt;table cellpadding="0" cellspacing="0" border="0" class="e2-text-table"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Характеристика&lt;/td&gt;
&lt;td style="text-align: center"&gt;QueryFlux&lt;/td&gt;
&lt;td style="text-align: center"&gt;Trino Gateway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Поддерживаемые протоколы клиента&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Trino HTTP, PostgreSQL wire, MySQL wire, Arrow Flight SQL&lt;/td&gt;
&lt;td style="text-align: center"&gt;Только Trino HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Бэкенды&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Trino, DuckDB, StarRocks, Athena, ClickHouse (planned)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Только Trino&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Маршрутизация&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;По протоколу, заголовкам, тегам, regex, Python скриптам&lt;/td&gt;
&lt;td style="text-align: center"&gt;По весам, группам, header `X-Trino-Routing-Group`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;SQL трансляция&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Да (sqlglot) – из PostgreSQL в Trino и наоборот&lt;/td&gt;
&lt;td style="text-align: center"&gt;Нет&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Конкурентность и очереди&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;`maxRunningQueries` на группу, очередь на прокси, spillover&lt;/td&gt;
&lt;td style="text-align: center"&gt;`maxConcurrentQueries` на кластер, очереди нет&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Auth/AuthZ&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;OIDC, LDAP, Static, OpenFGA&lt;/td&gt;
&lt;td style="text-align: center"&gt;Базовая поддержка `X-Trino-User`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Метрики&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Prometheus, Grafana, Admin API, Studio&lt;/td&gt;
&lt;td style="text-align: center"&gt;Prometheus (JMX), менее развит&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;GUI управления&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Полноценный веб-интерфейс (Studio)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Отсутствует (только конфигурация API)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;&lt;b&gt;Плюсы QueryFlux:&lt;/b&gt; гетерогенность (один шлюз на разные виды движков), гибкая маршрутизация, встроенный перевод диалектов, PostgreSQL wire, наличие красивого веб-интерфейса.&lt;br /&gt;
&lt;b&gt;Минусы:&lt;/b&gt; молодой проект (версия 0.1.2), не поддерживается Extended Query Protocol для PostgreSQL, требует настройки доступа к системным таблицам Trino.&lt;/p&gt;
&lt;h4&gt;8.2. Другие альтернативы&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Trino + многокаталожность&lt;/b&gt; – простейшее решение, но требует доработки приложений для переключения на trino диалект.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Apache Linkis&lt;/b&gt; – тяжеловесный ETL-ориентированный шлюз, не подходит для лёгкой ad-hoc аналитики.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Nginx + Lua + sqlglot&lt;/b&gt; – сложно поддерживать, требует глубокой кастомной разработки.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Коммерческие решения (Starburst, Dremio)&lt;/b&gt; – дорогостоящие, но предоставляют готовую маршрутизацию, закрытый код и полноценный SLA. но 100% всего не решает так как это готовые коробки. явно захочется что-то под себя подкрутить.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;и еще много с акцентов на gateway: Hoop.dev кстати интересный и GatewayD&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GatewayD и ProxySQL: Не заменяют Trino, но отлично решают вашу задачу с логированием. GatewayD работает с PostgreSQL и может проверять запросы через Casbin, а ProxySQL предоставляет детальное логирование запросов (время, строки, IP и т.д.). Логирование — есть (аудит запросов), диалект Postgres — полный, подключение к 90 БД — сложно (нужно настраивать 90 подключений).&lt;/li&gt;
&lt;li&gt;Mammoth и JumpWire: Специализированные прокси для PostgreSQL. Первый упрощает аудит, логируя каждую команду, второй позволяет гибко настраивать политики доступа и маскировать данные. Логирование — есть, диалект Postgres — полный, подключение к 90 БД — сложно (на каждый экземпляр нужен свой прокси).&lt;/li&gt;
&lt;li&gt;Hoop.dev: Платформа для контролируемого доступа к базам данных с сильным акцентом на аудит и безопасность. Логирует все: от попыток входа до полного текста запросов и даже планов выполнения. Логирование — детальное, диалект Postgres — полный, подключение к 90 БД — сложно (требует развёртывания на каждую базу).&lt;/li&gt;
&lt;li&gt;Уже посмотрели QueryFlux: Это решение ближе всего к Trino, но работает как высокоуровневый шлюз. На входе может принимать запросы через “PostgreSQL wire”, а на выходе автоматически транслировать диалект под Trino, Clickhouse и другие системы. Логирование — ограниченное, диалект Postgres — только как входной интерфейс (запросы уходят в Trino), подключение к 90 БД — замена Trino (шлюз к 90 разным источникам).&lt;/li&gt;
&lt;li&gt;SQL Gateway (CData): Позволяет представить любые ODBC-источники как виртуальную PostgreSQL или SQL Server базу. Логирование — только общее, диалект Postgres — виртуальный (эмуляция), подключение к 90 БД — сложно (настройка ODBC).&lt;/li&gt;
&lt;li&gt;Cloud Service Gateways (Infisical и др.): Специализированные облачные решения. Обещают централизованный доступ и аудит, но их возможности нативных диалектов сильно привязаны к конкретному провайдеру.&lt;/li&gt;
&lt;li&gt;Native PostgreSQL Gateways: Как сборник технологий (например, PgCat), из которых можно построить своё решение. Позволяет гибко настраивать подключения и логи, но требует ручной сборки и высокой квалификации.&lt;br /&gt;
Интеграция с Keycloak: К сожалению, прямой интеграции с Keycloak для аутентификации SQL-запросов практически нет. Keycloak используется для аутентификации доступа к веб-интерфейсам административных консолей, но не для самих SQL-клиентов. Исключение — GatewayD, который, хотя и не интегрируется с Keycloak, позволяет реализовать схожую логику через Casbin.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Вывод:&lt;/b&gt; QueryFlux идеален, если у вас уже есть несколько движков и вы хотите дать единую точку входа для бизнес-пользователей и аналитиков (особенно тех, кто привык к `psql`). Для production, где критична поддержка prepare-statements, стоит использовать Trino JDBC напрямую или использовать дополнительный прокси (например, `trino-pg-gateway`).&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;9. Итоги и рекомендации&lt;/h3&gt;
&lt;p&gt;Мы успешно запустили полноценный аналитический стек с Lakekeeper (Iceberg), Trino и StarRocks, а QueryFlux обеспечил единый вход через HTTP и PostgreSQL wire. Ключевые достижения:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ QueryFlux принимает Trino HTTP и PostgreSQL wire запросы, направляя их в Trino.&lt;/li&gt;
&lt;li&gt;✅ Клиент `psql` выполняет сложные `SELECT`-запросы к Iceberg таблицам (даже TPC-DS) через порт 5434.&lt;/li&gt;
&lt;li&gt;✅ Статистика в Studio отображается корректно.&lt;/li&gt;
&lt;li&gt;✅ Маршрутизация по протоколу (`protocolBased`) работает как задумано.&lt;/li&gt;
&lt;li&gt;✅ Веб-интерфейс Studio даёт полный контроль над кластерами, группами, маршрутами и скриптами.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Рекомендации для production:&lt;/b&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Замените `persistence: inMemory` на `persistence: postgres` и настройте репликацию БД конфигурации (чтобы не терять историю и настройки).&lt;/li&gt;
&lt;li&gt;Включите аутентификацию OIDC (Keycloak) и авторизацию OpenFGA для разграничения доступа к группам кластеров.&lt;/li&gt;
&lt;li&gt;Рассчитайте `maxRunningQueries` по формуле N = R \times T, исходя из планируемой нагрузки и SLA.&lt;/li&gt;
&lt;li&gt;Для PostgreSQL-клиентов с GUI (DataGrip/DBeaver) используйте параметр `prepareThreshold=0` (через JDBC) или переключитесь на официальный Trino JDBC драйвер.&lt;/li&gt;
&lt;li&gt;Настройте сбор метрик в Prometheus и дашборды Grafana для мониторинга длины очередей и задержек.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Заключение:&lt;/b&gt; QueryFlux — очень перспективный и многообещающий инструмент для построения унифицированного доступа к аналитическим движкам. Несмотря на молодость, он уже пригоден для некоторых сценариев, особенно если вы готовы ограничиться simple query protocol при использовании PostgreSQL wire. В связке с Iceberg-каталогами и объектным хранилищем он образует мощную open-source альтернативу дорогим коммерческим решениям.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
</item>

<item>
<title>Ничто не предвещало</title>
<guid isPermaLink="false">336</guid>
<link>https://gavrilov.info/all/nichto-ne-predveschalo/</link>
<pubDate>Wed, 10 Jun 2026 08:58:22 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/nichto-ne-predveschalo/</comments>
<description>
&lt;p&gt;Вчера зевнул на улице, съел муху. Блин, надо больше спать 😅&lt;/p&gt;
</description>
</item>

<item>
<title>Нормально делай, нормально будет</title>
<guid isPermaLink="false">335</guid>
<link>https://gavrilov.info/all/normalno-delay-normalno-budet/</link>
<pubDate>Mon, 18 May 2026 14:19:58 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/normalno-delay-normalno-budet/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/IMG_2835.jpeg" width="1170" height="1940" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;И добавить нечего&lt;/p&gt;
</description>
</item>

<item>
<title>Распределённые вычисления с Ray и отчетики</title>
<guid isPermaLink="false">334</guid>
<link>https://gavrilov.info/all/raspredelyonnye-vychisleniya-s-ray-i-otchetiki/</link>
<pubDate>Thu, 23 Apr 2026 08:49:14 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/raspredelyonnye-vychisleniya-s-ray-i-otchetiki/</comments>
<description>
&lt;h3&gt;Введение в распределённые вычисления с Ray&lt;/h3&gt;
&lt;p&gt;ПредположенИИе 🤖&lt;/p&gt;
&lt;p&gt;Ray — это унифицированный фреймворк с открытым исходным кодом для масштабирования AI- и Python-приложений. Он предоставляет простой API для создания распределённых приложений, которые могут масштабироваться от одного ноутбука до целого кластера без изменения кода. Ray эффективно обрабатывает разнообразные рабочие нагрузки: от пакетной обработки данных и распределённого обучения моделей до гиперпараметрической оптимизации и serving-а инференса моделей в продакшене. Ray не ограничивается только задачами ML: он также предоставляет Ray Data и потоковые примитивы для эффективных входных пайплайнов, пакетной обработки и онлайн-инференса.&lt;/p&gt;
&lt;h4&gt;Ключевые возможности Ray&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Единый фреймворк&lt;/b&gt;: Одна кодовая база охватывает все этапы жизненного цикла AI — от обработки данных до развёртывания моделей, устраняя сложность интеграции разнородных систем.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Масштабируемость&lt;/b&gt;: Бесшовный переход от локальной разработки к кластеру из тысяч ядер без переписывания кода.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Производительность&lt;/b&gt;: На некоторых ML-задачах Ray показывает результаты лучше, чем Spark и Dask, а на одном узле — на ~10% быстрее стандартной многопроцессорной обработки Python.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Гибкость&lt;/b&gt;: Поддержка как stateful (сохраняющих состояние), так и stateless (не сохраняющих состояние) рабочих нагрузок с помощью задач (tasks) и акторов (actors).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Фронтенд для дашбордов: Streamlit и Marimo&lt;/h3&gt;
&lt;p&gt;Для визуализации данных и создания интерактивных дашбордов на Python сегодня доступны два мощных инструмента: проверенный временем Streamlit и современный Marimo.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-23-v-08.46.41.png" width="2130" height="1370" alt="" /&gt;
&lt;/div&gt;
&lt;h4&gt;Streamlit: Классика для data-приложений&lt;/h4&gt;
&lt;p&gt;Streamlit — это open-source Python-библиотека, которая позволяет превратить скрипты анализа данных в полноценные веб-приложения за считанные минуты, без необходимости писать HTML, CSS или JavaScript. Streamlt поддерживает:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Виджеты&lt;/b&gt;: Слайдеры, кнопки, текстовые поля для создания интерактивных интерфейсов.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Кэширование&lt;/b&gt;: Декораторы `st.cache_data` и `st.cache_resource` для оптимизации загрузки данных и управления тяжёлыми объектами (моделями, подключениями к БД).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Многозатраничность&lt;/b&gt;: Возможность создавать приложения с несколькими страницами через папку `pages/`.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Marimo: Реактивная альтернатива&lt;/h4&gt;
&lt;p&gt;Marimo — это реактивный Python-ноутбук нового поколения, который также можно использовать для создания веб-приложений. Главное отличие от Streamlit — реактивная модель выполнения: при изменении одной ячейки или взаимодействии с UI-элементом автоматически пересчитываются только зависимые ячейки, а не весь скрипт. Marimo подходит для сложного исследовательского анализа и интерактивных дашбордов, где важна производительность и детальный контроль выполнения.&lt;/p&gt;
&lt;h4&gt;Сравнение Streamlit и Marimo&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Подход&lt;/b&gt;: Streamlit — это фреймворк для data-приложений, тогда как Marimo — ноутбук-среда, которую можно запускать как приложение.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Производительность&lt;/b&gt;: Marimo часто показывает лучшую производительность, так как перезапускает только зависимые части, в отличие от Streamlit, который выполняет весь скрипт заново при каждом взаимодействии.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Сценарии использования&lt;/b&gt;: Streamlt идеален для быстрой разработки надёжных бизнес-дашбордов, а Marimo — для исследовательских задач и сложной аналитики.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Архитектура системы: Ray как бэкенд для дашбордов&lt;/h3&gt;
&lt;p&gt;Рассмотрим практический пример построения масштабируемой системы отчётности, где Ray выступает в роли мощного бэкенда для обработки и serving-а данных, а Streamlit (или Marimo) — в роли фронтенда для визуализации. Код визуализаций хранится в Git, что упрощает версионирование, совместную работу и развёртывание.&lt;/p&gt;
&lt;h4&gt;Основные компоненты&lt;/h4&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Бэкенд (Ray)&lt;/b&gt;: Распределённое приложение (деплоймент) на Ray Serve, которое подключается к источнику данных (например, Trino), выполняет сложные агрегации и возвращает результат.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Фронтенд (Streamlit/Marimo)&lt;/b&gt;: Веб-приложение, которое отправляет HTTP-запросы к Ray-бэкенду, получает данные и отображает их в интерактивных дашбордах.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Внешнее хранилище состояния (опционально)&lt;/b&gt;: Redis или база данных для хранения состояния сессий пользователей.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Git&lt;/b&gt;: Репозиторий для хранения кода фронтенда (Streamlit-скрипты, Marimo-ноутбуки) и конфигураций.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Пример кода: Бэкенд на Ray Serve&lt;/h4&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-23-v-08.47.14.png" width="2478" height="1666" alt="" /&gt;
&lt;/div&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import ray
from ray import serve
from fastapi import FastAPI, HTTPException
import trino
import pandas as pd

app = FastAPI()

@serve.deployment(
    ray_actor_options={&amp;quot;num_cpus&amp;quot;: 0.5},
    autoscaling_config={&amp;quot;min_replicas&amp;quot;: 1, &amp;quot;max_replicas&amp;quot;: 2},
)
@serve.ingress(app)
class TrinoQuery:
    def __init__(self):
        self.conn = trino.dbapi.connect(
            host=&amp;quot;192.168.0.125&amp;quot;,
            port=9999,
            user=&amp;quot;jupyter&amp;quot;,
            catalog=&amp;quot;test_warehouse&amp;quot;,
            schema=&amp;quot;test_schema&amp;quot;,
            http_scheme=&amp;quot;http&amp;quot;,
        )
        print(&amp;quot;Соединение с Trino установлено.&amp;quot;)

    @app.get(&amp;quot;/query&amp;quot;)
    async def execute_query(self, query: str):
        if not query:
            raise HTTPException(status_code=400, detail=&amp;quot;Query parameter is required.&amp;quot;)
        try:
            cursor = self.conn.cursor()
            cursor.execute(query)
            rows = cursor.fetchall()
            col_names = [desc[0] for desc in cursor.description]
            df = pd.DataFrame(rows, columns=col_names)
            return df.to_dict(orient=&amp;quot;records&amp;quot;)
        except Exception as e:
            raise HTTPException(status_code=500, detail=str(e))

ray.init(ignore_reinit_error=True)
serve.start(http_options={&amp;quot;host&amp;quot;: &amp;quot;0.0.0.0&amp;quot;, &amp;quot;port&amp;quot;: 8000})
serve.run(TrinoQuery.bind(), blocking=True)&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Пример кода: Фронтенд на Streamlit&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import streamlit as st
import pandas as pd
import requests

BACKEND_URL = &amp;quot;http://127.0.0.1:8000/query&amp;quot;

st.set_page_config(page_title=&amp;quot;Аналитическая панель&amp;quot;, layout=&amp;quot;wide&amp;quot;)
st.title(&amp;quot;Дашборд данных из Trino через Ray&amp;quot;)

with st.sidebar:
    st.header(&amp;quot;Параметры запроса&amp;quot;)
    query = st.text_area(
        &amp;quot;SQL-запрос:&amp;quot;,
        value=&amp;quot;SELECT nationkey, COUNT(*) as cnt FROM test_warehouse.test_schema.my_table1 GROUP BY nationkey&amp;quot;,
        height=200,
    )
    execute_button = st.button(&amp;quot;Выполнить запрос&amp;quot;, type=&amp;quot;primary&amp;quot;)

if execute_button:
    if not query:
        st.warning(&amp;quot;Введите SQL-запрос.&amp;quot;)
    else:
        with st.spinner(&amp;quot;Выполняется запрос через Ray Serve...&amp;quot;):
            try:
                response = requests.get(BACKEND_URL, params={&amp;quot;query&amp;quot;: query}, timeout=30)
                response.raise_for_status()
                data = response.json()
                if data:
                    df = pd.DataFrame(data)
                    st.success(f&amp;quot;Запрос выполнен. Получено строк: {len(df)}&amp;quot;)
                    st.dataframe(df, use_container_width=True)
                    if df.select_dtypes(include='number').shape[1] &amp;gt; 0:
                        st.subheader(&amp;quot;Статистика по числовым колонкам&amp;quot;)
                        st.dataframe(df.describe(), use_container_width=True)
                else:
                    st.info(&amp;quot;Запрос вернул пустой результат.&amp;quot;)
            except Exception as e:
                st.error(f&amp;quot;Ошибка: {e}&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Хранение визуализаций в Git&lt;/h4&gt;
&lt;p&gt;Код фронтенда (Streamlit-скрипты или Marimo-ноутбуки) должен храниться в Git-репозитории. Это обеспечивает:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Версионирование&lt;/b&gt;: Возможность отслеживать изменения, откатываться к предыдущим версиям.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Совместную работу&lt;/b&gt;: Команда разработчиков может одновременно работать над разными частями дашборда.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Автоматизацию развёртывания&lt;/b&gt;: CI/CD пайплайны могут автоматически деплоить новую версию дашборда на сервер при пуше в определённую ветку.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Репозиторий может иметь следующую структуру:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;.
├── app.py                 # Основной файл Streamlit-приложения
├── pages/                 # Дополнительные страницы (если используются)
├── marimo_notebooks/      # Marimo-ноутбуки (если используются)
├── requirements.txt       # Зависимости
├── .gitignore
└── README.md&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Управление состоянием: как построить систему отчётов&lt;/h3&gt;
&lt;p&gt;В распределённой системе, где множество пользователей одновременно обращаются к дашборду, а сам бэкенд масштабируется на множество реплик, управление состоянием (state management) становится критически важным. Ошибка может привести к тому, что пользователь увидит чужие данные или потеряет свой прогресс в сессии.&lt;/p&gt;
&lt;h4&gt;Stateless vs. Stateful: Основной выбор&lt;/h4&gt;
&lt;p&gt;Ray поддерживает оба подхода:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Stateless бэкенд (рекомендуемый)&lt;/b&gt;: Бэкенд не хранит состояние пользователей. Вся сессионная информация (например, результаты фильтрации, текущая страница) хранится во фронтенде или во внешнем хранилище. Любая реплика Ray может обработать любой запрос. Это делает систему простой и отказоустойчивой, но требует, чтобы состояние было “лёгким” (например, хранилось в cookies или `session_state`).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Stateful бэкенд&lt;/b&gt;: Бэкенд хранит состояние в своей памяти. В этом случае необходимо обеспечить, чтобы все запросы от одного пользователя направлялись на одну и ту же реплику (sticky sessions).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Рекомендуемая архитектура: Stateless бэкенд + Session State во фронтенде&lt;/h4&gt;
&lt;p&gt;Для большинства BI-дашбордов идеальна следующая схема:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Бэкенд (Ray)&lt;/b&gt;: Полностью stateless. Он принимает запрос, выполняет вычисления и возвращает результат. Он не помнит, какие запросы делал пользователь ранее.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Фронтенд (Streamlit/Marimo)&lt;/b&gt;: Хранит состояние сессии локально. В Streamlit для этого используется `st.session_state`. Например, вы можете сохранить в `session_state` фильтры, выбранные пользователем, чтобы они применялись при каждом взаимодействии.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Внешнее хранилище&lt;/b&gt;: Для кэширования результатов тяжёлых запросов или для хранения общего состояния (например, результатов обучения модели) используйте Redis или базу данных.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Если требуется Stateful бэкенд (например, кэш в памяти реплики)&lt;/h4&gt;
&lt;p&gt;Иногда возникает необходимость, чтобы бэкенд хранил какое-то состояние для повышения производительности. Например, каждая реплика может загружать большую модель машинного обучения в свою память. В таком случае используется подход &lt;b&gt;Soft Session Affinity&lt;/b&gt;: все запросы от одного пользователя направляются на одну и ту же реплику, используя уникальный ключ (`X-SERVE-SHARD-KEY`).&lt;/p&gt;
&lt;h4&gt;Сценарий: Долгоживущий отчёт (Report as a Service)&lt;/h4&gt;
&lt;p&gt;Рассмотрим сценарий, где бизнес-пользователь хочет “заказать” отчёт, который генерируется 10 минут, и вернуться за ним через час. Stateless архитектура здесь не подойдёт, так как бэкенд “забудет” о задаче.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Stateful бэкенд (Ray Actor)&lt;/b&gt;: Используется долгоживущий Ray Actor (актор), который хранит состояние задачи и её результат.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Хранилище задач&lt;/b&gt;: База данных (например, PostgreSQL) используется для хранения информации о задаче (статус, результат). Актор периодически обновляет статус.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Фронтенд&lt;/b&gt;: Пользователь запускает задачу, получает её `task_id`, а затем периодически опрашивает эндпоинт `GET /task/{task_id}/status`, который возвращает статус и, при готовности, результат.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Преимущества использования Ray в архитектуре отчётов&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Масштабируемость под нагрузку&lt;/b&gt;: Ray может автоматически масштабировать количество реплик бэкенда в зависимости от количества запросов. Если вашим дашбордом пользуется 10 или 10 000 человек, Ray адаптируется.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Производительность&lt;/b&gt;: Ray оптимизирован для параллельных вычислений и может обрабатывать большие объёмы данных быстрее, чем традиционные инструменты.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Единая кодовая база&lt;/b&gt;: Вы можете использовать Ray не только для serving-а данных, но и для их предварительной обработки, обучения моделей и т.д. Это упрощает инфраструктуру.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Отказоустойчивость&lt;/b&gt;: Ray автоматически перезапускает упавшие реплики, обеспечивая высокую доступность ваших дашбордов.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Гибкость управления ресурсами&lt;/b&gt;: Вы можете точно указать, сколько CPU и GPU нужно выделить для каждого компонента системы.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;Ray, Streamlit и Marimo образуют мощный тандем для построения современных систем отчётности и аналитики. Ray обеспечивает масштабируемый и производительный бэкенд, способный обрабатывать большие объёмы данных. Streamlit и Marimo предоставляют удобные средства для создания интерактивных и красивых дашбордов, а Git гарантирует контроль версий и простоту развёртывания. Ключом к успешной архитектуре является правильный выбор стратегии управления состоянием: в большинстве случаев подходит stateless бэкенд с хранением состояния во фронтенде, что обеспечивает простоту и отказоустойчивость. Для более сложных сценариев (долгие задачи, кэширование моделей) можно использовать stateful подход с Ray акторами и внешним хранилищем.&lt;/p&gt;
&lt;p&gt;Если вы хотите увидеть полный рабочий пример с кодом, архитектурной схемой и инструкцией по развёртыванию, дайте знать — я подготовлю подробный гайд.&lt;/p&gt;
</description>
</item>

<item>
<title>ИИгрушки 🤖</title>
<guid isPermaLink="false">333</guid>
<link>https://gavrilov.info/all/iigrushki/</link>
<pubDate>Mon, 20 Apr 2026 21:49:21 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/iigrushki/</comments>
<description>
&lt;p&gt;Сегодня еще кстати крылатое выражение на уме или цитата, как хотите. «Когда выручка не растет, кровати 🛌 передвинуты, ш..х сменили и все против вас, то на помощь приходят ИИгрушки) 😁☺️😉 (с)&lt;/p&gt;
</description>
</item>

<item>
<title>Утиные истории: часть 2. Экосистема DuckDB в 2026 году</title>
<guid isPermaLink="false">331</guid>
<link>https://gavrilov.info/all/utinye-istorii-chast-2-ekosistema-duckdb-v-2026-godu/</link>
<pubDate>Mon, 20 Apr 2026 00:12:54 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/utinye-istorii-chast-2-ekosistema-duckdb-v-2026-godu/</comments>
<description>
&lt;p&gt;В &lt;a href="https://gavrilov.info/all/utinye-istorii-s-duckdb/"&gt;первой части Утиных историй&lt;/a&gt; мы детально разбирали, как DuckDB переворачивает принципы локальной и встраиваемой аналитики. Сегодня на календаре 19 апреля 2026 года, и экосистема «утки» развивается с невероятной скоростью. На днях вышел юбилейный, 40-й выпуск информационного бюллетеня от команды MotherDuck.&lt;/p&gt;
&lt;p&gt;В этой статье мы разберем самые горячие новинки обновления: релиз DuckLake 1.0, нативную поддержку протокола PostgreSQL, векторный поиск и то, как DuckDB покоряет новые горизонты программирования (от Elixir к Rust).&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;🦆 DuckLake 1.0: Озерный формат (Lakehouse) готов к продакшену&lt;/h3&gt;
&lt;p&gt;Главная новость апреля — релиз &lt;b&gt;DuckLake 1.0&lt;/b&gt;. Это lakehouse-формат, в котором &lt;b&gt;все метаданные хранятся непосредственно в каталоге базы данных&lt;/b&gt; (в PostgreSQL, SQLite или самой DuckDB), а не в разрозненных файлах, как это сделано в Delta Lake или Apache Iceberg.&lt;/p&gt;
&lt;h4&gt;Что под капотом?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Сортированные таблицы и Bucket-партиционирование:&lt;/b&gt; Оптимизируют чтение и ускоряют аналитику.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Решение проблемы “маленьких файлов”:&lt;/b&gt; Мелкие транзакции (где количество строк N≤10 по умолчанию) сохраняются напрямую (inlining) в каталог. Для сброса в объектное хранилище используется команда `CHECKPOINT`.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Векторы удаления (Deletion vectors):&lt;/b&gt; Поддержка совместимости с Iceberg.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Новый тип Variant:&lt;/b&gt; Позволяет работать с полуструктурированными данными, автоматически “раскладывая” их на примитивные типы для быстрого выполнения запросов.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Ускорение в цифрах&lt;/h4&gt;
&lt;p&gt;Отказ от чтения разрозненных файлов метаданных дает феноменальный прирост производительности базовых операций агрегации. Если сравнивать время выполнения запросов до оптимизации (T old) и с использованием чтения исключительного из каталога метаданных DuckLake (T new), то выигрыш в скорости можно выразить формулой:&lt;/p&gt;
&lt;p&gt;Speedup =T new / T old&lt;/p&gt;
&lt;p&gt;Для запросов вида `COUNT(*)` этот Speedup составляет от 8 до 258 раз! А вызов системной функции `duckdb_views()` ускорился примерно в 70 раз.&lt;/p&gt;
&lt;p&gt;Неудивительно, что DuckLake уже входит в топ-10 расширений по количеству скачиваний и поддерживается клиентами Apache DataFusion, Spark, Trino и Pandas. Издательство O’Reilly даже готовит книгу *“DuckLake: The Definitive Guide”*. (Фича доступна в DuckDB v1.5.2).&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;🐘 MotherDuck теперь говорит на языке Postgres&lt;/h3&gt;
&lt;p&gt;Чтобы внедрить мощь DuckDB в свою инфраструктуру разработчикам часто приходилось искать специальные драйверы и коннекторы. Это в прошлом!&lt;/p&gt;
&lt;p&gt;MotherDuck запустили &lt;b&gt;PostgreSQL wire-protocol endpoint&lt;/b&gt;. Теперь вы можете выполнять аналитические SQL-запросы к DuckDB, используя совершенно любой клиент, пулер (pooler) или BI-инструмент, совместимый с Postgres. Устанавливать библиотеки DuckDB на клиент больше не нужно!&lt;/p&gt;
&lt;p&gt;Достаточно направить ваш текущий клиент по адресу:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;pg.us-east-1-aws.motherduck.com:5432&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Авторизация происходит с помощью токена MotherDuck. При этом диалект SQL остается утиным (хотя он в значительной степени и совместим с PostgreSQL). Миграция данных возможна через обычные ETL-утилиты или расширение `pg_duckdb`.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;🦀 `quack-rs`: Пишем расширения на чистом Rust&lt;/h3&gt;
&lt;p&gt;Мощным толчком для развития комьюнити-плагинов стал релиз &lt;b&gt;`quack-rs`&lt;/b&gt;. До сих пор написание расширений для DuckDB на Rust требовало создания слоев совместимости (C++ glue) и возни с CMake.&lt;/p&gt;
&lt;p&gt;`quack-rs` — это SDK на чистом Rust, который оборачивает *C Extension API* (v1.1+). Инструмент предоставляет безопасные абстракции и устраняет 16 задокументированных проблем с FFI (Foreign Function Interface), предотвращая “тихую” порчу данных через NULL и ошибки “double-free” в callback-функциях агрегации.&lt;/p&gt;
&lt;p&gt;Для старта нового расширения достаточно вызвать функцию:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;generate_scaffold();&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Она сгенерирует все 11 файлов, необходимых для подачи плагина в репозиторий сообщества. Теперь безопасность памяти Rust и скорость DuckDB идут рука об руку.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;summary&gt;&lt;b&gt;🛠️ Важные новости комьюнити и новые инструменты (Нажмите, чтобы развернуть)&lt;/b&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;h4&gt;1. Lance Extension и векторный поиск&lt;/h4&gt;
&lt;p&gt;Открытый колоночный формат Lance, оптимизированный под ML и векторный поиск, теперь доступен и в DuckDB! Hao Ding реализовал поддержку чтения и записи таблиц Lance.&lt;/p&gt;
&lt;p&gt;Писать данные можно так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;COPY (...) TO 'path/dataset.lance' (FORMAT lance, MODE 'overwrite');&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для поиска доступны функции: `lance_vector_search()`, `lance_fts()` и `lance_hybrid_search()`.&lt;/p&gt;
&lt;h4&gt;2. Dux: Распределенные DataFrame для Elixir&lt;/h4&gt;
&lt;p&gt;Появилась библиотека `dux` — lazy-by-default (ленивые по умолчанию) датафреймы для Elixir поверх DuckDB. Конвейеры данных аккумулируются в AST структуре `%Dux{}` и компилируются в SQL CTE. Заявлено, что на тестах ($10$ млн строк, Apple M4 Max) Dux обгоняет Polars (Explorer) до 2.5 раз на операциях фильтрации.&lt;/p&gt;
&lt;h4&gt;3. eBPF трассировка с ИИ (`systing 1.0`)&lt;/h4&gt;
&lt;p&gt;Инструмент для трассировки ядра Linux `systing` (написанный Josef Bacik) перешел с сохранения логов Perfetto на прямую запись в DuckDB. А интеграция с Claude Code MCP (Model Context Protocol) позволяет ИИ динамически анализировать эти базы данных DuckDB в реальном времени.&lt;/p&gt;
&lt;h4&gt;4. Jupyter и DuckDB Kernel на Go&lt;/h4&gt;
&lt;p&gt;Создано полноценное Go-ядро DuckDB для Jupyter, которое напрямую отправляет поток данных (Arrow IPC) во встроенный WASM-просмотрщик `hugr-perspective-viewer`. На панели также агрегируются метрики без написания SQL: `approx_unique`, `avg`, `min`, `max`, `count`.&lt;/p&gt;
&lt;h4&gt;5. Web-framework, Neovim и игры&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;`neovim-web`&lt;/b&gt;: Фреймворк для создания статических сайтов с горячими клавишами Vim. Фишка — встроенная консоль DuckDB-Wasm (команда `:sql`) прямо в браузере.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`connections.duckdb`&lt;/b&gt;: Аналог игры “Connections” от NYT, целиком реализованный на SQL макросах.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;💻 Бенчмарки: Большие данные на самом дешевом MacBook Neo&lt;/h3&gt;
&lt;p&gt;Способен ли базовый ноутбук переваривать серьезную аналитику? Gábor проверил работу DuckDB на новом MacBook Neo с процессором Apple A18 Pro.&lt;/p&gt;
&lt;table cellpadding="0" cellspacing="0" border="0" class="e2-text-table"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Бенчмарк&lt;/td&gt;
&lt;td style="text-align: center"&gt;Параметры&lt;/td&gt;
&lt;td style="text-align: center"&gt;Результат (медиана)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;ClickBench&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;100M строк, лимит RAM: 5GB&lt;/td&gt;
&lt;td style="text-align: center"&gt;&lt; 1 секунды (cold run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;TPC-DS&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;SF100&lt;/td&gt;
&lt;td style="text-align: center"&gt;1.63 секунды на запрос&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;TPC-DS&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;SF300&lt;/td&gt;
&lt;td style="text-align: center"&gt;79 минут (высокий disk spill)&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Даже при 5 гигабайтах оперативной памяти DuckDB демонстрирует субсекундные ответы, эффективно утилизируя NVMe-память, когда RAM исчерпан (disk spill).&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;🎓 Внедрение в Академическую Среду&lt;/h3&gt;
&lt;p&gt;Стоит отдельно отметить профессора Dr. Torsten Grust из Тюбингенского университета (Германия). Его исследовательская группа, стоящая на стыке баз данных и технологий языков программирования, недавно запустила открытый курс &lt;b&gt;DiDi&lt;/b&gt; (*Design and Implementation of DuckDB Internals*).&lt;/p&gt;
&lt;p&gt;Курс использует DuckDB для обучения студентов архитектуре СУБД: от управления памятью и векторизованного исполнения до оптимизации запросов (включает около 50 рабочих примеров кода).&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;🗓 Ближайшие Мероприятия&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;21 апреля 2026 (Онлайн):&lt;/b&gt; Стрим MotherDuck Now Speaks Postgres: Fast Analytics Without Changing Your Stack. Демонстрация нового PG wire-protocol.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;30 апреля 2026 (Сан-Франциско):&lt;/b&gt; DuckDB + MotherDuck Meetup. Разговоры про DuckLake 1.0 и распределенный DuckDB (проект OpenDuck).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Экосистема DuckDB перестала быть просто *“SQLite для аналитики”*. С релизом DuckLake, нативной интеграцией протокола Postgres и появлением SDK для Rust, “утка” окончательно закрепилась как основополагающий инструмент в стеке современных данных.&lt;/p&gt;
</description>
</item>

<item>
<title>🚀 Создание почтиReal-Time Data Lake: Быстрая миграция данных в Apache Iceberg или Parquet</title>
<guid isPermaLink="false">330</guid>
<link>https://gavrilov.info/all/sozdanie-pochtireal-time-data-lake-bystraya-migraciya-dannyh-v-a/</link>
<pubDate>Thu, 16 Apr 2026 01:33:39 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/sozdanie-pochtireal-time-data-lake-bystraya-migraciya-dannyh-v-a/</comments>
<description>
&lt;p&gt;Сегодня Gemini 3.1 Pro Preview расскажет свое мненИИе))&lt;/p&gt;
&lt;p&gt;Связывание транзакционных баз (PostgreSQL) и аналитических хранилищ (ClickHouse) через прямые агрегации и `JOIN` часто приводит к жесточайшим блокировкам и деградации продакшена. Когда бизнес требует быстрый результат, а внедрение полноценного CDC (Debezium + Kafka) откладывается из-за сроков и сложности, лучшим решением становится пакетная и микро-пакетная выгрузка данных в озеро (в форматы Parquet и Apache Iceberg).&lt;/p&gt;
&lt;p&gt;С точки зрения архитектуры, наша главная цель — минимизировать время загрузки данных T load и усилия инженеров на развертывание E setup. Наша целевая функция: min(T load × E setup)&lt;/p&gt;
&lt;p&gt;В этой статье собраны исключительно рабочие, протестированные подходы для быстрой интеграции с озером данных (Data Lake) и аналитическим движком Trino.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;🐘 1. Экспорт данных из PostgreSQL: Проверенные инструменты&lt;/h3&gt;
&lt;p&gt;Мы полностью исключаем создание и восстановление тяжелых дампов (`pg_dump`). Вся транзитная нагрузка ложится &lt;b&gt;исключительно на асинхронные реплики&lt;/b&gt;.&lt;/p&gt;
&lt;h4&gt;🌟 Подход А: Движок OLake (Самый быстрый старт в Iceberg)&lt;/h4&gt;
&lt;p&gt;Для задачи “результат нужен вчера и без сложного стека” идеально подходит &lt;b&gt;OLake&lt;/b&gt;. Это высокопроизводительный движок репликации баз данных напрямую в Apache Iceberg (или Parquet), минуя промежуточные шины сообщений.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Шаг 1. Запуск сервиса (конфигурация `docker-compose.yml`):&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;version: '3.8'
services:
  olake:
    image: olakeio/olake:latest
    ports:
      - &amp;quot;8080:8080&amp;quot;
    environment:
      # Настройки доступов к вашему S3/MinIO
      - AWS_ACCESS_KEY_ID=your_access_key
      - AWS_SECRET_ACCESS_KEY=your_secret_key
      - AWS_REGION=us-east-1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Шаг 2. Запуск репликации:&lt;/b&gt;&lt;br /&gt;
Вы отправляете JSON-манифест в OLake (через UI или REST API). Движок самостоятельно делает первоначальный слепок PostgreSQL (Full Load со скоростью до 580K RPS), а затем переключается на чтение инкрементов (CDC):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
  &amp;quot;pipeline_name&amp;quot;: &amp;quot;pg_to_iceberg_fast&amp;quot;,
  &amp;quot;source&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;postgres&amp;quot;,
    &amp;quot;connection_url&amp;quot;: &amp;quot;postgresql://readonly_user:password@replica_host:5432/prod_db&amp;quot;,
    &amp;quot;tables&amp;quot;: [&amp;quot;public.customer&amp;quot;, &amp;quot;public.orders&amp;quot;]
  },
  &amp;quot;destination&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;iceberg&amp;quot;,
    &amp;quot;catalog_type&amp;quot;: &amp;quot;rest&amp;quot;,
    &amp;quot;catalog_uri&amp;quot;: &amp;quot;http://iceberg-rest:8181&amp;quot;,
    &amp;quot;warehouse_path&amp;quot;: &amp;quot;s3://my-datalake/warehouse/&amp;quot;
  },
  &amp;quot;replication_mode&amp;quot;: &amp;quot;full_and_cdc&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h4&gt;🐍 Подход Б: DuckDB (Легковесная скриптовая выгрузка)&lt;/h4&gt;
&lt;p&gt;Если вы хотите управлять выгрузкой через свои `cron`-задачи или Airflow, идеальным инструментом выступает аналитическая in-memory СУБД DuckDB. Ниже приведен протестированный Python-скрипт, который напрямую подключается к реплике и потоково перегоняет данные в Parquet на S3.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Рабочий скрипт на Python (`export_to_lake.py`):&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import duckdb

# Открываем in-memory соединение DuckDB
con = duckdb.connect()

# 1. Устанавливаем и загружаем необходимые расширения
con.execute(&amp;quot;INSTALL postgres;&amp;quot;)
con.execute(&amp;quot;INSTALL httpfs;&amp;quot;)
con.execute(&amp;quot;LOAD postgres;&amp;quot;)
con.execute(&amp;quot;LOAD httpfs;&amp;quot;)

# 2. Настраиваем подключение к объектному хранилищу
con.execute(&amp;quot;&amp;quot;&amp;quot;
    SET s3_region='us-east-1';
    SET s3_access_key_id='YOUR_KEY';
    SET s3_secret_access_key='YOUR_SECRET';
    SET s3_endpoint='s3.your-domain.com';
&amp;quot;&amp;quot;&amp;quot;)

# 3. Подключаемся к реплике PostgreSQL
# Команда ATTACH монтирует Postgres прямо в DuckDB под именем 'pg'
con.execute(&amp;quot;&amp;quot;&amp;quot;
    ATTACH 'host=replica_host port=5432 dbname=postgres user=postgres password=password' 
    AS pg (TYPE postgres);
&amp;quot;&amp;quot;&amp;quot;)

# 4. Копируем таблицу public.customer в S3 в сжатом формате Parquet
con.execute(&amp;quot;&amp;quot;&amp;quot;
    COPY pg.public.customer
    TO 's3://my-datalake/raw/customer.parquet' 
    (FORMAT PARQUET, COMPRESSION ZSTD);
&amp;quot;&amp;quot;&amp;quot;)

print(&amp;quot;Выгрузка в Data Lake успешно завершена!&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;🖱️ 2. Унификация аналитики с ClickHouse&lt;/h3&gt;
&lt;p&gt;Данные из ClickHouse также необходимо перегружать в Озеро (для Trino), чтобы избежать дублирования логики таблиц и нагрузки на саму СУБД тяжелыми сторонними `JOIN`-ами.&lt;/p&gt;
&lt;h4&gt;🛠 Базовый подход: Нативная табличная функция S3&lt;/h4&gt;
&lt;p&gt;Самый простой и не требующий дополнительной инфраструктуры способ — использовать встроенную функцию `s3()`. Она позволяет в один SQL-запрос отправить результат выборки прямо в объектное хранилище в нужном формате.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Пример выгрузки из ClickHouse в Parquet (выполняется в `clickhouse-client`):&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;-- Прямая вставка данных из локальной MergeTree таблицы в файл Parquet на S3
INSERT INTO FUNCTION s3(
    'https://s3.us-east-1.amazonaws.com/my-datalake/raw/clickhouse_export/events_{_partition_id}.parquet',
    'YOUR_KEY',
    'YOUR_SECRET',
    'Parquet'
)
SELECT id, event_type, payload, event_date
FROM local_events_mergetree
WHERE event_date = today();&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;*Совет: Используйте макрос `{_partition_id}` в пути файла для автоматического разбиения больших выгрузок.*&lt;/p&gt;
&lt;h4&gt;🌊 Продвинутый подход: Project Antalya (ClickHouse + Iceberg)&lt;/h4&gt;
&lt;p&gt;Для построения архитектуры на десятилетие вперед разработчики из Altinity создали сборку &lt;b&gt;Project Antalya&lt;/b&gt;. Она позволяет использовать таблицы Iceberg в S3 как *полноценное разделяемое хранилище*, работающее со скоростью локального диска, но обходящееся в 10 раз дешевле.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Пример прозрачного монтирования:&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;-- 1. Подключаем готовую Iceberg-таблицу прямо как движок ClickHouse
CREATE TABLE iceberg_customer
ENGINE = Iceberg('s3://my-datalake/warehouse/customer', 'aws_key', 'aws_secret');

-- 2. Запрашиваем данные. Теперь Trino и ClickHouse читают одни и те же Parquet-файлы!
SELECT count(*) FROM iceberg_customer WHERE status = 'active';&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;⚠️ Решение частых проблем при транзите данных (Troubleshooting)&lt;/h3&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;b&gt;1. Управление оперативной памятью (OOM) в DuckDB&lt;/b&gt;&lt;/summary&gt;&lt;br /&gt;
При скриптовой выгрузке гигантских таблиц in-memory движок может исчерпать RAM сервера.&lt;br /&gt;
&lt;b&gt;Решение:&lt;/b&gt; Обязательно ограничивайте ресурсы сразу после&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;duckdb.connect()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;con.execute(&amp;quot;PRAGMA memory_limit='16GB'&amp;quot;)
con.execute(&amp;quot;PRAGMA threads=4&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;b&gt;2. Консолидация сложных типов данных PostgreSQL&lt;/b&gt;&lt;/summary&gt;&lt;br /&gt;
Если в вашей таблице есть&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;JSONB&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;,&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;UUID&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;или пользовательские массивы, Parquet может упасть с ошибкой соответствия типов.&lt;br /&gt;
&lt;b&gt;Решение:&lt;/b&gt; Вместо&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;COPY pg.table&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;напишите явный SQL-запрос с приведением к строке (&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;::VARCHAR&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;con.execute(&amp;quot;&amp;quot;&amp;quot;
    COPY (
        SELECT id, metadata::VARCHAR AS metadata 
        FROM pg.public.customer
    )
    TO 's3://my-datalake/raw/customer.parquet' (FORMAT PARQUET);
&amp;quot;&amp;quot;&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Внутри Trino эти строки легко парсятся функциями вроде&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;json_extract()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;.&lt;br /&gt;
&lt;/details&gt;&lt;/p&gt;
&lt;p&gt;&lt;details&gt;&lt;br /&gt;
&lt;summary&gt;&lt;b&gt;3. Защита асинхронных реплик PostgreSQL от разрывов&lt;/b&gt;&lt;/summary&gt;&lt;br /&gt;
Длительный процесс&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;SELECT *&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(или&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;COPY&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;) мешает мастеру применять WAL-логи на реплике (из-за очистки строк VACUUM-ом).&lt;br /&gt;
&lt;b&gt;Решение:&lt;/b&gt; На аналитической реплике (в файле&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;postgresql.conf&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;) обязательно пропишите:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;max_standby_streaming_delay = -1
max_standby_archive_delay = -1
hot_standby_feedback = on&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Это позволит реплике “ставить на паузу” конфликтующие обновления и не обрывать ваш транзит данных.&lt;br /&gt;
&lt;/details&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;🎯 План внедрения (Roadmap)&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Мгновенный результат (Первые 1-3 дня):&lt;/b&gt; Используйте проверенный Python-скрипт на &lt;b&gt;DuckDB&lt;/b&gt; для баз PostgreSQL и классическую функцию &lt;b&gt;`s3()`&lt;/b&gt; для ClickHouse. Они перенесут исторические таблицы в Parquet на S3 без внесения изменений в инфраструктуру. Trino сразу увидит эти файлы.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Системный подход (1-2 недели):&lt;/b&gt; Разверните &lt;b&gt;OLake&lt;/b&gt;. Потратив пару часов на конфигурацию манифестов, вы получите автоматический конвейер инкрементальной загрузки, который напрямую питает ваши Iceberg-каталоги.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Объединение аналитики (2-4 недели):&lt;/b&gt; Начните использовать &lt;b&gt;Project Antalya&lt;/b&gt;, чтобы обогатить озеро горячими данными ClickHouse, избегая дублирования.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Окончательная эволюция:&lt;/b&gt; Когда бизнес-пожар потушен и аналитики получают данные в приемлемые сроки (T lag &lt; 1 часа), вы можете спокойно внедрить **Debezium + Kafka**. Но делать это стоит только для узкого сегмента сверхкритичных таблиц, где аналитика требуется в строгом Real-Time.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Часть 2 – Интеграция PostgreSQL, Trino и Iceberg&lt;/h3&gt;
&lt;h2&gt;Эффективный ELT: Интеграция PostgreSQL, Trino и Iceberg (сравнение подходов Table Functions и pg_lake)&lt;/h2&gt;
&lt;p&gt;В современных data-архитектурах часто возникает задача переноса реляционных данных в озера данных (Data Lakes). Если ваш стек включает &lt;b&gt;PostgreSQL&lt;/b&gt;, &lt;b&gt;Trino&lt;/b&gt; и &lt;b&gt;Iceberg&lt;/b&gt; (например, с REST-каталогом &lt;b&gt;Lakekeeper&lt;/b&gt;), возникает архитектурный вопрос: как переносить данные и обращаться к ним максимально эффективно?&lt;/p&gt;
&lt;p&gt;В этой статье мы разберем два мощных подхода: использование “нативного” для Trino проталкивания через `system.query()` и применение расширения `pg_lake` на стороне базы данных.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;Проблема: Почему Trino иногда “вытягивает” всю таблицу?&lt;/h4&gt;
&lt;p&gt;Обычно в Trino мы пишем простой федеративный запрос:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;SELECT * FROM postgres_catalog.public.customer WHERE acctbal &amp;gt; 1000;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В идеальном сценарии оптимизатор Trino считывает предикат (`acctbal &gt; 1000`) и транслирует его в SQL-диалект PostgreSQL. Это называется &lt;b&gt;Pushdown&lt;/b&gt; (проталкивание).&lt;/p&gt;
&lt;p&gt;Но на практике аналитические запросы гораздо сложнее. Если запрос содержит специфичную бизнес-логику, нестандартные оконные функции, сложные JOIN-ы или функции обработки строк, которых нет в базовом словаре коннектора Trino, оптимизатор не сможет транслировать этот кусок SQL. В результате Trino принимает решение &lt;b&gt;скачать всю таблицу в память своих воркеров&lt;/b&gt; и применить фильтрацию уже там.&lt;/p&gt;
&lt;p&gt;&lt;summary&gt;&lt;b&gt;Как работает Dynamic Filtering в Trino и почему он может не сработать (Детали)&lt;/b&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;p&gt;Особую роль при JOIN-ах играет механизм динамической фильтрации (Dynamic Filtering). Когда вы джоините большую таблицу из Postgres с маленькой таблицей (например, справочником из Hive/Iceberg), Trino сначала читает справочник (Build side), извлекает ключи, формирует SQL-фильтр (например, `IN (1, 2, 3)`) и на лету отправляет его в Postgres (Probe side).&lt;/p&gt;
&lt;p&gt;Два критичных параметра в конфигурации коннектора управляют этим процессом:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`dynamic-filtering.enabled`: Включает передачу динамических фильтров в JDBC-запросы (по умолчанию `true`).&lt;/li&gt;
&lt;li&gt;`dynamic-filtering.wait-timeout`: Максимальное время, которое Trino ждет сбора фильтров из Build-стороны JOIN-а перед тем, как запустить запрос в JDBC. По умолчанию это `20s`.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;В чем кроется опасность?&lt;/b&gt;&lt;br /&gt;
Если вычисление справочника на стороне Trino занимает больше времени, чем задано в `dynamic-filtering.wait-timeout` (например, 25 секунд против 20), координатор Trino прерывает ожидание. Чтобы не блокировать выполнение, он отправляет в Postgres “голый” запрос: `SELECT * FROM table`.&lt;br /&gt;
Вместо пары тысяч строк по сети внезапно начинают передаваться миллионы. Если загрузка сети — B, а объем таблицы PostgreSQL — V total, то время выполнения стремится к: T pull = B V total&lt;br /&gt;
что может привести к Out-of-Memory на воркерах Trino и падению кластера.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;Решение 1: Полный Pushdown через `system.query` (Для ELT-оркестрации)&lt;/h4&gt;
&lt;p&gt;Чтобы гарантировать, что вычисления и фильтры 100% выполнятся на мощностях PostgreSQL, мы можем использовать специальную табличную функцию `system.query()`.&lt;/p&gt;
&lt;p&gt;Этот подход разделяет обязанности: &lt;b&gt;PostgreSQL&lt;/b&gt; занимается фильтрацией и тяжелой математикой локально, а &lt;b&gt;Trino&lt;/b&gt; просто оркестрирует запись результата в Parquet/Iceberg.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;-- Создаем таблицу в Iceberg (Lakekeeper) и наполняем её результатами из Postgres
CREATE TABLE iceberg_catalog.raw_data.customer_metrics WITH (
    format = 'PARQUET',
    partitioning = ARRAY['mktsegment']
) AS 
SELECT
    *
FROM
    TABLE(
        postgres_catalog.system.query(
            query =&amp;gt; '
                -- Этот SQL выполняется СТРОГО внутри PostgreSQL
                SELECT 
                    custkey, 
                    name, 
                    mktsegment,
                    acctbal,
                    array_agg(acctbal) OVER (
                        PARTITION BY mktsegment 
                        ORDER BY custkey 
                        ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING
                        EXCLUDE GROUP
                    ) AS rolling_bals
                FROM public.customer
                WHERE acctbal &amp;gt; 1000 
                  AND created_at &amp;gt;= current_date - interval ''1 month''
            '
        )
    );&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Преимущество:&lt;/b&gt; Если селективность нашего фильтра S равна 0.05 (остается 5% строк), то объем передаваемых по сети данных составит строго V total \ times S. Никакие таймауты Trino не заставят Postgres отдать лишние данные.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;Решение 2: Использование `pg_lake` (Для концепции Lakehouse в PostgreSQL)&lt;/h4&gt;
&lt;p&gt;Если первый метод идеально подходит для использования Trino как движка трансформации, то зачем вообще существует проект `pg_lake`?&lt;/p&gt;
&lt;p&gt;`pg_lake` внедряет под капот PostgreSQL движок DuckDB через `pgduck_server`. Это позволяет базе данных &lt;b&gt;самостоятельно подключаться к S3 и читать/писать формат Iceberg&lt;/b&gt;, минуя Trino.&lt;/p&gt;
&lt;p&gt;&lt;summary&gt;&lt;b&gt;В чем выгода использования&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;pg_lake&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;прямо в PostgreSQL?&lt;/b&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Чтение ледяных архивов (Cold Data) без сторонних движков.&lt;/b&gt;&lt;br /&gt;
Допустим, вы переносите старые партиции данных из Postgres в Iceberg (S3) для экономии места. С `pg_lake` база Postgres “учится” читать эти архивы. Вы можете написать обычный запрос в вашем любимом клиенте (DBeaver, DataGrip, pgAdmin):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;-- Объединение горячих данных из кучи (heap) PG и холодных данных из Iceberg
SELECT * FROM public.orders_current
UNION ALL
SELECT * FROM iceberg.orders_archive WHERE order_date &amp;lt; '2023-01-01';&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;&lt;b&gt;Работа в родном диалекте PostgreSQL.&lt;/b&gt;&lt;br /&gt;
Если ваши аналитики и приложения жестко завязаны на специфические функции PostgreSQL (например, PostGIS для геоданных или сложные хранимые процедуры PL/pgSQL), интеграция с `pg_lake` позволяет анализировать гигантские внешние Iceberg-файлы, используя всю мощь экосистемы PG, без необходимости переписывать SQL-код под диалект Trino.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="3"&gt;
&lt;li&gt;&lt;b&gt;Меньше точек отказа.&lt;/b&gt;&lt;br /&gt;
Для небольших команд, которым не нужна горизонтальная масштабируемость Trino, установка `pg_lake` позволяет построить Data Lake вообще без развертывания отдельного аналитического кластера. Postgres сам выполняет COPY-команды в S3.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Итог итогов&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Используйте `system.query()` в Trino&lt;/b&gt;, если ваша цель — построить надежный, масштабируемый процесс &lt;b&gt;выгрузки (ELT)&lt;/b&gt;. Это самый безопасный паттерн: он разгружает сеть платформы данных, защищает от капризов динамической фильтрации и оставляет сервер БД свободным от сторонних плагинов.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Используйте `pg_lake`&lt;/b&gt;, если ваша бизнес-потребность — позволить самому &lt;b&gt;PostgreSQL прозрачно обращаться к Data Lake&lt;/b&gt;. Это идеальное решение для архивации холодных данных прямо из СУБД или если ваши процессы глубоко интегрированы с инструментами, понимающими только нативный протокол Postgres.&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>

<item>
<title>OPA’ля :) и хранитель озера – Lakekeeper</title>
<guid isPermaLink="false">329</guid>
<link>https://gavrilov.info/all/opalya-i-hranitel-ozera-lakekeeper/</link>
<pubDate>Tue, 14 Apr 2026 22:06:41 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/opalya-i-hranitel-ozera-lakekeeper/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-14-v-22.01.32.png" width="2130" height="1474" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/lakekeeper/lakekeeper/tree/main/authz/opa-bridge"&gt;https://github.com/lakekeeper/lakekeeper/tree/main/authz/opa-bridge&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;или тут &lt;a href="https://docs.lakekeeper.io/docs/nightly/opa/"&gt;https://docs.lakekeeper.io/docs/nightly/opa/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Много всего нового появилось у хранителя – роли, уточка и многое другое, статистика запросов&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-14-v-22.18.59.png" width="1026" height="692" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-14-v-22.05.20.png" width="2134" height="1210" alt="" /&gt;
&lt;/div&gt;
</description>
</item>

<item>
<title>Немного сборной сборки про качество и ML</title>
<guid isPermaLink="false">328</guid>
<link>https://gavrilov.info/all/nemnogo-sbornoy-sborki-pro-kachestvo-i-ml/</link>
<pubDate>Tue, 14 Apr 2026 21:43:18 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/nemnogo-sbornoy-sborki-pro-kachestvo-i-ml/</comments>
<description>
&lt;p&gt;Немного сборной сборки про качество и ML&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/andkret/Cookbook"&gt;https://github.com/andkret/Cookbook&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://podcast.ru/e/3Ldlf9-6ebG"&gt;https://podcast.ru/e/3Ldlf9-6ebG&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://habr.com/ru/companies/vtb/news/762384/"&gt;https://habr.com/ru/companies/vtb/news/762384/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Полезные ресурсы и ссылки:&lt;br /&gt;
Курс MLOps (OTUS): &lt;a href="https://otus.ru/lessons/ml-bigdata/"&gt;https://otus.ru/lessons/ml-bigdata/&lt;/a&gt;&lt;br /&gt;
Основные идеи из книги «Сотрудничество в DevOps-культуре»: &lt;a href="http://agilemindset.ru/основные-идеи-из-книги-сотрудничест/"&gt;http://agilemindset.ru/основные-идеи-из-книги-сотрудничест/&lt;/a&gt;&lt;br /&gt;
MLOps: Continuous delivery and automation pipelines in machine learning: &lt;a href="https://cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning"&gt;https://cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning&lt;/a&gt;&lt;br /&gt;
Как создавать качественные ML-системы. Часть 1: каждый проект должен начинаться с плана: &lt;a href="https://habr.com/ru/companies/vk/articles/749850/"&gt;https://habr.com/ru/companies/vk/articles/749850/&lt;/a&gt;&lt;br /&gt;
Как создавать качественные ML-системы. Часть 2: приручаем хаос: &lt;a href="https://habr.com/ru/companies/vk/articles/749852/"&gt;https://habr.com/ru/companies/vk/articles/749852/&lt;/a&gt;&lt;br /&gt;
The Data Engineering Cookbook: &lt;a href="https://github.com/andkret/Cookbook"&gt;https://github.com/andkret/Cookbook&lt;/a&gt;&lt;br /&gt;
Стандарты:&lt;br /&gt;
ISO/IEC DIS 5259-1: &lt;a href="https://www.iso.org/standard/81088.html"&gt;https://www.iso.org/standard/81088.html&lt;/a&gt;&lt;br /&gt;
SO/IEC DIS 5259-4: &lt;a href="https://www.iso.org/standard/81093.html"&gt;https://www.iso.org/standard/81093.html&lt;/a&gt;&lt;br /&gt;
ISO/IEC 8183:2023: &lt;a href="https://www.iso.org/standard/83002.html"&gt;https://www.iso.org/standard/83002.html&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>Архитектура Client Spooling: Как быстро выгружать гигантские датасеты в Trino и Apache DataFusion</title>
<guid isPermaLink="false">327</guid>
<link>https://gavrilov.info/all/arhitektura-client-spooling-kak-bystro-vygruzhat-gigantskie-data/</link>
<pubDate>Sun, 12 Apr 2026 19:11:05 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/arhitektura-client-spooling-kak-bystro-vygruzhat-gigantskie-data/</comments>
<description>
&lt;p&gt;Работа с Big Data часто упирается в классическое “узкое горлышко”: кластер может обработать терабайты данных за секунды, но передача результатов (Result Set) обратно на сторону клиента (например, в Jupyter или скрипт) занимает часы. На дворе апрель 2026 года, и современные аналитические движки предлагают эффективные методы обхода этой проблемы — концепцию &lt;b&gt;Spooling&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;Немного душноты: &lt;a href="https://www.starburst.io/blog/trino-spooling-protocol/"&gt;https://www.starburst.io/blog/trino-spooling-protocol/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Архитектура Client Spooling в Trino создавалась с параноидальным акцентом на безопасность, в S3 выкидываются куски сырых, возможно, чувствительных данных.&lt;/p&gt;
&lt;p&gt;Когда Trino решает сбросить данные в объектное хранилище, он всегда шифрует их на лету.&lt;br /&gt;
Для этого используется механизм S3 SSE-C (Server-Side Encryption with Customer-provided keys). Trino генерирует уникальный случайный AES-ключ для каждого запроса, отправляет его в MinIO вместе с данными, а клиенту (вашему Jupyter) отдает ссылку + этот же ключ для расшифровки.&lt;br /&gt;
Если мы используем локальный MinIO по адресу &lt;a href="http://minio:9000"&gt;http://minio:9000&lt;/a&gt; (без SSL/TLS), сервер MinIO видит, что ему пытаются передать секретный пароль (SSE-C ключ) по открытому незащищенному HTTP-каналу.&lt;br /&gt;
MinIO (как и настоящий AWS S3) строго запрещает это по спецификации. Он возвращает HTTP 400 Bad Request с ошибкой: “Requests specifying Server Side Encryption... must be made over a secure connection”. Поэтому тестировать лучше на реальном s3. И еще&lt;/p&gt;
&lt;p&gt;Мгновенное удаление (Сборка мусора)&lt;/p&gt;
&lt;p&gt;Главное правило Client Spooling: Trino удаляет файлы сразу же, как только они были прочитаны клиентом.&lt;br /&gt;
Как только ваш Python-скрипт или Jupyter получает ссылку на файл, скачивает его и отправляет координатору Trino HTTP-сигнал (ACK), что кусок получен, координатор дает команду немедленно удалить этот объект из S3.&lt;br /&gt;
Если запрос отменен или упал с ошибкой, Trino тоже моментально зачищает за собой fs.location. Вы просто не успеете их там увидеть.&lt;/p&gt;
&lt;p&gt;Данных слишком мало (Thresholds)&lt;/p&gt;
&lt;p&gt;Писать 10 строк в S3, генерировать для них Pre-signed URLs и отдавать клиенту — это дольше, чем просто плюнуть эти 10 строк текстом через координатор. Trino использует эвристику: если Result Set маленький, он отдается “инлайн” (внутри JSON-ответа самого координатора), и S3 не задействуется.&lt;/p&gt;
&lt;p&gt;В этой статье мы разберем, как передавать результаты запросов через промежуточное S3-хранилище, на примере движков Trino и Apache DataFusion.&lt;/p&gt;
&lt;h4&gt;Физика проблемы и математика Spooling&lt;/h4&gt;
&lt;p&gt;В классической архитектуре все воркеры кластера отправляют вычисленные строки на главный узел (Coordinator), а тот уже отдает их по одному каналу клиенту.&lt;/p&gt;
&lt;p&gt;Если D — это объем результирующей выборки, а B c — пропускная способность сети координатора, то время выгрузки данных клиенту без спулинга равно:&lt;/p&gt;
&lt;p&gt;T classic = B / Dc&lt;/p&gt;
&lt;p&gt;В режиме &lt;b&gt;Spooling&lt;/b&gt; координатор не гоняет данные через себя. Воркеры напрямую, параллельно пишут куски результата в дешевое объектное хранилище (S3/MinIO). Клиент получает лишь ссылки на эти файлы и скачивает их напрямую. Если у нас N файлов в S3, доступных для многопоточного скачивания с пропускной способностью клиента B client: T spooling ≈ min(N×B s3,B client)D&lt;/p&gt;
&lt;p&gt;Это позволяет ускорить выгрузку в десятки раз, так как $B_{client}$ и распределенный $B_{s3}$ обычно значительно больше ограничений одного координатора.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;Подготовка минимальной инфраструктуры&lt;/h4&gt;
&lt;p&gt;Для демонстрации двух подходов мы убрали из нашего кластера все тяжелые клиентские среды (Jupyter, Spark) и оставили только “голое” ядро: хранилище S3, REST-каталог и SQL-движок.&lt;/p&gt;
&lt;p&gt;&lt;summary&gt;&lt;b&gt;минимальный&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;docker-compose.yml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;/b&gt;&lt;/summary&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;version: '3.8'

services:
  minio:
    image: minio/minio:latest
    ports:
      - &amp;quot;19000:9000&amp;quot;
      - &amp;quot;19001:9001&amp;quot;
    environment:
      MINIO_ROOT_USER: &amp;quot;minio-root-user&amp;quot;
      MINIO_ROOT_PASSWORD: &amp;quot;minio-root-password&amp;quot;
    command: server /data --console-address &amp;quot;:9001&amp;quot;

  minio-setup:
    image: minio/mc:latest
    depends_on:
      - minio
    entrypoint: &amp;gt;
      /bin/sh -c &amp;quot;
      sleep 5;
      mc alias set myminio http://minio:9000 minio-root-user minio-root-password;
      mc mb myminio/warehouse || true;
      &amp;quot;

  lakekeeper:
    image: dalongrong/lakekeeper:latest
    ports:
      - &amp;quot;8181:8181&amp;quot;
    environment:
      - S3_ENDPOINT=http://minio:9000
      - S3_REGION=us-east-1
      - S3_ACCESS_KEY_ID=minio-root-user
      - S3_SECRET_ACCESS_KEY=minio-root-password
    depends_on:
      - minio-setup

  trino:
    image: trinodb/trino:latest
    ports:
      - &amp;quot;8080:8080&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;summary&gt;&lt;b&gt;Шаг 1. Настройка каталога и генерация данных (Trino)&lt;/b&gt;&lt;/summary&gt;&lt;br /&gt;
&lt;br&gt;&lt;br /&gt;
Сначала мы генерируем данные в Trino. Запрос&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;CREATE CATALOG&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;использует динамическое подключение к Lakekeeper REST API. Скрипт записывает файлы в формате Parquet в MinIO:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;config.properties&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;protocol.spooling.enabled=true
# 256-битный ключ в формате base64. Вы можете сгенерировать свой с помощью команды `openssl rand -base64 32`
protocol.spooling.shared-secret-key=jxTKysfCBuMZtFqUf8UJDQ1w9ez8rynEJsJqgJf66u0=

catalog.management=dynamic&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;spooling-manager.properties&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;spooling-manager.name=filesystem
# Включаем чтение/запись в S3 для Spooling
fs.s3.enabled=true
# Путь внутри MinIO (указываем через s3://)
fs.location=s3://warehouse/client-spooling/

# Системные настройки S3 (MinIO)
s3.endpoint=http://minio:9000
s3.region=us-east-1
s3.aws-access-key=minio-root-user
s3.aws-secret-key=minio-root-password
s3.path-style-access=true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;-- 1. Подключение каталога Iceberg&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;CREATE CATALOG test_warehouse USING iceberg
WITH (
    &amp;quot;iceberg.catalog.type&amp;quot; = 'rest',
    &amp;quot;iceberg.rest-catalog.uri&amp;quot; = 'http://lakekeeper:8181/catalog/',
    &amp;quot;iceberg.rest-catalog.warehouse&amp;quot; = '00000000-0000-0000-0000-000000000000/test_warehouse',
    &amp;quot;iceberg.rest-catalog.security&amp;quot; = 'OAUTH2',
    &amp;quot;iceberg.rest-catalog.nested-namespace-enabled&amp;quot; = 'true',
    &amp;quot;iceberg.rest-catalog.vended-credentials-enabled&amp;quot; = 'true',
    &amp;quot;fs.native-s3.enabled&amp;quot; = 'true',
    &amp;quot;s3.region&amp;quot; = 'us-east-1',
    &amp;quot;s3.path-style-access&amp;quot; = 'true',
    &amp;quot;s3.endpoint&amp;quot; = 'http://minio:9000'
);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;-- 2. Создание структуры&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;CREATE SCHEMA test_warehouse.test_schema;

CREATE TABLE test_warehouse.test_schema.my_table (
    id BIGINT,
    data VARCHAR
) WITH (format = 'PARQUET');&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;-- 3. Запись данных&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;INSERT INTO test_warehouse.test_schema.my_table VALUES (1, 'hello'), (2, 'world');&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;p&gt;Если написать Select – должно быть как-то так&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-12-v-18.25.52.png" width="490" height="284" alt="" /&gt;
&lt;/div&gt;
&lt;h4&gt;Аналог Spooling в Apache DataFusion (Через экспорт)&lt;/h4&gt;
&lt;p&gt;Trino поддерживает протокол *Client Spooling* “из коробки” — когда Python-клиент запрашивает огромный `SELECT`, Trino сам незаметно пишет куски в S3 и отдает клиенту готовые ссылки.&lt;/p&gt;
&lt;p&gt;В &lt;b&gt;Apache DataFusion&lt;/b&gt; (который часто работает как локальный движок `datafusion-cli` или встраиваемая библиотка поверх S3) применяется более прозрачный паттерн делегирования (Explicit Spooling). Мы вручную инструктируем движок сохранить результаты агрегации в распределенное хранилище, чтобы позже забрать их в удобном формате — например, упаковав их в `JSON` и сжав алгоритмом `ZSTD`.&lt;/p&gt;
&lt;h5&gt;1. Подключение к S3 и маппинг исходной таблицы&lt;/h5&gt;
&lt;p&gt;Запускаем `datafusion-cli`, передав доступы как переменные среды (для предотвращения ошибок парсинга опций):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;AWS_ACCESS_KEY_ID=&amp;quot;minio-root-user&amp;quot; \
AWS_SECRET_ACCESS_KEY=&amp;quot;minio-root-password&amp;quot; \
AWS_ENDPOINT=&amp;quot;http://localhost:19000&amp;quot; \
AWS_REGION=&amp;quot;us-east-1&amp;quot; \
AWS_ALLOW_HTTP=&amp;quot;true&amp;quot; \
datafusion-cli&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Внутри консоли подключаем директорию с Parquet-файлами, сгенерированными Trino:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;CREATE EXTERNAL TABLE my_parquet_data 
STORED AS PARQUET 
LOCATION 's3://warehouse/019d81a3-c2d6-7ed2-ab15-070becf62582/my_table-13e4b91a2b4e47d98f312b1384263880/data/';&lt;/code&gt;&lt;/pre&gt;&lt;h5&gt;2. Массовая конвертация и выгрузка (DataFusion COPY)&lt;/h5&gt;
&lt;p&gt;Вместо того чтобы тянуть миллионы строк на локальный терминал, мы просим DataFusion выполнить преобразование и записать итог запроса обратно в MinIO.&lt;/p&gt;
&lt;p&gt;Мы выбираем построчный JSON с экстремальным сжатием:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;COPY (
    -- Тут может быть любая сложная агрегация:
    -- SELECT id, count(data) FROM my_parquet_data GROUP BY id
    SELECT * FROM my_parquet_data
) 
TO 's3://warehouse/019d81a3-c2d6-7ed2-ab15-070becf62582/my_table-13e4b91a2b4e47d98f312b1384263880/json_export/' 
STORED AS JSON
OPTIONS (
    'format.compression' 'zstd'
);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Результат:&lt;/b&gt;&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;+-------+
| count |
+-------+
| 2     |
+-------+
1 row(s) fetched. 
Elapsed 0.270 seconds.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;За миллисекунды (0.270 sec) DataFusion прочитал партиции, трансформировал бинарные столбцы в текст и сжал его.&lt;/p&gt;
&lt;h4&gt;В чем преимущество подхода DataFusion?&lt;/h4&gt;
&lt;p&gt;Описанный паттерн выполнения команды `COPY TO` с сохранением `.json.zst` в MinIO полностью воспроизводит механику Spooling:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Отсутствие OOM (Out Of Memory):&lt;/b&gt; Клиент получает только метаданные `count`, а не гигабайты сырых данных в оперативную память.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Параллелизм:&lt;/b&gt; Если исходных файлов много, DataFusion будет писать множество потоков `part-0.json.zst`, `part-1.json.zst` в бакет параллельно.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Удаленное потребление:&lt;/b&gt; Вы можете запустить легкий Python-скрипт (Pandas) на дешевой машине, который просто прочитает эти сжатые легковесные JSON объекты напрямую из MinIO, минуя дорогостоящие вычислительные кластеры.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Еще немного про  Fault-Tolerant Execution (FTE), нужно провести важную границу между &lt;b&gt;архитектурой Trino&lt;/b&gt; (готовый распределенный кластер) и &lt;b&gt;архитектурой DataFusion&lt;/b&gt; (ядро/библиотека выполнения запросов).&lt;/p&gt;
&lt;p&gt;В самом “голом” ядре DataFusion (которое вы запускаете в `datafusion-cli` или в Jupyter) &lt;b&gt;нет встроенного механизма Task Retries&lt;/b&gt;, потому что процессы выполняются на одной машине в рамках одного приложения. Если сервер падает — запрос прерывается.&lt;/p&gt;
&lt;p&gt;Однако, в экосистеме DataFusion есть механизмы отказоустойчивости, которые делятся на два уровня: &lt;b&gt;локальный (Spilling)&lt;/b&gt; и &lt;b&gt;распределенный (Apache Ballista / Ray)&lt;/b&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;1. Локальная отказоустойчивость (защита от OOM)&lt;/h4&gt;
&lt;p&gt;В Trino частой причиной падения задач является нехватка памяти (Out of Memory). В DataFusion реализован мощный механизм управления памятью.&lt;/p&gt;
&lt;p&gt;Если DataFusion понимает, что оперативной памяти для агрегации или JOIN’а не хватает, он не “роняет” задачу, а начинает сбрасывать промежуточные данные на диск (&lt;b&gt;Spill to Disk&lt;/b&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Это настраивается через конфигурацию `datafusion.execution.disk_manager`.&lt;/li&gt;
&lt;li&gt;Это аналог локального `spill-enabled = true` в Trino. Запрос замедлится, но выполнится до конца, не упав с ошибкой.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. Распределенная отказоустойчивость (Аналог Trino FTE)&lt;/h4&gt;
&lt;p&gt;Trino использует архитектуру &lt;b&gt;Fault-Tolerant Execution (FTE)&lt;/b&gt;, при которой промежуточные результаты (Shuffle Exchange) пишутся в S3, а упавшие воркеры заменяются, и их задачи (Tasks) перезапускаются координатором.&lt;/p&gt;
&lt;p&gt;В мире DataFusion эту задачу решает не само ядро, а &lt;b&gt;распределенные планировщики&lt;/b&gt;, построенные поверх него:&lt;/p&gt;
&lt;h5&gt;А. Apache Ballista (Официальный распределенный DataFusion)&lt;/h5&gt;
&lt;p&gt;Ballista — это надстройка над DataFusion, превращающая его в полноценный кластер (с Coordinator и Executors), архитектурно очень похожая на Apache Spark и Trino.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Task Retries:&lt;/b&gt; Если один из Executor’ов теряется из-за сбоя сети или железа, Ballista Coordinator замечает это и &lt;b&gt;переназначает задачу (Task) другому воркеру&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Shuffle Spilling:&lt;/b&gt; Промежуточные данные между стадиями (Stages) записываются во временные файлы. Следовательно, если упала только последняя стадия, кластеру не нужно пересчитывать весь запрос с нуля — он прочитает промежуточные Shuffle-файлы и повторит только упавший кусок.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Б. DataFusion on Ray (datafusion-ray)&lt;/h5&gt;
&lt;p&gt;Сейчас огромную популярность набирает запуск DataFusion поверх кластера &lt;b&gt;Ray&lt;/b&gt;.&lt;br /&gt;
Ray — это супер-устойчивый распределенный фреймворк. Интеграция `datafusion-ray` позволяет разбить SQL-запрос на граф задач прямо в Ray.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;За отказоустойчивость, Retry-логику и восстановление упавших узлов (Actor/Task) здесь отвечает сам Ray, который делает это на уровне индустриального стандарта.&lt;/li&gt;
&lt;li&gt;Это максимально близко к концепции отказоустойчивого кластера.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Резюме: Как получить “Trino-like” Fault Tolerance в DataFusion?&lt;/h4&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Если вы используете &lt;b&gt;локальный DataFusion&lt;/b&gt; (в Python или CLI): Отказоустойчивости уровня узлов нет, но есть защита от падений по памяти (Spill to Disk). Если упадет процесс — нужно перезапускать запрос руками.&lt;/li&gt;
&lt;li&gt;Если вам нужен настоящий &lt;b&gt;Task Repeat / Fault Tolerance&lt;/b&gt; на сотнях серверов, где падение серверов — норма: вы используете движок DataFusion вместе с кластерным менеджером &lt;b&gt;Apache Ballista&lt;/b&gt; или &lt;b&gt;Ray&lt;/b&gt;, которые прозрачно обеспечат перезапуск задач (Retries) и сохранение промежуточных состояний (Shuffle), полностью повторяя логику Trino FTE.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;UPD: В локальном тестировании есть некоторые особенности. Когда контейнеры внутри имеют свою сеть, то трино посылает в dbeaver ссылки. А есть хост не знает что это за минива или localstack-spooling, то оно отдаст кусок данных, а остальные части просто не доедут. Квери упадет как отмененная, так как клиент получил не все результаты. Короче, надо просто так сделать&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;sudo nano /etc/hosts&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;и вставить строку вашего s3 хоста.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;127.0.0.1       localstack-spooling&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;то есть при спулинге клиент должен не только иметь сетевую связанность с s3 но различать dns имена корректно.&lt;/p&gt;
&lt;p&gt;Короче сравния строк пройдено, все сошлося :)&lt;/p&gt;
&lt;p&gt;со спулингом 2.2 сек&lt;br /&gt;
без спулинга 4.4 сек&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-14-v-23.33.03.png" width="1300" height="136" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Питончик 2.16 сек с чанками&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-14-v-23.53.52.png" width="880" height="506" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;в самом трино еще быстрее&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-14-v-23.57.40.png" width="1190" height="282" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;все строки на месте: 150тыщъ&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-14-v-23.58.37.png" width="614" height="84" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;код !!&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;details&gt;&lt;br /&gt;
from trino.dbapi import connect&lt;br /&gt;
import json&lt;/p&gt;
&lt;h2&gt;–&lt;s&gt; Конфигурация –&lt;/s&gt;&lt;/h2&gt;
&lt;p&gt;TRINO_HOST = “localhost”&lt;br /&gt;
TRINO_PORT = 9999&lt;br /&gt;
TRINO_USER = “trino”&lt;br /&gt;
TRINO_CATALOG = “test_warehouse”&lt;br /&gt;
TRINO_SCHEMA = “test_schema”&lt;br /&gt;
OUTPUT_FILE = “output.json”&lt;br /&gt;
CHUNK_SIZE = 10000  # Количество строк, обрабатываемых за один раз&lt;/p&gt;
&lt;p&gt;def export_to_json():&lt;br /&gt;
conn = connect(&lt;br /&gt;
host=TRINO_HOST,&lt;br /&gt;
port=TRINO_PORT,&lt;br /&gt;
user=TRINO_USER,&lt;br /&gt;
catalog=TRINO_CATALOG,&lt;br /&gt;
schema=TRINO_SCHEMA,&lt;br /&gt;
)&lt;br /&gt;
cursor = conn.cursor()&lt;/p&gt;
&lt;p&gt;try:&lt;/p&gt;
&lt;h2&gt;Отключаем Fault-Tolerant Execution&lt;/h2&gt;
&lt;p&gt;cursor.execute(“SET SESSION retry_policy = ‘NONE’”)&lt;br /&gt;
cursor.execute(“SELECT * FROM my_table2”)&lt;/p&gt;
&lt;p&gt;column_names = [desc[0] for desc in cursor.description]&lt;br /&gt;
row_count = 0&lt;/p&gt;
&lt;p&gt;with open(OUTPUT_FILE, “w”, encoding=“utf-8”) as f:&lt;/p&gt;
&lt;h2&gt;Используем fetchmany для чанков&lt;/h2&gt;
&lt;p&gt;while True:&lt;br /&gt;
rows = cursor.fetchmany(CHUNK_SIZE)&lt;br /&gt;
if not rows:&lt;br /&gt;
break&lt;br /&gt;
for row in rows:&lt;br /&gt;
row_dict = dict(zip(column_names, row))&lt;br /&gt;
f.write(json.dumps(row_dict, ensure_ascii=False, default=str) + “\n”)&lt;br /&gt;
row_count += len(rows)&lt;br /&gt;
print(f“Processed {row_count} rows...”)&lt;/p&gt;
&lt;p&gt;print(f“Successfully exported {row_count} rows to {OUTPUT_FILE}”)&lt;/p&gt;
&lt;p&gt;finally:&lt;br /&gt;
cursor.close()&lt;br /&gt;
conn.close()&lt;/p&gt;
&lt;p&gt;if __name__ == “__main__”:&lt;br /&gt;
export_to_json()&lt;/p&gt;
&lt;p&gt;&lt;/details&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;Вот еще с уточкой и чанками&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-15-v-01.12.49.png" width="1064" height="514" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;код&lt;/p&gt;
&lt;p&gt;&lt;html&gt;&lt;details&gt;&lt;br /&gt;
import duckdb&lt;br /&gt;
import json&lt;/p&gt;
&lt;p&gt;OUTPUT_FILE = “/home/jovyan/examples/output_duckdb.json”&lt;br /&gt;
CHUNK_SIZE = 10000&lt;/p&gt;
&lt;p&gt;conn = duckdb.connect()&lt;/p&gt;
&lt;h2&gt;расширения и настройки (как у вас)&lt;/h2&gt;
&lt;p&gt;conn.execute(“INSTALL httpfs; LOAD httpfs;”)&lt;br /&gt;
conn.execute(“INSTALL iceberg; LOAD iceberg;”)&lt;br /&gt;
conn.execute(“SET memory_limit = ‘4GB’;”)&lt;br /&gt;
conn.execute(“SET s3_region = ‘us-east-1’;”)&lt;/p&gt;
&lt;p&gt;conn.execute(“‘’&lt;br /&gt;
CREATE OR REPLACE SECRET minio_secret (&lt;br /&gt;
TYPE S3,&lt;br /&gt;
KEY_ID ‘minio-root-user’,&lt;br /&gt;
SECRET ‘minio-root-password’,&lt;br /&gt;
ENDPOINT ‘minio:9000’,&lt;br /&gt;
USE_SSL false,&lt;br /&gt;
URL_STYLE ‘path’&lt;br /&gt;
);&lt;br /&gt;
‘‘’)&lt;/p&gt;
&lt;p&gt;conn.execute(‘‘’&lt;br /&gt;
CREATE OR REPLACE SECRET iceberg_secret (&lt;br /&gt;
TYPE ICEBERG,&lt;br /&gt;
TOKEN ‘dummy’&lt;br /&gt;
);&lt;br /&gt;
‘‘’)&lt;/p&gt;
&lt;p&gt;conn.execute(‘‘’&lt;br /&gt;
ATTACH ‘test_warehouse’ AS lakekeeper_db (&lt;br /&gt;
TYPE ICEBERG,&lt;br /&gt;
ENDPOINT ’&lt;a href="http://lakekeeper:8181/catalog/',"&gt;http://lakekeeper:8181/catalog/',&lt;/a&gt;&lt;br /&gt;
ACCESS_DELEGATION_MODE ‘none’,&lt;br /&gt;
SECRET iceberg_secret&lt;br /&gt;
);&lt;br /&gt;
‘‘’)&lt;/p&gt;
&lt;h2&gt;Используем cursor и fetchmany для чанков&lt;/h2&gt;
&lt;p&gt;cursor = conn.cursor()&lt;br /&gt;
cursor.execute(‘SELECT * FROM lakekeeper_db.test_schema.my_table2’)&lt;/p&gt;
&lt;h2&gt;Получаем имена колонок&lt;/h2&gt;
&lt;p&gt;col_names = [desc[0] for desc in cursor.description]&lt;/p&gt;
&lt;p&gt;total_rows = 0&lt;br /&gt;
with open(OUTPUT_FILE, ‘w’, encoding=’utf-8’) as f:&lt;br /&gt;
while True:&lt;br /&gt;
rows = cursor.fetchmany(CHUNK_SIZE)&lt;br /&gt;
if not rows:&lt;br /&gt;
break&lt;br /&gt;
for row in rows:&lt;br /&gt;
row_dict = dict(zip(col_names, row))&lt;br /&gt;
f.write(json.dumps(row_dict, ensure_ascii=False, default=str) + ‘\n’)&lt;br /&gt;
total_rows += len(rows)&lt;br /&gt;
print(f’Обработано строк: {total_rows}’)&lt;/p&gt;
&lt;p&gt;print(f’✅ Загружено и сохранено строк: {total_rows}”)&lt;br /&gt;
print(f“📁 Данные сохранены в {OUTPUT_FILE}”)&lt;br /&gt;
conn.close()&lt;/p&gt;
&lt;p&gt;&lt;/details&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;Можно даже так внутри уточки&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-04-15-v-01.19.57.png" width="1100" height="204" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;html&gt; &lt;details&gt;&lt;br /&gt;
import duckdb&lt;/p&gt;
&lt;p&gt;OUTPUT_FILE = “/home/jovyan/examples/output_duckdb_direct.json”&lt;/p&gt;
&lt;p&gt;conn = duckdb.connect()&lt;/p&gt;
&lt;h2&gt;Расширения и настройки&lt;/h2&gt;
&lt;p&gt;conn.execute(“INSTALL httpfs; LOAD httpfs;”)&lt;br /&gt;
conn.execute(“INSTALL iceberg; LOAD iceberg;”)&lt;br /&gt;
conn.execute(“SET memory_limit = ‘4GB’;”)&lt;br /&gt;
conn.execute(“SET s3_region = ‘us-east-1’;”)&lt;/p&gt;
&lt;h2&gt;Секрет для MinIO&lt;/h2&gt;
&lt;p&gt;conn.execute(“‘’&lt;br /&gt;
CREATE OR REPLACE SECRET minio_secret (&lt;br /&gt;
TYPE S3,&lt;br /&gt;
KEY_ID ‘minio-root-user’,&lt;br /&gt;
SECRET ‘minio-root-password’,&lt;br /&gt;
ENDPOINT ‘minio:9000’,&lt;br /&gt;
USE_SSL false,&lt;br /&gt;
URL_STYLE ‘path’&lt;br /&gt;
);&lt;br /&gt;
‘‘’)&lt;/p&gt;
&lt;h2&gt;Секрет для Iceberg REST&lt;/h2&gt;
&lt;p&gt;conn.execute(‘‘’&lt;br /&gt;
CREATE OR REPLACE SECRET iceberg_secret (&lt;br /&gt;
TYPE ICEBERG,&lt;br /&gt;
TOKEN ‘dummy’&lt;br /&gt;
);&lt;br /&gt;
‘‘’)&lt;/p&gt;
&lt;h2&gt;Подключение каталога Lakekeeper&lt;/h2&gt;
&lt;p&gt;conn.execute(‘‘’&lt;br /&gt;
ATTACH ‘test_warehouse’ AS lakekeeper_db (&lt;br /&gt;
TYPE ICEBERG,&lt;br /&gt;
ENDPOINT ’&lt;a href="http://lakekeeper:8181/catalog/',"&gt;http://lakekeeper:8181/catalog/',&lt;/a&gt;&lt;br /&gt;
ACCESS_DELEGATION_MODE ‘none’,&lt;br /&gt;
SECRET iceberg_secret&lt;br /&gt;
);&lt;br /&gt;
‘‘’)&lt;/p&gt;
&lt;h2&gt;Экспорт в JSON (массив)&lt;/h2&gt;
&lt;p&gt;conn.execute(f’’’&lt;br /&gt;
COPY (&lt;br /&gt;
SELECT * FROM lakekeeper_db.test_schema.my_table2&lt;br /&gt;
) TO ‘{OUTPUT_FILE}’ (FORMAT JSON);&lt;br /&gt;
‘‘’)&lt;/p&gt;
&lt;p&gt;print(f’✅ Данные сохранены в {OUTPUT_FILE}’)&lt;br /&gt;
conn.close()&lt;br /&gt;
&lt;/details&gt;&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;К конце концов я использовал&lt;/p&gt;
&lt;p&gt;localstack-spooling&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;protocol.spooling.enabled=true
# 256-битный ключ в формате base64. Вы можете сгенерировать свой с помощью команды `openssl rand -base64 32`
protocol.spooling.shared-secret-key=jxTKysfCBuMZtFqUf8UJDQ1w9ez8rynEJsJqgJf66u0=
catalog.management=dynamic&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;так&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;spooling-manager.name=filesystem
fs.s3.enabled=true
fs.location=s3://spooling-bucket/client-spooling/

s3.endpoint=http://localstack-spooling:4566
s3.region=us-east-1
s3.aws-access-key=test
s3.aws-secret-key=test
s3.path-style-access=true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;и так&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;services:

  trino:
    build: ./trino
    environment:
      - CATALOG_MANAGEMENT=dynamic
      - LANCE_ALLOW_HTTP=true
      - AWS_ALLOW_HTTP=true
      - AWS_ACCESS_KEY_ID=minio-root-user
      - AWS_SECRET_ACCESS_KEY=minio-root-password
      - AWS_REGION=us-east-1
      - AWS_ENDPOINT_URL=http://minio:9000
      - CATALOG_MANAGEMENT=dynamic
      - 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
    healthcheck:
      test: [&amp;quot;CMD&amp;quot;, &amp;quot;curl&amp;quot;, &amp;quot;-I&amp;quot;, &amp;quot;http://localhost:8080/v1/status&amp;quot;]
      interval: 2s
      timeout: 10s
      retries: 2
      start_period: 10s
    ports:
      - &amp;quot;9999:8080&amp;quot;
    volumes:
      - ./lance5.properties:/etc/trino/catalog/lance5.properties
      - ./lance_rest.properties:/etc/trino/catalog/lance_rest.properties
      - ./lance_ice.properties:/etc/trino/catalog/lance_ice.properties
      # --- ДОБАВЬТЕ ЭТУ СТРОКУ ---
      - ./spooling-manager.properties:/etc/trino/spooling-manager.properties
      # (При необходимости пробросьте и config.properties, если он не копируется при build: ./trino)
      - ./config.properties:/etc/trino/config.properties
      - spooling-data:/tmp/spooling
    networks:
      - lakekeeper-network
    depends_on:
      localstack-setup:    # &amp;lt;--- Trino ждет, пока AWS CLI не создаст бакет!
        condition: service_completed_successfully

  localstack-spooling:
    image: localstack/localstack:3.4.0    # Жестко фиксируем бесплатную рабочую версию!
    container_name: localstack-spooling
    ports:
      - &amp;quot;4566:4566&amp;quot;
    environment:
      - SERVICES=s3
      - AWS_DEFAULT_REGION=us-east-1
    networks:
      - lakekeeper-network

  localstack-setup:
    image: amazon/aws-cli:latest
    container_name: localstack-setup
    depends_on:
      - localstack-spooling
    restart: &amp;quot;no&amp;quot;
    environment:
      - AWS_ACCESS_KEY_ID=test
      - AWS_SECRET_ACCESS_KEY=test
      - AWS_DEFAULT_REGION=us-east-1
    entrypoint: &amp;gt;
      /bin/sh -c &amp;quot;
        echo 'Waiting for LocalStack to fully start...';
        sleep 10;
        aws --endpoint-url=http://localstack-spooling:4566 s3 mb s3://spooling-bucket;
        echo 'LocalStack bucket created successfully!';
      &amp;quot;
    networks:
      - lakekeeper-network
      
  jupyter:
    image: quay.io/jupyter/pyspark-notebook:2024-10-14
    depends_on:
      lakekeeper:
        condition: service_healthy
      # Исправлено: теперь зависим от рабочего setup сервиса
      lakekeeper-setup:
        condition: service_completed_successfully
      trino:
        condition: service_healthy
      # Удалено: starrocks (сервис не описан в compose файле)
    command: start-notebook.sh --NotebookApp.token=''
    volumes:
      - ./notebooks:/home/jovyan/examples/
      - spooling-data:/tmp/spooling
    networks:
      - lakekeeper-network
    ports:
      - &amp;quot;8888:8888&amp;quot;

  # Сервис initialwarehouse УДАЛЕН, так как он дублировал lakekeeper-setup 
  # и ссылался на несуществующие сервисы (bootstrap, createbuckets).

  postgres-lakekeeper:
    image: postgres:17
    container_name: postgres-lakekeeper
    environment:
      POSTGRES_USER: lakekeeper
      POSTGRES_PASSWORD: lakekeeper
      POSTGRES_DB: lakekeeper
    ports:
      - &amp;quot;5435:5432&amp;quot;
    volumes:
      - lakekeeper-postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: [&amp;quot;CMD-SHELL&amp;quot;, &amp;quot;pg_isready -U lakekeeper -d lakekeeper&amp;quot;]
      interval: 2s
      timeout: 10s
      retries: 5
    networks:
      - lakekeeper-network

  minio:
    image: minio/minio:latest
    container_name: minio-lakekeeper
    environment:
      MINIO_ROOT_USER: minio-root-user
      MINIO_ROOT_PASSWORD: minio-root-password
      # MINIO_DOMAIN: minio
    command: server /data --console-address &amp;quot;:9001&amp;quot;
    ports:
      - &amp;quot;19000:9000&amp;quot;
      - &amp;quot;19001:9001&amp;quot;
    volumes:
      - lakekeeper-minio-data:/data
    healthcheck:
      test: [&amp;quot;CMD&amp;quot;, &amp;quot;mc&amp;quot;, &amp;quot;ready&amp;quot;, &amp;quot;local&amp;quot;]
      interval: 2s
      timeout: 10s
      retries: 5
    networks:
      - lakekeeper-network

  minio-setup:
    image: minio/mc:latest
    container_name: minio-setup
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: &amp;gt;
      /bin/sh -c &amp;quot;
        mc alias set myminio http://minio:9000 minio-root-user minio-root-password &amp;amp;&amp;amp;
        mc mb myminio/warehouse --ignore-existing &amp;amp;&amp;amp;
        echo 'MinIO bucket created'
      &amp;quot;
    networks:
      - lakekeeper-network

  lakekeeper-migrate:
    image: quay.io/lakekeeper/catalog:latest-main
    container_name: lakekeeper-migrate
    depends_on:
      postgres-lakekeeper:
        condition: service_healthy
    environment:
      - LAKEKEEPER__PG_ENCRYPTION_KEY=test-encryption-key-not-secure
      - LAKEKEEPER__PG_DATABASE_URL_READ=postgresql://lakekeeper:lakekeeper@postgres-lakekeeper:5432/lakekeeper
      - LAKEKEEPER__PG_DATABASE_URL_WRITE=postgresql://lakekeeper:lakekeeper@postgres-lakekeeper:5432/lakekeeper
    restart: &amp;quot;no&amp;quot;
    command: [&amp;quot;migrate&amp;quot;]
    networks:
      - lakekeeper-network

  lakekeeper:
    image: quay.io/lakekeeper/catalog:latest-main
    container_name: lakekeeper
    depends_on:
      lakekeeper-migrate:
        condition: service_completed_successfully
      minio-setup:
        condition: service_completed_successfully
    environment:
      - LAKEKEEPER__PG_ENCRYPTION_KEY=test-encryption-key-not-secure
      - LAKEKEEPER__PG_DATABASE_URL_READ=postgresql://lakekeeper:lakekeeper@postgres-lakekeeper:5432/lakekeeper
      - LAKEKEEPER__PG_DATABASE_URL_WRITE=postgresql://lakekeeper:lakekeeper@postgres-lakekeeper:5432/lakekeeper
      - LAKEKEEPER__AUTHZ_BACKEND=allowall
      - RUST_LOG=info
    command: [&amp;quot;serve&amp;quot;]
    healthcheck:
      test: [&amp;quot;CMD&amp;quot;, &amp;quot;/home/nonroot/lakekeeper&amp;quot;, &amp;quot;healthcheck&amp;quot;]
      interval: 2s
      timeout: 10s
      retries: 5
      start_period: 5s
    ports:
      - &amp;quot;8282:8181&amp;quot;
    networks:
      - lakekeeper-network

  lakekeeper-bootstrap:
    image: curlimages/curl
    container_name: lakekeeper-bootstrap
    depends_on:
      lakekeeper:
        condition: service_healthy
    restart: &amp;quot;no&amp;quot;
    command:
      - -w
      - &amp;quot;%{http_code}&amp;quot;
      - &amp;quot;-X&amp;quot;
      - &amp;quot;POST&amp;quot;
      - &amp;quot;-v&amp;quot;
      - &amp;quot;http://lakekeeper:8181/management/v1/bootstrap&amp;quot;
      - &amp;quot;-H&amp;quot;
      - &amp;quot;Content-Type: application/json&amp;quot;
      - &amp;quot;--data&amp;quot;
      - '{&amp;quot;accept-terms-of-use&amp;quot;: true}'
      - &amp;quot;-o&amp;quot;
      - &amp;quot;/dev/null&amp;quot;
    networks:
      - lakekeeper-network

  lakekeeper-setup:
    image: curlimages/curl
    container_name: lakekeeper-setup
    depends_on:
      lakekeeper-bootstrap:
        condition: service_completed_successfully
    restart: &amp;quot;no&amp;quot;
    entrypoint: [&amp;quot;/bin/sh&amp;quot;, &amp;quot;-c&amp;quot;]
    command:
      - |
        echo &amp;quot;Creating test_warehouse...&amp;quot;
        curl -sf -X POST &amp;quot;http://lakekeeper:8181/management/v1/warehouse&amp;quot; \
          -H &amp;quot;Content-Type: application/json&amp;quot; \
          -d '{
            &amp;quot;warehouse-name&amp;quot;: &amp;quot;test_warehouse&amp;quot;,
            &amp;quot;project-id&amp;quot;: &amp;quot;00000000-0000-0000-0000-000000000000&amp;quot;,
            &amp;quot;storage-profile&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;s3&amp;quot;,
              &amp;quot;bucket&amp;quot;: &amp;quot;warehouse&amp;quot;,
              &amp;quot;endpoint&amp;quot;: &amp;quot;http://minio:9000&amp;quot;,
              &amp;quot;region&amp;quot;: &amp;quot;us-east-1&amp;quot;,
              &amp;quot;path-style-access&amp;quot;: true,
              &amp;quot;flavor&amp;quot;: &amp;quot;minio&amp;quot;,
              &amp;quot;sts-enabled&amp;quot;: false
            },
            &amp;quot;storage-credential&amp;quot;: {
              &amp;quot;type&amp;quot;: &amp;quot;s3&amp;quot;,
              &amp;quot;credential-type&amp;quot;: &amp;quot;access-key&amp;quot;,
              &amp;quot;aws-access-key-id&amp;quot;: &amp;quot;minio-root-user&amp;quot;,
              &amp;quot;aws-secret-access-key&amp;quot;: &amp;quot;minio-root-password&amp;quot;
            }
          }' &amp;amp;&amp;amp; echo &amp;quot;Warehouse created successfully&amp;quot; || echo &amp;quot;Failed to create warehouse&amp;quot;
    networks:
      - lakekeeper-network

volumes:
  lakekeeper-postgres-data:
  lakekeeper-minio-data:
  spooling-data:
  
networks:
  lakekeeper-network:
    driver: bridge&lt;/code&gt;&lt;/pre&gt;</description>
</item>


</channel>
</rss>