Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 63 additions & 9 deletions docs/rfcs/0003-otlp-receiver.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
rfc: 0003
title: OTLP receiver — gRPC and HTTP wire endpoints for OpenTelemetry log ingest
status: green
status: specified
author: Jens Holdgaard Pedersen <jens@holdgaard.org>
drafting-assistance: Claude
created: 2026-05-13
Expand Down Expand Up @@ -543,6 +543,39 @@ concurrency).
> the OTLP spec's recommendation to support concurrent
> unary `Export` calls for throughput

> **Amendment (served-binary slice, post-`green`).** Scenarios
> RFC0003.1–.15 are implemented and were `green` (exercised
> in-process: `axum`/`tonic` handlers via direct call /
> `oneshot`, the pipeline + WAL directly). This amendment adds
> RFC0003.16 — the end-to-end *served-binary* contract, which
> the §9 *Receiver process model* resolution settles — and
> re-enters the ladder at `specified` until RFC0003.16 lands.

> **Scenario RFC0003.16 — Served binary: both transports bind, a client export round-trips, graceful shutdown `[§3.4]`**
> - **Given** `ourios-server` started with the receiver role
> enabled (config-toggled per the §9 resolution), the gRPC
> listener bound on its configured port (default 4317) and
> the HTTP listener on its (default 4318), both sharing one
> `IngestPipeline` over a single `Wal`
Comment on lines +555 to +559

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Addressed in #140 — the HTTP-listener Given clause now reads "bound on its configured port (default 4318)".

> - **When** a real OTLP client exports a non-empty batch
> (resolvable tenant) over each bound socket — gRPC `Export`
> and HTTP `POST /v1/logs` (`application/x-protobuf`) — and
> the server is then signalled to shut down

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in the latest commit — the When clause now states the client receives each transport response and only then is shutdown signalled (the steady-state export→ack→shutdown path), matching §8. In-flight-during-shutdown behaviour is explicitly out of scope for this scenario.

> - **Then** each client receives transport-level success
> (gRPC `OK` / HTTP 200) only after its batch is durable — the
> §6.5 WAL-before-ack contract holds **end-to-end over a real
> socket**, not just in-process
> - **And** the shutdown signal stops the listeners and exits
> the process cleanly, releasing the single `Wal` handle —
> without dropping it mid-fsync or losing a batch that was
> already acked (no in-flight, already-fsync'd batch is lost
> on the way out)
Comment on lines +573 to +575

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Addressed in #140 — reworded to "no already-acked batch is lost on the way out", dropping the contradictory "in-flight, already-fsync'd" phrasing.

> - **And** *after* that clean exit frees the single-writer
> handle (RFC 0008 §3.1), opening the WAL and running
> `Wal::replay` recovers each batch's `OtlpBatch` frame — the
> durability check necessarily follows shutdown, since the WAL
> cannot be reopened while the server holds it

## 6. Proposed design

### 6.1 Overall shape
Expand Down Expand Up @@ -593,8 +626,13 @@ decoded `ExportLogsServiceRequest` and:
transport-agnostic at the `AnyValue` level.
- Both listeners spawn off the same tokio runtime, share a
single instance of the business-logic layer, and bind on
operator-configured ports (defaults TBD, likely 4317 for
gRPC and 4318 for HTTP per the OTel convention).
operator-configured ports (defaults **4317** for gRPC and
**4318** for HTTP, per the OTel convention; configurable).
The receiver is a **role of the `ourios-server` binary**,
enabled by config and sharing that binary's tokio runtime
alongside the other roles (e.g. the compaction daemon) — the
§9 *Receiver process model* resolution. The served-binary
contract is RFC0003.16.

### 6.3 Tenant fan-out

Expand Down Expand Up @@ -991,6 +1029,21 @@ greppable.
per batch). RFC0003.15 throughput at N = 8 concurrent
callers. Regressions block merges per `CLAUDE.md` §6.2.

- **Served binary** (RFC0003.16): an integration test boots the
`ourios-server` receiver role bound on ephemeral ports
(`:0`), exports a non-empty batch over each transport with a
*real* client — a `tonic` gRPC client and an HTTP client
Comment on lines +1035 to +1039

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed — changed :0 to 127.0.0.1:0 (read back the OS-assigned port), which is a valid SocketAddr literal; the bare :0 was ambiguous.

(`reqwest`/`hyper`) — and asserts transport success for each.
It then signals shutdown and waits for the server task to
join cleanly, which releases the single `Wal` handle (RFC
0008 §3.1's single-writer rule — the WAL cannot be reopened
while the server holds it). *Only after that join* does the
test open the WAL and `Wal::replay` it, confirming each
batch's `OtlpBatch` frame is durable — WAL-before-ack over a
real socket, with no acked batch lost on the way out. Unlike
RFC0003.1–.15 (in-process: direct handler call / `oneshot`),
Comment on lines +1035 to +1048

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch — fixed in the latest commit. Both the §5 scenario and the §8 bullet now make the durability check explicitly follow graceful shutdown: the server task joins (releasing the single-writer Wal handle, RFC 0008 §3.1), and only then does the test open the WAL and run Wal::replay. The WAL cannot be reopened while the server holds it, so replay-after-shutdown is the only correct ordering. Also switched the awkward "replays" to an explicit Wal::replay.

this is the only scenario that crosses a real socket.

`docs/verification.md` §3's two-loop Red gate applies: the §5
scenarios become `#[ignore]`d test stubs at `red` stage, then
get implementations as the receiver crate is built (the same
Expand Down Expand Up @@ -1035,12 +1088,13 @@ scenarios — `#[ignore]`'d stubs first, implementations second).
0001 §3.2)? Current design says preserve; a future receiver-
side truncation step would need to either recompute or
use a separate column.
- [ ] **Receiver process model.** Is the receiver a separate
binary (sidecar shape) or a role of `ourios-server` like
`ingester`/`querier` (per `CLAUDE.md` §1)? Current
assumption: a role of the existing `ourios-server` binary,
toggled by config. Open until the deployment story is
considered in detail.
- [x] ~~**Receiver process model.**~~ *Resolved (served-binary
amendment):* the receiver is a **role of the `ourios-server`
binary**, enabled by config and sharing that binary's tokio
runtime alongside the other roles (e.g. the compaction
daemon) — **not** a separate sidecar. Default ports 4317
(gRPC) / 4318 (HTTP) per §6.1; the end-to-end served contract
(bind + client round-trip + graceful shutdown) is RFC0003.16.
- [ ] **Partial-success response semantics.** Is the all-or-
nothing batch contract (§6.3) sufficient long-term, or do we
need to expose `partial_success.rejected_log_records` for
Expand Down
Loading