-
Notifications
You must be signed in to change notification settings - Fork 663
[BFTree] Add RangeIndex cluster migration #1731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tiagonapoli
wants to merge
40
commits into
main
Choose a base branch
from
tiagonapoli/bftree-migration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
73f69f9
Add RangeIndex cluster migration with sketch protection
31e0017
Rename MigrationReceiveSession.cs to RangeIndexMigrationReceiveSessio…
4b1768c
Address PR review feedback for RangeIndex migration
ab97a36
Fix COPY option comment and restore key logging in RangeIndex migration
cea5d0f
Expand COPY option comment with crash recovery requirements
6774e03
Add using System.Text and simplify log messages in MigrateOperation
695068f
Refactor TransmitKeysAsync to use single HashSet<byte[]> skip set
a4cdaa4
Refactor migration key discovery to use PinnedSpanByte and callback
15afa23
Update DeleteRangeIndex doc comment to reflect COPY not yet supported
d61e50c
Rename keyPsb to key in VectorManager.Migration.cs
a23d491
Switch test classes to TestBase and minor comment cleanup
a09e9d8
w
74b9745
Align Migration.cs with current main APIs and update migration docs
0b6e188
Add TRYAGAIN response for RI commands during migration and fix build …
d6f5d4e
Lazy-init RangeIndex migration receive state and add documentation
4351563
Clean up TRYAGAIN parameter threading and fix double dispose
4e0879d
Remove unnecessary comment about source key deletion
f8f7a13
Add cancellation token support to RangeIndex migration
d3a8e74
Include key name in snapshot failure log message
c6c8577
Simplify TransmitRangeIndexAsync error handling
307e31e
Improve TransmitRangeIndexAsync log messages: add key and fix prefixes
5e25df3
Add CancellationToken parameter to TransmitRangeIndexAsync
c72adcf
Add CancellationToken parameter to MigrateRangeIndexKeysAsync
7726d89
Add method name and key to all log messages in MigrateSession.RangeIn…
d0beb96
Fix unsafe use of FromPinnedSpan on unpinned byte array
8baf55a
Simplify DeleteRangeIndex call: remove temp variable
8775e39
Fix SnapshotForMigration to take PinnedSpanByte and pin key in caller
1eadb44
Fix unpinned FromPinnedSpan in PublishMigratedIndex
cc25c27
Refactor migration temp directory: create once in constructor
ee6e473
Fix migration reads to use Read_RangeIndex to suppress CTT
178cf4e
Eliminate TOCTOU: read authoritative stub in SnapshotForMigration
3c19a4c
Remove TRYAGAIN changes from bftree-migration branch
328972b
Add RI migration fault injection tests and observability logging
d58b1bc
Refactor migration observability into metric structs
3517fee
Make metric struct fields public and use space-separated log format
593c33a
Extract metric structs into Server/Migration/RangeIndex/ folder
f38217c
Initialize RangeIndexMigrationReceiveState in ClusterSession constructor
14a54d4
Remove redundant isReceiving field, derive from currentDeserializer !…
7c5ecba
Add thread-safety documentation to Reset() addressing review comment
da135fd
Use WaitForSnapshot pattern in SnapshotForMigration instead of spin-wait
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
174 changes: 174 additions & 0 deletions
174
libs/cluster/Server/Migration/MigrateSession.RangeIndex.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT license. | ||
|
|
||
| using System; | ||
| using System.Buffers; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Garnet.client; | ||
| using Garnet.common; | ||
| using Garnet.server; | ||
| using Microsoft.Extensions.Logging; | ||
| using Tsavorite.core; | ||
|
|
||
| namespace Garnet.cluster | ||
| { | ||
| /// <summary> | ||
| /// RangeIndex migration support: source-side transmit driver. | ||
| /// </summary> | ||
| internal sealed partial class MigrateSession : IDisposable | ||
| { | ||
| /// <summary> | ||
| /// Transmit a single RangeIndex key to the destination node. | ||
| /// Uses <see cref="RangeIndexManager.SnapshotRangeIndexAndCreateReader"/> to obtain an async | ||
| /// migration reader that snapshots and streams the BfTree data. | ||
| /// Forces a flush and awaits ACK. | ||
| /// </summary> | ||
| private async Task<bool> TransmitRangeIndexAsync(MigrateOperation migrateOperation, byte[] keyBytes, int chunkSize, CancellationToken cancellationToken) | ||
| { | ||
| var rangeIndexManager = clusterProvider.storeWrapper.DefaultDatabase.RangeIndexManager; | ||
| if (rangeIndexManager == null) | ||
| { | ||
| logger?.LogError("TransmitRangeIndexAsync: RangeIndex feature is not enabled, skipping key {key}", Encoding.UTF8.GetString(keyBytes)); | ||
| return false; | ||
| } | ||
|
|
||
| var sessionClient = migrateOperation.Client; | ||
| var buffer = ArrayPool<byte>.Shared.Rent(chunkSize); | ||
| var metrics = new TransmitRangeIndexMetrics { tsStart = Stopwatch.GetTimestamp() }; | ||
| try | ||
| { | ||
| using var reader = rangeIndexManager.SnapshotRangeIndexAndCreateReader(migrateOperation.LocalSession, keyBytes, chunkSize); | ||
| metrics.snapshotTicks = Stopwatch.GetElapsedTime(metrics.tsStart).Ticks; | ||
| metrics.fileSizeBytes = reader.TotalFileBytes; | ||
|
|
||
| while (!reader.IsComplete) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
|
||
| var payloadLen = await reader.ReadNextChunkAsync(buffer, cancellationToken).ConfigureAwait(false); | ||
| if (payloadLen == 0) | ||
| { | ||
| logger?.LogError("TransmitRangeIndexAsync: reader returned zero-length payload with a {Size}-byte buffer for key {key}", chunkSize, Encoding.UTF8.GetString(keyBytes)); | ||
| return false; | ||
| } | ||
|
|
||
| metrics.totalBytesSent += payloadLen; | ||
|
|
||
| if (!await WriteOrSendRecordSpanAsync(sessionClient, MigrationRecordSpanType.SerializedRangeIndexStream, buffer.AsSpan(0, payloadLen)).ConfigureAwait(false)) | ||
| { | ||
| logger?.LogError("TransmitRangeIndexAsync: failed to write chunk for key {key}", Encoding.UTF8.GetString(keyBytes)); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // Force flush and await ACK | ||
| if (!await HandleMigrateTaskResponseAsync(sessionClient.SendAndResetIterationBuffer()).ConfigureAwait(false)) | ||
| { | ||
| logger?.LogError("TransmitRangeIndexAsync: flush failed for key {key}", Encoding.UTF8.GetString(keyBytes)); | ||
| return false; | ||
| } | ||
|
|
||
| metrics.success = true; | ||
| return true; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| logger?.LogError(ex, "TransmitRangeIndexAsync: error during snapshot or transmission for key {key}", Encoding.UTF8.GetString(keyBytes)); | ||
| return false; | ||
| } | ||
| finally | ||
| { | ||
| ArrayPool<byte>.Shared.Return(buffer); | ||
| metrics.LogSummary(logger, keyBytes); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Migrate a batch of RangeIndex keys with sketch protection. | ||
| /// Adds all keys to the sketch, transitions through TRANSMITTING → DELETING → MIGRATED | ||
| /// with epoch barriers, ensuring concurrent operations are properly gated. | ||
| /// </summary> | ||
| private async Task<bool> MigrateRangeIndexKeysAsync(MigrateOperation migrateOperation, HashSet<byte[]> rangeIndexKeys, CancellationToken cancellationToken) | ||
| { | ||
| var metrics = new MigrateRangeIndexMetrics | ||
| { | ||
| tsStart = Stopwatch.GetTimestamp(), | ||
| keyCount = rangeIndexKeys.Count, | ||
| }; | ||
|
|
||
| logger?.LogWarning("MigrateRangeIndexKeysAsync: migrating {count} RangeIndex keys", metrics.keyCount); | ||
|
|
||
| // Add all RI keys to sketch during INITIALIZING (no gating yet) | ||
| migrateOperation.sketch.Clear(); | ||
| migrateOperation.sketch.SetStatus(SketchStatus.INITIALIZING); | ||
| foreach (var key in rangeIndexKeys) | ||
| migrateOperation.sketch.TryHashAndStore(key); | ||
|
|
||
| ExceptionInjectionHelper.TriggerException(ExceptionInjectionType.RangeIndex_Migration_Before_Transmitting); | ||
|
|
||
| // Block writes during snapshot + transmit | ||
| migrateOperation.sketch.SetStatus(SketchStatus.TRANSMITTING); | ||
| await WaitForConfigPropagationAsync().ConfigureAwait(false); | ||
| metrics.tsTransmitting = Stopwatch.GetTimestamp(); | ||
|
|
||
| #if DEBUG | ||
| await ExceptionInjectionHelper.ResetAndWaitAsync(ExceptionInjectionType.RangeIndex_Migration_After_Transmitting).ConfigureAwait(false); | ||
| #endif | ||
|
|
||
| try | ||
| { | ||
| foreach (var key in rangeIndexKeys) | ||
| { | ||
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
|
||
| if (!await TransmitRangeIndexAsync(migrateOperation, key, RangeIndexManager.DefaultMigrationChunkSize, cancellationToken).ConfigureAwait(false)) | ||
| { | ||
| logger?.LogError("MigrateRangeIndexKeysAsync: failed to migrate RangeIndex key {key}", Encoding.UTF8.GetString(key)); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| ExceptionInjectionHelper.TriggerException(ExceptionInjectionType.RangeIndex_Migration_Before_Deleting); | ||
|
|
||
| // Block reads + writes during delete | ||
| migrateOperation.sketch.SetStatus(SketchStatus.DELETING); | ||
| await WaitForConfigPropagationAsync().ConfigureAwait(false); | ||
| metrics.tsDeleting = Stopwatch.GetTimestamp(); | ||
|
|
||
| #if DEBUG | ||
| await ExceptionInjectionHelper.ResetAndWaitAsync(ExceptionInjectionType.RangeIndex_Migration_After_Deleting).ConfigureAwait(false); | ||
| #endif | ||
|
|
||
| foreach (var key in rangeIndexKeys) | ||
| { | ||
| try | ||
| { | ||
| unsafe | ||
| { | ||
| fixed (byte* keyPtr = key) | ||
| migrateOperation.DeleteRangeIndex(PinnedSpanByte.FromPinnedPointer(keyPtr, key.Length)); | ||
| } | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| logger?.LogError(ex, "MigrateRangeIndexKeysAsync: failed to delete RangeIndex key {key} after migration", Encoding.UTF8.GetString(key)); | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| metrics.success = true; | ||
| return true; | ||
| } | ||
| finally | ||
| { | ||
| // Always clean up the sketch, even on failure, to unblock client operations | ||
| migrateOperation.sketch.Clear(); | ||
| metrics.LogSummary(logger); | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.