Skip to content

feat: improved partial load rule behavior - #19903

Open
clintropolis wants to merge 6 commits into
apache:masterfrom
clintropolis:partial-projection-matcher-improvement
Open

feat: improved partial load rule behavior#19903
clintropolis wants to merge 6 commits into
apache:masterfrom
clintropolis:partial-projection-matcher-improvement

Conversation

@clintropolis

@clintropolis clintropolis commented Aug 6, 2026

Copy link
Copy Markdown
Member

Description

This PR is a follow-up to #19791 to improve the behaviors possible with partial load rules/matchers, adding 2 new LoadSpec and CannotMatchBehavior implementations.

changes:

  • adds CannotMatchBehavior.BASE_LOAD to indicate that when partial rules interval applies but fails to match it will load the 'base table' (no projections) with newly added PartialBaseTableLoadSpec
  • adds CannotMatchBehavior.LOAD_ON_DEMAND which has taken over the previous behavior that was for FULL_LOAD (issues the load spec directly with no 'partial' wrapper). Since partial loads only apply when the historical is in 'virtual storage' mode, a regular load rule is a 'weak' load on demand load.
  • CannotMatchBehavior.FULL_LOAD now issues a PartialFullSegmentLoadSpec which loads everything in the segment, to make it honest about what it does.
  • PartialProjectionLoadSpec on failure to match will now always pick a PartialBaseTableLoadSpec since projections can always be recomputed from the base table so that historicals are still eagerly loading some data that can satisfy user queries instead of falling back to the CannotMatchBehavior

@FrankChen021 FrankChen021 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Severity Findings
P0 0
P1 3
P2 0
P3 0
Total 3

Reviewed 15 of 15 changed files. Projection fallback compatibility, partial-to-on-demand reconciliation, and unsupported FULL_LOAD backends need correction.


This is an automated review by Codex GPT-5.6-Sol

final List<String> resolved = resolveProjectionNames(segment);
if (resolved.isEmpty()) {
return null;
return new MatchResult(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P1] Respect onCannotMatch when projections are absent

Returning a base-table result here means PartialLoadRule never consults onCannotMatch when no configured projection exists. Existing rules using onCannotMatch: fallThrough followed by another load or drop rule now stop the cascade and base-load the segment instead, silently changing tier placement and retention behavior. Return null for an empty resolution and let the newly added BASE_LOAD behavior opt into this fallback, or add a separate explicit matcher option.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I thought this behavior made more sense for projection matching because a partial load rule is an intent to pre-warm data loading on historicals so that queries can be fast and projections can always be re-computed from the 'base' table if the desired projections are not present in the segment, so this change ensures that at least some user data is loaded that can satisfy user queries, and behaves better when used with a CompositePartialLoadMatcher, since a missing projection will not fail the whole match (this discussion #19791 (comment)).

// Matcher does not apply, but the rule still does — FALL_THROUGH would have made appliesTo return false, so run
// wouldn't have been invoked. How much of the segment to make resident is onCannotMatch's call.
switch (onCannotMatch) {
case LOAD_ON_DEMAND -> handler.replicateSegment(segment, getTieredReplicants());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P1] Reconcile partial profiles before going on-demand

When a replica currently announces a partialBaseTable or partialFullSegment profile and the rule changes to LOAD_ON_DEMAND, this enters the fingerprint-blind regular replication path. StrategicSegmentAssigner.updateReplicasInTier exits as soon as the replica count is satisfied, so it never sends the wrapper-free load request that makes SegmentLocalCacheManager release the old rule holds. The old bundles therefore remain pinned and the stale profile persists indefinitely. Reconcile partial-to-regular transitions explicitly, or represent on-demand state with a profile the partial reconciler can swap to.

@capistrant capistrant Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

> Reconcile partial-to-regular transitions explicitly, or represent on-demand state with a profile the partial reconciler can swap to.

The latter sounds better to me.

edit: response in thread clarified handling of this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

that is fixed by #19884

PartialBaseTableLoadSpec.wireForm(segment.getLoadSpec(), PartialBaseTableLoadSpec.FINGERPRINT),
PartialBaseTableLoadSpec.FINGERPRINT
);
case FULL_LOAD -> replicateWholly(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P1] Honor FULL_LOAD for non-rangeable segments

On a backend that cannot open a range reader, such as zipped or legacy deep storage, loadPartial clears any prior rule and returns the plain segment without downloading or pinning anything. FULL_LOAD therefore degrades to weak on-demand loading instead of making every bundle resident; because the plain announcement has no requested fingerprint, the coordinator continues treating the replica as stale and can requeue the same request each run. Add an eager full-download-and-pin fallback for this wrapper, or reject or advertise the unsupported state explicitly rather than promising FULL_LOAD.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think we actually end up with re-queue constantly in this case. wont we just lazily load the full segment, but still have announced it with the full fingerprint and full size accounting?

one alternative I've thought about for handling non range readable segments is a metric emitted with datasource, fingerprint dims when end up loading a situation like this. I'm not sure how difficult having a full load for non ranageable segments is in this case, so I figured a metric alerting an operator to this could be more straightforward to implement

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yea, this is a bit of a rough edge but I don't think i'm going to change it in this PR and since this functionality is still undocumented and a bit experimental i think its ok for now. Historicals currently lack the machinery to do sticky eagerly loads of non-rangeable segments since they currently are stored in different cache entries (PartialSegmentMetadataCacheEntry/PartialSegmentBundleCacheEntry vs CompleteSegmentCacheEntry for non-rangeables), so this would take some work to do to add that weak hold machinery to prevent eviction and honor the rule, or, they would need to be stored as 'static' entries in the cache and managed that way, both of which are not directly related to the changes in this PR.

@capistrant capistrant left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

commits got pushed mid review 😛 posting and I will mark anything stale as resolved

// Matcher does not apply, but the rule still does — FALL_THROUGH would have made appliesTo return false, so run
// wouldn't have been invoked. How much of the segment to make resident is onCannotMatch's call.
switch (onCannotMatch) {
case LOAD_ON_DEMAND -> handler.replicateSegment(segment, getTieredReplicants());

@capistrant capistrant Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

> Reconcile partial-to-regular transitions explicitly, or represent on-demand state with a profile the partial reconciler can swap to.

The latter sounds better to me.

edit: response in thread clarified handling of this

PartialBaseTableLoadSpec.wireForm(segment.getLoadSpec(), PartialBaseTableLoadSpec.FINGERPRINT),
PartialBaseTableLoadSpec.FINGERPRINT
);
case FULL_LOAD -> replicateWholly(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think we actually end up with re-queue constantly in this case. wont we just lazily load the full segment, but still have announced it with the full fingerprint and full size accounting?

one alternative I've thought about for handling non range readable segments is a metric emitted with datasource, fingerprint dims when end up loading a situation like this. I'm not sure how difficult having a full load for non ranageable segments is in this case, so I figured a metric alerting an operator to this could be more straightforward to implement

Comment on lines +44 to +49
* <b>Projection matchers always apply.</b> When none of the configured projections are present on a segment, the
* matcher resolves to a {@link PartialBaseTableLoadSpec} (every row, no projections) instead of going opaque. A
* projection is precomputation that is always recoverable from the base table, so the base table is a correct
* substitute for one the segment doesn't carry, and it is strictly less data than every bundle on the segment. This
* is the ordinary state of affairs mid-rollout, when a new projection is being reindexed in and only some segments
* carry it yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think I understand the why for this behavior. queries that would use the projection can still run on the data even if the projection doesn't exist. but what about cases like:

  • bad rules that misdefine a projection name and end up loading the base table
  • composites where maybe a few cluster groups + projection were spec'd in the rule but with the base table you end up with essentially the whole segment if the projection was missing

Either of those things is technically correct under documentation. But is it rather something an operator should specify in the projection rule rather than be boxed into it? maybe it even defaults like this so an operator has to consciously say if there isn't projection match then I understand the rule will do onCannotMatch behavior. and as I type this I ask - is this idea any better? 🫠

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

#19903 (comment) kind of responds to this comment

@capistrant capistrant Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

like could we have projection rules carry a no matched projections behavior desire of load base table on demand or base table eagerly I guess is what I'm noodling on.

but maybe I'm overcomplicating it and as long as this is defined behavior that it is a choice an operator takes when they use projection based partial rules

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think I take this all back after considering that it is going to be clearly defined behavior. projections rules will be created because the operator expects the projection to exist (at least eventually in the case of adding with compaction), and they don't want to just not see data because it wasn't created yet when the alternative is a non-projection query that gives back good results, albeit more slowly.

@capistrant capistrant left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Responses to Frank make sense to me. My comments either group under those discussions or I have came to no longer think they are issues that need to be addressed. One nit on an annotation other than that I think this is a good step improvement

* them are present on {@code segment}.
*/
@Override
@Nullable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: this impl is no longer @Nullable

@clintropolis clintropolis reopened this Aug 6, 2026
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.

3 participants