Распределённый cron на c с P2p‑репликацией: зачем он админам greenplum

Как я пришёл к распределённому Cron на C с P2P‑репликацией и почему без него админам Greenplum живётся хуже

Представьте типичный ночной инцидент: три часа утра, в вашем ведении здоровенный кластер Greenplum на сотню и больше сегмент‑хостов, и внезапно нужно одновременно:

- запустить тяжёлый ETL‑процесс;
- или в строго заданное время проверить доступность gpfdist на каждом сегменте;
- или синхронно включить диагностическую нагрузку.

Руки автоматически тянутся к привычным инструментам. Но очень быстро выясняется, что ни один из них не решает задачу "идеально одновременно и без центральной точки отказа".

- Обычный cron хорош, пока речь о единичных серверах. Как только узлов десятки, вы тонете в рассинхроне конфигов и "дрейфе часов".
- Ansible, SaltStack и прочие оркестраторы требуют центрального мастера и внятного SSH‑канала в тот самый момент, когда вы хотите запустить задачу. Стоит сети "дёрнуться" - часть узлов останется без команды.
- Скрипты "в лоб" через ssh‑for или parallel - это ровно та же зависимость от центрального узла и живого соединения.

Я захотел инструмент, который работает совсем иначе: вы один раз "вбрасываете" зашифрованную команду в сеть, а дальше она сама путешествует по инфраструктуре и спокойно ждёт своего часа на каждом узле. В момент "Ч" все ноды выполняют её синхронно, даже если сеть клинит, мастера нет, а половина сегментов видит только соседей. Так родилась Gorgona - распределённый Cron с P2P‑репликацией и end‑to‑end‑шифрованием.

Почему это ценно именно для админов Greenplum

Greenplum - это всегда много узлов. Десятки, иногда сотни сегмент‑хостов. И почти все действия, влияющие на данные или производительность, желательно делать одинаково и одновременно:

- запуск тяжёлого ETL в оконное время;
- перезапуск gpfdist на всех сегментах;
- включение/выключение сборщиков логов и профайлеров;
- проверка доступности дисков и сетевых интерфейсов в момент пикирования нагрузки.

Любая рассинхронизация превращается в головную боль. Один сегмент начал ETL раньше - у него уже блокировки, на другом ещё не стартовало, третий отвалился, потому что конфиг обновили на нём последним. В распределённой MPP‑СУБД цена такого "дрожания" по времени растёт экспоненциально с количеством узлов.

Gorgona решает именно эту боль: позволяет админам Greenplum один раз сформулировать действие, расписать его по времени и быть уверенными, что оно выстрелит одновременно и без участия центрального координатора.

Зачем вообще лезть в C и усложнять жизнь

На старте проекта было искушение сделать всё "по‑модному" - взять Go, Rust или хотя бы Python. Но была чёткая цель: нулевые внешние зависимости, минимальный runtime и предсказуемое поведение на любом железе - от стареньких серверов до плотных контейнеров.

Основные требования:

- запуск "из коробки" на максимально голых системах;
- минимум потребления памяти и CPU на каждом узле;
- отсутствие тяжёлых runtime‑окружений и менеджеров пакетов;
- строгий контроль над сетевым вводом‑выводом.

В результате - классический C плюс OpenSSL. Никаких дополнительных сервисов, интерпретаторов и многомегабайтных библиотек. Двоичный файл, который можно скопировать на узел и запустить хоть в emergency‑режиме.

Для админов Greenplum это важно: чем меньше софта на каждом сегмент‑хосте, тем проще поддерживать среду однородной, а значит - воспроизводимой и предсказуемой.

Философия "слепого сервера"

Ключевая идея Gorgona - сервер не знает, что именно он хранит и распространяет. Он по сути выступает "тупым" почтовым ящиком, который честно перемещает зашифрованные пакеты, не имея возможности их прочитать.

Механика такова:

1. Клиент на стороне администратора шифрует полезную нагрузку:
- для ключей используется RSA‑OAEP;
- для данных - AES‑GCM.
2. Шифротекст оборачивается в контейнер с минимальным набором метаданных:
- `unlock_at` - точный момент времени, когда команду можно забрать и выполнить;
- `expire_at` - дедлайн, после которого задача удаляется как "протухшая".
3. Сервер получает бинарный блоб. Для него это просто кусок данных: это может быть и `rm -rf /`, и отчёт о погоде, и SQL‑скрипт.
4. До наступления `unlock_at` эта "капсула" лишь реплицируется и хранится. Расшифровать содержимое не может никто, кроме владельца приватного ключа.
5. Когда наступает время, клиент (или агент на узле) забирает задачу, расшифровывает и выполняет локально.

Такой подход даёт сразу несколько бонусов:

- серверы Gorgona не хранят секреты и не знают, что именно они доставляют;
- админ может не доверять инфраструктуре, но доверять криптографии;
- легко внедрять Gorgona в средах с жёсткими регламентами по безопасности: задача зашифрована ещё до попадания в сетевой контур.

От централизованного сервера к mesh‑сети

Первая версия Gorgona была классической клиент‑серверной: один или несколько серверов, множество клиентов. Этого было достаточно, пока количество узлов невелико. Но в мире Greenplum "один сервер" быстро превращается в ту самую единственную точку отказа:

- упал сервер - кластер "ослеп", новые задачи не распространяются;
- сервер недоступен из‑за сетевого сегмента - часть узлов остаётся без обновлений.

Чтобы избежать этой ловушки, пришлось идти в сторону Active‑Active‑репликации с использованием gossip‑подхода.

Основная идея: каждый узел Gorgona одновременно и клиент, и сервер. Ноды постоянно обмениваются между собой списками известных задач и их состояний. Если одна нода знает о новой команде, через несколько итераций о ней узнают все, до кого добирается сеть, даже по непрямым маршрутам.

Gossip, эхо и "стоп‑кран"

Реализация P2P‑распространения кажется простой только на бумаге. На практике вы сталкиваетесь с эффектом "бесконечного эхо":

- один сервер отправил задачу трём соседям;
- каждый сосед переслал её ещё трём;
- через пару кругов та же задача начинает возвращаться к узлу‑источнику из других направлений, множится и забивает сеть.

Чтобы этого не происходило, в Gorgona реализовано несколько уровней защиты:

1. Уникальный идентификатор задачи
Каждой команде присваивается глобальный UUID и версия. Узел хранит у себя компактный журнал "я это уже видел". При получении дубликата он не раздувает трафик далее.

2. Версионирование и "стоп‑кран"
Если команда была отозвана или заменена новой версией, в сеть распространяется специальное "стоп‑сообщение". Узлы, получив его, помечают старую запись как отменённую и больше её не реплицируют.

3. Ограничение глубины эхо
В каждом сообщении хранится "возраст" распространения. Как только он превышает допустимый предел, пакет не пересылается дальше. Это не даёт одной и той же задаче гулять по сети бесконечно.

4. Стохастический выбор соседей
Узел не шлёт сообщения всем подряд, а выбирает подмножество соседей по определённым правилам. Этого достаточно для сходимости графа, но сильно снижает объём дубликатов.

Для больших Greenplum‑кластеров это критично: вам нужен инструмент, который не превратить и без того нагруженную сеть в свалку контролирующего трафика.

Как не потерять историю: Snowflake и взаимная синхронизация

Чтобы в P2P‑сети не было конфликтов и потерь, задачам нужен уникальный, монотонно растущий идентификатор. Вдохновляясь идеей Snowflake‑ID, в Gorgona используется схема, где ID строится из:

- временной метки;
- идентификатора ноды;
- счётчика внутри ноды.

Это гарантирует уникальность даже при одновременном создании множества задач на разных узлах.

Вместе с этим используется mutual sync - механизм взаимной сверки:

- при подключении двух узлов они обмениваются хэшами текущих журналов задач;
- при расхождении хэшей синхронизируют только недостающие записи;
- при конфликте версий (одна и та же задача, разные статусы) действует чёткое правило приоритета, основанное на версиях и временных метках.

В результате, даже если часть сети на время выпадает (типичная ситуация в дата‑центрах), после восстановления она постепенно догоняет общую картину и не теряет ни одной важной команды.

Практика: где Gorgona особенно полезна

Хотя изначально всё делалось под Greenplum, архитектурно Gorgona отлично чувствует себя рядом с любыми распределёнными системами, где:

- число узлов больше пары десятков;
- есть требования к одновременному выполнению операций;
- нежелательно центральное "узкое горлышко".

ClickHouse: операции без тормозящего Keeper

В больших установках ClickHouse нередко возникает задача: выполнить DDL или сервисные операции одновременно на десятках или сотнях шардов. Стандартный механизм `ON CLUSTER` опирается на Keeper (ZooKeeper или ClickHouseKeeper), и при нагрузке тот может банально не выдерживать.

Как действует Gorgona:

- через P2P‑сеть распространяется команда, к примеру, `FREEZE PARTITION` для подготовки к бэкапу;
- каждая нода получает её заранее, хранит локально и знает точное время запуска;
- в назначенный момент все узлы запускают операцию одновременно, не создавая дополнительного давления на Keeper.

Админу Greenplum этот сценарий очень знаком: вместо Keeper - master/standby и сегменты; вместо `FREEZE PARTITION` -, например, одновременный запуск `VACUUM` или ETL‑процесса.

Ceph: диагностика "в шторм"

В Ceph при ребалансировке после падения дисков сеть и CPU загружаются до предела. Залезть по SSH на сотни OSD‑узлов, чтобы посмотреть `smartctl` или `iostat`, - сомнительное удовольствие.

С агента Gorgona, заранее развёрнутого на каждом OSD‑хосте, можно:

- заранее подготовить диагностическую команду;
- разослать её по mesh‑сети;
- даже если прямой путь к какому‑то узлу забит трафиком репликации, пакет уйдёт через соседние ноды и там отработает;
- результаты собираются обратно теми же обходными маршрутами.

С Greenplum ситуация аналогична: когда сегменты под нагрузкой, SSH может "захлёбываться", а P2P‑агенты всё равно находят путь друг к другу.

Apache Kafka: массовое изменение конфигов без перекосов

Kafka‑брокеры болезненно реагируют на неравномерные задержки. Если обновлять правила фильтрации или квоты по одному, возникает риск: половина брокеров работает по старым настройкам, половина - по новым. В результате - перекос по партициям и непредсказуемое поведение кластера.

С помощью Gorgona можно:

- зашифровать пакет настроек;
- распространить его по всем брокерам;
- дать им чёткое "время Х" для применения;
- ноды применяют изменения локально, без централизованной "палочки‑дирижёра".

Для Greenplum это прямой аналог массового обновления конфигурации сегментов, параметров ресурсного планировщика или внешних сервисов (например, gpfdist), когда все изменения должны вступить в силу синхронно.

Конкретные кейсы для Greenplum и ETL

Какие практические задачи Gorgona решает в Greenplum‑мире:

1. Синхронный запуск тяжёлого ETL
Задачи по загрузке и трансформации данных заранее упаковываются в зашифрованные команды и разносятся по сегментам. В нужный момент они стартуют одновременно, не требуя центрального оркестратора и живого SSH на каждую ноду.

2. Одновременная проверка доступности gpfdist
Нужно убедиться, что все внешние загрузчики на сегментах работают. Команда проверки рассылается заранее, в назначенный момент собираются статусы. Неважно, есть ли прямой доступ к каждому сегменту - агенты сами доберутся друг до друга.

3. Координированная смена конфигураций
Изменения в параметрах окружения, лимитах ресурсов или поведении внешних скриптов можно готовить и разводить заранее, а затем одним "выстрелом" включать в силу на всех сегментах.

4. Автоматическое "самолечение" кластера
На каждый узел можно повесить локальные проверки состояния (диски, сеть, процессы Greenplum). При срабатывании триггера агент не только фиксирует проблему, но и получает из сети заранее подготовленный "рецепт лечения" - например, перезапуск gpfdist, переключение на резервный диск, перевод сегмента в ограниченный режим.

Дополнительные сценарии: от мониторинга до P2P‑relay

Помимо тяжёлых админских задач Gorgona хорошо подходит и для более лёгких вещей:

- Мгновенный аудит нагрузки
Команда на сбор `load average`, занятости CPU и дисков может быть разослана и выполнена одновременно. Результатом будет "моментальный снимок" всего кластера без необходимости пробегать по каждому хосту вручную.

- Пробитие сетевой изоляции (P2P Relay)
Если часть узлов находится за NAT, в отдельном VLAN или просто недоступна по прямому маршруту, Gorgona будет использовать другие ноды как "мосты". Сообщения пойдут в обход, пока существует хоть какая‑то связность в сети.

- Синхронный выстрел без jitter
В обычном скриптовом подходе команды всегда приходят с разбросом по времени: сеть, SSH‑накладные расходы, время выполнения. Gorgona позволяет задать момент запуска заранее и добиться максимального выравнивания времени старта.

- Зашифрованные "мелочи"
Никто не мешает использовать ту же инфраструктуру для более простых задач: разноса конфигов, скриптов диагностики или даже выборочного логирования. Содержание всё равно зашифровано, для сети это просто бинарный шум.

Что внутри (для тех, кто любит "железную" часть)

Под капотом Gorgona:

- реализация сетевого стека на C с неблокирующим I/O;
- шифрование на базе OpenSSL (RSA‑OAEP + AES‑GCM);
- собственный формат сообщений с учётом ограничений MTU и возможностей фрагментации;
- механизм устойчивой записи задач с журналированием для защиты от потерь при падении;
- модуль планировщика, обеспечивающий точный старт задач с учётом времени системы и возможных дрейфов;
- модуль репликации, реализующий gossip и правила "стоп‑крана" для подавления эхо.

Все это вместе даёт инструмент, который можно спокойно доверить крупному кластеру: он не тащит за собой тяжёлый зоопарк зависимостей, не превращается в третий "центральный мозг" и не становится очередной точкой отказа.

К чему всё идёт

Проект уже решает базовые задачи распределённого Cron с P2P‑репликацией, но пространство для развития огромное:

- более гибкие политики планирования;
- расширенные сценарии "самолечения" кластера;
- интеграция с метриками и алертингом;
- тонкая настройка приоритетов задач в больших сетях.

Для админа Greenplum мораль очень проста: чем больше узлов, тем опаснее полагаться на централизованный оркестратор, особенно в моменты сетевых проблем. Распределённый, криптографически защищённый Cron, который живёт на самих нодах и не зависит от "единственного мастера", даёт ту самую недостающую степень свободы - и возможность, наконец, спать ночью спокойнее.

Прокрутить вверх