|
| 1 | +# Apache Doris Sink |
| 2 | + |
| 3 | +The Doris sink connector consumes JSON messages from Iggy streams and writes them to a pre-created Apache Doris table via Doris's [Stream Load HTTP API](https://doris.apache.org/docs/data-operate/import/import-way/stream-load-manual). |
| 4 | + |
| 5 | +## Requirements |
| 6 | + |
| 7 | +- The target Doris **database and table must be pre-created** before enabling the sink. The connector never issues DDL. |
| 8 | +- Messages must arrive with `Payload::Json` (i.e. the configured stream schema is `json`). Other payload types are skipped with a warning. |
| 9 | +- The Iggy message JSON shape must match the target table columns. Use the optional `columns` plugin setting if the column order differs from the JSON keys. |
| 10 | + |
| 11 | +## How it works |
| 12 | + |
| 13 | +1. For each batch of messages, the connector serializes the JSON payloads into a JSON array. |
| 14 | +2. It computes a deterministic Stream Load `label` of the form `{label_prefix}-{stream}-{topic}-{partition}-{first_offset}-{last_offset}`. Doris dedupes loads by label inside its `label_keep_max_second` window, so a replayed batch (after restart, retry, etc.) is silently absorbed instead of producing duplicates. |
| 15 | +3. It `PUT`s the batch to `{fe_url}/api/{database}/{table}/_stream_load` with HTTP Basic auth and the headers `format: json`, `strip_outer_array: true`, `label: <label>`. |
| 16 | +4. The Doris frontend (FE) responds with a `307 Temporary Redirect` to a backend (BE). The connector follows the redirect manually so that the `Authorization` header is preserved across the hop (`reqwest`'s default policy strips it on cross-host redirects). |
| 17 | +5. The HTTP body is parsed as JSON and the `Status` field decides the outcome: |
| 18 | + - `Success` → batch accepted. |
| 19 | + - `Label Already Exists` → idempotent replay, treated as success. |
| 20 | + - `Publish Timeout` or HTTP `5xx` → transient error (`Error::CannotStoreData`); the runtime can retry. |
| 21 | + - `Fail` or HTTP `4xx` → permanent error (`Error::PermanentHttpError`); retrying is not useful. |
| 22 | + |
| 23 | +## Configuration |
| 24 | + |
| 25 | +| Field | Required | Default | Description | |
| 26 | +| --- | --- | --- | --- | |
| 27 | +| `fe_url` | yes | — | Doris frontend HTTP base URL, e.g. `http://localhost:8030`. | |
| 28 | +| `database` | yes | — | Target database. | |
| 29 | +| `table` | yes | — | Target table. | |
| 30 | +| `username` | yes | — | Doris user with `LOAD_PRIV` on the table. | |
| 31 | +| `password` | yes | — | Doris user password. Stored as a `secrecy::SecretString` and never logged. | |
| 32 | +| `label_prefix` | no | `iggy` | Prefix for the deterministic Stream Load label. | |
| 33 | +| `batch_size` | no | `1000` | Maximum number of messages per Stream Load request. | |
| 34 | +| `timeout_secs` | no | `30` | Per-request HTTP timeout. | |
| 35 | +| `max_filter_ratio` | no | unset | Forwarded as the `max_filter_ratio` Stream Load header. | |
| 36 | +| `columns` | no | unset | Forwarded as the `columns` Stream Load header. | |
| 37 | +| `where` | no | unset | Forwarded as the `where` Stream Load header. | |
| 38 | + |
| 39 | +### Example |
| 40 | + |
| 41 | +```toml |
| 42 | +type = "sink" |
| 43 | +key = "doris" |
| 44 | +enabled = true |
| 45 | +version = 0 |
| 46 | +name = "Doris sink" |
| 47 | +path = "target/release/libiggy_connector_doris_sink" |
| 48 | +plugin_config_format = "toml" |
| 49 | + |
| 50 | +[[streams]] |
| 51 | +stream = "events" |
| 52 | +topics = ["doris_events"] |
| 53 | +schema = "json" |
| 54 | +batch_length = 100 |
| 55 | +poll_interval = "5ms" |
| 56 | +consumer_group = "doris_sink" |
| 57 | + |
| 58 | +[plugin_config] |
| 59 | +fe_url = "http://localhost:8030" |
| 60 | +database = "iggy_demo" |
| 61 | +table = "events" |
| 62 | +username = "root" |
| 63 | +password = "replace_with_secret" |
| 64 | +label_prefix = "iggy" |
| 65 | +batch_size = 1000 |
| 66 | +timeout_secs = 30 |
| 67 | +``` |
| 68 | + |
| 69 | +## Limitations (v1) |
| 70 | + |
| 71 | +- JSON payload only. CSV and raw-text payloads are not supported yet. |
| 72 | +- HTTP Basic auth only. |
| 73 | +- No automatic table creation. |
| 74 | +- No built-in retry middleware or circuit breaker — the runtime decides whether to redrive a failing batch. A hardening pass with `iggy_connector_sdk::retry::*` is planned as a follow-up. |
0 commit comments