feat(connectors): add OpenDAL sink - #4123
George-Miao wants to merge 4 commits into
Conversation
|
Thanks for the PR. It is labeled Slash commands (own line, regular comment) move it around the queue:
See CONTRIBUTING.md for details. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #4123 +/- ##
============================================
- Coverage 87.48% 87.39% -0.10%
- Complexity 1575 1578 +3
============================================
Files 1280 1283 +3
Lines 223192 223851 +659
Branches 186555 187214 +659
============================================
+ Hits 195267 195633 +366
- Misses 23229 23479 +250
- Partials 4696 4739 +43
🚀 New features to boost your workflow:
|
mattp5657
left a comment
There was a problem hiding this comment.
2 things:
-
This connector is missing from the release/version-bump tooling. Absent from scripts/bump-version.sh, the connector_plugins default in _build_rust_artifacts.yml, and the connector list in edge-release.yml. It'll pass CI but won't ship in edge or tagged release artifacts, and a future version bump will silently skip it. Not unique to this PR (a handful of existing sinks are missing from these same lists too), but worth fixing here or in an immediate follow-up rather than adding a fourth thing to that pile.
-
I think we could use more unit and integration test coverage, happy and unhappy paths for every function. Untested today: path.rs's render_template and sanitize_path_segment beyond one edge case, lib.rs's consume()/write_message() unhappy path, the retry policy past one attempt, and S3/GCS/Azure end-to-end (fs is the only backend any test exercises)
hubcio
left a comment
There was a problem hiding this comment.
the plugin does not reach release artifacts yet: _build_rust_artifacts.yml:49 and edge-release.yml:108 do not list it, scripts/bump-version.sh:90 omits the crate, and the sinks table in core/connectors/sinks/README.md has no row. you noted that the CI part is a follow-up PR - can you confirm it covers all four?
| .replace("{timestamp}", ×tamp_millis)) | ||
| } | ||
|
|
||
| fn sanitize_path_segment(segment: &str) -> String { |
There was a problem hiding this comment.
warning: a b and a_b both sanitize to a_b, so the later write hides the earlier topic's object, and .. passes too and always fails. percent-encode the segment or hash the raw name.
| self.retry_policy, | ||
| &retry_context, | ||
| opendal::Error::is_temporary, | ||
| || operator.write(&path, buffer.clone()), |
There was a problem hiding this comment.
warning: with the shipped fs config opendal truncates the final key and writes in place, so a reader can see a partial object and a crash leaves one. set atomic_write_dir, and document that fs writes are not atomic.
| last_offset, | ||
| output_format, | ||
| )?; | ||
| let entries = messages |
There was a problem hiding this comment.
warning: collects a Vec<u8> per message, then finalize_buffer copies all of them into a second buffer. format straight into one buffer, and move the JsonArray bracket logic there first.
| } | ||
| } | ||
|
|
||
| async fn write_batch( |
There was a problem hiding this comment.
warning: one object per poll batch means nothing bounds object size in bytes. s3_sink rotates at 8 MiB, but the connector guidance says not to buffer across consume - which rule wins here?
| } else { | ||
| None | ||
| }, | ||
| payload: payload_to_json_value(&message.payload), |
There was a problem hiding this comment.
warning: rebuilds the payload tree before serializing, which allocates a node per element and a String per key. serialize in place with serde_json::to_writer and a wrapper that maps the six Payload variants.
| } | ||
| Err(write_error) => { | ||
| self.write_errors | ||
| .fetch_add(message_count, Ordering::Relaxed); |
There was a problem hiding this comment.
nit: write_errors counts messages, but close() logs it under an errors label. rename the label - counting batches instead breaks the test at line 609.
| #[serde(default)] | ||
| pub include_headers: bool, | ||
| #[serde(default)] | ||
| pub max_attempts: Option<u32>, |
There was a problem hiding this comment.
nit: s3_sink accepts both max_attempts and max_retries, and five other sinks use max_retries. add #[serde(alias = "max_retries")] so a copied config is not silently ignored.
|
|
||
| fn try_from(value: &str) -> Result<Self, Self::Error> { | ||
| match value.to_lowercase().as_str() { | ||
| "json_lines" | "jsonl" | "jsonlines" => Ok(Self::JsonLines), |
There was a problem hiding this comment.
nit: jsonl and jsonlines are accepted here but the README lists only three names. add them to the table, since s3_sink documents the same pair.
| | `service` | String | **required** | OpenDAL service name | | ||
| | `path_prefix` | String | empty | Prefix before the rendered object path | | ||
| | `path_template` | String | `{stream}/{topic}/{date}/{hour}` | Object directory template | | ||
| | `options` | Map | empty | Secret OpenDAL service options | |
There was a problem hiding this comment.
nit: values must be quoted strings in TOML, since SecretString deserializes through String, so a bare number or bool fails at load. say so in the row.
|
|
||
| [package] | ||
| name = "iggy_connector_opendal_sink" | ||
| version = "0.5.0-edge.4" |
There was a problem hiding this comment.
nit: 0.5.0-edge.4 is the only prerelease among the connector plugins, and bump-version.sh does not list this crate, so a release bump skips it. use 0.5.0 and add the crate to CONNECTOR_SINK_COMPONENTS.
Which issue does this PR address?
Closes #4071
Rationale
OpenDAL provides an uniformed interface for writing to various services, which is useful for iggy to support multiple new sink types with a very small amount of change & maintainance effort.
What changed?
Added opendal-sink crate that implements a sink adapter for OpenDAL and integration test. I intentionally didn't touch the other part, e.g., CI. Those can be done in another follow-up PR.
Local Execution
AI Usage
Uses codex & omp for writing tests, reviewing the changes & write some part of the doc.