<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nir Tayeb</title>
    <description>The latest articles on DEV Community by Nir Tayeb (@nirtayeb).</description>
    <link>https://dev.to/nirtayeb</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1269728%2Fe9590562-1a51-4e7e-809d-adf211a854b7.JPG</url>
      <title>DEV Community: Nir Tayeb</title>
      <link>https://dev.to/nirtayeb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nirtayeb"/>
    <language>en</language>
    <item>
      <title>Creative database denormalization techniques</title>
      <dc:creator>Nir Tayeb</dc:creator>
      <pubDate>Tue, 30 Jul 2024 19:18:50 +0000</pubDate>
      <link>https://dev.to/nirtayeb/creative-database-denormalization-techniques-2gfb</link>
      <guid>https://dev.to/nirtayeb/creative-database-denormalization-techniques-2gfb</guid>
      <description>&lt;p&gt;In the early stages of application development, the data schema is typically designed with normalization as the guiding principle. However, as the application grows and tables expand, specific queries can become sluggish, negatively impacting the user experience. In such situations, it becomes essential to consider denormalizing the data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is denormalization?
&lt;/h2&gt;

&lt;p&gt;Denormalization involves strategically duplicating a small amount of data across tables, reducing the need for joins that combine data from multiple sources. Another denormalization technique is maintaining precomputed data aggregations instead of calculating them in real time. While this approach introduces some data redundancy, it can significantly enhance query performance.&lt;br&gt;
Some "textbook" examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Classic Approach: Instead of joining the "orders" and "customers" tables, which might be large, to retrieve the customer name, we add the customer name as a column in the orders table.&lt;/li&gt;
&lt;li&gt;The "Justin Bieber" Case: In a social media app, rather than maintaining the relationship of "users liked a post" and then aggregating the count of likes for a post, we keep the current likes_count in the posts table. This approach is named the "Justin Bieber" case because Instagram used it to manage likes for celebrity posts.&lt;/li&gt;
&lt;li&gt;Short-Circuiting: In scenarios with three or more levels of joins, such as organization → users → categories → posts, to eliminate one or more join levels, we add organization_id and/or user_id directly to the posts table.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What are the drawbacks of denormalization?
&lt;/h2&gt;

&lt;p&gt;After denormalizing the data, new problems arise, including the need for extra storage space due to data duplication. When a value changes, all its copies in other tables must be updated to avoid anomalies and discrepancies. Updating all the copies of the data can lead to longer write operations.&lt;/p&gt;

&lt;p&gt;These are classic textbook examples of denormalization. Databases have evolved, and we have new tools to maintain our basic data model.&lt;/p&gt;
&lt;h2&gt;
  
  
  Materialized view
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What is a materialized view?
&lt;/h3&gt;

&lt;p&gt;A materialized view is a database object that includes a copy of a query's result. This way, we don't need to fetch, process, and combine the data from all the tables involved in the query or aggregate and group the data repeatedly.&lt;br&gt;
Examples (postgres syntax):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE MATERIALIZED VIEW MV_MY_VIEW
AS SELECT * FROM &amp;lt;table_name&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The supported RDBMS are&lt;/em&gt; Oracle (&amp;gt;8i), PostgreSQL (&amp;gt;9.3, since 2013), SQL Server (&amp;gt; 2000), DynamoDB, and BigQuery.&lt;br&gt;
MySQL doesn't support it out of the box.&lt;br&gt;
&lt;em&gt;Stream processing frameworks:&lt;/em&gt; Apache Kafka (since v0.10.2), Apache Spark (since v2.0), Apache Flink, Kinetica DB, Materialize, and RisingWave all support materialized views on data streams.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The benefits of using materialized views:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to change the DB schema&lt;/li&gt;
&lt;li&gt;Faster data retrieval because it doesn't re-calculate but stores the results&lt;/li&gt;
&lt;li&gt;It's possible to index the results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The cons of using materialized views:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Usually, materialized views are not auto-refreshed, so data may not be fresh.&lt;/li&gt;
&lt;li&gt;Maintainance outside of the code&lt;/li&gt;
&lt;li&gt;Requires more storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Offload as a Document
&lt;/h2&gt;

&lt;p&gt;When I worked at Tapingo/GrubHub a few years ago, we aimed to decouple the ordering system from the central database. One reason, though not the primary one, was the frequent need to fetch related order data for various purposes, such as payments, POS display, ticket printing, Kitchen Display System, courier information, and order content.&lt;br&gt;
We addressed this by creating a comprehensive document encapsulating all relevant data contexts and distributing it to various microservices. This document included duplicate data about shops and customers, which was accurate for the specific time. Each microservice had the autonomy to decide where and how long to store the document, allowing for fast query times by order-id, shop-id, and other indices.&lt;br&gt;
The document could be stored in a cache like Redis or MongoDB or streamed/pushed to a message broker or stream processing system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Array/JSON columns
&lt;/h2&gt;

&lt;p&gt;At EasyTeam, we implemented a "take a break feature," allowing admins to define which breaks are available during a shift, which the worker then clocks in for.&lt;br&gt;
A simple approach would be to create a many-to-many table between the scheduled shift and break types tables. However, since scheduled shifts are replicated frequently (new shifts every week), this would lead to an enormous many-to-many table. Instead, we used an array column on the scheduled shifts table to store the break types' IDs, with a GIN index on the column for faster searches.&lt;br&gt;
While this method may compromise data integrity, it significantly reduces the stored data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;From my experience, joining a table with a JSON field and selecting the JSON field in the query (either with *or specifically) could hurt query performance.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;In summary, denormalization helps fine-tune frequent heavy-read queries that lack performance. Use it wisely to avoid complex maintenance, extra storage costs, and data integrity.&lt;/p&gt;




&lt;p&gt;This post was originally published on my newsletter, &lt;a href="https://percentile99th.substack.com/" rel="noopener noreferrer"&gt;Percentile 99th&lt;/a&gt;. If you wish to learn how to improve application performance, I will write about it extensively there.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>learning</category>
      <category>development</category>
    </item>
    <item>
      <title>Latency Under the Lens</title>
      <dc:creator>Nir Tayeb</dc:creator>
      <pubDate>Tue, 12 Mar 2024 15:59:21 +0000</pubDate>
      <link>https://dev.to/nirtayeb/latency-under-the-lens-473n</link>
      <guid>https://dev.to/nirtayeb/latency-under-the-lens-473n</guid>
      <description>&lt;p&gt;In the past, I was usually trying to fine-tune the code of my application, either by coding better or using the most performant tools and techniques. But in several recent jobs, I've noticed that the performance issue was not actually in the code but was "around" the application.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timeouts between the printer at the locations and my server - in the APM, the request processing time takes tens of milliseconds, so why a timeout?&lt;/li&gt;
&lt;li&gt;Long query time to database - but the query was optimized and ran a few milliseconds on the query client when tested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue was high latency between the client and the server because of a nonoptimal geolocation deployment of servers.&lt;/p&gt;

&lt;p&gt;For example, the database deployed in the west region while the application server was in the east region. In another case I had, the app servers and the database were on two different continents (Europe and the U.S.)&lt;/p&gt;

&lt;p&gt;Nowadays, this issue is too common because provisioning servers is so easy, trying to keep cloud costs cheap and the separation between developers and the cloud infrastructure maintainers.&lt;/p&gt;

&lt;p&gt;Let's demonstrate the issue by exploring the latency difference between a few countries. We will use &lt;a href="https://wondernetwork.com/pings"&gt;https://wondernetwork.com/pings&lt;/a&gt;, one of the greatest sites I found a couple of years ago, telling the story of the importance of latency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferohd4maz5a7ao805zs4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferohd4maz5a7ao805zs4.png" alt="Table of ping time between countries" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The website runs about 30 pings and shows the average; a ping is the time for the round trip between the sender and receiver (Client ↔ Server).&lt;/p&gt;

&lt;p&gt;So, to establish the first TCP connection between a client and a server (Browser and Application Server, App Server and DB, etc.) - it takes ping-time X 1.5 to pass the 3-Way handshake.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fycmxq1rzalwe58o5c9tl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fycmxq1rzalwe58o5c9tl.png" alt="a graph of the TCP handshake" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, each transferring of packet of data will take the ping time. A standard TCP packet is up to 1.5KB, so to pass 15KB of data, you wait for at least a ping-time X 10 packets if none of the packets dropped in the middle.&lt;/p&gt;

&lt;p&gt;Now imagine how long it takes to pass 100 records of shop listings with their full catalog and item descriptions between an app server in Europe and a database server in the U.S. Or how long it takes the browser to download your single-page application from your servers.&lt;/p&gt;

&lt;p&gt;There are many ways to decrease the latency of your app, which I will elaborate on in the following posts in the latency series. but to name a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually align the deployment of your servers in the same region on your cloud provider&lt;/li&gt;
&lt;li&gt;Working with private networks inside your cloud provider&lt;/li&gt;
&lt;li&gt;Using a CDN for static content&lt;/li&gt;
&lt;li&gt;Keeping connections alive between sender and receiver&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post was originally published on my newsletter, &lt;a href="https://percentile99th.substack.com/"&gt;Percentile 99th&lt;/a&gt;. If you wish to learn about the ways to decrease latency and more about application performance, I write about it extensively there.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>devops</category>
      <category>learning</category>
    </item>
    <item>
      <title>Essential Database Optimizations Before Re-Architecting Your Backend</title>
      <dc:creator>Nir Tayeb</dc:creator>
      <pubDate>Fri, 23 Feb 2024 09:23:56 +0000</pubDate>
      <link>https://dev.to/nirtayeb/the-six-pillars-of-database-driven-application-performance-4mp1</link>
      <guid>https://dev.to/nirtayeb/the-six-pillars-of-database-driven-application-performance-4mp1</guid>
      <description>&lt;p&gt;In the last post, I covered &lt;a href="https://dev.to/nirtayeb/6-ways-youre-using-orms-wrong-and-how-to-fix-4edp"&gt;six ways ORMS made writing poorly performant code easy&lt;/a&gt;. But it is just a part of the story.&lt;/p&gt;

&lt;p&gt;Ensuring your applications run efficiently is essential. Before diving into complex solutions like caching, microservices, or rewriting code in different languages, it’s vital to solidify the fundamentals. By focusing on six key pillars, you can significantly boost your application’s performance, achieving up to an 80–90% improvement. Let’s explore these often-overlooked foundational techniques that can dramatically enhance the performance of your database-driven applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Side notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I may refer to RDBMS and SQL in the post, but these principles apply to NoSQL systems and beyond.&lt;/li&gt;
&lt;li&gt;This post was also published in my newsletter, containing &lt;a href="https://percentile99th.substack.com/p/the-six-pillars-of-database-driven" rel="noopener noreferrer"&gt;the 6 pillars with more depth&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start,&lt;/p&gt;

&lt;h2&gt;
  
  
  Database version
&lt;/h2&gt;

&lt;p&gt;New major versions are coming with new features and may include performance improvements. It is 200% right if we refer to PostgreSQL.&lt;/p&gt;

&lt;p&gt;Benchmark made by &lt;a href="https://www.enterprisedb.com/blog/performance-comparison-major-PostgreSQL-versions" rel="noopener noreferrer"&gt;EDB &lt;/a&gt;shows a 47% performance enhancement between PostgreSQL v9.6 and v15 and a 10.4% upgrade from v12 to v15. V16 has as well some minor improvements. You can read more in &lt;a href="https://benchant.com/blog/postgresql-v16-performance" rel="noopener noreferrer"&gt;this post by "benchant.com"&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;The database is the heart of your application. If it doesn't have enough computing resources, your whole application will be waiting for these resources to be available.&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU
&lt;/h3&gt;

&lt;p&gt;For small-medium size, you need a minimum of 4 cores for parallelism of your workloads, connection concurrencies, and background jobs done by the DB (i.e., journaling). Scale up with these considerations in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Workload Characteristics: CPU-intensive workloads (e.g., complex calculations and heavy transactions) require more cores for better performance.&lt;/li&gt;
&lt;li&gt;Concurrency: Higher concurrent connections and transactions can increase CPU requirements.&lt;/li&gt;
&lt;li&gt;Database Size and Complexity: Larger databases or those with complex schemas and queries may benefit from additional CPU resources to handle the increased processing demands.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Memory
&lt;/h3&gt;

&lt;p&gt;RAM impacts the database's ability to cache data, work with indexes, and process queries efficiently, significantly affecting overall performance. For small-medium sizes require at least 4GB of RAM and scale up by these considerations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database Working Set Size: Ideally, your server should have enough RAM to hold the frequently accessed ("hot") portion of your database (tables and indexes) in memory to reduce disk I/O.&lt;/li&gt;
&lt;li&gt;Connection Overhead: PostgreSQL uses a multi-process architecture, where each connection has its dedicated process. More connections imply more RAM for connection overhead. As a rough estimate, plan for about 10 MB (or more) per connection.&lt;/li&gt;
&lt;li&gt;Workload Type: Read-heavy workloads benefit from more RAM for caching, reducing read operations from disk. Write-heavy workloads may benefit less from caching but require sufficient RAM for sorting and transaction logs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;The storage size varies from one application to another, but what is important here is the type of storage and its IO performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage Type: SSDs should be your choice&lt;/li&gt;
&lt;li&gt;Provisioned IOPS: Consider using "provisioned IOPS" storage for high-performance workloads to ensure consistent and fast disk I/O.&lt;/li&gt;
&lt;li&gt;Storage Auto-scaling: Enable auto-scaling for storage to adjust as your database grows automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Scaling up your system usually costs more money. Monitor your current resources' use closely and consider ways to decrease the use of the resources using query/structure optimizations (RAM/CPU/storage) before adding more resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Network proximity / Geographics:
&lt;/h2&gt;

&lt;p&gt;It's best to keep your database and application server as close to each other as possible, network-wise so that they can communicate more efficiently. One way to do this is by provisioning them in the same region on your cloud service and putting them within the same private virtual network. Also, it's a good idea to use private IP addresses for communication between them rather than public IPs, which will help reduce the number of network hops and increase the speed of data transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connections Management
&lt;/h2&gt;

&lt;p&gt;Opening a connection to the database is an expensive operation. It includes the DNS resolving, TCP handshake, TLS Handshake, authentication, and setting up internal resources such as session objects and watchdogs. So, you should be conscious about opening a connection to the DB and reusing them as much as possible.&lt;/p&gt;

&lt;p&gt;The way to achieve it is by using connection pooling. It maintains a set number of connections and opens new ones until reaching a maximum value of connections. So, your application code should always have an open connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amount of queries in a single request/transaction
&lt;/h2&gt;

&lt;p&gt;Whether you use connection pooling or not, your database/pool has a limit on the maximum number of open connections. You should also verify the number of queries and their processing time so you won't get to the point where your code is waiting for a connection to be available instead of running your business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should you look for?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The N+1 problem&lt;/li&gt;
&lt;li&gt;Querying too much unnecessary data:

&lt;ul&gt;
&lt;li&gt;Filtering fetched data in code instead of in your queries.&lt;/li&gt;
&lt;li&gt;Aggregate fetched data in code instead of in a query (count/sum/averages/min/max/etc)&lt;/li&gt;
&lt;li&gt;Fetch fields without using them (look up big text fields or tables with a lot of columns)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use joins instead of multiple separate queries.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Optimize query processing time.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fine-tune condition evaluation with indexes
&lt;/h3&gt;

&lt;p&gt;Imagine you have a table of users containing ID, name, and birth date. Your app has a search function for all the users who have a Birthday today. Usually, the database will scan the table data row by row and filter all the records with this birthday. Imagine this table has 1M records.&lt;/p&gt;

&lt;p&gt;Indexes are like a sidecar to your tables; they contain a "table of contents" in various data structures, so when you query data, it will be possible to access the records without scanning all table content. The index contains the data of one or more of your columns, but instead of using a list to hold the data, it uses other data structures like trees and a hash table and divides the column value into different parts so the search will be faster. full table scan is O(n), while index scan usually is O(Log(n)) or O(1), depends on the index.&lt;/p&gt;

&lt;p&gt;So now, instead of scanning the entire table to find users with a birthday, it checks the index, gets pointers for the records in the table (or, more accurately, to the exact pages in the filesystem), and then retrieves the data.&lt;/p&gt;

&lt;p&gt;To understand why indexing is crucial, what to index, and what kind of indexes are available, &lt;a href="https://percentile99th.substack.com/p/the-six-pillars-of-database-driven" rel="noopener noreferrer"&gt;you can read here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fine-tune data fetching with Partitioning
&lt;/h3&gt;

&lt;p&gt;If you deal with big data and need more than indexing, partitioning the data will let you only query the relevant portion. For example, if partitioning the data by "Year," you will query only data from 2024, and all 2023 won't be retrieved from the storage if you haven't explicitly asked for it (partition is like a different storage unit). If most queries are in filtering by 2024, it will dramatically enhance your query performance.&lt;/p&gt;

&lt;p&gt;Partitioning has overhead in maintenance, and it can lead to a spike in lock manager waits and negatively impact user query performance. Increasing smaller tables and multiple indexes and partitions can generate many locks per query, causing contention problems.&lt;/p&gt;




&lt;p&gt;Based on my experience, focusing on these 6 pillars can significantly enhance the performance of the application by 80-90%, even before implementing any additional solutions such as caching, rewriting specific parts of the application in more performant languages, dividing into microservices, or using NoSQL.&lt;/p&gt;

</description>
      <category>database</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Six Pillars of Database-Driven Application Performance</title>
      <dc:creator>Nir Tayeb</dc:creator>
      <pubDate>Thu, 22 Feb 2024 23:19:01 +0000</pubDate>
      <link>https://dev.to/nirtayeb/the-six-pillars-of-database-driven-application-performance-42mp</link>
      <guid>https://dev.to/nirtayeb/the-six-pillars-of-database-driven-application-performance-42mp</guid>
      <description>&lt;p&gt;In the last post, I covered &lt;a href="https://dev.to/nirtayeb/6-ways-youre-using-orms-wrong-and-how-to-fix-4edp"&gt;six ways ORMS made writing poorly performant code easy&lt;/a&gt;. But it is just a part of the story.&lt;/p&gt;

&lt;p&gt;In this post, I will cover the bigger picture of working against a database and six significant topics that will most impact your application performance that you need to be conscious of and can decrease the need to add complexity to your system (like caching, multiple different DB's, etc.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Side notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I may refer to RDBMS and SQL in the post, but these principles apply to NoSQL systems and beyond.&lt;/li&gt;
&lt;li&gt;This post was also published in my newsletter, containing &lt;a href="https://percentile99th.substack.com/p/the-six-pillars-of-database-driven"&gt;the 6 pillars with more depth&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start,&lt;/p&gt;

&lt;h2&gt;
  
  
  Database version
&lt;/h2&gt;

&lt;p&gt;New major versions are coming with new features and may include performance improvements. It is 200% right if we refer to PostgreSQL.&lt;/p&gt;

&lt;p&gt;Benchmark made by &lt;a href="https://www.enterprisedb.com/blog/performance-comparison-major-PostgreSQL-versions"&gt;EDB &lt;/a&gt;shows a 47% performance enhancement between PostgreSQL v9.6 and v15 and a 10.4% upgrade from v12 to v15. V16 has as well some minor improvements. You can read more in &lt;a href="https://benchant.com/blog/postgresql-v16-performance"&gt;this post by "benchant.com"&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;The database is the heart of your application. If it doesn't have enough computing resources, your whole application will be waiting for these resources to be available.&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU
&lt;/h3&gt;

&lt;p&gt;For small-medium size, you need a minimum of 4 cores for parallelism of your workloads, connection concurrencies, and background jobs done by the DB (i.e., journaling). Scale up with these considerations in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Workload Characteristics: CPU-intensive workloads (e.g., complex calculations and heavy transactions) require more cores for better performance.&lt;/li&gt;
&lt;li&gt;Concurrency: Higher concurrent connections and transactions can increase CPU requirements.&lt;/li&gt;
&lt;li&gt;Database Size and Complexity: Larger databases or those with complex schemas and queries may benefit from additional CPU resources to handle the increased processing demands.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Memory
&lt;/h3&gt;

&lt;p&gt;RAM impacts the database's ability to cache data, work with indexes, and process queries efficiently, significantly affecting overall performance. For small-medium sizes require at least 4GB of RAM and scale up by these considerations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database Working Set Size: Ideally, your server should have enough RAM to hold the frequently accessed ("hot") portion of your database (tables and indexes) in memory to reduce disk I/O.&lt;/li&gt;
&lt;li&gt;Connection Overhead: PostgreSQL uses a multi-process architecture, where each connection has its dedicated process. More connections imply more RAM for connection overhead. As a rough estimate, plan for about 10 MB (or more) per connection.&lt;/li&gt;
&lt;li&gt;Workload Type: Read-heavy workloads benefit from more RAM for caching, reducing read operations from disk. Write-heavy workloads may benefit less from caching but require sufficient RAM for sorting and transaction logs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;The storage size varies from one application to another, but what is important here is the type of storage and its IO performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage Type: SSDs should be your choice&lt;/li&gt;
&lt;li&gt;Provisioned IOPS: Consider using "provisioned IOPS" storage for high-performance workloads to ensure consistent and fast disk I/O.&lt;/li&gt;
&lt;li&gt;Storage Auto-scaling: Enable auto-scaling for storage to adjust as your database grows automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Scaling up your system usually costs more money. Monitor your current resources' use closely and consider ways to decrease the use of the resources using query/structure optimizations (RAM/CPU/storage) before adding more resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Network proximity / Geographics:
&lt;/h2&gt;

&lt;p&gt;It's best to keep your database and application server as close to each other as possible, network-wise so that they can communicate more efficiently. One way to do this is by provisioning them in the same region on your cloud service and putting them within the same private virtual network. Also, it's a good idea to use private IP addresses for communication between them rather than public IPs, which will help reduce the number of network hops and increase the speed of data transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connections Management
&lt;/h2&gt;

&lt;p&gt;Opening a connection to the database is an expensive operation. It includes the DNS resolving, TCP handshake, TLS Handshake, authentication, and setting up internal resources such as session objects and watchdogs. So, you should be conscious about opening a connection to the DB and reusing them as much as possible.&lt;/p&gt;

&lt;p&gt;The way to achieve it is by using connection pooling. It maintains a set number of connections and opens new ones until reaching a maximum value of connections. So, your application code should always have an open connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amount of queries in a single request/transaction
&lt;/h2&gt;

&lt;p&gt;Whether you use connection pooling or not, your database/pool has a limit on the maximum number of open connections. You should also verify the number of queries and their processing time so you won't get to the point where your code is waiting for a connection to be available instead of running your business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should you look for?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The N+1 problem&lt;/li&gt;
&lt;li&gt;Querying too much unnecessary data:

&lt;ul&gt;
&lt;li&gt;Filtering fetched data in code instead of in your queries.&lt;/li&gt;
&lt;li&gt;Aggregate fetched data in code instead of in a query (count/sum/averages/min/max/etc)&lt;/li&gt;
&lt;li&gt;Fetch fields without using them (look up big text fields or tables with a lot of columns)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use joins instead of multiple separate queries.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Optimize query processing time.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fine-tune condition evaluation with indexes
&lt;/h3&gt;

&lt;p&gt;Imagine you have a table of users containing ID, name, and birth date. Your app has a search function for all the users who have a Birthday today. Usually, the database will scan the table data row by row and filter all the records with this birthday. Imagine this table has 1M records.&lt;/p&gt;

&lt;p&gt;Indexes are like a sidecar to your tables; they contain a "table of contents" in various data structures, so when you query data, it will be possible to access the records without scanning all table content. The index contains the data of one or more of your columns, but instead of using a list to hold the data, it uses other data structures like trees and a hash table and divides the column value into different parts so the search will be faster. full table scan is O(n), while index scan usually is O(Log(n)) or O(1), depends on the index.&lt;/p&gt;

&lt;p&gt;So now, instead of scanning the entire table to find users with a birthday, it checks the index, gets pointers for the records in the table (or, more accurately, to the exact pages in the filesystem), and then retrieves the data.&lt;/p&gt;

&lt;p&gt;To understand why indexing is crucial, what to index, and what kind of indexes are available, &lt;a href="https://percentile99th.substack.com/p/the-six-pillars-of-database-driven"&gt;you can read here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fine-tune data fetching with Partitioning
&lt;/h3&gt;

&lt;p&gt;If you deal with big data and need more than indexing, partitioning the data will let you only query the relevant portion. For example, if partitioning the data by "Year," you will query only data from 2024, and all 2023 won't be retrieved from the storage if you haven't explicitly asked for it (partition is like a different storage unit). If most queries are in filtering by 2024, it will dramatically enhance your query performance.&lt;/p&gt;

&lt;p&gt;Partitioning has overhead in maintenance, and it can lead to a spike in lock manager waits and negatively impact user query performance. Increasing smaller tables and multiple indexes and partitions can generate many locks per query, causing contention problems.&lt;/p&gt;




&lt;p&gt;Based on my experience, focusing on these 6 pillars can significantly enhance the performance of the application by 80-90%, even before implementing any additional solutions such as caching, rewriting specific parts of the application in more performant languages, dividing into microservices, or using NoSQL.&lt;/p&gt;

</description>
      <category>database</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Six Pillars of Database-Driven Application Performance</title>
      <dc:creator>Nir Tayeb</dc:creator>
      <pubDate>Thu, 22 Feb 2024 23:19:00 +0000</pubDate>
      <link>https://dev.to/nirtayeb/the-six-pillars-of-database-driven-application-performance-3099</link>
      <guid>https://dev.to/nirtayeb/the-six-pillars-of-database-driven-application-performance-3099</guid>
      <description>&lt;p&gt;In the last post, I covered &lt;a href="https://dev.to/nirtayeb/6-ways-youre-using-orms-wrong-and-how-to-fix-4edp"&gt;six ways ORMS made writing poorly performant code easy&lt;/a&gt;. But it is just a part of the story.&lt;/p&gt;

&lt;p&gt;In this post, I will cover the bigger picture of working against a database and six significant topics that will most impact your application performance that you need to be conscious of and can decrease the need to add complexity to your system (like caching, multiple different DB's, etc.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Side notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I may refer to RDBMS and SQL in the post, but these principles apply to NoSQL systems and beyond.&lt;/li&gt;
&lt;li&gt;This post was also published in my newsletter, containing &lt;a href="https://percentile99th.substack.com/p/the-six-pillars-of-database-driven"&gt;the 6 pillars with more depth&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start,&lt;/p&gt;

&lt;h2&gt;
  
  
  Database version
&lt;/h2&gt;

&lt;p&gt;New major versions are coming with new features and may include performance improvements. It is 200% right if we refer to PostgreSQL.&lt;/p&gt;

&lt;p&gt;Benchmark made by &lt;a href="https://www.enterprisedb.com/blog/performance-comparison-major-PostgreSQL-versions"&gt;EDB &lt;/a&gt;shows a 47% performance enhancement between PostgreSQL v9.6 and v15 and a 10.4% upgrade from v12 to v15. V16 has as well some minor improvements. You can read more in &lt;a href="https://benchant.com/blog/postgresql-v16-performance"&gt;this post by "benchant.com"&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware
&lt;/h2&gt;

&lt;p&gt;The database is the heart of your application. If it doesn't have enough computing resources, your whole application will be waiting for these resources to be available.&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU
&lt;/h3&gt;

&lt;p&gt;For small-medium size, you need a minimum of 4 cores for parallelism of your workloads, connection concurrencies, and background jobs done by the DB (i.e., journaling). Scale up with these considerations in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Workload Characteristics: CPU-intensive workloads (e.g., complex calculations and heavy transactions) require more cores for better performance.&lt;/li&gt;
&lt;li&gt;Concurrency: Higher concurrent connections and transactions can increase CPU requirements.&lt;/li&gt;
&lt;li&gt;Database Size and Complexity: Larger databases or those with complex schemas and queries may benefit from additional CPU resources to handle the increased processing demands.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Memory
&lt;/h3&gt;

&lt;p&gt;RAM impacts the database's ability to cache data, work with indexes, and process queries efficiently, significantly affecting overall performance. For small-medium sizes require at least 4GB of RAM and scale up by these considerations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Database Working Set Size: Ideally, your server should have enough RAM to hold the frequently accessed ("hot") portion of your database (tables and indexes) in memory to reduce disk I/O.&lt;/li&gt;
&lt;li&gt;Connection Overhead: PostgreSQL uses a multi-process architecture, where each connection has its dedicated process. More connections imply more RAM for connection overhead. As a rough estimate, plan for about 10 MB (or more) per connection.&lt;/li&gt;
&lt;li&gt;Workload Type: Read-heavy workloads benefit from more RAM for caching, reducing read operations from disk. Write-heavy workloads may benefit less from caching but require sufficient RAM for sorting and transaction logs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;The storage size varies from one application to another, but what is important here is the type of storage and its IO performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage Type: SSDs should be your choice&lt;/li&gt;
&lt;li&gt;Provisioned IOPS: Consider using "provisioned IOPS" storage for high-performance workloads to ensure consistent and fast disk I/O.&lt;/li&gt;
&lt;li&gt;Storage Auto-scaling: Enable auto-scaling for storage to adjust as your database grows automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Scaling up your system usually costs more money. Monitor your current resources' use closely and consider ways to decrease the use of the resources using query/structure optimizations (RAM/CPU/storage) before adding more resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Network proximity / Geographics:
&lt;/h2&gt;

&lt;p&gt;It's best to keep your database and application server as close to each other as possible, network-wise so that they can communicate more efficiently. One way to do this is by provisioning them in the same region on your cloud service and putting them within the same private virtual network. Also, it's a good idea to use private IP addresses for communication between them rather than public IPs, which will help reduce the number of network hops and increase the speed of data transfer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connections Management
&lt;/h2&gt;

&lt;p&gt;Opening a connection to the database is an expensive operation. It includes the DNS resolving, TCP handshake, TLS Handshake, authentication, and setting up internal resources such as session objects and watchdogs. So, you should be conscious about opening a connection to the DB and reusing them as much as possible.&lt;/p&gt;

&lt;p&gt;The way to achieve it is by using connection pooling. It maintains a set number of connections and opens new ones until reaching a maximum value of connections. So, your application code should always have an open connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amount of queries in a single request/transaction
&lt;/h2&gt;

&lt;p&gt;Whether you use connection pooling or not, your database/pool has a limit on the maximum number of open connections. You should also verify the number of queries and their processing time so you won't get to the point where your code is waiting for a connection to be available instead of running your business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should you look for?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The N+1 problem&lt;/li&gt;
&lt;li&gt;Querying too much unnecessary data:

&lt;ul&gt;
&lt;li&gt;Filtering fetched data in code instead of in your queries.&lt;/li&gt;
&lt;li&gt;Aggregate fetched data in code instead of in a query (count/sum/averages/min/max/etc)&lt;/li&gt;
&lt;li&gt;Fetch fields without using them (look up big text fields or tables with a lot of columns)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use joins instead of multiple separate queries.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Optimize query processing time.
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fine-tune condition evaluation with indexes
&lt;/h3&gt;

&lt;p&gt;Imagine you have a table of users containing ID, name, and birth date. Your app has a search function for all the users who have a Birthday today. Usually, the database will scan the table data row by row and filter all the records with this birthday. Imagine this table has 1M records.&lt;/p&gt;

&lt;p&gt;Indexes are like a sidecar to your tables; they contain a "table of contents" in various data structures, so when you query data, it will be possible to access the records without scanning all table content. The index contains the data of one or more of your columns, but instead of using a list to hold the data, it uses other data structures like trees and a hash table and divides the column value into different parts so the search will be faster. full table scan is O(n), while index scan usually is O(Log(n)) or O(1), depends on the index.&lt;/p&gt;

&lt;p&gt;So now, instead of scanning the entire table to find users with a birthday, it checks the index, gets pointers for the records in the table (or, more accurately, to the exact pages in the filesystem), and then retrieves the data.&lt;/p&gt;

&lt;p&gt;To understand why indexing is crucial, what to index, and what kind of indexes are available, &lt;a href="https://percentile99th.substack.com/p/the-six-pillars-of-database-driven"&gt;you can read here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fine-tune data fetching with Partitioning
&lt;/h3&gt;

&lt;p&gt;If you deal with big data and need more than indexing, partitioning the data will let you only query the relevant portion. For example, if partitioning the data by "Year," you will query only data from 2024, and all 2023 won't be retrieved from the storage if you haven't explicitly asked for it (partition is like a different storage unit). If most queries are in filtering by 2024, it will dramatically enhance your query performance.&lt;/p&gt;

&lt;p&gt;Partitioning has overhead in maintenance, and it can lead to a spike in lock manager waits and negatively impact user query performance. Increasing smaller tables and multiple indexes and partitions can generate many locks per query, causing contention problems.&lt;/p&gt;




&lt;p&gt;Based on my experience, focusing on these 6 pillars can significantly enhance the performance of the application by 80-90%, even before implementing any additional solutions such as caching, rewriting specific parts of the application in more performant languages, dividing into microservices, or using NoSQL.&lt;/p&gt;

</description>
      <category>database</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>6 Ways You're Using ORMs Wrong and How To Fix</title>
      <dc:creator>Nir Tayeb</dc:creator>
      <pubDate>Sun, 11 Feb 2024 22:18:00 +0000</pubDate>
      <link>https://dev.to/nirtayeb/6-ways-youre-using-orms-wrong-and-how-to-fix-4edp</link>
      <guid>https://dev.to/nirtayeb/6-ways-youre-using-orms-wrong-and-how-to-fix-4edp</guid>
      <description>&lt;p&gt;We love ORMs because they simplify and streamline the working process with relational databases.&lt;/p&gt;

&lt;p&gt;ORMs provide a higher level of abstraction, allowing us to work with objects and classes instead of raw SQL queries. They automate everyday database tasks such as CRUD operations, reducing the boilerplate code we need to write. ORMs handle database connections and transactions, making managing and scaling our applications easier. With ORMs, it's possible to write database-agnostic code, quickly switch between different database systems, and focus more on application logic.&lt;/p&gt;

&lt;p&gt;But with all the benefits and abstractions, it is easy to write poorly performant code.&lt;/p&gt;

&lt;p&gt;For the examples, I'll use the scheme from the Prisma ORM documentation, which describes a blogging platform with users, profile details, posts, and categories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  role    Role     @default(USER)
  posts   Post[]
  profile Profile?
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String
  user   User   @relation(fields: [userId], references: [id])
  userId Int    @unique
}

model Post {
  id         Int        @id @default(autoincrement())
  createdAt  DateTime   @default(now())
  updatedAt  DateTime   @updatedAt
  title      String
  published  Boolean    @default(false)
  author     User       @relation(fields: [authorId], references: [id])
  authorId   Int
  categories Category[]
}

model Category {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[]
}

enum Role {
  USER
  ADMIN
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The n+1 problem
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fetching of data without using eager loading
&lt;/h3&gt;

&lt;p&gt;Assuming we want to list all user posts with a User role. A naive approach to writing the code is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getUserPosts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;}});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, what is going to happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query the database for the user's table for all the users with role admin:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'User'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Then, For each record, query the database (again) for the user posts:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;userid&lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You won't feel the performance issue when developing the application with a few test users. But once you get to hundreds or thousands of users in the production environment, this function will take "forever" from a user experience perspective. Every round trip to your DB takes at least 10ms, not including the query that needs to run and the data transfer between the databases and your server.&lt;/p&gt;

&lt;p&gt;Using eager-loading, you will reach the database only once!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getUserPosts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; 
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using include here, the ORM (prisma in my example) uses the SQL Join statement when constructing the SQL to query the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userid&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'User'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Another variation of the n+1 problem
&lt;/h3&gt;

&lt;p&gt;Assuming we want to list all posts with their writer's short bio, a naive approach will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsWithBio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;authorBio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;bio&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the issue is that the code will make a round trip to the database for each post to query the author and the profile. The solution in this specific case is to use include twice, but if we have a more complex schema that uses more relations between the entities. We can implement another solution.&lt;/p&gt;

&lt;p&gt;First, map all the unique author IDs, then fetch only the profiles (or bios) of these author IDs and map them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPostWithBio&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authorIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authorIds&lt;/span&gt;&lt;span class="p"&gt;}});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authorToBio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bio&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postsWithBio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;authorBio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authorToBio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way, only two queries are going to the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;userid&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember that this is a straightforward use case; this issue is widespread in more complex systems and looks slightly different. Your code will iterate over one entity, then go into other classes and functions, which will fetch more data from the DB, creating the N+1 problem without you notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to identify the N+1&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best way to identify the N+1 issue in your system is by monitoring your app in three different ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Turn on your debug/info log and configure your ORM to log the SQL queries it generates.&lt;/li&gt;
&lt;li&gt;Watch the database query log and look for frequent and similar queries fetching by one ID.&lt;/li&gt;
&lt;li&gt;Using an Application Performance Monitoring (APM) system such as NewRelic, Sentry, and Azure Application Insight - they visualize all the calls to the DB on each request/transaction and show you how long it took and how long each request spent communicating with the DB.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Querying without using indexes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  No defining Index
&lt;/h3&gt;

&lt;p&gt;Assuming we need to search for posts by title, a naive implementation will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we look in the schema, we won't see an index definition on the title field. We won't see any index explicitly defined in our schema (except primary keys/foreign keys relations/unique). Why? Because it's not intuitive for object-oriented/functional programmers to think about data indexing.&lt;/p&gt;

&lt;p&gt;Running the code above will result in a full table scan to look up the query. As the name suggests, a full table scan means an O(N) lookup for the data. Running the query will take a lot of time and resources if we have thousands of posts and more properties on each post.&lt;/p&gt;

&lt;p&gt;If we set an index (of the default "b-tree" type) on the field, the lookup time will usually decrease to O(Log-N).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model Post {
  id         Int        @id @default(autoincrement())
  createdAt  DateTime   @default(now())
  updatedAt  DateTime   @updatedAt
  title      String
  published  Boolean    @default(false)
  author     User       @relation(fields: [authorId], references: [id])
  authorId   Int
  categories Category[]

  @@index([title])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 Suppose you don't use the query frequently. Instead of defining an index on the title field, you can add conditions to the query using other indexes. Then, the search will be on a subset of the data instead of having a full table scan.&lt;/p&gt;

&lt;p&gt;💡 Remember that the cost of indexing the data is a lower write performance. So, depending on your use case, decide which index is necessary and which is nice if writing performance matters to you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The query cannot use the index (wrong index type)
&lt;/h3&gt;

&lt;p&gt;Assuming we want to query posts by a string found in the title.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code results in a query including the "LIKE" operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%query%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default index algorithm is B-Tree, and leading '%' cannot be queried using the index.&lt;/p&gt;

&lt;p&gt;The solution is to define another type of index to query the data efficiently; in this case, it's GIN or GiST (on PostgreSQL)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@@index([title], type: GIN)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For further reading, I suggest reading this &lt;a href="https://alexklibisz.com/2022/02/18/optimizing-postgres-trigram-search"&gt;fantastic blog post &lt;/a&gt; explaining how trigram-based operations and indexes improve query performance by several orders of magnitude.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using model objects as output
&lt;/h2&gt;

&lt;p&gt;Assuming we need to implement an endpoint to fetch all users, a naive approach will look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When nest.js and other frameworks evaluate objects, they transform the object entirely to JSON, iterating all over the properties and sub-properties. Using the model as the output will result in lazy loading of each user's relations, meaning more queries to the DB and more unnecessary data returned with the requests. It can also lead to a security risk of exposing private or confidential information.&lt;/p&gt;

&lt;p&gt;Instead, you should clearly define the interface to use. With typescript, it's possible to use Pick to define a partial of another type to pick only a subset of the properties or Omit to exclude some properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query unnecessary data
&lt;/h2&gt;

&lt;p&gt;Assuming we want to query all posts and print only their dates and titles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code ends up with this SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks simple, but if we have even 1000 posts and their content each is 10KB of words, we transfer at least 10MB of data without even using it. Instead, we should select only the fields we need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;publishedAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]});&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which will end up with a query like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;publishedAt&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In summary, using ORM is excellent for productivity but not always suitable for performance. Understanding what is happening behind the scenes is crucial to ensure optimal code performance.&lt;/p&gt;




&lt;p&gt;If you enjoyed the article, please forward and share it with your teammates/acquaintances and help them avoid these mistakes.&lt;br&gt;
This post was originally published in my newsletter &lt;a href="https://percentile99th.substack.com/"&gt;"Percentile 99th"&lt;/a&gt;. &lt;br&gt;
My subscribers get all the information first; in the future, exclusive content will be shared only over the newsletter.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>database</category>
      <category>bestpractices</category>
    </item>
    <item>
      <title>Why you should avoid Percentile 99th for your users</title>
      <dc:creator>Nir Tayeb</dc:creator>
      <pubDate>Mon, 05 Feb 2024 20:29:17 +0000</pubDate>
      <link>https://dev.to/nirtayeb/percentile-99th-4cn1</link>
      <guid>https://dev.to/nirtayeb/percentile-99th-4cn1</guid>
      <description>&lt;p&gt;The 99th percentile is the highest percentile you can get. It means you are among the top scorers since you scored higher than 99% of the group who took the test. Only 1 in 100 the group scores in this range, so it places you at the very top of the pool.&lt;/p&gt;

&lt;p&gt;Most of us want to be in the 1% percent that scored the highest. We want to be the top earners, performers, the fastest, the fittest, and generally the best in our crafts.&lt;/p&gt;

&lt;p&gt;However, in application performance metrics, the users in the 99th percentile have the highest latency score (or response time, first contentfull paint, etc.…). Therefore, they had the worst experience in the measured period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you should care?
&lt;/h2&gt;

&lt;p&gt;You probably think, okay, it's only 1% who experience some shitty performance. Why should I care about 1%?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The 1% percent could happen where your system doesn't scale, for example, when working on too much data - which usually occurs for the top clients.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The 1% can be the edge cases when one user's action might affect other users' experience when the system runs on shared resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The 1% percent could lead your way through capacity planning. By analyzing this metric, you can determine the level of infrastructure, compute power, or database capacity needed to handle the occasional high-latency requests without sacrificing overall performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Outliers can skew the mean score. Removing them can give a more accurate representation of the average user experience.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Monitoring this metric is only the first step; the next step is finding the root cause of the score and fixing it for good. But it is only sometimes straightforward. &lt;/p&gt;

&lt;p&gt;In my newsletter &lt;a href="https://percentile99th.substack.com/"&gt;"Percentile 99th"&lt;/a&gt;, I'll cover, every other week, the principles, how-to's, and deep dive analysis into different technologies, including real-life examples from my experience and top companies, to help you decrease the percentile 99th metric and help you become the top 1% percent of engineers who knows to improve the system to work like magic (and design new systems better), a real performance speedster.&lt;/p&gt;

&lt;p&gt;See you there, at &lt;a href="https://percentile99th.substack.com/"&gt;Percentile 99th&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>devops</category>
      <category>backend</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
