sales
Contacts CRM
One index answering two different questions.
Contacts searched by company and by tag. Rather than two indexes, one index whose key holds a type-prefixed value - so which question it answers depends on what was written into it.
The model
Contact
A person, plus the index rows that make them findable.
- pk
- CONTACT#<contactId>
- sk
- PROFILE
Attributes: name (S), company (S), lastSeenAt (N), GSI1PK (S), GSI1SK (S)
TagLink
A row that exists only to put a contact into the same index under a tag.
- pk
- CONTACT#<contactId>
- sk
- TAG#<tag>
Attributes: GSI1PK (S), GSI1SK (S)
Access patterns
- Query · GSI1Contacts at a company
Everyone at one company, alphabetically.
- QueryA contact and its tags
The profile row and every tag row for one person, in one read.
- GetItemRead a contact
One person's profile.
Design notes
The prefix is what the index is aboutwhy
GSI1PK holds COMPANY#halden-mill on one row and TAG#decision-maker on another. The index does not know the difference - it sorts strings - but a Query for COMPANY# can only match company rows, so one index behaves as several, at the cost of one index rather than three.
A tag is a row, not a listwhy
Storing tags as a list on the contact would make finding everyone with a tag a Scan. Writing one small row per tag puts each into the index under its own key, so the lookup is a Query. Adding a tag is a write, removing one is a delete, and the contact itself never changes.
Indexes cost writes, not just storagetrade-off
Every item carrying GSI1PK is copied into the index, and every update to it is a second write. Three indexes would be three copies of the projected attributes and three writes per change; one overloaded index is one. That saving is the reason to do this; every row in the index having to share one key shape is what it costs.
The tag query is real, but this page can run only onetrade-off
TAG#decision-maker rows sit in the same index as the COMPANY# rows, and a Query for GSI1PK = TAG#decision-maker returns Nadia and Priya across both companies. The console offers one index read per model - the resolver keys every index query from the same seeded row - so the tag question is in the table to be seen rather than pressed. Look at the TAG# rows: that is the second question, already indexed.
One index means one shape for everythingtrade-off
Every question this index answers has to fit its two keys, and they all share a sort key. Company rows sort by name, so tag rows must too, whether or not that is what you would have chosen. Overloading trades flexibility for cost, and the point at which a query needs a different ordering is the point at which it needs its own index.
Taught in the course
- A different key for a different question - A secondary index re-keys your items so you can Query them on a new axis.
- One index, many questions - Different entity types share generic index keys, so one index serves several access patterns.
Everyone at one company, alphabetically.
Run an operation to see the raw engine response.
The in-browser engine is a preview build. Transactions, vector search, streams, tags and TTL are among the operations it doesn't implement yet.