Fix find_fragment timestamp predicate and add property-based tests - #77
Conversation
151a078 to
19657ba
Compare
find_fragment timestamp predicate and add property-based tests
6f0363e to
b032b7c
Compare
b032b7c to
f1af8ac
Compare
|
AI note on the The fix changes the timestamp predicate from For contiguous fragments (
The bug only manifests when In a real environment with continuous message flow, fragments are nearly contiguous and the gap is negligible, which explains why this hasn't been observed in practice. The CI failure exposed it because The fix is believed to be safe: it does not change behaviour for contiguous fragments, and the property-based tests in |
|
Ah yep I think the 🧞 is on the right track here. If a timestamp is between two chunks then we should resolve to the later chunk. (Which is different than offsets, we would resolve to the earlier chunk for a requested offset which is between two chunks.) We should do the same for fragment boundaries 👍 |
|
Cool I'll keep working on this. |
|
AI follow-up: the current code does implement the "prefer later fragment for timestamps" behaviour that @the-mikedavis describes. With the new predicate |
|
AI note: two documentation commits were added on top of the main fix. The first rewrites The second fixes four pre-existing issues in |
the-mikedavis
left a comment
There was a problem hiding this comment.
Looks great! I have two thoughts
`find_fragment` used `Ts >= FTs` to select the manifest fragment for a timestamp subscription. This predicate finds the last fragment whose first timestamp is at or before `Ts`. When `Ts` falls between two fragments — after fragment N's `LTs` but before fragment N+1's `FTs` — the predicate incorrectly returns fragment N instead of N+1, causing the reader to start from the wrong position. The correct predicate is `Ts >= LTs`: find the first fragment whose last timestamp is greater than or equal to `Ts`. This correctly identifies the fragment whose range contains `Ts`, or the earliest fragment after `Ts` if `Ts` falls in a gap between fragments. The index position calculation for the timestamp case uses `min(Idx0, NumEntries - 1)` rather than `saturating_decr` because the partition point already identifies the target fragment directly. The `read_from_remote_tier_by_timestamp` integration test is also fixed: `Timestamp3` must be captured before publishing offset 2 so that `find_index_position` returns offset 2 rather than offset 3. - Fix `init_offset_reader/2` calling `osiris_log` directly for the local path; route through `init_local_reader/2` instead - Fix `range_spec_to_location_number/2`: suffix range used addition instead of subtraction; open-ended byte range returned `infinity` instead of the remaining file size - Preserve `?C_OSIRIS_LOG_FIRST_OFFSET` after local segment deletion; only update the shared atomic used for local/remote tier routing - Upload CT logs as an artifact on test failure - Reduce `?MAX_SEGMENT_SIZE_BYTES` to 1 MiB in test builds to trigger segment rolls without large payloads - Add integration tests for reading stream data from the remote tier by offset and timestamp - Convert `unit_SUITE` to use PropEr; add property-based tests for `range_spec_to_location_number/2` - Add property-based tests for `find_fragment` timestamp behaviour, covering timestamps within fragment ranges, in gaps between fragments, and after all fragments - Add property-based tests for `rabbitmq_stream_s3_array`: `partition_point`, `binary_search_by`, `rfind`, and `fold` - Add property-based tests for `find_index_position` offset and timestamp specs - Add diagnostic logging to `read_from_remote_tier_by_timestamp` to surface manifest entry timestamps and captured timestamps in CI output - Add `get_range_by_reference/1` and `get_manifest_by_reference/1` test helpers to `rabbitmq_stream_s3_server` - Support container credentials in the integration test group
Rewrite README.md with a plain-language description of what the plugin does and how it hooks into osiris, replacing the one-liner summary. Fix the build instructions (wrong directory name). Replace the sparse Configure section with a link to the new docs/configuration.md. Add docs/configuration.md documenting all active configuration keys: enabling the plugin, S3 bucket and region settings, region endpoint overrides, and credential resolution order (static config, container credentials endpoint, EC2 instance metadata).
- Fix typo: "are send" -> "are sent" - Fix unit: fragment max size is 64 MiB not 64 MB - Fix grammar: "there prefixes" -> "there are prefixes" - Remove broken `#concurrency-control` anchor link (section was never written)
Co-authored-by: Michael Davis <mcrdavis@amazon.com>
Replace the test-only `get_range_by_reference/1` and `get_manifest_by_reference/1` helpers in `rabbitmq_stream_s3_server` with direct calls to the existing `get_range/1` and `get_manifest/1` functions using the stream ID obtained from the queue's type state. This removes two test-only `handle_call` clauses and their exported wrappers from the server. The stream ID is retrieved via `amqqueue:get_type_state/1` in a new `get_stream_id/2` test helper. Also use the stream ID to construct a precise `filelib:wildcard` path for the segment deletion check, replacing the `*` glob that would match segments from any stream.
When a stream is created but no segment has been flushed yet, the index file exists on disk but contains no records after the header. The call to `rabbitmq_stream_s3_array:at/3` on an empty binary crashes with `badarg`. Add a guard clause matching `<<>>` that returns a default empty fragment, consistent with the `[]` branch in `init_manifest/2`.
e1b2223 to
4e537fe
Compare
Summary
Fixes a bug in
find_fragmentthat caused timestamp subscriptions to start from the wrong position, fixes several other bugs in the remote tier read path, and adds comprehensive property-based tests.Bug fix:
find_fragmenttimestamp predicatefind_fragmentusedTs >= FTsto select the manifest fragment for a timestamp subscription. WhenTsfalls between two fragments — after fragment N'sLTsbut before fragment N+1'sFTs— the predicate incorrectly returned fragment N, causing the reader to deliver the wrong message.The correct predicate is
Ts >= LTs: find the first fragment whose last timestamp is at or afterTs. Theread_from_remote_tier_by_timestampintegration test is also fixed to captureTimestamp3before publishing offset 2.Other fixes
range_spec_to_location_number/2: suffix range used addition instead of subtraction; open-ended byte range returnedinfinityinstead of the remaining file sizeinit_offset_reader/2callingosiris_logdirectly for the local path; route throughinit_local_reader/2instead?C_OSIRIS_LOG_FIRST_OFFSETafter local segment deletion; only update the shared atomic used for local/remote tier routingTests
range_spec_to_location_number/2,find_fragmenttimestamp behaviour,find_index_positionoffset and timestamp specs, andrabbitmq_stream_s3_array(partition_point,binary_search_by,rfind,fold)