Skip to main content

data-dataflow-coordination

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

Source facts

Repository
tools-only/X-Skills
Last source activity
March 1, 2026 at 03:37
Detected SKILL.md language
English
Stars
7
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
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
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub