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;