tenant_id column.
Without a reference table or GSI, looking up a row by email instead of tenant_id, for example, may require searching every shard.
Both can avoid unnecessary scatter reads, but add storage and work to writes.
The original table and row behind a GSI are called its owner table and owner row.
Reference tables
Reference tables are best for shared data that every shard in a group needs. If thecustomers table is sharded across the tenant_data shard group and is often joined
with common data from the countries table, keep a full copy of
countries on each shard and declare it a reference table.


countries data exists on every shard in the tenant_data
shard group, and Neki can send the join to shards based on the predicates on
customers. If a tenant_id predicate narrows the query to one shard, the join
executes on one shard. If the query scatters, each shard joins its customer rows
to its local countries copy. The final result is calculated by the router, but
joins stay local to each shard because any row from customers is co-located
with all rows from countries.
A query against countries alone can use any copy in the group.
The binding changes routing only. Neki assumes the copies already exist and
contain the same rows. It does not verify them, so an incomplete copy can
produce an incomplete result.
Configure a reference table and GSI
The following complete topology declares:countriesas a reference table expected on both shards intenant_data.usersas a table sharded bytenant_id.users_by_emailas an unsharded lookup table for the uniqueby_emailGSI.
countries, users, or
users_by_email, and it does not copy existing rows.
The GSI has two linked declarations. The database-level
global_secondary_indexes.by_email entry defines the lookup table and its
owner. The users.secondary_indexes entry activates that GSI for owner reads
and writes. The lookup table also needs an ordinary table binding so Neki can
route lookup rows.
owner_sk_columns names the owner primary-index columns stored in each lookup
row. It can be omitted when the owner has exactly one primary index whose
ignore_null value is false; Neki infers that index. Name
owner_sk_columns when the owner has more than one such primary index.
For a GSI, omitted scalar fields use their protobuf defaults:
schema is resolved from the lookup table when unambiguous, and unique,
ignore_null, and enabled default to false. A non-unique GSI must also set
owner_pk_columns so lookup rows for different owner rows remain distinct.
A reference table can omit shard_groups only when a schema, database, or
cluster default shard group resolves for it. Neki then uses that effective
default. Listing the groups explicitly makes every copy location visible in
the topology.
Activate a GSI safely
An enabled GSI does not backfill historical lookup rows. Enabling an incomplete GSI can return incomplete results without an error. Use this sequence:- Create the lookup table with the required keys and constraints.
- Declare both linked GSI entries and set
global_secondary_indexes.<name>.enabledtofalse. - Backfill the lookup table while also following owner-table changes.
- Verify every expected owner row has the correct lookup row.
- Set
enabled: trueand apply the topology at cutover.
Reference-table writes
An ordinaryINSERT, UPDATE, or DELETE against countries runs on every
distinct shard that holds a copy. Neki sends the statements concurrently and
waits for every shard before reporting completion.
For a group with four shards, one inserted row produces four shard executions
and four stored copies. Postgres replicas and indexes add their usual storage
overhead.
The router coordinates one backend transaction per participating shard.
Those transactions commit independently, so a commit can succeed on some
shards and fail on another.
GSI mapping
In the example,users is sharded by tenant_id, but the application also needs to look up a user by email:
tenant_id, so the table’s primary routing cannot identify
one shard. A GSI on email adds a lookup table that maps the email address to
the owning row’s tenant_id. For a supported single-table equality query,
Neki reads that mapping and uses it to route the original query to the correct
shard. The lookup table has its own placement, so this first step may itself
reach one shard or several.


users table.
This covering path applies only when the lookup row contains every owner column the plan needs.
Locking reads still fetch the owner row so Postgres locks the underlying record rather than the lookup entry.
A non-unique GSI may return several tenant_id values. Neki uses them to
narrow the owner query to the shard(s) that contain matches. Because reading the
GSI itself requires work, it’s best to use GSIs for highly selective lookups.
Otherwise, if the looked up value appears across most shards, the work needed to
run a GSI-assisted query approaches a scatter query, and the GSI also adds
overhead on the write path.
Query planning shows how to inspect
the resulting plan.
GSI mappings for writes
Neki maintains GSI lookup rows in the same request that writes the owner row. For an insert intousers, the router first checks mappings owned by other
tables that the new row references. It writes the users row, then writes the
email GSI mapping owned by users.
Neki supports one narrow kind of GSI-column update. It updates the owner row,
removes the old lookup row, and adds the new lookup row. All of these conditions
must be true:
- The update affects one active, globally unique, single-column GSI.
- The
SETlist changes only that indexed column. - The
WHEREclause contains only equality predicates. They pin every shard-key column and a unique key, so the update reaches one row on one shard. - The statement has no common table expression (CTE) or
RETURNINGclause. - The new value can be computed on the shard.
DEFAULTand values that the router must compute are not supported.
Global uniqueness
Postgres enforces uniqueness within each lookup-table shard. To make that constraint global, equal indexed values must always reach the same shard. An unsharded lookup table meets that condition. For a sharded lookup table, every shard-key column must also be one of the GSI’s indexed columns. Ifemail_lookup is sharded by email, duplicate addresses meet on the same shard
and conflict.
GSI limits and fallbacks
A covering read replaces the owner table with the lookup table in the SQL sent
to Postgres. Different RLS could change which rows are visible, so Neki fails
planning.
ignore_null applies only to indexed inputs. Owner keys and lookup routing
values remain strict because index-driven reads cannot reach a NULL-keyed
mapping.
For a non-unique GSI, Neki compares the estimated lookup cost with the fallback
route. It uses the GSI only when the lookup is expected to cost less.

