Skip to content

Recipe 37 — Data-Affinity Placement

Problem: You’re running a compute tasklet that reads from a leased memory region. If the tasklet lands on a different node than the data, every read crosses the fabric — adding latency and consuming bandwidth.

Solution: Use Affinity::new(Strength::Preferred, Target::node(data_node)) to tell the scheduler to prefer the node that holds your data lease.

Affinity is not the same thing as replicated-resource placement policy. Placement defines the legal failure-domain envelope for a logical resource. Affinity then helps the scheduler choose a specific node, rack, GPU, or data-local lease inside that envelope. Affinity must not expand placement into a provider, region, or availability zone that the resource policy did not authorize.

Core grafOS API Path

The direct grafos-std path is: acquire the data lease, read the node that owns it, then request compute with an affinity hint to that node.

use grafos_std::cpu::CpuBuilder;
use grafos_std::mem::MemBuilder;
use grafos_std::affinity::{Affinity, Strength, Target};
// Step 1: Allocate a memory lease — note which node it lands on.
let mem_lease = MemBuilder::new().bytes(1024 * 1024).lease_secs(300).acquire()?;
let data_node_id = mem_lease.node_id(); // u128
// Step 2: Allocate a CPU lease with preferred data affinity.
let cpu_lease = CpuBuilder::new()
.single_core()
.affinity(Affinity::new(Strength::Preferred, Target::node(data_node_id)))
.lease_secs(60)
.acquire()?;
// The scheduler will prefer placing the CPU lease on the same node as
// the memory lease, reducing cross-fabric reads.
# let _ = cpu_lease;
# Ok::<(), grafos_std::error::GrafosError>(())

When to use Required instead of Preferred:

Use Strength::Required only when the workload cannot function if the data is remote — for example, a shared-memory tasklet where workers access the memory region via direct pointers. If the scheduler can’t find capacity on the data node, the request fails rather than silently degrading to remote access.

// Required: fail if we can't co-locate.
let cpu_lease = CpuBuilder::new()
.single_core()
.affinity(Affinity::new(Strength::Required, Target::node(data_node_id)))
.lease_secs(60)
.acquire()?;
# let _ = cpu_lease;
# Ok::<(), grafos_std::error::GrafosError>(())

When NOT to use data affinity:

  • If your workload does a single small read at startup and then computes independently — the fabric read cost is amortized over the compute time, and affinity over-constrains placement for no benefit.
  • If you need fault tolerance more than locality — over-constraining to a single node means you can’t fail over. Use replicated-resource placement policy to express the failure-domain envelope, then use affinity as a local preference within each allowed domain.

Multi-Cloud Placement Variant

For a replicated resource, start with placement and then layer data affinity underneath it:

use grafos_replicated::{
FailureDomain, FailureDomainLevel, PlacementPolicy, PlacementPreference,
ReplicatedResourceSpec, ResourceKind,
};
use grafos_std::affinity::{Affinity, Strength, Target};
let placement = PlacementPolicy::new()
.allow(FailureDomain::region("aws", "us-east-1"))
.allow(FailureDomain::region("gcp", "us-central1"))
.require_distinct(FailureDomainLevel::CloudProvider)
.prefer(PlacementPreference::PreferLowerLatency);
let orders = ReplicatedResourceSpec::builder("orders", ResourceKind::Queue)
.placement(placement)
.build()?;
// Within whichever allowed domain the scheduler is evaluating, prefer the
// node that already hosts the hot data lease. This preference cannot move
// the queue outside the replicated-resource placement envelope above.
let data_node_id: u128 = 42; // node holding the data lease
let local_affinity = Affinity::new(Strength::Preferred, Target::node(data_node_id));
# let _ = (orders, local_affinity);
# Ok::<(), grafos_replicated::PolicyError>(())

This is the intended boundary: placement carries the availability promise; affinity carries the locality hint.

See also:

  • docs/spec/affinity-request-model.md — wire format
  • docs/grafos/affinity-taxonomy.md §5.2 — state/data affinity category
  • docs/grafos-std-guide.md — affinity constraints subsection