<?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: Asad Zaman</title>
    <description>The latest articles on DEV Community by Asad Zaman (@asad_zaman_250f19f22742c4).</description>
    <link>https://dev.to/asad_zaman_250f19f22742c4</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3045277%2Fdd02231f-89d3-4c37-88fd-dc6033f6d8e3.png</url>
      <title>DEV Community: Asad Zaman</title>
      <link>https://dev.to/asad_zaman_250f19f22742c4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/asad_zaman_250f19f22742c4"/>
    <language>en</language>
    <item>
      <title>Scaling a Keyword Marking System: Partitioned vs Non-Partitioned Database Approaches</title>
      <dc:creator>Asad Zaman</dc:creator>
      <pubDate>Wed, 23 Jul 2025 16:55:02 +0000</pubDate>
      <link>https://dev.to/asad_zaman_250f19f22742c4/scaling-a-keyword-marking-system-partitioned-vs-non-partitioned-database-approaches-2e3n</link>
      <guid>https://dev.to/asad_zaman_250f19f22742c4/scaling-a-keyword-marking-system-partitioned-vs-non-partitioned-database-approaches-2e3n</guid>
      <description>&lt;h2&gt;
  
  
  Scalable Keyword Marking System
&lt;/h2&gt;

&lt;p&gt;Building a realtime keyword highlighting system for millions of users and billions of keywords presents significant architectural challenges. In this document I will try to address database partitioning and efficient keyword matching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem Overview
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1 Million Users:&lt;/strong&gt; Each user can store up to 10,000 keywords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10 Billion Keywords:&lt;/strong&gt; Assume total system capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Requirement:&lt;/strong&gt; Sub-second retrieval and highlighting of all user keywords on any given page, including overlapping matches (e.g., "Dhaka," "Dhaka City").&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/asadpstu/keyword_match
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Database: PostGres
&lt;/h2&gt;

&lt;p&gt;You can run as a docker service. Just copy and paste in the terminal.  Within a few moment, your pgres database will be up and running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d --name my-postgres-db --network my_pg_network -p 5434:5432 -e POSTGRES_DB=mydatabase -e POSTGRES_USER=myuser -e POSTGRES_PASSWORD=mypassword -v pg_data:/var/lib/postgresql/data postgres:16-alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download dbever or you can use your favorite db tools.&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.amazonaws.com%2Fuploads%2Farticles%2Fgb4d9eqnsdl0wv65847h.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.amazonaws.com%2Fuploads%2Farticles%2Fgb4d9eqnsdl0wv65847h.png" alt=" " width="800" height="661"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`CREATE TABLE keywords (user_id BIGINT NOT NULL,keyword TEXT NOT NULL,PRIMARY KEY (user_id, keyword)) PARTITION BY HASH (user_id);`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating partitions of keywords table e.g. 256 partitions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DO $$
BEGIN
  FOR i IN 0..255 LOOP
    EXECUTE format('CREATE TABLE keywords_p%s PARTITION OF keywords FOR VALUES WITH (MODULUS 256, REMAINDER %s);', i, i);
  END LOOP;
END;
$$;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am attaching some queries too. It might come handy if you feel it is worthy too practice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM keywords WHERE user_id = 1;
SELECT * FROM keywords_p125 WHERE user_id=1;

SELECT tableoid::regclass AS partition_name, *
FROM keywords
WHERE user_id = 1;

EXPLAIN SELECT user_id, keyword FROM keywords WHERE user_id=1;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Database Partitioning is important
&lt;/h2&gt;

&lt;p&gt;Without partitioning, a single, massive table may lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slow Queries:&lt;/strong&gt; Indexing and scanning across billions of rows become prohibitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large, Inefficient Indexes:&lt;/strong&gt; Unable to fit in memory, causing I/O bottlenecks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Maintenance Overhead:&lt;/strong&gt; &lt;code&gt;VACUUM&lt;/code&gt;, &lt;code&gt;ANALYZE&lt;/code&gt;, &lt;code&gt;REINDEX&lt;/code&gt; operations are lengthy and disruptive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow Data Management:&lt;/strong&gt; Deleting old user data is resource-intensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With hash partitioning on &lt;code&gt;user_id&lt;/code&gt; (e.g., into 256 partitions), we achieve:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast Queries:&lt;/strong&gt; Queries only hit a relevant, small partition (1/256th of the data).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smaller, In-Memory Indexes:&lt;/strong&gt; Faster lookups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient Maintenance:&lt;/strong&gt; Operations run quickly per partition, with less impact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant Data Pruning:&lt;/strong&gt; Dropping old user data by dropping entire partitions is instantaneous.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Parallelism:&lt;/strong&gt; Concurrent access and maintenance across partitions improve throughput.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This directly translates to &lt;strong&gt;sub-second query times&lt;/strong&gt; even at massive scale, unlike the multi-second or more queries seen without partitioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyword Matching: The Role of Aho–Corasick Algorithm (Trie Data structure)
&lt;/h2&gt;

&lt;p&gt;Retrieving keywords is just one part. Efficiently matching thousands of keywords against page content is solved by the &lt;strong&gt;Aho–Corasick algorithm&lt;/strong&gt;. This algorithm builds a Trie of keywords, enabling &lt;strong&gt;linear-time scanning&lt;/strong&gt; of text to detect all occurrences, including overlapping matches, which is vital for real-time performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary: Partitioning vs. Non-Partitioning
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Without Partitioning&lt;/th&gt;
&lt;th&gt;With Partitioning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Degrades with scale (seconds+)&lt;/td&gt;
&lt;td&gt;Stays fast (sub-second) due to smaller scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Index Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Huge, inefficient, disk-bound&lt;/td&gt;
&lt;td&gt;Smaller, efficient, fits in memory per partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance Ops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slow, blocking&lt;/td&gt;
&lt;td&gt;Fast, isolated, less impactful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Pruning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Painfully slow row-by-row&lt;/td&gt;
&lt;td&gt;Instant (drop partition)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Quick bottlenecks&lt;/td&gt;
&lt;td&gt;Efficient, leverages parallelism&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Database partitioning, especially by &lt;code&gt;user_id&lt;/code&gt;, combined with an efficient keyword matching algorithm like Aho–Corasick can help building a performant and scalable keyword marking system. I would like to suggest partitioning your table early for any system that may handle large scale per user data.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Handling Race Conditions Using Node.js — Yes, You Read It Right.</title>
      <dc:creator>Asad Zaman</dc:creator>
      <pubDate>Mon, 21 Apr 2025 15:13:07 +0000</pubDate>
      <link>https://dev.to/asad_zaman_250f19f22742c4/handling-race-conditions-using-nodejs-yes-you-read-it-right-5ji</link>
      <guid>https://dev.to/asad_zaman_250f19f22742c4/handling-race-conditions-using-nodejs-yes-you-read-it-right-5ji</guid>
      <description>&lt;p&gt;Have you ever thought about what could happen if two people tried to withdraw from the same bank account at the exact same time?&lt;/p&gt;

&lt;p&gt;That’s a classic case of a race condition — and if not handled properly, it can lead to real money vanishing (or duplicating!) out of thin air.&lt;/p&gt;

&lt;p&gt;In this post, I’ll walk you through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What a race condition is (in the context of banking)&lt;/li&gt;
&lt;li&gt;How to simulate one in Node.js&lt;/li&gt;
&lt;li&gt;How to fix it using a mutex (lock).&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  What Is a Race Condition?
&lt;/h4&gt;

&lt;p&gt;A race condition occurs when two or more operations access shared data at the same time, and the final result depends on the order in which they execute.&lt;/p&gt;

&lt;p&gt;In banking terms:&lt;br&gt;
Two people transfer money from the same account at the same time. If not handled properly, both could think the money is still there — and withdraw it — causing the account to go negative or become inconsistent.&lt;/p&gt;
&lt;h4&gt;
  
  
  Simulating the Problem in Node.js
&lt;/h4&gt;

&lt;p&gt;Let’s say we have a shared in-memory balance of $100, and two transfer requests come in at the same time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const app = express();
const port = 3000;
let accountBalance = 100; 

function transfer(amount) {
    const balance = accountBalance;
    if (balance &amp;lt; amount) {
        throw new Error('Insufficient funds');
    }
    // Simulate some delay in processing. It can be many reason
    setTimeout(() =&amp;gt; {
        accountBalance = balance - amount;
        console.log(`Balance after transfer: $${accountBalance}`);
    }, 1000);
}

app.post('/transfer', (req, res) =&amp;gt; {
    const amount = 50;
    try {
        transfer(amount);
        res.send('Transfer completed');
    } catch (error) {
        res.status(400).send(error.message);
    }
});

app.listen(port, () =&amp;gt; {
    console.log(`Server running on http://localhost:${port}`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From your terminal - &lt;/p&gt;

&lt;blockquote&gt;

&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST http://localhost:3000/transfer &amp;amp; curl -X POST http://localhost:3000/transfer &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Did you notice? You still have 50$! &lt;br&gt;
This is broken! If two requests run simultaneously, both read $100, both think there's enough, and both deduct $50 — ending up with a final balance of $50, when it should be $0.&lt;/p&gt;

&lt;p&gt;Let's fix the problem -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { Mutex } = require('async-mutex');
const mutex = new Mutex();

async function transfer(amount) {
    const release = await mutex.acquire();
    try {
        const currentBalance = accountBalance;
        if (currentBalance &amp;lt; amount) {
            throw new Error('Insufficient funds');
        }
        await new Promise(resolve =&amp;gt; setTimeout(resolve, 100));
        accountBalance = currentBalance - amount;
        console.log(`Transfer successful. New balance: $${accountBalance}`);
    } finally {
        release();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, only one transfer runs at a time. The second request waits until the first finishes. And you may have noticed - your balance is now zero.&lt;/p&gt;

&lt;p&gt;This is a hypothetical example, but race conditions like this do happen in real-world applications — such as counting page views, managing product stock in e-commerce platforms, and more. The scary part? They often go unnoticed until they cause serious issues. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Optimized Email lookup for millions of users</title>
      <dc:creator>Asad Zaman</dc:creator>
      <pubDate>Sat, 12 Apr 2025 18:26:59 +0000</pubDate>
      <link>https://dev.to/asad_zaman_250f19f22742c4/optimizing-email-registration-super-efficient-reliable-solution-with-redisbloom-and-postgresql-546i</link>
      <guid>https://dev.to/asad_zaman_250f19f22742c4/optimizing-email-registration-super-efficient-reliable-solution-with-redisbloom-and-postgresql-546i</guid>
      <description>&lt;p&gt;Fast, Scalable, and Reliable — How to solve the email registration problem at scale for millions of users without compromising on performance or data integrity.so, here’s the deal.&lt;/p&gt;

&lt;p&gt;When you’re dealing with millions of users, one tiny problem starts eating your system alive — checking if an email already exists. It sounds small, but if you're doing it 10 million times? Boom, memory gone. Performance drops. It is not good.&lt;/p&gt;

&lt;p&gt;But don’t worry. We got this. Let’s mix a bit of RedisBloom and PostgreSQL, and build a system that’s fast, scalable, and doesn't chew up all your memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  RedisBloom: The probabilistic memory-saver
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory efficiency:&lt;/strong&gt; It doesn’t store the full emails. It just uses a bit array and some math magic (Bloom Filter logic). So, 10 million emails = only ~30MB memory. Yup. Not kidding.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Crazy fast:&lt;/strong&gt;  Check if an email exists in constant time, O(1). Like blink-of-an-eye fast.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lightweight:&lt;/strong&gt;  No need to store actual emails in Redis. Less load, more speed.\&lt;br&gt;
 Downside? Well... false positives are possible (~1%).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;RedisBloom’s cool, but to be honest — 1% false positive is still a problem. So, when RedisBloom thinks the email is already there, we just double-check in PostgreSQL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  PostgreSQL: The Trusty Double Checker
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accurate:&lt;/strong&gt; It’s our final truth source.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fast checks:&lt;/strong&gt; We use SELECT 1 instead of pulling the whole email to keep things lean.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Workflow: How it all comes together
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check RedisBloom:&lt;/strong&gt; Check if the email might exist. If RedisBloom says no — we’re good. Move forward and register.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check PostgreSQL:&lt;/strong&gt; If RedisBloom says “maybe”, we run a simple &lt;code&gt;SELECT 1&lt;/code&gt; in Postgres. Just to be safe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insert into Both:&lt;/strong&gt; If the email doesn’t exist in Postgres, insert it in both Postgres and RedisBloom to keep them in sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why it will work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficient:&lt;/strong&gt; RedisBloom handles most checks. Saves RAM. Super snappy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reliablity:&lt;/strong&gt; PostgreSQL catches the rare false positive. You get 100% accuracy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalable:&lt;/strong&gt; Works smooth with 100 or 100 million users. Future-proof.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secure:&lt;/strong&gt; We hash emails (SHA-256) so we’re not storing plain data anywhere.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;If you’re dealing with around 10 million emails, using RedisBloom can literally save you hundreds of megabytes of memory. Instead of storing every email directly, RedisBloom squeezes all that down into around 30MB. That’s crazy efficient.&lt;/p&gt;

&lt;p&gt;On the other hand, PostgreSQL still does its job reliably, but only when we really need it. We’re not hammering the database for every check—only when RedisBloom gives us a “maybe.” And even then, we’re just doing a lightweight SELECT 1, which is super fast and doesn’t pull any unnecessary data.&lt;/p&gt;

&lt;p&gt;For performance — each RedisBloom check is almost instant, like around 0.001 to 0.005 milliseconds. PostgreSQL might take a tad longer, maybe 0.005 to 0.02 milliseconds, but again, that’s only for a small chunk of the checks. Combine them and even if you do 10 million checks, it’s done in under a few minutes. So, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You're saving a lot of memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're not burning your database with unnecessary query&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;our system stays fast and clean even at a huge scale.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Boom. That’s what good architecture looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  SHA-256: Keeping emails private
&lt;/h3&gt;

&lt;p&gt;Don’t save raw emails. Before storing anything, we hash each email using SHA-256, so even if someone breaks into your DB, they won’t be able to read the email addresses. Please check the following code, I hope this is understandable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function **emailExists**(email: string): Promise\&amp;lt;boolean&amp;gt; {
    const hash = hashEmail(email);
    const mightExist = await redis.bf.exists('email\_bloom', hash);
    if (!mightExist) return false;
    const res = await pg.query(
        'SELECT 1 FROM registered\_emails WHERE email\_hash = $1', \[hash]
    );
    return res.rowCount &amp;gt; 0;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function **registerEmail**(email: string) {
    if (await emailExists(email)) {
      console.log('Email already exists.');
      return;
    }
    const hash = hashEmail(email);
    await pg.query(
       'INSERT INTO registered\_emails (email\_hash) VALUES ($1)', \[hash]
    );
    await redis.bf.add('email\_bloom', hash);
    console.log('Email registered!');

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use RedisBloom for fast, memory-efficient lookups.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use PostgreSQL for accurate, final checks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hash your emails with SHA-256 for privacy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get the best of both worlds: speed  and integrity.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup is battle-tested. It’s like the perfect combo of fast-n-loose and strict-n-safe. It works at scale, it respects your resources, and your users are protected.&lt;/p&gt;

&lt;p&gt;Give it a try. Scale with confidence. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>database</category>
      <category>api</category>
    </item>
  </channel>
</rss>
