Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion docs/site/src/content/docs/grains/grain-placement.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Grain placement and migration
description: Understand placement, resource-optimized defaults, and activation movement in Orleans.
ms.date: 08/17/2026
ms.date: 08/26/2026
ms.topic: concept-article
---

Expand Down Expand Up @@ -82,6 +82,16 @@ Activation-count-based placement applies the power-of-two-choices technique desc
:::code language="csharp" source="../snippets/compiled/Grains/PlacementSnippets.cs" id="prefer_local_grain":::
Placement happens when creating an activation. Changing cluster membership or a strategy doesn't move existing activations by itself.

## Direct placement with placement hints

A placement hint directs a new activation to a specific silo. Set <xref:Orleans.Runtime.Placement.IPlacementDirector.PlacementHintKey> in <xref:Orleans.Runtime.RequestContext> before making the grain call that can trigger activation. The built-in placement directors select the hinted silo when it belongs to the compatible candidate set after version compatibility and placement filters are applied.

The following grain injects <xref:Orleans.Runtime.IClusterMembershipService> and <xref:Orleans.Runtime.ILocalSiloDetails>, selects an active silo other than its own, and directs a worker activation there:

:::code language="csharp" source="snippets/placement/PlacementHints.cs" id="direct_placement_with_hint":::

The membership snapshot provides the silos known to the caller at that point in time. The grain call, rather than `GetGrain`, triggers activation and placement. If the worker is already active, Orleans routes the call to its existing activation. If cluster membership or compatibility changes before placement and the hinted silo isn't a candidate, the configured placement strategy selects another compatible silo. Restore the previous request-context value after the call because request context propagates to outgoing grain calls.

## Override the cluster default

Register a different default strategy only when all unannotated grains should use it:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Orleans.Runtime;
using Orleans.Runtime.Placement;

namespace GrainPlacement;

public interface IOrderWorkerGrain : IGrainWithStringKey
{
Task ProcessOrder();
}

public interface IOrderCoordinatorGrain : IGrainWithStringKey
{
Task ProcessOrder(string orderId);
}

public sealed class OrderWorkerGrain : Grain, IOrderWorkerGrain
{
public Task ProcessOrder() => Task.CompletedTask;
}

// <direct_placement_with_hint>
public sealed class OrderCoordinatorGrain(
IClusterMembershipService clusterMembership,
ILocalSiloDetails localSiloDetails)
: Grain, IOrderCoordinatorGrain
{
public async Task ProcessOrder(string orderId)
{
var targetSilo = clusterMembership.CurrentSnapshot.Members
.Where(member => member.Value.Status == SiloStatus.Active)
.Select(member => member.Key)
.FirstOrDefault(address =>
!address.Equals(localSiloDetails.SiloAddress))
?? throw new InvalidOperationException(
"No active remote silo is available.");

var previousHint = RequestContext.Get(
IPlacementDirector.PlacementHintKey);

RequestContext.Set(
IPlacementDirector.PlacementHintKey,
targetSilo);

try
{
var worker = GrainFactory.GetGrain<IOrderWorkerGrain>(orderId);
await worker.ProcessOrder();
}
finally
{
if (previousHint is null)
{
RequestContext.Remove(
IPlacementDirector.PlacementHintKey);
}
else
{
RequestContext.Set(
IPlacementDirector.PlacementHintKey,
previousHint);
}
}
}
}
// </direct_placement_with_hint>