ArkFlow Documentation

repository·main·Indexed 23 days ago

https://github.com/arkflow-rs/arkflow

A high-performance Rust-based stream processing engine for cloud-native environments. ArkFlow supports multiple I/O sources like Kafka and MQTT, and provides data processing via SQL, Python, and VRL. It features a modular architecture with configurable processors (JSON, Protobuf, Arrow), buffer types for windowing and backpressure (memory, session, sliding, tumbling), and a CLI for component discovery and configuration validation.

Tokens
80.2K
Snippets
300
Records
526
Agent score
79%

What's inside ArkFlow

  1. Understand the ArkFlow Core Architecture and Capabilities

    main

    As of version 0.5.0, ArkFlow is a single-binary, configuration-based (YAML), and plugin-oriented stream processing engine built with Rust 1.97 and DataFusion 54.1.

    Key Technical Strengths:

    • Data Model: Uses Apache Arrow RecordBatch (MessageBatch), providing a columnar data model unlike row-based JSON engines.
    • SQL Processing: Powered by DataFusion 54, supporting aggregations, window functions, joins, UDFs, and temporary tables.
    • Input Durability: Features Input-level Write-Ahead Logging (WAL) providing at-least-once delivery, ack-gated cursors, crash recovery, and S3 backend support.
    • Extensibility: Supports Python UDFs (via PyO3) and VRL (Vector Remap Language) for scripting, alongside a wide range of plugins (14 inputs, 12 outputs, 6 processors, etc.).
    • CLI Support: Includes components list/show/schema commands to support IDE auto-completion.
  2. How Tumbling Window buffers work internally

    main

    The tumbling_window component is built on the BaseWindow component and uses the following mechanism:

    • Grouping: Messages are grouped by input name using an internal RwLock<HashMap<String, Arc<RwLock<VecDeque>>>> structure.
    • Triggering: A background timer using the Tokio async runtime triggers window processing at the configured interval.
    • Batching: When the interval elapses, messages are batched and concatenated using Arrow's concat_batches for high-performance processing.
    • Joins: If configured, SQL joins are performed via DataFusion with parallel decoding.
    • Safety: The component implements backpressure handling to prevent memory overflow and uses cancellation tokens for graceful shutdowns.
  3. Access Pulsar message metadata

    main

    The Pulsar input component automatically extracts metadata for every message. The following keys are available in the message payload:

    • __meta_topic: The source topic.
    • __meta_message_id: The unique Pulsar message ID.
    • __meta_publish_time: The timestamp when the message was published.
    • __meta_ingest_time: The timestamp when the message was ingested by ArkFlow.
  4. Use the schema_registry codec for Confluent Protobuf messages

    main

    The schema_registry codec is used to decode Protobuf messages that follow the Confluent wire format. It resolves the schema ID embedded in the message from a Schema Registry at runtime. It supports schema evolution by fetching and caching each schema version (ID) as it is encountered, allowing multiple versions to coexist in a single stream.

    When to use

    • When consuming Protobuf messages produced by Confluent serializers (the Kafka ecosystem standard).
    • In CDC or pipeline scenarios where source schemas evolve and producers register new versions to a Schema Registry.

    Wire Format

    The codec expects the following structure: [0x00 magic][4-byte big-endian schema id][Protobuf payload]

    The codec validates the magic byte, extracts the ID, resolves it to a schema via the registry, and then decodes the payload.

  5. Implementation of the backpressure signaling mechanism

    main

    The backpressure mechanism uses tokio::sync::Notify to transition from periodic polling (sleeping) to an event-driven model. This ensures that when the output stage advances the next_seq, the processor workers are notified immediately rather than waiting for a sleep timer to expire.

    Key Logic:

    • Trigger: The do_output component calls next_seq_notify.notify_one() immediately after incrementing next_seq via fetch_add(1, Release).
    • Waiting Pattern: Processors use a "check-then-await" pattern to minimize latency and avoid unnecessary blocking. They first acquire a notification future, check if the pending count is still above the threshold, and only then .await the notification.
    • Lifecycle: The next_seq_notify (an Arc<Notify>) is a field within the Stream struct, initialized during Stream::new and cloned to workers in run_inner.
  6. Configure the SQL input component

    main

    The SQL input component allows you to query data from various sources using SQL statements. It supports local file-based data sources (like Parquet, CSV, or JSON) and database connections (like MySQL, Postgres, or SQLite). You can also enable distributed computing via Ballista for large-scale queries.

    Core configuration keys:

    • select_sql: (Required) The SQL query string to execute.
    • input_type: (Required) An object specifying the source type and its specific connection or file details.
    • ballista: (Optional) Configuration for distributed execution using a Ballista server.
  7. Use Stateful Processors with Checkpointing and Recovery

    main

    For complex processing that requires memory of previous events (beyond simple windowing), ArkFlow supports stateful processors with periodic checkpointing. This prevents the need to recompute the entire stream from the beginning of the WAL after a crash.

    Key Concepts:

    • State Backend: An abstraction for storing processor state. Supported backends include in-memory and embedded redb (with future support for RocksDB).
    • Checkpointing: The system takes periodic snapshots of the processor state combined with the corresponding WAL sequence number. This checkpoint is aligned with the input-durability WAL cursor.
    • Recovery: Upon startup, the processor restores its state from the last valid checkpoint and then replays the stream starting from the corresponding WAL cursor.
    • Capabilities: processor-state-checkpoint.
    • Implementation: Located in crates/arkflow-core/src/state/ and crates/arkflow-core/src/processor/mod.rs (via a stateful trait).
  8. Achieve End-to-End Exactly-Once (EOS) via Output Idempotency

    main

    ArkFlow provides end-to-end Exactly-Once (EOS) semantics by focusing on idempotent or transactional writes at the output (sink) layer, rather than modifying the internal ack-linkage. This prevents duplicate processing after a crash recovery.

    How it works:

    1. Idempotent/Transactional Sinks: Sinks are adapted to handle duplicates. For example, Kafka uses transactional producers (where the transactional_id is explicitly configured), and JDBC uses upsert logic with a deduplication key.
    2. Mechanism: It reuses the existing ack-gated cursor. The flow is: Output write success $\rightarrow$ Cursor advances $\rightarrow$ Source commit. If a duplicate is sent during recovery, the sink's idempotency logic absorbs it.
    3. Capabilities: end-to-end-exactly-once.
    4. Implementation Details:
      • crates/arkflow-plugin/src/output/{kafka,sql}.rs: Handles transaction/upsert adaptation.
      • crates/arkflow-core/src/output/mod.rs: Defines the idempotency contract trait.
      • crates/arkflow-core/src/wal/: Exposes the cursor sequence to sinks for use as transaction IDs.
  9. CDC Offset Management via ack-gated Kafka offset

    main

    The debezium_json codec does not manage its own offsets or checkpoints. CDC position tracking is handled entirely by the existing Kafka input's ack-gated offset mechanism.

    Offsets are advanced via the input-durability component's ack-gated source-commit logic: when downstream writes are confirmed and the Kafka input's ack is triggered, the Kafka offset advances according to standard input-durability semantics.

  10. Use UPSERT with primary keys in SQL output

    main

    To enable UPSERT (Insert or Update) functionality, provide the primary_key configuration. This allows for idempotent writes by performing INSERT ... ON CONFLICT/DUPLICATE KEY UPDATE (PostgreSQL/MySQL) or INSERT OR REPLACE (SQLite) based on the specified key(s). You can use a single column or a composite key (array of columns).

    # PostgreSQL with UPSERT on a single ID
    - output:
        type: "sql"
        type: "postgresql"
        dsn: "postgresql://user:pass@localhost:5432/production"
        table: "metrics"
        primary_key: "id"
        batch_size: 1000
    
    # PostgreSQL with Composite Primary Key
    - output:
        type: "sql"
        type: "postgresql"
        dsn: "postgresql://user:pass@postgres:5432/app"
        table: "daily_stats"
        primary_key: ["device_id", "date"]
        batch_size: 200