<?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: Mhamd Ghanoum</title>
    <description>The latest articles on DEV Community by Mhamd Ghanoum (@don21).</description>
    <link>https://dev.to/don21</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%2F3969621%2F141757b2-2f3a-4872-9cc6-1b21878fbbe6.jpg</url>
      <title>DEV Community: Mhamd Ghanoum</title>
      <link>https://dev.to/don21</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/don21"/>
    <language>en</language>
    <item>
      <title>How I Used PostgreSQL Events and MongoDB Event Logs in Two Different Real-World Systems</title>
      <dc:creator>Mhamd Ghanoum</dc:creator>
      <pubDate>Sat, 04 Jul 2026 15:51:22 +0000</pubDate>
      <link>https://dev.to/don21/how-i-used-postgresql-events-and-mongodb-event-logs-in-two-different-real-world-systems-2346</link>
      <guid>https://dev.to/don21/how-i-used-postgresql-events-and-mongodb-event-logs-in-two-different-real-world-systems-2346</guid>
      <description>&lt;p&gt;I built two systems that both needed event tracking.&lt;br&gt;
I used PostgreSQL in one and MongoDB in the other.&lt;br&gt;
Here's exactly why — and what I learned from doing both.&lt;br&gt;
Although the two systems were completely different in purpose, they shared one architectural requirement: each needed a reliable way to record everything that happened — not just the final state, but the story of how that state came to be.&lt;br&gt;
That sounds simple, but the nature of the events in each system was so different that using the same database for both would have caused real problems. This article explains how I approached event tracking in each system, why the solutions diverged, and what the experience taught me about choosing the right persistence model.&lt;/p&gt;
&lt;h2&gt;
  
  
  PostgreSQL Events and Projections in the Delivery Backend
&lt;/h2&gt;

&lt;p&gt;The delivery backend was built around structured workflows: vendors create orders, couriers pick them up, jobs move through well-defined states, and batches group multiple deliveries. Every action in this system has a clear relational meaning. An event like ORDER_CREATED or JOB_STARTED isn't just a log entry — it's part of the domain model.&lt;br&gt;
Because of that, PostgreSQL was the natural home for events. Each event needed to be tied to a specific entity, validated against constraints, indexed for fast lookup, and projected into timeline tables that the dashboard could query efficiently.&lt;br&gt;
The event log table looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model EventLog {
  id          Int             @id @default(autoincrement())
  entityType  EventEntityType
  entityId    Int
  eventType   String
  payload     Json
  createdAt   DateTime        @default(now())

  @@index([entityType, entityId, createdAt])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every event is immutable. Every event belongs to a relational entity. Every event carries structured metadata.&lt;br&gt;
Once an event is written, it is immediately transformed into a timeline entry:&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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entityType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EventEntityType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ORDER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderTimelineProjection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;EventEntityType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JOB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobTimelineProjection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;break&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 projection layer is what makes the system feel fast. Instead of running complex joins to reconstruct history, the dashboard reads from dedicated timeline tables optimized for sequential queries. It also makes debugging easier: if something goes wrong, I can replay the event stream and see exactly how the system reached its current state.&lt;br&gt;
PostgreSQL excels in this environment because the events are structured, relational, and part of the domain's consistency model. They aren't just analytics — they're part of the truth.&lt;/p&gt;
&lt;h2&gt;
  
  
  MongoDB Event Logs and Aggregation in Waitless
&lt;/h2&gt;

&lt;p&gt;Waitless is a completely different kind of system. It's a real-time queue management platform where customers join via QR code, track their position live, and leave without creating an account. The system generates a lot of events — but these events are not relational. They're behavioral.&lt;br&gt;
A CUSTOMER_JOINED event might contain the customer's position.&lt;br&gt;
A NO_SHOW event might contain a timestamp.&lt;br&gt;
A CUSTOMER_LEFT event might contain a reason.&lt;br&gt;
A CALLED_NEXT event might contain which token was called.&lt;/p&gt;

&lt;p&gt;The shape of the metadata changes depending on the event type. Forcing this into a rigid SQL schema would either require dozens of nullable columns or a JSON column that PostgreSQL would struggle to index efficiently at scale.&lt;br&gt;
MongoDB is built for exactly this. Each event is a document with flexible metadata:&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;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;timestamps&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueueEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required&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="nx"&gt;queueId&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;required&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="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QueueEventType&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;eventType&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QueueEventType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;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;This flexibility becomes powerful when combined with MongoDB's aggregation pipeline. Waitless computes analytics like total joined, total arrived, total no-shows, completion rate, and drop-off rate — all derived from event counts, not relational queries.&lt;br&gt;
A note on Redis: Waitless already had Redis in the stack backing BullMQ. But Redis is a key-value store optimized for job queues and caching — not for querying across documents or running aggregations. MongoDB was the right fit for this specific need.&lt;br&gt;
The separation between MongoDB and PostgreSQL is also crucial for performance. If analytics were computed in PostgreSQL, every dashboard refresh would compete with real-time queue operations for the same connection pool.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I had used PostgreSQL for Waitless events, every analytics query would hit the same connection pool as the queue state — meaning a slow dashboard query could delay a customer joining a queue.&lt;br&gt;
MongoDB avoids this entirely. It handles flexible event shapes, high write throughput, and analytics workloads without interfering with the transactional state stored in PostgreSQL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why PostgreSQL for One and MongoDB for the Other?
&lt;/h2&gt;

&lt;p&gt;The answer becomes clear once you see the nature of the data.&lt;br&gt;
In the delivery backend, events are part of the domain model. They describe state transitions, belong to relational entities, and require strong consistency. PostgreSQL is the right tool.&lt;br&gt;
In Waitless, events are part of analytics. They are flexible, metadata-heavy, and high-volume. They have no relational meaning — they're behavioral observations about what happened in a queue session. MongoDB is the right tool.&lt;br&gt;
Using PostgreSQL for both would have created unnecessary coupling and performance bottlenecks. Using MongoDB for both would have sacrificed relational guarantees where they mattered most.&lt;br&gt;
This is the essence of polyglot persistence: choose the database that matches the shape and purpose of the data, not the one you're most comfortable with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Question That Actually Matters
&lt;/h2&gt;

&lt;p&gt;Building two event-driven systems taught me that "SQL vs NoSQL" is the wrong question entirely.&lt;br&gt;
The real question is: what role do your events play in the system?&lt;br&gt;
If events belong to the domain model — use a relational database.&lt;br&gt;
If events belong to analytics — use a document database.&lt;br&gt;
PostgreSQL gave me structured timelines and strong consistency. MongoDB gave me flexible event logs and fast aggregation. Both systems use events, but the events serve completely different masters.&lt;br&gt;
Understanding that difference before writing a single line of code is what separates a working system from a well-designed one.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>postgres</category>
      <category>nestjs</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Understanding SQL Transactions: ACID, Isolation Levels, and How to Handle Locks</title>
      <dc:creator>Mhamd Ghanoum</dc:creator>
      <pubDate>Sun, 28 Jun 2026 10:57:12 +0000</pubDate>
      <link>https://dev.to/don21/understanding-sql-transactions-acid-isolation-levels-and-how-to-handle-locks-24g3</link>
      <guid>https://dev.to/don21/understanding-sql-transactions-acid-isolation-levels-and-how-to-handle-locks-24g3</guid>
      <description>&lt;p&gt;Modern backend systems rely on transactions to guarantee correctness especially in financial operations, delivery platforms, and any workflow where multiple users modify shared data.&lt;br&gt;
A solid understanding of ACID, isolation levels, and locking is essential for designing reliable systems and performing well in backend interviews.&lt;/p&gt;

&lt;p&gt;This article explains how transactions work, how isolation is implemented, and how to handle locks in real SQL databases such as PostgreSQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. What Is a Transaction?&lt;/strong&gt;&lt;br&gt;
A Transaction is a group of SQL operations that must be executed as a single atomic unit.&lt;br&gt;
Either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all operations succeed → COMMIT, or&lt;/li&gt;
&lt;li&gt;all operations fail → ROLLBACK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example: Paying for a pizza&lt;/strong&gt;&lt;br&gt;
When customer clicks Buy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deduct $20 from their balance&lt;/li&gt;
&lt;li&gt;Transfer $20 to the restaurant&lt;/li&gt;
&lt;li&gt;Create the order&lt;/li&gt;
&lt;li&gt;Commit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the server crashes after step 1, the database rolls back the deduction.&lt;br&gt;
This prevents partial updates and ensures data correctness.&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%2F6k371o75st2icy06r6u6.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%2F6k371o75st2icy06r6u6.png" alt=" " width="600" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. ACID Properties&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A — Atomicity&lt;br&gt;
All steps succeed or none do.&lt;/p&gt;

&lt;p&gt;C — Consistency&lt;br&gt;
The database always moves from one valid state to another.&lt;/p&gt;

&lt;p&gt;I — Isolation&lt;br&gt;
Each transaction behaves as if it’s the only one running.&lt;/p&gt;

&lt;p&gt;D — Durability&lt;br&gt;
Once committed, data survives crashes.&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%2Ft45wdedncwh1hnn79pv4.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%2Ft45wdedncwh1hnn79pv4.png" alt=" " width="700" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Isolation Problems&lt;/strong&gt;&lt;br&gt;
When multiple transactions run concurrently, anomalies can occur:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dirty Read — reading uncommitted data&lt;/li&gt;
&lt;li&gt;Non‑repeatable Read — reading the same row twice with different results&lt;/li&gt;
&lt;li&gt;Phantom Read — new rows appear between identical queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isolation levels define which anomalies are allowed.&lt;/p&gt;

&lt;p&gt;PostgreSQL uses MVCC (Multi‑Version Concurrency Control), which allows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;readers to avoid blocking writers&lt;/li&gt;
&lt;li&gt;writers to avoid blocking readers&lt;/li&gt;
&lt;li&gt;This improves performance and reduces unnecessary locking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. How to Deal with Isolation in SQL?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Isolation is handled by choosing the appropriate isolation level for the workload.&lt;br&gt;
For high‑throughput systems, Read Committed is usually enough.&lt;br&gt;
For operations requiring strict correctness, Repeatable Read or Serializable is used.&lt;br&gt;
PostgreSQL relies on MVCC snapshots to avoid unnecessary locking, and optimistic concurrency (versioning) can be used to prevent lost updates without blocking other transactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Isolation Levels&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read Uncommitted: all violations allowed.&lt;/li&gt;
&lt;li&gt;Read Committed: prevents dirty reads only.&lt;/li&gt;
&lt;li&gt;Repeatable Read: prevents dirty reads and Non‑repeatable Read.&lt;/li&gt;
&lt;li&gt;Serializable: prevents all violations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. How to Deal with Locks in SQL?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Locks are handled by using them intentionally and keeping them as short as possible.&lt;br&gt;
When exclusive access is required for example, assigning a driver to an order — SELECT … FOR UPDATE is used to lock the row.&lt;br&gt;
Long‑running transactions are avoided, optimistic locking is used to reduce contention, and deadlock errors are caught and retried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;&lt;br&gt;
Two drivers attempt to claim the same delivery order simultaneously.&lt;/p&gt;

&lt;p&gt;Step 1 — Lock the row&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;BEGIN&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;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This places a row‑level exclusive lock.&lt;br&gt;
Other transactions attempting to lock or update this row will wait.&lt;/p&gt;

&lt;p&gt;Step 2 — Update safely&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;UPDATE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;driver_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&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;'assigned'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&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 row is locked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no two drivers can claim the same order&lt;/li&gt;
&lt;li&gt;no race conditions occur&lt;/li&gt;
&lt;li&gt;no lost updates happen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the lock is released.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Preventing Lost Updates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three common strategies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Atomic Update
&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;UPDATE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;SET&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;'done'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Optimistic Locking (Versioning)
&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;UPDATE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;SET&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;'done'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;version&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;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="o"&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;ol&gt;
&lt;li&gt;Explicit Locking
&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;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&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;8. Deadlocks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A deadlock occurs when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transaction A waits for B&lt;/li&gt;
&lt;li&gt;Transaction B waits for A&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PostgreSQL resolves this automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one transaction is terminated&lt;/li&gt;
&lt;li&gt;an error is returned&lt;/li&gt;
&lt;li&gt;the application retries the operation
Deadlocks are normal in high‑concurrency systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Transactions are the foundation of reliable backend systems.&lt;br&gt;
A strong understanding of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ACID&lt;/li&gt;
&lt;li&gt;isolation levels&lt;/li&gt;
&lt;li&gt;MVCC&lt;/li&gt;
&lt;li&gt;locking&lt;/li&gt;
&lt;li&gt;deadlocks&lt;/li&gt;
&lt;li&gt;optimistic concurrency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…is essential for building correct, scalable, and safe applications.&lt;/p&gt;

&lt;p&gt;This knowledge is frequently tested in backend interviews, especially for roles involving distributed systems, financial operations, or high‑concurrency workloads.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>acid</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Indexing &amp; Query Optimization: How to Make Your Database Fast</title>
      <dc:creator>Mhamd Ghanoum</dc:creator>
      <pubDate>Tue, 09 Jun 2026 17:14:40 +0000</pubDate>
      <link>https://dev.to/don21/indexing-query-optimization-how-to-make-your-database-fast-5elp</link>
      <guid>https://dev.to/don21/indexing-query-optimization-how-to-make-your-database-fast-5elp</guid>
      <description>&lt;p&gt;Imagine you have a huge vendors table in an e‑commerce system, and thousands of orders are created every second.&lt;br&gt;
Every time a new order comes in, the system needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check if the vendor exists&lt;/li&gt;
&lt;li&gt;check if the vendor is active&lt;/li&gt;
&lt;li&gt;check if the product belongs to that vendor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your table is large, the database will scan the entire table to find the vendor.&lt;br&gt;
This is called a full table scan, and it becomes extremely slow as your data grows.&lt;/p&gt;

&lt;p&gt;This is where indexes save your system.&lt;/p&gt;

&lt;p&gt;🟦 What Is an Index?&lt;br&gt;
An index is a small, optimized data structure that tells the database engine exactly where a specific value is located.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of scanning the whole table, the database:&lt;/li&gt;
&lt;li&gt;looks at the index&lt;/li&gt;
&lt;li&gt;finds the exact row location&lt;/li&gt;
&lt;li&gt;jumps directly to it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns a slow O(n) search into a fast O(log n) lookup.&lt;/p&gt;

&lt;p&gt;🟩 Example: Indexing the Vendor ID&lt;br&gt;
Without an index:&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;vendors&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database scans the entire table.&lt;/p&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 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;idx_vendors_id&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vendors&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checks the index&lt;/li&gt;
&lt;li&gt;finds the row instantly&lt;/li&gt;
&lt;li&gt;returns the result in milliseconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between a system that collapses under load…&lt;br&gt;
and a system that handles millions of requests smoothly.&lt;/p&gt;

&lt;p&gt;🟧 Why Not Index Everything? (The Trade‑Off)&lt;br&gt;
Indexes make reads faster,&lt;br&gt;
but they make writes slower.&lt;/p&gt;

&lt;p&gt;Every time you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;INSERT&lt;/li&gt;
&lt;li&gt;UPDATE&lt;/li&gt;
&lt;li&gt;DELETE
…the database must also update every index.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Too many indexes = slow writes = bottlenecks.&lt;/p&gt;

&lt;p&gt;So we only index the hot paths — the most frequently queried fields.&lt;/p&gt;

&lt;p&gt;🟪 Clustered vs Non‑Clustered Indexes (PostgreSQL)&lt;br&gt;
Clustered Index&lt;br&gt;
The table is physically sorted by the index&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast range queries&lt;/li&gt;
&lt;li&gt;Only one per table&lt;/li&gt;
&lt;li&gt;Non‑Clustered Index&lt;/li&gt;
&lt;li&gt;Separate structure&lt;/li&gt;
&lt;li&gt;Points to the actual rows&lt;/li&gt;
&lt;li&gt;You can have many of them&lt;/li&gt;
&lt;li&gt;PostgreSQL uses B‑Tree indexes by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🟦 Other Index Types in PostgreSQL (Short Overview)&lt;br&gt;
PostgreSQL supports several index types optimized for different use cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B‑Tree (default)&lt;/strong&gt;&lt;br&gt;
Best for equality and range queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hash Index&lt;/strong&gt;&lt;br&gt;
Fast equality lookups (=), but limited.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GIN Index&lt;/strong&gt;&lt;br&gt;
Perfect 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;li&gt;Tags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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;idx_products_tags&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&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;GiST Index&lt;/strong&gt;&lt;br&gt;
Used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Geospatial data&lt;/li&gt;
&lt;li&gt;Distances&lt;/li&gt;
&lt;li&gt;Geometric shapes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example (useful in delivery apps):&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;idx_locations_gist&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geo_point&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;BRIN Index&lt;/strong&gt;&lt;br&gt;
Great for very large tables with naturally ordered data (logs, events, time‑series).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Partial Index&lt;/strong&gt;&lt;br&gt;
Index with a condition:&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;idx_active_vendors&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vendors&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="k"&gt;WHERE&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;&lt;strong&gt;Expression Index&lt;/strong&gt;&lt;br&gt;
Index on computed values:&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;idx_lower_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="k"&gt;LOWER&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;This flexibility is one of PostgreSQL’s biggest strengths.&lt;/p&gt;

&lt;p&gt;🟨 &lt;strong&gt;Query Optimization Tips&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Indexing is powerful, but you also need efficient queries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✔️ Select only the columns you need&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;id&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;vendors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&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;vendors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✔️ Use wildcards only at the end&lt;br&gt;
Good:&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;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'Sam%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bad:&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;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%Sam%'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;✔️ Use LIMIT when previewing 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;orders&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✔️ Run heavy queries during off‑peak hours&lt;br&gt;
Especially analytics or batch jobs.&lt;/p&gt;

&lt;p&gt;🟫 Conclusion&lt;br&gt;
Indexes make your reads extremely fast&lt;/p&gt;

&lt;p&gt;But they slow down writes&lt;/p&gt;

&lt;p&gt;Use them wisely on the most important fields&lt;/p&gt;

&lt;p&gt;Combine indexing with good query practices&lt;/p&gt;

&lt;p&gt;Always measure performance before and after&lt;/p&gt;

&lt;p&gt;Indexing is one of the simplest ways to make your backend feel instant, even under heavy load&lt;/p&gt;

</description>
      <category>database</category>
      <category>indexing</category>
      <category>postgres</category>
    </item>
    <item>
      <title>SQL vs NoSQL — How to Choose the Right Database for Your System</title>
      <dc:creator>Mhamd Ghanoum</dc:creator>
      <pubDate>Fri, 05 Jun 2026 10:32:01 +0000</pubDate>
      <link>https://dev.to/don21/sql-vs-nosql-how-to-choose-the-right-database-for-your-system-2iic</link>
      <guid>https://dev.to/don21/sql-vs-nosql-how-to-choose-the-right-database-for-your-system-2iic</guid>
      <description>&lt;p&gt;Hello everyone!&lt;br&gt;
My name is Mhamd, a backend engineer who loves building scalable systems and explaining complex backend topics in a simple, practical way.&lt;/p&gt;

&lt;p&gt;If you enjoy system design, backend architecture, or want to exchange ideas — feel free to reach out!&lt;/p&gt;

&lt;p&gt;Why This Topic Matters?&lt;br&gt;
When designing a new system, one of the most important architectural decisions is choosing the right type of database.&lt;/p&gt;

&lt;p&gt;This choice affects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;performance&lt;/li&gt;
&lt;li&gt;scalability&lt;/li&gt;
&lt;li&gt;consistency&lt;/li&gt;
&lt;li&gt;development speed&lt;/li&gt;
&lt;li&gt;and even cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, I’ll break down SQL vs NoSQL, when to use each, and how I combine both in real-world projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQL — Structured, Relational, and Consistent&lt;/strong&gt;&lt;br&gt;
 SQL databases (PostgreSQL, MySQL, SQL Server…) rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A predefined schema&lt;/li&gt;
&lt;li&gt;Strong consistency&lt;/li&gt;
&lt;li&gt;ACID transactions&lt;/li&gt;
&lt;li&gt;Clear relationships between tables
When SQL is a great fit?
Use SQL when:&lt;/li&gt;
&lt;li&gt;Your data structure is known in advance&lt;/li&gt;
&lt;li&gt;You need strong consistency&lt;/li&gt;
&lt;li&gt;You have complex relationships (Users ↔ Orders ↔ Products)&lt;/li&gt;
&lt;li&gt;You need powerful queries and joins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: E‑commerce&lt;br&gt;
Entities like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Products&lt;/li&gt;
&lt;li&gt;Users&lt;/li&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…have clear relationships and require consistent transactions.&lt;br&gt;
SQL is perfect here.&lt;/p&gt;

&lt;p&gt;But what about Amazon‑scale systems?&lt;br&gt;
As the system grows globally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;traffic explodes&lt;/li&gt;
&lt;li&gt;data becomes distributed&lt;/li&gt;
&lt;li&gt;sharding becomes complex&lt;/li&gt;
&lt;li&gt;consistency becomes harder&lt;/li&gt;
&lt;li&gt;SQL still works — but requires advanced architecture.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;NoSQL — Flexible, Fast, and Horizontally Scalable&lt;/strong&gt;&lt;br&gt;
NoSQL is not one technology — it’s a family:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document stores (MongoDB)&lt;/li&gt;
&lt;li&gt;Key‑value stores (Redis)&lt;/li&gt;
&lt;li&gt;Wide‑column stores (Cassandra)&lt;/li&gt;
&lt;li&gt;Graph databases (Neo4j)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When NoSQL is a great fit?&lt;br&gt;
Use NoSQL when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your data is semi‑structured or unstructured&lt;/li&gt;
&lt;li&gt;You need high write throughput&lt;/li&gt;
&lt;li&gt;You need horizontal scaling&lt;/li&gt;
&lt;li&gt;You’re building real‑time systems&lt;/li&gt;
&lt;li&gt;Your schema changes frequently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;Real‑time feeds&lt;/li&gt;
&lt;li&gt;Chat systems&lt;/li&gt;
&lt;li&gt;IoT&lt;/li&gt;
&lt;li&gt;Caching layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Using Both Together — The Hybrid Approach&lt;br&gt;
Modern systems rarely fit into one category&lt;/strong&gt;.&lt;br&gt;
In my projects, I often combine SQL + NoSQL.&lt;/p&gt;

&lt;p&gt;Example: My Delivery Backend System&lt;br&gt;
PostgreSQL for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;li&gt;Products&lt;/li&gt;
&lt;li&gt;Users&lt;/li&gt;
&lt;li&gt;State machines&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;li&gt;Because these require structure and consistency.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Real‑time deliverer tracking&lt;/li&gt;
&lt;li&gt;WebSocket events&lt;/li&gt;
&lt;li&gt;Caching&lt;/li&gt;
&lt;li&gt;Fast lookups&lt;/li&gt;
&lt;li&gt;Because these require speed and flexibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What Happens When You Use GraphQL on Top of PostgreSQL?&lt;/p&gt;

&lt;p&gt;When you put GraphQL above PostgreSQL:&lt;/p&gt;

&lt;p&gt;PostgreSQL remains the source of truth GraphQL becomes a flexible API layer The client can request exactly the data it needs No overfetching or underfetching.Perfect for dashboards and mobile apps&lt;/p&gt;

&lt;p&gt;This combination gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL consistency&lt;/li&gt;
&lt;li&gt;GraphQL flexibility&lt;/li&gt;
&lt;li&gt;A single endpoint for all queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;SQL is perfect for structured, relational, consistent data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NoSQL is perfect for speed, flexibility, and large-scale distributed systems&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most real systems use both.&lt;/strong&gt;&lt;br&gt;
The right choice depends on your data and your system’s behavior — not on trends.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article&lt;br&gt;
I’ll be writing more about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;system design&lt;/li&gt;
&lt;li&gt;caching&lt;/li&gt;
&lt;li&gt;event-driven architecture&lt;/li&gt;
&lt;li&gt;microservices&lt;/li&gt;
&lt;li&gt;real-time backend patter
ns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow me on DEV Community — and feel free to share your thoughts or questions!&lt;/p&gt;

</description>
      <category>sql</category>
      <category>nosql</category>
      <category>postgres</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
