<?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>Cline и Continue: два подхода к AI-ассистенту в VS Code</title>
<guid isPermaLink="false">360</guid>
<link>https://gavrilov.info/all/cline-i-continue-dva-podhoda-k-ai-assistentu-v-vs-code/</link>
<pubDate>Sun, 11 Oct 2026 20:16:12 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/cline-i-continue-dva-podhoda-k-ai-assistentu-v-vs-code/</comments>
<description>
&lt;p&gt;За последние два года AI-ассистенты для программирования перестали быть экзотикой и превратились в рабочий инструмент миллионов разработчиков. Однако выбрать подходящий — задача нетривиальная. Cline и Continue — два самых популярных open-source решения для VS Code, и хотя оба ставят перед собой схожую цель, подходы у них принципиально разные.&lt;/p&gt;
&lt;p&gt;Эта статья поможет разобраться, чем они отличаются, как настроить каждый из них и когда какой инструмент стоит использовать.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Cline: автономный агент&lt;/h3&gt;
&lt;p&gt;Cline — это автономный кодинг-агент, который живёт прямо в вашей IDE. Вы описываете задачу, а он читает файлы, вносит правки, запускает команды в терминале и повторяет цикл, пока задача не будет выполнена. Это ближайший open-source аналог Claude Code по стилю работы: агент владеет циклом выполнения, человек проверяет diff’ы.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-10-11-v-20.13.47.png" width="1084" height="916" alt="" /&gt;
&lt;/div&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; — навигация по кодовой базе, создание файлов и точечные правки. Каждое изменение показывается в виде diff’а перед применением.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Выполнение команд&lt;/b&gt; — запуск shell-команд, тестов, установка пакетов и мониторинг вывода в реальном времени.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Браузер&lt;/b&gt; — headless-браузер для тестирования веб-приложений, скриншотов и отладки ошибок.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MCP-серверы&lt;/b&gt; — расширение возможностей через подключение внешних инструментов: баз данных, API, документации.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Любая модель&lt;/b&gt; — Claude, GPT-4, Gemini, DeepSeek, локальные Ollama или любой OpenAI-совместимый endpoint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Каждое действие требует вашего подтверждения — это zero-trust архитектура, при которой код не покидает вашу машину. Cline распространяется под лицензией Apache 2.0 и недавно преодолел отметку в 5 миллионов установок.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Continue: ассистент, который остаётся в потоке&lt;/h3&gt;
&lt;p&gt;Continue — это open-source AI-ассистент, построенный вокруг чата, inline-правок и автодополнения. Здесь человек остаётся за рулём: модель помогает, но не берёт управление на себя.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/image-246.png" width="2176" height="544" alt="" /&gt;
&lt;/div&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; — inline-подсказки прямо в редакторе, работающие на низкой задержке.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Чат и inline-редактирование&lt;/b&gt; — общение с моделью в боковой панели и правки прямо в файле.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Контекстные провайдеры&lt;/b&gt; — `@codebase`, `@file`, `@terminal` для запросов с учётом кода проекта.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Slash-команды&lt;/b&gt; — кастомные команды для типовых задач: ревью кода, генерация тестов, рефакторинг.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Гибкая конфигурация&lt;/b&gt; — всё управляется через единый `config.yaml`.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Continue — это middleware между редактором и LLM: расширение не поставляет собственную модель, а маршрутизирует запросы к тому провайдеру, который вы укажете. Проект поддерживает VS Code, JetBrains и Neovim, а его кодовая база остаётся открытой.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Сравнение: что выбрать&lt;/h3&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;Cline&lt;/td&gt;
&lt;td style="text-align: center"&gt;Continue&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;Apache 2.0&lt;/td&gt;
&lt;td style="text-align: center"&gt;Open source&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;Да (inline-подсказки)&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;Plan/Act с подтверждением каждого шага&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;MCP&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;`cline_mcp_settings.json` (JSON)&lt;/td&gt;
&lt;td style="text-align: center"&gt;`config.yaml` (YAML)&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;5M+&lt;/td&gt;
&lt;td style="text-align: center"&gt;Данные не раскрыты&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Если коротко: &lt;b&gt;Cline&lt;/b&gt; лучше подходит для крупных механических задач — массовых рефакторингов, скаффолдинга, повторяющихся правок. &lt;b&gt;Continue&lt;/b&gt; — для быстрой помощи в потоке, когда вы не хотите, чтобы модель «убегала вперёд».&lt;/p&gt;
&lt;p&gt;Большинство разработчиков используют оба инструмента, переключаясь в зависимости от задачи.&lt;/p&gt;
&lt;p&gt;Стоит упомянуть и о третьем игроке — &lt;b&gt;Hermes&lt;/b&gt;. Это open-source AI-агент, который работает по протоколу Agent Client Protocol (ACP) и может быть подключён к VS Code, Zed или JetBrains через отдельное расширение ACP Client. Hermes CLI запускается как локальный подпроцесс, без облачного прокси, а расширение для VS Code стримит чат, выполняет инструменты и управляет сессиями. Главное отличие Hermes от Cline и Continue в том, что это не самостоятельное расширение с собственным интерфейсом, а агентный рантайм, который подключается к IDE через стандартизированный протокол. Подробнее о нём — в отдельной статье.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Установка и базовая настройка&lt;/h3&gt;
&lt;h4&gt;Cline&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Установка:&lt;/b&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Откройте VS Code и перейдите в Extensions (`Ctrl/Cmd + Shift + X`).&lt;/li&gt;
&lt;li&gt;Введите в поиске &lt;b&gt;Cline&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Установите расширение от `saoudrizwan` (ID: `saoudrizwan.claude-dev`).&lt;/li&gt;
&lt;li&gt;После установки нажмите на иконку Cline в Activity Bar. Если её не видно, перезапустите VS Code полностью.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;Подключение провайдера:&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Откройте настройки Cline и выберите провайдера. Можно использовать Cline Provider (pay-as-you-go), ClinePass (подписка) или подключить собственный API-ключ от Anthropic, OpenAI, OpenRouter или любого OpenAI-совместимого сервиса.&lt;/p&gt;
&lt;h4&gt;Continue&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Установка:&lt;/b&gt;&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Откройте Extensions (`Ctrl/Cmd + Shift + X`).&lt;/li&gt;
&lt;li&gt;Найдите &lt;b&gt;Continue&lt;/b&gt; и установите расширение от `Continue.dev`.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;b&gt;Настройка:&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;При первом запуске Continue предложит мастер настройки. Конфигурация хранится в файле `~/.continue/config.yaml` (macOS/Linux) или `%USERPROFILE%\.continue\config.yaml` (Windows). Открыть его можно из панели Continue: нажмите на иконку шестерёнки рядом с выпадающим списком моделей.&lt;/p&gt;
&lt;p&gt;Если у вас остался старый `config.json`, его можно мигрировать в YAML — при наличии `config.yaml` расширение загрузит именно его.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Настройка кастомной модели в Continue&lt;/h3&gt;
&lt;p&gt;Одно из главных преимуществ Continue — возможность подключить любую модель через OpenAI-совместимый API. Вот пример конфигурации для подключения модели через прокси-сервис:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;name: My Config
version: 1.0.0
schema: v1

models:
  - name: GLM 5.3 Flash via Proxy
    provider: openai
    model: z-ai/glm-5.3-flash
    apiBase: https://your-proxy-host.example.com/v1
    apiKey: ${{ secrets.PROXY_API_KEY }}
    roles:
      - chat
      - edit
      - apply
    defaultCompletionOptions:
      temperature: 0.7
      maxTokens: 4096
    capabilities:
      - tool_use&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Ключевые моменты:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`apiBase` должен заканчиваться на `/v1` — Continue автоматически добавит `/chat/completions`.&lt;/li&gt;
&lt;li&gt;`apiKey: ${{ secrets.PROXY_API_KEY }}` — ссылка на переменную из файла `.env`, который должен лежать рядом с `config.yaml`. Никогда не вписывайте ключ напрямую в конфиг.&lt;/li&gt;
&lt;li&gt;`capabilities: [tool_use]` — явно включает поддержку вызова инструментов, необходимую для работы Agent mode. Без этого кнопки Plan и Act будут неактивны.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Настройка MCP-серверов&lt;/h3&gt;
&lt;p&gt;MCP (Model Context Protocol) — это стандарт, позволяющий подключать к агенту внешние инструменты: базы данных, API, системы управления задачами. Оба инструмента поддерживают MCP, но конфигурируются по-разному.&lt;/p&gt;
&lt;h4&gt;Cline&lt;/h4&gt;
&lt;p&gt;Файл конфигурации: `cline_mcp_settings.json`. Открыть его можно через панель Cline: нажмите на иконку &lt;b&gt;MCP Servers&lt;/b&gt;, перейдите на вкладку &lt;b&gt;Configure&lt;/b&gt; и нажмите &lt;b&gt;Configure MCP Servers&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;Пример подключения локального MCP-сервера:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
  &amp;quot;mcpServers&amp;quot;: {
    &amp;quot;trino&amp;quot;: {
      &amp;quot;command&amp;quot;: &amp;quot;mcp-trino&amp;quot;,
      &amp;quot;args&amp;quot;: [],
      &amp;quot;env&amp;quot;: {
        &amp;quot;TRINO_HOST&amp;quot;: &amp;quot;192.168.3.81&amp;quot;,
        &amp;quot;TRINO_PORT&amp;quot;: &amp;quot;8080&amp;quot;,
        &amp;quot;TRINO_USER&amp;quot;: &amp;quot;trino&amp;quot;,
        &amp;quot;TRINO_PASSWORD&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;TRINO_CATALOG&amp;quot;: &amp;quot;tpcds&amp;quot;,
        &amp;quot;TRINO_SCHEMA&amp;quot;: &amp;quot;sf10&amp;quot;,
        &amp;quot;TRINO_SSL&amp;quot;: &amp;quot;false&amp;quot;,
        &amp;quot;TRINO_SCHEME&amp;quot;: &amp;quot;http&amp;quot;
      },
      &amp;quot;disabled&amp;quot;: false,
      &amp;quot;alwaysAllow&amp;quot;: []
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для удалённых серверов используйте `“type”: “streamableHttp”` и поле `“url”`.&lt;/p&gt;
&lt;h4&gt;Continue&lt;/h4&gt;
&lt;p&gt;MCP-серверы добавляются в тот же `config.yaml`, где настраиваются модели, либо в отдельные файлы в папке `.continue/mcpServers/`.&lt;/p&gt;
&lt;p&gt;Пример:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mcpServers:
  - name: Trino
    command: mcp-trino
    env:
      TRINO_HOST: &amp;quot;192.168.3.81&amp;quot;
      TRINO_PORT: &amp;quot;8080&amp;quot;
      TRINO_USER: &amp;quot;trino&amp;quot;
      TRINO_PASSWORD: &amp;quot;&amp;quot;
      TRINO_CATALOG: &amp;quot;tpcds&amp;quot;
      TRINO_SCHEMA: &amp;quot;sf10&amp;quot;
      TRINO_SSL: &amp;quot;false&amp;quot;
      TRINO_SCHEME: &amp;quot;http&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После сохранения конфигурации &lt;b&gt;полностью перезапустите VS Code&lt;/b&gt; — оба расширения подхватывают MCP-серверы только при старте.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;MCP-серверы для Trino: что выбрать&lt;/h3&gt;
&lt;p&gt;Trino — это распределённый SQL-движок для аналитических запросов к большим данным. Подключение MCP-сервера для Trino даёт AI-агенту возможность исследовать каталоги, выполнять запросы и анализировать планы выполнения прямо из чата. Для Trino существует несколько MCP-серверов, которые можно разделить на две категории: легковесные Python-серверы и высокопроизводительные Go-серверы.&lt;/p&gt;
&lt;h4&gt;Python-серверы (быстрый старт)&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;`trino-mcp`&lt;/b&gt; — самый функциональный и простой в настройке Python-сервер, который хорошо подходит для большинства задач. Поддерживает OAuth2 и Basic Auth, по умолчанию разрешает только `SELECT`-запросы (для записи нужно явно установить `ALLOW_WRITE_QUERIES=true`), автоматически добавляет комментарии в запросы с именем пользователя для аудита, а также умеет экспортировать результаты в JSON или CSV.&lt;/p&gt;
&lt;p&gt;Установка через `uvx`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;uvx trino-mcp&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Пример конфигурации для Continue:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mcpServers:
  - name: Trino
    command: uvx
    args:
      - &amp;quot;trino-mcp&amp;quot;
    env:
      TRINO_HOST: &amp;quot;your-trino-host&amp;quot;
      TRINO_PORT: &amp;quot;8080&amp;quot;
      TRINO_USER: &amp;quot;your-user&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;`alaturqua/mcp-trino-python`&lt;/b&gt; — сервер, ориентированный на работу с Trino и Iceberg. Предоставляет расширенные возможности для исследования данных и обслуживания таблиц Iceberg, поддерживает три режима транспорта (`stdio`, `streamable-http`, `sse`) и имеет готовый `Dockerfile` для быстрого развёртывания.&lt;/p&gt;
&lt;h4&gt;Go-серверы (для продакшена)&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;`txn2/mcp-trino`&lt;/b&gt; — это, пожалуй, самый мощный и функциональный MCP-сервер для Trino. Он даёт AI-ассистентам безопасный, структурированный доступ к Trino и любым Trino-совместимым движкам. По умолчанию работает в режиме только для чтения, поддерживает ограничения на количество строк и таймауты запросов, а также имеет опциональный семантический слой для отображения бизнес-описаний, владельцев и маркеров чувствительности данных из DataHub или других каталогов метаданных.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Ключевые особенности:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Выполнение SQL-запросов&lt;/b&gt; — `SELECT`, `SHOW`, `DESCRIBE` с настраиваемыми лимитами и таймаутами.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Анализ планов выполнения&lt;/b&gt; — логические, распределённые и I/O-планы запросов.&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; — запросы к нескольким серверам Trino из одной установки через `TRINO_ADDITIONAL_CONNECTIONS`.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Библиотека для Go&lt;/b&gt; — можно импортировать как Go-пакет и собрать собственный MCP-сервер с кастомной аутентификацией и аудитом.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Установка через Homebrew:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;brew install txn2/tap/mcp-trino&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Пример конфигурации для Cline:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
  &amp;quot;mcpServers&amp;quot;: {
    &amp;quot;trino&amp;quot;: {
      &amp;quot;command&amp;quot;: &amp;quot;mcp-trino&amp;quot;,
      &amp;quot;args&amp;quot;: [],
      &amp;quot;env&amp;quot;: {
        &amp;quot;TRINO_HOST&amp;quot;: &amp;quot;your-trino-host&amp;quot;,
        &amp;quot;TRINO_PORT&amp;quot;: &amp;quot;8080&amp;quot;,
        &amp;quot;TRINO_USER&amp;quot;: &amp;quot;trino&amp;quot;,
        &amp;quot;TRINO_PASSWORD&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;TRINO_CATALOG&amp;quot;: &amp;quot;hive&amp;quot;,
        &amp;quot;TRINO_SCHEMA&amp;quot;: &amp;quot;default&amp;quot;,
        &amp;quot;TRINO_SSL&amp;quot;: &amp;quot;false&amp;quot;,
        &amp;quot;TRINO_SCHEME&amp;quot;: &amp;quot;http&amp;quot;
      },
      &amp;quot;disabled&amp;quot;: false,
      &amp;quot;alwaysAllow&amp;quot;: []
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Пример конфигурации для Continue:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mcpServers:
  - name: Trino
    command: mcp-trino
    env:
      TRINO_HOST: &amp;quot;your-trino-host&amp;quot;
      TRINO_PORT: &amp;quot;8080&amp;quot;
      TRINO_USER: &amp;quot;trino&amp;quot;
      TRINO_PASSWORD: &amp;quot;&amp;quot;
      TRINO_CATALOG: &amp;quot;hive&amp;quot;
      TRINO_SCHEMA: &amp;quot;default&amp;quot;
      TRINO_SSL: &amp;quot;false&amp;quot;
      TRINO_SCHEME: &amp;quot;http&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Важные параметры `mcp-trino`:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Подключение&lt;/b&gt;: обязательные переменные — `TRINO_HOST`, `TRINO_USER`. Порт по умолчанию: `8080` для HTTP или `443` для HTTPS.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Режим только для чтения&lt;/b&gt;: включён по умолчанию. Для записи установите `TRINO_ALLOW_WRITE=true`.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SSL&lt;/b&gt;: если Trino работает по HTTP, обязательно установите `TRINO_SSL: “false”` и `TRINO_SCHEME: “http”`, иначе сервер попытается использовать HTTPS и выдаст ошибку `invalid URL escape “%2F”`.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Что выбрать?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Для быстрого старта&lt;/b&gt;: начните с `trino-mcp` (Python). Он легко устанавливается через `uvx` и имеет безопасные настройки по умолчанию.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Для работы с Iceberg&lt;/b&gt;: обратите внимание на `alaturqua/mcp-trino-python`, если ваша основная задача — исследование и обслуживание таблиц Iceberg.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Для продакшена&lt;/b&gt;: выбирайте `txn2/mcp-trino` (Go), если нужна высокая производительность, поддержка нескольких кластеров и возможность встраивания в собственные приложения.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Типичные проблемы и их решения&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Модель не появляется в Continue после добавления в конфиг.&lt;/b&gt;&lt;br /&gt;
Проверьте, что файл `.env` существует и лежит в той же папке, что `config.yaml`. Имя переменной в `.env` должно точно совпадать с именем в `${{ secrets.ИМЯ }}`.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Кнопки Plan и Act неактивны.&lt;/b&gt;&lt;br /&gt;
Добавьте `capabilities: [tool_use]` в блок модели. Без этой способности Continue блокирует агентные режимы.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Ошибка `401 status code (no body)`.&lt;/b&gt;&lt;br /&gt;
Ключ API не распознан. Проверьте, что `apiBase` заканчивается на `/v1`, ключ действителен и имеет положительный баланс.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;MCP-сервер не подключается с ошибкой `invalid URL escape “%2F”`.&lt;/b&gt;&lt;br /&gt;
Сервер пытается использовать HTTPS, хотя Trino работает по HTTP. Добавьте `TRINO_SSL: “false”` и `TRINO_SCHEME: “http”` в переменные окружения MCP-сервера.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Модель зависает после долгих раздумий в Agent mode.&lt;/b&gt;&lt;br /&gt;
Скорее всего, прокси-сервис не передаёт `tool_calls` в потоковом режиме. Проверьте это через `curl` с `“stream”: true` и имитацией вызова инструмента. Если `tool_calls` в потоке нет — попробуйте отключить стриминг через `config.json` или использовать модель с нативной поддержкой function calling.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;Cline и Continue решают разные задачи, и это их главное достоинство. Cline — это автономный агент, которому можно делегировать крупные задачи и вернуться к готовому результату. Continue — это ассистент, который помогает здесь и сейчас, не забирая контроль.&lt;/p&gt;
&lt;p&gt;Оба инструмента бесплатны, оба поддерживают MCP и работают с любой моделью. С помощью MCP-серверов, таких как `txn2/mcp-trino`, вы можете подключить к агенту Trino и работать с данными прямо из чата — без переключения между окнами и ручного написания запросов.&lt;/p&gt;
&lt;p&gt;Начните с того инструмента, который ближе к вашему стилю работы, — а когда освоитесь, попробуйте второй. Большинство разработчиков в итоге используют оба.&lt;/p&gt;
</description>
</item>

<item>
<title>Пополнение в коллекции – волк</title>
<guid isPermaLink="false">359</guid>
<link>https://gavrilov.info/all/popolnenie-v-kollekcii-volk/</link>
<pubDate>Fri, 09 Oct 2026 08:00:00 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/popolnenie-v-kollekcii-volk/</comments>
<description>
&lt;p&gt;Peakstarwave — это цифровой художник, визуальный дизайнер и автор моушн-графики, известный своими ретро и ностальгическими эстетиками (80-е и 90-е годы), работами с анимацией, коллажами и экспериментальными визуальными образами.&lt;/p&gt;
&lt;p&gt;Основные направления творчества&lt;/p&gt;
&lt;p&gt;• Моушн-дизайн и анимация: Создает визуализации и короткие анимационные ролики в программах вроде After Effects и смешанных техниках (цифровой и аналоговый подход).&lt;br /&gt;
• NFT и криптоарт: Выпускает цифровые коллекции на специализированных платформах (например, серия Animals из 100 уникальных GIF-работ на SuperRare и NFT Calendar).&lt;br /&gt;
• Графика и ретро-эстетика: Разрабатывает работы с эффектами зернистости, типографикой и ностальгическими образами (включая известные серии вроде Blue horse и графику для музыкальных релизов).&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/ezgif-38c66781bd850115.gif" width="297" height="321" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;&lt;a href="https://superrare.com/artwork/eth/0xDFf3b7A47Cf780C1D15ea1c9ba0E5c0Db771Ce70/6"&gt;https://superrare.com/artwork/eth/0xDFf3b7A47Cf780C1D15ea1c9ba0E5c0Db771Ce70/6&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
</description>
</item>

<item>
<title>Части 3: Настройка HTTPS с самоподписными сертификатами для Traefik и Marimo</title>
<guid isPermaLink="false">358</guid>
<link>https://gavrilov.info/all/chasti-3-nastroyka-https-s-samopodpisnymi-sertifikatami-dlya-tra/</link>
<pubDate>Tue, 29 Sep 2026 00:02:00 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/chasti-3-nastroyka-https-s-samopodpisnymi-sertifikatami-dlya-tra/</comments>
<description>
&lt;p&gt;В предыдущей части мы настроили балансировку нагрузки через Traefik, но весь трафик ходил по HTTP — незашифрованным. В этой части мы добавим HTTPS с помощью самоподписных сертификатов. Это хороший способ подготовить инфраструктуру к продакшену, где вы сможете заменить их на сертификаты от Let’s Encrypt буквально одной строкой.&lt;/p&gt;
&lt;p&gt;Часть 1: &lt;a href="https://gavrilov.info/all/hashicorp-nomad-razvertyvanie-interaktivnogo-python-prilozheniya/"&gt;HashiCorp Nomad: развертывание интерактивного Python-приложения на macOS&lt;/a&gt;&lt;br /&gt;
Часть 2: &lt;a href="https://gavrilov.info/all/chast-2-balansirovka-nagruzki-i-servis-diskaveri-s-consul-i-trae/"&gt;Балансировка нагрузки и сервис-дискавери с Consul и Traefik для Marimo&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Зачем нужен HTTPS&lt;/h3&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;: архитектура с двумя точками входа (HTTP и HTTPS) и автоматическим редиректом — это стандартная схема.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Доверие к сертификатам&lt;/b&gt;: локально вы можете использовать самоподписные сертификаты, а в продакшене — Let’s Encrypt, не меняя структуру конфигурации.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 1: Генерация самоподписного сертификата&lt;/h3&gt;
&lt;p&gt;Для создания сертификата используем `openssl`, который уже есть в macOS. Важный момент: обязательно указываем &lt;b&gt;Subject Alternative Name (SAN)&lt;/b&gt; с именем домена, иначе современные браузеры откажутся считать сертификат валидным.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mkdir -p ~/marimo-app/certs &amp;amp;&amp;amp; cd ~/marimo-app/certs

openssl req -x509 -nodes -days 365 \
  -newkey rsa:2048 \
  -keyout marimo.localhost.key \
  -out marimo.localhost.crt \
  -subj &amp;quot;/CN=marimo.localhost&amp;quot; \
  -addext &amp;quot;subjectAltName=DNS:marimo.localhost&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В папке `certs` появятся два файла:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`marimo.localhost.key` — приватный ключ.&lt;/li&gt;
&lt;li&gt;`marimo.localhost.crt` — сертификат.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Что произойдёт при открытии сайта&lt;/b&gt;: браузер покажет предупреждение, что сертификат не является доверенным. Это ожидаемое поведение — вы можете принять исключение для домена `marimo.localhost`. В продакшене предупреждения не будет, так как сертификаты Let’s Encrypt автоматически доверенные.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 2: Обновление конфигурации Traefik&lt;/h3&gt;
&lt;p&gt;Traefik поддерживает разделение конфигурации на &lt;b&gt;статическую&lt;/b&gt; (точки входа, провайдеры) и &lt;b&gt;динамическую&lt;/b&gt; (маршруты, сертификаты). Мы создадим обе через блоки `template` в задании Nomad.&lt;/p&gt;
&lt;h4&gt;Статическая конфигурация&lt;/h4&gt;
&lt;p&gt;Добавим точку входа `websecure` на порту 8443 и настроим автоматический редирект с HTTP на HTTPS:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;[entryPoints]
  [entryPoints.web]
    address = &amp;quot;:8080&amp;quot;
    [entryPoints.web.http.redirections.entryPoint]
      to = &amp;quot;websecure&amp;quot;
      scheme = &amp;quot;https&amp;quot;
      permanent = true

  [entryPoints.websecure]
    address = &amp;quot;:8443&amp;quot;

  [entryPoints.traefik]
    address = &amp;quot;:8081&amp;quot;

[api]
  dashboard = true
  insecure  = true

[providers.consulCatalog]
  prefix           = &amp;quot;traefik&amp;quot;
  exposedByDefault = false

  [providers.consulCatalog.endpoint]
    address = &amp;quot;127.0.0.1:8500&amp;quot;
    scheme  = &amp;quot;http&amp;quot;

[providers.file]
  filename = &amp;quot;${NOMAD_TASK_DIR}/dynamic.toml&amp;quot;
  watch    = true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Ключевые моменты:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`websecure` на порту `8443` — точка входа для HTTPS-трафика.&lt;/li&gt;
&lt;li&gt;`[entryPoints.web.http.redirections.entryPoint]` — HTTP-запросы автоматически перенаправляются на HTTPS.&lt;/li&gt;
&lt;li&gt;`[providers.file]` — подключает динамический провайдер, который читает наш файл с сертификатами.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Динамическая конфигурация&lt;/h4&gt;
&lt;p&gt;Здесь мы описываем сам сертификат:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;[tls]
  [tls.stores]
    [tls.stores.default]
      [tls.stores.default.defaultCertificate]
        certFile = &amp;quot;/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.crt&amp;quot;
        keyFile  = &amp;quot;/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.key&amp;quot;

  [[tls.certificates]]
    certFile = &amp;quot;/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.crt&amp;quot;
    keyFile  = &amp;quot;/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.key&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Обратите внимание, что здесь используются &lt;b&gt;абсолютные пути&lt;/b&gt;. Это работает, потому что мы запускаем Traefik через `raw_exec` — процесс работает прямо на хосте и имеет доступ ко всем файлам.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 3: Полное задание Traefik&lt;/h3&gt;
&lt;p&gt;Вот итоговый файл `traefik.nomad` с обеими конфигурациями:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;job &amp;quot;traefik&amp;quot; {
  region      = &amp;quot;global&amp;quot;
  datacenters = [&amp;quot;dc1&amp;quot;]
  type        = &amp;quot;service&amp;quot;

  group &amp;quot;traefik&amp;quot; {
    count = 1

    network {
      port &amp;quot;http&amp;quot; {
        static = 8080
      }
      port &amp;quot;api&amp;quot; {
        static = 8081
      }
    }

    service {
      name = &amp;quot;traefik&amp;quot;
      check {
        name     = &amp;quot;alive&amp;quot;
        type     = &amp;quot;tcp&amp;quot;
        port     = &amp;quot;http&amp;quot;
        interval = &amp;quot;10s&amp;quot;
        timeout  = &amp;quot;2s&amp;quot;
      }
    }

    task &amp;quot;traefik&amp;quot; {
      driver = &amp;quot;raw_exec&amp;quot;

      # Статическая конфигурация Traefik
      template {
        data = &amp;lt;&amp;lt;EOF
[entryPoints]
  [entryPoints.web]
    address = &amp;quot;:8080&amp;quot;
    [entryPoints.web.http.redirections.entryPoint]
      to = &amp;quot;websecure&amp;quot;
      scheme = &amp;quot;https&amp;quot;
      permanent = true

  [entryPoints.websecure]
    address = &amp;quot;:8443&amp;quot;

  [entryPoints.traefik]
    address = &amp;quot;:8081&amp;quot;

[api]
  dashboard = true
  insecure  = true

[providers.consulCatalog]
  prefix           = &amp;quot;traefik&amp;quot;
  exposedByDefault = false

  [providers.consulCatalog.endpoint]
    address = &amp;quot;127.0.0.1:8500&amp;quot;
    scheme  = &amp;quot;http&amp;quot;

[providers.file]
  filename = &amp;quot;${NOMAD_TASK_DIR}/dynamic.toml&amp;quot;
  watch    = true
EOF
        destination = &amp;quot;local/traefik.toml&amp;quot;
      }

      # Динамическая конфигурация: сертификаты по абсолютному пути
      template {
        data = &amp;lt;&amp;lt;EOF
[tls]
  [tls.stores]
    [tls.stores.default]
      [tls.stores.default.defaultCertificate]
        certFile = &amp;quot;/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.crt&amp;quot;
        keyFile  = &amp;quot;/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.key&amp;quot;

  [[tls.certificates]]
    certFile = &amp;quot;/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.crt&amp;quot;
    keyFile  = &amp;quot;/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.key&amp;quot;
EOF
        destination = &amp;quot;local/dynamic.toml&amp;quot;
      }

      config {
        command = &amp;quot;/usr/local/bin/traefik&amp;quot;
        args = [
          &amp;quot;--configfile&amp;quot;,
          &amp;quot;${NOMAD_TASK_DIR}/traefik.toml&amp;quot;
        ]
      }

      resources {
        cpu    = 100
        memory = 128
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;Шаг 4: Обновление тегов Marimo&lt;/h3&gt;
&lt;p&gt;В задании `marimo-job.nomad` нужно переключить сервис на точку входа `websecure` и включить TLS:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;service {
  name = &amp;quot;marimo-app&amp;quot;
  port = &amp;quot;http&amp;quot;
  tags = [
    &amp;quot;traefik.enable=true&amp;quot;,
    &amp;quot;traefik.http.routers.marimo.rule=Host(`marimo.localhost`)&amp;quot;,
    &amp;quot;traefik.http.routers.marimo.entrypoints=websecure&amp;quot;,
    &amp;quot;traefik.http.routers.marimo.tls=true&amp;quot;,
    &amp;quot;traefik.http.services.marimo-app.loadbalancer.sticky.cookie=true&amp;quot;,
  ]
  check {
    type     = &amp;quot;http&amp;quot;
    path     = &amp;quot;/&amp;quot;
    interval = &amp;quot;2s&amp;quot;
    timeout  = &amp;quot;2s&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Что изменилось:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`entrypoints=websecure` — трафик направляется на HTTPS-точку входа.&lt;/li&gt;
&lt;li&gt;`tls=true` — включена TLS-терминация для этого маршрута.&lt;/li&gt;
&lt;/ul&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;nomad job stop traefik
nomad job run traefik.nomad
nomad job run marimo-job.nomad&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Откройте в браузере:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;https://marimo.localhost:8443&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-28-v-23.38.21.png" width="1176" height="1114" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Браузер покажет предупреждение о недоверенном сертификате. Нажмите «Дополнительно» → «Перейти на marimo.localhost (небезопасно)» или добавьте сертификат в системный раздел доверенных сертов (в macOS это делается через «Связка ключей»).&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-28-v-23.49.01.png" width="1154" height="736" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Если вы попробуете открыть `&lt;a href="http://marimo.localhost:8080"&gt;http://marimo.localhost:8080&lt;/a&gt;`, Traefik автоматически перенаправит вас на HTTPS.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Возможные ошибки и их решения&lt;/h3&gt;
&lt;h4&gt;Ошибка: `Unclosed configuration block`&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Причина:&lt;/b&gt; незакрытые блоки `{ ... }` в HCL-файле.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Решение:&lt;/b&gt; внимательно проверьте, что все блоки `job`, `group`, `task`, `config`, `template` закрыты. В HCL отступы не важны, но фигурные скобки — критичны.&lt;/p&gt;
&lt;h4&gt;Ошибка: `Missing required argument: “command”` при `raw_exec`&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Причина:&lt;/b&gt; конфигурация скопирована из задания для `docker` (с полями `image`, `volumes`, `network_mode`), но применён драйвер `raw_exec`.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Решение:&lt;/b&gt; для `raw_exec` используйте поля `command` и `args`. Файлы конфигурации создавайте через `template`.&lt;/p&gt;
&lt;h4&gt;Ошибка: `volume_mount` не поддерживается драйвером `raw_exec`&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Причина:&lt;/b&gt; драйвер `raw_exec` работает напрямую с процессами хоста и не поддерживает монтирование томов.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Решение:&lt;/b&gt; используйте &lt;b&gt;абсолютные пути&lt;/b&gt; к файлам (как мы сделали в динамической конфигурации). Или переходите на драйвер `docker`/`podman` — там `volume_mount` работает штатно.&lt;/p&gt;
&lt;h4&gt;Ошибка: `entryPoint “websecure” doesn’t exist`&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Причина:&lt;/b&gt; имя точки входа в тегах сервиса не совпадает с именем в статической конфигурации Traefik.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Решение:&lt;/b&gt; убедитесь, что в конфиге есть блок `[entryPoints.websecure]`, а в тегах указано `traefik.http.routers.marimo.entrypoints=websecure`.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Итог&lt;/h3&gt;
&lt;p&gt;Мы добавили HTTPS в наш стек. Теперь архитектура выглядит так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Пользователь (HTTPS)
     │
     ▼
  Traefik  ──►  Marimo 1 / 2 / 3
(8443)              │
     │              ▼
     └─────────► Consul
                    ▲
                    │
             Nomad Cluster&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;b&gt;HTTP-трафик&lt;/b&gt; на порту 8080 автоматически перенаправляется на HTTPS.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTPS-трафик&lt;/b&gt; на порту 8443 расшифровывается Traefik и направляется на реплики Marimo.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Сертификаты&lt;/b&gt; хранятся на хосте и подключаются через абсолютные пути.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;В продакшене достаточно заменить пути к сертификатам и указать Traefik получать их автоматически от Let’s Encrypt — структура конфигурации останется той же. Мы разберём это в следующей части.&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-10-01-v-21.07.56.png" width="1728" height="1268" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Все это через туннель &lt;a href="https://cloudpub.ru/docs/http"&gt;https://cloudpub.ru/docs/http&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Вот так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;yuriygavrilov@MacBookPro ~ % ./clo publish https marimo.localhost:8443
Сервис опубликован: https://marimo.localhost:8443 -&amp;gt; https://thirdly-fast-bushbuck.cloudpub.ru:443&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ну и конечно нужно DNS поправить, но работает и с авторизацией&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-10-01-v-21.10.22.png" width="1360" height="966" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Но лучше так не делать, все такие весь трафик так проходит через cloudpub&lt;/p&gt;
</description>
</item>

<item>
<title>Часть 2: Балансировка нагрузки и сервис-дискавери с Consul и Traefik для Marimo</title>
<guid isPermaLink="false">357</guid>
<link>https://gavrilov.info/all/chast-2-balansirovka-nagruzki-i-servis-diskaveri-s-consul-i-trae/</link>
<pubDate>Mon, 28 Sep 2026 23:37:19 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/chast-2-balansirovka-nagruzki-i-servis-diskaveri-s-consul-i-trae/</comments>
<description>
&lt;p&gt;В первой части мы развернули приложение Marimo через Nomad, используя драйвер `raw_exec` и динамические порты. Приложение работало, но у него был существенный недостаток: каждая реплика была доступна по отдельному порту, а пользователю приходилось вручную искать актуальный адрес. Кроме того, отсутствовала отказоустойчивость — при падении одной реплики трафик не перераспределялся.&lt;/p&gt;
&lt;p&gt;Первая часть тут &lt;a href="https://gavrilov.info/all/hashicorp-nomad-razvertyvanie-interaktivnogo-python-prilozheniya/"&gt;HashiCorp Nomad: развертывание интерактивного Python-приложения на macOS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;В этой статье мы решим эти проблемы с помощью &lt;b&gt;Consul&lt;/b&gt; (сервис-дискавери) и &lt;b&gt;Traefik&lt;/b&gt; (балансировщик нагрузки). Мы также настроим &lt;b&gt;липкие сессии&lt;/b&gt; (sticky sessions), которые критически важны для stateful-приложений, таких как Marimo.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-28-v-22.55.24.png" width="444" height="160" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;Зачем нужны Consul и Traefik?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Consul&lt;/b&gt; — распределённый каталог сервисов. Он хранит информацию о работающих репликах, проверяет их здоровье и предоставляет DNS-интерфейс. Без него балансировщик не знал бы, куда направлять трафик.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Traefik&lt;/b&gt; — современный обратный прокси и балансировщик нагрузки. Он автоматически обнаруживает сервисы через Consul Catalog Provider и распределяет запросы между здоровыми репликами.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Nomad&lt;/b&gt; — оркестратор, который управляет всеми компонентами.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Итоговая архитектура:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Пользователь
     │
     ▼
  Traefik  ──►  Marimo 1 / 2 / 3
(балансировщик)      │
     │               ▼
     └──────────► Consul
                     ▲
                     │
              Nomad Cluster&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;Предварительные требования&lt;/h3&gt;
&lt;p&gt;Убедитесь, что у вас есть:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Работающий Nomad&lt;/b&gt; в режиме разработки (`nomad agent -dev -bind=0.0.0.0`).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Проект `~/marimo-app`&lt;/b&gt; с файлами `app.py`, `pyproject.toml`, `uv.lock` и виртуальным окружением.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Установленный Consul&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Установленный Traefik&lt;/b&gt; (мы будем запускать его через Nomad, но бинарник должен быть доступен).&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Установка Consul&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;brew tap hashicorp/tap
brew install hashicorp/tap/consul&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Установка Traefik&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;brew install traefik&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Проверьте пути:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;which consul   # /usr/local/bin/consul
which traefik  # /usr/local/bin/traefik&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;Шаг 1: Запуск Consul в режиме разработки&lt;/h3&gt;
&lt;p&gt;Откройте отдельный терминал и запустите Consul:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;consul agent -dev -bind=0.0.0.0 -client=0.0.0.0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Consul будет доступен по адресу `&lt;a href="http://127.0.0.1:8500"&gt;http://127.0.0.1:8500&lt;/a&gt;`. Оставьте терминал открытым.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Важно:&lt;/b&gt; Consul должен быть запущен &lt;b&gt;до&lt;/b&gt; того, как вы запустите Nomad с интеграцией с Consul, иначе сервисы не зарегистрируются.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 2: Обновление задания Marimo&lt;/h3&gt;
&lt;p&gt;Мы добавим в задание блок `service`, который зарегистрирует приложение в Consul и снабдит его тегами для Traefik. Также включим &lt;b&gt;липкие сессии&lt;/b&gt;, чтобы запросы одного пользователя всегда попадали на одну и ту же реплику.&lt;/p&gt;
&lt;p&gt;Создайте файл `marimo-job.nomad`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;job &amp;quot;marimo-app&amp;quot; {
  datacenters = [&amp;quot;dc1&amp;quot;]
  type = &amp;quot;service&amp;quot;

  group &amp;quot;app&amp;quot; {
    count = 3

    network {
      port &amp;quot;http&amp;quot; {}
    }

    service {
      name = &amp;quot;marimo-app&amp;quot;
      port = &amp;quot;http&amp;quot;
      tags = [
        &amp;quot;traefik.enable=true&amp;quot;,
        &amp;quot;traefik.http.routers.marimo.rule=Host(`marimo.localhost`)&amp;quot;,
        &amp;quot;traefik.http.routers.marimo.entrypoints=web&amp;quot;,
        &amp;quot;traefik.http.services.marimo-app.loadbalancer.sticky.cookie=true&amp;quot;,
      ]
      check {
        type     = &amp;quot;http&amp;quot;
        path     = &amp;quot;/&amp;quot;
        interval = &amp;quot;2s&amp;quot;
        timeout  = &amp;quot;2s&amp;quot;
      }
    }

    task &amp;quot;marimo&amp;quot; {
      driver = &amp;quot;raw_exec&amp;quot;

      config {
        command = &amp;quot;/Users/yuriygavrilov/marimo-app/.venv/bin/marimo&amp;quot;
        args = [
          &amp;quot;run&amp;quot;, &amp;quot;app.py&amp;quot;,
          &amp;quot;--headless&amp;quot;,
          &amp;quot;--port&amp;quot;, &amp;quot;${NOMAD_PORT_http}&amp;quot;,
          &amp;quot;--host&amp;quot;, &amp;quot;0.0.0.0&amp;quot;
        ]
        work_dir = &amp;quot;/Users/yuriygavrilov/marimo-app&amp;quot;
      }

      resources {
        cpu    = 500
        memory = 256
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Что изменилось:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`count = 3` — запускаем три реплики.&lt;/li&gt;
&lt;li&gt;Блок `service` — регистрирует сервис в Consul.&lt;/li&gt;
&lt;li&gt;Теги Traefik:
&lt;ul&gt;
  &lt;li&gt;`traefik.enable=true` — разрешает Traefik обслуживать сервис.&lt;/li&gt;
  &lt;li&gt;`traefik.http.routers.marimo.rule=Host(\`marimo.localhost\`)` — правило маршрутизации по домену.&lt;/li&gt;
  &lt;li&gt;`traefik.http.routers.marimo.entrypoints=web` — точка входа (должна совпадать с именем в конфиге Traefik).&lt;/li&gt;
  &lt;li&gt;`traefik.http.services.marimo-app.loadbalancer.sticky.cookie=true` — включает липкие сессии на основе cookie.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;b&gt;Примечание про домен `.localhost`&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;В примере используется домен `marimo.localhost`. &lt;b&gt;Добавлять его в `/etc/hosts` не нужно&lt;/b&gt; — имена, заканчивающиеся на `.localhost`, автоматически резолвятся в `127.0.0.1` согласно стандарту &lt;b&gt;RFC 6761&lt;/b&gt; (раздел 6.3) и &lt;b&gt;RFC 6762&lt;/b&gt;. Это поведение поддерживается:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;системным резолвером macOS (`mDNSResponder`),&lt;/li&gt;
&lt;li&gt;всеми современными браузерами (Chrome, Safari, Firefox),&lt;/li&gt;
&lt;li&gt;а также большинством инструментов командной строки (`curl`, `ping` и т.д.).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Именно поэтому `&lt;a href="http://marimo.localhost:8080"&gt;http://marimo.localhost:8080&lt;/a&gt;` будет работать «из коробки» без правки системных файлов.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Когда `/etc/hosts` всё-таки понадобится:&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Если вы хотите использовать другое имя — например, `marimo.dev`, `marimo.internal` или реальный домен вроде `marimo.example.com`, — тогда запись нужна:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;echo &amp;quot;127.0.0.1 marimo.dev&amp;quot; | sudo tee -a /etc/hosts&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для локальной разработки и тестирования домен `.localhost` — самый удобный вариант: он не требует никаких дополнительных настроек и работает одинаково на любой машине.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 3: Создание задания для Traefik&lt;/h3&gt;
&lt;p&gt;Traefik будет запущен через `raw_exec`. Конфигурационный файл создаётся динамически с помощью шаблона Nomad.&lt;/p&gt;
&lt;p&gt;Создайте файл `traefik.nomad`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;job &amp;quot;traefik&amp;quot; {
  region      = &amp;quot;global&amp;quot;
  datacenters = [&amp;quot;dc1&amp;quot;]
  type        = &amp;quot;service&amp;quot;

  group &amp;quot;traefik&amp;quot; {
    count = 1

    network {
      port &amp;quot;http&amp;quot; {
        static = 8080
      }
      port &amp;quot;api&amp;quot; {
        static = 8081
      }
    }

    service {
      name = &amp;quot;traefik&amp;quot;
      check {
        name     = &amp;quot;alive&amp;quot;
        type     = &amp;quot;tcp&amp;quot;
        port     = &amp;quot;http&amp;quot;
        interval = &amp;quot;10s&amp;quot;
        timeout  = &amp;quot;2s&amp;quot;
      }
    }

    task &amp;quot;traefik&amp;quot; {
      driver = &amp;quot;raw_exec&amp;quot;

      # Создаём конфигурационный файл Traefik через шаблон
      template {
        data = &amp;lt;&amp;lt;EOF
[entryPoints]
  [entryPoints.web]
    address = &amp;quot;:8080&amp;quot;
  [entryPoints.traefik]
    address = &amp;quot;:8081&amp;quot;

[api]
  dashboard = true
  insecure  = true

# Включаем Consul Catalog Provider
[providers.consulCatalog]
  prefix           = &amp;quot;traefik&amp;quot;
  exposedByDefault = false

  [providers.consulCatalog.endpoint]
    address = &amp;quot;127.0.0.1:8500&amp;quot;
    scheme  = &amp;quot;http&amp;quot;
EOF
        destination = &amp;quot;local/traefik.toml&amp;quot;
      }

      config {
        command = &amp;quot;/usr/local/bin/traefik&amp;quot;
        args = [
          &amp;quot;--configfile&amp;quot;,
          &amp;quot;${NOMAD_TASK_DIR}/traefik.toml&amp;quot;
        ]
      }

      resources {
        cpu    = 100
        memory = 128
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Ключевые моменты:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`driver = “raw_exec”` — запускаем бинарник напрямую.&lt;/li&gt;
&lt;li&gt;`template` — создаёт файл `traefik.toml` в директории задачи.&lt;/li&gt;
&lt;li&gt;`command` — полный путь к Traefik.&lt;/li&gt;
&lt;li&gt;`args` — указываем путь к созданному конфигу через переменную `${NOMAD_TASK_DIR}`.&lt;/li&gt;
&lt;li&gt;В конфиге Traefik точка входа называется `web` (порт 8080). Это имя должно совпадать с тегом `traefik.http.routers.marimo.entrypoints=web`.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 4: Запуск всех компонентов&lt;/h3&gt;
&lt;p&gt;Убедитесь, что Consul и Nomad запущены в отдельных терминалах.&lt;/p&gt;
&lt;p&gt;Затем в терминале, где вы работаете с заданиями:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;cd ~/marimo-app

# Запускаем приложение
nomad job run marimo-job.nomad

# Запускаем Traefik
nomad job run traefik.nomad&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Проверьте статус:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;nomad job status marimo-app
nomad job status traefik&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;Шаг 5: Проверка работы&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Consul&lt;/b&gt;: откройте `&lt;a href="http://127.0.0.1:8500"&gt;http://127.0.0.1:8500&lt;/a&gt;` и убедитесь, что сервис `marimo-app` зарегистрирован.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="2"&gt;
&lt;li&gt;&lt;b&gt;Traefik Dashboard&lt;/b&gt;: откройте `&lt;a href="http://127.0.0.1:8081/dashboard/"&gt;http://127.0.0.1:8081/dashboard/&lt;/a&gt;`. В разделе &lt;b&gt;HTTP Routers&lt;/b&gt; должен быть маршрут `marimo` со статусом &lt;b&gt;Enabled&lt;/b&gt;. В &lt;b&gt;HTTP Services&lt;/b&gt; — сервис `marimo-app` с тремя здоровыми репликами.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start="3"&gt;
&lt;li&gt;&lt;b&gt;Приложение&lt;/b&gt;: откройте `&lt;a href="http://marimo.localhost:8080"&gt;http://marimo.localhost:8080&lt;/a&gt;`. Вы должны увидеть интерфейс Marimo с полем ввода имени и диаграммой. Проверьте, что при нажатии кнопки данные обновляются, а ошибка `Invalid session id` не появляется.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;Возможные ошибки и их решения&lt;/h3&gt;
&lt;p&gt;В процессе настройки мы столкнулись с несколькими типичными проблемами. Вот они и способы их устранения.&lt;/p&gt;
&lt;h4&gt;Ошибка 1: `EntryPoint doesn’t exist entryPointName=web`&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Причина:&lt;/b&gt; в тегах сервиса указана точка входа `web`, а в конфигурации Traefik она называется иначе (например, `http`).&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Решение:&lt;/b&gt; приведите имена к единому виду. В нашем случае мы переименовали точку входа в конфиге Traefik в `web`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;[entryPoints]
  [entryPoints.web]
    address = &amp;quot;:8080&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И в тегах указали `traefik.http.routers.marimo.entrypoints=web`.&lt;/p&gt;
&lt;h4&gt;Ошибка 2: `Invalid session id: s_bpy8u7`&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Причина:&lt;/b&gt; отсутствие липких сессий. Marimo — stateful-приложение, оно хранит сессию на конкретной реплике. Если запросы распределяются случайно, браузер попадает на разные реплики, и сессия теряется.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Решение:&lt;/b&gt; добавьте тег `traefik.http.services.marimo-app.loadbalancer.sticky.cookie=true`. Traefik установит cookie и будет направлять все запросы от одного браузера на одну и ту же реплику.&lt;/p&gt;
&lt;h4&gt;Ошибка 3: `Missing required argument: “command”` при использовании `raw_exec`&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Причина:&lt;/b&gt; конфигурация написана для драйвера `docker` (с полями `image`, `volumes`, `network_mode`), но применён `raw_exec`.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Решение:&lt;/b&gt; для `raw_exec` используйте `command` и `args`. Файлы конфигурации создавайте через `template`. Пример для Traefik приведён выше.&lt;/p&gt;
&lt;h4&gt;Ошибка 4: `raw_exec` отключён&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Причина:&lt;/b&gt; в некоторых сборках Nomad драйвер `raw_exec` отключён по умолчанию.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Решение:&lt;/b&gt; добавьте в конфигурацию клиента Nomad (например, `/etc/nomad.d/nomad.hcl`):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;plugin &amp;quot;raw_exec&amp;quot; {
  config {
    enabled = true
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;и перезапустите агент Nomad, но в моем случае это не понадобилось.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Итог&lt;/h3&gt;
&lt;p&gt;Мы добавили к нашему приложению Marimo два важных компонента:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Consul&lt;/b&gt; — для сервис-дискавери и проверки здоровья.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Traefik&lt;/b&gt; — для балансировки нагрузки и единой точки входа.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Теперь приложение доступно по адресу `&lt;a href="http://marimo.localhost:8080"&gt;http://marimo.localhost:8080&lt;/a&gt;`, а трафик автоматически распределяется между тремя репликами. Благодаря липким сессиям пользователи не теряют состояние при работе с интерактивным интерфейсом.&lt;/p&gt;
&lt;p&gt;Этот подход является основой для построения отказоустойчивых и масштабируемых систем. В следующих статьях мы рассмотрим настройку TLS/HTTPS, использование Vault для управления секретами и автоматическое масштабирование.&lt;/p&gt;
&lt;p&gt;Поздравляю! Теперь ваше приложение готово к более серьёзным нагрузкам. 🚀&lt;/p&gt;
</description>
</item>

<item>
<title>HashiCorp Nomad: развертывание интерактивного Python-приложения на macOS</title>
<guid isPermaLink="false">356</guid>
<link>https://gavrilov.info/all/hashicorp-nomad-razvertyvanie-interaktivnogo-python-prilozheniya/</link>
<pubDate>Sun, 27 Sep 2026 00:59:39 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/hashicorp-nomad-razvertyvanie-interaktivnogo-python-prilozheniya/</comments>
<description>
&lt;p&gt;В мире оркестрации контейнеров и приложений существует множество инструментов, но одним из самых гибких и простых в освоении является &lt;b&gt;HashiCorp Nomad&lt;/b&gt;. Это оркестратор рабочих нагрузок, который позволяет развертывать как контейнеризированные, так и обычные приложения с использованием единого декларативного подхода.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-26-v-21.45.50.png" width="286" height="94" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;В этой статье мы разберем, что такое Nomad, установим его на macOS, создадим простое интерактивное приложение на Marimo с полем ввода имени и генерацией случайных данных на столбчатой диаграмме, а затем развернем его с помощью Nomad, используя драйвер `raw_exec`. В конце обсудим ограничения такого подхода и путь к настоящему продакшену.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Что такое HashiCorp Nomad?&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Nomad&lt;/b&gt; — это гибкий оркестратор рабочих нагрузок, который позволяет развертывать и управлять любыми контейнеризированными или устаревшими приложениями с помощью единого рабочего процесса.&lt;/p&gt;
&lt;h4&gt;Ключевые особенности&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Универсальность&lt;/b&gt;: Nomad может запускать Docker-контейнеры, обычные приложения (без контейнеризации), микросервисы и пакетные задачи на одной инфраструктуре.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Простота&lt;/b&gt;: Nomad распространяется как единый бинарный файл, не требует внешних зависимостей для хранения или координации и автоматически обрабатывает сбои приложений, узлов и драйверов.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Масштабируемость&lt;/b&gt;: Nomad способен масштабироваться до 10 000+ узлов и имеет встроенную поддержку GPU-нагрузок.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Гибкость&lt;/b&gt;: Поддерживает различные типы задач: сервисы, пакетные задания и системные задачи. Работает на Linux, macOS и Windows.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Установка Nomad на macOS&lt;/h3&gt;
&lt;p&gt;Для установки Nomad на macOS удобнее всего использовать Homebrew. Выполните следующие команды в терминале:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;# Добавляем репозиторий HashiCorp
brew tap hashicorp/tap

# Устанавливаем Nomad
brew install hashicorp/tap/nomad&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После установки проверьте версию:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;nomad version&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Пример вывода:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Nomad v2.0.5
BuildDate 2026-08-12T18:22:41Z
Revision 5c8612bba6eb8e44cc3fc434125cb1b13463c309&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Важно&lt;/b&gt;: На Intel Mac (x86_64) могут возникнуть проблемы с загрузкой бинарников из-за прекращения поддержки Apple. В этом случае рекомендуется использовать VPN для доступа к ресурсам HashiCorp. Если Nomad не устанавливается через Homebrew, можно собрать его из исходников или использовать MacPorts.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Запуск Nomad в режиме разработки&lt;/h3&gt;
&lt;p&gt;Для тестирования и разработки Nomad поддерживает режим `-dev`, который запускает одноузловой кластер на вашем компьютере:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;nomad agent -dev -bind=0.0.0.0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Эта команда запустит Nomad, и веб-интерфейс будет доступен по адресу `&lt;a href="http://127.0.0.1:4646"&gt;http://127.0.0.1:4646&lt;/a&gt;`. Оставьте этот терминал открытым — это ваш “сервер” Nomad.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Создание Marimo-приложения&lt;/h3&gt;
&lt;h4&gt;Установка `uv`&lt;/h4&gt;
&lt;p&gt;`uv` — это современный и быстрый менеджер пакетов для Python. Установите его:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;curl -LsSf https://astral.sh/uv/install.sh | sh&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Создание проекта&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mkdir ~/marimo-app
cd ~/marimo-app
uv init
uv add marimo altair pandas numpy&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Написание приложения&lt;/h4&gt;
&lt;p&gt;Создайте файл `app.py` со следующим содержимым. Приложение будет иметь поле для ввода имени, кнопку для генерации случайных значений и столбчатую диаграмму:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import marimo

__generated_with = &amp;quot;0.11.0&amp;quot;
app = marimo.App()


@app.cell
def _():
    import marimo as mo
    import altair as alt
    import pandas as pd
    import numpy as np
    return alt, mo, np, pd


@app.cell
def _(mo):
    # Поле для ввода имени
    name_input = mo.ui.text(
        placeholder=&amp;quot;Введите ваше имя...&amp;quot;,
        label=&amp;quot;Имя пользователя&amp;quot;
    )
    name_input
    return (name_input,)


@app.cell
def _(mo, name_input):
    # Отображение приветствия
    if name_input.value:
        mo.md(f&amp;quot;## Привет, **{name_input.value}**! 👋&amp;quot;)
    else:
        mo.md(&amp;quot;## Введите имя выше&amp;quot;)
    return


@app.cell
def _(mo):
    # Кнопка для генерации данных
    generate_button = mo.ui.button(
        label=&amp;quot;🎲 Сгенерировать данные&amp;quot;,
        value=0,
        on_click=lambda value: value + 1
    )
    generate_button
    return (generate_button,)


@app.cell
def _(alt, generate_button, mo, np, pd):
    # Генерация случайных данных при нажатии кнопки
    np.random.seed(generate_button.value)
    categories = [&amp;quot;Категория A&amp;quot;, &amp;quot;Категория B&amp;quot;, &amp;quot;Категория C&amp;quot;, &amp;quot;Категория D&amp;quot;, &amp;quot;Категория E&amp;quot;]
    values = np.random.randint(10, 100, size=5)
    
    df = pd.DataFrame({
        &amp;quot;Категория&amp;quot;: categories,
        &amp;quot;Значение&amp;quot;: values
    })
    
    chart = alt.Chart(df).mark_bar().encode(
        x=alt.X(&amp;quot;Категория&amp;quot;, sort=None),
        y=&amp;quot;Значение&amp;quot;,
        color=alt.value(&amp;quot;#4C78A8&amp;quot;)
    ).properties(
        title=&amp;quot;Случайные значения по категориям&amp;quot;,
        width=500,
        height=300
    )
    
    mo.ui.altair_chart(chart)
    return (chart,)


if __name__ == &amp;quot;__main__&amp;quot;:
    app.run()&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Проверка локально&lt;/h4&gt;
&lt;p&gt;Запустите приложение в режиме редактирования:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;uv run marimo run app.py&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Убедитесь, что приложение работает: введите имя, нажмите кнопку — диаграмма должна обновиться. Закройте сервер (`Ctrl+C`).&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Создание задания для Nomad&lt;/h3&gt;
&lt;p&gt;Создайте файл `marimo-job.nomad` в папке проекта:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;job &amp;quot;marimo-app&amp;quot; {
  datacenters = [&amp;quot;dc1&amp;quot;]
  type = &amp;quot;service&amp;quot;

  group &amp;quot;app&amp;quot; {
    count = 2

    network {
      port &amp;quot;http&amp;quot; {}
    }

    task &amp;quot;marimo&amp;quot; {
      driver = &amp;quot;raw_exec&amp;quot;

      config {
        command = &amp;quot;/Users/yuriygavrilov/marimo-app/.venv/bin/marimo&amp;quot;
        args = [
          &amp;quot;run&amp;quot;, &amp;quot;app.py&amp;quot;,
          &amp;quot;--headless&amp;quot;,
          &amp;quot;--port&amp;quot;, &amp;quot;${NOMAD_PORT_http}&amp;quot;,
          &amp;quot;--host&amp;quot;, &amp;quot;0.0.0.0&amp;quot;
        ]
        work_dir = &amp;quot;/Users/yuriygavrilov/marimo-app&amp;quot;
      }

      resources {
        cpu    = 500
        memory = 256
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Пояснения:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`driver = “raw_exec”` — запускает процесс напрямую на хосте без изоляции.&lt;/li&gt;
&lt;li&gt;`count = 2` — запускаем две реплики приложения. Nomad автоматически распределит их по узлам (в нашем случае — по одному узлу, но на разных портах).&lt;/li&gt;
&lt;li&gt;`port “http” {}` — динамический порт. Nomad резервирует случайный свободный порт для каждой аллокации. Такой подход избавляет от конфликтов портов при запуске нескольких реплик.&lt;/li&gt;
&lt;li&gt;`command` — полный путь к исполняемому файлу `marimo` в виртуальном окружении.&lt;/li&gt;
&lt;li&gt;`${NOMAD_PORT_http}` — Nomad автоматически подставляет динамически выделенный порт через переменную окружения `NOMAD_PORT_&lt;label&gt;`.&lt;/li&gt;
&lt;li&gt;`work_dir` — рабочая директория, где находится `app.py`.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Важно&lt;/b&gt;: замените `/Users/yuriygavrilov/` на реальный путь к вашей домашней директории.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Запуск задания&lt;/h3&gt;
&lt;p&gt;Убедитесь, что Nomad работает (в первом терминале). Во втором терминале выполните:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;nomad job run marimo-job.nomad&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Пример успешного вывода:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;==&amp;gt; 2026-09-26T21:26:27+03:00: Monitoring evaluation &amp;quot;cba695bd&amp;quot;
    2026-09-26T21:26:27+03:00: Allocation &amp;quot;2327286c&amp;quot; created: node &amp;quot;7f836ccd&amp;quot;, group &amp;quot;app&amp;quot;
==&amp;gt; 2026-09-26T21:26:27+03:00: Monitoring deployment &amp;quot;9ea4ff53&amp;quot;
  ✓ Deployment &amp;quot;9ea4ff53&amp;quot; successful
    
    Deployed
    Task Group  Desired  Placed  Healthy  Unhealthy  Progress Deadline
    app         1        1       1        0&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;Как узнать порт приложения&lt;/h3&gt;
&lt;p&gt;После запуска задания выполните:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;nomad job status marimo-app&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Вы увидите таблицу аллокаций с их ID. Для каждой аллокации выполните:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;nomad alloc status &amp;lt;ID_аллокации&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;В выводе найдите секцию `Allocation Addresses`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Allocation Addresses:
Label  Dynamic  Address
*http  yes      127.0.0.1:20938&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Откройте в браузере `&lt;a href="http://127.0.0.1:20938"&gt;http://127.0.0.1:20938&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-09-26-v-21.48.33.png" width="1172" height="1080" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;Ограничения текущего подхода&lt;/h3&gt;
&lt;p&gt;Мы успешно запустили приложение, но важно понимать, что &lt;b&gt;это ещё не полноценное масштабированное решение&lt;/b&gt;. Вот почему:&lt;/p&gt;
&lt;h4&gt;1. Нет балансировщика нагрузки&lt;/h4&gt;
&lt;p&gt;У нас работают &lt;b&gt;две реплики&lt;/b&gt; на разных портах (`20938`, `21001` и т.д.). Чтобы обратиться к приложению, пользователь должен знать конкретный порт каждой реплики. Это неудобно:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Нельзя дать пользователям один адрес, например `&lt;a href="http://marimo.mycompany.com"&gt;http://marimo.mycompany.com&lt;/a&gt;`.&lt;/li&gt;
&lt;li&gt;Нет автоматического распределения трафика между репликами.&lt;/li&gt;
&lt;li&gt;Нет отказоустойчивости: если одна реплика упадёт, клиент, знающий только её порт, потеряет доступ.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. Нет сервис-дискавери&lt;/h4&gt;
&lt;p&gt;Nomad не регистрирует сервисы автоматически (для этого нужен &lt;b&gt;Consul&lt;/b&gt;). Без сервис-дискавери балансировщик не сможет узнать, какие реплики сейчас работают.&lt;/p&gt;
&lt;h4&gt;3. Минимальная изоляция&lt;/h4&gt;
&lt;p&gt;Драйвер `raw_exec` запускает процессы прямо на хосте. В продакшене это небезопасно: приложение имеет доступ ко всей файловой системе и может влиять на систему.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Путь к продакшену: что нужно добавить&lt;/h3&gt;
&lt;h4&gt;1. Балансировщик нагрузки (Traefik, NGINX, HAProxy)&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Traefik&lt;/b&gt; — самый простой вариант для интеграции с Nomad. Он умеет:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Автоматически обнаруживать сервисы через Consul.&lt;/li&gt;
&lt;li&gt;Распределять трафик между всеми здоровыми репликами.&lt;/li&gt;
&lt;li&gt;Работать с TLS/HTTPS “из коробки”.&lt;/li&gt;
&lt;li&gt;Иметь удобную панель управления.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Как это выглядит в задании&lt;/b&gt;: вы добавляете специальные теги в блок `service` вашего приложения, и Traefik сам настраивает маршрутизацию:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;service {
  name = &amp;quot;marimo-app&amp;quot;
  port = &amp;quot;http&amp;quot;
  tags = [
    &amp;quot;traefik.enable=true&amp;quot;,
    &amp;quot;traefik.http.routers.marimo.rule=Host(`marimo.localhost`)&amp;quot;,
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Затем запускаете Traefik как отдельную задачу в Nomad, и он становится единой точкой входа для всех пользователей.&lt;/p&gt;
&lt;h4&gt;2. Сервис-дискавери (Consul)&lt;/h4&gt;
&lt;p&gt;&lt;b&gt;Consul&lt;/b&gt; — это распределённый каталог сервисов. Он:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Хранит информацию о том, какие реплики сейчас работают и на каких портах.&lt;/li&gt;
&lt;li&gt;Предоставляет DNS-интерфейс: ваше приложение доступно как `marimo-app.service.consul`.&lt;/li&gt;
&lt;li&gt;Позволяет балансировщику динамически обновлять список бэкендов.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. Контейнеризация (Docker/Podman)&lt;/h4&gt;
&lt;p&gt;В продакшене используйте &lt;b&gt;контейнерные драйверы&lt;/b&gt; вместо `raw_exec`:&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;h4&gt;4. Полноценный кластер Nomad&lt;/h4&gt;
&lt;p&gt;В продакшене Nomad работает не как один процесс, а как &lt;b&gt;кластер&lt;/b&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;3 серверных узла&lt;/b&gt; (минимум) — хранят состояние и принимают решения о планировании.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;N клиентских узлов&lt;/b&gt; — выполняют задачи.&lt;/li&gt;
&lt;li&gt;Интеграция с &lt;b&gt;Vault&lt;/b&gt; для управления секретами.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Итоговая архитектура для продакшена&lt;/h3&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;Пользователь
     │
     ▼
  Traefik  ──►  Marimo 1 / 2 / 3
(балансировщик)      │
                     ▼
                  Consul
                     ▲
                     │
              Nomad Cluster&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;Мы рассмотрели базовый процесс развертывания Python-приложения с помощью HashiCorp Nomad:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Установили Nomad&lt;/b&gt; через Homebrew.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Запустили Nomad&lt;/b&gt; в режиме разработки.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Создали Marimo-приложение&lt;/b&gt; с полем ввода имени и интерактивной диаграммой.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Написали задание Nomad&lt;/b&gt; с драйвером `raw_exec` и динамическим портом.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Запустили две реплики&lt;/b&gt; приложения и научились находить их порты.&lt;/li&gt;
&lt;/ol&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; (Traefik, NGINX, HAProxy), чтобы пользователи обращались к одному адресу.&lt;/li&gt;
&lt;li&gt;Внедрить &lt;b&gt;сервис-дискавери&lt;/b&gt; (Consul), чтобы балансировщик знал о работающих репликах.&lt;/li&gt;
&lt;li&gt;Использовать &lt;b&gt;контейнерные драйверы&lt;/b&gt; (Docker, Podman) для изоляции.&lt;/li&gt;
&lt;li&gt;Построить &lt;b&gt;полноценный кластер Nomad&lt;/b&gt; с тремя серверными узлами.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nomad — это мощный и гибкий инструмент, который подходит как для простых задач, так и для сложных распределённых систем. Его простота и универсальность делают его отличным выбором для разработчиков, которые хотят быстро развертывать приложения без излишней сложности Kubernetes.&lt;/p&gt;
&lt;p&gt;Следующий логичный шаг — добавить Traefik и Consul, чтобы превратить наш прототип в отказоустойчивое и масштабируемое решение. Об этом — в следующих статьях.&lt;/p&gt;
</description>
</item>

<item>
<title>Мастерская новатора: цифровое искусство Константина Худякова</title>
<guid isPermaLink="false">355</guid>
<link>https://gavrilov.info/all/masterskaya-novatora-cifrovoe-iskusstvo-konstantina-hudyakova/</link>
<pubDate>Sun, 20 Sep 2026 21:27:50 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/masterskaya-novatora-cifrovoe-iskusstvo-konstantina-hudyakova/</comments>
<enclosure url="https://gavrilov.info/video/Strekoza.mov" type="video/quicktime" length="3580525" />
<description>
&lt;p&gt;Вчера, 19 сентября 2026 года, мне посчастливилось побывать в парке «Зарядье» на выставке &lt;b&gt;«Мастерская новатора. Константин Худяков»&lt;/b&gt;. Это событие стало для меня не просто очередным культурным выходом, а настоящим погружением в мир, где технологии и искусство сплетаются в единое целое. Выставка, посвященная памяти художника-визионера и одного из основоположников цифрового искусства в России, открылась 18 сентября и продлится до 8 ноября.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/9.JPG" width="800" height="600" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Спасибо организаторам 🙏🤗🤝&lt;/p&gt;
&lt;h3&gt;🖼️ Два образа, которые не отпускают&lt;/h3&gt;
&lt;p&gt;Среди почти полусотни представленных работ меня особенно поразили два произведения, которые, на первый взгляд, совершенно разные, но в моем восприятии оказались неразрывно связаны.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;«Стрекоза»&lt;/b&gt; — это не просто изображение насекомого, а целая вселенная, застывшая в хрупком панцире. Меня восхитила филигранная точность, с которой прорисована каждая деталь: переливы крыльев, грани фасеточных глаз, тончайшие прожилки. Казалось, что это не цифровая печать, а живой организм, пойманный в момент полета. В этой работе чувствуется рука архитектора, который видит мир в структурах и формах, но при этом наполняет их невероятной эмоциональностью.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/4.JPG" width="800" height="600" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;Стерео-лайт-панель — это уникальное технологичное произведение цифрового искусства, созданное российским художником Константином Худяковым. Эта техника соединяет многоракурсную стереофотографию, компьютерную графику и внутреннюю светодиодную подсветку, создавая эффект живого объемного изображения без очков.&lt;/div&gt;
&lt;/div&gt;
&lt;div class="e2-text-video"&gt;
&lt;video src="https://gavrilov.info/video/Strekoza.mov#t=0.001" width="640" height="360" controls alt="" /&gt;

&lt;/div&gt;
&lt;p&gt;&lt;b&gt;«Глаз ангела»&lt;/b&gt; — работа, которая завораживает с первого взгляда. Это не просто глаз, а портал в иную реальность, где каждый отраженный луч, каждая частица света рассказывают свою историю. Меня поразила глубина и многослойность этого образа и детализация. Когда смотришь на него, кажется, что видишь не только саму картину, но и время, в которое она была создана, и мысли автора, вложенные в нее. Эти две работы понравились мне одинаково сильно, и я до сих пор нахожусь под их впечатлением.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/2.JPG" width="800" height="600" alt="" /&gt;
&lt;/div&gt;
&lt;h3&gt;🧬 Код цифрового искусства&lt;/h3&gt;
&lt;p&gt;Размышляя над увиденным, я осознал одну очень существенную черту цифрового искусства, которая отличает его от традиционного. С одной стороны, цифровая картина — это просто изображение, которое можно скопировать и размножить. Но с другой — в ней всегда скрыты &lt;b&gt;тысячи слоев&lt;/b&gt;, созданных ее творцом.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/6.JPG" width="600" height="800" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Каждый слой — это не просто технический элемент. Это &lt;b&gt;образ жизни художника&lt;/b&gt;, его мировоззрение, &lt;b&gt;технологии&lt;/b&gt;, которые были доступны на момент создания, и само &lt;b&gt;время&lt;/b&gt;, которое диктовало свои условия. Раскрывая эти слои один за другим, начинаешь видеть, как автор &lt;b&gt;превосходил свое время&lt;/b&gt;, заглядывая в будущее и предугадывая то, что станет доступно лишь десятилетия спустя. Худяков начал работать с цифровыми технологиями еще в конце 1990-х, когда многие даже не представляли, что компьютер может быть инструментом искусства. Его авторская техника «искусства высокого разрешения» позволяла создавать графику, которая по качеству и глубине не уступала живописи.&lt;/p&gt;
&lt;p&gt;В конце концов, все эти слои сливаются воедино, в одну работу. Это как &lt;b&gt;небольшой генетический код&lt;/b&gt; произведения. В нем одновременно есть что-то живое, пульсирующее, и частица самого автора — его душа, мастерство и интеллект.&lt;/p&gt;
&lt;p&gt;Именно это делает работы Худякова такими уникальными. Они не просто фиксируют реальность, а создают новую, наполненную смыслом и предчувствием. Работы немного заводят вперед своей детальностью, но при этом &lt;b&gt;не вызывают ощущения оторванности от реальности&lt;/b&gt;. Наоборот, они доносят до зрителя то, что происходит &lt;b&gt;здесь и сейчас&lt;/b&gt;, теми словами и образами, которые мы понимаем, находясь в текущем времени. Неживые работы обычно уводят далеко без причин, и связь с настоящим теряется. В этом уникальность и сложность цифровых произведений, а мастерство автора заключается в умении донести свой смысл и свой взгляд. Авторы, опираясь на события, доступные им, в сочетании с мастерством, стараются донести то, что видят, чувствуют или хотят сказать.&lt;/p&gt;
&lt;h3&gt;🌱 Ученики и последователи&lt;/h3&gt;
&lt;p&gt;На выставке также были представлены работы учеников и последователей Худякова. Мое внимание привлекли проекты, созданные командой студии &lt;b&gt;Synticate&lt;/b&gt;. Эта арт-группа, основанная Владиславом Ткачуком и Романом Цукановым, занимается созданием цифровых форм жизни и исследованием будущего симбиоза природы и технологий. Их концепция «экофутуризма» очень близка идеям Худякова о синтезе искусства и науки.&lt;/p&gt;
&lt;p&gt;Особенно интересной мне показалась работа из серии &lt;b&gt;«Киберорганика»&lt;/b&gt;. В этой работе чувствуется тот же подход, что и у Худякова: синтез природы и технологий, создание новой формы жизни на стыке реального и цифрового. Synticate интегрируют компьютерную графику в реальные съемки, что позволяет им рассказывать истории о будущем, не отрываясь от настоящего.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/15.JPG" width="600" height="800" alt="" /&gt;
&lt;/div&gt;
&lt;h3&gt;🤝 Коллективное творчество&lt;/h3&gt;
&lt;p&gt;Отдельно хочется сказать о том, как Худяков работал над своими масштабными проектами. Художник создавал цифровые портреты, используя фотографии реальных людей, а затем объединял их в единое многоликое произведение. Например, в проекте «Биохакинг» приняли участие 250 человек — Худяков сделал более 150 снимков лица каждого, чтобы затем создать собирательный образ. Этот подход — использование коллективного опыта и множества человеческих лиц для создания одного образа — как нельзя лучше отражает его философию: искусство создается не в вакууме, а из живого опыта многих людей и становятся их кодом.&lt;/p&gt;
&lt;h3&gt;💎 Заключение&lt;/h3&gt;
&lt;p&gt;Посещение «Мастерской новатора» стало для меня не просто знакомством с творчеством Константина Худякова, но и глубоким размышлением о природе цифрового искусства. Работы, увиденные вчера, — это не просто картинки. Это &lt;b&gt;многослойные коды&lt;/b&gt;, в которых зашифрована жизнь их создателя, его время и его взгляд в будущее.&lt;/p&gt;
&lt;p&gt;И, возможно, именно в этом и заключается главная сила цифрового искусства — в его способности быть одновременно и зеркалом настоящего, и окном в будущее, и генетическим кодом заложенным автором.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Konstantin-1.JPG" width="600" height="900" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;&lt;a href="https://discovery-ru.livejournal.com/14183.html"&gt;https://discovery-ru.livejournal.com/14183.html&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
</description>
</item>

<item>
<title>Hugr: Data Mesh-платформа и GraphQL-бэкенд на базе DuckDB</title>
<guid isPermaLink="false">354</guid>
<link>https://gavrilov.info/all/hugr-data-mesh-platforma-i-graphql-bekend-na-baze-duckdb/</link>
<pubDate>Sun, 20 Sep 2026 09:00:00 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/hugr-data-mesh-platforma-i-graphql-bekend-na-baze-duckdb/</comments>
<description>
&lt;h3&gt;1. Что такое Hugr и зачем он нужен&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;Hugr&lt;/b&gt; — это open-source платформа класса Data Mesh и высокопроизводительный GraphQL-бэкенд, построенный поверх аналитического движка DuckDB. Проект развивается командой hugr-lab под руководством Владимира Грибанова и распространяется под лицензией MIT.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Назначение&lt;/b&gt; — решить конкретную инженерную боль: данные в организации разбросаны по десяткам систем (PostgreSQL, SQL Server, S3, REST API, Kafka, Redis), и каждая команда строит собственный слой доступа. Классические ETL-пайплайны и дублирование данных в единое хранилище дают задержку актуальности, удваивают инфраструктуру и создают узкое горлышко на дата-инженерах.&lt;/p&gt;
&lt;p&gt;Hugr предлагает иную модель: &lt;b&gt;данные остаются на месте, а поверх них выстраивается единый GraphQL-слой&lt;/b&gt;, который компилирует декларативные схемы в исполняемые запросы к каждому источнику. Для аналитика — один интерфейс вместо десятков коннекторов. Для разработчика — типизированный GraphQL API без ручного CRUD. Для дата-инженера — декларативное описание схемы через SDL-директивы вместо поддержки ETL.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Ключевые характеристики:&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;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Лицензия&lt;/td&gt;
&lt;td style="text-align: center"&gt;MIT&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;DuckDB (in-process, колоночный, OLAP)&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;GraphQL (queries, mutations, subscriptions)&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;Go&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;`hugr-lab/hugr`, `hugr-lab/query-engine`&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;Docker, Kubernetes, embedded в Go-сервисы&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h3&gt;2. Архитектура: почему именно такая&lt;/h3&gt;
&lt;h4&gt;2.1. Мотивация&lt;/h4&gt;
&lt;p&gt;Традиционный подход «единое хранилище» (Data Warehouse / Lakehouse) предполагает перемещение данных в одну точку. Это создаёт три проблемы:&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; — потребители привязаны к физической структуре хранилища.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Data Mesh инвертирует модель: &lt;b&gt;каждый домен владеет своими данными и публикует их как продукт&lt;/b&gt;, а платформа обеспечивает федеративный доступ. Hugr — инфраструктура этого доступа.&lt;/p&gt;
&lt;h4&gt;2.2. Компоненты&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;┌────────────────────────────────────────────────┐
│                  Клиенты                       │
│   Web · BI · Python · MCP/AI                   │
└───────────────────────┬────────────────────────┘
                        │ GraphQL / IPC / REST
┌───────────────────────▼────────────────────────┐
│              Hugr Server (Go)                  │
│  ┌──────────┐ ┌──────────┐ ┌────────────────┐  │
│  │ GraphQL  │ │ Schema   │ │ Access Control │  │
│  │ API+Subs │ │ Compiler │ │ OAuth2 / RBAC  │  │
│  └──────────┘ └──────────┘ └────────────────┘  │
│  ┌──────────────────────────────────────────┐  │
│  │     DuckDB Analytical Engine (Go)        │  │
│  │  In-process · Колоночный · Vectorized    │  │
│  └──────────────────────────────────────────┘  │
│  ┌──────────┐ ┌──────────┐ ┌────────────────┐  │
│  │  Cache   │ │  CoreDB  │ │ IPC Protocol   │  │
│  │Mem+Redis │ │ metadata │ │  (Arrow)       │  │
│  └──────────┘ └──────────┘ └────────────────┘  │
└──┬──────────┬──────────┬───────────┬───────────┘
   │          │          │           │
┌──▼───┐  ┌───▼────┐ ┌───▼────┐ ┌────▼──────┐
│ PG / │  │SQL Srv │ │DuckLake│ │ REST API  │
│MySQL │  │Azure   │ │Iceberg │ │Arrow Fl.  │
│Parq. │  │        │ │S3/CSV  │ │ Redis KV  │
└──────┘  └────────┘ └────────┘ └───────────┘&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;DuckDB как ядро&lt;/b&gt; — in-process колоночная СУБД для OLAP. Не требует отдельного процесса и сетевого порта, работает с десятками форматов (Parquet, CSV, JSON, GeoParquet, Delta Lake), обеспечивает векторизованное исполнение. Для Hugr это нулевая сетевая задержка между движком и логикой и возможность кросс-источниковых JOIN’ов в памяти.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;CoreDB&lt;/b&gt; хранит метаданные: источники, каталоги схем, роли, политики доступа. Может быть DuckDB-файлом или PostgreSQL (обязателен для кластера).&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Компилятор схем&lt;/b&gt; преобразует GraphQL SDL с директивами (`@table`, `@view`, `@function`, `@field_references`, `@join`, `@module`) в логическую модель, из которой генерируются типы, фильтры, агрегации, мутации и sub-query поля для связей.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;3. Источники данных: единая точка входа&lt;/h3&gt;
&lt;h4&gt;3.1. Поддерживаемые типы&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;Источники&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;PostgreSQL (PostGIS, TimescaleDB, pgvector), MySQL, SQL Server / Azure SQL&lt;/td&gt;
&lt;td style="text-align: center"&gt;Pushdown фильтров, сортировки, агрегаций и JOIN’ов&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;DuckLake, Apache Iceberg (REST, Glue, S3 Tables)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Time-travel, snapshot-based DDL/DML&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;Parquet, Delta Lake, CSV, JSON, GeoParquet, Shapefiles&lt;/td&gt;
&lt;td style="text-align: center"&gt;Через DuckDB, поддержка S3 и Hive-партиционирования&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;REST API, Arrow Flight gRPC, GraphQL (в разработке)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Basic, ApiKey, OAuth2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;AI/ML&lt;/td&gt;
&lt;td style="text-align: center"&gt;Embeddings, LLM (OpenAI, Anthropic, Gemini)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Единый tool calling, streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Key-Value&lt;/td&gt;
&lt;td style="text-align: center"&gt;Redis&lt;/td&gt;
&lt;td style="text-align: center"&gt;Pub/Sub, keyspace events, TTL&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;Extension-источник, Hugr Apps&lt;/td&gt;
&lt;td style="text-align: center"&gt;Кросс-источниковые представления, кастомные функции&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h4&gt;3.2. Декларативная регистрация&lt;/h4&gt;
&lt;p&gt;Источник описывается мутацией `insert_data_sources`, после чего его схема компилируется и попадает в общее GraphQL-дерево. Адаптеры и коннекторы писать не нужно.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mutation {
  core {
    insert_data_sources(data: {
      name: &amp;quot;my_llm&amp;quot;
      type: &amp;quot;llm-openai&amp;quot;
      prefix: &amp;quot;my_llm&amp;quot;
      path: &amp;quot;http://localhost:1234/v1/chat/completions?model=gemma-4&amp;amp;timeout=120s&amp;quot;
    }) { name }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Новый источник появляется в API за одну мутацию, без пересборки и перезапуска.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;4. GraphQL как универсальный слой доступа&lt;/h3&gt;
&lt;h4&gt;4.1. Автогенерация из схемы&lt;/h4&gt;
&lt;p&gt;Для каждого табличного объекта компилятор создаёт:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;запросы с `filter`, `order_by`, `distinct_on`, `limit`, `offset`;&lt;/li&gt;
&lt;li&gt;запрос по первичному ключу (`object_name_by_pk`) и уникальным полям;&lt;/li&gt;
&lt;li&gt;агрегации (`object_name_aggregation`) и корзинные агрегации (`object_name_bucket_aggregation`);&lt;/li&gt;
&lt;li&gt;мутации `insert`, `update`, `delete` с типизированными входными структурами;&lt;/li&gt;
&lt;li&gt;фильтровые типы с операторами `eq`, `in`, `gt`, `lt` и вложенными фильтрами по связям;&lt;/li&gt;
&lt;li&gt;sub-query поля для связей (включая M2M), query-time JOIN’ы и пространственные JOIN’ы.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Дата-инженер описывает логическую модель — полный CRUD, агрегации и связи появляются автоматически.&lt;/p&gt;
&lt;h4&gt;4.2. Подписки&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Streaming LLM completions&lt;/b&gt; — токены с событиями `content_delta`, `reasoning`, `tool_use`, `finish`, `error`.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pub/Sub&lt;/b&gt; — подписка на каналы Redis.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Keyspace events&lt;/b&gt; — мониторинг изменений ключей по паттерну.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4.3. JQ-трансформации&lt;/h4&gt;
&lt;p&gt;Серверные JQ-преобразования: встроенный `jq()` в GraphQL, REST-эндпоинт `/jq-query`, доступ к переменным через `$var_name`, вложенные GraphQL-запросы из JQ через `queryHugr()`. Это постобработка без выгрузки в Python — группировка, фильтрация, реструктуризация на сервере.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;5. AI-возможности: LLM и Embeddings&lt;/h3&gt;
&lt;h4&gt;5.1. Модуль `core.models`&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;Назначение&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;`embedding` / `embeddings`&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;`completion`&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;`chat_completion`&lt;/td&gt;
&lt;td style="text-align: center"&gt;Многооборотный диалог с tool calling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;`model_sources`&lt;/td&gt;
&lt;td style="text-align: center"&gt;Список зарегистрированных моделей&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Провайдеры: &lt;b&gt;OpenAI&lt;/b&gt; (и совместимые: Ollama, LM Studio, vLLM, Mistral, Qwen, Azure OpenAI), &lt;b&gt;Anthropic&lt;/b&gt; (Claude), &lt;b&gt;Google Gemini&lt;/b&gt;. Различия форматов абстрагированы.&lt;/p&gt;
&lt;h4&gt;5.2. Tool Calling и Round-trip&lt;/h4&gt;
&lt;p&gt;Вызовы инструментов нормализованы. Для Gemini 2.5+ поддерживается `thought_signature` — обязательное поле при отправке результатов инструментов обратно модели. Пайплайн: запрос → `tool_calls` + `thought_signature` → исполнение инструмента → сообщение с `role: “tool”` → полная история в `chat_completion`.&lt;/p&gt;
&lt;h4&gt;5.3. Thinking Budget&lt;/h4&gt;
&lt;p&gt;Параметр `thinking_budget` управляет chain-of-thought: задаётся на уровне источника (максимум) и запроса (кап). Модель сначала выдаёт `reasoning`-события, затем `content_delta`.&lt;/p&gt;
&lt;h4&gt;5.4. MCP-интеграция&lt;/h4&gt;
&lt;p&gt;Поддержка Model Context Protocol позволяет AI-ассистентам (Claude, Cursor) исследовать схему, выполнять семантический поиск и генерировать/валидировать GraphQL-запросы. Основа для «vibe-аналитики»: LLM строит запросы на естественном языке, Hugr их исполняет.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;6. Hugr Apps: расширяемость через Arrow Flight&lt;/h3&gt;
&lt;h4&gt;6.1. Концепция&lt;/h4&gt;
&lt;p&gt;Hugr Apps — внешние Go-приложения, подключаемые через DuckDB Airport extension (Apache Arrow Flight gRPC). Публикуют в общую GraphQL-схему скалярные функции, таблицы, табличные функции и собственные источники данных (например, PostgreSQL с миграциями).&lt;/p&gt;
&lt;h4&gt;6.2. Жизненный цикл&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;Обнаружение&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;Graceful shutdown&lt;/td&gt;
&lt;td style="text-align: center"&gt;Немедленно&lt;/td&gt;
&lt;td style="text-align: center"&gt;N/A&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;Heartbeat (~90 сек)&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;Рестарт Hugr&lt;/td&gt;
&lt;td style="text-align: center"&gt;Startup load&lt;/td&gt;
&lt;td style="text-align: center"&gt;LoadDataSource&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;td style="text-align: center"&gt;Мгновенно (миграция)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Нет&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Heartbeat: интервал 30 сек, таймаут 10 сек, 3 неудачи → приостановка каталога. При восстановлении каталог перекомпилируется.&lt;/p&gt;
&lt;h4&gt;6.3. Практический смысл&lt;/h4&gt;
&lt;p&gt;Бизнес-логику (геокодирование, скоринг, расчёты) можно вынести в отдельный сервис и опубликовать как SQL/GraphQL-функции без изменения основного конвейера.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;7. Production-readiness и работа под нагрузкой&lt;/h3&gt;
&lt;h4&gt;7.1. Кластерный режим&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;CLUSTER_ROLE&lt;/b&gt;: `management` или `worker`.&lt;/li&gt;
&lt;li&gt;Management-нода координирует синхронизацию схем, жизненный цикл источников и конфигурацию хранилищ.&lt;/li&gt;
&lt;li&gt;Worker-ноды получают обновления через push (broadcast) и pull (polling).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PostgreSQL CoreDB обязателен&lt;/b&gt; — все ноды разделяют единую метабазу.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;7.2. Кеширование&lt;/h4&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;In-memory&lt;/b&gt; — горячие данные на каждой ноде.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Внешний кеш&lt;/b&gt; (Redis / Memcached) — разделяемый, инвалидация при мутациях.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Директива `@cache` управляет кешированием на уровне схемы.&lt;/p&gt;
&lt;h4&gt;7.3. Производительность&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Нулевая сетевая задержка между логикой и движком.&lt;/li&gt;
&lt;li&gt;Векторизованное колоночное исполнение.&lt;/li&gt;
&lt;li&gt;Поддержка десятков форматов без конвертации.&lt;/li&gt;
&lt;li&gt;Кросс-источниковые JOIN’ы в памяти.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Filter pushdown&lt;/b&gt; — условия `WHERE` передаются на сторону источника (особенно PostgreSQL).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;7.4. Безопасность&lt;/h4&gt;
&lt;p&gt;OAuth2 / OpenID Connect, field-level и row-level security, ролевая модель с предопределёнными фильтрами, автозаполнение контекста пользователя/роли в мутациях.&lt;/p&gt;
&lt;h4&gt;7.5. Развёртывание&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Docker&lt;/b&gt;: `docker run -d --name hugr -p 15000:15000 -v ./schemas:/schemas ghcr.io/hugr-lab/automigrate:latest`&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Kubernetes&lt;/b&gt;: Helm-чарты в `hugr-lab/docker` (multi-node, load balancing, кеширование).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Embedded&lt;/b&gt;: Go-пакет `hugr-lab/query-engine`.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;7.6. Ограничения&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Планируются коннекторы к SQLite (через DuckDB) и ClickHouse.&lt;/li&gt;
&lt;li&gt;GraphQL как источник данных — в разработке.&lt;/li&gt;
&lt;li&gt;«Talk-to-data» (естественный язык) — анонсирован.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Для высоких нагрузок рекомендуется кластерный режим с PostgreSQL CoreDB и Redis-кешем.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;8. Области применения и примеры&lt;/h3&gt;
&lt;h4&gt;8.1. Бэкенд данных для приложений&lt;/h4&gt;
&lt;p&gt;Единый GraphQL-слой поверх существующих БД: быстрый запуск API, централизованная схема, контроль доступа.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
  marketing {
    campaigns_aggregation_bucket {
      key { channel { name } }
      aggregations { payments { amount { sum } } }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;8.2. Data Mesh-платформа&lt;/h4&gt;
&lt;p&gt;Каждый домен публикует свою схему как модуль. Федеративный доступ через единый API, децентрализованное владение.&lt;/p&gt;
&lt;h4&gt;8.3. Аналитика и MLOps&lt;/h4&gt;
&lt;p&gt;OLAP-запросы и пространственные агрегации через GraphQL, экспорт в Arrow IPC → Python (pandas, GeoDataFrame, Jupyter), JQ-трансформации на сервере, цикл Ingestion → Processing → ML → API Access.&lt;/p&gt;
&lt;h4&gt;8.4. Агентная аналитика (Vibe Analytics)&lt;/h4&gt;
&lt;p&gt;Через MCP-эндпоинт LLM-агент исследует схему, генерирует запросы, выполняет цепочки и строит JQ-преобразования. Пользователь задаёт вопрос на естественном языке — агент возвращает результат.&lt;/p&gt;
&lt;h4&gt;8.5. Реал-тайм дашборды&lt;/h4&gt;
&lt;p&gt;Подключение Grafana/Metabase к Hugr → данные обновляются на каждый запрос, без ETL-задержек, с комбинацией нескольких источников.&lt;/p&gt;
&lt;h4&gt;8.6. Геопространственные задачи&lt;/h4&gt;
&lt;p&gt;Нативные пространственные типы, кросс-источниковые spatial JOIN’ы, поддержка GeoParquet, GeoJSON, Shapefiles, агрегации по пространственным отношениям.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;9. Итог&lt;/h3&gt;
&lt;p&gt;Hugr занимает нишу между «тяжёлыми» платформами данных (Databricks, Snowflake) и лёгкими GraphQL-обёртками над одной БД. Его ценность:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Декларативность&lt;/b&gt; — схема через SDL-директивы, полный CRUD генерируется автоматически.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Федеративность&lt;/b&gt; — данные не перемещаются, доступ на месте.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Производительность&lt;/b&gt; — DuckDB in-process даёт колоночную векторизацию без сетевых накладных расходов.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Расширяемость&lt;/b&gt; — Hugr Apps, Extension-источники, AI-модуль, Redis.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Открытость&lt;/b&gt; — MIT, Go-стек, Docker/K8s, embedded-режим.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Для команды, которая хочет дать аналитикам и приложениям единый доступ к разнородным данным без ETL-империи, Hugr — зрелый и прагматичный выбор с понятной траекторией масштабирования.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;10. Что ещё: другие инструменты и события из рассылки DuckDB (сентябрь 2026)&lt;/h3&gt;
&lt;p&gt;Ежемесячная рассылка DuckDB #45 (16 сентября 2026) принесла несколько значимых новостей.&lt;/p&gt;
&lt;h4&gt;Крупные события&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;AWS приобретает DuckLabs&lt;/b&gt; (компанию-создателя DuckDB). Проекты DuckDB, DuckLake, Quack остаются под MIT-лицензией и управлением DuckDB Foundation. Сообщество восприняло новость со сдержанным оптимизмом благодаря независимому фонду и открытой лицензии. Однако аналитики предупреждают: «разработчики не должны путать неизменность лицензии с неизменностью проекта — люди, пишущие код DuckDB, теперь получают зарплату от AWS, а зарплата влияет на дорожную карту».&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MotherDuck приобретает Tower.dev&lt;/b&gt; для интеграции рантайм-инфраструктуры в MotherDuck Flights — AI-агенты смогут выполнять задачи дата-инжиниринга и публиковать данные как API. Tower — это hosted-рантайм для Python-пайплайнов, обеспечивающий песочницу, планирование и наблюдаемость для кода, написанного LLM.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;DuckDB 2.0 preview (кодовое имя «Cyanoptera»)&lt;/h4&gt;
&lt;p&gt;Доступна preview-версия с клиент/серверным режимом через Quack и `CONNECT`, новым PEG-парсером SQL, переработанным C API для расширений, новым форматом хранения по умолчанию. Асинхронный I/O для S3, переписанный движок рекурсивных CTE и оптимизированный тип VARIANT. Релиз запланирован на октябрь 2026.&lt;/p&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;Суть&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;sql.garden&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Open-source бесконечный холст для данных на Wails (Go + Vue 3) с MCP-сервером для AI-взаимодействия&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;DuckDB Table Functions in Java&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Регистрация табличных функций на чистом Java без C++ расширений; векторизованный батч 2048 строк, pushdown предикатов&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;JetBrains Parquet/Avro/ORC Viewer&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Плагин для IDE: 1 млрд строк / 29 ГБ Parquet открывается за ~1 сек&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Bulk Load в SQL Server&lt;/b&gt; (В. Грибанов)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Параллельные писатели и явные типы (`MSSQL_NVARCHAR(200)`) ускоряют загрузку 38 млн строк с 933 до 96 сек&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;DuckLake vs Iceberg (CDC)&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;DuckLake решает проблему мелких файлов в Postgres→DuckDB CDC: 6 576 мс против 269 344 мс у Iceberg COW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;duckdb-zarr&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Rust-расширение для запросов к Zarr-хранилищам (N-мерные массивы) через SQL, поддержка S3/GCS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;DuckDB for Apache Iceberg&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Полноценный read/write lifecycle через REST-каталоги, MERGE INTO, ALTER TABLE, Iceberg V3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;SQLite vs DuckDB на $16-железе&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;Для observability-данных DuckDB отодвигает «обрыв чтения» в 100 раз дальше и даёт 4× на метриках, 15× на логах&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Эти новости подтверждают вектор: экосистема DuckDB стремительно расширяется от аналитического движка в сторону полноценной платформы данных — с клиент/серверным режимом, AI-интеграцией и корпоративными сценариями. Hugr органично встраивается в эту экосистему, используя DuckDB как вычислительное ядро и добавляя слой декларативного доступа, который превращает сырой движок в управляемую платформу данных.&lt;/p&gt;
</description>
</item>

<item>
<title>Работа — боль: как «мазохистическое лидерство» становится системной ловушкой российских компаний — и как из неё выйти</title>
<guid isPermaLink="false">353</guid>
<link>https://gavrilov.info/all/rabota-bol-kak-mazohisticheskoe-liderstvo-stanovitsya-sistemnoy/</link>
<pubDate>Wed, 16 Sep 2026 22:44:45 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/rabota-bol-kak-mazohisticheskoe-liderstvo-stanovitsya-sistemnoy/</comments>
<description>
&lt;p&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-09-16-v-22.34.25.png" width="1254" height="582" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;&lt;a href="https://www.rbc.ru/business/16/09/2026/6aa39f109a7947237af8c218"&gt;https://www.rbc.ru/business/16/09/2026/6aa39f109a7947237af8c218&lt;/a&gt;&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-09-16-v-22.35.49.png" width="710" height="304" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://sberuniversity.ru/research/management-practices/igra-na-nervakh/"&gt;можно скачать тут – Игра на нервах&lt;/a&gt; Есть еще 28 файлов.&lt;/p&gt;
&lt;p&gt;Вот тут еще небольшое введение &lt;a href="https://www.kommersant.ru/doc/8956332"&gt;https://www.kommersant.ru/doc/8956332&lt;/a&gt;&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-16-v-22.49.31.png" width="1432" height="924" alt="" /&gt;
&lt;div class="e2-text-caption"&gt;&lt;a href="https://www.kommersant.ru/doc/8956332"&gt;https://www.kommersant.ru/doc/8956332&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Исследование СберУниверситета описывает этот феномен как &lt;b&gt;мазохистическое лидерство&lt;/b&gt;. Важно подчеркнуть: речь идёт не о клиническом диагнозе и не о характеристике конкретного человека, а о &lt;b&gt;системной роли&lt;/b&gt;, которую организация нередко сама подпитывает и воспроизводит. Логика этой роли проста: если всё спокойно, значит, я недостаточно нужен. Поэтому вокруг лидера регулярно возникают авралы, срочные совещания, ночные переписки и «героические» спасательные операции. Проблема в том, что часто эти пожары создаются самой системой управления — или самим руководителем.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Масштаб явления&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;По данным исследования СберУниверситета «Игра на нервах? Деструктивные проявления в коммуникациях руководителя» (осень 2024 года, 518 респондентов), &lt;b&gt;72% сотрудников регулярно сталкиваются хотя бы с одним из девяти видов деструктивного поведения со стороны руководителя&lt;/b&gt;. Самое распространённое — отсутствие эмпатии и сочувствия: с этим систематически сталкиваются 54% респондентов, а 87% испытывают негативное воздействие из-за такого поведения — например, когда работают по ночам или в выходные. При этом 42% указывают на слабые управленческие навыки начальства, а 47% демотивирует обесценивание их работы.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Почему такие лидеры появляются&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Одна из причин — наследие индустриальной и бюрократической культуры, где ценились дисциплина, терпение и готовность «пахать». Во многих компаниях до сих пор карьерный рост получают не те, кто выстроил устойчивые процессы, а те, кто заметнее всех «тащит», «горит» и «спасает». В такой среде спокойная, предсказуемая работа воспринимается почти как безделье. Если руководитель заранее распределил задачи, убрал лишние согласования и добился результата без драматизма, его вклад может остаться незамеченным. А вот начальник, который в пятницу вечером собирает всех на экстренный созвон и в понедельник докладывает, что «мы выстояли», выглядит героем.&lt;/p&gt;
&lt;p&gt;Есть и психологический фактор. Для мазохистического лидера перегрузка становится способом самоутверждения. Он привыкает быть незаменимым, получать признание через жертву и контролировать команду через постоянное напряжение. Иногда такой руководитель искренне уверен, что иначе люди «расслабятся» и работа развалится. Как отмечают эксперты СберУниверситета, за таким поведением часто стоят неосознанные защитные механизмы: руководитель может выступать одновременно в роли «жертвы» обстоятельств и «преследователя» по отношению к подчинённым.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Как страдание становится нормой&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Опасность в том, что подобная модель быстро заражает организацию. Сотрудники начинают понимать: ценится не качество результата, а демонстрация вовлечённости. Значит, нужно отвечать ночью, приходить больным, жаловаться на перегрузку, но не отказываться от новых задач. Так формируется негласное правило: хороший работник — тот, кому тяжело.&lt;/p&gt;
&lt;p&gt;Со временем компания теряет способность отличать продуктивность от суеты. Совещаний становится больше, решений — меньше. Люди боятся брать паузу, признавать ошибки и говорить о реальных рисках. Хронический стресс снижает внимание, ухудшает качество решений и повышает текучесть кадров. По данным исследования Русской Школы Управления, к концу 2025 года &lt;b&gt;48% работающих россиян отмечали у себя симптомы эмоционального выгорания&lt;/b&gt;, а 68% говорили о постоянной усталости. Выгорание перестаёт быть личной проблемой конкретного человека и становится управленческим дефектом всей системы.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Героизм в кризисе и героизм как норма — не одно и то же&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Важно различать два принципиально разных явления. Разовый аврал — когда проект горит из-за внешних обстоятельств, форс-мажора или рыночного шока — это нормальная часть работы. Люди мобилизуются, помогают друг другу, выкладываются больше обычного. Это не патология, а адаптация.&lt;/p&gt;
&lt;p&gt;Патология начинается тогда, когда аврал становится &lt;b&gt;хроническим режимом&lt;/b&gt;. Если каждая неделя заканчивается «спасением», если дедлайны постоянно сдвигаются, если «ночной созвон» — привычная практика, а не исключение, — значит, проблема не в людях, а в системе. Разовый героизм — это ресурс. Хронический героизм — это его истощение. Компания, которая живёт в режиме постоянного подвига, на самом деле живёт в режиме постоянного управленческого сбоя.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Методика диагностики: диаграмма Исикавы&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Чтобы разорвать порочный круг, важно сначала понять, какие именно факторы подпитывают мазохистическое лидерство в конкретной организации. Здесь может помочь &lt;b&gt;диаграмма Исикавы&lt;/b&gt; («рыбья кость») — инструмент причинно-следственного анализа, который позволяет структурировать корневые причины проблемы.&lt;/p&gt;
&lt;p&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&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&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&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&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&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&gt;Недоукомплектованность, нехватка полномочий, вынуждающая «всё брать на себя»&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Практический алгоритм: соберите рабочую группу из 5–7 человек (руководители, HR, сотрудники), за 30–40 минут сформулируйте проблему в «голове рыбы», затем предложите участникам записать факторы на стикерах и распределить их по блокам. После этого выберите &lt;b&gt;три корневые причины&lt;/b&gt;, которые получают больше всего голосов, и под каждую определите одно конкретное действие с ответственным и сроком. Такой анализ позволяет увидеть, что мазохистическое лидерство — не личная патология, а &lt;b&gt;системный сбой&lt;/b&gt;, в котором сходятся культурные, управленческие и психологические факторы.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Что могут сделать компании&lt;/b&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;Текучесть кадров&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;li&gt;&lt;b&gt;eNPS&lt;/b&gt; — полезен, но с оговоркой: он измеряет лояльность, а не выгорание. Исследования показывают, что «промоутеры» могут увольняться чаще всех: парадокс «удовлетворённого увольняющегося». Поэтому eNPS стоит дополнять прямым вопросом: «Планируете ли вы остаться в компании через год?».&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Второй шаг — менять систему признания. Награждать нужно не только тех, кто «потушил пожар», но и тех, кто сделал так, чтобы он не начался. Хороший менеджмент часто незаметен: процессы работают, сотрудники понимают цели, риски обсуждаются заранее, решения принимаются без истерики. Именно это и должно считаться профессионализмом.&lt;/p&gt;
&lt;p&gt;Третий шаг — учить руководителей работать с тревогой и контролем. Многим начальникам сложно делегировать не потому, что команда слаба, а потому, что они сами не умеют выдерживать неопределённость. Здесь помогают управленческое обучение, коучинг, регулярная обратная связь и честные разговоры о стиле лидерства. Программы психологической поддержки и коучинга для руководителей снижают риск их выгорания и повышают эффективность управления.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Что делать сотрудникам&lt;/b&gt;&lt;/p&gt;
&lt;p&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; — ваши результаты принижаются или игнорируются, компетентность ставится под сомнение.&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;/p&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; — о том, как сильные стороны руководителя превращаются в критические недостатки, и о 11 наиболее распространённых деструкторах.&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; — «Игра на нервах? Деструктивные проявления в коммуникациях руководителя» и «Демотивирующее поведение руководителей» — доступны на сайте организации.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Вместо вывода&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Культура страдания держится на мифе, что боль равна эффективности. Но зрелые организации устроены иначе. В них ценится не способность бесконечно терпеть, а умение думать, договариваться, планировать и создавать устойчивые процессы.&lt;/p&gt;
&lt;p&gt;Мазохистическое лидерство может выглядеть ярким, самоотверженным и даже искренне преданным делу. Но если компания зависит от постоянных подвигов, значит, она плохо управляется. Настоящая сила руководителя — не в том, чтобы каждый день спасать всех из огня, а в том, чтобы однажды перестать его разводить. И путь к этому начинается с честного взгляда на систему: какие условия мы создаём — и какие условия мы готовы менять.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Бежит кучка ежиков, вдруг вожак орёт: «Стой!!!» — все ежи встали, снова вожак орёт: «Пастись!!!» — все ежи пасутся, а вожак про себя думает: «Хы… Ну чем не кони!»&lt;/p&gt;
</description>
</item>

<item>
<title>Помогать легко – DoBro!</title>
<guid isPermaLink="false">352</guid>
<link>https://gavrilov.info/all/pomogat-legko-dobro/</link>
<pubDate>Tue, 15 Sep 2026 23:10:22 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/pomogat-legko-dobro/</comments>
<description>
&lt;p&gt;Эту простую истину — помогать легко — я открывал для себя не раз, но этим летом она снова напомнила о себе. Особенно когда за абстрактными лозунгами стоят конкретные дела, живое общение и неожиданные встречи. Эта статья о двух событиях, которые случились со мной в течение одного месяца и отлично встряхнули городскую рутину.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/1.jpg" width="800" height="600" alt="" /&gt;
&lt;/div&gt;
&lt;h3&gt;День первый: поездка в детский хоспис&lt;/h3&gt;
&lt;p&gt;Всю ту неделю я чувствовал, что мне катастрофически не хватает физической активности. Сидячая работа, встречи, дедлайны — тело требовало движения и какой-то разрядки. Но тратить время и силы на спортзал не хотелось — хотелось чего-то нового и свежего. И тут подвернулась возможность съездить от компании в &lt;a href="https://mohd.ru"&gt;Московский областной хоспис для детей – ГБУЗ МО «МОХД»&lt;/a&gt; — помочь с благоустройством территории и по хозяйству.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/3.jpg" width="800" height="600" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Это учреждение — первое в Подмосковье и одно из первых в России, оно открылось в 2019 году при поддержке благотворительного фонда «Бумажная птица». Хоспис оказывает паллиативную помощь, и здесь важно всё: не только медицинская, но и социальная, психологическая и духовная поддержка.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Sl_sm_1.jpg" width="800" height="600" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Территория оказалась невероятно красивой. Ухоженный парк, аккуратные дорожки, здание в усадебном стиле — на фотографиях, которые я сделал, это отлично видно. Мы помогали по хозяйству приводить в порядок территорию: что-то подстригали, что-то убирали, где-то просто наводили красоту и красили. Физическая работа на свежем воздухе — именно то, что было нужно. Приехал без задних лап.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="600" data-ratio="0.75"&gt;
&lt;img src="https://gavrilov.info/pictures/4-1.jpg" width="600" height="800" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/9.jpg" width="600" height="800" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/10.jpg" width="600" height="800" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/4.jpg" width="800" height="600" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/5.jpg" width="600" height="800" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/6.jpg" width="800" height="600" alt="" /&gt;
&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/7.jpg" width="800" height="600" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Кстати, если вы тоже хотите найти подобные мероприятия, очень удобно пользоваться платформой &lt;b&gt;&lt;a href="https://dobro.ru"&gt;dobro.ru&lt;/a&gt;&lt;/b&gt; (DoBro — «Просто Делай Бро», звучит! :) ) — там собраны тысячи волонтёрских событий: от помощи приютам до экологических акций. Заполняете профиль, выбираете то, что цепляет, подаёте заявку — и вперёд.&lt;/p&gt;
&lt;h3&gt;День второй: ярмарка RWB Участие в офисе&lt;/h3&gt;
&lt;p&gt;А сегодня в офисе прошла благотворительная ярмарка &lt;b&gt;&lt;a href="https://esg.rwb.ru"&gt;RWB Участие&lt;/a&gt;&lt;/b&gt;. Это уже не первая такая ярмарка. Два раза утром я проходил мимо, а на третий раз выкроилось время — заглянул и не прогадал. Все три раза, кстати, коллеги толпились вокруг и разглядывали все.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/11.jpg" width="400" height="534" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;На первой ярмарке я приобрёл стакан для очков «Самое время жить» — маме он очень нравится, пользуется до сих пор. От сегодняшнего визита я не ждал чего-то особенного, но хотелось чего-то необычного, как и в прошлый раз.&lt;/p&gt;
&lt;p&gt;И не прогадал. Во-первых, случайно встретил &lt;b&gt;Татьяну Владимировну Ким&lt;/b&gt; — основателя Wildberries, главу РВБ. Встреча была неожиданной, но очень тёплой. Ну и посчастливилось сделать фото на память.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/13.jpg" width="800" height="600" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Во-вторых, купил книгу. И да, я знал, что книги есть на Wildberries, но не думал, что их можно купить прямо на ярмарке в офисе. Бумажные книги приятнее покупать физически — прикоснуться к бумаге, полистать. Теперь у меня есть экземпляр с автографом — но его я вам не покажу, это личное :).&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="800" data-ratio="1.3333333333333"&gt;
&lt;img src="https://gavrilov.info/pictures/15.jpg" width="800" height="600" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/book.webp" width="900" height="1200" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;О какой книге речь? О &lt;a href="https://books.wildberries.ru/catalog/paper/775833141"&gt;сборнике историй партнёров Wildberries&lt;/a&gt;. В предисловии Татьяна Ким пишет:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;«Эта книга не о брендах, а о людях, которые изменили свою жизнь раз и навсегда… Дело не в удаче, а в подходе и отношении, в ваших усилиях и желании сделать самое лучшее. Можно сказать, что эта книга о мечте — большой, искренней, исполнимой!»&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Пока я ещё не читал эту книгу, но верю, что это действительно так. За каждым успехом стоит не просто удача, а годы труда, ошибок и веры в своё дело.&lt;/p&gt;
&lt;p&gt;Вторая книга — &lt;b&gt;&lt;a href="https://books.wildberries.ru/catalog/book/M0RUsI4RpB"&gt;«Я — Сания»&lt;/a&gt;&lt;/b&gt; Дианы Машковой и Сании Испергеновой. Это история сироты, написанная в соавторстве с известной писательницей. Книга вышла в рамках проекта «Библиотека благотворительного фонда “Арифметика добра”». Тяжёлая, но важная история. Записал в план на чтение и в закладки.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/12.jpg" width="800" height="1067" alt="" /&gt;
&lt;/div&gt;
&lt;h4&gt;Почему это важно&lt;/h4&gt;
&lt;p&gt;Всё это — и поездка в хоспис, и ярмарка — для меня не просто «мероприятия». Это напоминание о том, что &lt;b&gt;помогать легко&lt;/b&gt;. Можно найти мероприятие на dobro.ru и поехать волонтёрить. Можно зайти на ярмарку в офисе и купить что-то у фонда. Можно просто перевести деньги через платформу &lt;b&gt;RWB Участие&lt;/b&gt; — там собраны верифицированные НКО, и за каждый благотворительный перевод начисляется кешбэк 3% ягодками.&lt;/p&gt;
&lt;p&gt;Лето закончилось, но добрые дела не имеют сезонности. Я точно знаю, что буду повторять этот опыт — и в хосписе, и на ярмарках. Потому что после таких дней действительно лучше спишь. И это не только про физическую усталость. Это про ощущение, что день прожит не зря. Кстати, ещё не скоро, но зимой организуется «&lt;a href="https://gavrilov.info/all/pozdravlyaem-detey-na-novy-god-yolka-zhelaniy/"&gt;Ёлка желаний&lt;/a&gt;» — тоже очень интересное благотворительное мероприятие, где можно подарить подарки детям и сделать реальностью чью-то мечту.&lt;/p&gt;
</description>
</item>

<item>
<title>Trino + OTP верификация запросов в bi – параноя.mode = true</title>
<guid isPermaLink="false">351</guid>
<link>https://gavrilov.info/all/trino-otp-verifikaciya-zaprosov-v-bi-paranoya-mode-true/</link>
<pubDate>Fri, 11 Sep 2026 01:17:04 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/trino-otp-verifikaciya-zaprosov-v-bi-paranoya-mode-true/</comments>
<description>
&lt;p&gt;Представьте: дашборд в BI видят все сотрудники. Закрыть его нельзя — так исторически сложилось. Но данные нужно скрыть. Решение — динамическая фильтрация на уровне SQL: пользователь вводит логин и одноразовый код (TOTP), а запрос показывает только те строки, которые ему разрешены.&lt;/p&gt;
&lt;p&gt;Это не полноценная система безопасности, но это работающий барьер, который можно внедрить без изменения BI-системы. В статье — пошаговая инструкция: от демо-таблиц до настройки прав в Trino, генерации QR-кодов и увеличения окна действия OTP.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Архитектура&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;`catalog.schema.dashboard_users`&lt;/b&gt; — справочник сотрудников: логин, имя, отдел, должность, руководитель, TOTP-секрет, флаг активности.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`catalog.schema.dashboard_data`&lt;/b&gt; — данные дашборда. У каждой строки есть политика доступа: `ALL`, `OWNER`, `DEPARTMENT`, `MANAGER`, `DIRECTOR`.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Функция `verify_totp`&lt;/b&gt; — Python UDF, проверяет OTP-код по секрету и текущему времени.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Итоговый запрос&lt;/b&gt; — аутентифицирует пользователя и фильтрует строки по атрибутам.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Пользователь вводит логин и OTP как параметры BI. Если они верны — получает свои данные. Если нет — пустой результат.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 1. Демо-таблицы&lt;/h3&gt;
&lt;p&gt;Создадим две таблицы в вымышленной схеме `catalog.schema`.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;CREATE TABLE catalog.schema.dashboard_users (
    login         VARCHAR,
    employee_name VARCHAR,
    department    VARCHAR,
    position_name VARCHAR,
    manager_login VARCHAR,
    totp_secret   VARCHAR,
    is_active     BOOLEAN
);

INSERT INTO catalog.schema.dashboard_users VALUES
    ('vasya', 'Василий', 'sales',    'manager',  'ivan',  'JBSWY3DPEHPK3PXP', true),
    ('petya', 'Пётр',    'sales',    'analyst',  'vasya', 'KRSXG5CTMVRXEZLU', true),
    ('masha', 'Мария',   'finance',  'analyst',  'ivan',  'NB2W45DFOIZA',     true),
    ('ivan',  'Иван',    'management','director', null,   'MFRGGZDFMZTWQ2LK', true);

CREATE TABLE catalog.schema.dashboard_data (
    row_id        BIGINT,
    row_title     VARCHAR,
    amount        DECIMAL(12,2),
    owner_login   VARCHAR,
    department    VARCHAR,
    manager_login VARCHAR,
    access_policy VARCHAR
);

INSERT INTO catalog.schema.dashboard_data VALUES
    (1, 'Личная строка Васи',       1000.00, 'vasya', 'sales',   'ivan',  'OWNER'),
    (2, 'Личная строка Пети',       2000.00, 'petya', 'sales',   'vasya', 'OWNER'),
    (3, 'Общая строка отдела Sales',3000.00, null,    'sales',   null,    'DEPARTMENT'),
    (4, 'Общая строка Finance',     4000.00, null,    'finance', null,    'DEPARTMENT'),
    (5, 'Строка команды Васи',      5000.00, 'petya', 'sales',   'vasya', 'MANAGER'),
    (6, 'Строка команды Ивана',     6000.00, 'masha', 'finance', 'ivan',  'MANAGER'),
    (7, 'Директорская строка',      7000.00, null,    null,      null,    'DIRECTOR'),
    (8, 'Публичная строка',         8000.00, null,    null,      null,    'ALL');&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;p&gt;Либо можно сгенерировать коды разово прямо в запросе.&lt;/p&gt;
&lt;h3&gt;Шаг 1.1. Массовая генерация TOTP-секретов и QR-кодов&lt;/h3&gt;
&lt;p&gt;Когда пользователей много, вручную придумывать Base32-секреты неудобно. Можно сгенерировать их прямо в Trino при вставке в таблицу `dashboard_users`. Ниже — готовый блок для демо и тестов.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Важно:&lt;/b&gt; `random()` в Trino не является криптостойким генератором. Для продакшена секреты лучше генерировать вне Trino (например, через `openssl rand -base64 20` или Python `pyotp.random_base32()`) и вставлять уже готовыми. Для демонстрации и параноидального режима «лишь бы закрыть дашборд» этого достаточно.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Вставка пользователей с автоматической генерацией секретов&lt;/h4&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;INSERT INTO catalog.schema.dashboard_users (
    login,
    employee_name,
    department,
    position_name,
    manager_login,
    totp_secret,
    is_active
)
SELECT
    login,
    employee_name,
    department,
    position_name,
    manager_login,

    -- Генерация Base32-секрета длиной 16 символов (80 бит)
    array_join(
        transform(
            sequence(1, 16),
            i -&amp;gt; substr(
                'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
                CAST(floor(random() * 32) + 1 AS INTEGER),
                1
            )
        ),
        ''
    ) AS totp_secret,

    true AS is_active
FROM (
    VALUES
        ('vasya', 'Василий', 'sales',     'manager',  'ivan'),
        ('petya', 'Пётр',    'sales',     'analyst',  'vasya'),
        ('masha', 'Мария',   'finance',   'analyst',  'ivan'),
        ('ivan',  'Иван',    'management','director', CAST(NULL AS VARCHAR))
) AS src (
    login,
    employee_name,
    department,
    position_name,
    manager_login
);&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Как получить строку для QR-кода&lt;/h4&gt;
&lt;p&gt;После вставки можно сразу сформировать URI формата `otpauth://`, который останется только превратить в QR-код любым генератором:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;SELECT
    login,
    totp_secret,
    'otpauth://totp/DemoBI:' || login
        || '?secret=' || totp_secret
        || '&amp;amp;issuer=DemoBI' AS otpauth_uri
FROM catalog.schema.dashboard_users;&lt;/code&gt;&lt;/pre&gt;&lt;p&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;login&lt;/td&gt;
&lt;td style="text-align: center"&gt;totp_secret&lt;/td&gt;
&lt;td style="text-align: center"&gt;otpauth_uri&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;vasya&lt;/td&gt;
&lt;td style="text-align: center"&gt;JBSWY3DPEHPK3PXP&lt;/td&gt;
&lt;td style="text-align: center"&gt;otpauth://totp/DemoBI:vasya?secret=JBSWY3DPEHPK3PXP&amp;issuer=DemoBI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;petya&lt;/td&gt;
&lt;td style="text-align: center"&gt;KRSXG5CTMVRXEZLU&lt;/td&gt;
&lt;td style="text-align: center"&gt;otpauth://totp/DemoBI:petya?secret=KRSXG5CTMVRXEZLU&amp;issuer=DemoBI&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Эту строку можно вставить в любой генератор QR-кодов (онлайн или офлайн), а затем отсканировать приложением:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google Authenticator,&lt;/li&gt;
&lt;li&gt;Yandex ID,&lt;/li&gt;
&lt;li&gt;KeePass (с плагином TOTP),&lt;/li&gt;
&lt;li&gt;и любым другим менеджером паролей, поддерживающим TOTP.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Настройка длины секрета&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;`sequence(1, 16)` — 16 символов Base32 = 80 бит. Этого достаточно для большинства случаев и именно такую длину часто используют по умолчанию.&lt;/li&gt;
&lt;li&gt;Если хотите более длинный секрет, замените `16` на `32`. Тогда получится 160 бит.&lt;/li&gt;
&lt;li&gt;Алфавит `’ABCDEFGHIJKLMNOPQRSTUVWXYZ234567’` — это стандартный Base32-алфавит (без цифр 0, 1, 8, 9). Менять его не нужно.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Если нужен детерминированный секрет (не рекомендуется)&lt;/h4&gt;
&lt;p&gt;Иногда для тестов хотят, чтобы секрет зависел от логина. Можно использовать хеш:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;substr(
    upper(to_base32(from_utf8(login || 'some_salt'))),
    1, 16
) AS totp_secret&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Но это &lt;b&gt;небезопасно&lt;/b&gt;: зная логин и соль, можно вычислить секрет. Для реальной эксплуатации используйте случайную генерацию вне Trino.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 2. Inline-функция для тестов в DBeaver&lt;/h3&gt;
&lt;p&gt;В DBeaver можно использовать `WITH FUNCTION` — Python-код прямо в запросе. Это удобно для отладки, но &lt;b&gt;не работает в BI&lt;/b&gt;, потому что BI схлопывает запрос в одну строку и ломает форматирование Python.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;WITH FUNCTION verify_totp(secret VARCHAR, code VARCHAR, time_step BIGINT)
RETURNS BOOLEAN
LANGUAGE PYTHON
WITH (handler = 'verify_handler')
AS $$
import hmac, hashlib, struct, base64

def verify_handler(secret_b32, code, time_step):
    if secret_b32 is None or code is None or time_step is None:
        return False

    code = str(code).strip()
    if not code.isdigit() or len(code) != 6:
        return False

    secret = str(secret_b32).strip().replace(&amp;quot; &amp;quot;, &amp;quot;&amp;quot;).upper()
    padding = &amp;quot;=&amp;quot; * ((8 - len(secret) % 8) % 8)

    try:
        key = base64.b32decode(secret + padding, casefold=True)
    except Exception:
        return False

    current = int(time_step // 30)   # 30 секунд — стандарт TOTP
    for offset in (-1, 0, 1):
        counter = current + offset
        msg = struct.pack(&amp;quot;&amp;gt;Q&amp;quot;, counter)
        digest = hmac.new(key, msg, hashlib.sha1).digest()
        o = digest[19] &amp;amp; 15
        expected = (struct.unpack(&amp;quot;&amp;gt;I&amp;quot;, digest[o:o+4])[0] &amp;amp; 0x7fffffff) % 1000000
        expected_code = f&amp;quot;{expected:06d}&amp;quot;
        if hmac.compare_digest(expected_code, code):
            return True
    return False
$$
SELECT verify_totp('JBSWY3DPEHPK3PXP', '123456', CAST(to_unixtime(now()) AS BIGINT));&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Важно:&lt;/b&gt; тело функции должно начинаться с новой строки после `$$`. Если BI отправляет всё в одну строку — будет ошибка `Function definition must start with a newline after opening quotes`.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Screenshot-from-2026-09-11-00-28-13.png" width="1024" height="691" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 3. Регистрация функции администратором&lt;/h3&gt;
&lt;p&gt;Чтобы BI-запросы не содержали Python-код, функцию нужно создать один раз на стороне Trino. Это делает администратор или пользователь с правами на создание функций.&lt;/p&gt;
&lt;p&gt;Сразу еще скажу, что регистрация не во всех каталогах работает, в iceberg например не работает, а вот в iceberg + rest надо проверять. Такая фича давно в плане, но не уверен готова ли. Можно регать функции в каталоге memory конектора, но это придется делать каждую загрузку.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;DROP FUNCTION IF EXISTS catalog.schema.verify_totp(VARCHAR, VARCHAR, BIGINT);

CREATE FUNCTION catalog.schema.verify_totp(
    secret VARCHAR,
    code VARCHAR,
    time_step BIGINT
)
RETURNS BOOLEAN
LANGUAGE PYTHON
WITH (handler = 'verify_handler')
AS $$
import hmac, hashlib, struct, base64

def verify_handler(secret_b32, code, time_step):
    if secret_b32 is None or code is None or time_step is None:
        return False

    code = str(code).strip()
    if not code.isdigit() or len(code) != 6:
        return False

    secret = str(secret_b32).strip().replace(&amp;quot; &amp;quot;, &amp;quot;&amp;quot;).upper()
    padding = &amp;quot;=&amp;quot; * ((8 - len(secret) % 8) % 8)

    try:
        key = base64.b32decode(secret + padding, casefold=True)
    except Exception:
        return False

    current = int(time_step // 30)
    for offset in (-1, 0, 1):
        counter = current + offset
        msg = struct.pack(&amp;quot;&amp;gt;Q&amp;quot;, counter)
        digest = hmac.new(key, msg, hashlib.sha1).digest()
        o = digest[19] &amp;amp; 15
        expected = (struct.unpack(&amp;quot;&amp;gt;I&amp;quot;, digest[o:o+4])[0] &amp;amp; 0x7fffffff) % 1000000
        expected_code = f&amp;quot;{expected:06d}&amp;quot;
        if hmac.compare_digest(expected_code, code):
            return True
    return False
$$;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Проверка:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;SELECT catalog.schema.verify_totp(
    'JBSWY3DPEHPK3PXP',
    '465617',
    CAST(to_unixtime(now()) AS BIGINT)
) AS is_valid;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Примечание:&lt;/b&gt; в вашей среде создание функции может быть не разрешено без настройки прав. Об этом — следующий шаг.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 4. Настройка прав доступа&lt;/h3&gt;
&lt;p&gt;Нужно разрешить группе &lt;b&gt;admins&lt;/b&gt; создавать функции в схеме `catalog.schema`, а группе &lt;b&gt;bi_users&lt;/b&gt; — только вызывать `verify_totp`. В Trino это делается через system access control.&lt;/p&gt;
&lt;h4&gt;Вариант A. File-based ACL&lt;/h4&gt;
&lt;p&gt;Файл `/etc/trino/access-control.properties`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;access-control.name=file
security.config-file=/etc/trino/rules/access-control.json
security.refresh-period=1m&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Файл `access-control.json`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
  &amp;quot;catalogs&amp;quot;: [
    {
      &amp;quot;group&amp;quot;: &amp;quot;admins&amp;quot;,
      &amp;quot;catalog&amp;quot;: &amp;quot;catalog&amp;quot;,
      &amp;quot;allow&amp;quot;: &amp;quot;all&amp;quot;
    },
    {
      &amp;quot;group&amp;quot;: &amp;quot;bi_users&amp;quot;,
      &amp;quot;catalog&amp;quot;: &amp;quot;catalog&amp;quot;,
      &amp;quot;allow&amp;quot;: &amp;quot;read-only&amp;quot;
    }
  ],
  &amp;quot;schemas&amp;quot;: [
    {
      &amp;quot;group&amp;quot;: &amp;quot;admins&amp;quot;,
      &amp;quot;catalog&amp;quot;: &amp;quot;catalog&amp;quot;,
      &amp;quot;schema&amp;quot;: &amp;quot;schema&amp;quot;,
      &amp;quot;owner&amp;quot;: true
    },
    {
      &amp;quot;group&amp;quot;: &amp;quot;bi_users&amp;quot;,
      &amp;quot;catalog&amp;quot;: &amp;quot;catalog&amp;quot;,
      &amp;quot;schema&amp;quot;: &amp;quot;schema&amp;quot;,
      &amp;quot;owner&amp;quot;: false
    }
  ],
  &amp;quot;functions&amp;quot;: [
    {
      &amp;quot;group&amp;quot;: &amp;quot;admins&amp;quot;,
      &amp;quot;catalog&amp;quot;: &amp;quot;catalog&amp;quot;,
      &amp;quot;schema&amp;quot;: &amp;quot;schema&amp;quot;,
      &amp;quot;function&amp;quot;: &amp;quot;.*&amp;quot;,
      &amp;quot;privileges&amp;quot;: [&amp;quot;EXECUTE&amp;quot;, &amp;quot;GRANT_EXECUTE&amp;quot;, &amp;quot;OWNERSHIP&amp;quot;]
    },
    {
      &amp;quot;group&amp;quot;: &amp;quot;bi_users&amp;quot;,
      &amp;quot;catalog&amp;quot;: &amp;quot;catalog&amp;quot;,
      &amp;quot;schema&amp;quot;: &amp;quot;schema&amp;quot;,
      &amp;quot;function&amp;quot;: &amp;quot;verify_totp&amp;quot;,
      &amp;quot;privileges&amp;quot;: [&amp;quot;EXECUTE&amp;quot;]
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Вариант B. OPA (Rego)&lt;/h4&gt;
&lt;p&gt;Если Trino использует OPA, политика может выглядеть так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;package trino

default allow := false

is_admin {
    input.context.identity.groups[_] == &amp;quot;admins&amp;quot;
}

is_bi_user {
    input.context.identity.groups[_] == &amp;quot;bi_users&amp;quot;
}

is_target_schema {
    input.action.resource.catalog.name == &amp;quot;catalog&amp;quot;
    input.action.resource.schema.name == &amp;quot;schema&amp;quot;
}

is_verify_totp {
    input.action.resource.catalog.name == &amp;quot;catalog&amp;quot;
    input.action.resource.schema.name == &amp;quot;schema&amp;quot;
    input.action.resource.function.name == &amp;quot;verify_totp&amp;quot;
}

allow {
    is_admin
    is_target_schema
    input.action.operation == &amp;quot;CreateFunction&amp;quot;
}

allow {
    is_admin
    is_target_schema
    input.action.operation == &amp;quot;DropFunction&amp;quot;
}

allow {
    is_admin
    is_target_schema
    input.action.operation == &amp;quot;ExecuteFunction&amp;quot;
}

allow {
    is_bi_user
    is_verify_totp
    input.action.operation == &amp;quot;ExecuteFunction&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Точные названия операций (`CreateFunction`, `ExecuteFunction`) лучше уточнить в логах OPA при тестовом запросе.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 5. Запрос для BI (без Python)&lt;/h3&gt;
&lt;p&gt;После регистрации функции BI-запрос становится обычным SQL. Пользователь вводит `:login` и `:otp` как параметры.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;WITH auth_user AS (
    SELECT
        login,
        employee_name,
        department,
        position_name,
        manager_login
    FROM catalog.schema.dashboard_users
    WHERE login = lower(trim(':login'))
      AND is_active = true
      AND catalog.schema.verify_totp(
            totp_secret,
            trim(':otp'),
            CAST(to_unixtime(now()) AS BIGINT)
          )
    LIMIT 1
)
SELECT
    d.row_id,
    d.row_title,
    d.amount,
    d.owner_login,
    d.department,
    d.manager_login,
    d.access_policy,
    a.login AS current_login,
    a.employee_name AS current_employee_name,
    a.department AS current_department,
    a.position_name AS current_position_name,
    CASE
        WHEN d.access_policy = 'ALL' THEN 'Видно всем авторизованным'
        WHEN d.access_policy = 'OWNER' AND d.owner_login = a.login THEN 'Видно владельцу строки'
        WHEN d.access_policy = 'DEPARTMENT' AND d.department = a.department THEN 'Видно сотруднику отдела'
        WHEN d.access_policy = 'MANAGER' AND d.manager_login = a.login THEN 'Видно руководителю'
        WHEN d.access_policy = 'DIRECTOR' AND a.position_name = 'director' THEN 'Видно директору'
        ELSE null
    END AS access_reason
FROM catalog.schema.dashboard_data d
CROSS JOIN auth_user a
WHERE
       d.access_policy = 'ALL'
    OR (d.access_policy = 'OWNER' AND d.owner_login = a.login)
    OR (d.access_policy = 'DEPARTMENT' AND d.department = a.department)
    OR (d.access_policy = 'MANAGER' AND d.manager_login = a.login)
    OR (d.access_policy = 'DIRECTOR' AND a.position_name = 'director');&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;Важно про параметры:&lt;/b&gt; если BI подставляет их как сырой текст, нужны кавычки: `’:login’`, `’:otp’`. Если BI использует bind-параметры — кавычки не нужны: `:login`, `:otp`. В предыдущих ошибках (`Column ‘jbswy3dpehpk3pxp’ cannot be resolved`) видно, что в вашем случае нужны именно кавычки.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 6. Логика доступа&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;`auth_user`&lt;/b&gt; — возвращает 0 строк, если логин не найден, пользователь неактивен или OTP неверный.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`CROSS JOIN auth_user`&lt;/b&gt; — если `auth_user` пуст, результат всего запроса пуст.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;`WHERE` с `OR`&lt;/b&gt; — оставляет только те строки, которые разрешены политикой:
&lt;ul&gt;
  &lt;li&gt;`ALL` — всем авторизованным;&lt;/li&gt;
  &lt;li&gt;`OWNER` — владельцу (`owner_login = a.login`);&lt;/li&gt;
  &lt;li&gt;`DEPARTMENT` — сотруднику того же отдела;&lt;/li&gt;
  &lt;li&gt;`MANAGER` — руководителю (`manager_login = a.login`);&lt;/li&gt;
  &lt;li&gt;`DIRECTOR` — директору (`position_name = ‘director’`).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Пример: `vasya` увидит свои строки, строки отдела `sales`, строки, где он руководитель, и публичные. `petya` — только свои, отдел `sales` и публичные.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 7. Генерация QR-кода для OTP&lt;/h3&gt;
&lt;p&gt;Каждому пользователю нужно выдать TOTP-секрет и удобно передать его в приложение-аутентификатор. Стандартный способ — использовать URI формата `otpauth://`.&lt;/p&gt;
&lt;p&gt;Пример для пользователя `vasya`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;otpauth://totp/DemoBI:vasya?secret=JBSWY3DPEHPK3PXP&amp;amp;issuer=DemoBI&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Здесь:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;`DemoBI:vasya` — метка, которая отобразится в приложении. Можно заменить на название отчёта, отдела или что угодно, например `SalesDashboard:vasya`.&lt;/li&gt;
&lt;li&gt;`secret=JBSWY3DPEHPK3PXP` — Base32-секрет.&lt;/li&gt;
&lt;li&gt;`issuer=DemoBI` — имя издателя, обычно название компании или системы.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Screenshot-from-2026-09-11-00-31-21.png" width="596" height="538" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Эту строку можно вставить в любой генератор QR-кодов (онлайн или офлайн). После генерации QR-код сканируется приложением:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google Authenticator,&lt;/li&gt;
&lt;li&gt;Yandex ID,&lt;/li&gt;
&lt;li&gt;KeePass (с плагином TOTP),&lt;/li&gt;
&lt;li&gt;и любым другим менеджером паролей, поддерживающим TOTP.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Это стандарт, поэтому совместимость широкая. QR-код можно распечатать или отправить пользователю.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Совет:&lt;/b&gt; если у вас много пользователей, сгенерируйте QR-коды автоматически из таблицы `dashboard_users`, подставляя логин и секрет.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 8. Увеличение окна действия OTP (2–5 минут)&lt;/h3&gt;
&lt;p&gt;По умолчанию TOTP-код действует 30 секунд. Это может быть неудобно: пользователь не успевает ввести код. Можно увеличить окно действия, изменив функцию.&lt;/p&gt;
&lt;p&gt;В Python-коде есть строка:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;current = int(time_step // 30)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Здесь `30` — это количество секунд в одном временном шаге. Если заменить `30` на `300`, то код будет действителен 5 минут (300 секунд). При этом приложение-аутентификатор продолжит генерировать коды каждые 30 секунд, но сервер будет принимать любой код, сгенерированный в течение этих 5 минут. Это удобно и безопасно: окно не бесконечное, но достаточное, чтобы успеть ввести код.&lt;/p&gt;
&lt;p&gt;Пример изменённой функции:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;current = int(time_step // 300)   # 5 минут
for offset in (-1, 0, 1):
    ...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если оставить смещения `-1, 0, 1`, то общее окно станет 15 минут (5 минут назад, текущие 5 минут, 5 минут вперёд). Это может быть избыточно. Лучше убрать смещения и проверять только текущий интервал:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;current = int(time_step // 300)
counter = current
msg = struct.pack(&amp;quot;&amp;gt;Q&amp;quot;, counter)
...
# без цикла for&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Тогда код будет действителен ровно 5 минут.&lt;/p&gt;
&lt;p&gt;Если хотите 2 минуты — используйте `120` вместо `300`.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Важно:&lt;/b&gt; после изменения функции её нужно пересоздать ( `DROP FUNCTION` и `CREATE FUNCTION` ). Все пользователи автоматически начнут работать с новым окном.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;Шаг 9. Безопасность&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;&lt;b&gt;Секреты TOTP&lt;/b&gt; нельзя хранить в открытой схеме. Лучше вынести `dashboard_users` в закрытую схему, например `security.users`.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;BI-пользователи&lt;/b&gt; не должны иметь прямой доступ к таблице с секретами. Только к функции и к данным.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OTP-код&lt;/b&gt; попадает в логи Trino и BI. Логи могут быть доступны администраторам или храниться в системе. Это не смертельно, потому что код короткоживущий, но помните: любой, кто видит лог, может увидеть введённый OTP. Если окно действия увеличено до 5 минут, риск возрастает. По возможности используйте реального пользователя из аутентификации BI, а не ручной ввод логина.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Права на создание функций&lt;/b&gt; — только у группы `admins`. BI-пользователям — только `EXECUTE` на конкретную функцию.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;Заключение&lt;/h3&gt;
&lt;p&gt;Мы построили параноидальный, но рабочий механизм: OTP-аутентификация + динамическая фильтрация строк на уровне SQL. Это не заменяет полноценный RBAC, но позволяет быстро закрыть дашборд от посторонних глаз без изменения BI-системы.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Что проверено:&lt;/b&gt; inline-функция в DBeaver, запрос с фильтрацией, логика доступа.&lt;br /&gt;
&lt;b&gt;Что требует настройки:&lt;/b&gt; регистрация функции и права в Trino. После настройки прав (шаг 4) функция создаётся один раз, а BI-запросы становятся чистыми и безопасными.&lt;/p&gt;
&lt;p&gt;Удачи в паранойе! :)&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;div class="fotorama" data-width="416" data-ratio="1.0833333333333"&gt;
&lt;img src="https://gavrilov.info/pictures/Screenshot-from-2026-09-11-00-33-10.png" width="416" height="384" alt="" /&gt;
&lt;img src="https://gavrilov.info/pictures/Screenshot-from-2026-09-11-01-21-57.png" width="362" height="424" alt="" /&gt;
&lt;/div&gt;
&lt;/div&gt;
</description>
</item>

<item>
<title>Современный семантический слой на dbt + MetricFlow + DuckDB: макросы вместо пакетов</title>
<guid isPermaLink="false">350</guid>
<link>https://gavrilov.info/all/sovremenny-semanticheskiy-sloy-na-dbt-metricflow-duckdb-makrosy/</link>
<pubDate>Wed, 09 Sep 2026 22:52:12 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/sovremenny-semanticheskiy-sloy-na-dbt-metricflow-duckdb-makrosy/</comments>
<description>
&lt;p&gt;В этой статье мы построим полностью рабочий семантический слой для аналитики на данных Superstore.csv (от Tableau Desktop, можно скачать его где-то), используя &lt;b&gt;dbt&lt;/b&gt;, &lt;b&gt;MetricFlow&lt;/b&gt; и &lt;b&gt;DuckDB&lt;/b&gt;. Вместо устаревшего пакета `dbt_metrics` (который несовместим с dbt 1.12+), мы применим &lt;b&gt;современный подход&lt;/b&gt; — вынесем логику метрик в &lt;b&gt;макросы dbt&lt;/b&gt;. Это позволяет централизованно управлять расчётами, автоматически обновлять витрины при изменении метрик и использовать семантический слой для ad-hoc-запросов через `mf`. Все шаги проверены на последних версиях инструментов и готовы к использованию в реальных проектах.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/image-245.png" width="2088" height="456" alt="" /&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h3&gt;1. Зачем нужен семантический слой и почему макросы — это современный подход?&lt;/h3&gt;
&lt;p&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;Автоматическая генерация SQL&lt;/b&gt; — не нужно писать `GROUP BY`, `JOIN` и агрегации вручную для ad-hoc-запросов.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Согласованность&lt;/b&gt; — аналитики и BI-инструменты используют одни и те же метрики.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ранее для использования метрик в моделях применялся пакет `dbt_metrics`, но он &lt;b&gt;несовместим с dbt 1.12 и выше&lt;/b&gt; (требует версию &lt;1.6.0). Современный подход — **выносить выражения метрик в макросы dbt**. Это:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Не требует внешних пакетов.&lt;/li&gt;
&lt;li&gt;Работает с любой версией dbt.&lt;/li&gt;
&lt;li&gt;Даёт полный контроль над SQL.&lt;/li&gt;
&lt;li&gt;Легко поддерживается и версионируется.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Мы будем использовать:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;dbt&lt;/b&gt; 1.12.4 — для управления моделями и витринами.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MetricFlow&lt;/b&gt; (встроенный в dbt) — для семантического слоя и ad-hoc-запросов через `mf`.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DuckDB&lt;/b&gt; 1.5.5 — лёгкая БД для разработки.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Макросы dbt&lt;/b&gt; — для переиспользования логики метрик в витринах.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;2. Установка инструментов и создание проекта&lt;/h3&gt;
&lt;p&gt;Используем `uv` — быстрый менеджер пакетов Python.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mkdir dbtflow_duck
cd dbtflow_duck

uv init
uv venv
source .venv/bin/activate

uv pip install dbt-duckdb dbt-metricflow pandas duckdb&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Проверяем версии (на момент написания):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dbt-core: 1.12.4&lt;/li&gt;
&lt;li&gt;duckdb: 1.5.5&lt;/li&gt;
&lt;li&gt;metricflow: 0.212.0&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;3. Загрузка данных Superstore&lt;/h3&gt;
&lt;p&gt;Скачайте `Superstore.csv` (например, с Kaggle) и поместите в корень проекта.&lt;/p&gt;
&lt;p&gt;Создайте `load_data.py`:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;import pandas as pd
import duckdb

df = pd.read_csv('Superstore.csv', sep='\t', encoding='latin1')
df.columns = [col.lower().replace(' ', '_').replace('/', '_').replace('-', '_') for col in df.columns]

for col in ['sales', 'discount', 'profit']:
    if col in df.columns:
        df[col] = df[col].astype(str).str.replace(',', '.').astype(float)

df['order_date'] = pd.to_datetime(df['order_date'], dayfirst=True)
df['ship_date'] = pd.to_datetime(df['ship_date'], dayfirst=True)
df['postal_code'] = df['postal_code'].astype(str)

db_path = 'dbtflow_duck.duckdb'
con = duckdb.connect(db_path)

con.execute(&amp;quot;DROP TABLE IF EXISTS superstore&amp;quot;)
con.execute(&amp;quot;&amp;quot;&amp;quot;
CREATE TABLE superstore (
    row_id INTEGER, order_id VARCHAR, order_date DATE, ship_date DATE,
    ship_mode VARCHAR, customer_id VARCHAR, customer_name VARCHAR, segment VARCHAR,
    country_region VARCHAR, city VARCHAR, state VARCHAR, postal_code VARCHAR,
    region VARCHAR, product_id VARCHAR, category VARCHAR, sub_category VARCHAR,
    product_name VARCHAR, sales DOUBLE, quantity INTEGER, discount DOUBLE, profit DOUBLE
)
&amp;quot;&amp;quot;&amp;quot;)

con.execute(&amp;quot;INSERT INTO superstore SELECT * FROM df&amp;quot;)
print(f&amp;quot;✅ Загружено {len(df)} строк в {db_path}&amp;quot;)
con.close()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Запуск:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;python load_data.py&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;4. Инициализация dbt-проекта вручную&lt;/h3&gt;
&lt;p&gt;Создаём папку проекта и файлы конфигурации (без `dbt init`, чтобы избежать проблем):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mkdir dbt_project
cd dbt_project&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;dbt_project.yml&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;name: 'dbt_project'
version: '1.0.0'
profile: 'dbt_project'

model-paths: [&amp;quot;models&amp;quot;]
analysis-paths: [&amp;quot;analyses&amp;quot;]
test-paths: [&amp;quot;tests&amp;quot;]
seed-paths: [&amp;quot;seeds&amp;quot;]
macro-paths: [&amp;quot;macros&amp;quot;]
snapshot-paths: [&amp;quot;snapshots&amp;quot;]

clean-targets:
  - &amp;quot;target&amp;quot;
  - &amp;quot;dbt_packages&amp;quot;

models:
  dbt_project:
    +materialized: table&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;profiles.yml&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;dbt_project:
  target: dev
  outputs:
    dev:
      type: duckdb
      path: ../dbtflow_duck.duckdb
      schema: main
      threads: 4&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Создаём структуру папок:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mkdir -p models analyses tests seeds macros snapshots
export DBT_PROFILES_DIR=$(pwd)
dbt debug   # должно быть All checks passed!&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;5. Модели данных&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;models/sources.yml&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;version: 2

sources:
  - name: default
    schema: main
    tables:
      - name: superstore&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;models/superstore_clean.sql&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{{ config(materialized='table') }}

SELECT
    row_id,
    order_id,
    order_date,
    ship_date,
    ship_mode,
    customer_id,
    customer_name,
    segment,
    country_region,
    city,
    state,
    postal_code,
    region,
    product_id,
    category,
    sub_category,
    product_name,
    sales,
    quantity,
    discount,
    profit
FROM {{ source('default', 'superstore') }}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;models/time_spine.sql&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{{ config(materialized='table') }}

SELECT 
    CAST(generate_series AS DATE) AS date_day
FROM generate_series(DATE '2020-01-01', DATE '2026-12-31', INTERVAL '1' DAY)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;models/metricflow_time_spine.yml&lt;/b&gt; (обязательно для MetricFlow):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;models:
  - name: time_spine
    description: &amp;quot;A time spine with one row per day for MetricFlow.&amp;quot;
    time_spine:
      standard_granularity_column: date_day
    columns:
      - name: date_day
        granularity: day&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;6. Семантическая модель и метрики&lt;/h3&gt;
&lt;p&gt;&lt;b&gt;models/superstore.yml&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;semantic_models:
  - name: superstore
    model: ref('superstore_clean')
    description: &amp;quot;Superstore sales data&amp;quot;
    defaults:
      agg_time_dimension: order_date
    
    entities:
      - name: order_id
        type: primary
        
    dimensions:
      - name: order_date
        type: time
        type_params:
          time_granularity: day
      - name: category
        type: categorical
      - name: region
        type: categorical
    
    measures:
      - name: total_sales
        agg: sum
        expr: sales
      - name: total_profit
        agg: sum
        expr: profit
      - name: total_discount
        agg: average          # Внимание: не &amp;quot;avg&amp;quot;, а &amp;quot;average&amp;quot;!
        expr: discount

metrics:
  - name: total_sales
    label: Total Sales
    type: simple
    type_params:
      measure: total_sales
  - name: total_profit
    label: Total Profit
    type: simple
    type_params:
      measure: total_profit
  - name: avg_discount
    label: Average Discount
    type: simple
    type_params:
      measure: total_discount
  - name: profit_margin
    label: Profit Margin
    type: derived
    type_params:
      expr: total_profit / total_sales
      metrics:
        - total_profit
        - total_sales&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Выполняем сборку и генерацию артефактов:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;dbt run
dbt parse&lt;/code&gt;&lt;/pre&gt;&lt;hr /&gt;
&lt;h3&gt;7. Ad-hoc-запросы через MetricFlow CLI (`mf`)&lt;/h3&gt;
&lt;p&gt;Проверяем конфигурацию:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mf validate-configs&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Список метрик:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mf list metrics&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Вывод:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;• avg_discount: metric_time, order_id__category, order_id__order_date, order_id__region
• profit_margin: metric_time, order_id__category, order_id__order_date, order_id__region
• total_profit: metric_time, order_id__category, order_id__order_date, order_id__region
• total_sales: metric_time, order_id__category, order_id__order_date, order_id__region&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Примеры запросов:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;mf query --metrics total_sales --group-by order_id__category
mf query --metrics profit_margin --group-by order_id__region
mf query --metrics avg_discount --group-by order_id__category&lt;/code&gt;&lt;/pre&gt;&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/Snimok-ekrana-2026-09-09-v-22.44.49.png" width="1638" height="630" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;MetricFlow генерирует SQL автоматически — мы не пишем ни строчки кода для этих запросов.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;8. Современный подход: макросы для переиспользования метрик в витринах&lt;/h3&gt;
&lt;p&gt;Вместо устаревшего пакета `dbt_metrics` мы создадим &lt;b&gt;макросы&lt;/b&gt;, которые возвращают SQL-выражение для каждой метрики. Это позволяет:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Централизованно управлять логикой расчёта.&lt;/li&gt;
&lt;li&gt;Использовать одну и ту же логику во всех витринах.&lt;/li&gt;
&lt;li&gt;Легко изменять метрику в одном месте.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;macros/get_metric_expr.sql&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{% macro get_total_sales_expr() %}
    SUM(sales)
{% endmacro %}

{% macro get_total_profit_expr() %}
    SUM(profit)
{% endmacro %}

{% macro get_avg_discount_expr() %}
    AVG(discount)
{% endmacro %}

{% macro get_profit_margin_expr() %}
    {{ get_total_profit_expr() }} / NULLIF({{ get_total_sales_expr() }}, 0)
{% endmacro %}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь мы можем строить витрины, используя эти макросы.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;models/sales_by_category.sql&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{{ config(materialized='table') }}

SELECT
    category,
    {{ get_total_sales_expr() }} AS total_sales
FROM {{ ref('superstore_clean') }}
GROUP BY category&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;models/profit_by_region.sql&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{{ config(materialized='table') }}

SELECT
    region,
    {{ get_total_profit_expr() }} AS total_profit,
    {{ get_profit_margin_expr() }} AS profit_margin
FROM {{ ref('superstore_clean') }}
GROUP BY region&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;b&gt;models/avg_discount_by_category.sql&lt;/b&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{{ config(materialized='table') }}

SELECT
    category,
    {{ get_avg_discount_expr() }} AS avg_discount
FROM {{ ref('superstore_clean') }}
GROUP BY category&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После добавления новых моделей выполняем `dbt run` — все витрины создаются с актуальной логикой метрик.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;9. Преимущества подхода с макросами&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Совместимость&lt;/b&gt; — работает с любой версией dbt, без внешних пакетов.&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; — метрики определены как в YAML (для `mf`), так и в макросах (для витрин).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Автоматизация&lt;/b&gt; — изменения в макросах автоматически обновляют все витрины при следующем `dbt run`.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;10. Расширенный пример: добавление фильтра в метрику&lt;/h3&gt;
&lt;p&gt;Предположим, нам нужна выручка только для категории “Technology”. Мы можем создать отдельный макрос:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{% macro get_tech_sales_expr() %}
    SUM(CASE WHEN category = 'Technology' THEN sales ELSE 0 END)
{% endmacro %}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;И использовать его в витрине:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{{ config(materialized='table') }}

SELECT
    region,
    {{ get_tech_sales_expr() }} AS tech_sales
FROM {{ ref('superstore_clean') }}
GROUP BY region&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Это демонстрирует, насколько легко расширять систему без изменения основной семантической модели.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;11. Автоматизация пайплайнов&lt;/h3&gt;
&lt;p&gt;Настройте регулярный запуск `dbt run` в CI/CD или Airflow. При каждом запуске:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Обновляются сырые данные (если они загружаются заново).&lt;/li&gt;
&lt;li&gt;Пересчитываются модели `superstore_clean` и `time_spine`.&lt;/li&gt;
&lt;li&gt;Пересчитываются все витрины с актуальной логикой метрик из макросов.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Таким образом, ваши отчёты всегда актуальны, а бизнес-логика централизована.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;12. Заключение&lt;/h3&gt;
&lt;p&gt;Мы построили современный семантический слой на стеке dbt + MetricFlow + DuckDB, используя макросы для переиспользования логики метрик в витринах. Этот подход:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Не требует устаревших пакетов.&lt;/li&gt;
&lt;li&gt;Совместим с последними версиями dbt.&lt;/li&gt;
&lt;li&gt;Даёт полный контроль над SQL.&lt;/li&gt;
&lt;li&gt;Легко масштабируется и поддерживается.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Все команды проверены и работают на практике. Вы можете адаптировать этот проект под свои данные и метрики, получая все преимущества семантического слоя без лишних зависимостей.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Полезные ссылки:&lt;/b&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.getdbt.com/docs/use-dbt-semantic-layer/dbt-sl"&gt;dbt Semantic Layer docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://duckdb.org/docs/guides/dbt"&gt;DuckDB + dbt &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.astral.sh/uv/"&gt;uv package manager &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
</description>
</item>

<item>
<title>Huawei Mate XT 2 и Huawei Pura X View</title>
<guid isPermaLink="false">349</guid>
<link>https://gavrilov.info/all/huawei-mate-xt-2-i-huawei-pura-x-view/</link>
<pubDate>Mon, 07 Sep 2026 22:48:56 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/huawei-mate-xt-2-i-huawei-pura-x-view/</comments>
<description>
&lt;p&gt;Huawei скоро весь рынок переформатирует :) точнее форм-фрагментирует потом фиг загонят всех обратно в прямоугольники. Ждем телефон для кружочкаф :))&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/image-243.png" width="1338" height="785" alt="" /&gt;
&lt;/div&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/image-244.png" width="1920" height="1080" alt="" /&gt;
&lt;/div&gt;
</description>
</item>

<item>
<title>Apache Airflow или Temporal: выбор между оркестрацией данных и бизнес-логикой</title>
<guid isPermaLink="false">348</guid>
<link>https://gavrilov.info/all/apache-airflow-ili-temporal-vybor-mezhdu-orkestraciey-dannyh-i-b/</link>
<pubDate>Mon, 07 Sep 2026 22:11:01 +0300</pubDate>
<author></author>
<comments>https://gavrilov.info/all/apache-airflow-ili-temporal-vybor-mezhdu-orkestraciey-dannyh-i-b/</comments>
<description>
&lt;p&gt;В мире разработки и инженерии данных инструменты &lt;b&gt;Apache Airflow&lt;/b&gt; и &lt;b&gt;Temporal&lt;/b&gt; часто могут восприниматься как конкуренты, поскольку оба решают задачу управления многошаговыми процессами. Однако на практике их философия, архитектура и сценарии использования настолько разные, что сравнивать их корректно можно лишь с четким пониманием контекста задачи.&lt;/p&gt;
&lt;p&gt;В этой статье мы разберем, для чего предназначен каждый инструмент, в чем их сильные и слабые стороны, и поможем вам определиться с выбором.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;1. В чем суть каждого инструмента?&lt;/h3&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/image-242.png" width="28" height="28" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;Apache Airflow&lt;/b&gt; — это классический оркестратор batch-процессов. Он создавался как решение для управления ETL/ELT-пайплайнами, регулярной загрузки данных, построения отчетов и ML-циклов. Центральная концепция Airflow — &lt;b&gt;DAG&lt;/b&gt; (направленный ациклический граф), который описывает зависимости между задачами и обычно запускается по расписанию (например, ежедневно в 6 утра).&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://gavrilov.info/pictures/image-241.png" width="28" height="28" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;&lt;b&gt;Temporal&lt;/b&gt; — это платформа для надежного исполнения распределенных бизнес-процессов. Она не просто «запускает задачи», а управляет долгоживущими workflow с сохранением состояния, автоматическими повторами, обработкой сбоев и возможностью ждать внешние события (ответ пользователя, платеж, подтверждение от другой системы). Workflow в Temporal пишутся на обычном коде и выполняются детерминированно.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;2. Главное различие&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Airflow&lt;/b&gt; — это продвинутый планировщик для задач по расписанию в мире данных.&lt;br /&gt;
&lt;b&gt;Temporal&lt;/b&gt; — это движок надежного исполнения бизнес-логики в распределенных системах, который «помнит» состояние процесса даже через неделю.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;3. Сравнительная таблица&lt;/h3&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;Apache Airflow&lt;/td&gt;
&lt;td style="text-align: center"&gt;Temporal&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;Оркестрация batch-задач и data pipeline’ов&lt;/td&gt;
&lt;td style="text-align: center"&gt;Надежное выполнение бизнес-процессов и workflow&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;ETL, загрузка данных, отчеты, ML-обучение, backfill&lt;/td&gt;
&lt;td style="text-align: center"&gt;Оформление заказов, платежи, onboarding, Saga-паттерны, фоновые операции&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;DAG из задач (Task)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Workflow + Activities&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;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;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;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;td style="text-align: center"&gt;Встроенная, полная история выполнения&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Повторные попытки (retries)&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;/td&gt;
&lt;td style="text-align: center"&gt;Хорошая для batch-задач&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;Python (DAG-файлы)&lt;/td&gt;
&lt;td style="text-align: center"&gt;Go, Java, TypeScript, Python и другие&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;UI для DAG, логи, статусы задач&lt;/td&gt;
&lt;td style="text-align: center"&gt;UI с историей событий, ретраями и состоянием workflow&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;Относительно низкая для data-команд&lt;/td&gt;
&lt;td style="text-align: center"&gt;Выше (требует проектирования workflow и инфраструктуры)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Идеально подходит для Data Engineering&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;/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;/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;/td&gt;
&lt;td style="text-align: center"&gt;Data Engineers, ML Engineers, аналитики&lt;/td&gt;
&lt;td style="text-align: center"&gt;Backend Engineers, Platform Engineers, команды микросервисов&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h3&gt;4. Сложности и подводные камни&lt;/h3&gt;
&lt;h4&gt;4.1. Когда Airflow становится тяжелым&lt;/h4&gt;
&lt;p&gt;На старте Airflow кажется простым, но по мере роста числа DAG’ов и их сложности возникают типичные проблемы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Зоопарк DAG’ов&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; — если задача выполняется несколько часов, Airflow справится, но если процесс длится дни с ожиданием внешнего действия — начнутся трудности.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Промежуточное состояние&lt;/b&gt; — его нужно сохранять во внешних системах (S3, Redis, БД), что усложняет архитектуру.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Риск превращения в «универсальный Cron»&lt;/b&gt; — когда в Airflow пытаются запихнуть всё подряд, поддержка становится кошмаром.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Вывод:&lt;/b&gt; Airflow хорош для периодических, четко структурированных задач с понятным началом и концом. Для сложной бизнес-логики он не предназначен.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;4.2. Когда Temporal требует дисциплины&lt;/h4&gt;
&lt;p&gt;Temporal дает суперспособности, но платой за них является более высокая инженерная культура:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Детерминированность&lt;/b&gt; — код Workflow обязан быть детерминированным (например, нельзя использовать `time.Now()` или случайные числа без специальной обертки).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Управление версиями&lt;/b&gt; — обновление логики workflow требует аккуратной миграции, иначе сломаются уже запущенные процессы.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Инфраструктура&lt;/b&gt; — Temporal требует собственного кластера (Cassandra/PostgreSQL + сервисы), это сложнее, чем поднять Airflow с локальной БД.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Порог входа&lt;/b&gt; — разработчикам нужно усвоить модель Workflow/Activity, понять, как работают таймауты и сигналы.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;b&gt;Вывод:&lt;/b&gt; Temporal не нужен для простых ETL-задач, но незаменим там, где процесс должен «жить» и не умирать при сбоях.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h3&gt;5. Temporal на платформах данных: когда он может быть полезен&lt;/h3&gt;
&lt;p&gt;Хотя в классическом data‑инжиниринге безраздельно правит Airflow, современные платформы данных становятся всё более распределёнными, событийно‑ориентированными и критичными к задержкам. В таких экосистемах появляются сценарии, где Temporal оказывается не избыточным, а очень даже востребованным.&lt;/p&gt;
&lt;p&gt;Вот несколько примеров, когда Temporal может усилить вашу data‑платформу:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Долгие и многоэтапные ML‑пайплайны&lt;/b&gt;  &lt;br /&gt;
Обучение модели может занимать часы и включать этапы подготовки данных, экспериментирования, валидации и деплоя. Если какой‑то этап упал, Temporal восстановит процесс с последней успешной точки, не перезапуская всё заново.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Координация распределённых трансформаций&lt;/b&gt;  &lt;br /&gt;
В архитектурах типа Data Mesh или при использовании множества независимых сервисов (например, микросервисы, каждый из которых обновляет свои витрины) Temporal выступает как надёжный координатор, гарантирующий согласованность обновлений даже при частичных отказах.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Event‑driven data pipelines&lt;/b&gt;  &lt;br /&gt;
Вместо жёсткого расписания вы можете запускать workflow по событиям — поступление нового файла в S3, сообщение в Kafka или завершение внешнего процесса. Temporal отлично поддерживает ожидание сигналов, что делает его естественным выбором для реактивных пайплайнов.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Компенсации и исправление данных&lt;/b&gt;  &lt;br /&gt;
Если в процессе загрузки обнаруживается ошибка в уже обработанных данных, Temporal позволяет реализовать компенсирующие действия (откат, дозагрузка, пересчёт) как часть workflow, с полным сохранением контекста.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Интеграция с внешними API с непредсказуемым временем ответа&lt;/b&gt;  &lt;br /&gt;
Например, вызов внешнего сервиса обогащения данных может зависнуть или отвечать минуты. Temporal управляет таймаутами, ретраями и не теряет состояние, даже если внешний сервис временно недоступен.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Обеспечение exactly‑once семантики в критичных data‑операциях&lt;/b&gt;  &lt;br /&gt;
Когда повторная обработка одних и тех же данных недопустима (финансовые отчёты, списания), Temporal с его идемпотентными Activity и уникальными идентификаторами workflow даёт высокие гарантии.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;При этом Temporal &lt;b&gt;не заменяет&lt;/b&gt; Airflow для стандартных ETL по расписанию. Однако если на вашей платформе данных есть процессы, которые:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;длятся дольше пары часов,&lt;/li&gt;
&lt;li&gt;требуют строгого состояния,&lt;/li&gt;
&lt;li&gt;зависят от внешних событий,&lt;/li&gt;
&lt;li&gt;должны безупречно восстанавливаться после сбоев,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;— то Temporal может стать вторым (и очень мощным) инструментом в вашем арсенале, работающим плечом к плечу с Airflow.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;6. Рекомендации по выбору&lt;/h3&gt;
&lt;h4&gt;Выбирайте &lt;b&gt;Airflow&lt;/b&gt;, если:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Ваша задача — регулярный запуск batch-процессов (ежедневно, почасово).&lt;/li&gt;
&lt;li&gt;Вы строите ETL/ELT, витрины данных, ML-пайплайны.&lt;/li&gt;
&lt;li&gt;Процесс укладывается в DAG с четкими шагами.&lt;/li&gt;
&lt;li&gt;Команда хорошо знает Python и работает в среде data-инженерии.&lt;/li&gt;
&lt;li&gt;Вам нужен backfill (перезапуск за прошлые периоды).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Примеры:&lt;/b&gt; загрузка из CRM в DWH, пересчет отчетов, обучение модели по ночам.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;Выбирайте &lt;b&gt;Temporal&lt;/b&gt;, если:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Процесс длится часы, дни или недели.&lt;/li&gt;
&lt;li&gt;Важен строгий контроль состояния — система должна запомнить, на каком шаге остановилась.&lt;/li&gt;
&lt;li&gt;Требуются сложные retries, таймауты и компенсационные транзакции (Saga).&lt;/li&gt;
&lt;li&gt;Есть ожидание событий от пользователя или внешнего сервиса.&lt;/li&gt;
&lt;li&gt;Вы строите микросервисную архитектуру или современную событийно‑управляемую платформу данных.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;b&gt;Примеры:&lt;/b&gt; оформление заказа, прохождение KYC, обработка платежей, доставка товара, долгий процесс верификации, а также упомянутые выше сложные data‑пайплайны с внешними зависимостями.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;7. Можно ли использовать их вместе?&lt;/h3&gt;
&lt;p&gt;Да, и это частая практика в крупных компаниях:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Airflow&lt;/b&gt; берет на себя все data-пайплайны и подготовку данных для аналитики.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Temporal&lt;/b&gt; отвечает за критичные бизнес-процессы, а также за те data‑процессы, которые требуют повышенной надёжности и управления долгим состоянием.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Например, Airflow утром подготавливает агрегированные данные, а Temporal запускает workflow их распределённой валидации и отправки во внешние системы с гарантией доставки. Инструменты не конфликтуют, а дополняют друг друга.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Итог&lt;/h3&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;b&gt;Airflow&lt;/b&gt;&lt;/td&gt;
&lt;td style="text-align: center"&gt;&lt;b&gt;Temporal&lt;/b&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;Планировщик для data-задач&lt;/td&gt;
&lt;td style="text-align: center"&gt;Движок надежных процессов (бизнес и data)&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;Расписание, простота, Python-экосистема&lt;/td&gt;
&lt;td style="text-align: center"&gt;Состояние, устойчивость, долгие workflow, события&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;/table&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;b&gt;Airflow&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;Нужны &lt;b&gt;бизнес-логика, надёжность, долгие операции, событийная оркестрация&lt;/b&gt; (включая сложные сценарии на платформе данных) → &lt;b&gt;Temporal&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Выбор инструмента — это в первую очередь выбор архитектурного подхода. Не пытайтесь забить гвозди микроскопом, но и не используйте молоток для починки часов — выбирайте то, что соответствует реальной сложности ваших задач. А если сложность смешанная — смело комбинируйте оба решения.&lt;/p&gt;
</description>
</item>

<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;
&lt;p&gt;Еще пост про это &lt;a href="https://habr.com/ru/amp/publications/1077408/"&gt;https://habr.com/ru/amp/publications/1077408/&lt;/a&gt;&lt;/p&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>


</channel>
</rss>