Kanject marker indexes

View .md

A Kanject marker index is not a GSI. It is a model-declared access path materialized as reserved rows in the base table. Markers can enforce uniqueness, group related items, order values for range reads, or support search. Kanject generates both the index-aware write methods that maintain those rows and the finder methods that read through them.

The model is the definition

csharp
[Searchable(template: "Roast#{RoastLevel}")]public string Name { get; set; }​[Range(name: "Price", template: "Roast#{RoastLevel}")]public int PriceCents { get; set; }​[Collection(name: "Roast")]public string RoastLevel { get; set; }​[Unique("Sku")]public string Sku { get; set; }

The annotation carries more than a property name. A marker key can depend on its name, a template or rangeTemplate, and several properties from the entity. Composite unique markers also share a named group. That model metadata — together with the matching generator contract — is the canonical definition.

  • [Unique] / [CompositeUnique] reserve a key for a value or named value group so competing owners cannot claim the same marker.
  • [Collection] materializes a membership access path, optionally composing other properties into its ordering key.
  • [Range] materializes a value-ordered access path for generated range finders.
  • [Searchable] materializes the tokens and references used by generated search methods.

Writes maintain the base item and markers together

csharp
products.BeginTransaction();await products.InsertWithIndexAsync(product);await products.CommitAsync();​products.BeginTransaction();await products.UpdateWithIndexAsync(updated, previous);await products.CommitAsync();​products.BeginTransaction();await products.RemoveWithIndexAsync(product);await products.CommitAsync();

The generated *WithIndexAsync methods stage the base-item operation and its marker operations in the same transaction. BeginTransaction() opens that unit of work and CommitAsync() is the point that submits it; the Kanject analyzers warn when an index-aware write is missing either one. Updates receive the previous entity because changing a participating property may require deleting old marker keys before inserting the new ones.

Generated finders turn markers into access paths

csharp
var (roastProducts, metadata) =    await products.FindProductsByRoastAsync(page, roastLevel);​var product =    await products.FindProductBySkuAsync(sku);

Each finder is named after its marker: [Collection(name: "Roast")] generates FindProductsByRoastAsync, [Unique("Sku")] generates FindProductBySkuAsync, and the range and search markers on the same model add FindProductsByPriceAsync and SearchByNameAsync.

A generated finder usually reads marker rows first and then resolves their base-item references. This is why markers are not only constraints: collection, range, and search markers are application query paths. DynoStudio can expose the definition, composed key shape, reserved rows, owner references, generated finder, and the extra read phase involved.

A generated Is…UniqueAsync method (here, IsProductSkuUniqueAsync) is useful for friendly validation, but it is not the concurrency boundary by itself. Correctness comes from the transactional index-aware write; another writer can race a standalone precheck.

Existing data needs a migration

Adding or changing an annotation governs index-aware writes made after that code ships; it does not populate marker rows for items already stored. Existing-data backfill, reconciliation, or removal must run as a separate durable migration that consumes the same Kanject model metadata and marker composer as the application.

  • Pin the Kanject generator or marker-contract version and record a definition hash.
  • Checkpoint progress so a long run can resume safely, and make retries idempotent.
  • Compose every marker type from complete entities, including templates, search tokens, reference fields, and composite groups.
  • Detect unique collisions without choosing a winner, and report enough owner information to resolve them.
  • Account for concurrent application writes, throttling, cancellation, audit history, and a controlled rollback or cleanup path.

Related: Tables, indexes & replicas for supported control-plane DDL · CREATE GLOBAL INDEX for native DynamoDB secondary indexes · Kanject.Core.NoSqlDatabase for the annotations and generated repositories.

Was this page helpful?