Snapshots 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 Snapshots sample.
Program.cs
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Orleans.Hosting;
using Orleans.Lattice;
// =============================================================================
// Snapshots
// -----------------------------------------------------------------------------
// Demonstrates SnapshotAsync: a point-in-time copy of a whole tree into a new
// destination tree. Snapshots are useful for backups, read-only analytics
// forks, or cloning a dataset for experimentation.
//
// This sample uses SnapshotMode.Offline: the source tree is locked shard by
// shard during the copy, producing a strictly consistent point-in-time image.
// Switching to SnapshotMode.Online is a one-line change - the source then stays
// readable and writable throughout, with live mutations mirrored to the
// destination (see the README for the trade-off).
//
// SnapshotAsync starts a crash-safe, timer-driven coordinator, so we poll
// IsSnapshotCompleteAsync until it finishes. Snapshot cost is driven by the shard
// fan-out as well as the key count.
// =============================================================================
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));
})
.Build();
Console.Write("Silo starting...");
await host.StartAsync();
Console.WriteLine(" ready.");
Console.WriteLine();
var grainFactory = host.Services.GetRequiredService<IGrainFactory>();
static byte[] Value(string s) => Encoding.UTF8.GetBytes(s);
static string Text(byte[] b) => Encoding.UTF8.GetString(b);
// Poll until the snapshot coordinator reports completion, printing a heartbeat
// so the (multi-shard, multi-second) copy shows visible progress.
static async Task WaitForSnapshotAsync(ILattice source)
{
var sw = Stopwatch.StartNew();
while (!await source.IsSnapshotCompleteAsync())
{
Console.Write(".");
await Task.Delay(1000);
}
Console.WriteLine($" done in {sw.Elapsed.TotalSeconds:F0}s.");
}
// Count every live key in a tree by scanning it.
static async Task<int> CountByScanAsync(ILattice tree)
{
var n = 0;
await foreach (var _ in tree.ScanKeysAsync())
{
n++;
}
return n;
}
var source = grainFactory.GetGrain<ILattice>("orders");
// --- Step 1: populate the source tree --------------------------------------
const int keyCount = 12;
Console.WriteLine($"Seeding source tree 'orders' with {keyCount} keys...");
for (var i = 0; i < keyCount; i++)
{
await source.SetAsync($"order:{i:D3}", Value($"amount={i * 10}"));
}
Console.WriteLine($" source count = {await source.CountAsync()}");
Console.WriteLine();
// --- Step 2: take an offline point-in-time snapshot -------------------------
// The source is locked shard-by-shard during the copy, so the destination is a
// strictly consistent image of the source at this instant.
Console.Write("Offline snapshot: orders -> orders-backup ");
await source.SnapshotAsync("orders-backup", SnapshotMode.Offline);
await WaitForSnapshotAsync(source);
Console.WriteLine();
// --- Step 3: verify the copy matches the source -----------------------------
var backup = grainFactory.GetGrain<ILattice>("orders-backup");
var backupCount = await CountByScanAsync(backup);
var sampleBackup = await backup.GetAsync("order:005");
Console.WriteLine("Verifying the snapshot:");
Console.WriteLine($" backup live-key count = {backupCount} (expected {keyCount})");
Console.WriteLine($" backup[order:005] = \"{Text(sampleBackup!)}\"");
Console.WriteLine($" source readable again = {await source.GetAsync("order:005") is not null}");
Console.WriteLine();
// --- Step 4: the snapshot is an independent tree ----------------------------
// Editing the backup does not touch the source, and writing to the source does
// not touch the backup. They diverge from the shared point-in-time origin.
await backup.SetAsync("order:005", Value("edited-in-backup"));
await source.SetAsync("order:999", Value("new-in-source"));
var sourceStill005 = await source.GetAsync("order:005");
var backupHas999 = await backup.GetAsync("order:999");
Console.WriteLine("Independence after editing each tree separately:");
Console.WriteLine($" backup[order:005] = \"{Text((await backup.GetAsync("order:005"))!)}\" (edited)");
Console.WriteLine($" source[order:005] = \"{Text(sourceStill005!)}\" (unchanged)");
Console.WriteLine($" source[order:999] = \"{Text((await source.GetAsync("order:999"))!)}\" (new)");
Console.WriteLine($" backup[order:999] = {(backupHas999 is null ? "<absent>" : Text(backupHas999))} (not in the snapshot)");
Console.WriteLine();
Console.WriteLine("Done: the offline snapshot produced an independent point-in-time copy.");
await host.StopAsync();
Snapshots.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Orleans.Lattice.Samples.Snapshots</RootNamespace>
<AssemblyName>Orleans.Lattice.Samples.Snapshots</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>