Skip to content
PПромтбук
RUEN
03Оркестрация

Scatter-gather: параллель N задач + сборка

Параллельный fan-out на N однотипных задач, сборка результатов, partial failures, timeout, дедупликация.

Реализуй scatter-gather для {{unit_task}} с fan-out = {{fanout}}. Паттерн прост на бумаге и сложен в проде: вся боль — в partial failures и сборке.

1. Когда scatter-gather подходит

Идеально когда:

  • N однотипных задач (один template, разный input)
  • Задачи независимы друг от друга
  • Результаты — composable (union, max, sum, list)
  • Latency важна — sequential займёт N × t

Плохо когда:

  • Задачи зависимы (выход одной = вход другой) → pipeline
  • Нужен ранний exit при первом успехе → лучше race / quorum
  • N маленькое (2-3) и t короткое → overhead spawn > выигрыша

2. Структура

                   scatter
       supervisor ───────┬────► worker_1 (input_1) ─┐
                         ├────► worker_2 (input_2) ─┤
                         ├────► worker_3 (input_3) ─┤  gather
                         └────► worker_N (input_N) ─┘──► assembler
                                                          │
                                                          ▼
                                                      final_output

3. Контракт unit-задачи

scatter_task:
  task_id: scatter_2026-05-17_001
  unit_template:
    goal: "{{unit_task}} на одном элементе"
    input_schema: { item_id, item_data, context }
    return_schema:
      status: "ok" | "partial" | "failed" | "timeout"
      output: <по типу>
      evidence: [...]
      confidence: 0-100
  inputs: [{ item_id: 1, ... }, { item_id: 2, ... }, ...]
  budget_per_worker:
    max_tokens: 5000
    max_time_sec: 60
  global_budget:
    total_max_time_sec: 180   # gather не ждёт дольше
    min_success_count: 7      # ниже — считаем scatter провалившимся
    min_success_ratio: 0.7

Без min_success_count/min_success_ratio нельзя автоматически решить "достаточно ли результатов".

4. Обработка partial failures

В fan-out на 10+ workers что-то всегда провалится. План:

StatusЧто делать
okВключить в merge
partialВключить с пометкой; снизить confidence итога
failedЛогировать причину; retry если transient (rate-limit, network)
timeoutНе ждать; пометить timed_out в evidence

Решение об итоге:

  1. success_count >= min_success_count И success_ratio >= min_success_ratio → продолжаем merge
  2. Иначе → escalate: "scatter провалился, X из Y успешно"
  3. Никогда не маскируй неполный результат под полный

5. Дедупликация

Когда нужна:

  • Workers могут вернуть пересекающиеся данные (одна и та же библиотека из двух источников)
  • Один item_id случайно подан дважды
  • Retry создал две копии result'а

Стратегии:

  • Pre-dedup: дедуп inputs по ключу перед dispatch (cheap)
  • Post-dedup: дедуп outputs по signature после gather (надёжнее)
  • Both: pre + post (если данные дорогие)

Signature = stable hash от ключевых полей (не от всего payload — иначе collision).

6. Timeout strategy

Два уровня:

  • Per-worker: каждый worker не дольше max_time_sec
  • Global gather: даже если worker ещё работает, после total_max_time_sec собираем то, что есть

Реализация: Promise.allSettled + явный timeout race. Promise.all НЕ годится — упадёт целиком на первой ошибке.

results = await Promise.race([
  Promise.allSettled(workers),
  timeoutAfter(total_max_time_sec)
])

7. Merge / assembly

Зависит от типа output'а:

ТипMerge
Listconcat → dedup by signature → sort
Setunion
Mapmerge by key; conflict policy: first / last / max-confidence
Number aggregatesum / avg / max / min (явно выбрать)
Textsummarise N → 1 (LLM call с явной инструкцией)

Confidence итога = функция от individual confidences (например, mean × success_ratio).

8. Метрики

  • success_ratiook / total (target ≥ 0.8)
  • p50 / p95 worker latency — распределение времени (длинный хвост = плохой sizing)
  • timeout_rate — % timed-out (target ≤ 5%)
  • dedup_rate — % дубликатов в outputs (≥ 10% → подумай о pre-dedup)
  • gather_overhead — assembler time / total time (target ≤ 10%)

9. Anti-patterns

  • Promise.all без allSettled — один failed убивает весь scatter
  • ❌ Нет global timeout — gather зависает навсегда из-за одного worker'а
  • ❌ Нет min_success_count — merge на 1 из 10 результатов выдаётся как "готово"
  • ❌ Workers с shared state (общая БД на запись) — race и дубликаты
  • ❌ Retry без exponential backoff — DDoS на источник
  • ❌ Dedup по hash от всего payload — миссы при незначительных вариациях
  • ❌ Workers возвращают разные schemas — assembler не сможет merge
  • ❌ Все workers стартуют одновременно без throttle — rate-limit от внешнего API
  • ❌ Маскирование timed-out как ok с пустым output — фейковый успех
  • ❌ Sequential scatter (последовательно): N × t latency — теряется весь смысл паттерна

Deliverable

  • Schema scatter_task с примером на 3 inputs
  • Алгоритм assembler'а под конкретный тип output'а
  • Политика partial failures (правила решения)
  • 5 метрик с порогами
  • Bench: latency single vs scatter на реальных N
К подразделу «Оркестрация»
Похожие промты