Skip to content

Развертывание

Поддерживаемый путь

Текущая версия проекта после squashed migrations поддерживает deployment через fresh-install.

Шаги

cp .env.example .env
docker compose down -v
docker compose up -d --build

Проверки

docker compose ps
docker compose logs --tail=200 worker

Проверьте, что worker успешно применил миграции и запустился без ошибок.

Примечание

Upgrade-in-place со старых БД до squash-версии не поддерживается.

CI auto-deploy (GitHub Actions -> production server)

В репозитории используется workflow .github/workflows/deploy.yml.

Триггеры: - push в main (кроме docs-only изменений); - ручной запуск через workflow_dispatch.

Pipeline выполняет SSH deploy на сервер: 1. git fetch origin --prune 2. git checkout main 3. git reset --hard <github.sha> 4. docker compose up -d --build --remove-orphans 5. post-deploy проверки: docker compose ps, анализ worker логов, проверка на exited-сервисы.

Обязательная конфигурация GitHub

Secrets: - PROD_SSH_HOST - PROD_SSH_USER - PROD_SSH_PRIVATE_KEY - PROD_SSH_KNOWN_HOSTS

Variables: - PROD_SSH_PORT (опционально, default 22) - PROD_APP_PATH (абсолютный путь к репозиторию на сервере)

Серверный контракт

  • На сервере есть clone этого репозитория и origin указывает на GitHub.
  • Установлены git, docker, docker compose (v2).
  • Production .env хранится на сервере и не перезаписывается workflow.

Rollback (manual)

При падении workflow команда отката печатается в логи job. Шаблон:

cd <PROD_APP_PATH> \
  && git fetch origin --prune \
  && git checkout main \
  && git reset --hard <previous-sha> \
  && docker compose up -d --build --remove-orphans

Документация (Cloudflare -> main)

Документация публикуется Cloudflare напрямую из ветки main.

Workflow .github/workflows/docs.yml не выполняет отдельный deploy документации и не пишет в gh-pages. Его задача только одна: проверить, что текущие файлы документации и mkdocs.yml успешно собираются через mkdocs build --strict.

Источник истины для версий docs toolchain: requirements-docs.txt.

Каноничный порядок заполнения таблиц (bootstrap + schedules)

Этот раздел является единственным источником истины по последовательности запуска data pipelines.

1. Prerequisites / readiness gates

docker compose up -d --build
docker compose ps
docker compose logs --tail=200 worker

Ожидаемое состояние: - postgres и temporal в healthy; - temporal-admin-tools в healthy; - worker в started/up; - в логах worker есть успешный старт и нет ошибки Namespace default is not found.

2. Manual bootstrap (правильная последовательность)

Шаг 1. SyncExchangesWorkflow (обязательно): актуализирует метаданные exchanges и фиксирует базовое состояние каталога бирж перед последующими sync-шагами.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncExchangesWorkflow \
  --workflow-id sync-exchanges-bootstrap-$(date +%s)

Шаг 2. SyncNetworksCatalogWorkflow (обязательно): заполняет networks_catalog и связанные идентификаторы/эндпоинты/метрики.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncNetworksCatalogWorkflow \
  --workflow-id sync-networks-catalog-bootstrap-$(date +%s) \
  --input '{"force_full_refresh":false,"soft_delete_grace_days":30,"metrics_required":false}'

Шаг 3. SyncExchangeCoinsWorkflow (обязательно): заполняет exchange_assets*, global_assets*, asset_mapping_quarantine.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncExchangeCoinsWorkflow \
  --workflow-id sync-exchange-coins-bootstrap-$(date +%s) \
  --input '{"force_full_refresh":false,"soft_delete_grace_days":30}'

Шаг 4. SyncGateLaunchpoolWorkflow (обязательно для launchpool аналитики): заполняет fct_gate_launchpool_* с использованием уже созданного mapping-контура из шага 3.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncGateLaunchpoolWorkflow \
  --workflow-id sync-gate-launchpool-bootstrap-$(date +%s) \
  --input '{"page":1,"page_size":1000,"status":0}'

Шаг 5. EnsureGateLaunchpoolScheduleWorkflow (после успешного шага 4): включает регулярный 5-минутный ingestion.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type EnsureGateLaunchpoolScheduleWorkflow \
  --workflow-id ensure-gate-launchpool-schedule-bootstrap-$(date +%s) \
  --input '{"schedule_id":"sync-gate-launchpool-5m","cron":"*/5 * * * *","timezone":"UTC","task_queue":"default","page":1,"page_size":1000,"status":0}'

Expected result: schedule sync-gate-launchpool-5m создан/обновлен idempotent-образом.

Шаг 6. SyncBybitLaunchpoolWorkflow (manual-only): снимает snapshot Bybit Launchpool в отдельные таблицы fct_bybit_launchpool_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncBybitLaunchpoolWorkflow \
  --workflow-id sync-bybit-launchpool-bootstrap-$(date +%s) \
  --input '{"current":1}'

Шаг 7. SyncBybitEasyEarnWorkflow (manual-only): снимает snapshot Bybit Easy Earn в отдельные таблицы fct_bybit_easy_earn_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncBybitEasyEarnWorkflow \
  --workflow-id sync-bybit-easy-earn-bootstrap-$(date +%s) \
  --input '{}'

Шаг 8. SyncGateSimpleEarnWorkflow (manual-only): снимает snapshot Gate Simple Earn в отдельные таблицы fct_gate_simple_earn_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncGateSimpleEarnWorkflow \
  --workflow-id sync-gate-simple-earn-bootstrap-$(date +%s) \
  --input '{}'

Шаг 9. SyncKuCoinEarnWorkflow (manual-only): снимает snapshot KuCoin Earn All Products в отдельные таблицы fct_kucoin_earn_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncKuCoinEarnWorkflow \
  --workflow-id sync-kucoin-earn-bootstrap-$(date +%s) \
  --input '{}'

Шаг 10. SyncBingXLaunchpoolWorkflow (manual-only): снимает snapshot BingX Launchpool в отдельные таблицы fct_bingx_launchpool_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncBingXLaunchpoolWorkflow \
  --workflow-id sync-bingx-launchpool-bootstrap-$(date +%s) \
  --input '{"include_details":true}'

Шаг 11. SyncBingXXPoolWorkflow (manual-only): снимает snapshot BingX XPool в отдельные таблицы fct_bingx_xpool_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncBingXXPoolWorkflow \
  --workflow-id sync-bingx-xpool-bootstrap-$(date +%s) \
  --input '{"include_details":true}'

Шаг 12. SyncBitgetLaunchpoolWorkflow (manual-only): снимает snapshot Bitget PoolX в отдельные таблицы fct_bitget_launchpool_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncBitgetLaunchpoolWorkflow \
  --workflow-id sync-bitget-launchpool-bootstrap-$(date +%s) \
  --input '{"include_details":true}'

Шаг 13. SyncKuCoinLaunchpoolWorkflow (manual-only): снимает snapshot KuCoin GemPool в отдельные таблицы fct_kucoin_launchpool_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncKuCoinLaunchpoolWorkflow \
  --workflow-id sync-kucoin-launchpool-bootstrap-$(date +%s) \
  --input '{"include_current":true,"include_history":true,"history_page_size":20,"max_history_pages":3}'

Шаг 14. SDK loans pipelines (manual-only): снимают snapshot catalog offers в отдельные таблицы fct_<exchange>_loans_*.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncGateLoansWorkflow \
  --workflow-id sync-gate-loans-bootstrap-$(date +%s) \
  --input '{}'

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncBybitLoansWorkflow \
  --workflow-id sync-bybit-loans-bootstrap-$(date +%s) \
  --input '{}'

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncKuCoinLoansWorkflow \
  --workflow-id sync-kucoin-loans-bootstrap-$(date +%s) \
  --input '{}'

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type SyncWhitebitLoansWorkflow \
  --workflow-id sync-whitebit-loans-bootstrap-$(date +%s) \
  --input '{}'

Шаг 15. Terry default schedule (optional): если задан TERRY_DEFAULT_DATA_SOURCE_ID, worker при старте автоматически делает paused upsert schedule для RunIngestionWorkflow. Для ручного управления можно вызвать orchestration workflow напрямую.

docker compose exec -T temporal-admin-tools temporal workflow start \
  --address temporal:7233 --namespace default --task-queue default \
  --type EnsureRunIngestionScheduleWorkflow \
  --workflow-id ensure-run-ingestion-schedule-bootstrap-$(date +%s) \
  --input '{"schedule_id":"run-ingestion","cron":"0 0 * * *","timezone":"UTC","task_queue":"default","data_source_id":"<uuid>","paused":true,"preserve_paused_state":true}'

3. Почему порядок именно такой

  • SyncExchangeCoinsWorkflow резолвит network_key через networks_catalog; без шага 2 растет missing_network_mapping.
  • SyncGateLaunchpoolWorkflow маппит project_coin/subpool_coin через exchange_assets* + exchange_asset_mappings; без шага 3 растет fct_gate_launchpool_quarantine.
  • SyncGateSimpleEarnWorkflow маппит project_coin/reward_coin через exchange_symbol_assets; без шага 3 растет fct_gate_simple_earn_quarantine.
  • SyncExchangesWorkflow должен выполняться первым шагом как обязательный этап консистентного bootstrap каталога бирж.
  • Поэтому каноничный порядок bootstrap для core-контура: SyncExchangesWorkflow -> SyncNetworksCatalogWorkflow -> SyncExchangeCoinsWorkflow -> SyncGateLaunchpoolWorkflow.
  • Для Bybit/BingX/Bitget/KuCoin Launchpool, Bybit Easy Earn, Gate Simple Earn, KuCoin Earn, BingX XPool и SDK loans используются отдельные manual-only pipelines (SyncBybitLaunchpoolWorkflow, SyncBybitEasyEarnWorkflow, SyncGateSimpleEarnWorkflow, SyncKuCoinLaunchpoolWorkflow, SyncKuCoinEarnWorkflow, SyncBingXLaunchpoolWorkflow, SyncBitgetLaunchpoolWorkflow, SyncBingXXPoolWorkflow, SyncGateLoansWorkflow, SyncBybitLoansWorkflow, SyncKuCoinLoansWorkflow, SyncWhitebitLoansWorkflow) и отдельные хранилища fct_*; schedule по умолчанию для них не включается.
  • Terry runtime живет отдельно от exchange-specific pipelines: RunIngestionWorkflow выполняет universal loader по persisted data_source_id, а EnsureRunIngestionScheduleWorkflow управляет paused-by-default schedule run-ingestion.

4. Проверки после каждого шага

Проверка статуса workflow (должен быть COMPLETED):

docker compose exec -T temporal-admin-tools temporal workflow describe \
  --address temporal:7233 --namespace default \
  --workflow-id <workflow-id>

Минимальные SQL checks:

docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM networks_catalog;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM exchange_assets;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM exchange_asset_networks;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_gate_launchpool_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_bybit_launchpool_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_bybit_easy_earn_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_gate_simple_earn_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_kucoin_earn_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_kucoin_launchpool_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_bingx_launchpool_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_bitget_launchpool_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_bingx_xpool_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_gate_loans_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_bybit_loans_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_kucoin_loans_snapshot;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT COUNT(*) FROM fct_whitebit_loans_snapshot;"

Мониторинг quarantine:

docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM asset_mapping_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_gate_launchpool_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_bybit_launchpool_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_bybit_easy_earn_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_gate_simple_earn_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_kucoin_earn_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_kucoin_launchpool_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_bingx_launchpool_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_bitget_launchpool_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"
docker exec -i kit-postgres psql -U temporal -d kit -At -c "SELECT reason, COUNT(*) FROM fct_bingx_xpool_quarantine GROUP BY reason ORDER BY COUNT(*) DESC;"

5. Порядок включения расписаний после bootstrap

Включать только после успешного bootstrap шагов 2-4.

Рекомендуемый порядок: 1. sync-gate-launchpool-5m (обязательно) 2. sync-exchange-coins-daily (если используется) 3. sync-networks-catalog-daily (если используется) 4. sync-exchanges-daily (опционально)

Не включайте регулярный launchpool ingestion до успешного выполнения bootstrap для SyncNetworksCatalogWorkflow, SyncExchangeCoinsWorkflow и SyncGateLaunchpoolWorkflow.

6. Документационные сценарии валидации runbook

  1. Fresh-install: пройти шаги runbook от старта сервисов до включения schedule без дополнительных правок.
  2. Повторный bootstrap: повторный запуск шагов не должен приводить к разрушению состояния (идемпотентный смысл).
  3. Race scenario: при Namespace default is not found recovery через restart worker должен возвращать пайплайн в рабочее состояние.
  4. Quality scenario: запуск launchpool до ExchangeCoins является анти-паттерном (рост quarantine); runbook предотвращает это правильным порядком.