<?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: Yashika Vijayvargiya</title>
    <description>The latest articles on DEV Community by Yashika Vijayvargiya (@yashika_vijayvargiya).</description>
    <link>https://dev.to/yashika_vijayvargiya</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3977303%2F1ec6531e-a0cf-4905-b074-574c5cf458f5.JPG</url>
      <title>DEV Community: Yashika Vijayvargiya</title>
      <link>https://dev.to/yashika_vijayvargiya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yashika_vijayvargiya"/>
    <language>en</language>
    <item>
      <title>Understanding Database Locking in Ruby on Rails: Types, Examples, and Best Practices</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 15 Jul 2026 08:39:39 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/understanding-database-locking-in-ruby-on-rails-types-examples-and-best-practices-23nc</link>
      <guid>https://dev.to/yashika_vijayvargiya/understanding-database-locking-in-ruby-on-rails-types-examples-and-best-practices-23nc</guid>
      <description>&lt;p&gt;Goal of this article: Build a strong foundation before learning pessimistic locking, optimistic locking, deadlocks, and isolation levels.&lt;/p&gt;

&lt;p&gt;When I started working with production Rails applications, I realized that most database problems were not caused by missing indexes but by concurrent requests modifying the same data. Understanding locking completely changed the way I design payment, inventory, and booking systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a lock?
&lt;/h3&gt;

&lt;p&gt;A lock is a mechanism used by PostgreSQL to protect data from being modified incorrectly by multiple transactions at the same time.&lt;/p&gt;

&lt;p&gt;In simple words:&lt;/p&gt;

&lt;p&gt;A lock tells other transactions: “Someone is currently working with this data. Wait your turn.”&lt;/p&gt;

&lt;p&gt;Imagine two people editing the same Google Doc at the same moment. Without coordination, one person’s changes could overwrite the other’s. Databases use locks to prevent similar problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do databases need locking?
&lt;/h3&gt;

&lt;p&gt;Suppose an e-commerce application has only 1 item left in inventory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8qgwp5xtkezw1ksm1r1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8qgwp5xtkezw1ksm1r1g.png" width="796" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without locking, both requests may read:&lt;/p&gt;

&lt;p&gt;Both transactions believe the product is available and both place an order.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnf43sdr7w3tpon0kj12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnf43sdr7w3tpon0kj12.png" width="560" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is called a race condition.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a race condition?
&lt;/h3&gt;

&lt;p&gt;A race condition occurs when the final result depends on the timing of concurrent operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;If two requests execute this code simultaneously, both may read the same inventory value before either update occurs.&lt;/p&gt;

&lt;p&gt;This is the fundamental problem locking is designed to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transactions: The Foundation of Locking
&lt;/h3&gt;

&lt;p&gt;Locks exist inside database transactions.&lt;/p&gt;

&lt;p&gt;A transaction guarantees that all operations inside the block either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Succeed together (COMMIT)&lt;/li&gt;
&lt;li&gt;Fail together (ROLLBACK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Locks are typically acquired when the transaction begins modifying or explicitly locking rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  How PostgreSQL Handles Concurrent Transactions
&lt;/h3&gt;

&lt;p&gt;Consider two transactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo10qhhg5pghoefccet2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffo10qhhg5pghoefccet2.png" width="768" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL automatically prevents both transactions from modifying the same row simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  MVCC: Why SELECT Usually Doesn’t Block
&lt;/h3&gt;

&lt;p&gt;One of PostgreSQL’s most important features is MVCC (Multi-Version Concurrency Control).&lt;/p&gt;

&lt;p&gt;Instead of blocking readers, PostgreSQL creates multiple versions of rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Transaction A updates a product but has not committed yet.&lt;/p&gt;

&lt;p&gt;At the same time, Transaction B runs:&lt;/p&gt;

&lt;p&gt;Transaction B usually does not wait. It reads the previous committed version of the row.&lt;/p&gt;

&lt;p&gt;This is why PostgreSQL can handle many readers efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Concurrency Problems
&lt;/h3&gt;

&lt;h3&gt;
  
  
  1. Lost Update
&lt;/h3&gt;

&lt;p&gt;Very common&lt;/p&gt;

&lt;p&gt;Problem: Two users overwrite each other’s changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgzuhxyb15lab2pijx21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flgzuhxyb15lab2pijx21.png" width="775" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Correct result should be: $30&lt;/p&gt;

&lt;p&gt;Actual result: $50&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Dirty Read
&lt;/h3&gt;

&lt;p&gt;Usually prevented in PostgreSQL&lt;/p&gt;

&lt;p&gt;Reading data that another transaction has modified but not committed.&lt;/p&gt;

&lt;p&gt;PostgreSQL’s default isolation level (Read Committed) prevents this.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Non-repeatable Read
&lt;/h3&gt;

&lt;p&gt;Possible in Read Committed&lt;/p&gt;

&lt;p&gt;You read the same row twice and get different values because another transaction committed between the reads.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Phantom Read
&lt;/h3&gt;

&lt;p&gt;Isolation-level issue&lt;/p&gt;

&lt;p&gt;A query returns a different set of rows the second time it runs because another transaction inserted matching records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rails Example: Preventing Double Booking
&lt;/h3&gt;

&lt;p&gt;Unsafe code:&lt;/p&gt;

&lt;p&gt;Safe code using locking:&lt;/p&gt;

&lt;p&gt;What changed?&lt;/p&gt;

&lt;p&gt;generates:&lt;/p&gt;

&lt;p&gt;This acquires a row-level lock so only one transaction can modify that event record at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Timeline
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbm0u08p3f47bbrha2tci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbm0u08p3f47bbrha2tci.png" width="773" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Insight: PostgreSQL Uses Multiple Lock Types
&lt;/h3&gt;

&lt;p&gt;Many beginners think there is only one lock.&lt;/p&gt;

&lt;p&gt;In reality PostgreSQL has:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F772dofyate0sbg25guxm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F772dofyate0sbg25guxm.png" width="774" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll explore these deeply in later parts of the series.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to See Locks in Production
&lt;/h3&gt;

&lt;p&gt;PostgreSQL exposes lock information through pg_locks.&lt;/p&gt;

&lt;p&gt;A more useful query:&lt;/p&gt;

&lt;p&gt;This helps identify which transaction is holding a lock and which transaction is waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;p&gt;Locks prevent race conditions and data corruption.&lt;/p&gt;

&lt;p&gt;Locks exist inside transactions.&lt;/p&gt;

&lt;p&gt;PostgreSQL uses MVCC so readers usually do not block writers.&lt;/p&gt;

&lt;p&gt;Row locks protect individual records.&lt;/p&gt;

&lt;p&gt;Table locks protect entire tables.&lt;/p&gt;

&lt;p&gt;FOR UPDATE is the foundation of pessimistic locking in Rails.&lt;/p&gt;

&lt;p&gt;Most production concurrency bugs are caused by missing transactions or missing locks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interview Question
&lt;/h3&gt;

&lt;p&gt;Why does PostgreSQL allow many SELECT queries to run without blocking each other?&lt;/p&gt;

&lt;p&gt;Answer: PostgreSQL uses MVCC (Multi-Version Concurrency Control). Instead of blocking readers, it keeps multiple versions of rows so a SELECT query can read the last committed version while another transaction is updating the row. This greatly improves concurrency and is one of PostgreSQL’s biggest advantages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Article in the Series
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Pessimistic Locking in Rails
&lt;/h3&gt;

&lt;p&gt;We will dive into lock, with_lock, FOR UPDATE, NOWAIT, SKIP LOCKED, and real production examples such as inventory reservation and payment processing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://railswithyashika.hashnode.dev/understanding-database-locking-in-rails-postgresql-part-1" rel="noopener noreferrer"&gt;&lt;em&gt;https://railswithyashika.hashnode.dev&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on July 13, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>howto</category>
      <category>rails</category>
      <category>locking</category>
    </item>
    <item>
      <title>PostgreSQL Index Types Explained with Real Rails Examples</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Thu, 02 Jul 2026 08:00:26 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/postgresql-index-types-explained-with-real-rails-examples-1549</link>
      <guid>https://dev.to/yashika_vijayvargiya/postgresql-index-types-explained-with-real-rails-examples-1549</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AmYg1PApQNoZlAEZR" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AmYg1PApQNoZlAEZR" width="1024" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Part-1 &lt;a href="https://railswithyashika.hashnode.dev/understanding-indexes-before-learning-index-types" rel="noopener noreferrer"&gt;Understanding Indexes before learning Index Types&lt;/a&gt;&lt;br&gt;&lt;br&gt;
Now that we understand what indexes are and why they matter, let’s explore the different types of indexes available in PostgreSQL and when to use them in Rails applications.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Single Column Index
&lt;/h3&gt;

&lt;p&gt;The simplest and most commonly used index.&lt;/p&gt;
&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  SQL Generated
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;index_users_on_email&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;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Email lookups&lt;/li&gt;
&lt;li&gt;SKU lookups&lt;/li&gt;
&lt;li&gt;UUID searches&lt;/li&gt;
&lt;li&gt;Foreign keys&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Real Example
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;sku: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sku&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Without an index, PostgreSQL scans the entire table.&lt;/p&gt;

&lt;p&gt;With an index, PostgreSQL can jump directly to the matching row.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Unique Index
&lt;/h3&gt;

&lt;p&gt;A unique index improves lookup performance while also preventing duplicate values.&lt;/p&gt;
&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;unique: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  What It Solves
&lt;/h3&gt;

&lt;p&gt;Prevents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;john@example.com
john@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Email addresses&lt;/li&gt;
&lt;li&gt;Usernames&lt;/li&gt;
&lt;li&gt;External IDs&lt;/li&gt;
&lt;li&gt;API tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rails Validation vs Database Constraint
&lt;/h3&gt;

&lt;p&gt;Many developers write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;uniqueness: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not enough.&lt;/p&gt;

&lt;p&gt;Always add a database unique index because validations can be bypassed during race conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Composite (Multi-column) Index
&lt;/h3&gt;

&lt;p&gt;Used when queries filter using multiple columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"paid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Important Rule
&lt;/h3&gt;

&lt;p&gt;Index order matters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works efficiently for:&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;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But not for:&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;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real Example
&lt;/h3&gt;

&lt;p&gt;In e-commerce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Partial Index
&lt;/h3&gt;

&lt;p&gt;Indexes only a subset of rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;where: &lt;/span&gt;&lt;span class="s2"&gt;"active = true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Use It?
&lt;/h3&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 million users
9 million inactive 
1 million active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating an index only for active users makes the index:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller&lt;/li&gt;
&lt;li&gt;Faster&lt;/li&gt;
&lt;li&gt;Less memory intensive&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common Uses
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Soft deletes&lt;/li&gt;
&lt;li&gt;Active records&lt;/li&gt;
&lt;li&gt;Published content&lt;/li&gt;
&lt;li&gt;Pending jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Index with Order
&lt;/h3&gt;

&lt;p&gt;Optimizes sorting operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;created_at: :desc&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;p&gt;Useful for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;News feeds&lt;/li&gt;
&lt;li&gt;Activity logs&lt;/li&gt;
&lt;li&gt;Product listings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. GIN Index
&lt;/h3&gt;

&lt;p&gt;GIN stands for Generalized Inverted Index.&lt;/p&gt;

&lt;p&gt;Used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSONB&lt;/li&gt;
&lt;li&gt;Arrays&lt;/li&gt;
&lt;li&gt;Full-text search&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JSONB Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:preferences&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :gin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;preferences&lt;/span&gt; &lt;span class="o"&gt;@&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'{"theme":"dark"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real Rails Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"preferences @&amp;gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;theme: &lt;/span&gt;&lt;span class="s2"&gt;"dark"&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without GIN indexes, JSONB searches become slow as data grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Full Text Search Index
&lt;/h3&gt;

&lt;p&gt;PostgreSQL can act as a search engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"to_tsvector('english', content)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :gin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"index_articles_on_content_search"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&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;articles&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt; &lt;span class="n"&gt;plainto_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'rails indexing'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Useful For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Blogs&lt;/li&gt;
&lt;li&gt;Documentation sites&lt;/li&gt;
&lt;li&gt;Product search&lt;/li&gt;
&lt;li&gt;Knowledge bases&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Concurrent Index
&lt;/h3&gt;

&lt;p&gt;One of the most important production techniques.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;Running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;on a huge table can lock writes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddIndexToUsersEmail&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="n"&gt;disable_ddl_transaction!&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt; 
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;algorithm: :concurrently&lt;/span&gt; 
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No downtime&lt;/li&gt;
&lt;li&gt;No table lock&lt;/li&gt;
&lt;li&gt;Safe for production&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real World
&lt;/h3&gt;

&lt;p&gt;Never add indexes to large production tables without considering concurrent creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Expression Index
&lt;/h3&gt;

&lt;p&gt;Indexes a calculated value instead of a column.&lt;/p&gt;

&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"LOWER(email) = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"LOWER(email)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"index_users_on_lower_email"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;p&gt;Supports fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case-insensitive searches&lt;/li&gt;
&lt;li&gt;Date transformations&lt;/li&gt;
&lt;li&gt;String manipulations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10. Covering Index (INCLUDE)
&lt;/h3&gt;

&lt;p&gt;Introduced to reduce table lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;include: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Useful?
&lt;/h3&gt;

&lt;p&gt;Query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL can answer directly from the index.&lt;/p&gt;

&lt;p&gt;This is called an &lt;strong&gt;Index Only Scan&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fewer disk reads&lt;/li&gt;
&lt;li&gt;Faster queries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  11. BRIN Index
&lt;/h3&gt;

&lt;p&gt;BRIN stands for Block Range Index.&lt;/p&gt;

&lt;p&gt;Designed for huge tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :brin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;Analytics data&lt;/li&gt;
&lt;li&gt;Audit records&lt;/li&gt;
&lt;li&gt;Time-series events&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;Instead of storing every value, BRIN stores summaries for ranges of pages.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely small indexes&lt;/li&gt;
&lt;li&gt;Very low maintenance overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 million events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A BRIN index may be only a few MBs compared to hundreds of MBs for a B-tree index.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Hash Index
&lt;/h3&gt;

&lt;p&gt;Optimized for equality lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;using: :hash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limitation
&lt;/h3&gt;

&lt;p&gt;Does not support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;
&amp;gt;
BETWEEN
ORDER BY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because B-tree indexes support more operations, Hash indexes are rarely used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Index Should You Choose?
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;Indexes are not about adding them everywhere.&lt;/p&gt;

&lt;p&gt;A good database engineer first identifies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Slow queries&lt;/li&gt;
&lt;li&gt;Query patterns&lt;/li&gt;
&lt;li&gt;Filter columns&lt;/li&gt;
&lt;li&gt;Sort columns&lt;/li&gt;
&lt;li&gt;Join columns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then chooses the index that matches the workload.&lt;/p&gt;

&lt;p&gt;Always verify improvements using:&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;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and remember that every index speeds up reads but adds overhead to writes. The best indexing strategy is the one that balances both.&lt;/p&gt;

&lt;p&gt;Concurrent Indexes: &lt;a href="https://railswithyashika.hashnode.dev/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production" rel="noopener noreferrer"&gt;https://railswithyashika.hashnode.dev/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;a href="https://railswithyashika.hashnode.dev/postgresql-index-types-explained-with-real-rails-examples" rel="noopener noreferrer"&gt;&lt;em&gt;https://railswithyashika.hashnode.dev&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on July 1, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>performance</category>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>Understanding Indexes Before Learning Index Types</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 01 Jul 2026 07:59:39 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/understanding-indexes-before-learning-index-types-193h</link>
      <guid>https://dev.to/yashika_vijayvargiya/understanding-indexes-before-learning-index-types-193h</guid>
      <description>&lt;p&gt;Before diving into different types of indexes, let’s understand why indexes are needed.&lt;/p&gt;

&lt;p&gt;Imagine a library with 100,000 books.&lt;/p&gt;

&lt;p&gt;If someone asks for a book named &lt;em&gt;“Clean Code”&lt;/em&gt;, there are two ways to find it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Without an Index
&lt;/h3&gt;

&lt;p&gt;You start from the first shelf and check every book one by one until you find the required book.&lt;/p&gt;

&lt;p&gt;This approach is similar to a &lt;strong&gt;Sequential Scan&lt;/strong&gt; in PostgreSQL.&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;books&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Clean Code'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL may need to scan every row in the table to find matching records.&lt;/p&gt;

&lt;p&gt;As the table grows from thousands to millions of rows, query performance degrades significantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  With an Index
&lt;/h3&gt;

&lt;p&gt;Now imagine the library has a catalog that stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Clean Code → Shelf A12
The Pragmatic Programmer → Shelf B03
Refactoring → Shelf C08
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of checking every book, you first look at the catalog and immediately jump to the correct shelf.&lt;/p&gt;

&lt;p&gt;This catalog is similar to a database index.&lt;/p&gt;

&lt;p&gt;An index stores selected column values in a structure that allows PostgreSQL to locate rows much faster than scanning the entire table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Index Every Column?
&lt;/h3&gt;

&lt;p&gt;A common beginner question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If indexes make queries faster, why not create indexes everywhere?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Indexes improve read performance, but they come with a cost.&lt;/p&gt;

&lt;p&gt;Whenever data changes, PostgreSQL must update both:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The table data&lt;/li&gt;
&lt;li&gt;Every related index&lt;/li&gt;
&lt;/ol&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the users table has 10 indexes, PostgreSQL must update all 10 indexes during the insert operation.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More storage usage&lt;/li&gt;
&lt;li&gt;Slower INSERT operations&lt;/li&gt;
&lt;li&gt;Slower UPDATE operations&lt;/li&gt;
&lt;li&gt;Slower DELETE operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because of this trade-off, indexes should be added only for queries that are frequently executed and benefit from faster lookups.&lt;/p&gt;

&lt;h3&gt;
  
  
  How PostgreSQL Uses an Index
&lt;/h3&gt;

&lt;p&gt;Suppose we have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run:&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;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index Scan using index_users_on_email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database can jump directly to matching records instead of examining every row.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Rule
&lt;/h3&gt;

&lt;p&gt;Create an index when a column is frequently used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WHERE clauses&lt;/li&gt;
&lt;li&gt;JOIN conditions&lt;/li&gt;
&lt;li&gt;ORDER BY clauses&lt;/li&gt;
&lt;li&gt;GROUP BY clauses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;email: &lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;user_id: &lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Lead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;status: &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are strong candidates for indexing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Verify an Index Is Being Used
&lt;/h3&gt;

&lt;p&gt;Never assume PostgreSQL is using your index.&lt;/p&gt;

&lt;p&gt;Use:&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;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&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;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index Scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms PostgreSQL is benefiting from the index.&lt;/p&gt;

&lt;p&gt;Understanding these fundamentals makes it much easier to choose the right indexing strategy, whether it is a simple B-Tree index, a composite index, a GIN index for JSONB data, or a BRIN index for massive datasets.&lt;/p&gt;

</description>
      <category>indexes</category>
      <category>rails</category>
      <category>database</category>
      <category>performance</category>
    </item>
    <item>
      <title>PostgreSQL Concurrent Indexes in Rails: Avoiding Downtime in Production</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Thu, 18 Jun 2026 07:02:08 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production-7mf</link>
      <guid>https://dev.to/yashika_vijayvargiya/postgresql-concurrent-indexes-in-rails-avoiding-downtime-in-production-7mf</guid>
      <description>&lt;p&gt;When working with large production databases, adding an index may seem like a simple migration.&lt;/p&gt;

&lt;p&gt;However, on tables with millions of records, a regular index creation can lock writes and impact your application's availability.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore what concurrent indexes are, why they matter, and how to safely create them in Ruby on Rails.&lt;/p&gt;

&lt;p&gt;The Problem with Normal Index Creation&lt;/p&gt;

&lt;p&gt;Suppose we have a large users table with millions of records.&lt;/p&gt;

&lt;p&gt;A typical Rails migration might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, PostgreSQL executes:&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;index_users_on_email&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;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While PostgreSQL builds the index, it acquires locks on the table.&lt;/p&gt;

&lt;p&gt;On large tables this can lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Slower API responses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blocked INSERT operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blocked UPDATE operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Blocked DELETE operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Potential downtime during deployment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;users table = 50 million rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating an index on such a table can take several minutes, and during that time write operations may be affected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution: Concurrent Indexes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL provides a safer alternative:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CREATE INDEX CONCURRENTLY&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This allows PostgreSQL to build the index while minimizing disruption to ongoing database operations.&lt;/p&gt;

&lt;p&gt;In Rails, this can be achieved using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddIndexToUsersEmail&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;disable_ddl_transaction!&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;algorithm: :concurrently&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt; 
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How Concurrent Indexes Work&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normal Index Creation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lock table&lt;br&gt;
↓&lt;br&gt;
Scan rows&lt;br&gt;
↓&lt;br&gt;
Build index&lt;br&gt;
↓&lt;br&gt;
Unlock table&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrent Index Creation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scan table&lt;br&gt;
↓&lt;br&gt;
Build index in background&lt;br&gt;
↓&lt;br&gt;
Track ongoing changes&lt;br&gt;
↓&lt;br&gt;
Finalize index&lt;/p&gt;

&lt;p&gt;The application continues to serve requests while the index is being created.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;disable_ddl_transaction!&lt;/code&gt; Is Required&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common mistake is forgetting:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;disable_ddl_transaction!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL does not allow:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;inside a transaction.&lt;/p&gt;

&lt;p&gt;Rails wraps migrations in a transaction by default, so we must disable it before creating concurrent indexes.&lt;/p&gt;

&lt;p&gt;Without it, Rails raises an error during migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complete Rails Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AddIndexToBookingsUserId&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;7.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
&lt;span class="n"&gt;disable_ddl_transaction!&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;

    &lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;algorithm: :concurrently&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When Should You Use Concurrent Indexes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Concurrent indexes are recommended when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The table contains millions of rows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application is live&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Downtime is unacceptable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The database receives frequent writes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Typical examples include:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Users table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Orders table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bookings table&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transactions table&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When Is a Regular Index Fine?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For smaller tables and development environments:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;add_index :users, :email&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;is usually sufficient.&lt;/p&gt;

&lt;p&gt;Since concurrent index creation performs additional work, it is generally slower than normal index creation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qdem8rvvwfcli5w0kvk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qdem8rvvwfcli5w0kvk.png" alt=" " width="749" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Production Scenario&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine a booking platform with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bookings table = 100 million rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A slow query is identified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Booking&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;hotel_id: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hotel_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The obvious fix is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:hotel_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running a normal index creation during business hours could affect booking APIs.&lt;/p&gt;

&lt;p&gt;Instead, deploying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;add_index&lt;/span&gt; &lt;span class="ss"&gt;:bookings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:hotel_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;algorithm: :concurrently&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;allows the application to remain available while the index is built.&lt;/p&gt;

&lt;p&gt;Checking Existing Indexes&lt;/p&gt;

&lt;p&gt;To view indexes in PostgreSQL:&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;pg_indexes&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tablename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'users'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also use:&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="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from the PostgreSQL console.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interview Question:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why do we use concurrent indexes in Rails?&lt;/p&gt;

&lt;p&gt;A good answer is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Normal index creation can lock writes on large tables and impact application availability. PostgreSQL provides CREATE INDEX CONCURRENTLY to build indexes with minimal locking. In Rails, this is achieved using algorithm: :concurrently along with disable_ddl_transaction!. Although concurrent indexes take longer to build, they are much safer for production systems with large datasets and high traffic&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Creating indexes is one of the easiest ways to improve query performance, but doing it incorrectly in production can introduce downtime.&lt;/p&gt;

&lt;p&gt;Whenever you’re adding indexes to large, high-traffic tables, consider using concurrent indexes. The migration may take longer to complete, but your users won’t experience disruptions while it runs.&lt;/p&gt;

&lt;p&gt;A few extra minutes during deployment are often worth avoiding minutes of downtime in production.&lt;/p&gt;

</description>
      <category>indexing</category>
      <category>performance</category>
      <category>database</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>Rails Performance Optimization: Fixing N+1 Queries with includes, preload, and eager_load</title>
      <dc:creator>Yashika Vijayvargiya</dc:creator>
      <pubDate>Wed, 10 Jun 2026 10:22:00 +0000</pubDate>
      <link>https://dev.to/yashika_vijayvargiya/rails-performance-optimization-fixing-n1-queries-with-includes-preload-and-eagerload-4bca</link>
      <guid>https://dev.to/yashika_vijayvargiya/rails-performance-optimization-fixing-n1-queries-with-includes-preload-and-eagerload-4bca</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on Hashnode:&lt;br&gt;
&lt;a href="https://railswithyashika.hashnode.dev/rails-performance-n-plus-one-queries" rel="noopener noreferrer"&gt;https://railswithyashika.hashnode.dev/rails-performance-n-plus-one-queries&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When working with associations in Rails, it's easy to accidentally introduce performance issues. One of the most common problems is the N+1 Query Problem.&lt;/p&gt;

&lt;p&gt;In this article, we'll understand what N+1 queries are, how they impact performance, and the differences between includes, preload, and eager_load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is an N+1 Query?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose we have the following models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_many&lt;/span&gt; &lt;span class="ss"&gt;:posts&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We fetch all posts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And display the author's name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queries Generated&lt;br&gt;
Rails first loads all posts:&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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&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;Then for each post:&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="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are 100 posts, Rails executes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 query for posts&lt;/li&gt;
&lt;li&gt;100 queries for users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: &lt;strong&gt;101 queries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is called the N+1 query problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is it a Problem?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As the amount of data grows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database load increases&lt;/li&gt;
&lt;li&gt;Response times become slower&lt;/li&gt;
&lt;li&gt;More memory and CPU are consumed&lt;/li&gt;
&lt;li&gt;Application scalability decreases
A page that works fine with 10 records can become painfully slow with 1,000 records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fixing N+1 Queries with includes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The simplest solution is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails executes:&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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&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;Then:&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="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&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;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only 2 queries are executed regardless of how many posts exist.&lt;/p&gt;

&lt;p&gt;Understanding includes&lt;/p&gt;

&lt;p&gt;Most Rails developers use includes, but many don't know how it actually works.&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generated Queries&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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&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="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&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;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rails loads records using separate queries and associates them in memory.&lt;/p&gt;

&lt;p&gt;When Rails Converts includes into JOIN&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;users: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Rails generates:&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;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&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;title&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;,&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;name&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;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&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;user_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&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;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the query references the users table, Rails automatically switches to a JOIN strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding preload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;preload&lt;/code&gt; always loads associations using separate queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Queries Generated&lt;/strong&gt;&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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&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="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&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;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the generated SQL is similar to &lt;code&gt;includes&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;Key Difference&lt;/strong&gt;&lt;br&gt;
Unlike &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;preload&lt;/code&gt; never converts into a JOIN.&lt;br&gt;
This will fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;users: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;missing FROM-clause entry for table "users"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since no JOIN is generated, Rails cannot reference columns from the users table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to Use preload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;preload&lt;/code&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You know separate queries are preferred.&lt;/li&gt;
&lt;li&gt;You only want to avoid N+1 queries.&lt;/li&gt;
&lt;li&gt;You don't need conditions on associated tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Understanding eager_load&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eager_load&lt;/code&gt; always uses a &lt;code&gt;LEFT OUTER JOIN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Query Generated&lt;/strong&gt;&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;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&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;title&lt;/span&gt;&lt;span class="p"&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;user_id&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;,&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;name&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;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&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;user_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is fetched in a single query.&lt;/p&gt;

&lt;p&gt;Filtering on Associated Tables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;users: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;active: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Generated SQL:&lt;/strong&gt;&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="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"user_id"&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"active"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because the users table is already joined.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;includes vs preload vs eager_load&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Queries&lt;/th&gt;
&lt;th&gt;Uses JOIN&lt;/th&gt;
&lt;th&gt;Can Filter Associated Table&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;includes&lt;/td&gt;
&lt;td&gt;Usually 2&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;preload&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eager_load&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Always&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Which One Should You Use?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Use includes&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
&lt;code&gt;Post.includes(:user)&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Default choice for most cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use preload&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you explicitly want separate queries and no JOIN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use eager_load&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eager_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When filtering, ordering, or searching on associated tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Detecting N+1 Queries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A few ways to identify N+1 problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check Development Logs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Look for repeated queries being executed inside loops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Bullet Gem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'bullet'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bullet will notify you whenever an N+1 query is detected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Monitoring Tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scout APM&lt;/li&gt;
&lt;li&gt;New Relic&lt;/li&gt;
&lt;li&gt;Datadog&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools help identify slow database queries in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;N+1 queries are one of the most common performance issues in Rails applications.&lt;/p&gt;

&lt;p&gt;Understanding the differences between includes, preload, and eager_load can help you write more efficient database queries and build scalable applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As a rule of thumb:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with &lt;code&gt;includes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;preload&lt;/code&gt; when you want guaranteed separate queries&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;eager_load&lt;/code&gt; when you need JOIN-based filtering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few minutes spent analyzing your SQL queries can save hours of performance troubleshooting later.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>performance</category>
      <category>database</category>
    </item>
  </channel>
</rss>
