Skip to content

Add log-reader abstractions and hooks into writer/replica lifecycles - #232

Open
the-mikedavis wants to merge 7 commits into
rabbitmq:mainfrom
amazon-mq:tiered-storage-abstractions
Open

Add log-reader abstractions and hooks into writer/replica lifecycles#232
the-mikedavis wants to merge 7 commits into
rabbitmq:mainfrom
amazon-mq:tiered-storage-abstractions

Conversation

@the-mikedavis

Copy link
Copy Markdown
Collaborator

This is a recreation of #196 - some git nonsense lead to the PR there auto-closing.

This is is three changes:

  • Introduction of a {'fun', fun()} retention spec which lets retention policies have fine-grained control over what prefix of the osiris_log is deleted.
  • osiris_log_reader behaviour and module. Consumers of Osiris use the osiris API to create an offset reader and then osiris_log_reader to use the reader. The default implementation uses osiris_log as usual.
  • osiris_log_hooks behaviour and default (empty) implementation. Implementors can use this to hook into the lifecycles of Osiris around writer and acceptor creation, and retention updates. This one is super small.

Together these things make a solid base for implementing #184 (fixes #184). We use it in https://github.com/amazon-mq/rabbitmq-stream-s3 to serve a tiered-storage approach that aggressively archives data to elastic blob storage (Amazon S3) which appears seemless from the outside. It should be generic to other approaches (e.g. other blob stores or HDFS, or maybe even offload to shared spinning disks / RAID).

See a bunch of prior history in #196 and #194 before that.

the-mikedavis and others added 7 commits August 7, 2026 23:13
This change introduces a behaviour `osiris_log_reader` which can be
implemented externally to read from a stream at a given offset spec.
This closes over the high-level reading operations `send_file/3` and
`chunk_iterator/3`. `osiris:init_reader/4` selects the reader module
based on application env, and then callers use `osiris_log_reader` to
interact with the reader.

By default all of these functions delegate to `osiris_log`. `osiris_log`
doesn't need any meaningful changes this way. The only change is to
expose the `header_map()` type.
This can be used flexibly to evaluate retention depending on the name or
contents of index files. You pass in a function which returns a tuple
with the index files split into two lists: to delete and to keep.
This could be used as a way to truncate everything up to an offset or to
guarantee that an offset (for example an uncommitted one) won't be
truncated. Since these files are sorted, some retention functions could
operate just on the names (deriving the offset of the segment with
`erlang:binary_to_integer/1`).
This change refactors `parse_header/2` to take the chunk header binary
and the position at which it was read and return a `header_map()`. This
is useful for other readers - so that they do not need to duplicate the
binary match code and `next_position` calculation.
When local retention on the writer node deletes a segment while the
`osiris_replica_reader` is reading it, `read_header_with_ra` skips
ahead to the next available segment using `first_chunk_id`. If the
replica has not yet received data up to that offset, `accept_chunk`
exits with `{accept_chunk_out_of_order, Received, Expected}`.

The replica recovers correctly either way — the stream coordinator
restarts it and `init_acceptor` resyncs to the writer's current
position. However, the unhandled exit floods the log with thousands
of `[error]` lines per event.

Catch the exit in `handle_incoming_data`, log a single `[warning]`,
and stop with `normal`. Recovery behavior is unchanged.
This adds hooks in the log which are executed for writers and acceptors
that can be used in plugins. The plugin intercepts the config map in
init and can modify values. Same for retention: a plugin can modify the
retention specs when it is updated.
After `trigger_retention_eval` sets counters from local segment state,
call the hook to let plugins override values. The tiered storage plugin
uses this to correct `?C_FIRST_OFFSET` when the remote tier holds older
data than the local tier.
This allows a more hermetic upgrade with no race between reading
retention from its source (i.e. the metadata store in RabbitMQ) and
updating it, in rabbitmq-stream-s3.
@the-mikedavis the-mikedavis self-assigned this Aug 7, 2026
@the-mikedavis the-mikedavis added the enhancement New feature or request label Aug 7, 2026
@mergify

mergify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@lukebakken lukebakken self-assigned this Aug 7, 2026
@lukebakken

Copy link
Copy Markdown
Collaborator

Guilty 😬

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tiered Storage Support for RabbitMQ Streams

2 participants