API with NestJS #66. Improving PostgreSQL performance with indexes using MikroORM

NestJS SQL

This entry is part 66 of 121 in the API with NestJS

The complexity of our database queries grows together with our application. Due to that, the time necessary to complete the queries. A common way to address this problem is using indexes. In this article, we explore indexes both through MikroORM and SQL queries.

The idea behind indexes

Across the last few articles, we’ve defined a table where we keep posts. Among others, it contains the field.

We might need a routine query to look for posts written by a specific author.

We must be aware that the above query needs to scan the entire table to find matching entities. Sometimes iterating a table from cover to cover might not be good enough performance-wise. We can deal with this issue by creating an index.

The goal of the index is to make our queries faster by creating a data structure that organizes a table using a particular column.

The above command creates an index using the column of the table. We can imagine the index as key and value pairs. In our case, the keys are the author ids, and the values point to particular posts.

author_idpost_id
11
12
13
24
35

In real life, the data structures used by PostgreSQL for indexing are more elaborate to maximze the performance. By default, PostgreSQL uses the B-tree data structure when creating indexes where each leaf contains a pointer to a particular table row.

Thanks to the sorted data structure, we can quickly find all posts written by a particular author. However, besides the noticeable advantage when fetching data, there are some crucial downsides.

Every time we insert or update data, PostgreSQL also needs to update the indexes. While indexes can speed up our SELECT queries, they slow down our inserts and updates. Besides the performance, indexes create data structures that need additional space.

Creating indexes with MikroORM

To create an index using MikroORM, we need to use the decorator.

post.entity.ts

Adding the above to our schema and running results in the following migration:

Migration20220614231701.ts

We can use the option in the decorator to change the auto-generated name of the index to something else.

Multi-column indexes

Sometimes we might notice that we often make queries with multiple conditions. For example, let’s look for posts authored by a certain user and deleted during the last month.

We can easily create a multi-column index using an SQL query.

We can achieve the same thing with MikroORM by using the decorator with the argument.

post.entity.ts

Creating the above index and running generates the following migration:

Migration20220615230639.ts

Unique indexes

In one of the previous articles, we’ve defined a table for a user.

One of the columns of the above table is email, which we’ve declared as unique.

Whenever we define a unique constraint, PostgreSQL automatically creates a unique index to enforce the constraint.

We don’t need to manualy create indexes on unique columns, PostgtreSQL does that for us when we define the constraint.

Remember that PostgreSQL also creates the unique constraint and index for primary keys. Because of that, every table has at least one index if it contains a primary key.

Creating unique indexes with MikroORM

To create a unique constraint and index with MikroORM, we can use along with the decorator.

user.entity.ts

An alternative approach to the above is using the decorator.

user.entity.ts

Defining the above schema and running causes the following migration to be created:

Migration20220615013946.ts

Types of indexes

So far, all the indexes we’ve created in this article used the B-tree data structure. While it fits most of the cases, there are some other options. For example, we can use the property of the decorator to provide an SQL query used to create the index.

Generalized Inverted Indexes (GIN)

GIN indexes fit best where the values contain more than one key. A good example would be the array data type. However, they can also come in handy when implementing text searching.

Please notice that GIN indexes might not work out of the box.

Hash indexes

Hash index uses the hash table data structure and might come in handy in some specific use-cases.

Block Range Indexes (BRIN)

The Block Range Indexes can come in handy when used with data types that have a linear sort order.

Generalized Search Tree (GIST)

The GIST indexes can be helpful when indexing geometric data and implementing text search. In some cases, it might be preferable over GIN.

Summary

In this article, we’ve gone through indexes and how they can affect the performance of our queries. The above includes improving the performance and, in some cases, making it worse. We’ve also learned how to create indexes through SQL queries and MikroORM. Besides regular indexes, we’ve also created multi-column indexes and used index types other than B-tree. All of the above gives us a solid introduction to how indexes work and what are their pros and cons.

Series Navigation<< API with NestJS #65. Implementing soft deletes using MikroORM and filtersAPI with NestJS #67. Migrating to TypeORM 0.3 >>
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments