Raw capture โ raw/flare-db.md
url: https://github.com/flare-db/flare-db title: FlareDB author: Ganesh Sivakumar date_fetched: 2026-07-08 date_published: 2025
FlareDB is an Apache Beam-native streaming database written in Rust. It accepts Beam pipeline jobs (submitted via a Java SDK runner), fuses the pipeline graph into executable stages, and executes them either natively (for core transforms like Impulse and GroupByKey) or by delegating to a Java Beam SDK harness. PCollections are persisted in an embedded Arrow-native LSM-tree store (Tonbo).
Version: 0.1.8. License: Apache 2.0. Single author: Ganesh Sivakumar.
flare-db/
flaredb/ # core engine (Rust)
src/
main.rs # entry: starts 6 gRPC servers on 127.0.0.1:8099
lib.rs # module declarations, DEFAULT_API_SERVICE_URL
engine/
mod.rs
executor.rs # StageExecutor: BFS traversal of executable graph
store.rs # FlareElementStore, BeamRecord, Arrow schema derivations
coders.rs # Beam wire-format coders (varint, KV, iterable, windowed value)
harness/ # Beam Fn API servers
control.rs # FlareControlService: register/process bundle gRPC
data.rs # FlareDataService: element streaming with multiplexer
log.rs # FlareLogService
state.rs # FlareStateService
fusion/
fuser.rs # GreedyPipelineFuser: greedy fusion algorithm
pipeline.rs # QueryablePipeline, ExecutableGraph, FusedPipeline
stage.rs # ExecutableStage, CollectionConsumers, SiblingKey
refs.rs # SideInputRef, TimerRef, UserStateRef
transforms/
mod.rs # FlareTransform trait, from_urn() dispatch
impluse.rs # Impulse: produces single empty-byte[] element
gbk.rs # GroupByKey: in-memory HashMap-based grouping
jobservice/
server.rs # FlareJobService: Beam Job API gRPC implementation
job.rs # Job, JobStore, fuse_pipeline()
artifact.rs # ArtifactStore
urns.rs # All Beam URN constants
utils/
errors.rs # Error types
macros.rs # check_argument! macro
path.rs # Directory layout helpers
visualization.rs # DOT graph output
flare-cli/ # CLI tool (Rust)
src/main.rs # flare init/up/down commands
beam-model-rs/ # Generated protobuf Rust types for Beam model
runner-sdk/java/ # Java runner SDK (FlareRunner, FlarePipelineJob)
beam/model/ # Apache Beam protobuf definitions (vendored)
example/wordcount/ # WordCount example pipeline
Code size: flaredb/src ~4,000 lines of hand-written Rust; beam-model-rs ~17,000 lines of generated protobuf types; flare-cli ~630 lines total.
flare_up() starts 6 gRPC servers on a single tonic Server::builder():
All on one port (127.0.0.1:8099) โ a single-node deployment model.
FlareRunner.run(pipeline) translates Beam pipeline to proto, calls PrepareJob gRPCFlareJobService.prepare() receives pipeline proto, calls Job::new() which:
a. Builds a QueryablePipeline (petgraph-based graph of PTransform/PCollection nodes)
b. Runs GreedyPipelineFuser.fuse_pipeline() to group transforms into ExecutableStages
c. Builds an ExecutableGraph (DAG of Worker nodes + Runner nodes with ConsumerMetaData edges)FlareJobService.run() spawns a Java FnHarness process, waits for gRPC connection, then calls StageExecutor.execute_pipeline()Executes the ExecutableGraph in a BFS traversal:
Root (Runner: Impulse) โ Worker (fused SDK stages) โ Runner (GroupByKey) โ ...
For Worker nodes (SDK stages):
1. Register the ProcessBundleDescriptor with the harness via control channel
2. Send ProcessBundleRequest to start the bundle
3. Spawn a background task that reads the input PCollection from the store, encodes elements through the Beam coder pipeline, and sends them to the harness via the data channel
4. Spawn a concurrent task that receives output elements from the harness, decodes them, and writes to the store
5. tokio::select! between harness response and decode task completion, with 60s timeout
For Runner nodes (native transforms):
1. Build a ProcessBundleDescriptor with the transform's spec, PCollections, coders, etc.
2. Register the bundle with the harness
3. Call runner_transform.execute(ctx) directly โ reads from store, processes, writes back
GreedyPipelineFuser โ the most architecturally interesting module:
(PCollection, Environment) key. Within each key, sibling groups are formed where every member is mutually compatibleGreedyStageFuser grows a stage by walking forward through the pipeline graph, fusing compatible PCollections into the stage until it hits a materialization boundaryensure_single_producer() detects PCollections with multiple producers and injects synthetic Flatten transforms to merge partial outputsAfter fusion, the pipeline is a FusedPipeline with:
- sdk_stages: IndexSet<ExecutableStage> โ fused SDK transforms
- runner_stages: IndexSet<PTransformNode> โ runner-implemented transforms that break fusion
FlareElementStore is the core state layer:
fusio for async I/O, typed_arrow for Arrow integration)FlareSchemaRegistry (DashMap-backed) maps pcollection_id โ Arrow Schema. Schema is auto-derived from the first batch of records via derive_schema_from_records()BeamRecord enum with four variants:PRIMITIVE(PrimitiveValue) โ String, Bytes, Int64, Bool, VoidKV(BeamKV) โ key/value pairs (both primitives)GBK(BeamGbk) โ key + iterable value (post-GroupByKey)ITERABLE(IterableValue) โ list of primitivespcollection_id = <id> โ RecordBatches โ record_batch_to_beamrecords(){base_path}/{safe_pcollection_id}), with DB instances cached in a DashMapHand-written Rust implementations of Beam's standard wire-format coders:
KvCoder's value component is an IterableCoder, it's automatically promoted to GbkCoderEach Beam Fn API endpoint follows the same pattern:
Arc<Inner> with outgoing: Mutex<Option<Receiver>> and incoming: Mutex<Option<Streaming>>ReceiverStream) and stores the incoming streamsend_elements, register_bundle, etc.)The data channel is the most complex: it has an ElementStreamMultiplexer that routes elements from the harness back to the correct stage consumer by DataKey {instruction_id, transform_id}. A background task (stream_elements) drains the tonic stream and fans out to per-key unbounded channels.
Impulse (impluse.rs): Produces a single PrimitiveValue::Bytes(vec![]) element. This is the Beam root transform โ every pipeline starts here.
GroupByKey (gbk.rs): Scans the input PCollection (expecting KV records), builds a HashMap<PrimitiveValue, IterableValue> grouping values by key, and writes GBK records. Pure Rust, no harness involved. This is the critical optimization โ avoiding a shuffle through the harness.
Three commands:
- flare init โ Downloads FlareDB binary + Java harness JAR from GitHub releases, creates ~/.flaredb/{bin,instances}
- flare up โ Spawns FlareDB process with instance UUID, writes state.json, polls for port 8099
- flare down โ SIGTERM โ wait โ SIGKILL, removes state.json
FlareRunner extends Beam's PipelineRunner<FlarePipelineJob>:
1. Translates pipeline to proto via PipelineTranslation.toProto()
2. Sends PrepareJobRequest โ gets preparation_id + staging token
3. Stages artifacts via Beam's ArtifactStagingService
4. Sends RunJobRequest โ blocks until job completes
Greedy fusion with deduplication: The fusion algorithm mirrors the Beam portability framework's approach in Java but reimplemented in Rust with petgraph. The dedup pass injecting synthetic Flattens for multi-producer PCollections is a correctness mechanism often missed by naive fusion implementations.
PCollection-as-database: Unlike most Beam runners that hold PCollections in memory or on disk as opaque blobs, FlareDB stores each PCollection as a typed Arrow RecordBatch in an LSM-tree with schema derived from the actual element types. This means PCollections are queryable and survive beyond pipeline execution.
Schema auto-derivation: derive_schema_from_records() inspects the first batch of elements and determines the Arrow schema. It enforces type homogeneity across batches (e.g., no mixing String and Int64 elements in the same PCollection).
Dual execution model: Runner transforms execute directly in Rust (no serialization, no harness overhead), while SDK transforms go through the Java harness. The fusion algorithm treats runner transforms as materialization boundaries โ SDK transforms fuse until they hit a runner transform, then materialize.
WindowedValue wrapping: All elements crossing the runner-SDK boundary are wrapped in WindowedValue (timestamp + windows + pane info). Currently all windows are GlobalWindow, but the codec infrastructure supports general windowing.
todo!() stubs in the JobService gRPC implementation (GetJobs, GetState, Cancel, Drain, etc.)executed set, the pipeline state is inconsistenttonbo (=0.4.0-a1): Embedded LSM-tree database with Arrow supportarrow-* (57.3.1): Apache Arrow columnar formattonic (0.14.2): gRPC frameworktokio (1.38): Async runtimepetgraph (0.8.3): Graph data structures for pipeline DAGprost (0.14.3): Protobuf codecdashmap (6.1.0): Concurrent hash mapsbeam-model-rs (2.70.0): Generated Rust types for Beam protobufsbeam-sdks-java-harness-2.72.0-flare-bundled.jar: Bundled Java SDK harness (external, downloaded by CLI)