Skip to content

feat(sinks): add iggy sink to publish OTLP to Apache Iggy#25846

Open
alexpacio wants to merge 1 commit into
vectordotdev:masterfrom
alexpacio:feat/iggy-sink
Open

feat(sinks): add iggy sink to publish OTLP to Apache Iggy#25846
alexpacio wants to merge 1 commit into
vectordotdev:masterfrom
alexpacio:feat/iggy-sink

Conversation

@alexpacio

Copy link
Copy Markdown

Summary

Adds a new iggy sink that publishes OTLP logs, metrics, and traces to
Apache Iggy topics. It's intended for
pipelines that want Iggy as a durable, partitioned queue ahead of a
storage/observability backend — pair it with the opentelemetry source
configured with use_otlp_decoding: true so the OTLP structure is
preserved end to end through the sink's mapping.

Gated behind a new sinks-iggy feature flag (included in the
sinks-logs/sinks-metrics meta-features, plus a new
iggy-integration-tests feature for future CI integration coverage).

Design

  • The sink shards each batch of rows (by series/label fingerprint for
    logs & metrics, by trace id for spans) and encodes them into a
    compact, versioned MessagePack envelope with per-envelope label
    interning, before appending to the matching Iggy partition and
    issuing an explicit flush for durability.
  • New dependencies: the iggy client crate (=0.10.0) and
    xxhash-rust (used for series fingerprinting). rmp-serde and hex
    were already optional dependencies used elsewhere in the tree.
  • The sink has no dependency on any downstream project — the wire
    format and OTLP→row mapping (metric name normalization, histogram
    and summary explosion, severity mapping, span structure) live
    entirely in src/sinks/iggy/{proto,otlp}.rs.
  • Config: connection_string, stream, topic, shards, tenant /
    tenant_attribute (multi-tenant routing via a resource attribute),
    max_message_bytes, lanes (concurrent Iggy connections), and
    batch_events.

Testing

  • Unit tests (cargo test --features sinks-iggy sinks::iggy) cover
    wire encoding + shard-placement round-tripping and OTLP→row mapping
    for logs (severity, trace id), metrics (monotonic sum → _total,
    gauge), and the config's generate_config round trip.
  • Manually verified end to end locally: ran a vector binary built
    with --features sinks-iggy against a local Apache Iggy broker and
    a downstream consumer that decodes the sink's wire format, drove
    OTLP logs/metrics/traces through the opentelemetry source, and
    confirmed all three signals were received, correctly sharded, and
    queryable on the consumer side.

Notes for reviewers

  • On my local toolchain (pinned 1.95 via rust-toolchain.toml),
    adding the iggy/xxhash-rust dependencies triggered cargo to
    relock a large number of unrelated packages to their "latest 1.95
    compatible" versions (unrelated to this change — the same relock
    happens even when checking only this feature in isolation). I've
    left Cargo.lock out of this PR so CI/maintainers can regenerate it
    in a clean environment rather than carrying an unrelated, very large
    diff.
  • Happy to add an integration test against a real Iggy broker (there's
    already an iggy-integration-tests feature flag stubbed in for
    this) if that's the preferred path for this kind of sink — let me
    know the convention you'd like me to follow.

Adds a new `iggy` sink that shards, encodes, and durably publishes OTLP
logs, metrics, and traces (from the `opentelemetry` source configured
with `use_otlp_decoding: true`) to Apache Iggy topics.

The sink is self-contained: it depends only on the `iggy` client crate
and existing Vector dependencies (rmp-serde, xxhash-rust), with no
dependency on any downstream project. It is intended for pipelines that
route observability data through Iggy as a durable, partitioned queue
ahead of a storage backend, replacing a reference OpenTelemetry
Collector plus a bespoke OTLP-to-Iggy adapter with a single Vector
component.

Gated behind the `sinks-iggy` feature flag (included in the
`sinks-logs`/`sinks-metrics` meta-features and a new
`iggy-integration-tests` feature).
@alexpacio
alexpacio requested a review from a team as a code owner July 15, 2026 15:54
@github-actions github-actions Bot added the domain: sinks Anything related to the Vector's sinks label Jul 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution! Before we can merge this PR, please sign our Contributor License Agreement.

To sign, copy and post the phrase below as a new comment on this PR.

Note: If the bot says your username was not found, the email used in your git commit may not be linked to your GitHub account. Fix this at github.com/settings/emails, then comment recheck to retry.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@datadog-vectordotdev

Copy link
Copy Markdown

Pipelines

⚠️ Warnings

🚦 1 Pipeline job failed

CLA Assistant | CLAAssistant   View in Datadog   GitHub Actions

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: a58c9da | Docs | Give us feedback!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a58c9daef8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/sinks/iggy/sink.rs
.or_else(|| obj.get("resourceMetrics"))
.or_else(|| obj.get("resourceSpans"))?
.as_array()?;
let first = resources.first()?.as_object()?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route each OTLP resource to its own tenant

When one decoded OTLP request contains multiple resourceLogs, resourceMetrics, or resourceSpans entries, this selects only the first resource's tenant while decode_event appends rows from every resource in the request to that same WriteBatch. Multi-tenant exporters commonly batch multiple resources together, so later resources with a different tenant_attribute will be published under the first tenant, causing cross-tenant misrouting/data leakage.

Useful? React with 👍 / 👎.

Comment thread Cargo.toml
rmpv = { version = "1.3.0", default-features = false, features = ["with-serde"], optional = true }

# Obstack `iggy` sink: Apache Iggy client + wire-format hashing.
iggy = { version = "=0.10.0", optional = true }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Commit the new Iggy dependency in Cargo.lock

This adds the iggy dependency and enables it through sinks-iggy (also pulled in by the sinks meta-feature), but Cargo.lock still has no name = "iggy" entry. Any CI or release build using --locked with sinks-iggy, sinks-logs, sinks-metrics, or sinks will fail before compilation because Cargo is not allowed to update the lockfile.

Useful? React with 👍 / 👎.

let sends = prepared
.into_iter()
.map(|(partition, mut messages)| async move {
let partitioning = Partitioning::partition_id(partition);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Offset zero-based shards before sending to Iggy

This passes the zero-based storage shard directly as the Iggy partition id, but Apache Iggy partition ids start at 1. Any batch assigned to shard 0 will be sent/flushed against partition 0, which is not a valid topic partition, so normal traffic will fail whenever a row hashes to the first shard.

Useful? React with 👍 / 👎.

Comment thread src/sinks/iggy/otlp.rs
Comment on lines +101 to +102
fn as_id(v: Option<&Value>) -> String {
let s = as_string(v);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Hex-encode raw OTLP trace and span IDs

With the opentelemetry source's OTLP decoding enabled, traceId/spanId fields are stored as raw bytes, but this path turns those bytes into a lossy UTF-8 string instead of hex. Real traces and log correlation metadata will therefore be published with invalid/non-hex IDs and hashed for shard placement using the wrong value, while the tests miss it because they build IDs as JSON strings.

Useful? React with 👍 / 👎.

Comment thread src/sinks/iggy/otlp.rs
Comment on lines +519 to +521
SampleRow {
fingerprint: Default::default(),
timestamp_ns: ts,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Populate row fingerprints from their label sets

Every encoded sample row currently carries Fingerprint(0) even though the surrounding label set has the actual series fingerprint and the row fingerprint is serialized as part of the wire format. For metrics (and the analogous log rows), downstream storage that keys/upserts by the row's fingerprint will collapse distinct series that share a timestamp because they all arrive with the same zero fingerprint.

Useful? React with 👍 / 👎.

Comment thread src/sinks/iggy/sink.rs
let status = if result.is_ok() {
EventStatus::Delivered
} else {
EventStatus::Rejected

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat transient publish failures as retriable

When send_messages or flush_unsaved_buffer fails because the broker/network is temporarily unavailable, this finalizes the events as Rejected, which Vector treats as a permanent failure rather than a retriable Errored status. In configurations relying on source acknowledgements or disk buffers, those transient Iggy outages will therefore drop the batch instead of allowing it to be replayed.

Useful? React with 👍 / 👎.

Comment thread src/sinks/iggy/otlp.rs
as_f64(field(dp, "count")).unwrap_or(0.0),
);
}
} else if let Some(h) = mobj.and_then(|o| o.get("exponentialHistogram")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve exponential histogram buckets

For OTLP exponentialHistogram metrics, this branch emits only _sum and _count and ignores the positive/negative bucket arrays that carry the actual distribution. Any latency/size distribution sent as an exponential histogram will arrive downstream without buckets, making quantiles and histogram queries impossible even though the sink claims to publish OTLP metrics.

Useful? React with 👍 / 👎.

Comment thread src/sinks/iggy/proto.rs
Comment on lines +76 to +79
labels.sort();
labels.reverse();
labels.dedup_by(|a, b| a.name == b.name);
labels.reverse();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve later labels when de-duplicating

This sort/reverse/dedup sequence keeps the lexicographically largest value for duplicate label names, not the later label as the comment and call sites assume. If OTLP attributes collide after sanitization or a datapoint has a __name__ attribute, the user-provided label can replace the metric name or another intended override based solely on string ordering, routing samples under the wrong series.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain: sinks Anything related to the Vector's sinks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant