BackupAndRestore 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 BackupAndRestore sample.
Program.cs
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Orleans.Hosting;
using Orleans.Lattice;
using Orleans.Lattice.Backup;
using Orleans.Lattice.Api.Backup;
// =============================================================================
// BackupAndRestore
// -----------------------------------------------------------------------------
// Demonstrates the Orleans.Lattice backup surface end to end against a single
// in-process silo with the default in-cluster sink:
//
// 1. Register a backup scope and a full + incremental schedule.
// 2. Seed a tree with some keys.
// 3. Trigger a FULL backup on demand.
// 4. Mutate the tree (add, change, and delete keys).
// 5. Trigger an INCREMENTAL backup layered on the full base.
// 6. List the catalog.
// 7. Restore the latest backup (base + increment folded) into a FRESH tree.
// 8. Print the restored values and an inventory summary.
//
// The scheduled full / incremental reminders are registered to show the API,
// but the captures themselves are driven synchronously through the scheduler's
// on-demand trigger methods so the sample runs to completion deterministically
// instead of waiting on the one-minute reminder floor.
// =============================================================================
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));
silo.AddLatticeBackup();
silo.AddLatticeBackupApi();
// Enable a daily full + hourly incremental schedule for every scope. The
// reminders are registered by EnsureScheduleAsync below; this sample then
// drives the captures by hand so it does not have to wait for them.
silo.ConfigureLatticeBackupSchedule(schedule =>
{
schedule.FullBackupScheduleEnabled = true;
schedule.FullBackupInterval = TimeSpan.FromDays(1);
schedule.IncrementalBackupScheduleEnabled = true;
schedule.IncrementalBackupInterval = TimeSpan.FromHours(1);
});
})
.Build();
Console.Write("Silo starting...");
await host.StartAsync();
Console.WriteLine(" ready.");
Console.WriteLine();
var grainFactory = host.Services.GetRequiredService<IGrainFactory>();
var scheduler = host.Services.GetRequiredService<ILatticeBackupScheduler>();
var catalog = host.Services.GetRequiredService<ILatticeBackupCatalogStore>();
var restore = host.Services.GetRequiredService<ILatticeBackupRestoreService>();
static byte[] Value(string s) => Encoding.UTF8.GetBytes(s);
static string Text(byte[] b) => Encoding.UTF8.GetString(b);
const string treeId = "orders";
const string restoredTreeId = "orders-restored";
var scope = BackupScopeSelector.WholeTree(treeId);
var source = grainFactory.GetGrain<ILattice>(treeId);
// --- Step 1: register the scope and its full + incremental schedule ---------
Console.WriteLine("Registering backup scope 'orders' with a full + incremental schedule...");
await scheduler.EnsureScheduleAsync(scope);
Console.WriteLine();
// --- Step 2: seed the source tree -------------------------------------------
const int keyCount = 8;
Console.WriteLine($"Seeding tree 'orders' with {keyCount} keys...");
for (var i = 0; i < keyCount; i++)
{
await source.SetAsync($"order:{i:D3}", Value($"amount={i * 10}"));
}
Console.WriteLine($" live count = {await source.CountAsync()}");
Console.WriteLine();
// --- Step 3: trigger a full backup on demand --------------------------------
Console.Write("Triggering a FULL backup...");
var fullBackupId = await scheduler.TriggerFullBackupAsync(scope);
Console.WriteLine($" captured {fullBackupId}.");
Console.WriteLine();
// --- Step 4: mutate the tree ------------------------------------------------
Console.WriteLine("Mutating the tree (change one, add two, delete one)...");
await source.SetAsync("order:003", Value("amount=CHANGED"));
await source.SetAsync("order:100", Value("amount=1000"));
await source.SetAsync("order:101", Value("amount=1010"));
await source.DeleteAsync("order:000");
Console.WriteLine($" live count = {await source.CountAsync()}");
Console.WriteLine();
// --- Step 5: trigger an incremental backup layered on the full --------------
Console.Write("Triggering an INCREMENTAL backup...");
var incrementalBackupId = await scheduler.TriggerIncrementalBackupAsync(scope);
Console.WriteLine($" captured {incrementalBackupId}.");
Console.WriteLine();
// --- Step 6: list the catalog -----------------------------------------------
Console.WriteLine("Backup catalog:");
long totalBytes = 0;
var backupCount = 0;
await foreach (var manifest in catalog.ListAsync())
{
var bytes = manifest.ContentDescriptors.Sum(d => d.ByteLength);
totalBytes += bytes;
backupCount++;
Console.WriteLine(
$" {manifest.Id} kind={manifest.Kind,-11} base={manifest.BaseBackupId ?? "<none>",-40} bytes={bytes}");
}
Console.WriteLine();
// --- Step 7: restore the latest backup into a fresh tree --------------------
// Restoring the incremental backup folds its base full backup and the increment
// into a single faithful image, written into a brand-new tree that did not
// exist before.
Console.Write($"Restoring {incrementalBackupId} into fresh tree '{restoredTreeId}'...");
var restoreResult = await restore.RestoreAsync(
new LatticeRestoreRequest(incrementalBackupId!, targetTreeId: restoredTreeId));
Console.WriteLine($" applied {restoreResult.EntriesApplied} entries over a chain of {restoreResult.ManifestChain.Count}.");
Console.WriteLine();
// --- Step 8: print the restored values and an inventory summary -------------
var restored = grainFactory.GetGrain<ILattice>(restoredTreeId);
Console.WriteLine("Restored values (should match the live tree after mutation):");
foreach (var key in new[] { "order:000", "order:001", "order:003", "order:100", "order:101" })
{
var live = await source.GetAsync(key);
var back = await restored.GetAsync(key);
var liveText = live is null ? "<absent>" : Text(live);
var backText = back is null ? "<absent>" : Text(back);
var match = liveText == backText ? "OK" : "MISMATCH";
Console.WriteLine($" {key}: live=\"{liveText}\" restored=\"{backText}\" [{match}]");
}
Console.WriteLine();
Console.WriteLine("Inventory summary (from the public catalog store):");
Console.WriteLine($" backup count = {backupCount}");
Console.WriteLine($" total catalog bytes = {totalBytes}");
Console.WriteLine($" restored live count = {await restored.CountAsync()} (expected {await source.CountAsync()})");
Console.WriteLine();
Console.WriteLine("Done: full + incremental capture round-tripped faithfully into a fresh tree.");
await host.StopAsync();
BackupAndRestore.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Orleans.Lattice.Samples.BackupAndRestore</RootNamespace>
<AssemblyName>Orleans.Lattice.Samples.BackupAndRestore</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" />
<ProjectReference Include="..\..\src\lattice.backup\Orleans.Lattice.Backup.csproj" />
<ProjectReference Include="..\..\src\lattice.api.backup\Orleans.Lattice.Api.Backup.csproj" />
</ItemGroup>
</Project>