fix: Fix clone historicals and inter-historical move handling of partial load profiles - #19843
fix: Fix clone historicals and inter-historical move handling of partial load profiles#19843capistrant wants to merge 8 commits into
Conversation
| final long fullSize = Long.parseLong( | ||
| cluster.callApi().runSql( | ||
| "SELECT \"size\" FROM sys.segments WHERE datasource = '" + dataSource + "'" | ||
| ).trim() | ||
| ); |
| final String result = cluster.callApi().runSql( | ||
| "SELECT curr_size FROM sys.servers WHERE server_type = 'historical' AND server = '" + host + "'" | ||
| ).trim(); | ||
| return result.isEmpty() ? 0L : Long.parseLong(result); |
FrankChen021
left a comment
There was a problem hiding this comment.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 1 |
| P3 | 0 |
| Total | 2 |
Reviewed 8 of 8 changed files.
This is an automated review by Codex GPT-5.6-Sol
| loadableSegment, | ||
| targetServer, | ||
| SegmentAction.LOAD, | ||
| sourceProfile == null ? null : sourceProfile.asRequest() |
There was a problem hiding this comment.
[P1] Clear partial state before cloning a full load
When the source profile is null and the target already has a partial profile, this queues a plain LOAD. On a virtual-storage Historical, however, the plain SegmentLocalCacheManager.load path does not clear PartialSegmentMetadataCacheEntry's rule or rewrite its partial info file; SegmentManager also leaves the existing segment in place. The announcer then reports a plain profile, so the coordinator believes the clone is full while the old rule holds remain, and a restart re-applies and re-announces the stale partial profile. Explicitly clear the persisted partial rule for this conversion or perform a drop/reload. The added unit test only verifies the queued request, not this Historical-side transition.
| */ | ||
| public PartialLoadProfile asRequest() | ||
| { | ||
| return loadedBytes == null ? this : forRequest(wrappedLoadSpec, fingerprint); |
There was a problem hiding this comment.
[P2] Rebase forwarded profiles onto the current load spec
wrappedLoadSpec contains the source announcement's original deep-storage delegate, and asRequest carries it forward verbatim. Both new callers start with a DataSegment fetched from the current metadata snapshot, but SegmentHolder subsequently replaces that segment's loadSpec with this stale wrapper. If a segment payload was corrected or migrated since the source loaded, clone and move requests therefore use the old location; after that object is removed, cloning fails and balancing or decommissioning moves cannot complete. Preserve the selection and fingerprint while rebuilding the wrapper with the latest metadata loadSpec as its delegate.
…partial loaded. drop and let next cycle load it
| * Returns the clone target to a full load of {@code segment}, for when the source has stopped holding it partially. | ||
| * <p> | ||
| * A plain load request on top of the existing replica does not achieve this. A historical that receives an unwrapped | ||
| * load request for a segment it already holds under a partial-load rule keeps that rule applied: its holds go on |
There was a problem hiding this comment.
A historical that receives an unwrapped load request for a segment it already holds under a partial-load rule keeps that rule applied
Is this a good thing, or are you working around a Historical bug here? To me it seems to make sense to have the Historical able to handle a new load request in this case, rather than requiring a drop + load.
There was a problem hiding this comment.
yes, this was me trying to keep the change clone related only. latest branch attempts to make load() properly handle this transition in load profile, which simplifies the clone handling
| { | ||
| if (targetServer.isLoadingSegment(segment)) { | ||
| if (targetServer.cancelOperation(SegmentAction.LOAD, segment)) { | ||
| loadSegmentOnTargetServer(segment, null, targetServer, params); |
There was a problem hiding this comment.
Isn't it possible that a LOAD was in-flight to a target server that already had the segment loaded, so the cancelOperation succeeding doesn't necessarily mean the target doesn't have the segment? I think this could happen from the call to loadSegmentOnTargetServer on line 131, if the projected profile fingerprint had changed.
There was a problem hiding this comment.
if the new load() handling is acceptable, we should no longer need to consider this. but yes you are right and I have a commit with this handled explicitly in case there are issues with the proposed changes that make this special handling not needed anymore.
FrankChen021
left a comment
There was a problem hiding this comment.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 0 |
| P3 | 0 |
| Total | 1 |
Reviewed 11 of 11 changed files.
This is an automated review by Codex GPT-5.6-Sol
| { | ||
| if (targetServer.isLoadingSegment(segment)) { | ||
| if (targetServer.cancelOperation(SegmentAction.LOAD, segment)) { | ||
| loadSegmentOnTargetServer(segment, null, targetServer, params); |
There was a problem hiding this comment.
[P1] Drop a served partial replica after cancelling its reload
A target can simultaneously serve an existing partial replica and have another partial reload queued—for example, profile A is loaded while profile B is waiting when the source transitions to full. Cancellation succeeds here, but the subsequent plain load runs over the existing A replica. As documented above, the ordinary virtual-storage load path does not clear the applied rule or partial info file, while its plain announcement makes the coordinator record a null profile; later clone cycles therefore consider the target full and never repair it. After cancellation, check whether the target already serves a partial replica and drop it first; only queue the immediate full load when no replica is currently served.
…artial to full load of a segment
| * the segment using the prior wrapper and re-announce the old rule until the coordinator resyncs. | ||
| */ | ||
| private void writePartialInfoFile(DataSegment segment) throws IOException | ||
| private void rewriteInfoFile(DataSegment segment) throws IOException |
There was a problem hiding this comment.
not totally sure this rename was necessary. But since it is now called during a partial --> full migration I took out the partial bit. I'm ok to add back if we want
| rewriteInfoFile(dataSegment); | ||
| } | ||
| catch (IOException e) { | ||
| log.warn( |
There was a problem hiding this comment.
as of now I've accepted the consequence that on restart the info file is not desirable as it will partial load to the prior state. the coordinator should quickly try to reconcile though
There was a problem hiding this comment.
you could flip the order i think if you wanted to only release the rule holds if you updated the info file
There was a problem hiding this comment.
ok ya I flipped and now throw a segment load exception on fail. I convinced myself it was better to get a correct in memory state eagerly and live with sub-optimal restart behavior if the info file update failed. but that seems like it is probably short sighted
| if (!targetProjectedSegments.contains(segment) | ||
| || !Objects.equals( | ||
| fingerprintOf(sourceProfile), | ||
| fingerprintOf(targetServer.getProjectedProfile(segment)) | ||
| )) { |
There was a problem hiding this comment.
Maybe put this check in a new method shouldLoadSegmentOnTargetServer()
| * <li>{@code loadedBytes} is dropped. A profile read back off a server carries the footprint that server | ||
| * realized, which belongs to that server's announcement and not to a request.</li> | ||
| * <li>The wrapper's {@link PartialLoadSpec#DELEGATE_FIELD} is replaced with {@code segment}'s load spec. The | ||
| * wrapper was built when the source server was asked to load, so it carries whatever deep-storage location |
There was a problem hiding this comment.
Is this needed? I think in most places, we already assume that the load spec (and the payload in general) of a DataSegment is immutable.
Is that not applicable for the partial-load segments?
There was a problem hiding this comment.
I think you are right. got talked into needing to be defensive by an agent review who convinced me I could be handing back a stale request. But I cannot find evidence that it could possibly be stale so it looks to me like useless behavior. will revert
| * carrying no load spec, or one whose load spec is already a partial-load wrapper (an outbound request segment | ||
| * rather than the metadata view, which would otherwise nest one wrapper inside another). | ||
| */ | ||
| public PartialLoadProfile asRequestFor(DataSegment segment) |
There was a problem hiding this comment.
Nit: rename the method to indicate that this method modifies the profile for loading on a different server. (I feel the "clone" prefix serves the "move" case too, since there too, we first clone the segment on target server and then delete it from the source server)
| public PartialLoadProfile asRequestFor(DataSegment segment) | |
| public PartialLoadProfile asCloneRequestFor(DataSegment segment) |
| // `segment` is the metadata-resolved segment (see TierSegmentBalancer.getLoadableSegment), which is what | ||
| // asRequestFor needs to rebase the request onto the segment's current location. |
There was a problem hiding this comment.
This comment should probably move into the javadoc of getProjectedProfile() method.
| rewriteInfoFile(dataSegment); | ||
| } | ||
| catch (IOException e) { | ||
| log.warn( |
There was a problem hiding this comment.
you could flip the order i think if you wanted to only release the rule holds if you updated the info file
| continue; | ||
| } | ||
| cacheEntry.setOnUnmount(null); | ||
| if (isFullLoadRequest |
There was a problem hiding this comment.
do you need to check this here? if this is true, we already should have called loadPartial instead
There was a problem hiding this comment.
good point, after analysis, I think it is at best a waste of time. dropping
Description
Segment moves and Historical cloning did not have proper handling for partial load profiles. When a segment should have been being loaded partially, it was not.
This PR now makes a change to
SegmentLocalCacheManager#loadto identify a move from a partial load to a full load and properly respond to that by releasing all pinned holds and trying to re-write the info file. This is in place of a work-a-round I was doing in the clone code to properly handle a clone processing a partial to full by doing a drop then try again on next coordinator cycle.Release note
Fix a partial load rule application bug for inter-historical segment balancing and historical cloning
Key changed/added classes in this PR
This PR has: