K2I Whitepaper — Deep dive into the architecture

K2I: A High-Performance Streaming Ingestion Engine for Apache Iceberg

Version 1.0 | January 2026 OSO DevOps Engineering Team

Abstract

Modern data architectures face a fundamental tension between query latency and storage cost. Real-time streaming systems provide millisecond freshness but at significant infrastructure complexity and cost, while batch-oriented data lakes offer cost-efficient analytics but with minutes-to-hours of data delay. K2I (Kafka to Iceberg) resolves this trade-off through a novel single-process architecture that combines in-memory hot buffering with durable cold storage, delivering sub-second query freshness with the cost efficiency of columnar storage.

This whitepaper presents K2I's architecture, implementation details, and operational characteristics. K2I consumes events from Apache Kafka, buffers them in-memory using Apache Arrow for immediate queryability, and durably persists them to Apache Iceberg tables in Parquet format. A write-ahead transaction log ensures exactly-once semantics and crash recovery without data loss.

K2I is implemented in Rust for maximum performance and memory safety, supports multiple Iceberg catalog backends (REST, Hive Metastore, AWS Glue, Nessie), and includes automated maintenance operations for production deployments.

< 1s
Query Freshness
Zero
Data Loss
1 Binary
No Cluster Required

1. Introduction

The data lakehouse paradigm has emerged as the dominant architecture for modern analytical workloads, combining the flexibility of data lakes with the performance characteristics of data warehouses. Apache Iceberg has become the de facto open table format, providing ACID transactions, schema evolution, and time travel capabilities on top of commodity object storage.

However, ingesting streaming data into Iceberg tables presents significant challenges. Traditional batch ETL introduces unacceptable latency for real-time use cases, while naive streaming approaches create excessive small files that degrade query performance and increase metadata overhead.

K2I addresses these challenges through a purpose-built streaming ingestion engine that:

  • Minimizes data latency: Sub-second freshness via in-memory hot buffer
  • Optimizes file sizes: Intelligent buffering produces read-optimized Parquet files
  • Ensures data integrity: Exactly-once semantics through write-ahead logging
  • Reduces operational burden: Automated compaction, expiration, and cleanup
  • Maximizes performance: Native Rust implementation with zero-copy data paths

1.1 Inspiration

K2I draws inspiration from the Moonlink architecture developed by Mooncake Labs, which pioneered the concept of combining in-memory buffering with Iceberg's durable storage. K2I extends this approach with:

  • Kafka-native ingestion (vs. CDC-focused)
  • Multiple catalog backend support
  • Comprehensive maintenance automation
  • Production-grade observability

2. Problem Statement

2.1 The Latency-Cost Trade-off

Organizations face a fundamental tension when building data pipelines:

Approach Latency Cost Complexity
Real-time streaming (Kafka + KSQL) Milliseconds High High
Micro-batch (Spark Streaming) Seconds-Minutes Medium Medium
Batch ETL (Airflow + Spark) Minutes-Hours Low Low

Real-time systems require maintaining separate infrastructure, duplicating storage, and managing complex stateful processing. Batch systems are simpler but cannot serve time-sensitive use cases.

2.2 The Small File Problem

Streaming data into Iceberg creates a cascade of problems:

  1. File proliferation: Each micro-batch creates new files, potentially thousands per hour
  2. Metadata explosion: Each file requires manifest entries, increasing planning time
  3. Query degradation: Small files increase I/O operations and reduce scan efficiency
  4. Storage overhead: Per-file metadata and object storage API costs accumulate

2.3 Exactly-Once Challenges

Achieving exactly-once semantics in streaming systems requires coordination between:

  • Message consumption acknowledgment
  • State persistence
  • External system commits

Failures at any point can result in data loss or duplication, requiring complex distributed transactions or idempotency mechanisms.

2.4 Operational Complexity

Production streaming pipelines require ongoing maintenance:

  • File compaction to optimize query performance
  • Snapshot expiration to manage metadata growth
  • Orphan file cleanup from failed operations
  • Schema evolution handling
  • Monitoring and alerting

3. Design Philosophy

K2I is built on four foundational principles:

3.1 Single-Process Simplicity

Unlike distributed streaming frameworks (Flink, Spark Streaming), K2I runs as a single process per topic partition. This eliminates:

  • Distributed coordination overhead (ZooKeeper, consensus protocols)
  • Network serialization between processing stages
  • Complex failure recovery across nodes
  • Operational complexity of cluster management

Single-process design provides deterministic behavior, simpler debugging, and predictable resource consumption.

3.2 Hot/Cold Data Separation

K2I maintains two storage tiers:

Hot Tier (In-Memory)

  • Apache Arrow columnar format
  • Hash indexes for O(1) key lookup
  • Sub-millisecond query latency
  • Bounded by configurable memory limit

Cold Tier (Object Storage)

  • Apache Parquet files on S3/GCS/Azure
  • Optimized for analytical queries
  • Unlimited retention
  • Cost-efficient at scale

This separation allows immediate data availability while accumulating sufficient data for optimal file sizes.

3.3 Transaction Log Durability

All state-changing operations are recorded in an append-only transaction log before execution. This provides:

  • Crash recovery: Reconstruct state from log replay
  • Exactly-once semantics: Idempotency through operation tracking
  • Audit trail: Complete history of data mutations
  • Debugging: Forensic analysis of failures

3.4 Defensive Design

K2I assumes failures will occur and designs for resilience:

  • Backpressure propagation: Pause consumption when buffer is full
  • Circuit breakers: Prevent cascading failures to external systems
  • Graceful degradation: Continue operating with reduced functionality
  • Automatic recovery: Self-heal from transient failures

4. System Architecture

4.1 High-Level Overview

+-----------------------------------------------------------------------------+
|                           K2I Ingestion Engine                              |
+-----------------------------------------------------------------------------+
|                                                                             |
|   +------------------+    +------------------+    +------------------+      |
|   | SmartKafka       |    |    Hot Buffer    |    | Iceberg Writer   |      |
|   | Consumer         |--->| (Arrow + Index)  |--->| (Parquet)        |      |
|   |                  |    |                  |    |                  |      |
|   | - rdkafka        |    | - RecordBatch    |    | - Catalog        |      |
|   | - Backpressure   |    | - DashMap Index  |    | - Object Store   |      |
|   | - Retry Logic    |    | - TTL Eviction   |    | - Atomic Commit  |      |
|   +------------------+    +------------------+    +------------------+      |
|            |                       |                       |               |
|            v                       v                       v               |
|   +---------------------------------------------------------------------+  |
|   |                      Transaction Log                                |  |
|   | - Append-only entries with CRC32 checksums                          |  |
|   | - Periodic checkpoints for fast recovery                            |  |
|   | - Idempotency records for exactly-once semantics                    |  |
|   +---------------------------------------------------------------------+  |
|                                                                             |
+-----------------------------------------------------------------------------+

4.2 Component Responsibilities

Component Responsibility
SmartKafkaConsumerMessage ingestion with backpressure and retry
HotBufferIn-memory storage with indexes and eviction
IcebergWriterParquet generation and catalog commits
TransactionLogDurability and recovery coordination
TransactionCoordinatorAtomic commits with CAS semantics
MaintenanceSchedulerBackground optimization tasks

4.3 Technology Stack

K2I is implemented entirely in Rust, leveraging:

Library Purpose
rdkafkaKafka consumer (librdkafka bindings)
arrowIn-memory columnar storage
parquetFile format encoding
icebergTable format operations
tokioAsync runtime
axumHTTP servers
prometheusMetrics exposition

5. Core Components

5.1 Smart Kafka Consumer

The SmartKafkaConsumer wraps rdkafka with production-grade features:

5.1.1 Batch Processing

Messages are collected into batches before processing:

pub struct KafkaBatch {
    messages: Vec<KafkaMessage>,
    partition_offsets: HashMap<i32, i64>,
    timestamp_range: (DateTime<Utc>, DateTime<Utc>),
}

Batching amortizes processing overhead and enables efficient Arrow conversion.

5.1.2 Backpressure Control

When the hot buffer reaches capacity, the consumer pauses:

+-------------+     BufferFull     +-------------+
|  Consuming  |------------------>|   Paused    |
|             |                   |             |
|  (polling)  |<------------------|  (waiting)  |
+-------------+     BufferSpace    +-------------+

This prevents memory exhaustion while maintaining offset consistency.

5.1.3 Retry Strategy

Transient failures trigger exponential backoff:

Delay = min(max_delay, base_delay × 2^attempt) + jitter

Default configuration:
  base_delay:   100ms
  max_delay:    30s
  max_attempts: 10
  jitter:       0-10% random

5.2 Hot Buffer

The hot buffer provides sub-millisecond access to recent data.

5.2.1 Data Structure

pub struct HotBuffer {
    // Columnar storage
    schema: SchemaRef,
    builders: RwLock<ColumnBuilders>,
    records: RwLock<Vec<BufferedRecord>>,

    // O(1) lookup indexes
    key_index: DashMap<Vec<u8>, RowId>,
    offset_index: DashMap<(i32, i64), RowId>,

    // Management
    stats: BufferStats,
    config: BufferConfig,
}

5.2.2 Index Design

Index Key Value Use Case
key_index Message key bytes Row ID Lookup by business key
offset_index (partition, offset) Row ID Offset-based queries

Both indexes use DashMap for lock-free concurrent access.

5.2.3 Flush Triggers

The buffer flushes when any condition is met:

Trigger Configuration Default Rationale
Time flush_interval_seconds 30s Ensure freshness SLA
Size max_size_mb 500MB Prevent OOM
Count flush_batch_size 10,000 Optimal file size

5.3 Iceberg Writer

The Iceberg writer handles the critical path of durable persistence.

5.3.1 Write Pipeline

Arrow RecordBatch
        |
        v
+-------------------+
| Parquet Encoder   |
| - Row group: 128K |
| - Compression     |
| - Statistics      |
+-------------------+
        |
        v
+-------------------+
| Object Storage    |
| - Multipart upload|
| - Retry on failure|
| - Checksum verify |
+-------------------+
        |
        v
+-------------------+
| Catalog Commit    |
| - CAS semantics   |
| - Conflict retry  |
| - Snapshot create |
+-------------------+

5.3.2 File Naming Convention

{warehouse}/{database}/{table}/
  event_date={YYYY-MM-DD}/
    kafka_partition={N}/
      part-{uuid}-{offset_min}-{offset_max}.parquet

This structure enables partition pruning by date, Kafka partition isolation for replay, and offset range identification for debugging.


6. Data Flow & Processing Pipeline

6.1 Write Path

The write path processes messages from Kafka to the hot buffer:

Step 1: Poll
+-------------+
|   Kafka     |---- poll() -----> Vec<RawMessage>
+-------------+

Step 2: Batch
+-------------+
|   Batcher   |---- collect() --> KafkaBatch {messages, offsets}
+-------------+

Step 3: Convert
+-------------+
|   Arrow     |---- convert() --> RecordBatch
+-------------+

Step 4: Buffer
+-------------+
| Hot Buffer  |---- append() ---> Updated indexes
+-------------+

Step 5: Log
+-------------+
| Transaction |---- write() ----> OffsetMarker entry
|    Log      |
+-------------+

Latency: < 10ms end-to-end for buffering

6.2 Flush Path

The flush path persists buffered data to Iceberg. Latency: 200-800ms typical flush cycle

6.3 Query Path

The hot buffer supports immediate queries:

// By message key
buffer.query_by_key("user-123") -> Option<KafkaMessage>

// By partition and offset
buffer.query_by_offset(partition=0, offset=12345) -> Option<KafkaMessage>

// Range query
buffer.query_range(partition=0, start=12000, end=12100) -> Vec<KafkaMessage>

Latency: < 1ms for indexed lookups


7. Exactly-Once Semantics

K2I provides exactly-once delivery guarantees through coordinated transaction management.

7.1 The Challenge

Exactly-once requires atomicity across three systems:

  1. Kafka: Consume and commit offsets
  2. Object Storage: Upload files
  3. Iceberg Catalog: Commit metadata

Failure at any point must not result in data loss or duplication.

7.2 Solution: Write-Ahead Logging + Idempotency

All operations are logged before execution:

FlushStart(batch_id=abc, rows=1000)
  └── If crash here: recover knows flush was attempted

ParquetWritten(file=part-001.parquet, size=50MB)
  └── If crash here: file exists but not committed

IcebergSnapshot(snapshot_id=123)
  └── If crash here: snapshot exists, mark complete

FlushComplete(batch_id=abc, snapshot=123)
  └── Safe to commit Kafka offset

7.3 Guarantees

Scenario Outcome
Crash before FlushStartNo data loss, resume from last offset
Crash after ParquetWrittenFile exists, will be cleaned as orphan
Crash after IcebergSnapshotData committed, will skip on recovery
Crash after FlushCompleteFully committed, Kafka offset safe

8. Crash Recovery

K2I automatically recovers from crashes without data loss or duplication.

8.1 Recovery Process

Startup
    |
    v
+---------------------------------+
| 1. Load last checkpoint         |
|    - Last Kafka offsets         |
|    - Last Iceberg snapshot      |
+---------------------------------+
    |
    v
+---------------------------------+
| 2. Replay log since checkpoint  |
|    - Process each entry         |
|    - Track incomplete flushes   |
+---------------------------------+
    |
    v
+---------------------------------+
| 3. Execute cleanup              |
|    - Delete orphan files        |
|    - Log cleanup actions        |
+---------------------------------+
    |
    v
+---------------------------------+
| 4. Resume normal operation      |
|    - Seek to starting offsets   |
|    - Begin consuming            |
+---------------------------------+

9. Performance Characteristics

9.1 Latency Profile

Operation P50 P99 Notes
Kafka poll2ms10msNetwork dependent
Buffer append0.1ms0.5msCPU bound
Arrow → Parquet20ms100msBatch size dependent
Object upload100ms500msFile size dependent
Catalog commit50ms200msCatalog dependent
Total flush200ms800msEnd-to-end

9.2 Query Freshness

Data State Availability Latency
Hot bufferImmediate< 1ms
Cold storageAfter flush30s default
After compactionFull optimizedVariable

9.3 Throughput

Configuration Throughput Notes
Default10K msg/sConservative settings
Tuned50K msg/sLarger batches, LZ4
Maximum100K+ msg/sDedicated hardware

9.3.1 Throughput Tuning

# High throughput configuration
[kafka]
batch_size = 5000
batch_timeout_ms = 1000

[buffer]
max_size_mb = 2000
flush_batch_size = 50000

[iceberg]
compression = "lz4"
target_file_size_mb = 256

9.4 Memory Consumption

Component Typical Maximum Configuration
Hot buffer200MB2GBbuffer.max_size_mb
Indexes20MB200MB~10% of buffer
Arrow batches10MB100MBPer batch
Transaction log5MB50MBAutomatic
Total235MB2.35GB

10. Maintenance Operations

K2I includes automated maintenance to ensure long-term health.

10.1 Compaction

Compaction merges small files into larger ones:

Before:
├── part-001.parquet (50MB)
├── part-002.parquet (30MB)
├── part-003.parquet (40MB)
├── part-004.parquet (60MB)
└── part-005.parquet (20MB)

After compaction:
├── compacted-001.parquet (200MB)

10.1.1 Configuration

[maintenance]
compaction_enabled = true
compaction_interval_seconds = 3600    # Every hour
compaction_threshold_mb = 100         # Files smaller than this
compaction_target_mb = 512            # Target output size

10.2 Snapshot Expiration

Removes old snapshots to control metadata growth.

[maintenance]
snapshot_expiration_enabled = true
snapshot_retention_days = 7

10.3 Orphan File Cleanup

Removes unreferenced files from storage with safety measures:

  • Retention buffer: Only delete files older than orphan_retention_days (default: 3)
  • Reference check: Verify file not in any snapshot
  • Modified time: Don't delete recently modified files

11. Catalog Integration

K2I supports multiple Iceberg catalog backends through a unified interface.

11.1 REST Catalog

[iceberg]
catalog_type = "rest"
rest_uri = "http://catalog.example.com:8181"

[iceberg.rest]
credential_type = "oauth2"
oauth2_token_endpoint = "https://auth.example.com/token"
oauth2_client_id = "k2i-client"

11.2 Hive Metastore

[iceberg]
catalog_type = "hive"
hive_metastore_uri = "thrift://hive-metastore:9083"

11.3 AWS Glue

[iceberg]
catalog_type = "glue"
aws_region = "us-east-1"

[iceberg.glue]
role_arn = "arn:aws:iam::123456789012:role/K2IRole"

11.4 Nessie

[iceberg]
catalog_type = "nessie"
nessie_uri = "http://nessie:19120/api/v1"

[iceberg.nessie]
default_branch = "main"

12. Observability

K2I provides comprehensive observability through metrics, health checks, and structured logging.

12.1 Prometheus Metrics

Counters

Metric Labels Description
k2i_messages_totaltopic, partitionMessages consumed
k2i_bytes_totaltopic, partitionBytes consumed
k2i_flushes_totalstatusFlush operations
k2i_errors_totaltypeErrors by category

Gauges

Metric Description
k2i_buffer_size_bytesCurrent buffer memory
k2i_buffer_record_countCurrent buffered records
k2i_kafka_consumer_lagConsumer lag

12.2 Health Checks

Endpoint Response Use Case
GET /healthFull JSONDebugging, dashboards
GET /healthz200/503Kubernetes liveness
GET /readyz200/503Kubernetes readiness

Health Response Example

{
  "status": "healthy",
  "components": {
    "kafka": { "status": "healthy", "message": "Connected to 3 brokers" },
    "buffer": { "status": "healthy", "message": "45% capacity" },
    "iceberg": { "status": "healthy", "message": "Last commit 5s ago" }
  },
  "uptime_seconds": 3600,
  "version": "0.1.0"
}

13. Deployment Models

13.1 Docker

FROM ghcr.io/osodevops/k2i:latest

COPY config.toml /etc/k2i/config.toml

HEALTHCHECK --interval=30s --timeout=5s \
  CMD curl -f http://localhost:8080/healthz || exit 1

EXPOSE 8080 9090

ENTRYPOINT ["k2i", "ingest", "--config", "/etc/k2i/config.toml"]

13.2 Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: k2i
spec:
  replicas: 1  # Single process per partition set
  template:
    spec:
      containers:
      - name: k2i
        image: ghcr.io/osodevops/k2i:v0.1.0
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "2000m"
        ports:
        - containerPort: 8080
          name: health
        - containerPort: 9090
          name: metrics
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
        readinessProbe:
          httpGet:
            path: /readyz
            port: 8080

13.3 Scaling Considerations

Scenario Approach
Multiple topicsDeploy one K2I instance per topic
High-throughput topicDeploy one K2I instance per partition subset
High availabilityActive-passive with shared transaction log storage

14. Comparison with Alternatives

14.1 Feature Matrix

Feature K2I Spark Streaming Flink Kafka Connect
Single processYesNoNoPer-connector
Sub-second latencyYesMinutesSecondsSeconds
Exactly-onceYesYesYesDepends
Auto compactionYesNoNoNo
Hot buffer queriesYesNoNoNo
Memory footprintLowHighHighMedium
Operational complexityLowHighHighMedium

14.2 Use Case Fit

Use Case Best Choice Rationale
Simple Kafka→IcebergK2IMinimal complexity
Complex transformationsFlinkStream processing capabilities
ML feature pipelinesSparkML library ecosystem
CDC replicationMoonlinkDelete handling

14.3 Performance Comparison

Metric K2I Spark Micro-batch Flink
Latency (P50)200ms30s+1-5s
Memory per partition50-200MB1GB+500MB+
Files per hour1-10100+10-50
Setup timeMinutesHoursHours

15. Future Directions

15.1 Planned Features

Feature Description
Schema registry integrationAvro/Protobuf with Confluent SR
Multi-table supportSingle process, multiple destination tables
Delete handlingRow-level deletes via deletion vectors
Query APIREST API for hot buffer queries

15.2 Research Areas

  • Adaptive flush triggers: ML-based flush timing optimization
  • Tiered hot buffer: NVMe-backed overflow for larger buffers
  • Distributed mode: Coordinated multi-process deployment
  • Change data capture: Direct database CDC support

16. Conclusion

K2I represents a new approach to streaming data ingestion that prioritizes simplicity without sacrificing capability. By combining single-process architecture with intelligent buffering and comprehensive durability mechanisms, K2I delivers:

  • Sub-second data freshness through in-memory hot buffering
  • Cost-efficient storage via optimized Parquet files on object storage
  • Data integrity with exactly-once semantics and crash recovery
  • Operational simplicity through automated maintenance
  • Production readiness with comprehensive observability

K2I is ideal for organizations seeking to bridge the gap between real-time streaming and batch analytics without the complexity of distributed stream processing frameworks.


17. References

  1. Apache Iceberg Specification. https://iceberg.apache.org/spec/
  2. Apache Arrow Columnar Format. https://arrow.apache.org/docs/format/Columnar.html
  3. Apache Parquet Format. https://parquet.apache.org/docs/
  4. Mooncake Labs - Moonlink. https://www.mooncake.dev/moonlink/
  5. rdkafka - Rust Kafka Client. https://github.com/fede1024/rust-rdkafka
  6. Iceberg Rust Implementation. https://github.com/apache/iceberg-rust

Ready to get started with K2I?

Deploy real-time streaming ingestion for Apache Iceberg in minutes.