OnlineReshard source
This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at source.md, and llms.txt lists every page.The source of the OnlineReshard sample.
Program.cs
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Orleans.Hosting;
using Orleans.Lattice;
// ---------------------------------------------------------------------------
// OnlineReshard
// ---------------------------------------------------------------------------
// ILattice.ReshardAsync changes a tree's physical shard count ONLINE: the tree
// keeps serving reads and writes throughout, with no global cutover lock and no
// maintenance window. Resharding spreads the key space across more independent
// write paths; a smaller count folds them back together.
//
// This sample:
// 1. writes a set of keys and records the starting shard count,
// 2. calls ReshardAsync to grow the shard count,
// 3. polls IsReshardCompleteAsync until the migration finishes, and
// 4. proves every key is still readable and the shard count actually grew.
//
// See docs/lattice/online-reshard.md.
// ---------------------------------------------------------------------------
const int TargetShardCount = 72;
using var host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.None);
})
.UseOrleans(silo =>
{
silo.UseLocalhostClustering();
silo.AddMemoryGrainStorageAsDefault();
silo.UseInMemoryReminderService();
silo.AddLattice((s, name) => s.AddMemoryGrainStorage(name));
// Dispatch more concurrent splits per migration tick so the demo
// finishes quickly. Higher values migrate faster at the cost of more
// drain I/O; the default is 4.
silo.ConfigureLattice(o => o.MaxConcurrentMigrations = 16);
})
.Build();
Console.Write("Silo starting...");
await host.StartAsync();
Console.WriteLine(" ready.");
Console.WriteLine();
var grainFactory = host.Services.GetRequiredService<IGrainFactory>();
var tree = grainFactory.GetGrain<ILattice>("catalogue");
// Seed the tree with keys we will re-read after the reshard to prove no data
// was lost during the online migration.
const int keyCount = 24;
Console.WriteLine($"== Writing {keyCount} keys ==");
for (var i = 0; i < keyCount; i++)
{
await tree.SetAsync($"key/{i:D3}", Encoding.UTF8.GetBytes($"value-{i}"));
}
Console.WriteLine($" wrote key/000 .. key/{keyCount - 1:D3}");
Console.WriteLine();
var startShards = await DistinctShardsAsync(tree);
Console.WriteLine($"Starting physical shard count: {startShards}");
Console.WriteLine($"Resharding online to {TargetShardCount} shards...");
Console.WriteLine();
// Kick off the reshard. It returns as soon as the intent is durably persisted;
// the migration then proceeds in the background, split by split.
await tree.ReshardAsync(TargetShardCount);
// While the reshard runs, the tree stays fully available. Issue a live write to
// demonstrate that reads and writes are served throughout the migration.
await tree.SetAsync("key/live-during-reshard", Encoding.UTF8.GetBytes("written mid-migration"));
Console.WriteLine(" wrote key/live-during-reshard WHILE the migration was in flight");
// Poll until the coordinator reports completion (bounded so the sample never
// hangs if something is misconfigured). Keep the loop light - a heavy
// DiagnoseAsync fan-out on every tick would compete with the migration.
var deadline = DateTime.UtcNow + TimeSpan.FromMinutes(4);
var tick = 0;
while (!await tree.IsReshardCompleteAsync())
{
if (DateTime.UtcNow > deadline)
{
Console.WriteLine(" [WARN] reshard did not complete within the timeout.");
break;
}
if (++tick % 5 == 0)
{
Console.WriteLine($" ...migrating (distinct shards so far: {await DistinctShardsAsync(tree)})");
}
await Task.Delay(TimeSpan.FromSeconds(2));
}
Console.WriteLine();
var endShards = await DistinctShardsAsync(tree);
Console.WriteLine($"Final physical shard count: {endShards}");
Console.WriteLine();
// Verify every original key - plus the one written mid-migration - is intact.
Console.WriteLine("== Verifying all keys survived the reshard ==");
var missing = 0;
for (var i = 0; i < keyCount; i++)
{
var value = await tree.GetAsync($"key/{i:D3}");
if (value is null || Encoding.UTF8.GetString(value) != $"value-{i}")
{
missing++;
}
}
var liveValue = await tree.GetAsync("key/live-during-reshard");
Console.WriteLine($" original keys intact : {keyCount - missing}/{keyCount}");
Console.WriteLine($" mid-migration write : {(liveValue is null ? "<lost>" : Encoding.UTF8.GetString(liveValue))}");
Console.WriteLine();
Console.WriteLine(
missing == 0 && endShards > startShards
? $"[OK] shard count grew {startShards} -> {endShards} with zero data loss and no downtime."
: "[FAIL] reshard did not meet expectations.");
await host.StopAsync();
// Reports the number of DISTINCT physical shards the tree's key space is
// currently spread across, read from the effective ShardMap. forceRefresh
// busts the activation's cached routing snapshot so growth is observed
// immediately. This is the count ReshardAsync grows.
static async Task<int> DistinctShardsAsync(ILattice tree)
{
var routing = await tree.GetRoutingAsync(forceRefresh: true);
return routing.Map.GetPhysicalShardIndices().Count;
}
OnlineReshard.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Orleans.Lattice.Samples.OnlineReshard</RootNamespace>
<AssemblyName>Orleans.Lattice.Samples.OnlineReshard</AssemblyName>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.11" />
<PackageReference Include="Microsoft.Orleans.Server" Version="10.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\lattice\Orleans.Lattice.csproj" />
</ItemGroup>
</Project>