Grain Index
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 README.md, and llms.txt lists every page.What it shows
A grain index tracks a grain's typed state in a lattice tree, so you can ask
"which User grains are 18 or over?" without hand-maintaining a secondary
index and without activating every grain to find out.
This sample:
- Declares an index over
IUserGrain'sUserState, projectingAgeandCountry. Only the properties named withIncludeare projected. - Writes five users. Each grain enrols itself on its own write path, because its
state is annotated with
[Indexed]- there is no index maintenance code in the grain. (UserGrainalso derives from the optionalIndexedGrain<UserState>base class forGrain<TState>-styleState/WriteStateAsyncergonomics; it carries no enrolment logic, so[Indexed]alone is the opt-in.) - Runs a single-property comparison (
Age >= 18), which becomes one contiguous range scan over the order-preserving key encoding rather than a full scan plus a filter. - Runs a conjunction (
Age >= 18 && Country == "UK"). An index entry carries exactly one property, so the planner issues one range scan per property and intersects the resulting grain keys. - Runs a disjunction (
Age < 18 || Age >= 60), which unions its branches and de-duplicates, so a grain matching both is yielded once. - Confirms a match against live grain state, because the index is eventually consistent with respect to grain state.
Run it
dotnet run --project samples/GrainIndex
Expected output
Wrote 5 users.
Adults (Age >= 18):
carla
alice
dan
Users in the UK:
alice
bob
dan
UK adults (Age >= 18 && Country == "UK"):
alice (age 34)
dan (age 61)
Under 18 or 60+: erin, bob, dan
Done.
Result order follows the index's key ordering, not insertion order: entries are ordered by the encoded property value, then by grain key, and a conjunction follows the scan of its most selective clause, which seeds the grain keys the other clauses narrow.
Key points
- Opt in twice. The silo declares the index and the grain annotates its
state with
[Indexed]. Nothing is tracked by accident. Includeis the whole projection. There is no index-everything mode, because every indexed property costs write amplification on the grain's write path.WriteStateAsyncrepublishes entries atomically. A reader never sees a grain half-way through a projection.- Queries read the index, not the grains. Re-read the grain when the answer must be authoritative - the sample does this in the conjunction query.
- This sample relies on activation enrolment only. A real deployment with a
pre-existing grain population also registers an
IGrainKeySourceso the reminder-driven backfill can onboard grains that are dormant.