Skip to main content

etl/
lib.rs

1//! High-performance, at-least-once ETL pipeline framework.
2//!
3//! `etl` is the facade crate for the `etl-rs` framework and the only crate
4//! applications need to depend on. The engine ([`etl_core`]) is re-exported
5//! at the root; connectors are enabled through cargo features:
6//!
7//! | Feature | Enables |
8//! |---|---|
9//! | `kafka` | [`kafka`] — Kafka source built on `rdkafka` (single consumer, partition-queue lanes) |
10//! | `clickhouse` | [`clickhouse`] — ClickHouse sink (RowBinary, dedup tokens, replica rotation) |
11//! | `clickhouse-uuid` | `uuid::Uuid` fields for `UUID` columns (`clickhouse::serde::uuid`) |
12//! | `clickhouse-chrono` | `chrono` fields for `Date`/`DateTime`/`DateTime64`/`Time` columns |
13//! | `clickhouse-time` | `time` crate fields for the same date/time columns |
14//! | `clickhouse-rust-decimal` | `rust_decimal::Decimal` conversions for `Decimal` columns |
15//! | `avro` | [`avro`] — Avro deserialization (Confluent wire format, schema registry) |
16//! | `avro-fast` | Opt-in `serde_avro_fast` decode backend for [`avro`]: single-pass typed decode, borrowed (zero-copy) records. Not part of `full` — see the `etl-avro` docs for the license note |
17//! | `full` | All connectors (`avro`, `kafka`, `clickhouse`) |
18//!
19//! # Anatomy of a pipeline
20//!
21//! One process runs one pipeline (see `docs/DESIGN.md` in the repository
22//! for the full architecture and its rationale):
23//!
24//! ```text
25//!                     ┌───────────────────────────────────────────────┐
26//!  pinned std thread  │ lane.poll → deserialize (borrowed) → operator │──try_send──▶ per-shard
27//!  (× N)              │ chain (map/filter/flat_map, monomorphized)    │             bounded queues
28//!                     └───────────────────────────────────────────────┘                  │
29//!                          ▲          full? pause lanes, keep polling                    ▼
30//!                          │                                            ┌───────────────────────────┐
31//!                     ┌──────────┐   acks (never block)                 │ sink workers: merge chunks,│
32//!                     │ source   │◀───────────────────────────────────── │ seal batches, rotate       │
33//!                     │ control  │   watermarks → store/commit          │ replicas, retry; admin     │
34//!                     └──────────┘                                      │ server (/metrics, probes)  │
35//!                                                                       └───────────────────────────┘
36//! ```
37//!
38//! Delivery is **at-least-once**: a batch's offsets commit only after every
39//! record derived from it was durably written (or intentionally dropped).
40//! Duplicates are possible after a crash — design target tables to
41//! tolerate replays.
42//!
43//! # A minimal pipeline
44//!
45//! Operators are stateful closures chained Flink/Streams-style; the YAML
46//! carries tuning and connector configuration; [`pipeline::Pipeline`]
47//! assembles and runs the process. This compiles and runs against the
48//! `etl-test` mocks — swap in `KafkaSource::from_component_config` and a
49//! ClickHouse sink for the production version (see
50//! `examples/kafka_avro_to_clickhouse.rs`):
51//!
52//! ```
53//! use etl::prelude::*;
54//! use etl_test::{BytesPassthrough, TestEncoder, capture_sink, memory_source};
55//!
56//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
57//! let config = PipelineConfig::from_str(
58//!     "pipeline: { name: demo, threads: 1, io_threads: 1 }\n\
59//!      checkpoint: { interval: 100ms }\n\
60//!      metrics: { exporter: none }\n\
61//!      source: { memory: {} }\n\
62//!      sink: { capture: {} }",
63//! )?;
64//! let (source, handle) = memory_source();
65//! let (sink, script) = capture_sink(1, 1);
66//!
67//! let runtime = Pipeline::from_config(config)?
68//!     .sink(sink)?
69//!     .chains(|ctx| {
70//!         chain_owned::<Vec<u8>, _>(BytesPassthrough)
71//!             .with_metrics(ctx.pipeline, "main")
72//!             .filter(|payload: &Vec<u8>| !payload.is_empty())
73//!             .sink(TestEncoder, KeyHashRouter, ChunkConfig::default(),
74//!                   ctx.queues, ctx.budget)
75//!             .build()
76//!     })
77//!     .runtime_options(RuntimeOptions { handle_signals: false, ..Default::default() })
78//!     .into_runtime(source)?;
79//!
80//! // Drive it: assign a lane, produce, wait for the durable commit.
81//! let shutdown = runtime.shutdown_handle();
82//! let join = std::thread::spawn(move || runtime.run());
83//! handle.assign_lanes(&[(etl::source::LaneId(0), PartitionId(0))]);
84//! let last = handle.push(PartitionId(0), None, b"hello");
85//! while handle.last_committed(PartitionId(0)) != Some(last + 1) {
86//!     std::thread::sleep(std::time::Duration::from_millis(5));
87//! }
88//! shutdown.trigger();
89//! let report = join.join().expect("join")?;
90//! assert_eq!(report.exit_code(), 0);
91//! assert!(!script.writes().is_empty());
92//! # Ok(())
93//! # }
94//! ```
95//!
96//! # Where things live
97//!
98//! - [`ops`] — the chain builder and operator combinators.
99//! - [`source`] / [`sink`] — the connector traits ([`source::Source`],
100//!   [`source::SourceLane`], [`sink::RowEncoder`], [`sink::ShardWriter`])
101//!   and the framework-owned sink pool.
102//! - [`pipeline`] — the runtime: pinned threads, controller, shutdown.
103//! - [`checkpoint`] / [`backpressure`] — acknowledgements and flow control.
104//! - [`config`] — YAML with `${VAR:-default}` interpolation and opaque
105//!   per-connector sections.
106//! - [`metrics`] / [`admin`] / [`telemetry`] — observability (the
107//!   [`metrics`](https://crates.io/crates/metrics) facade is the
108//!   instrumentation API; see `docs/METRICS.md` for the taxonomy).
109//! - Testing your pipelines: the `etl-test` crate (in-memory sources and
110//!   sinks with scripting handles).
111
112pub use etl_core::*;
113
114/// Curated imports for pipeline assemblies: one `use etl::prelude::*;`
115/// brings in the builder, chain entry points, and the types every
116/// assembly touches.
117///
118/// Connector constructors are deliberately excluded — import those from
119/// their feature-gated modules ([`kafka`], [`clickhouse`], [`avro`]).
120/// Additions to this module are semver-additive; nothing is ever removed.
121pub mod prelude {
122    pub use etl_core::config::PipelineConfig;
123    pub use etl_core::deser::{Owned, RecFamily};
124    pub use etl_core::error::ErrorPolicy;
125    pub use etl_core::ops::{ChunkConfig, chain, chain_owned};
126    pub use etl_core::pipeline::{
127        BuildError, ChainCtx, ExitReport, ExitState, Pipeline, PipelineError, PipelineRuntime,
128        RuntimeOptions, ShutdownHandle, SinkOptions,
129    };
130    pub use etl_core::record::PartitionId;
131    pub use etl_core::sink::{KeyHashRouter, SinkBundle, SinkParts, SinkPoolConfig};
132    pub use etl_core::telemetry::LogFormat;
133}
134
135/// Avro deserialization support (Confluent wire format, schema registry).
136#[cfg(feature = "avro")]
137pub use etl_avro as avro;
138
139/// Kafka source connector.
140#[cfg(feature = "kafka")]
141pub use etl_kafka as kafka;
142
143/// ClickHouse sink connector.
144#[cfg(feature = "clickhouse")]
145pub use etl_clickhouse as clickhouse;