Skip to main content

Index

Indexes enable efficient queries on Entities using different partition/sort keys than the original entity's configuration. Each entity supports up to 20 global secondary indexes (GSIs) and 5 local secondary indexes (LSIs).

Create an Index

Here's an example of creating a global secondary index on the userData entity. This index is called usersByUserName and uses userName as its partition key:

const usersByUserName = userData.index("byUserName", {
partition: ["userName"],
});

An index works much like an entity — you can run queries against it. Here's an example of querying the usersByUserName index:

await userByUserName.query({
userName: "sam",
});

This query will return all users where the userName attribute equals "sam".

Consider a case where you would like to query users based on their userName and age. For instance, if you want to retrieve all users named "sam" who are 30 years old, you can create a GSI with userName as the partition key and age as the sort key:

const usersByNameAndAge = userData.index("usersByNameAndAge", {
partition: ["userName"],
sort: ["age"],
});

With this index, you can perform efficient queries to retrieve all users named "sam" who are 30 years old:

await usersByNameAndAge.query({
userName: "sam",
age: 30,
});
info

For a complete guide to queries, see the entity query section.

Local vs Global Secondary Indexes

There are two types of secondary indexes you can use to speed up queries on your tables: Local Secondary Indexes (LSIs) and Global Secondary Indexes (GSIs). Both types of indexes allow you to query your data on alternative keys, providing more flexibility and performance. However, they are used in slightly different scenarios.

info

In Eventual, the type of secondary index created—LSI or GSI—depends on a few conditions. If the partition key remains the same and the table already has a sort key, a LSI is automatically created. On the other hand, if you specify a new partition key or the table did not have a sort key before, a GSI is created.

Local Secondary Indexes (LSIs)

LSIs maintain the same partition key as your table's primary key but allow for a different sort key. Since LSIs share the same partition key as the original table, they are scoped to a specific partition and provide quick access to data within that partition.

For example, a posts entity may use a postId for the sort key, but an LSI can be created to sort by postDate:

const posts = entity("posts", {
attributes: {
forum: z.string(),
userId: z.string(),
postId: z.string(),
postDate: z.string(),
},
partition: ["forum"],
sort: ["postId"],
});

const postsByDate = posts.index("postsByDate", {
sort: ["postDate"],
});

With this LSI, you can efficiently retrieve all posts, sorted by post date:

await postsByDate.query({
forum: "eventualAi",
});

Global Secondary Indexes (GSIs)

GSIs, on the other hand, allow for a completely different partition key and sort key. This allows for more flexibility as you can perform queries across all partitions of your table.

For instance, if you want to all posts by date for a user, you can create a GSI:

const postsByUserId = posts.index("postsByUserId", {
partition: ["userId"],
sort: ["postDate"],
});

This GSI allows you to efficiently retrieve all entities where the userId equals a specific value:

await postsByUserId.query({
userId: "sam",
});

Sparse Indexes

Unlike the entity, an index can have partition or sort key attributes that may be undefined on some entities. When part of the key is undefined, the entity will not exist in the index. We call these Sparse Indexes. Sparse Indexes can be GSIs or LSIs.

Lets modify the userData entity to contain information about memberships. Users do not need to be members and we only want to retrieve users that are members.

const userData = entity("userData", {
attributes: {
userId: z.string(),
userName: z.string(),
age: z.number(),
membershipStartDate: z.string().optional(),
},
partition: ["userId"],
});

const members = userData.index("members", {
sort: ["membershipStartDate"],
});

Now when we scan this new index, only members will be returned:

const memberEntries = await members.scan();

Querying and Scanning

Query and Scan operations on an Index behave the same as they do on an Entity.

See the documentation for Entity query and scan.