Аудит и race-safe дедупликация trades.order_id #502

Closed
opened 2026-07-21 17:01:58 +00:00 by andrei · 2 comments
Owner

Production preflight обнаружил 42 duplicate non-empty live order_id по всем trade-типам; среди closed rows за 30д затронуты 4 grid rows. Нельзя удалять историю или ставить UNIQUE вслепую. Нужен audit duplicate cohorts/причин, backup, канонизация без искажения PnL, partial unique index для непустых live order_id и INSERT ... ON CONFLICT DO NOTHING. Отдельная задача после originating-strategy attribution #501.

Production preflight обнаружил 42 duplicate non-empty live order_id по всем trade-типам; среди closed rows за 30д затронуты 4 grid rows. Нельзя удалять историю или ставить UNIQUE вслепую. Нужен audit duplicate cohorts/причин, backup, канонизация без искажения PnL, partial unique index для непустых live order_id и INSERT ... ON CONFLICT DO NOTHING. Отдельная задача после originating-strategy attribution #501.
Author
Owner

Отчёт аудита и канонизации (2026-07-22, выполнено)

Аудит (read-only, arnold_trader_prod.trades): 42 duplicate live order_id / 84 строки, все ≤ 2026-07-02 (свежих нет — advisory-lock дедуп в db_save_trade уже работает). Классификация:

  • A (33 группы): точные same-strategy дубли (qty/price/side/pnl идентичны, лаг 1–60с) — двойная запись одного филла.
  • B (8 групп): пары futures_monitor(buy/sell) + grid_bidir_*(short/long_closed) с равными qty/price — один биржевой ордер записан наблюдателем И стратегией; канонична grid-строка (атрибуция #501).
  • Ambiguous (1): 77ff7ada… qty 0.1 vs 2.9 — экономики разные, обе строки сохранены.

Канонизация (backup-first, одна транзакция с инвариант-гейтами 33/8/0):

  • Backup: таблица trades_dedup_backup_20260722 (84 строки) + /root/backups/trades-dedup-backup-20260722.sql.
  • Удалены 33 (A, кроме min(id)) + 8 (B, monitor-строка). Снят двойной счёт PnL: +7.0241 USD (истинный PnL ниже прежних агрегатов на эту сумму).
  • Ambiguous: monitor-строке order_id→NULL, оригинал в metadata.dedup_audit — история цела, уникальность возможна.

Индекс: CREATE UNIQUE INDEX CONCURRENTLY idx_trades_live_order_id_uniq ON trades(order_id) WHERE order_id IS NOT NULL AND order_id <> '' AND is_paper=false — создан, дубликатов 0.

Код: advisory-lock дедуп уже был; ON CONFLICT-страховка — PR выше.

## Отчёт аудита и канонизации (2026-07-22, выполнено) **Аудит (read-only, arnold_trader_prod.trades):** 42 duplicate live order_id / 84 строки, все ≤ 2026-07-02 (свежих нет — advisory-lock дедуп в db_save_trade уже работает). Классификация: - **A (33 группы):** точные same-strategy дубли (qty/price/side/pnl идентичны, лаг 1–60с) — двойная запись одного филла. - **B (8 групп):** пары futures_monitor(buy/sell) + grid_bidir_*(short/long_closed) с равными qty/price — один биржевой ордер записан наблюдателем И стратегией; канонична grid-строка (атрибуция #501). - **Ambiguous (1):** 77ff7ada… qty 0.1 vs 2.9 — экономики разные, обе строки сохранены. **Канонизация (backup-first, одна транзакция с инвариант-гейтами 33/8/0):** - Backup: таблица `trades_dedup_backup_20260722` (84 строки) + `/root/backups/trades-dedup-backup-20260722.sql`. - Удалены 33 (A, кроме min(id)) + 8 (B, monitor-строка). **Снят двойной счёт PnL: +7.0241 USD** (истинный PnL ниже прежних агрегатов на эту сумму). - Ambiguous: monitor-строке order_id→NULL, оригинал в `metadata.dedup_audit` — история цела, уникальность возможна. **Индекс:** `CREATE UNIQUE INDEX CONCURRENTLY idx_trades_live_order_id_uniq ON trades(order_id) WHERE order_id IS NOT NULL AND order_id <> '' AND is_paper=false` — создан, дубликатов 0. **Код:** advisory-lock дедуп уже был; ON CONFLICT-страховка — PR выше.
Author
Owner

⚠️ POST-INCIDENT (2026-07-22 12:57–14:06Z) — вызван реализацией этого issue, устранён

Что случилось: при патче #527 ssh-heredoc съел пустые кавычки литерала — в прод ушло order_id <> (без '') → PostgresSyntaxError: syntax error at or near "AND" на КАЖДОМ live-INSERT trades. Мой string-assert тест проверял наличие ключевых слов, но не литерал — инцидент прошёл тесты и CI (юнит перехватывал SQL фейк-пулом, живого prepare не было). Обнаружено адверсариальной проверкой Sentry-событий (9 шт за 69 мин).

Устранение: 14:2xZ hot-patch прод-checkout + pm2 restart (real.bin, ADAMI-guard manual path); коммит-закрепление 09a94b2f в master (тест теперь пиннит точный литерал order_id <> '').

Ущерб и recovery: деньги/позиции целы (Bybit-исполнение не зависело от записи). Ручной futures_position_sync_daemon.run_once: users 1/1, positions 4/4, errors 0, closed-ghost'ов нет → закрытий без записи НЕТ. Потеря ограничена ≤9 entry-строками журнала за окно (Sentry-каунт; возможны ретраи одного филла) — их будущие закрытия атрибутируются fallback-эвристикой resolve_close_attribution. Реконструкция entries из Bybit execution history сознательно не выполнялась (риск двойной атрибуции > аналитическая ценность микро-грид записей).

Уроки (в learned): (1) прод-SQL никогда не патчить через ssh-heredoc — только file-transfer; (2) SQL-тесты обязаны пиннить литералы; (3) string-assert ≠ живой prepare — предупреждение ревьюера r2h1 повторено мною же.

## ⚠️ POST-INCIDENT (2026-07-22 12:57–14:06Z) — вызван реализацией этого issue, устранён **Что случилось:** при патче #527 ssh-heredoc съел пустые кавычки литерала — в прод ушло `order_id <> ` (без `''`) → `PostgresSyntaxError: syntax error at or near "AND"` на КАЖДОМ live-INSERT trades. Мой string-assert тест проверял наличие ключевых слов, но не литерал — инцидент прошёл тесты и CI (юнит перехватывал SQL фейк-пулом, живого prepare не было). Обнаружено адверсариальной проверкой Sentry-событий (9 шт за 69 мин). **Устранение:** 14:2xZ hot-patch прод-checkout + pm2 restart (real.bin, ADAMI-guard manual path); коммит-закрепление 09a94b2f в master (тест теперь пиннит точный литерал `order_id <> ''`). **Ущерб и recovery:** деньги/позиции целы (Bybit-исполнение не зависело от записи). Ручной `futures_position_sync_daemon.run_once`: users 1/1, positions 4/4, errors 0, closed-ghost'ов нет → закрытий без записи НЕТ. Потеря ограничена ≤9 entry-строками журнала за окно (Sentry-каунт; возможны ретраи одного филла) — их будущие закрытия атрибутируются fallback-эвристикой resolve_close_attribution. Реконструкция entries из Bybit execution history сознательно не выполнялась (риск двойной атрибуции > аналитическая ценность микро-грид записей). **Уроки (в learned):** (1) прод-SQL никогда не патчить через ssh-heredoc — только file-transfer; (2) SQL-тесты обязаны пиннить литералы; (3) string-assert ≠ живой prepare — предупреждение ревьюера r2h1 повторено мною же.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
europa-tech-srl/arnold-trader-app#502
No description provided.