Moonlink Managed Ingestion Engine

repository·main·Indexed 43 days ago

https://github.com/mooncake-labs/moonlink

A managed ingestion engine for Apache Iceberg that enables streaming inserts and upserts. Moonlink prevents the 'small file problem' and metadata churn in lakehouses by buffering data in Arrow format and using deletion vectors. It supports ingestion via PostgreSQL CDC and REST API, and commits data as Iceberg v3 tables compatible with DuckDB, Apache Spark, and Postgres.

Tokens
34.8K
Snippets
114
Records
152
Agent score
56%

What's inside Moonlink

  1. Moonlink ingestion sources and read paths

    main

    Supported Ingestion Sources (Write Paths)

    • PostgreSQL CDC: Via logical replication (millisecond latency).
    • REST API: HTTP endpoint for direct event ingestion.
    • Kafka: Sink support is currently in the roadmap.
    • OTEL: Sink support is currently on the roadmap.

    Supported Query Engines (Read Path)

    Moonlink commits data as Iceberg v3 tables with deletion vectors, which can be queried by:

    • DuckDB
    • Apache Spark
    • Postgres (using pg_duckdb or pg_mooncake extensions)

    Real-Time Reads (Sub-second freshness)

    For workloads requiring immediate visibility, use these specific extensions:

    • DuckDB: via duckdb_mooncake extension.
    • Postgres: via pg_mooncake extension.
    • DataFusion: via Moonlink Datafusion.
  2. Understand the Moonlink Chaos Test suite

    main

    The Moonlink chaos test suite is a stress-testing tool designed to validate the core engine's persistence and snapshot semantics. It simulates adversarial event sequences (appends, updates, deletes, and transaction types) interleaved with maintenance operations (index merges, data compaction, and snapshots) across various backends like FS, S3, and GCS.

    It specifically validates:

    • Causality & ordering: Ensuring LSNs strictly increase and transaction boundaries (Begin/End) are respected.
    • State consistency: Ensuring only committed changes affect snapshots.
    • Persistence fidelity: Verifying that a snapshot at a specific LSN can be reloaded to exactly match the in-memory state.
    • Maintenance safety: Ensuring maintenance runs only during quiescent periods.
  3. How Moonlink optimizes Iceberg writes

    main

    Moonlink is an ingestion engine designed to prevent the common issues of real-time Iceberg ingestion, such as tiny data files and metadata explosion. It achieves this through two primary write paths:

    1. Inserts: Incoming raw inserts are buffered in Arrow format and then flushed as size-tuned Parquet files to Iceberg.
    2. Deletes: Deletes are mapped to deletion vectors using an index built on row positions, rather than creating expensive equality-deletes.

    By buffering, caching, and indexing data on NVMe, Moonlink ensures that Iceberg tables remain read-optimized without requiring manual compaction jobs.

  4. Run Moonlink benchmarks with profiling

    main

    To generate profiling data for a benchmark, use the --profile-time flag passed through cargo to the underlying benchmarking tool. This allows you to generate flamegraphs to analyze performance bottlenecks.

    After running the command, the resulting flamegraph can be found in target/criterion/TEST_NAME/profile, where TEST_NAME is the name of the benchmark you ran.

    cargo bench --bench microbench_write_mooncake_table --features='bench' -- --profile-time=5
  5. Verify Moonlink deployment status and connectivity

    main

    After deployment, you can verify the health and connectivity of the Moonlink service by port-forwarding to the moonlink-service in the moonlink namespace.

    Test Health Endpoint (Port 3030)

    kubectl port-forward -n moonlink svc/moonlink-service 3030:3030
    curl 127.0.0.1:3030/health

    Test Connectivity (Port 3031)

    kubectl port-forward -n moonlink svc/moonlink-service 3031:3031
    nc -vz 127.0.0.1 3031

    Test Web Interface (Port 8080)

    To test the web interface, port-forward port 8080, then exec into the nginx container within the pod to set a test index page.

    kubectl port-forward -n moonlink svc/moonlink-service 8080:8080
    # Obtain pod name
    kubectl get pods -l app=moonlink-dev -n moonlink -o name
    # Replace <pod name> with the actual name obtained above
    kubectl exec -n moonlink -it <pod name> -c nginx -- sh -lc 'echo "<h1>OK</h1>" > /usr/share/nginx/html/index.html'
    curl 127.0.0.1:8080
    # Test health endpoint on port 3030
    kubectl port-forward -n moonlink svc/moonlink-service 3030:3030
    curl 127.0.0.1:3030/health
  6. Create an Iceberg table in Moonlink

    main

    You can create a new Iceberg table by sending a POST request to the /tables/{table_name} endpoint. The request body must include the database, table name, a schema array defining fields, and an optional table_config.

    Schema Field Types (based on example): int32, string, date32.

    Table Configuration: The table_config object can include a mooncake key, such as {"append_only": true}.

    curl -X POST http://localhost:3030/tables/users \
      -H "Content-Type: application/json" \
      -d '{
        "database": "my_database",
        "table": "users",
        "schema": [
          {"name": "id", "data_type": "int32", "nullable": false},
          {"name": "name", "data_type": "string", "nullable": false},
          {"name": "email", "data_type": "string", "nullable": true},
          {"name": "age", "data_type": "int32", "nullable": true},
          {"name": "created_at", "data_type": "date32", "nullable": true}
        ],
        "table_config": {"mooncake": {"append_only": true}}
      }'
  7. Run Moonlink microbenchmarks

    main

    Moonlink provides microbenchmarks to measure the performance of its core components. You can run all benchmarks or target a specific benchmark using cargo bench. Note that the bench feature must be enabled.

    # Run all benchmarks
    cargo bench --features='bench'
    
    # Run a specific benchmark (e.g., microbench_write_mooncake_table)
    cargo bench --bench microbench_write_mooncake_table --features='bench'
  8. Deploy Moonlink to a local kind cluster

    main

    To deploy Moonlink to a local Kubernetes cluster using kind, ensure you have Docker, kind, kubectl, and yq installed. Use the provided setup script to automate cluster creation, image loading, and manifest application.

    Prerequisites Verification:

    docker --version && kind --version && kubectl version --client=true --output=yaml && yq --version

    Deployment Steps:

    1. Run the setup script to create/ensure the cluster and namespace, build/load the moonlink:dev image, and apply manifests.
    2. Verify the deployment status using kubectl.
    # 1) Create/ensure cluster & namespace, build & load image, deploy manifests, wait for rollout
    ./deploy/kind/local_setup_script.sh
    
    # 2) Verify deployment status
    kubectl get pods,svc -n moonlink
    # 1) Create/ensure cluster & namespace, build & load image, deploy manifests, wait for rollout
    ./deploy/kind/local_setup_script.sh
    
    # 2) Verify deployment status
    kubectl get pods,svc -n moonlink
  9. Quick Start: Install and Run Moonlink

    main

    To get started with Moonlink, you need to build the service from source and run it with a local data directory.

    1. Clone and Build: Clone the repository and use cargo to build the moonlink_service binary.
    2. Start Service: Run the binary and provide a path for data storage (e.g., ./data).
    3. Verify: Use curl to check the /health endpoint to ensure the service is running.
    # 1. Clone & Build
    git clone https://github.com/Mooncake-Labs/moonlink.git
    cd moonlink
    cargo build --release --bin moonlink_service
    
    # 2. Start the Moonlink Service
    ./target/release/moonlink_service ./data
    
    # 3. Verify Service Health
    curl http://localhost:3030/health
  10. Clean up Moonlink kind deployment

    main

    Use the cleanup.sh script to remove Moonlink resources. By default, it only deletes the resources defined in the manifests. You can use environment variables to perform a more thorough cleanup.

    Standard Cleanup (Resources only)

    ./deploy/kind/cleanup.sh

    Advanced Cleanup

    • Delete the entire kind cluster: Set NUKE_CLUSTER=true.
    • Delete the moonlink namespace: Set NUKE_NAMESPACE=true.
    # Delete the entire kind cluster
    NUKE_CLUSTER=true ./deploy/kind/cleanup.sh
    
    # Delete the 'moonlink' namespace
    NUKE_NAMESPACE=true ./deploy/kind/cleanup.sh
    # Delete the entire kind cluster
    NUKE_CLUSTER=true ./deploy/kind/cleanup.sh
    
    # Delete the 'moonlink' namespace
    NUKE_NAMESPACE=true ./deploy/kind/cleanup.sh
  11. Insert data into a Moonlink table

    main

    To ingest data, send a POST request to the /ingest/{table_name} endpoint. The payload requires an operation (e.g., insert), a request_mode (e.g., async), and a data object containing the row values matching the table schema.

    curl -X POST http://localhost:3030/ingest/users \
      -H "Content-Type: application/json" \
      -d '{
        "operation": "insert",
        "request_mode": "async",
        "data": {
          "id": 1,
          "name": "Alice Johnson",
          "email": "alice@example.com",
          "age": 30,
          "created_at": "2024-01-01"
        }
      }'
  12. Reproduce chaos test runs using seeds

    main

    To ensure reproducibility of a specific sequence of events, use the --seed flag. If no seed is provided, the suite uses a timestamp-derived default. Providing a specific u64 seed allows you to recreate the exact same sequence of operations to debug failures.

    To see the events even when the test passes, use the --print-events-on-success flag.

    # Example of running with a specific seed for reproducibility
    ./chaos_test_binary --seed 123456789