Skip to main content

data-dataflow-coordination

Coordination patterns for distributed dataflow systems including barriers, epochs, and distributed snapshots

Informations de source

Dépôt
tools-only/X-Skills
Dernière activité de la source
1 mars 2026 à 03:37
Langue détectée de SKILL.md
anglais
Étoiles
7
Forks
1

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
data-dataflow-coordination
description
Coordination patterns for distributed dataflow systems including barriers, epochs, and distributed snapshots
# Dataflow Coordination **Scope**: Coordination primitives, barrier synchronization, epoch markers, distributed snapshots, consistency **Lines**: 370 **Last Updated**: 2025-10-27 **Format Version**: 1.0 (Atomic) ## When to Use This Skill Use this skill when: - Coordinating multiple parallel dataflow streams - Implementing barrier synchronization across workers - Managing epochs and checkpointing in distributed systems - Building consistent snapshots of distributed state - Coordinating multi-stage pipeline computations - Implementing exactly-once semantics with coordination - Synchronizing external systems with dataflow progress - Handling backpressure across distributed workers ## Core Concepts ### Coordination Mechanisms ``` Barriers → Synchronization points across parallel streams → All workers must reach barrier before proceeding → Use for: Global aggregations, checkpointing → Cost: Latency spike at barrier Epochs → Logical time units for batching → Watermarks: "No data before T will arrive" → Use for: Windowing, progress tracking → Cost: Buffering until epoch closes Snapshots → Consistent global state capture → Chandy-Lamport algorithm → Use for: Fault recovery, migration → Cost: Storage and I/O overhead ``` ### Progress Tracking Models ``` Global Frontier → Minimum timestamp across all workers → Conservative: Waits for slowest worker → Guarantees: Completeness at timestamp Per-Worker Frontiers → Independent progress per worker → Optimistic: Faster workers proceed → Requires: Careful synchronization Hierarchical Frontiers → Per-operator, per-worker tracking → Fine-grained progress visibility → Complex but enables optimization ``` ### Consistency Models ``` Strong Consistency → All workers see same state → Requires: Global coordination → Use for: Financial transactions Eventual Consistency → Workers converge over time → Minimal coordination → Use for: Analytics, monitoring Causal Consistency → Respects causal relationships → Vector clocks, happens-before → Use for: Distributed collaboration ``` ## Patterns ### Pattern 1: Barrier Synchronization (Rust/Timely) ```rust use timely::dataflow::{Scope, Stream}; use timely::dataflow::channels::pact::Pipeline; use timely::dataflow::operators::generic::operator::Operator; use std::collections::HashMap; // Barrier that emits when all inputs reach same timestamp fn barrier<G: Scope>( streams: Vec<&Stream<G, i32>>, ) -> Stream<G, Vec<i32>> { assert!(!streams.is_empty()); let mut builder = timely::dataflow::operators::generic::builder_rc::OperatorBuilder::new( "Barrier".to_string(), streams[0].scope(), ); // Create inputs for all streams let mut inputs: Vec<_> = streams.iter() .map(|stream| builder.new_input(stream, Pipeline)) .collect(); let (mut output, stream) = builder.new_output(); builder.build(move |_capability| { let num_inputs = inputs.len(); let mut buffers: HashMap<G::Timestamp, Vec<Vec<i32>>> = HashMap::new(); move |_frontiers| { // Collect data from all inputs for (idx, input) in inputs.iter_mut().enumerate() { input.for_each(|time, data| { let entry = buffers.entry(time.time().clone()) .or_insert_with(|| vec![Vec::new(); num_inputs]); entry[idx].extend(data.iter().cloned()); }); } // Emit when all inputs have data at timestamp let ready: Vec<G::Timestamp> = buffers.iter() .filter(|(_, vecs)| vecs.iter().all(|v| !v.is_empty())) .map(|(t, _)| t.clone()) .collect(); for time in ready { if let Some(mut data_vecs) = buffers.remove(&time) { let mut session = output.session(&time); // Combine data from all inputs let combined: Vec<i32> = data_vecs.into_iter() .flat_map(|v| v) .collect(); session.give(combined); } } } }); stream } // Usage fn main() { use timely::dataflow::operators::{ToStream, Inspect}; timely::execute_from_args(std::env::args(), |worker| { worker.dataflow::<u64, _, _>(|scope| { let stream1 = (0..5).to_stream(scope); let stream2 = (10..15).to_stream(scope); barrier(vec![&stream1, &stream2]) .inspect(|data| println!("Barrier output: {:?}", data)); }); }).expect("Execution failed"); } ``` ### Pattern 2: Epoch Markers (Go) ```go package main import ( "fmt" "sync" "time" ) // EpochCoordinator manages epoch transitions across workers type EpochCoordinator struct { currentEpoch int64 numWorkers int barriers map[int64]*sync.WaitGroup mu sync.Mutex } func NewEpochCoordinator(numWorkers int) *EpochCoordinator { return &EpochCoordinator{ currentEpoch: 0, numWorkers: numWorkers, barriers: make(map[int64]*sync.WaitGroup), } } // ArriveAtEpoch signals that worker has reached epoch func (ec *EpochCoordinator) ArriveAtEpoch(workerID int, epoch int64) { ec.mu.Lock() defer ec.mu.Unlock() // Create barrier for this epoch if needed if _, exists := ec.barriers[epoch]; !exists { ec.barriers[epoch] = &sync.WaitGroup{} ec.barriers[epoch].Add(ec.numWorkers) } fmt.Printf("Worker %d arrived at epoch %d\n", workerID, epoch) ec.barriers[epoch].Done() } // WaitForEpoch blocks until all workers reach epoch func (ec *EpochCoordinator) WaitForEpoch(epoch int64) { ec.mu.Lock() barrier := ec.barriers[epoch] ec.mu.Unlock() if barrier != nil { barrier.Wait() fmt.Printf("Epoch %d complete\n", epoch) // Clean up old barriers ec.mu.Lock() delete(ec.barriers, epoch) ec.mu.Unlock() } } // AdvanceEpoch moves to next epoch func (ec *EpochCoordinator) AdvanceEpoch() int64 { ec.mu.Lock() defer ec.mu.Unlock() ec.currentEpoch++ return ec.currentEpoch } // Worker simulates dataflow worker with epoch coordination func worker(id int, coordinator *EpochCoordinator, wg *sync.WaitGroup) { defer wg.Done() for epoch := int64(0); epoch < 5; epoch++ { // Simulate work time.Sleep(time.Duration(id*100) * time.Millisecond) fmt.Printf("Worker %d processing epoch %d\n", id, epoch) // Signal arrival at epoch barrier coordinator.ArriveAtEpoch(id, epoch) // Wait for all workers to reach epoch coordinator.WaitForEpoch(epoch) } } func main() { numWorkers := 3 coordinator := NewEpochCoordinator(numWorkers) var wg sync.WaitGroup wg.Add(numWorkers) for i := 0; i < numWorkers; i++ { go worker(i, coordinator, &wg) } wg.Wait() fmt.Println("All workers complete") } ``` ### Pattern 3: Distributed Snapshot (Chandy-Lamport) ```python import threading import queue from dataclasses import dataclass from typing import Dict, List, Set from enum import Enum class MessageType(Enum): DATA = 1 MARKER = 2 @dataclass class Message: msg_type: MessageType data: any snapshot_id: int = 0 class SnapshotWorker: """Implements Chandy-Lamport snapshot algorithm""" def __init__(self, worker_id: int, neighbors: List[int]): self.worker_id = worker_id self.neighbors = neighbors self.state = {} # Local state self.channels: Dict[int, queue.Queue] = {} # Input channels # Snapshot state self.recording: Dict[int, bool] = {} # Per snapshot self.recorded_state: Dict[int, dict] = {} # Snapshot ID -> state self.recorded_messages: Dict[int, Dict[int, List]] = {} # Snapshot -> channel -> messages self.markers_received: Dict[int, Set[int]] = {} # Snapshot -> set of channels # Initialize channels for neighbor in neighbors: self.channels[neighbor] = queue.Queue() def start_snapshot(self, snapshot_id: int): """Initiate snapshot (only by coordinator)""" print(f"Worker {self.worker_id}: Starting snapshot {snapshot_id}") # Record local state self.recorded_state[snapshot_id] = self.state.copy() self.recording[snapshot_id] = True self.recorded_messages[snapshot_id] = {n: [] for n in self.neighbors} self.markers_received[snapshot_id] = set() # Send markers to all neighbors for neighbor in self.neighbors: self.send_message(neighbor, Message(MessageType.MARKER, None, snapshot_id)) def receive_marker(self, snapshot_id: int, from_channel: int): """Handle marker message""" if snapshot_id not in self.recording: # First marker for this snapshot print(f"Worker {self.worker_id}: Received first marker for snapshot {snapshot_id}") # Record state self.recorded_state[snapshot_id] = self.state.copy() self.recording[snapshot_id] = True self.recorded_messages[snapshot_id] = {n: [] for n in self.neighbors} self.markers_received[snapshot_id] = {from_channel} # Channel from which marker arrived is empty self.recorded_messages[snapshot_id][from_channel] = [] # Send markers to all neighbors for neighbor in self.neighbors: self.send_message(neighbor, Message(MessageType.MARKER, None, snapshot_id)) else: # Subsequent marker print(f"Worker {self.worker_id}: Received marker from channel {from_channel}") self.markers_received[snapshot_id].add(from_channel) # Check if snapshot complete if len(self.markers_received[snapshot_id]) == len(self.neighbors): self.finalize_snapshot(snapshot_id) def receive_data(self, data: any, from_channel: int): """Handle data message""" # Process data (update state) self.state[f'key_{len(self.state)}'] = data # Record message if snapshot in progress for snapshot_id, recording in self.recording.items(): if recording and from_channel not in self.markers_received.get(snapshot_id, set()): # Recording messages on this channel self.recorded_messages[snapshot_id][from_channel].append(data) def finalize_snapshot(self, snapshot_id: int): """Snapshot complete for this worker""" print(f"Worker {self.worker_id}: Snapshot {snapshot_id} complete") print(f" State: {self.recorded_state[snapshot_id]}") print(f" Messages: {self.recorded_messages[snapshot_id]}") self.recording[snapshot_id] = False def send_message(self, to_worker: int, message: Message): """Send message to another worker (simulated)""" # In real system, this would send over network pass # Example usage def main(): # Create 3 workers in ring topology: 0 -> 1 -> 2 -> 0 workers = [ SnapshotWorker(0, [1, 2]), SnapshotWorker(1, [0, 2]), SnapshotWorker(2, [0, 1]), ] # Simulate some processing workers[0].state = {'count': 10} workers[1].state = {'count': 20} workers[2].state = {'count': 30} # Initiate snapshot from worker 0 workers[0].start_snapshot(snapshot_id=1) # Simulate marker propagation workers[1].receive_marker(snapshot_id=1, from_channel=0) workers[2].receive_marker(snapshot_id=1, from_channel=1) workers[0].receive_marker(snapshot_id=1, from_channel=2) workers[1].receive_marker(snapshot_id=1, from_channel=2) workers[2].receive_marker(snapshot_id=1, from_channel=0) if __name__ == '__main__': main() ``` ### Pattern 4: Backpressure Coordination (Python) ```python import asyncio from dataclasses import dataclass from typing import Optional import time @dataclass class Watermark: """Progress indicator for backpressure""" timestamp: int worker_id: int class BackpressureCoordinator: """Coordinates flow control across pipeline stages""" def __init__(self, num_workers: int, buffer_size: int = 100): self.num_workers = num_workers self.buffer_size = buffer_size self.worker_watermarks = [0] * num_workers self.global_watermark = 0 self.lock = asyncio.Lock() async def update_watermark(self, worker_id: int, timestamp: int): """Update watermark for worker""" async with self.lock: self.worker_watermarks[worker_id] = timestamp old_global = self.global_watermark self.global_watermark = min(self.worker_watermarks) if self.global_watermark > old_global: print(f"Global watermark advanced to {self.global_watermark}") async def can_proceed(self, worker_id: int, timestamp: int) -> bool: """Check if worker can proceed without overwhelming downstream""" async with self.lock: # Allow if within buffer_size of slowest worker
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub