8000 fix(api,engine): data-stream rollover honours dry_run, conditions and write routing by Tong-bit-art · Pull Request #2900 · xerj-org/xerj · GitHub
[go: up one dir, main page]

Skip to content

fix(api,engine): data-stream rollover honours dry_run, conditions and write routing - #2900

Merged
xerj-org merged 2 commits into
xerj-org:mainfrom
Tong-bit-art:fix/datastream-rollover-conditions
Nov 3, 2026
Merged

xerj-org merged 2 commits into
xerj-org:mainfrom
Tong-bit-art:fix/datastream-rollover-conditions

Conversation

@Tong-bit-art
Copy link
Copy Markdown
Contributor

What this changes, and why

POST /{data_stream}/_rollover accepted a body but never read it. Measured on rc.100:

POST /ds/_rollover {"dry_run": true}
-> 200 {"old_index":"ds","new_index":".ds-ds-000002","rolled_over":true,"dry_run":false}
   and .ds-ds-000002 was actually created

POST /ds/_rollover {"conditions": {"max_docs": 100}}   # stream has 1 doc
-> 200, rolled_over:true, generation incremented

The body fields are not cosmetic: dry_run promises no mutation, and conditions is how ILM/data-stream maintenance decides whether a new backing is needed. The response also named the data stream (ds) instead of the old backing index.

While writing the post-rollover test, a second defect surfaced: the engine added the new backing to the alias but never marked it is_write_index: true. With two members and no flag, the alias was ambiguous, so the first write after any rollover answered 400 no write index is defined for alias [ds].

The fix

  • Parse the rollover body (and ?dry_run) instead of ignoring it.
  • Evaluate max_docs and max_age before touching the engine. Conditions are ORed, as ES defines them, and each result is reported as "[max_docs: N]": true|false / "[max_age: 1h]": true|false.
  • Return the candidate old_index (current backing) and new_index (next generated backing) even when the conditions are unmet, matching ES's response.
  • A dry run or an unmet condition creates nothing. An existing candidate index is a 409 even for a dry run (ES validates the target).
  • Unsupported size-based conditions and alias-rollover body fields (new_index, settings, mappings, aliases) are refused with a named 400 rather than silently ignored.
  • Engine::rollover_data_stream marks the new backing is_write_index: true and clears every older member's flag, preserving each member's filter/routing. The stream is writable immediately after the rollover.

Evidence

Real node, rc.100; stream ds starts with one backing and one document:

operation before after
{"dry_run": true} 200, rolled_over:true, dry_run:false, generation 2, backing created 200, rolled_over:false, dry_run:true, generation 1, candidate .ds-ds-000002 not created
{"conditions":{"max_docs":2}} with 1 doc 200, rolled, generation 2 200, rolled_over:false, conditions["[max_docs: 2]"]:false, generation 1
same with 2 docs 200, rolled 200, rolled_over:true, conditions true, old_index:".ds-ds-000001", new_index:".ds-ds-000002"
write through the stream after a rollover 400 no write index is defined for alias [ds] 201, _index:".ds-ds-000002"
plain rollover response old_index:"ds" old_index:".ds-ds-000001"
{"conditions":{"max_size":"1gb"}} 200, rolled 400 naming max_size, nothing created

Fail-before / pass-after (old handler + old engine, new tests kept):

dry_run_reports_candidate_without_creating ... FAILED
dry_run_query_parameter_is_honoured ... FAILED
max_docs_condition_blocks_then_rolls ... FAILED
max_age_condition ... FAILED
conditions_are_ored ... FAILED
unsupported_conditions_and_body_fields_are_refused ... FAILED
plain_rollover_reports_backing_names ... FAILED
# with the fix restored:
test result: ok. 7 passed; 0 failed

Checks

  • cargo fmt on every touched file (rustfmt --check clean)
  • Scoped release build: cargo build --release -j 32 -p xerj-api -p xerj-engine — not run; a quick build of xerj-server succeeded on the final head. Left to CI.
  • cargo test -p xerj-api --lib: 411 passed, 0 failed (final head b220fc30 + this diff)
  • New data_stream_rollover_conditions: 7 passed; cluster_metadata_survives_restart 13, ilm_safety_rails 7
  • ES-YAML conformance at 0 failed — not run locally against a current runner; CI will run it. The response shapes and condition keys follow the upstream indices.rollover YAML fixtures.
  • New ES-compatible behaviour has a matching YAML case — N/A locally: this repo's checked-in suite has no data-stream rollover-condition fixture; the Rust test pins the ES shapes.
  • Docs updated: CHANGELOG.md Unreleased/Fixed states dry-run/conditions, the write-index fix, and the refused fields.
  • Audit in docs/CONTRIBUTION_REVIEW.md — state correctness: dry-run and unmet conditions have no engine call; the target-exists check precedes conditions; the write flag is set before the stream returns to service.
  • Operational audit — N/A: no extraction, install, publish or serve path.

Not run: scoped release build and a current-runner ES-YAML pass; both left to CI.

Provenance

  • Written by: AI agent (DeepSeek V4.1 Flash, OpenCode), run by @Tong-bit-art, who reviewed the diff and the runs above.
  • Verified (commands run, output observed):
    • the before/after table on a real node, including the generation/backing list, the write after rollover, and the refused max_size;
    • all seven new tests fail on the old handler/engine and pass after;
    • xerj-api --lib 411/0, the stream/rollover suites above, rustfmt --check, clippy clean on the changed files, quick build.
  • Assumed (not tested, and what would falsify it):
    • ES ORs rollover conditions and emits keys like [max_docs: 2] / [max_age: 1h]; upstream indices.rollover YAML pins both, including rolled_over:false with candidate names.
    • ES validates an existing target before evaluating conditions (the upstream "dry-run but target index exists" case expects an already-exists error even when max_docs is not met); this handler returns the same class of conflict.
    • max_age uses the persisted created_at.json time for the backing index; an index missing that record falls back to "now", so it would not meet a positive age condition until it ages. If ES used the index's settings timestamp instead, the measured boundary could differ.
    • Data-stream writes route through the alias write flag in this engine. Marking the new backing is what makes the post-roll write succeed; if a separate stream-write path is added later, both must agree.

`POST /{data_stream}/_rollover` ignored its body. `dry_run:true` created a
real backing index and answered dry_run:false, `conditions` never blocked a
rollover, and old_index reported the data-stream name instead of the current
backing index. The engine also left the alias with two members and no
is_write_index flag, so the first write after any rollover answered 400
"no write index is defined".

The handler now parses body and ?dry_run, evaluates max_docs/max_age (ORed,
reported in an ES-shaped conditions map) before touching the engine, and
returns the candidate old/new backing names even when it does not roll. A dry
run or unmet condition creates nothing; an existing candidate is a 409. Size
conditions and alias-rollover body fields are refused rather than ignored.
rollover_data_stream now marks the new backing is_write_index:true and every
older member false, preserving filter/routing metadata, so the stream stays
writable.

Evidence (rc.100, real node):
- before: dry_run:true -> rolled_over:true, generation 2; max_docs:100 with
  one doc -> rolled; old_index "ds"; after rollover a write -> 400 no write index
- after: dry_run -> rolled_over:false/dry_run:true, generation 1;
  max_docs:2 with one doc -> false/no roll; with two docs -> true/roll,
  old_index .ds-ds-000001/new .ds-ds-000002; write after roll -> .ds-ds-000002;
  max_size -> 400; write after plain rollover succeeds
- fail-before: all 7 new tests fail on the old handler
- xerj-api --lib 411/0; cluster_metadata_survives_restart 13; ilm_safety_rails 7;
  fmt/clippy clean; quick build.

Scope: alias rollover remains unsupported; max_size/max_primary_shard_*
conditions and new_index/settings/mappings/aliases are refused.
@cla-bot cla-bot Bot added the cla-signed label Nov 3, 2026
@xerj-org
xerj-org merged commit b780048 into xerj-org:main Nov 3, 2026
23 checks passed
@xerj-org
xerj-org commented Nov 3, 2026
Copy link
Copy Markdown
Owner

Xerj Merger made the fix: merged. Data-stream _rollover now honours dry_run (body or query) and max_docs/max_age conditions (ORed, reported in conditions), reports the real old/new backing indices, refuses unsupported conditions and alias-rollover fields, and marks the new backing as the alias write index so writes keep working after a rollover. Thanks @Tong-bit-art!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0