<script data-pm-proxy="intercept"></script><?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[duthaho]]></title><description><![CDATA[I learn one system at a time, to the point where I can make design decisions and read other people's code. These are those notes, rewritten as structured deep-dives — mental models first, gotchas included.]]></description><link>https://duthaho.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png</url><title>duthaho</title><link>https://duthaho.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 01 Sep 2026 16:24:57 GMT</lastBuildDate><atom:link href="/__u/duthaho.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[duthaho]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[duthaho@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[duthaho@substack.com]]></itunes:email><itunes:name><![CDATA[duthaho]]></itunes:name></itunes:owner><itunes:author><![CDATA[duthaho]]></itunes:author><googleplay:owner><![CDATA[duthaho@substack.com]]></googleplay:owner><googleplay:email><![CDATA[duthaho@substack.com]]></googleplay:email><googleplay:author><![CDATA[duthaho]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Elasticsearch running at scale: shards, replicas & the write path]]></title><description><![CDATA[How one index survives a billion documents and a dead machine &#8212; and what 'near real-time' actually is, mechanically.]]></description><link>https://duthaho.substack.com/p/elasticsearch-running-at-scale-shards</link><guid isPermaLink="false">https://duthaho.substack.com/p/elasticsearch-running-at-scale-shards</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Sat, 22 Aug 2026 00:34:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NihD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Elasticsearch deep-dive &#183; Part 7 of 8.</em></p><p>Everything in this series so far has quietly run on <strong>one index</strong>, and you&#8217;ve been picturing it as a single thing sitting on a single machine. That picture is fine for learning queries and mappings. It falls apart the moment someone asks the two questions that actually decide whether Elasticsearch stays up in production: how does one index hold <strong>a billion documents</strong> when no single machine has the RAM or disk for it &#8212; and what happens when one of those machines simply <strong>dies</strong> at 3am?</p><p>The answer to both is that Elasticsearch is <strong>distributed</strong>. It doesn&#8217;t keep your index on one box; it spreads the data across many machines and coordinates them so the whole thing looks like one index to you. If you&#8217;ve run containers or clusters before &#8212; Kubernetes, a database replica set, anything with more than one node &#8212; the shape will feel familiar. Four terms, built up from the bottom.</p><h2>Shards, replicas, nodes, cluster</h2><p><strong>A primary shard is the unit of scaling.</strong> When you create an index, Elasticsearch doesn&#8217;t store it as one blob &#8212; it <em>splits</em> it into shards. The crucial thing to internalise: <strong>each shard is itself a complete, self-contained Lucene index</strong> &#8212; a full inverted index with its own segments, exactly like the one from the earlier parts of this series. An index isn&#8217;t &#8220;one Lucene index cut into pieces&#8221;; it&#8217;s <em>N</em> independent Lucene indices that Elasticsearch presents as one. Split an index into 5 primaries and spread them across 5 machines and you have <strong>horizontal scaling</strong>: five machines&#8217; worth of RAM, CPU, and disk working one index.</p><p>Every document lives in <strong>exactly one</strong> primary shard. Which one isn&#8217;t random &#8212; Elasticsearch hashes the document&#8217;s id:</p><pre><code>shard = hash(_id) % number_of_primaries</code></pre><p>That formula is how a search knows where a document is, and (as you&#8217;ll see) it&#8217;s also the reason one number can never change.</p><p><strong>A replica is a copy of a shard.</strong> It does two jobs. First, <strong>failover</strong>: if the node holding a primary dies, Elasticsearch promotes one of that shard&#8217;s replicas to be the new primary &#8212; no data lost, no downtime for that shard. Second, <strong>read scaling</strong>: a search for a given shard can be served by the primary <em>or</em> any of its replicas, so more replicas means more machines answering reads in parallel. One hard rule governs placement: <strong>a replica is never put on the same node as its primary.</strong> A copy that dies with the original is not a copy.</p><p><strong>A node is a single Elasticsearch process</strong> &#8212; one server, one JVM &#8212; that holds some shards. <strong>A cluster is a set of nodes working together</strong>, coordinating which shards (primary and replica) live on which node, and reshuffling them when nodes join or leave. You talk to any node; it routes your request to wherever the data actually is.</p><h2>Fixed vs flexible</h2><p>Two settings control this, and they behave completely differently &#8212; which one you can change later is the single most important operational fact in this whole article.</p><blockquote><p><strong>&#10022; One number is carved in stone, the other is a dial</strong></p><p><code>number_of_shards</code> &#8212; the count of <strong>primaries</strong> &#8212; is <strong>fixed at index creation</strong>. You cannot change it later without reindexing into a new index. The reason is the routing formula: <code>hash(_id) % number_of_primaries</code>. Change the divisor and <em>every existing document</em> would now hash to a different shard than the one it&#8217;s actually stored in &#8212; lookups would miss, and the index would be corrupt. Since ES 7.0 the default is <strong>1 primary shard</strong>.</p><p><code>number_of_replicas</code> is the opposite &#8212; <strong>changeable anytime, live.</strong> Dial it up when you need more read capacity or more redundancy; dial it down to reclaim disk. No reindex, no downtime. It&#8217;s a runtime knob.</p></blockquote><p>The mental shortcut: <strong>decide your primary count carefully once; treat replicas as a slider you adjust forever.</strong></p><h2>Cluster health colours</h2><p>Walk through what actually happens when you create an index, because it explains the colour your cluster reports and why a fresh single-node dev box is almost always &#8220;yellow.&#8221;</p><pre><code>PUT /products
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1
  }
}</code></pre><p>You&#8217;ve asked for 3 primaries and 1 replica <em>of each</em>, so Elasticsearch has <strong>6 shards to place</strong>: 3 primaries + 3 replicas. On a <strong>1-node</strong> dev laptop it assigns the 3 primaries with no problem &#8212; but the 3 replicas have nowhere to go, because a replica can&#8217;t sit on the same node as its primary and there&#8217;s no second node. Those 3 replicas stay <strong>unassigned</strong>, and the cluster reports <strong>yellow</strong>.</p><p>Add a <strong>second node</strong> and the cluster immediately places the 3 replicas there &#8212; now all 6 shards are assigned and health goes <strong>green</strong>. And the payoff: if a node now dies, its shards exist as copies on the other node, so Elasticsearch promotes the surviving replicas to primaries and <strong>keeps serving with no data lost</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!NihD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!NihD!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!NihD!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!NihD!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!NihD!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!NihD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg" width="1170" height="1258" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:1258,&quot;width&quot;:1170,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:0,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!NihD!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!NihD!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!NihD!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!NihD!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaeb2cfb-7b4a-43ef-bd41-b14d146ac21f_1170x1258.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The shortcut worth memorising: <strong>yellow is a redundancy problem, red is a data problem.</strong> Yellow means &#8220;you&#8217;d lose availability if another node failed.&#8221; Red means &#8220;some of your data is <em>already</em> unreachable right now.&#8221; Yellow can wait until morning; red is a page.</p><h2>The oversharding trap</h2><p>Now the mistake that new cluster designers make almost universally, because the intuition is so reasonable.</p><blockquote><p><strong>&#9888; 'More shards = faster' &#8212; the #1 cluster-design mistake</strong></p><p>The reasoning goes: shards enable parallelism, so more shards must mean more speed &#8212; I&#8217;ll create 50 up front to be safe. It bites you two ways.</p><p><strong>(1) Every shard has fixed overhead.</strong> Remember each shard is a <em>full Lucene index</em> &#8212; it costs file handles, heap for its segments and metadata, and a slice of cluster-state bookkeeping, <em>regardless of how little data it holds</em>. Thousands of tiny shards can exhaust the heap and slow the entire cluster even when the total data is small. Common guidance is to aim for <strong>tens of GB per shard</strong> and to keep the shard count proportional to your heap &#8212; not &#8220;one shard per anything.&#8221;</p><p><strong>(2) A search fans out to every shard.</strong> A query hits <strong>all</strong> shards of the index and then merges the results. More shards means more per-query fan-out and more merging; past a point, <em>adding</em> shards makes queries <strong>slower</strong>, not faster.</p><p>And you can&#8217;t cheaply undo it, because primaries are fixed &#8212; an over-sharded index means a <strong>reindex</strong>. So shard for the data size and node count you actually have, not &#8220;just in case.&#8221;</p></blockquote><p>The fluent stance is boring on purpose: with the ES 7.0+ default of 1 primary and small-to-medium data, you often don&#8217;t touch the shard count at all until a single shard would grow past the tens-of-GB range.</p><div><hr></div><p>That&#8217;s the architecture. Now the other half of running at scale: getting data <em>in</em> fast, and understanding what &#8220;in&#8221; even means.</p><h2>The bulk API &#8212; batch your writes</h2><p>Here&#8217;s the scenario this section exists for: you need to <strong>sync 5 million rows from Postgres</strong> into Elasticsearch. The naive approach is one HTTP request per document &#8212; <code>POST /products/_doc/1</code>, then <code>/2</code>, then <code>/3</code>. That&#8217;s fine for a handful of documents and <strong>catastrophic</strong> for volume: each request pays a full network round-trip plus per-request coordination overhead, five million times over. You&#8217;ll be waiting for days and hammering the cluster with connection churn.</p><p>The <strong>bulk API</strong> exists precisely for this. <code>POST /_bulk</code> packs many operations &#8212; <code>index</code>, <code>create</code>, <code>update</code>, <code>delete</code> &#8212; into a <strong>single request</strong>, amortizing the round-trip and coordination cost across all of them. This isn&#8217;t an optimization you reach for occasionally; for loading data it is simply <em>the</em> way. Any real ingest path &#8212; the Postgres sync, a reindex, a backfill &#8212; runs through bulk.</p><p>The format has one quirk that surprises everyone the first time. It&#8217;s <strong>NDJSON</strong> &#8212; newline-delimited JSON &#8212; and each document takes <strong>two lines</strong>: an <em>action line</em> describing what to do, then the <em>source line</em> with the document body.</p><pre><code>POST /_bulk
{ "index": { "_index": "products", "_id": "1" } }
{ "name": "Widget", "price": 9.99, "in_stock": true }
{ "index": { "_index": "products", "_id": "2" } }
{ "name": "Gadget", "price": 14.50, "in_stock": false }</code></pre><p>Two things about that format. The body <strong>must end with a trailing newline</strong> &#8212; the parser uses it as a delimiter, and omitting it is a classic &#8220;why is my last document missing?&#8221; bug. And the reason for the two-lines-per-doc design is deliberate: Elasticsearch parses the payload <strong>line by line</strong> rather than loading the whole request as one giant JSON tree, so it can stream a huge batch without holding all of it in memory at once.</p><h2>Bulk is partial-success &#8212; check every item</h2><p>This is the trap that silently loses data, and it&#8217;s worth being blunt about.</p><blockquote><p><strong>&#9888; A 200 does not mean 'all indexed'</strong></p><p>A bulk request does <strong>not</strong> succeed or fail as a unit. If document #37 hits a mapping conflict, the other 999 in the batch still index just fine &#8212; #37 fails <strong>alone</strong>. And here&#8217;s the part that catches people: the overall <strong>HTTP status is still 200</strong>, even when individual items failed.</p><p>The truth lives in the <em>response body</em>, not the status code. Every bulk response carries an <code>"errors": true</code> flag when any item failed, plus a per-item <code>items</code> array where each entry has its own <code>status</code> and, on failure, an <code>error</code>:</p><pre><code>{
  "errors": true,
  "items": [
    { "index": { "_id": "1", "status": 201 } },
    { "index": { "_id": "2", "status": 400,
        "error": { "type": "mapper_parsing_exception" } } }
  ]
}</code></pre><p>Treat a 200 as &#8220;everything indexed&#8221; and you will <strong>silently drop documents</strong> &#8212; a data-loss bug that surfaces weeks later as &#8220;some records just aren&#8217;t in search.&#8221; Production ingest <strong>must</strong> read <code>errors</code>, walk the per-item results, and log or retry each failure individually.</p></blockquote><p>For the Postgres sync, that means your loader can&#8217;t just fire batches and move on. After each <code>_bulk</code> call it inspects <code>response.errors</code>; if true, it pulls the failed ids out of the <code>items</code> array and decides &#8212; retry the transient ones, quarantine and log the genuinely malformed ones. &#8220;Fire and forget&#8221; is how you end up with a search index that&#8217;s quietly missing 0.3% of your data.</p><h2>Refresh, flush, translog &#8212; three different things</h2><p>These three words get used interchangeably in casual conversation, and that&#8217;s exactly how people end up confused about durability and visibility. They are <strong>three distinct operations</strong>, and being precise about them is what separates someone who can reason about Elasticsearch from someone who guesses.</p><blockquote><p><strong>&#10022; Visibility, crash-safety, and durability are three separate mechanisms</strong></p><p>When you index a document it first lands in an <strong>in-memory buffer</strong> &#8212; it is <em>not</em> yet in any searchable segment, so a <code>_search</code> won&#8217;t find it.</p><p><strong>Refresh = visibility.</strong> A refresh takes that buffer and writes it into a <strong>new Lucene segment</strong>, which initially lives in the OS filesystem cache. Search reads segments, so a document only becomes searchable <em>after a refresh</em>. The default refresh interval is <strong>1 second</strong> &#8212; and that one-second gap between &#8220;indexed&#8221; and &#8220;searchable&#8221; is exactly what &#8220;<strong>near real-time</strong>&#8221; means, mechanically. (Note: a direct <code>GET /products/_doc/1</code>bypasses this entirely &#8212; it reads the buffer, so <em>get-by-id</em> is real-time even though <em>search</em> is not.)</p><p><strong>Translog = crash-safety, immediately.</strong> The translog (transaction log) is <strong>appended on every write</strong> the instant it happens. So if a node crashes after you&#8217;ve indexed but <em>before</em> the data reached durable disk segments, Elasticsearch <strong>replays the translog</strong> on restart and nothing is lost.</p><p><strong>Flush = durability.</strong> A flush is the separate operation that <strong>fsyncs the segments to disk</strong> and then <strong>trims the translog</strong> (its entries are now safely persisted in segments, so they&#8217;re no longer needed for replay).</p><p>Crisp summary: <strong>refresh</strong> makes data searchable (in the fs cache); the <strong>translog</strong> keeps it crash-safe between flushes; <strong>flush</strong> persists it to disk and trims the log.</p></blockquote><p>The one sentence to <em>never</em> say is &#8220;refresh flushes to disk.&#8221; It doesn&#8217;t &#8212; refresh only moves data into a segment in the filesystem cache and makes it searchable; it says nothing about durability. Durability is flush&#8217;s job, and crash-safety in the meantime is the translog&#8217;s job. Conflate refresh and flush and every reasoning-about-data-loss conversation you have afterward will be subtly wrong.</p><h2>Tuning a big bulk load</h2><p>Back to the 5-million-row sync, armed with the write path. Refreshing once per second is great for interactive use, but during a massive load it&#8217;s <strong>wasteful</strong> &#8212; you&#8217;re building a fresh Lucene segment every second, producing a pile of tiny segments that Elasticsearch then has to merge together later. And every replica has to index every document too, doubling (or more) the write work while the load runs.</p><p>The standard recipe is to <strong>turn both off during the load and restore them after</strong>. Disable auto-refresh by setting <code>refresh_interval</code> to <code>-1</code>, and drop <code>number_of_replicas</code> to <code>0</code> so only primaries do the indexing work:</p><pre><code># 1. Before the load: disable refresh, drop replicas.
PUT /products/_settings
{ "index": { "refresh_interval": "-1", "number_of_replicas": 0 } }

# 2. Stream the Postgres rows in batches of ~5000 via _bulk,
#    using the Postgres primary key as _id (Part 2's PK-as-_id).
#    CHECK response.errors on EVERY batch &#8212; see the gotcha above.
POST /_bulk
{ "index": { "_index": "products", "_id": "42" } }
{ "name": "Widget", "price": 9.99 }
# ...4999 more documents...

# 3. After the load: restore refresh and replicas, then refresh once.
PUT /products/_settings
{ "index": { "refresh_interval": "1s", "number_of_replicas": 1 } }
POST /products/_refresh</code></pre><p>The trade you&#8217;re making is explicit: with refresh disabled the newly-loaded documents <strong>aren&#8217;t searchable until step 3</strong>, and with replicas at 0 you have <strong>no redundancy during the load</strong>. In exchange you get a large throughput gain and a clean set of segments &#8212; the &#8220;load 10 million documents without melting the cluster&#8221; pattern. Restoring replicas afterward lets Elasticsearch copy the finished primaries across nodes in one efficient pass instead of indexing every document twice as it arrives.</p><p>Notice how much of this reuses earlier parts: <strong>PK-as-</strong><code>_id</code> (Part 2) so re-running the sync updates rather than duplicates, <strong>replicas</strong> for the redundancy you restore at the end, and <strong>refresh</strong> for the visibility you deliberately traded away and then bought back.</p><h2>Where this goes next</h2><p>You now have the operational half of Elasticsearch: how an index survives scale and machine death, and exactly what happens to a document between &#8220;indexed&#8221; and &#8220;searchable and durable.&#8221; The final part, Part 8: design decisions, steps back from mechanics to judgement &#8212; <em>when</em> Elasticsearch is the right tool at all, and when reaching for it is a mistake.</p><blockquote><p>If you can explain why a fresh single-node cluster is yellow rather than green, and state the difference between refresh, flush, and the translog without conflating any two of them, you understand how Elasticsearch actually runs &#8212; not just how to query it.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[Elasticsearch aggregations: the analytics engine]]></title><description><![CDATA[The pivot from 'which documents match?' to 'what can I compute across them?' &#8212; and it maps straight onto GROUP BY.]]></description><link>https://duthaho.substack.com/p/elasticsearch-aggregations-the-analytics</link><guid isPermaLink="false">https://duthaho.substack.com/p/elasticsearch-aggregations-the-analytics</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Sat, 15 Aug 2026 04:47:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!R1T0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Elasticsearch deep-dive &#183; Part 6 of 8.</em></p><p>Everything so far has answered one question: <strong>which documents match?</strong> You wrote queries, they filtered and ranked, they handed back a list of hits. Aggregations answer a completely different question &#8212; <strong>what can I compute across the documents that matched?</strong> Counts, averages, groupings, histograms, distributions.</p><p>This is the analytics engine of Elasticsearch. It&#8217;s the machinery under every Kibana dashboard and the &#8220;A&#8221; in the reason people run the ELK stack for logs and metrics. And here&#8217;s the good news if you already know SQL: aggregations map almost one-to-one onto the <code>GROUP BY</code> and aggregate functions you write every day. You&#8217;re not learning a new idea; you&#8217;re learning a new syntax for an idea you already have.</p><h2>Two families</h2><p>There are two kinds of aggregation, and keeping them straight is most of the battle.</p><p><strong>Metric aggregations</strong> compute a single number over a set of documents. These are the direct analogs of SQL&#8217;s aggregate functions:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!R1T0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!R1T0!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!R1T0!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!R1T0!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!R1T0!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!R1T0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg" width="1170" height="868" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:868,&quot;width&quot;:1170,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:0,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!R1T0!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!R1T0!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!R1T0!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!R1T0!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66f9ea5-e83f-436b-ad31-dc0bc44c6bdf_1170x868.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><code>stats</code> is a small gift: one aggregation returns the count, min, max, average, and sum together, so you don&#8217;t fire five separate ones to describe a column.</p><p><strong>Bucket aggregations</strong> group documents into buckets &#8212; this is <code>GROUP BY</code>. Each bucket is a set of documents that share something, and every bucket carries a <code>doc_count</code>: how many documents fell into it (your <code>COUNT(*)</code> per group).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!k-dj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!k-dj!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png 424w, /__u/substackcdn.com/image/fetch/$s_!k-dj!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png 848w, /__u/substackcdn.com/image/fetch/$s_!k-dj!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png 1272w, /__u/substackcdn.com/image/fetch/$s_!k-dj!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!k-dj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png" width="853" height="331" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/24e5f260-750d-47ca-ac3a-54743725c273_853x331.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:331,&quot;width&quot;:853,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37472,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://duthaho.substack.com/i/211180911?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!k-dj!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png 424w, /__u/substackcdn.com/image/fetch/$s_!k-dj!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png 848w, /__u/substackcdn.com/image/fetch/$s_!k-dj!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png 1272w, /__u/substackcdn.com/image/fetch/$s_!k-dj!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e5f260-750d-47ca-ac3a-54743725c273_853x331.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So <code>terms</code> on a <code>status</code> field gives you one bucket for <code>paid</code>, one for <code>pending</code>, one for <code>cancelled</code>, each with its own doc count &#8212; exactly what <code>GROUP BY status</code> produces.</p><h2>The power move: nesting</h2><p>Metric and bucket aggregations are useful alone, but the real leverage comes from combining them.</p><blockquote><p><strong>&#10022; A metric per group is just a metric inside a bucket</strong></p><p>Put a <strong>metric</strong> aggregation <em>inside</em> a <strong>bucket</strong> aggregation and you get &#8220;a number per group&#8221; &#8212; which is precisely what <code>SELECT category, AVG(price) ... GROUP BY category</code> does. The bucket aggregation carves the documents into groups; the metric runs once per group. Buckets can nest inside buckets too: group by <code>category</code>, then by <code>status</code> <em>within</em> each category, and you&#8217;ve written a two-level <code>GROUP BY</code> without a single <code>JOIN</code>. Nesting is the whole toolkit &#8212; most real aggregation requests are a bucket with one or more metrics spooned inside it.</p></blockquote><h2>The request shape</h2><p>Aggregations don&#8217;t get their own endpoint. They ride on <code>_search</code>, alongside the query. That raises an obvious question: if you only want the grouped numbers, why pay to return documents too?</p><p>You don&#8217;t. Set <code>"size": 0</code> to say &#8220;I don&#8217;t want any hits back &#8212; just the aggregation results.&#8221; It&#8217;s faster (Elasticsearch skips fetching and scoring documents to return) and the answer lands in a separate <code>aggregations</code> block in the response.</p><p>Here&#8217;s the parallel, side by side. The SQL you&#8217;d write:</p><pre><code>SELECT category, AVG(price) AS avg_price, COUNT(*) AS n
FROM products
WHERE in_stock = true
GROUP BY category;</code></pre><p>And the Elasticsearch request:</p><pre><code>GET /products/_search
{
  "size": 0,
  "query": { "term": { "in_stock": true } },
  "aggs": {
    "by_category": {
      "terms": { "field": "category" },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } }
      }
    }
  }
}</code></pre><p>Read it against the SQL. The <code>terms</code> aggregation named <code>by_category</code> is your <code>GROUP BY category</code>. The nested <code>avg_price</code>metric is your <code>AVG(price)</code>. The <code>query</code> is your <code>WHERE</code>. The response mirrors it:</p><pre><code>{
  "aggregations": {
    "by_category": {
      "buckets": [
        { "key": "keyboards", "doc_count": 42, "avg_price": { "value": 58.3 } },
        { "key": "mice",      "doc_count": 31, "avg_price": { "value": 24.9 } }
      ]
    }
  }
}</code></pre><p>Each entry in <code>by_category.buckets[]</code> is one group: <code>key</code> is the category, <code>doc_count</code> is your <code>COUNT(*)</code>, and <code>avg_price.value</code> is your <code>AVG(price)</code>. You didn&#8217;t need to select the count &#8212; the bucket hands it to you for free.</p><blockquote><p><strong>&#8594; The filter runs first &#8212; like WHERE before GROUP BY</strong></p><p>One twist versus SQL, though it works the way you&#8217;d hope: the <code>query</code> runs <strong>first</strong>, and the aggregations compute <strong>only over the documents it matched</strong>. That&#8217;s the same ordering as <code>WHERE</code> before <code>GROUP BY</code> &#8212; you group what survived the filter, not the whole index. Narrow with the query, then aggregate over what&#8217;s left.</p></blockquote><h2>You can&#8217;t aggregate on analyzed text</h2><p>Now the concept from earlier in this series comes back to collect its debt. Text analysis &#8212; the tokenizing, lowercasing, stemming that makes full-text search work &#8212; is exactly what makes aggregating on a <code>text</code> field either fail or lie to you.</p><blockquote><p><strong>&#9888; A terms agg on a text field either errors or does the wrong thing</strong></p><p>It&#8217;s tempting to point a <code>terms</code> aggregation straight at a <code>name</code> field and expect one bucket per product name. Two things stop you:</p><p><strong>(1) It usually errors.</strong> Aggregations and sorting read from a columnar structure called <strong>doc_values</strong>, built per-document at index time. Analyzed <code>text</code> fields don&#8217;t build doc_values by default &#8212; their content lives in the<strong>inverted index</strong> instead. So by default a <code>terms</code> agg on a <code>text</code> field <strong>fails</strong>, telling you to enable <code>fielddata</code>. Don&#8217;t reach for that switch: <code>fielddata</code> loads the field into heap memory and is memory-expensive enough to be a genuine footgun on anything but tiny fields.</p><p><strong>(2) Even forced, it buckets the wrong thing.</strong> Enable <code>fielddata</code> and the aggregation runs &#8212; over the <strong>individual analyzed tokens</strong>, not the whole value. Aggregate a <code>name</code> field holding <code>"Wireless Keyboard"</code> and you get buckets for <code>wireless</code>, <code>keyboard</code>, <code>bluetooth</code>&#8230; the tokenized fragments, never <code>"Wireless Keyboard"</code> as one group. You asked to group by product; you grouped by word.</p></blockquote><p>The fix is the rule from Part 2: <strong>aggregate and sort on </strong><code>keyword</code><strong>, numeric, or date fields &#8212; never on analyzed </strong><code>text</code><strong>.</strong>Those field types build doc_values and store the value whole, exactly one per document, which is what grouping needs.</p><p>This is the whole reason dynamic mapping gives a string <em>two</em> fields. When Elasticsearch first sees a string like a product name, it maps it as <code>name</code> (a <code>text</code> field, <a href="/__u/duthaho.substack.com/elasticsearch-03-text-analysis/">analyzed</a> for search) <strong>and</strong> <code>name.keyword</code> (a <code>keyword</code> field, stored whole for aggregating and sorting). So to group by product name, you write:</p><pre><code>"terms": { "field": "name.keyword" }</code></pre><p>The <code>.keyword</code> sub-field is the one with doc_values and the intact value. Search hits <code>name</code>; analytics hits <code>name.keyword</code>. Same string, two jobs, two field types.</p><h2>Where this goes next</h2><p>You&#8217;ve now got the load-bearing shape of every aggregation: metrics compute numbers, buckets group documents, nesting gives you a metric per group, <code>size: 0</code> drops the hits, and you aggregate on <code>keyword</code>/numeric/date fields &#8212; never analyzed <code>text</code>. That covers the large majority of real dashboards and reports.</p><p>There&#8217;s more depth here when you need it, all built on the same nesting idea:</p><ul><li><p><strong>Sub-aggregations</strong> go deeper &#8212; bucket inside bucket inside metric, for multi-level breakdowns.</p></li><li><p><strong>Pipeline aggregations</strong> compute over the <em>output</em> of other aggregations &#8212; running totals, moving averages, bucket-to-bucket derivatives.</p></li><li><p><code>cardinality</code> gives you approximate distinct counts (like <code>COUNT(DISTINCT)</code>) cheaply at scale, trading a little accuracy for a lot of memory saved on high-cardinality fields.</p></li></ul><p>But the more pressing question is how any of this runs when your index is spread across many machines. An aggregation like <code>terms</code> has to be computed on each shard and then merged into one answer &#8212; and that merge step has consequences for accuracy and cost. That&#8217;s Part 7: how Elasticsearch runs across shards and replicas, and what the write path looks like underneath.</p><blockquote><p>If you can say which field type you&#8217;d aggregate on for a product-name breakdown and <em>why</em> the <code>text</code> version fails &#8212; and why you&#8217;d set <code>size: 0</code> when you only want the numbers &#8212; you understand aggregations better than most people copy-pasting dashboard queries.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[Why results come back ranked: BM25 relevance]]></title><description><![CDATA[Three intuitions &#8212; term frequency, rarity, and field length &#8212; that explain every _score Elasticsearch returns.]]></description><link>https://duthaho.substack.com/p/why-results-come-back-ranked-bm25</link><guid isPermaLink="false">https://duthaho.substack.com/p/why-results-come-back-ranked-bm25</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Tue, 11 Aug 2026 13:49:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Elasticsearch deep-dive &#183; Part 5 of 8.</em></p><p>Part 1 made a claim and then moved on: Elasticsearch returns results <strong>ranked</strong>, not just matched. A SQL <code>WHERE name LIKE '%keyboard%'</code> gives you rows that match &#8212; in whatever order the database felt like. Elasticsearch gives you rows <em>sorted by how well they match</em>, and it attaches a number, the <code>_score</code>, to each one.</p><p>This part is <em>how</em> that number gets computed. The engine behind it is <strong>BM25</strong> (Best Match 25), the default similarity in modern Elasticsearch. You will never need to write the formula out. What you need are three intuitions &#8212; and together they explain almost every &#8220;why did <em>that</em> product rank first?&#8221; question you&#8217;ll ever ask.</p><p>Keep a <code>products</code> index in mind the whole way through: each document has a short <code>name</code> and a long <code>description</code>, and you&#8217;re building the search box for the store.</p><h2>Factor 1 &#8212; Term Frequency (TF), with diminishing returns</h2><p>Start with the obvious signal. If someone searches <code>keyboard</code>, a document whose text says &#8220;keyboard&#8221; five times is probably more <em>about</em> keyboards than one that says it once. That&#8217;s <strong>term frequency</strong>: the more often a query term appears in a document, the higher that document should score.</p><p>The naive version of this idea &#8212; just count the occurrences &#8212; has a nasty failure mode. A page that repeats &#8220;keyboard keyboard keyboard keyboard keyboard&#8230;&#8221; fifty times would bury a genuinely relevant product. Keyword stuffing wins.</p><p>BM25 fixes this by making term frequency <strong>saturate</strong>. Going from 1 occurrence to 2 boosts the score a lot. Going from 2 to 3 boosts it less. By the time you&#8217;re going from 20 to 21, the needle barely moves &#8212; the curve flattens out toward a ceiling. Each additional mention is worth less than the one before it.</p><p>That single design choice is the headline improvement over naive counting: it captures &#8220;this term shows up here&#8221; without letting a document win by sheer repetition. Presence matters; obsessive repetition doesn&#8217;t pay.</p><h2>Factor 2 &#8212; Inverse Document Frequency (IDF): rare terms matter more</h2><p>Term frequency looks <em>inside</em> one document. The second intuition looks <em>across the whole index</em>, and it&#8217;s the one that most changes how rankings feel.</p><p>Ask yourself how much a match actually tells you. If a term appears in almost every document in the index, matching it is nearly worthless &#8212; it doesn&#8217;t distinguish this document from the pile. If a term appears in only a handful of documents, matching it is highly informative: this is one of the few documents that&#8217;s about that thing.</p><p>BM25 encodes exactly this with <strong>inverse document frequency</strong>. Common terms get weighted <em>down</em>; rare terms get weighted <em>up</em>. IDF is computed per term, across the index.</p><p>Make it concrete. Search <code>wireless mechanical keyboard</code> against your <code>products</code> index. Nearly every product mentions &#8220;keyboard,&#8221; so matching &#8220;keyboard&#8221; barely separates the field. Far fewer say &#8220;mechanical.&#8221; So a document that matches &#8220;mechanical&#8221; earns much more from that match than a document that only matches &#8220;keyboard&#8221; &#8212; even though both are query words. Rarity is worth more than ubiquity.</p><h2>Factor 3 &#8212; Field-length normalization</h2><p>The third intuition is about <em>where</em> the match lands. The same term is not equally significant in a 3-word <code>name</code> as it is in a 500-word <code>description</code>.</p><p>If &#8220;keyboard&#8221; appears in a three-word product name, it&#8217;s a big fraction of what that field is about &#8212; the field is practically <em>declaring itself</em> to be about keyboards. If &#8220;keyboard&#8221; appears once somewhere in a 500-word description, it&#8217;s a footnote among hundreds of other words.</p><p>BM25 accounts for this by <strong>normalizing for field length</strong>: it effectively divides out how long the field is. A match in a short field scores higher than the same match buried in a long one. Short, focused fields punch above their weight &#8212; which is usually what you want, since a product&#8217;s name is a much stronger relevance signal than a paragraph of marketing copy.</p><h2>Put them together</h2><p>Those three factors don&#8217;t operate in isolation &#8212; BM25 combines them into a single number per matched term, then sums across the query&#8217;s terms.</p><blockquote><p><strong>&#10022; The whole formula, in one sentence</strong></p><p><code>_score</code> &#8776; <strong>(how often the term appears, saturating)</strong> &#215; <strong>(how rare the term is across the index)</strong> &#247; <strong>(how long the field is)</strong>. Term frequency pushes the score up but flattens out; IDF multiplies rare terms up and common terms down; field length divides it back down. Internalize that one sentence and you can <em>predict</em> rankings before you ever run the query &#8212; which is the whole point.</p></blockquote><h2>Predicting a ranking &#8212; worked example</h2><p>Let&#8217;s use the model to call a ranking in advance. Two documents in your <code>products</code> index:</p><pre><code>{ "name": "Mechanical Keyboard" }</code></pre><pre><code>{ "name": "Wireless Keyboard Bundle with Keyboard Wrist Rest and Keyboard Cover" }</code></pre><p>Call them Doc A and Doc B. Now run this query against the <code>name</code> field:</p><pre><code>{
  "query": {
    "match": { "name": "mechanical keyboard" }
  }
}</code></pre><p>Doc A ranks higher. Walk through <em>why</em>, factor by factor:</p><ul><li><p><strong>IDF.</strong> The query has two terms. &#8220;keyboard&#8221; is common across the index, so it&#8217;s weighted low. &#8220;mechanical&#8221; is rarer, so it&#8217;s weighted high &#8212; and <strong>only Doc A matches it at all.</strong> That rare, high-weight term goes entirely to Doc A. Doc B collects nothing from the most valuable word in the query.</p></li><li><p><strong>Field length.</strong> Doc A&#8217;s <code>name</code> is two words, so its matches normalize <em>up</em>. Doc B&#8217;s <code>name</code> is eleven words, so its matches normalize <em>down</em> &#8212; the field is longer, so each match counts for less.</p></li><li><p><strong>TF saturation.</strong> Doc B repeats &#8220;keyboard&#8221; three times, which looks like it should help. But term frequency saturates: the second and third &#8220;keyboard&#8221; add far less than the first. Repetition can&#8217;t rescue Doc B, and it certainly can&#8217;t outweigh the &#8220;mechanical&#8221; match it&#8217;s missing.</p></li></ul><p>Every factor points the same direction, so Doc A wins comfortably. You don&#8217;t have to take that on faith &#8212; you can ask Elasticsearch to show its work:</p><pre><code>{
  "query": {
    "match": { "name": "mechanical keyboard" }
  },
  "explain": true
}</code></pre><p>With <code>"explain": true</code> in the search body, each hit comes back with a breakdown of how its <code>_score</code> was built &#8212; the <code>tf</code>, <code>idf</code>, and field-length components for every matched term. When a ranking surprises you, this is the first place to look.</p><h2>The trap: _score is not an absolute number</h2><p>Here&#8217;s where real bugs come from. The <code>_score</code> is a number, and numbers <em>look</em> comparable, so people treat it like a universal relevance grade. It isn&#8217;t. Two wrong assumptions cause most of the pain.</p><blockquote><p><strong>&#9888; You cannot threshold _score across queries</strong></p><p><strong>Wrong assumption #1: scores are comparable between queries.</strong> BM25 scores are <strong>not</strong> normalized to 0&#8211;1 or to any fixed range. A top score of <code>8.2</code> in one query and <code>2.1</code> in another tells you <em>nothing</em> about which result is &#8220;better&#8221; in absolute terms &#8212; the numbers only order results <em>within a single query&#8217;s result set</em>. So a rule like &#8220;only show results with <code>_score</code> above 5.0&#8221; is a trap: it&#8217;s a different, meaningless cutoff for every query.</p><p><strong>Wrong assumption #2: a document has a stable score.</strong> IDF is computed relative to the <em>current</em> index, so the same document with the same query can score differently as the index&#8217;s content changes. (Historically, scores could also differ slightly across shards, since each shard computed IDF from its own local statistics.) <code>_score</code> is a ranking signal for <em>one query against the index as it is right now</em> &#8212; not a portable, durable relevance grade.</p></blockquote><p>So what do you do when you genuinely need an absolute or normalized notion of relevance &#8212; say, &#8220;hide results that aren&#8217;t good enough&#8221;? You treat it as a separate problem. Options include <strong>rescoring</strong> the top window with a secondary model, <strong>learning-to-rank</strong>, or <strong>client-side min-max normalization</strong> within a result set. Just don&#8217;t reach for a raw-score threshold and expect it to mean the same thing tomorrow, or on the next query.</p><h2>Where this goes next</h2><p>You now have a mental model that predicts rankings. Everything else in relevance is a way to <em>bend</em> that model on purpose:</p><ul><li><p><strong>Relevance tuning</strong> &#8212; boosting one field over another (a match in <code>name</code> should count more than one in <code>description</code>), or reshaping scores entirely with <code>function_score</code> (recency, popularity, price).</p></li><li><p><strong>Analyzers</strong> &#8212; synonyms and stemming from Part 3: Text Analysis decide <em>what counts as a match</em> in the first place, which is upstream of every score BM25 computes.</p></li></ul><p>But the pivot from here is bigger than tuning search. In Part 6: aggregations, we stop asking &#8220;which documents match?&#8221; and start asking &#8220;what do the matching documents <em>tell me</em> in aggregate?&#8221; &#8212; the move from search engine to analytics engine.</p><blockquote><p>If you can predict which of two products scores higher for a given query &#8212; and explain why in terms of rarity, saturation, and field length &#8212; you understand BM25. And if you can explain why you must <em>never </em>threshold raw <code>_score</code> across two different queries, you understand its one real trap.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[The Query DSL: match, term, bool & the two contexts]]></title><description><![CDATA[Which leaf query to use, and whether it should score relevance or just include-and-exclude.]]></description><link>https://duthaho.substack.com/p/the-query-dsl-match-term-bool-and</link><guid isPermaLink="false">https://duthaho.substack.com/p/the-query-dsl-match-term-bool-and</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Fri, 07 Aug 2026 11:41:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!vEwO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Elasticsearch deep-dive &#183; Part 4 of 8.</em></p><p>In SQL you ask questions with a <code>WHERE</code> clause: a flat string of <code>AND</code>s, <code>OR</code>s, and comparisons. In Elasticsearch you ask questions with <strong>JSON trees</strong> &#8212; a structure called the <strong>Query DSL</strong>, where every node is a query and queries nest inside other queries. If you&#8217;re building a search box for a <code>products</code> catalogue, this is the language you&#8217;ll live in.</p><p>Two things make the DSL click, and this part is about both: <strong>which leaf query to use</strong> for a given field, and <strong>which context</strong>that query runs in. The first decides whether your terms even match; the second decides whether the match contributes to ranking. Both build directly on <a href="/__u/duthaho.substack.com/elasticsearch-03-text-analysis/">text analysis</a> &#8212; because whether a query analyzes its input is the difference between finding your documents and finding nothing at all.</p><h2>The three workhorse queries</h2><p>Ninety percent of the queries you write are built from three pieces: <code>match</code>, <code>term</code>, and <code>bool</code>. Learn these and the rest is variation.</p><h3>match &#8212; the full-text query</h3><p><code>match</code> is what you reach for when you&#8217;re searching human-typed words against a <code>text</code> field. Its defining behaviour: it <strong>analyzes your search string first</strong>, using the same analyzer as the field, then looks for the resulting terms.</p><pre><code>{ "query": { "match": { "name": "wireless keyboard" } } }</code></pre><p>The string <code>"wireless keyboard"</code> gets analyzed to the terms <code>[wireless, keyboard]</code>, and Elasticsearch finds documents whose <code>name</code> field contains those terms. Because it&#8217;s full-text, results come back <strong>scored</strong> &#8212; ranked by how well each document matches. A product named &#8220;Wireless Keyboard, Compact&#8221; scores higher than one that only mentions &#8220;wireless&#8221; in passing.</p><h3>term &#8212; the exact query</h3><p><code>term</code> is the opposite reflex. It does <strong>not</strong> analyze your input &#8212; it looks up the literal value you gave it, byte for byte.</p><pre><code>{ "query": { "term": { "status": "shipped" } } }</code></pre><p>This finds documents where <code>status</code> is exactly <code>shipped</code>. Use <code>term</code> on the fields that hold exact values: <code>keyword</code> fields, numbers, booleans, and dates. It&#8217;s the query for structured, categorical data &#8212; the equivalent of <code>WHERE status = 'shipped'</code>.</p><blockquote><p><strong>&#9888; term against a text field is a classic miss</strong></p><p>Run <code>term</code> on an analyzed <code>text</code> field and you&#8217;ll often get zero hits. The field was analyzed at index time (<code>"Wireless Keyboard"</code> became the terms <code>[wireless, keyboard]</code>), but <code>term</code> does <strong>not</strong> analyze your input &#8212; so a search for the literal <code>"Wireless Keyboard"</code> looks for that whole capitalized string as a single term, which was never stored. Rule: <code>term</code> for <code>keyword</code>/numbers/booleans/dates, <code>match</code> for <code>text</code>.</p></blockquote><h3>bool &#8212; the combiner</h3><p>Real questions have more than one condition. <code>bool</code> is the query that assembles other queries into <code>AND</code> / <code>OR</code> / <code>NOT</code> logic. It has exactly four clause types, and each one means something specific:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!vEwO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!vEwO!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png 424w, /__u/substackcdn.com/image/fetch/$s_!vEwO!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png 848w, /__u/substackcdn.com/image/fetch/$s_!vEwO!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vEwO!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!vEwO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png" width="868" height="469" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:469,&quot;width&quot;:868,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46864,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://duthaho.substack.com/i/210208450?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!vEwO!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png 424w, /__u/substackcdn.com/image/fetch/$s_!vEwO!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png 848w, /__u/substackcdn.com/image/fetch/$s_!vEwO!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vEwO!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdca514a8-b370-4b7a-9430-6cee38503e4e_868x469.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here&#8217;s a <code>bool</code> that combines all four ideas &#8212; the words the user typed, some hard constraints, and an exclusion:</p><pre><code>{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "wireless keyboard" } }
      ],
      "filter": [
        { "term":  { "in_stock": true } },
        { "range": { "price": { "lte": 100 } } }
      ],
      "must_not": [
        { "term": { "brand": "Generic" } }
      ]
    }
  }
}</code></pre><p>Read it top to bottom: the <code>name</code> must match &#8220;wireless keyboard&#8221; (and that match drives ranking); the product must be in stock and cost no more than 100; and it must not be brand &#8220;Generic&#8221;. One tree, four kinds of logic.</p><h2>Query context vs filter context</h2><p>Notice that the table above splits the four clauses along a line that has nothing to do with AND/OR/NOT: some clauses <strong>score</strong> and some don&#8217;t. That split is the single most important idea in this article.</p><p>Every query in Elasticsearch runs in one of two <strong>contexts</strong>, and the context &#8212; not the query type &#8212; decides whether a relevance score is computed.</p><blockquote><p><strong>&#10022; The question each context asks</strong></p><p><strong>Query context</strong> asks <em>&#8220;how well does this document match?&#8221;</em> &#8212; it computes a numeric relevance <code>_score</code>. <strong>Filter context</strong> asks <em>&#8220;does this document match, yes or no?&#8221;</em> &#8212; it computes no score at all, just a boolean include/exclude. Same <code>term</code> query can run in either context; the context is what changes. Put your relevance signals in query context and your hard constraints in filter context, and the DSL starts writing itself.</p></blockquote><p><strong>Query context</strong> is used by <code>must</code> and <code>should</code>. It computes the <code>_score</code> &#8212; the ranked-results behaviour that makes Elasticsearch a search engine rather than a filter. When you put the user&#8217;s typed words in <code>must</code>, you&#8217;re saying &#8220;rank documents by how well they match this.&#8221;</p><p><strong>Filter context</strong> is used by <code>filter</code> and <code>must_not</code>. It computes no score &#8212; just membership. This buys you two things:</p><ul><li><p><strong>It&#8217;s faster.</strong> No relevance math runs, so a filter is cheaper than the equivalent scored query.</p></li><li><p><strong>It&#8217;s cacheable.</strong> Elasticsearch caches the set of documents matching a filter. Ask &#8220;which documents have <code>in_stock: true</code>?&#8221; once, and the answer is remembered &#8212; every later query that reuses that filter is near-instant, because the set is already computed.</p></li></ul><p>The rule of thumb falls straight out of this. Hard constraints you don&#8217;t want influencing the ranking &#8212; <code>status = shipped</code>, <code>price &lt;= 100</code>, <code>in_stock = true</code> &#8212; belong in <strong>filter</strong> context. The words the user actually typed, the ones that should decide what ranks first &#8212; belong in <strong>must</strong> (or <strong>should</strong>). Ranking signals get scored; gatekeeping conditions don&#8217;t.</p><h2>From SQL to bool</h2><p>Put it all together with a concrete translation. Here&#8217;s the question in SQL &#8212; find in-stock wireless keyboards under 100, excluding a house brand, ordered by how well they match:</p><pre><code>SELECT *
FROM products
WHERE description LIKE '%wireless keyboard%'
  AND in_stock = true
  AND price &lt;= 100
  AND brand &lt;&gt; 'Generic'
ORDER BY &lt;relevance&gt;;</code></pre><p>That last line is the tell: a relational engine has no native notion of &#8220;order by relevance.&#8221; <code>LIKE</code> either matches or it doesn&#8217;t &#8212; there&#8217;s no built-in score for <em>how well</em> a row matched. You&#8217;d have to bolt on your own ranking. In Elasticsearch, relevance ranking is the default, and the idiomatic translation makes the split explicit:</p><pre><code>{
  "query": {
    "bool": {
      "must": [
        { "match": { "description": "wireless keyboard" } }
      ],
      "filter": [
        { "term":  { "in_stock": true } },
        { "range": { "price": { "lte": 100 } } }
      ],
      "must_not": [
        { "term": { "brand": "Generic" } }
      ]
    }
  }
}</code></pre><p>The <code>must</code> clause <strong>drives ranking</strong>: results come back ordered by how well <code>description</code> matched &#8220;wireless keyboard&#8221;. The <code>filter</code> clauses <strong>narrow the set</strong> &#8212; in stock, under 100 &#8212; without disturbing that ranking, and they&#8217;re cached for the next query. The <code>must_not</code> excludes the house brand, also without scoring. Each condition sits in the context that matches its job.</p><h2>The &#8220;just put everything in must&#8221; trap</h2><p>Once you know <code>must</code> means AND, it&#8217;s tempting to pour every condition into it: match the description, <code>must</code> the <code>in_stock: true</code>, <code>must</code> the price range, done. It returns the right documents &#8212; so what&#8217;s wrong with it?</p><blockquote><p><strong>&#9888; Everything in must is a performance and correctness smell</strong></p><p>Putting a hard constraint like <code>in_stock: true</code> in <code>must</code> goes wrong three ways. <strong>(1) Wasted work:</strong> <code>must</code> runs in query context, so Elasticsearch computes a relevance score for a plain true/false condition &#8212; scoring math that means nothing. <strong>(2) No caching:</strong> query context doesn&#8217;t use the filter cache, so a condition you apply on <em>every</em> search gets recomputed every single time, instead of being answered from a cached set. <strong>(3) Skewed ranking:</strong> the constraint&#8217;s score contributes to <code>_score</code>, so a pure gatekeeping condition subtly reorders your results for no good reason. The fluent move is the split from earlier &#8212; <strong>query context for what to rank by, filter context for what must be true.</strong></p></blockquote><p>The fix is always the same edit: take the conditions that are hard constraints and move them from <code>must</code> to <code>filter</code>. Same documents come back, but faster, cacheable, and with ranking that reflects only the signals you actually meant to rank by.</p><h2>Where this goes next</h2><p>You now know <em>which</em> clause a condition belongs in and <em>why</em>. What you haven&#8217;t seen is how the <code>must</code> and <code>should</code> scores are actually computed &#8212; where the number in <code>_score</code> comes from when a <code>match</code> query ranks one document above another. That&#8217;s the algorithm behind relevance, called <strong>BM25</strong>, and it&#8217;s the subject of Part 5. Once you understand how scoring works, the choice between query and filter context stops being a rule of thumb and becomes an obvious consequence.</p><blockquote><p>If you can look at any condition in a query and say instantly which <code>bool</code> clause it belongs in &#8212; and whether it should shape the ranking or merely gate the results &#8212; you understand the Query DSL better than most people shipping search to production.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[How Elasticsearch reads text: the analysis pipeline]]></title><description><![CDATA[The same analyzer runs when you index and when you search &#8212; and when it doesn't, matching silently breaks.]]></description><link>https://duthaho.substack.com/p/how-elasticsearch-reads-text-the</link><guid isPermaLink="false">https://duthaho.substack.com/p/how-elasticsearch-reads-text-the</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Tue, 04 Aug 2026 16:01:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Elasticsearch deep-dive &#183; Part 3 of 8. </em></p><p>Part 1 gave you the load-bearing idea: an inverted index maps <strong>terms</strong> to the document IDs that contain them. Search a term, get back the docs &#8212; fast, because the lookup is a dictionary hit, not a scan.</p><p>But that skipped a question. Your product catalog is full of messy strings &#8212; <code>"Wireless Keyboards, Running FAST!"</code> &#8212; with mixed case, commas, exclamation marks, plurals. The inverted index doesn&#8217;t store <em>that</em>. It stores clean terms. So what turns the string a user typed into the terms that actually go in the index?</p><p>That process is <strong>analysis</strong>, and the thing that performs it is an <strong>analyzer</strong>. Understanding the analyzer is the single biggest step toward reasoning about Elasticsearch search behaviour &#8212; including the <code>text</code> vs <code>keyword</code> rule you met in Part 2, which turns out to be nothing more than <em>two different analyzers running on the same string.</em></p><h2>The analyzer pipeline</h2><p>An analyzer is a pipeline with three stages, always in this order:</p><pre><code>raw text &#8594; [character filters] &#8594; [tokenizer] &#8594; [token filters] &#8594; terms</code></pre><p>Each stage has a clear job.</p><p><strong>Character filters (0 or more)</strong> operate on the <em>raw string</em>, before anything is split up. They add, remove, or rewrite characters. The classic example is the <code>html_strip</code> filter turning <code>&lt;b&gt;hi&lt;/b&gt;</code> into <code>hi</code>. Most fields use none at all &#8212; this stage is often empty.</p><p><strong>Tokenizer (exactly one)</strong> splits the string into individual tokens. This is the heart of the pipeline, and every analyzer has exactly one. The <code>standard</code> tokenizer breaks on whitespace and punctuation:</p><pre><code>"Wireless Keyboards, Running FAST!" &#8594; [Wireless, Keyboards, Running, FAST]</code></pre><p>The comma and exclamation mark are gone; they were separators, not content.</p><p><strong>Token filters (0 or more)</strong> transform the token stream that comes out of the tokenizer. They run in sequence, each one seeing the output of the last. Common filters:</p><ul><li><p><strong>lowercase</strong> &#8212; <code>[Wireless, Keyboards, Running, FAST]</code> &#8594; <code>[wireless, keyboards, running, fast]</code></p></li><li><p><strong>stemming</strong> &#8212; reduces words to their root: <code>keyboards</code> &#8594; <code>keyboard</code>, <code>running</code> &#8594; <code>run</code></p></li><li><p><strong>stop-words</strong> &#8212; drops high-frequency noise words like <code>the</code>, <code>a</code>, <code>is</code></p></li></ul><p>So the terms that land in your inverted index are the <em>output of the whole pipeline</em>, not the words you typed.</p><blockquote><p><strong>&#8594; The default standard analyzer does NOT stem</strong></p><p>This is the part almost everyone gets wrong at first. The default analyzer &#8212; called <code>standard</code> &#8212; lowercases and splits on punctuation and whitespace, but it does <strong>not</strong> stem. Run <code>"Wireless Keyboards, Running FAST!"</code> through it and you get <code>[wireless, keyboards, running, fast]</code> &#8212; plurals kept, <code>running</code> still<code>running</code>. Stemming is a separate token filter that the <code>standard</code> analyzer simply doesn&#8217;t include. To get it, you choose a different analyzer, such as <code>english</code>. Keep this distinction sharp; two of the gotchas below fall straight out of it.</p></blockquote><h2>Analysis runs at BOTH index time and query time</h2><p>Here is the idea that everything else in this article depends on. Analysis doesn&#8217;t happen only when you store data. It happens <em>twice</em> &#8212; once on the way in, once on the way out &#8212; and it has to.</p><p><strong>At index time</strong>, when you store a document, Elasticsearch runs each <code>text</code> field&#8217;s value through that field&#8217;s analyzer. The resulting terms are what get written into the inverted index. The original string is kept separately (in <code>_source</code>), but it is <em>not</em>what you search against.</p><p><strong>At query time</strong>, a full-text query like <code>match</code> takes the string <em>you searched for</em> and runs it through the <em>same</em> analyzer, before doing any lookup. Only then does it go to the inverted index to find matching terms.</p><blockquote><p><strong>&#10022; Both sides normalize, and meet in the middle</strong></p><p>Suppose you index <code>"Keyboard"</code>. The <code>standard</code> analyzer lowercases it, so the inverted index holds the term <code>keyboard</code>. Now a user searches <code>"Keyboard"</code> with a capital K. If the query text went to the index <em>raw</em>, it would look for <code>Keyboard</code>, find no such term, and return nothing &#8212; even though the document is right there. It works because the <code>match</code> query runs your search string through the <strong>same</strong> analyzer, turning <code>Keyboard</code> into <code>keyboard</code>, which <em>does</em> exist. Index-time analysis and query-time analysis normalize both sides to the same form so they can meet in the middle. That symmetry is the whole trick.</p></blockquote><p>The corollary is where the pain lives: if the index-time analyzer and the query-time analyzer <strong>disagree</strong>, the two sides normalize to different forms and never meet. You get <strong>zero hits and no error</strong> &#8212; the query is valid, the field exists, the document exists, and yet nothing matches. It is one of the most confusing situations in Elasticsearch precisely because nothing looks broken. Keeping &#8220;same analyzer, both times&#8221; in your head is what saves you here.</p><h2>Watch it happen: the _analyze API</h2><p>You don&#8217;t have to guess what an analyzer produces &#8212; Elasticsearch will show you. The <code>_analyze</code> API runs any analyzer against any text and hands back the exact terms.</p><pre><code>POST /_analyze
{
  "analyzer": "standard",
  "text": "Wireless Keyboards, Running FAST!"
}</code></pre><p>The response contains four tokens: <code>wireless</code>, <code>keyboards</code>, <code>running</code>, <code>fast</code>. Lowercased, punctuation stripped, and &#8212; note again &#8212; <strong>not</strong> stemmed. <code>keyboards</code> stays plural; <code>running</code> stays <code>running</code>. This is precisely what a <code>text</code> field does to its contents at index time.</p><p>Now try the <code>keyword</code> analyzer on the <em>same</em> string:</p><pre><code>POST /_analyze
{
  "analyzer": "keyword",
  "text": "Wireless Keyboards, Running FAST!"
}</code></pre><p>You get back a single token: <code>Wireless Keyboards, Running FAST!</code> &#8212; the entire string, verbatim, untouched. The <code>keyword</code> analyzer effectively does no tokenization at all; it treats the whole input as one indivisible term, preserving case and punctuation.</p><p>That contrast <em>is</em> the answer to the <code>text</code> vs <code>keyword</code> question from Part 2:</p><ul><li><p>A <code>text</code> field runs the <code>standard</code> analyzer &#8594; many small, normalized terms. Great for <code>match</code> (full-text search across words), useless for exact equality (there&#8217;s no single term equal to the original string).</p></li><li><p>A <code>keyword</code> field runs the <code>keyword</code> analyzer &#8594; exactly one term, the string verbatim. Perfect for <code>term</code> queries, filters, aggregations, and sorting, where you need the value as a whole, exact unit.</p></li></ul><p>So the rule you learned as &#8220;use <code>text</code> for search, <code>keyword</code> for exact match&#8221; was never really about two field types. It&#8217;s about <strong>two analyzers applied to the same string</strong> &#8212; one that shatters it into normalized pieces, one that keeps it whole.</p><h2>Gotcha 1: term does NO analysis</h2><p>Now the pipeline pays off by explaining a bug that traps nearly everyone.</p><p>Index <code>"Wireless Keyboard"</code> into a <code>text</code> field. The <code>standard</code> analyzer runs, and the inverted index holds the terms <code>[wireless, keyboard]</code> &#8212; lowercased and split. So far so good. Now search for it exactly:</p><pre><code>{ "term": { "name": "Wireless Keyboard" } }</code></pre><p>Zero hits. No error.</p><blockquote><p><strong>&#9888; term bypasses analysis entirely</strong></p><p>The critical thing to understand: a <code>term</code> query does <strong>not</strong> analyze its input. It doesn&#8217;t lowercase, doesn&#8217;t split &#8212; it takes your literal string <code>"Wireless Keyboard"</code> (capital W, capital K, the space in the middle) and looks for that <em>exact term</em> in the inverted index. But the index never held <code>"Wireless Keyboard"</code>. Analysis shattered it into <code>wireless</code> and <code>keyboard</code> at index time. There is no single term equal to the whole phrase, so the lookup fails silently. The precise framing matters: this isn&#8217;t &#8220;the query used a different analyzer.&#8221; It&#8217;s <strong>no analysis at all</strong>, run against a field that was stored <strong>with</strong> analysis &#8212; a mismatch by omission. Rule of thumb: use <code>match</code>(analyzed) on <code>text</code> fields, and <code>term</code> (not analyzed) on <code>keyword</code> fields. We formalize <code>match</code> vs <code>term</code> in Part 4.</p></blockquote><h2>Gotcha 2: the standard analyzer doesn&#8217;t stem</h2><p>The second trap comes straight from the &#8220;no stemming by default&#8221; fact.</p><p>You have a <code>text</code> field storing the word <code>"box"</code>. Under the default <code>standard</code> analyzer, the indexed term is <code>box</code>. A user searches for the plural:</p><pre><code>{ "match": { "name": "boxes" } }</code></pre><p>You might expect a hit &#8212; surely Elasticsearch knows <code>boxes</code> is just <code>box</code>? It doesn&#8217;t, not by default.</p><blockquote><p><strong>&#9888; Plurals don't match without a stemming analyzer</strong></p><p>The <code>match</code> query analyzes <code>"boxes"</code> with the field&#8217;s analyzer. Under the default <code>standard</code> analyzer, <code>boxes</code>stays <code>boxes</code> &#8212; no stemming &#8212; so the query looks for the term <code>boxes</code>. The stored term is <code>box</code>. Different terms, no match. To make singular and plural interchange, you need a <strong>stemming analyzer</strong> such as <code>english</code>, which reduces <em>both</em> <code>box</code> (at index time) and <code>boxes</code> (at query time) to the same root <code>box</code> &#8212; and now they meet. The lesson: don&#8217;t assume the default stems. If <code>"keyboards"</code> failing to find <code>"keyboard"</code> surprises you, this is why.</p></blockquote><h2>A three-tier way to think about matching</h2><p>Pulling the threads together, whether a search matches comes down to three moving parts lining up:</p><ol><li><p><strong>The stored terms</strong> are produced by the <em>field&#8217;s</em> analyzer at index time.</p></li><li><p><strong>A </strong><code>match</code><strong> query</strong> re-analyzes your query string with that <em>same</em> analyzer, so both sides land in the same normalized form.</p></li><li><p><strong>A </strong><code>term</code><strong> query</strong> bypasses analysis entirely and looks up your string as a literal.</p></li></ol><p>Getting a hit is a matter of choosing the query type that lines up with how the field was stored. <code>match</code> on a <code>text</code> field: both sides analyzed the same way &#8212; they meet. <code>term</code> on a <code>keyword</code> field: the field stored the value verbatim, and <code>term</code> looks it up verbatim &#8212; they meet. Cross the wires &#8212; <code>term</code> on a <code>text</code> field, or a search that assumes stemming that isn&#8217;t there &#8212; and you get the silent zero-hit result.</p><h2>Where this goes next</h2><p>Everything downstream builds on this pipeline:</p><ul><li><p><strong>The Query DSL (Part 4)</strong> formalizes the <code>match</code> vs <code>term</code> distinction you just met, along with the rest of the query and filter vocabulary. With the analyzer model in hand, those queries stop being magic and become predictable.</p></li><li><p><strong>Aggregations (Part 6)</strong> lean on <code>keyword</code> fields for exactly the reason this article explains: aggregating and sorting need one exact term per value, which is what the <code>keyword</code> analyzer gives you and the <code>standard</code> analyzer destroys.</p></li></ul><blockquote><p>If you can predict when a <code>term</code> query will return nothing &#8212; and explain why a search for <code>"keyboards"</code> may fail to find a document containing <code>"keyboard"</code> &#8212; you understand Elasticsearch text matching better than most people who query it every day.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[Elasticsearch deep dive: Documents, mappings & the shape of your data]]></title><description><![CDATA[Elasticsearch has a schema after all &#8212; you just don't always see it being written.]]></description><link>https://duthaho.substack.com/p/elasticsearch-deep-dive-documents</link><guid isPermaLink="false">https://duthaho.substack.com/p/elasticsearch-deep-dive-documents</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Sun, 02 Aug 2026 09:26:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_V_i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Elasticsearch deep-dive &#183; Part 2 of 8.</em></p><p>In Part 1 we drew the rough equivalence: an index is roughly a table, and a document is roughly a row. That gets you oriented, but it dodges the question a backend engineer asks next. When you send Elasticsearch a lump of JSON, how does it know that <code>name</code> is searchable text, <code>price</code> is a number you can sort and filter on ranges, and <code>created_at</code> is a date it can bucket by month? A relational database knows because you wrote <code>CREATE TABLE</code> first. Elasticsearch knows because of the <strong>mapping</strong> &#8212; and the twist is that it will happily write that mapping for you, from the first document you send, whether you meant it to or not.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Throughout this part we&#8217;ll keep one concrete scenario in mind: a <code>products</code> index, a catalog synced out of a Postgres table that is your source of truth. Everything &#8212; the field types, the CRUD calls, the sync strategy &#8212; hangs off that.</p><h2>The mapping is the schema</h2><p>A <strong>mapping</strong> is the definition of each field in an index: its name, its type, and how Elasticsearch indexes it. It is the schema. The reason it feels invisible is that you rarely write it by hand &#8212; but it is always there, and you can read it back at any time.</p><p>The mapping&#8217;s real job is to turn raw JSON into the right <em>internal structures</em>. Text destined for full-text search goes into an <strong>inverted index</strong> (the term-to-document map we met in Part 1). Numbers and dates go into structures optimised for range queries and sorting. Booleans, keywords, geo-points &#8212; each type has machinery behind it. The mapping is what routes a field to the correct machinery.</p><p>Here are the field types you&#8217;ll reach for most, with the SQL analogy each one maps to:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!_V_i!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!_V_i!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png 424w, /__u/substackcdn.com/image/fetch/$s_!_V_i!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png 848w, /__u/substackcdn.com/image/fetch/$s_!_V_i!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png 1272w, /__u/substackcdn.com/image/fetch/$s_!_V_i!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!_V_i!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png" width="862" height="703" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:703,&quot;width&quot;:862,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:91817,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://duthaho.substack.com/i/209475081?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!_V_i!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png 424w, /__u/substackcdn.com/image/fetch/$s_!_V_i!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png 848w, /__u/substackcdn.com/image/fetch/$s_!_V_i!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png 1272w, /__u/substackcdn.com/image/fetch/$s_!_V_i!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b2bef09-1d2b-4303-9113-aea5cfe41fd5_862x703.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Most of these behave the way your SQL intuition expects. The one distinction that trips up every newcomer &#8212; and the one worth burning into memory &#8212; is <code>text</code><strong> vs </strong><code>keyword</code>.</p><p>Take the string <code>"Wireless Keyboard"</code>. Map it as <code>text</code> and Elasticsearch <strong>analyses</strong> it: it breaks the string into terms, lowercases them, and stores <code>[wireless, keyboard]</code> in the inverted index. That&#8217;s exactly what you want for search &#8212; a user typing &#8220;wireless&#8221; finds the product &#8212; but it&#8217;s useless for exact work. You can&#8217;t reliably group by it, sort by it, or match the whole phrase as one atomic value, because the original string was shredded into pieces.</p><p>Map that same string as <code>keyword</code> and Elasticsearch stores it as <strong>one exact token</strong>: <code>"Wireless Keyboard"</code>, untouched, case and spaces intact. Now it&#8217;s perfect for filters (<code>status = "shipped"</code>), for aggregations (count products per brand), and for sorting &#8212; but you <em>can&#8217;t</em> do partial full-text search on it. Searching &#8220;wireless&#8221; will not match <code>"Wireless Keyboard"</code>, because as far as the keyword field is concerned, the only term that exists is the whole string.</p><blockquote><p><strong>&#10022; text vs keyword is just two analyzers on one string</strong></p><p>It looks like two different data types, but underneath it&#8217;s the same string run through two different <strong>analyzers</strong>. <code>text</code> uses an analyzer that splits and lowercases into searchable terms; <code>keyword</code> uses an analyzer that does nothing at all &#8212; it keeps the string whole as a single term. Same input, different processing, and every behavioural difference (searchable vs groupable, partial vs exact) falls out of that one choice. We unpack <em>how</em> analysis actually works in Part 3; for now, hold the reframe: not two types, two treatments.</p></blockquote><h2>Dynamic vs explicit mapping</h2><p>There are two ways a field ends up in the mapping.</p><p><strong>Dynamic mapping</strong> is the default, and it&#8217;s the source of that &#8220;invisible schema&#8221; feeling. Index a document into an index that has no mapping for a field, and Elasticsearch <strong>guesses</strong> the type from the value in front of it. It&#8217;s <code>CREATE TABLE</code> happening automatically from your first <code>INSERT</code> &#8212; you never wrote the schema, but a schema got written.</p><p>Watch it happen. Index one product, then read the mapping back:</p><pre><code><code>POST /products/_doc/1
{
  "name": "Wireless Keyboard",
  "sku": "MX-01",
  "price": 99.99,
  "in_stock": true,
  "created_at": "2026-07-30T10:00:00Z"
}

GET /products/_mapping</code></code></pre><p>Elasticsearch infers: <code>price</code> &#8594; <code>float</code>, <code>in_stock</code> &#8594; <code>boolean</code>, <code>created_at</code> &#8594; <code>date</code>. And the strings? This is the detail that surprises people: a dynamically-mapped string becomes <strong>two fields at once</strong> &#8212; a <code>text</code> field <em>and</em> a <code>keyword</code> sub-field:</p><pre><code><code>{
  "name": {
    "type": "text",
    "fields": {
      "keyword": { "type": "keyword", "ignore_above": 256 }
    }
  }
}</code></code></pre><p>So <code>name</code> is full-text searchable and <code>name.keyword</code> is available for filtering, sorting, and aggregations. That&#8217;s genuinely convenient &#8212; you get both behaviours for free without deciding anything. The cost is that you now store every string twice, and you&#8217;ve let Elasticsearch <em>guess</em> types for fields you may care about deeply.</p><p><strong>Explicit mapping</strong> is you taking the wheel. You <code>PUT</code> the index with the mapping spelled out before any data arrives:</p><pre><code><code>PUT /products
{
  "mappings": {
    "properties": {
      "name":       { "type": "text" },
      "sku":        { "type": "keyword" },
      "price":      { "type": "scaled_float", "scaling_factor": 100 },
      "in_stock":   { "type": "boolean" },
      "created_at": { "type": "date" }
    }
  }
}</code></code></pre><p>Every choice here is deliberate. <code>name</code> is <code>text</code> because you search it. <code>price</code> is <code>scaled_float</code> (a float stored as a scaled integer &#8212; a tidy fit for money) and could equally be <code>float</code>. And <code>sku</code> is <code>keyword</code>, not <code>text</code>, because you will only ever filter it exactly &#8212; <code>sku = "MX-01"</code> &#8212; and never run a full-text search over it. A SKU has no words to analyse; treating it as <code>keyword</code> keeps it exact, cheap, and aggregatable. That&#8217;s a design decision, made once, on purpose, rather than left to a guess.</p><h2>You can&#8217;t change a field&#8217;s type</h2><p>Explicit mapping isn&#8217;t just tidiness. It&#8217;s insurance against a limitation that catches people badly.</p><blockquote><p><strong>&#9888; Field types are set for the life of the index</strong></p><p>Once a field is mapped in an index, <strong>you cannot change its type.</strong> Suppose the first document your sync job ever sent had <code>"price": "99.99"</code> &#8212; a string, because someone forgot to cast it. Dynamic mapping maps <code>price</code> as <code>text</code> forever. You cannot later flip it to <code>float</code>. Range queries and numeric sorts on <code>price</code> are now broken, and no <code>PUT</code> will fix the existing field. The only cure is to create a <strong>new index</strong> with the correct mapping and <strong>reindex</strong> the data into it (Elasticsearch has a <code>_reindex</code> API for exactly this). This is the whole argument for explicit mappings on anything you care about &#8212; a wrong first guess is expensive to undo.</p></blockquote><p>Two clarifications, because this is easy to over-read. You <strong>can</strong> add brand-new fields to an existing index at any time &#8212; send a document with a field nobody&#8217;s seen before and it gets mapped on the spot. What you can&#8217;t do is <em>change the type of a field that already exists</em>. Adding is free; mutating is not.</p><h2>CRUD over REST &#8212; and a twist</h2><p>Every document lives at a predictable address: <code>/&lt;index&gt;/_doc/&lt;id&gt;</code>. Think of the trio &#8212; index, <code>_doc</code>, id &#8212; as a primary key scoped to a table. The index picks the &#8220;table&#8221;, the id is the key within it.</p><p><strong>Create</strong> comes in two flavours, and the difference matters more than it looks.</p><pre><code><code># You supply the id &#8594; create-or-replace, idempotent
PUT /products/_doc/1
{ "name": "Wireless Keyboard", "sku": "MX-01", "price": 99.99 }

# No id &#8594; Elasticsearch generates one &#8594; a NEW document every call
POST /products/_doc
{ "name": "Wireless Keyboard", "sku": "MX-01", "price": 99.99 }</code></code></pre><p>Run the <code>PUT</code> three times and you still have one document (id <code>1</code>), each call overwriting the last. Run the <code>POST</code> three times and you have three documents with three random ids. Hold that thought &#8212; it comes back to bite in a moment.</p><p>A note on vocabulary while we&#8217;re here: Elasticsearch uses &#8220;index&#8221; as a <strong>verb</strong>. To <em>index a document</em> means to store it. It&#8217;s an unfortunate collision with &#8220;index&#8221; the noun (the table-like container), and reading the docs is smoother once you know both meanings are in play.</p><p><strong>Read</strong> by id is a <code>GET</code>:</p><pre><code><code>GET /products/_doc/1</code></code></pre><p>And this call has a property that will save you real debugging time: <code>GET</code><strong> by id is real-time.</strong> It reads from the in-memory buffer, so it sees a document the instant it&#8217;s written &#8212; it is <em>not</em> subject to the ~1-second refresh delay we flagged in Part 1. That &#8220;I just indexed it and I can&#8217;t find it!&#8221; moment only bites <code>_search</code>, which waits for a refresh before a new document becomes visible. Fetch the same document by its id and it&#8217;s there immediately. If you can <code>GET</code> it but <code>_search</code> can&#8217;t find it yet, you&#8217;re not losing data &#8212; you&#8217;re waiting on a refresh.</p><p><strong>Update</strong> is a partial patch:</p><pre><code><code>POST /products/_update/1
{ "doc": { "price": 89.99 } }</code></code></pre><p>You send only the changed fields; Elasticsearch merges them into the existing document. (What actually happens underneath is more interesting than &#8220;merge&#8221; &#8212; see the next section.)</p><p><strong>Delete</strong> is what you&#8217;d guess:</p><pre><code><code>DELETE /products/_doc/1</code></code></pre><p>And running through all of it is a <code>_version</code> number that increments on every write to a document. That&#8217;s your handle for <strong>optimistic concurrency</strong>: read a document, note its <code>_seq_no</code> and <code>_primary_term</code>, and send them back with <code>if_seq_no</code> / <code>if_primary_term</code> on your write. If someone else changed the document in between, your write is rejected instead of silently clobbering theirs &#8212; exactly like an optimistic-lock version column in a relational table.</p><h2>Documents are immutable underneath</h2><p>Here&#8217;s the mechanical truth behind that &#8220;update&#8221; call, and it explains a surprising amount of Elasticsearch&#8217;s behaviour.</p><p>The inverted index lives in <strong>Lucene segments</strong> &#8212; files on disk &#8212; and those segments <strong>cannot be modified in place.</strong> Once written, a segment is immutable. So there is no such thing as editing a document where it sits. An &#8220;update&#8221; is really a small dance:</p><ol><li><p>Fetch the current document.</p></li><li><p>Apply your changes in memory to build the <em>new</em> full document.</p></li><li><p><strong>Re-index the entire new document</strong> as a fresh one.</p></li><li><p>Mark the old version as deleted (a tombstone; the space is reclaimed later during segment merges).</p></li></ol><blockquote><p><strong>&#10022; Every update is a delete-and-reinsert</strong></p><p>There is no in-place edit anywhere in Elasticsearch. Changing one field of a large document rewrites the whole document and tombstones the old copy. This is why Elasticsearch is a poor fit for <strong>heavy-update workloads</strong> &#8212; a counter you bump thousands of times a second, say &#8212; where each tiny change forces a full re-index. It&#8217;s built to index once and read many, not to churn. We come back to this design point in Part 8 when we talk about what Elasticsearch is and isn&#8217;t for.</p></blockquote><h2>The PUT-vs-POST trap</h2><p>Now the two <code>Create</code> flavours collide with the real world, and the result is a classic production mess.</p><blockquote><p><strong>&#9888; POST /_doc silently fills your index with duplicates</strong></p><p><code>POST /products/_doc</code> (no id) run three times creates <strong>three</strong> documents &#8212; Elasticsearch mints a fresh id each call. <code>PUT /products/_doc/1</code> run three times leaves <strong>one</strong> document, because you named the id and each call overwrites the last. When you&#8217;re syncing from Postgres, this is the whole ballgame: use <code>PUT</code><strong> with your database&#8217;s primary key as the Elasticsearch id.</strong> Then re-syncing a row &#8212; because a nightly job reran, or a change-data-capture event replayed &#8212; <em>updates the same document in place</em> instead of spawning a copy. Reach for <code>POST</code> in a sync loop and every replay quietly doubles your data, with no error to warn you. The catalog just slowly fills with dupes until someone notices the counts don&#8217;t match Postgres.</p></blockquote><p>The rule that falls out of this is short: <strong>let your source of truth own the id.</strong> If a product is row <code>4217</code> in Postgres, it&#8217;s document <code>4217</code> in Elasticsearch. Idempotent sync, no duplicates, and re-running the sync is always safe.</p><h2>Where this goes next</h2><p>We keep circling one unanswered question: <em>what does &#8220;analysed&#8221; actually mean?</em> We&#8217;ve said <code>text</code> gets split and lowercased into terms while <code>keyword</code> is left whole, and that this single choice decides everything about how a field behaves. That&#8217;s the promise Part 3 pays off.</p><p><strong>How Elasticsearch reads text</strong> opens up the analyzer: how a string becomes tokens, what the standard analyzer does to your product names, and why the <em>same</em> query text has to go through the <em>same</em> analysis as the indexed text for a match to happen. It&#8217;s the <em>why</em> underneath the <code>text</code>-vs-<code>keyword</code> rule you just learned to apply.</p><blockquote><p>If you can explain why you <code>PUT</code> with the Postgres id instead of <code>POST</code>, and why a wrong first-guess type means a full reindex rather than an <code>ALTER TABLE</code>, you already understand how Elasticsearch shapes your data better than most people who ship to it daily.</p></blockquote><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Elasticsearch deep dive: from the ground up ]]></title><description><![CDATA[Elasticsearch deep-dive &#183; Part 1 of 8.]]></description><link>https://duthaho.substack.com/p/elasticsearch-deep-dive-from-the</link><guid isPermaLink="false">https://duthaho.substack.com/p/elasticsearch-deep-dive-from-the</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Sat, 01 Aug 2026 12:28:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2Lgu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Elasticsearch deep-dive &#183; Part 1 of 8.</em></p><p>Most people meet Elasticsearch as one of two things: &#8220;the search box&#8221; behind a site&#8217;s search bar, or &#8220;a fast JSON store&#8221; they can throw documents at. Both are true, and both hide the one idea that makes the whole system click.</p><p>Here&#8217;s the sentence to hold onto: <strong>Elasticsearch is a distributed document store built around an inverted index.</strong> Two ideas live in there. First, it stores JSON <em>documents</em>, and an <em>index</em> is a collection of similar documents &#8212; roughly a SQL table. Second, the <em>inverted index</em> is the data structure that makes search fast. Almost everything Elasticsearch is good at, and almost everything it&#8217;s bad at, falls out of that second idea.</p><p>We&#8217;ll hang every example on something you already know: a <code>products</code> table in Postgres for an e-commerce store, and the copy of it you sync into Elasticsearch for search.</p><h2>The inverted index &#8212; the whole trick</h2><p>Start with how a relational database thinks. Postgres stores your <code>products</code> data <strong>row by row</strong>. Each row is a product: its name, its description, its price, sitting together on disk. That layout is perfect for &#8220;give me the row with <code>id = 42</code>&#8221; or &#8220;sum the prices&#8221; &#8212; but it&#8217;s the wrong shape for text search.</p><p>Ask Postgres this:</p><pre><code>SELECT * FROM products WHERE description LIKE '%wireless%';</code></pre><p>To answer it, the database has to walk <strong>every row</strong> and check whether the text contains that substring. It&#8217;s a full scan, it can&#8217;t use a normal B-tree index, and it only gets slower as your catalog grows. Worse, it&#8217;s doing dumb substring matching &#8212; it has no idea that <code>wireless</code> is a word.</p><p>An inverted index flips the problem inside out. When Elasticsearch indexes a document, it doesn&#8217;t just store the text &#8212; it <strong>breaks the text into terms</strong> and builds a map from each term to the list of document IDs that contain it. For a handful of product descriptions, that map might look like:</p><pre><code>wireless  &#8594;  [doc1, doc7, doc42]
keyboard  &#8594;  [doc1, doc9]
mechanical &#8594;  [doc9, doc42]
mouse     &#8594;  [doc7]</code></pre><p>Now &#8220;find documents containing <code>wireless</code>&#8221; is not a scan at all. It&#8217;s a single dictionary lookup &#8212; you go straight to the <code>wireless</code> row and read off <code>[doc1, doc7, doc42]</code>. It&#8217;s the same trick as the index at the back of a textbook: you don&#8217;t read every page looking for a word, you look the word up and it tells you the pages.</p><p>That&#8217;s the whole idea. The name even says it: a normal database maps <em>document &#8594; its words</em>; an inverted index maps <em>word &#8594; its documents</em>. Inverting that relationship is what turns text search from a scan into a lookup.</p><blockquote><p><strong>&#10022; Why search comes back ranked, not yes/no</strong></p><p>The inverted index doesn&#8217;t just tell you <em>which</em> documents contain a term &#8212; it tracks <em>how often</em> and <em>where</em>, across the whole collection. So Elasticsearch can score each matching document and return them <strong>ordered by relevance</strong>, not as a flat yes/no match. That&#8217;s the deeper reason it beats <code>LIKE</code>: a <code>LIKE</code> query can only say &#8220;this row matches or it doesn&#8217;t.&#8221; An inverted index can say &#8220;these 50 documents match, and here&#8217;s the best one first.&#8221; Ranking isn&#8217;t a feature bolted on top of search &#8212; it falls straight out of the data structure.</p></blockquote><h2>From SQL to Elasticsearch &#8212; a worked example</h2><p>Let&#8217;s make it concrete. In Postgres you&#8217;d define the table and search it like this:</p><pre><code>CREATE TABLE products (
  id          SERIAL PRIMARY KEY,
  name        TEXT,
  description TEXT,
  price       NUMERIC
);

SELECT * FROM products
WHERE description LIKE '%wireless keyboard%';</code></pre><p>Two problems with that query, both baked into the row-by-row model. It can&#8217;t use a normal B-tree index, so it scans. And the match is literal: a description that says &#8220;wireless keyboards&#8221; (plural) or &#8220;wireless mechanical keyboard&#8221; (words apart) won&#8217;t match <code>%wireless keyboard%</code> at all. You&#8217;re matching characters, not meaning.</p><p>Now the Elasticsearch version. You index a document with an HTTP request:</p><pre><code>POST /products/_doc
{
  "name": "Wireless Keyboard",
  "description": "wireless keyboard",
  "price": 49.99
}</code></pre><p>And you search it with the Query DSL, a JSON body:</p><pre><code>GET /products/_search
{
  "query": {
    "match": {
      "description": "wireless keyboard"
    }
  }
}</code></pre><p>At index time, Elasticsearch <strong>tokenized</strong> <code>"wireless keyboard"</code> into the terms <code>[wireless, keyboard]</code> and added both to the inverted index. So the search becomes two fast lookups &#8212; one for <code>wireless</code>, one for <code>keyboard</code> &#8212; and the results come back <strong>scored</strong>. A product whose description contains <em>both</em> words ranks above one that only has <code>wireless</code>. You didn&#8217;t ask for that ordering; the index gave it to you.</p><p>It helps to keep a rough translation table in your head. The mapping is <strong>approximate, not exact</strong> &#8212; the concepts don&#8217;t line up one-for-one &#8212; but it&#8217;s enough to stop feeling lost:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!2Lgu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!2Lgu!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!2Lgu!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!2Lgu!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!2Lgu!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!2Lgu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg" width="1170" height="1192" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:1192,&quot;width&quot;:1170,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:0,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!2Lgu!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!2Lgu!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!2Lgu!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!2Lgu!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb772d9bb-86f3-417a-a84d-f13aeeab4b5e_1170x1192.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Notice that &#8220;table&#8221; and &#8220;database&#8221; both land near &#8220;index&#8221; &#8212; that&#8217;s one of the places the analogy strains, and we&#8217;ll untangle it in later parts. For now, the one row that matters most is the last one: a SQL <code>WHERE</code> is a filter that a row either passes or fails, while a Query DSL <code>match</code> is a <em>ranked</em> question. Same job, different worldview.</p><h2>What Elasticsearch is not</h2><p>Here&#8217;s where beginners get burned. Once you see how fast and flexible Elasticsearch is, the temptation is enormous: <em>&#8220;This is just a better database. I&#8217;ll make it my primary store and drop Postgres.&#8221;</em></p><p>Don&#8217;t. Elasticsearch is a <strong>search and analytics engine</strong>, not a transactional database, and the differences are not cosmetic:</p><ul><li><p><strong>No ACID transactions.</strong> You can&#8217;t wrap a group of writes in a transaction and get all-or-nothing guarantees across documents.</p></li><li><p><strong>No SQL-style joins.</strong> There&#8217;s no general <code>JOIN</code> between indices the way you&#8217;d join tables. You model around that, not with it.</p></li><li><p><strong>It&#8217;s </strong><em><strong>near real-time</strong></em><strong>, not real-time.</strong> A document you just indexed isn&#8217;t searchable <em>immediately</em> &#8212; it becomes visible only after the index <strong>refreshes</strong>, which by default happens about once a second. That ~1 second gap is fine for search; it&#8217;s a disaster for &#8220;read your own write&#8221; transactional flows.</p></li></ul><p>Historically, Elasticsearch also made deliberate trade-offs &#8212; giving up some consistency and durability guarantees in exchange for speed and horizontal scale. It was built to search and aggregate enormous piles of data quickly, not to be the ledger you bet the business on.</p><blockquote><p><strong>&#9888; 'It's just a faster database, so replace Postgres with it'</strong></p><p>This is the number one beginner mistake, and it fails in production in the worst way: quietly. You move your <code>products</code> into Elasticsearch as the only store, and for a while it works. Then a checkout needs an atomic multi-row update and there&#8217;s no transaction to give it. Then a user updates their profile and doesn&#8217;t see the change on the next page load, because the index hadn&#8217;t refreshed yet. Then you need to join orders to customers and there&#8217;s no join. None of these are bugs &#8212; they&#8217;re the engine behaving exactly as designed. Elasticsearch didn&#8217;t fail to be a good database; it was never trying to be one.</p></blockquote><p>So what&#8217;s the right shape? The standard architecture is boringly reliable: <strong>keep the source of truth in a real database</strong> &#8212; Postgres or MySQL &#8212; and <strong>sync a copy into Elasticsearch</strong> for search and analytics. Writes go to Postgres, where you get transactions, joins, and durability. A pipeline mirrors the relevant data into Elasticsearch, where you get fast, ranked, full-text search over it.</p><p>Elasticsearch <strong>complements</strong> your database. It rarely replaces it. When you feel the urge to make it your only store, that urge is the bug.</p><h2>Where this goes next</h2><p>Everything above is the foundation. Once &#8220;distributed document store built around an inverted index&#8221; is second nature, the rest of Elasticsearch reads as consequences of that one idea rather than a pile of features to memorize, these posts are in the queue:</p><ul><li><p><strong>Mappings &amp; field types</strong> &#8212; how Elasticsearch knows a field is searchable text versus a number, a date, or an exact keyword, and why that decision shapes everything downstream.</p></li><li><p><strong>Text analysis</strong> &#8212; the exact process that turns a string like <code>"Wireless Keyboards!"</code> into terms such as <code>[wireless, keyboards]</code>, including lowercasing and stripping punctuation &#8212; and why matching a plural to its singular takes more than the default analyzer.</p></li><li><p><strong>The Query DSL</strong> &#8212; the JSON language for asking questions, and the crucial split between <em>filtering</em> (yes/no, cacheable) and <em>querying</em> (scored).</p></li><li><p><strong>BM25 relevance</strong> &#8212; the actual math behind &#8220;why did this document rank first,&#8221; so ranking stops being magic.</p></li><li><p><strong>Aggregations</strong> &#8212; using the same index to answer analytical questions, not just search ones.</p></li><li><p><strong>Distributed sharding</strong> &#8212; how one index is split across machines so it can hold and search far more than one node could.</p></li><li><p><strong>Design decisions</strong> &#8212; the judgment of <em>when</em> to reach for Elasticsearch at all, and when your database already has you covered.</p></li></ul><p>Each of those makes sense only in light of the core idea. Analysis matters <em>because</em> search is term lookups, so how you build the terms is everything. Sharding matters <em>because</em> the index can outgrow one machine. The database-alongside architecture matters <em>because</em> the inverted index is a search structure, not a transactional one. Learn the mental model first, and the rest stops being intimidating.</p><blockquote><p>If you can explain why an inverted-index lookup beats a <code>LIKE</code> scan, and why the source of truth still belongs in Postgres, you already understand what Elasticsearch is for.</p></blockquote>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: caching ký sự (phần kết)]]></title><description><![CDATA[B&#224;i tr&#432;&#7899;c m&#236;nh k&#7875; v&#7873; caching v&#7899;i m&#7897;t con Redis &#273;&#7913;ng tr&#432;&#7899;c database.]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-caching-ky-su-phan</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-caching-ky-su-phan</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Tue, 21 Jul 2026 15:34:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>B&#224;i tr&#432;&#7899;c m&#236;nh k&#7875; v&#7873; caching v&#7899;i m&#7897;t con Redis &#273;&#7913;ng tr&#432;&#7899;c database. Nh&#432;ng c&#243; m&#7897;t ng&#224;y traffic &#273;&#7885;c l&#7899;n t&#7899;i m&#7913;c ch&#237;nh con Redis &#273;&#243; c&#361;ng th&#224;nh n&#250;t th&#7855;t, ho&#7863;c &#273;&#417;n gi&#7843;n l&#224; m&#236;nh mu&#7889;n nhanh h&#417;n n&#7919;a, mu&#7889;n c&#7855;t lu&#244;n c&#225;i v&#242;ng &#273;i m&#7841;ng t&#7899;i Redis. L&#250;c &#273;&#243; ng&#432;&#7901;i ta th&#234;m m&#7897;t t&#7847;ng cache n&#7919;a, l&#7847;n n&#224;y n&#7857;m ngay b&#234;n trong process c&#7911;a application, g&#7885;i l&#224; in-process cache hay local cache, ki&#7875;u Caffeine hay Guava trong Java, hay &#273;&#417;n gi&#7843;n l&#224; m&#7897;t c&#225;i map c&#243; TTL. V&#224; th&#7871; l&#224; m&#236;nh b&#432;&#7899;c v&#224;o th&#7871; gi&#7899;i cache nhi&#7873;u t&#7847;ng, n&#417;i m&#224; m&#7885;i th&#7913; nhanh h&#417;n th&#7853;t, nh&#432;ng k&#233;o theo m&#7897;t b&#224;i to&#225;n &#273;au &#273;&#7847;u m&#224; m&#236;nh mu&#7889;n k&#7875; h&#244;m nay.</p><p></p><p>H&#236;nh dung ki&#7871;n tr&#250;c ba t&#7847;ng th&#7871; n&#224;y. Tr&#234;n c&#249;ng l&#224; L1, cache local n&#7857;m trong RAM c&#7911;a t&#7915;ng instance, &#273;&#7885;c c&#7921;c nhanh v&#236; kh&#244;ng t&#7889;n m&#7897;t byte network n&#224;o. D&#432;&#7899;i n&#243; l&#224; L2, con Redis d&#249;ng chung cho c&#7843; c&#7909;m. D&#432;&#7899;i c&#249;ng l&#224; database, ngu&#7891;n s&#7921; th&#7853;t. Gi&#7843; s&#7917; m&#236;nh ch&#7841;y 20 instance c&#7911;a service, th&#236; c&#243; ngh&#297;a l&#224; c&#243; 20 c&#225;i L1 ri&#234;ng bi&#7879;t, m&#7895;i c&#225;i n&#7857;m trong m&#7897;t process, v&#224; quan tr&#7885;ng l&#224; ch&#250;ng kh&#244;ng bi&#7871;t nhau t&#7891;n t&#7841;i.</p><p></p><p>Lu&#7891;ng &#273;&#7885;c th&#236; &#273;&#7865;p v&#224; tr&#7921;c gi&#225;c. Request t&#7899;i, ng&#243; L1 tr&#432;&#7899;c, c&#243; th&#236; tr&#7843; lu&#244;n. Kh&#244;ng c&#243; th&#236; h&#7887;i xu&#7889;ng L2 Redis, c&#243; th&#236; n&#7841;p ng&#432;&#7907;c l&#234;n L1 r&#7891;i tr&#7843; v&#7873;. L2 c&#361;ng kh&#244;ng c&#243; th&#236; m&#7899;i xu&#7889;ng database, l&#7845;y ra, n&#7841;p l&#234;n L2, n&#7841;p l&#234;n L1, r&#7891;i tr&#7843; v&#7873;. &#272;i t&#7915; nhanh nh&#7845;t xu&#7889;ng ch&#7853;m nh&#7845;t, v&#224; tr&#234;n &#273;&#432;&#7901;ng v&#7873; th&#236; r&#7843;i d&#7919; li&#7879;u ng&#432;&#7907;c l&#234;n c&#225;c t&#7847;ng. C&#225;i n&#224;y kh&#244;ng c&#243; g&#236; &#273;&#7875; b&#224;n nhi&#7873;u.</p><p></p><p>Ch&#7895; b&#7855;t &#273;&#7847;u &#273;au l&#224; lu&#7891;ng ghi. H&#7891;i &#273;&#7847;u m&#236;nh t&#7915;ng ngh&#297; m&#7897;t h&#432;&#7899;ng nghe r&#7845;t m&#432;&#7907;t: c&#7913; &#273;&#7885;c ghi h&#7871;t v&#224;o L1 cho nhanh, r&#7891;i &#273;&#7875; m&#7897;t background job cu&#7889;i v&#242;ng &#273;&#7901;i request &#273;&#7849;y ng&#432;&#7907;c xu&#7889;ng Redis v&#224; database. Nghe th&#236; hay, nh&#432;ng &#273;&#226;y l&#224; m&#7897;t c&#225;i b&#7851;y, v&#224; n&#243; c&#243; t&#234;n &#273;&#224;ng ho&#224;ng l&#224; write-behind, ghi v&#224;o cache tr&#432;&#7899;c r&#7891;i m&#7899;i flush xu&#7889;ng store sau. V&#7845;n &#273;&#7873; l&#224; m&#236;nh &#273;ang &#273;&#7863;t c&#225;i write-behind &#273;&#243; l&#234;n L1 local, t&#7913;c l&#224; l&#234;n RAM c&#7911;a m&#7897;t process mong manh. Process &#273;&#243; ch&#7871;t gi&#7919;a ch&#7915;ng v&#236; deploy hay OOM l&#224; d&#7919; li&#7879;u ch&#432;a flush bay lu&#244;n, database kh&#244;ng bao gi&#7901; th&#7845;y. T&#7879; h&#417;n, hai m&#432;&#417;i c&#225;i L1 c&#249;ng ghi th&#236; m&#236;nh c&#243; hai m&#432;&#417;i phi&#234;n b&#7843;n s&#7921; th&#7853;t m&#226;u thu&#7851;n nhau m&#224; ch&#7859;ng c&#225;i n&#224;o l&#224; ch&#226;n l&#253;. Cho n&#234;n b&#224;i h&#7885;c x&#432;&#417;ng m&#225;u &#7903; &#273;&#226;y l&#224;: L1 v&#224; L2 l&#224; cache, kh&#244;ng ph&#7843;i l&#224; store. Ngu&#7891;n s&#7921; th&#7853;t lu&#244;n l&#224; database. Cache c&#243; th&#7875; m&#7845;t b&#7845;t c&#7913; l&#250;c n&#224;o m&#224; h&#7879; th&#7889;ng v&#7851;n ph&#7843;i &#273;&#250;ng.</p><p></p><p>V&#7853;y lu&#7891;ng ghi &#273;&#250;ng l&#224; g&#236;. V&#7851;n gi&#7919; nguy&#234;n hai nguy&#234;n t&#7855;c t&#7915; b&#224;i tr&#432;&#7899;c: ghi database tr&#432;&#7899;c v&#236; n&#243; l&#224; ngu&#7891;n s&#7921; th&#7853;t, v&#224; v&#7899;i cache th&#236; x&#243;a ch&#7913; kh&#244;ng update. Ch&#7881; kh&#225;c m&#7897;t &#273;i&#7873;u, gi&#7901; c&#225;i &#273;&#7897;ng t&#225;c x&#243;a cache ph&#7843;i lan ra hai t&#7847;ng. Ghi database xong, x&#243;a key &#7903; L2 Redis, r&#7891;i t&#236;m c&#225;ch x&#243;a key &#273;&#243; &#7903; c&#7843; 20 c&#225;i L1 &#273;ang n&#7857;m r&#7843;i r&#225;c. V&#224; ch&#237;nh c&#225;i v&#7871; cu&#7889;i c&#249;ng &#273;&#243;, x&#243;a 20 c&#225;i L1, m&#7899;i l&#224; ph&#7847;n kh&#243; nh&#7845;t v&#224; th&#250; v&#7883; nh&#7845;t c&#7911;a c&#7843; c&#226;u chuy&#7879;n.</p><p></p><p>V&#7845;n &#273;&#7873; n&#224;y c&#243; t&#234;n ch&#237;nh th&#7913;c l&#224; local cache coherence, l&#224;m sao gi&#7919; cho 20 b&#7843;n copy trong 20 process c&#249;ng nh&#236;n th&#7845;y m&#7897;t gi&#225; tr&#7883;. C&#243; m&#7845;y c&#225;ch, m&#236;nh x&#7871;p t&#7915; nh&#7865; t&#7899;i n&#7863;ng. C&#225;ch nh&#7865; nh&#7845;t, v&#224; c&#361;ng l&#224; c&#225;ch m&#236;nh ngh&#297; nhi&#7873;u ng&#432;&#7901;i b&#7887; qua m&#7897;t c&#225;ch &#273;&#225;ng ti&#7871;c, l&#224; ch&#7881; c&#7847;n cho L1 m&#7897;t TTL th&#7853;t ng&#7855;n, ki&#7875;u n&#259;m t&#7899;i ba m&#432;&#417;i gi&#226;y, r&#7891;i th&#244;i. Kh&#244;ng invalidation, kh&#244;ng pub/sub, kh&#244;ng g&#236; c&#7843;. Ch&#7845;p nh&#7853;n r&#7857;ng d&#7919; li&#7879;u &#7903; L1 c&#243; th&#7875; c&#361; t&#7889;i &#273;a v&#224;i gi&#226;y. V&#7899;i nh&#7919;ng th&#7913; &#237;t &#273;&#7893;i ho&#7863;c ch&#7845;p nh&#7853;n sai s&#7889; nh&#7887;, &#273;&#226;y th&#432;&#7901;ng l&#224; c&#226;u tr&#7843; l&#7901;i &#273;&#7911; t&#7889;t v&#224; r&#7867; nh&#7845;t. M&#236;nh h&#7885;c &#273;&#432;&#7907;c r&#7857;ng m&#7897;t trong nh&#7919;ng d&#7845;u hi&#7879;u c&#7911;a ng&#432;&#7901;i l&#224;m h&#7879; th&#7889;ng ch&#237;n l&#224; bi&#7871;t ch&#7885;n gi&#7843;i ph&#225;p &#273;&#7911; d&#249;ng thay v&#236; nh&#7843;y ngay v&#224;o c&#225;i ph&#7913;c t&#7841;p nh&#7845;t &#273;&#7875; tr&#244;ng cho ng&#7847;u.</p><p></p><p>C&#225;ch ph&#7893; bi&#7871;n h&#417;n khi c&#7847;n d&#7919; li&#7879;u t&#432;&#417;i nhanh l&#224; pub/sub invalidation. Khi c&#243; ghi, c&#225;i instance x&#7917; l&#253; s&#7869; publish m&#7897;t message ki&#7875;u key X v&#7915;a &#273;&#7893;i l&#234;n m&#7897;t k&#234;nh Redis, c&#7843; 20 instance &#273;&#7873;u subscribe k&#234;nh &#273;&#243;, nh&#7853;n &#273;&#432;&#7907;c th&#236; t&#7921; x&#243;a key X trong L1 c&#7911;a m&#236;nh. &#272;&#226;y l&#224; c&#225;ch chu&#7849;n m&#224; nhi&#7873;u h&#7879; near-cache d&#249;ng. Nh&#432;ng ph&#7843;i hi&#7875;u r&#245; &#273;i&#7875;m y&#7871;u ch&#237; m&#7841;ng c&#7911;a n&#243;: Redis pub/sub l&#224; fire-and-forget, g&#7917;i m&#7897;t l&#7847;n r&#7891;i th&#244;i, kh&#244;ng h&#7873; l&#432;u l&#7841;i message. C&#225;i instance n&#224;o &#273;&#250;ng l&#250;c &#273;&#243; &#273;ang m&#7845;t k&#7871;t n&#7889;i, &#273;ang b&#7853;n GC, hay v&#7915;a restart ch&#432;a k&#7883;p subscribe, th&#236; n&#243; miss lu&#244;n c&#225;i event x&#243;a, v&#224; L1 c&#7911;a n&#243; s&#7869; &#244;m gi&#225; tr&#7883; c&#361; m&#227;i m&#227;i. &#272;&#226;y ch&#237;nh l&#224; n&#7895;i lo m&#7845;t event m&#224; ai l&#224;m qua &#273;&#7873;u th&#7845;m.</p><p></p><p>Cho n&#234;n c&#243; m&#7897;t nguy&#234;n t&#7855;c m&#236;nh mu&#7889;n nh&#7845;n m&#7841;nh: pub/sub kh&#244;ng bao gi&#7901; &#273;&#432;&#7907;c &#273;i m&#7897;t m&#236;nh. N&#243; lu&#244;n ph&#7843;i &#273;i k&#232;m TTL tr&#234;n L1 l&#224;m l&#432;&#7899;i an to&#224;n. Pub/sub lo cho ch&#237;n m&#432;&#417;i ch&#237;n ph&#7847;n tr&#259;m tr&#432;&#7901;ng h&#7907;p, x&#243;a g&#7847;n nh&#432; t&#7913;c th&#236; trong d&#432;&#7899;i m&#7897;t gi&#226;y. C&#242;n TTL lo cho c&#225;i m&#7897;t ph&#7847;n tr&#259;m s&#7921; ki&#7879;n b&#7883; l&#7885;t, &#273;&#7843;m b&#7843;o d&#249; c&#243; miss th&#236; cache c&#361;ng t&#7921; h&#7871;t h&#7841;n sau v&#224;i gi&#226;y ch&#7913; kh&#244;ng sai v&#297;nh vi&#7877;n. N&#7871;u b&#7841;n &#273;&#7875; &#253;, &#273;&#226;y l&#7863;p l&#7841;i &#273;&#250;ng c&#225;i b&#224;i h&#7885;c t&#7915; bu&#7893;i caching &#273;&#7847;u ti&#234;n: m&#7885;i c&#417; ch&#7871; invalidation d&#7921;a tr&#234;n event &#273;&#7873;u c&#7847;n TTL l&#224;m backstop, b&#7903;i event th&#236; lu&#244;n c&#243; th&#7875; m&#7845;t.</p><p></p><p>C&#242;n n&#7871;u b&#224;i to&#225;n kh&#7855;t khe t&#7899;i m&#7913;c kh&#244;ng &#273;&#432;&#7907;c ph&#233;p miss invalidation, th&#236; m&#236;nh leo l&#234;n b&#7853;c tin c&#7853;y cao h&#417;n: thay pub/sub b&#7857;ng m&#7897;t message queue hay Redis Streams c&#243; l&#432;u tr&#7919;, &#273;&#7875; instance n&#224;o restart c&#361;ng &#273;&#7885;c l&#7841;i &#273;&#432;&#7907;c event &#273;&#227; l&#7905;, t&#7913;c l&#224; chuy&#7875;n t&#7915; g&#7917;i nhi&#7873;u nh&#7845;t m&#7897;t l&#7847;n sang g&#7917;i &#237;t nh&#7845;t m&#7897;t l&#7847;n. Ho&#7863;c d&#249;ng t&#237;nh n&#259;ng client-side caching c&#7911;a Redis &#273;&#7901;i m&#7899;i, n&#417;i ch&#237;nh Redis server theo d&#245;i instance n&#224;o &#273;ang cache key n&#224;o v&#224; ch&#7911; &#273;&#7897;ng &#273;&#7849;y t&#237;n hi&#7879;u v&#244; hi&#7879;u h&#243;a v&#7873; &#273;&#250;ng client &#273;&#243;. &#272;&#7893;i l&#7841;i th&#236; c&#225;i n&#224;o c&#361;ng ph&#7913;c t&#7841;p h&#417;n, v&#224; &#273;&#243; lu&#244;n l&#224; c&#225;i gi&#225; ph&#7843;i c&#226;n.</p><p></p><p>Nh&#432;ng c&#226;u chuy&#7879;n ch&#432;a d&#7915;ng &#7903; vi&#7879;c g&#7917;i event cho &#273;&#225;ng tin. Ngay c&#7843; khi th&#7913; t&#7921; &#273;&#250;ng v&#224; event kh&#244;ng m&#7845;t, v&#7851;n c&#242;n m&#7897;t khe h&#7903; tinh vi m&#224; m&#236;nh r&#7845;t th&#237;ch. T&#432;&#7903;ng t&#432;&#7907;ng m&#7897;t request &#273;&#7885;c b&#7883; miss c&#7843; L1 l&#7851;n L2, n&#243; xu&#7889;ng database &#273;&#7885;c, v&#224; &#273;&#250;ng kho&#7843;nh kh&#7855;c &#273;&#243; n&#243; &#273;&#7885;c tr&#250;ng gi&#225; tr&#7883; c&#361; v&#236; c&#225;i l&#7879;nh ghi kia ch&#432;a commit xong. Ngay sau &#273;&#243; l&#7879;nh ghi commit, x&#243;a L2, b&#7855;n event x&#243;a L1. R&#7891;i c&#225;i request &#273;&#7885;c l&#250;c n&#227;y m&#7899;i l&#7919;ng th&#7919;ng quay l&#7841;i, n&#7841;p c&#225;i gi&#225; tr&#7883; c&#361; m&#224; n&#243; c&#7847;m tr&#234;n tay v&#224;o L2 v&#224; L1, t&#7913;c l&#224; n&#7841;p sau khi m&#7885;i th&#7913; &#273;&#227; b&#7883; x&#243;a. K&#7871;t qu&#7843; l&#224; cache l&#7841;i stale, v&#224; l&#7847;n n&#224;y publish c&#243; tin c&#7853;y t&#7899;i &#273;&#226;u c&#361;ng v&#244; d&#7909;ng, v&#236; l&#7895;i kh&#244;ng ph&#7843;i m&#7845;t event, m&#224; l&#224; ghi &#273;&#232; sau khi x&#243;a.</p><p></p><p>&#272;&#7875; v&#225; c&#225;i khe n&#224;y, th&#7913; t&#7921; &#273;&#250;ng l&#224; commit database tr&#432;&#7899;c &#273;&#227;, xong m&#7899;i x&#243;a cache v&#224; ph&#225;t event, &#273;&#7875; kh&#244;ng ai b&#7883; b&#225;o &#273;i &#273;&#7885;c l&#7841;i khi ngu&#7891;n s&#7921; th&#7853;t c&#242;n ch&#432;a m&#7899;i. Nh&#432;ng ri&#234;ng c&#225;i race &#273;&#7885;c &#273;&#7891;ng th&#7901;i &#7903; tr&#234;n th&#236; c&#7847;n th&#234;m hai l&#7899;p n&#7919;a, v&#224; c&#7843; hai &#273;&#7873;u &#273;&#227; xu&#7845;t hi&#7879;n &#7903; b&#224;i tr&#432;&#7899;c. M&#7897;t l&#224; delayed double delete, t&#7913;c l&#224; x&#243;a xong ch&#7901; m&#7897;t nh&#7883;p ng&#7855;n r&#7891;i x&#243;a l&#7847;n th&#7913; hai, &#273;&#7875; d&#7885;n &#273;&#250;ng c&#225;i gi&#225; tr&#7883; c&#361; m&#224; request &#273;&#7885;c kia c&#243; th&#7875; &#273;&#227; k&#7883;p n&#7841;p v&#224;o. Hai l&#224; TTL tr&#234;n c&#7843; L1 l&#7851;n L2 l&#224;m t&#7845;m l&#432;&#7899;i cu&#7889;i c&#249;ng, &#273;&#7875; d&#249; m&#7885;i th&#7913; &#7903; tr&#234;n &#273;&#7873;u l&#7885;t th&#236; cache c&#361;ng ch&#7881; sai trong m&#7897;t kho&#7843;ng b&#7883; ch&#7863;n r&#7891;i t&#7921; s&#7917;a.</p><p></p><p>V&#7873; chuy&#7879;n ph&#225;t event cho &#273;&#225;ng tin, m&#236;nh c&#243; m&#7897;t kho&#7843;nh kh&#7855;c kh&#225; vui khi nh&#7853;n ra: vi&#7879;c ph&#7843;i ghi v&#224;o database r&#7891;i ph&#225;t &#273;i m&#7897;t t&#237;n hi&#7879;u m&#224; kh&#244;ng &#273;&#432;&#7907;c m&#7845;t, ch&#237;nh x&#225;c l&#224; c&#225;i Dual Write Problem v&#224; l&#7901;i gi&#7843;i outbox m&#224; m&#236;nh t&#7915;ng g&#7863;p b&#234;n message queue. C&#249;ng m&#7897;t b&#224;i to&#225;n. N&#234;n c&#243; th&#7875; d&#249;ng outbox &#273;&#7875; &#273;&#7843;m b&#7843;o event lu&#244;n &#273;&#432;&#7907;c g&#7917;i. C&#243; &#273;i&#7873;u v&#7899;i ri&#234;ng b&#224;i to&#225;n v&#244; hi&#7879;u h&#243;a cache th&#236; outbox ki&#7875;u polling h&#417;i ch&#7853;m, v&#236; ph&#7843;i ch&#7901; worker qu&#233;t b&#7843;ng, l&#224;m L1 stale l&#226;u h&#417;n m&#7897;t ch&#250;t. N&#7871;u mu&#7889;n v&#7915;a tin c&#7853;y v&#7915;a nhanh th&#236; h&#432;&#7899;ng &#273;&#7865;p h&#417;n l&#224; CDC, &#273;&#7885;c th&#7859;ng binlog c&#7911;a database b&#7857;ng th&#7913; nh&#432; Debezium, commit xong l&#224; c&#243; t&#237;n hi&#7879;u g&#7847;n nh&#432; t&#7913;c th&#236; m&#224; v&#7851;n kh&#244;ng m&#7845;t. Ngh&#297;a l&#224; m&#236;nh l&#7841;i quay v&#7873; &#273;&#250;ng b&#7897; c&#244;ng c&#7909; &#273;&#227; b&#224;n &#7903; bu&#7893;i cache-DB consistency, ch&#7881; kh&#225;c ng&#7919; c&#7843;nh.</p><p></p><p>V&#224; &#273;&#226;y l&#224; c&#225;i insight m&#224; m&#236;nh th&#7845;y &#273;&#225;ng gi&#225; nh&#7845;t c&#7911;a c&#7843; bu&#7893;i. C&#225;i c&#226;u h&#7887;i l&#224;m sao &#273;&#7875; 20 b&#7843;n copy trong 20 process c&#249;ng th&#7845;y m&#7897;t gi&#225; tr&#7883;, n&#243; kh&#244;ng m&#7899;i. N&#243; ch&#237;nh l&#224; b&#224;i to&#225;n cache coherence c&#7911;a CPU, n&#417;i m&#7895;i nh&#226;n c&#243; L1 L2 ri&#234;ng v&#224; ph&#7843;i ch&#7841;y giao th&#7913;c &#273;&#7875; &#273;&#7891;ng b&#7897;. N&#243; c&#361;ng ch&#237;nh l&#224; b&#224;i to&#225;n replication trong h&#7879; ph&#226;n t&#225;n, nhi&#7873;u b&#7843;n sao, c&#243; &#273;&#7897; tr&#7877; lan truy&#7873;n, c&#243; th&#7875; m&#7845;t tin. V&#224; m&#7897;t l&#7847;n n&#7919;a n&#243; l&#7841;i l&#224; Dual Write Problem &#273;&#7897;i m&#7897;t c&#225;i m&#361; kh&#225;c: m&#236;nh &#273;ang ghi v&#224;o nhi&#7873;u n&#417;i m&#224; kh&#244;ng c&#243; m&#7897;t transaction chung n&#224;o &#244;m h&#7871;t. Khi th&#7845;y c&#249;ng m&#7897;t b&#224;i to&#225;n hi&#7879;n ra d&#432;&#7899;i ba b&#7889;n l&#7899;p &#225;o kh&#225;c nhau, t&#7915; con chip cho t&#7899;i c&#7909;m server, m&#236;nh m&#7899;i th&#7845;m r&#7857;ng h&#7885;c architecture kh&#244;ng ph&#7843;i l&#224; h&#7885;c thu&#7897;c nhi&#7873;u gi&#7843;i ph&#225;p, m&#224; l&#224; nh&#7853;n ra &#237;t nguy&#234;n l&#253; nh&#432;ng ch&#250;ng l&#7863;p l&#7841;i &#7903; kh&#7855;p m&#7885;i n&#417;i.</p><p></p><p>Ch&#7889;t l&#7841;i, &#273;i&#7873;u m&#236;nh mang theo sau c&#7843; chu&#7895;i caching l&#224;: kh&#244;ng c&#243; c&#225;ch n&#224;o l&#224;m cho cache nh&#7845;t qu&#225;n tuy&#7879;t &#273;&#7889;i v&#7899;i database. M&#7885;i thi&#7871;t k&#7871; t&#7917; t&#7871; &#273;&#7873;u ch&#7881; l&#224; x&#7871;p t&#7847;ng ph&#242;ng th&#7911;, invalidation nhanh lo ph&#7847;n l&#7899;n, double delete lo c&#225;i race, TTL lo ph&#7847;n c&#242;n s&#243;t l&#7841;i. M&#236;nh kh&#244;ng &#273;i t&#236;m gi&#7843;i ph&#225;p &#273;&#250;ng, m&#236;nh &#273;i t&#236;m m&#7897;t m&#7913;c sai l&#7879;ch &#273;&#7911; nh&#7887; cho &#273;&#250;ng nghi&#7879;p v&#7909; n&#224;y. M&#244; t&#7843; s&#7843;n ph&#7849;m th&#236; c&#361; v&#224;i ph&#250;t c&#361;ng ch&#7859;ng sao. Gi&#225; th&#236; ch&#7881; n&#234;n c&#361; v&#224;i gi&#226;y. C&#242;n t&#7891;n kho l&#250;c kh&#225;ch b&#7845;m ch&#7889;t &#273;&#417;n th&#236; m&#7913;c sai l&#7879;ch cho ph&#233;p ph&#7843;i b&#7857;ng kh&#244;ng, n&#234;n tuy&#7879;t &#273;&#7889;i kh&#244;ng &#273;&#432;&#7907;c quy&#7871;t &#273;&#7883;nh d&#7921;a tr&#234;n cache. Bi&#7871;t g&#7855;n c&#225;i m&#7913;c sai l&#7879;ch ch&#7845;p nh&#7853;n &#273;&#432;&#7907;c &#273;&#243; v&#224;o t&#7915;ng lo&#7841;i d&#7919; li&#7879;u, theo m&#236;nh, m&#7899;i l&#224; th&#7913; ph&#226;n bi&#7879;t m&#7897;t ng&#432;&#7901;i thi&#7871;t k&#7871; h&#7879; th&#7889;ng v&#7899;i m&#7897;t ng&#432;&#7901;i ch&#7881; bi&#7871;t x&#224;i Redis.</p><p></p><p>M&#236;nh c&#361;ng m&#7899;i hi&#7875;u t&#7899;i &#273;&#226;y th&#244;i, ae n&#224;o l&#224;m s&#226;u h&#417;n ph&#7847;n near-cache hay client-side caching th&#236; b&#7893; sung gi&#250;p, hay mu&#7889;n m&#236;nh &#273;&#224;o ti&#7871;p ph&#7847;n n&#224;o c&#7913; n&#243;i. B&#224;i t&#7899;i ch&#7855;c m&#236;nh r&#7869; sang Kafka, v&#236; outbox v&#7899;i CDC nh&#7855;c ho&#224;i m&#224; ch&#432;a m&#7893; t&#7899;i n&#417;i.</p>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: hãy cẩn thận với bài toán indexing?]]></title><description><![CDATA[H&#7891;i x&#432;a v&#7899;i m&#236;nh, index &#273;&#417;n gi&#7843;n l&#7855;m: query ch&#7853;m th&#236; th&#234;m index, xong.]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-hay-can-than-voi</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-hay-can-than-voi</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Sat, 11 Jul 2026 14:58:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>H&#7891;i x&#432;a v&#7899;i m&#236;nh, index &#273;&#417;n gi&#7843;n l&#7855;m: query ch&#7853;m th&#236; th&#234;m index, xong. C&#7897;t n&#224;o n&#7857;m trong <code>WHERE</code> th&#236; &#273;&#225;nh index c&#7897;t &#273;&#243;. N&#243; nh&#432; ki&#7875;u b&#7841;n tra t&#7915; &#273;i&#7875;n &#8212; c&#243; b&#7843;ng m&#7909;c l&#7909;c th&#236; t&#236;m nhanh h&#417;n l&#7853;t t&#7915;ng trang. M&#236;nh tin v&#224;o &#273;i&#7873;u &#273;&#243; su&#7889;t m&#7845;y n&#259;m &#273;&#7847;u &#273;i l&#224;m, v&#224; th&#224;nh th&#7853;t m&#224; n&#243;i, n&#243; &#273;&#250;ng trong 90% tr&#432;&#7901;ng h&#7907;p.</p><p>Cho &#273;&#7871;n h&#244;m m&#236;nh g&#7863;p b&#224;i to&#225;n n&#224;y trong m&#7897;t bu&#7893;i ph&#7887;ng v&#7845;n. N&#243; b&#7855;t &#273;&#7847;u b&#7857;ng m&#7897;t c&#7889;c cafe, v&#224; k&#7871;t th&#250;c b&#7857;ng vi&#7879;c m&#236;nh nh&#7853;n ra: <strong>th&#234;m index &#273;&#244;i khi kh&#244;ng ph&#7843;i l&#224; c&#226;u tr&#7843; l&#7901;i &#8212; m&#224; l&#224; c&#225;ch nhanh nh&#7845;t &#273;&#7875; gi&#7871;t ch&#7871;t c&#7843; h&#7879; th&#7889;ng.</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>M&#236;nh k&#7875; l&#7841;i nguy&#234;n v&#259;n nh&#233;.</p><div><hr></div><p>C&#243; m&#7897;t b&#7843;ng t&#234;n <code>events</code> tr&#234;n PostgreSQL 15. N&#243; v&#7915;a v&#432;&#7907;t m&#7889;c <strong>200 tri&#7879;u d&#242;ng</strong>.</p><p>H&#7879; th&#7889;ng ingestion &#273;ang ch&#7841;y &#7903; m&#7913;c <strong>8.000 writes/gi&#226;y</strong>, &#273;&#7893; v&#224;o t&#7915; m&#7897;t Kafka consumer. Tr&#432;&#7899;c &#273;&#226;y P99 write latency l&#224; 12ms &#8212; m&#432;&#7907;t. Gi&#7901; n&#243; nh&#7843;y l&#234;n <strong>140ms v&#224; v&#7851;n &#273;ang t&#259;ng</strong> (ch&#7919; &#8220;v&#7851;n &#273;ang t&#259;ng&#8221; n&#224;y quan tr&#7885;ng l&#7855;m, &#273;&#7875; l&#225;t n&#7919;a m&#236;nh quay l&#7841;i).</p><p>R&#7891;i &#273;&#7897;i l&#224;m dashboard th&#7889;ng k&#234; k&#233;o t&#7899;i. Query c&#7911;a h&#7885; l&#7885;c theo <code>(tenant_id, event_type, created_at)</code>, v&#224; m&#7895;i request ph&#7843;i <strong>qu&#233;t 40 tri&#7879;u d&#242;ng</strong>. H&#7885; mua cho m&#236;nh m&#7897;t c&#7889;c cafe, c&#432;&#7901;i hi&#7873;n, b&#7843;o: &#8220;Cho b&#7885;n t&#7899; m&#7897;t c&#225;i index th&#244;i m&#224;.&#8221;</p><p>Nghe th&#236; d&#7877;. Nh&#432;ng m&#236;nh m&#7903; <code>pg_stat_user_indexes</code> ra xem th&#236; h&#417;i l&#7841;nh s&#7889;ng l&#432;ng: b&#7843;ng n&#224;y <strong>&#273;&#227; c&#243; s&#7861;n 4 index r&#7891;i</strong>. M&#7895;i l&#7847;n ghi m&#7897;t d&#242;ng m&#7899;i, c&#7843; 4 c&#225;i index &#273;&#243; &#273;&#7873;u ph&#7843;i c&#7853;p nh&#7853;t. Ng&#432;&#7901;i ph&#7887;ng v&#7845;n n&#243;i th&#7859;ng m&#7897;t c&#226;u l&#224;m m&#236;nh nh&#7899; m&#227;i:</p><blockquote><p>&#8220;N&#7871;u c&#7853;u th&#234;m index th&#7913; 5, ingestion lag s&#7869; bi&#7871;n th&#224;nh ingestion failure.&#8221;</p></blockquote><p>T&#7913;c l&#224;: c&#225;i write path v&#7889;n &#273;&#227; h&#7845;p h&#7889;i &#7903; 140ms, th&#234;m m&#7897;t index n&#7919;a l&#224; n&#243; g&#227;y h&#7859;n. Dashboard nhanh l&#234;n nh&#432;ng d&#7919; li&#7879;u ng&#7915;ng ch&#7843;y v&#224;o. &#272;&#432;&#7907;c c&#225;i n&#224;y m&#7845;t c&#225;i kia.</p><p>Nh&#432;ng c&#243; m&#7897;t chi ti&#7871;t m&#224; l&#250;c &#273;&#7847;u m&#236;nh l&#432;&#7899;t qua, sau m&#7899;i th&#7845;y n&#243; l&#224; ch&#236;a kh&#243;a: <strong>92% query c&#7911;a dashboard ch&#7881; &#273;&#7909;ng v&#224;o </strong><code>signup</code><strong> event c&#7911;a m&#7897;t tenant, trong 7 ng&#224;y g&#7847;n nh&#7845;t.</strong> M&#7897;t m&#7849;u t&#237; x&#237;u c&#7911;a c&#7843; c&#225;i b&#7843;ng kh&#7893;ng l&#7891; 200 tri&#7879;u d&#242;ng.</p><p>R&#7891;i ng&#432;&#7901;i ph&#7887;ng v&#7845;n &#273;&#432;a ra 4 ph&#432;&#417;ng &#225;n.</p><div><hr></div><p><strong>A) Th&#234;m m&#7897;t composite B-tree index tr&#234;n </strong><code>(tenant_id, event_type, created_at)</code><strong>.</strong> S&#225;ch gi&#225;o khoa. Bao tr&#7885;n query. Xong.</p><p><strong>B) Th&#234;m m&#7897;t covering index v&#7899;i </strong><code>INCLUDE (user_id, payload)</code> &#273;&#7875; query kh&#244;ng bao gi&#7901; ph&#7843;i ch&#7841;m v&#224;o heap n&#7919;a &#8212; index-only scan, &#273;&#7885;c bay.</p><p><strong>C) Ng&#7915;ng &#273;&#7909;ng v&#224;o index tr&#234;n b&#7843;ng ch&#237;nh. T&#7841;o m&#7897;t read replica, &#273;&#7849;y to&#224;n b&#7897; query dashboard sang &#273;&#243;.</strong> Gi&#7919; lu&#7891;ng write lu&#244;n g&#7885;n nh&#7865;.</p><p><strong>D) Th&#234;m m&#7897;t </strong><em><strong>partial</strong></em><strong> index, v&#7851;n tr&#234;n c&#225;c c&#7897;t &#273;&#243;, nh&#432;ng k&#232;m &#273;i&#7873;u ki&#7879;n:</strong></p><pre><code><code>WHERE event_type = 'signup'
  AND created_at &gt; now() - interval '7 days'</code></code></pre><p>Ch&#7881; &#273;&#225;nh index &#273;&#250;ng ph&#7847;n d&#7919; li&#7879;u &#8220;n&#243;ng&#8221;.</p><p>C&#7843; b&#7889;n &#273;&#7873;u l&#224; m&#244; h&#236;nh th&#7853;t, d&#249;ng trong production th&#7853;t. Ba trong s&#7889; &#273;&#243; s&#7869; l&#224;m dashboard nhanh l&#234;n. <strong>Nh&#432;ng ch&#7881; m&#7897;t c&#225;i l&#224;m &#273;&#432;&#7907;c &#273;i&#7873;u &#273;&#243; m&#224; kh&#244;ng &#273;&#7841;p v&#7905; write throughput.</strong></p><p>M&#236;nh &#273;o&#225;n &#273;&#432;&#7907;c anh em &#273;&#7885;c t&#7899;i &#273;&#226;y &#273;&#227; bi&#7871;t &#273;&#225;p &#225;n l&#224; D. Nh&#432;ng c&#225;i hay c&#7911;a bu&#7893;i ph&#7887;ng v&#7845;n kh&#244;ng n&#7857;m &#7903; ch&#7919; &#8220;D&#8221; &#8212; n&#243; n&#7857;m &#7903; c&#226;u h&#7887;i <em>&#8220;t&#7841;i sao ba c&#225;i kia sai?&#8221;</em> v&#224; <em>&#8220;D tho&#225;t b&#7857;ng c&#417; ch&#7871; g&#236;?&#8221;</em>. M&#236;nh &#273;i t&#7915;ng c&#225;i.</p><div><hr></div><h2><strong>V&#236; sao A v&#224; B gi&#7871;t write path</strong></h2><p>Mu&#7889;n hi&#7875;u t&#7841;i sao th&#234;m index l&#7841;i nguy hi&#7875;m &#273;&#7871;n th&#7871;, m&#236;nh ph&#7843;i hi&#7875;u <strong>m&#7897;t c&#225;i </strong><code>INSERT</code><strong> th&#7921;c ra t&#7889;n k&#233;m c&#7905; n&#224;o.</strong></p><p>Tr&#432;&#7899;c &#273;&#226;y m&#236;nh t&#432;&#7903;ng insert m&#7897;t d&#242;ng l&#224; ghi m&#7897;t ch&#7895;. Sai. Trong Postgres, m&#7897;t d&#242;ng insert v&#224;o b&#7843;ng c&#243; N index th&#7921;c ra l&#224;:</p><blockquote><p>1 heap tuple + N index entry + WAL cho t&#7845;t c&#7843; + (c&#243; th&#7875;) page split + (c&#243; th&#7875;) full-page write.</p></blockquote><p>M&#7895;i index l&#224; <strong>m&#7897;t c&#226;y B-tree &#273;&#7897;c l&#7853;p, ri&#234;ng bi&#7879;t</strong>. N&#243; kh&#244;ng chia s&#7867; g&#236; v&#7899;i c&#225;c index kh&#225;c. Th&#234;m index th&#7913; 5 ngh&#297;a l&#224; th&#234;m nguy&#234;n m&#7897;t c&#225;i c&#226;y ph&#7843;i b&#7843;o tr&#236; &#273;&#7891;ng b&#7897;, trong c&#249;ng m&#7897;t transaction, m&#7895;i l&#7847;n ghi.</p><p>V&#224; c&#225;i &#273;&#7855;t nh&#7845;t kh&#244;ng ph&#7843;i l&#224; &#8220;ch&#232;n v&#224;o l&#225; c&#226;y&#8221;. C&#225;i &#273;&#7855;t nh&#7845;t l&#224; <strong>page split</strong>.</p><p>H&#236;nh dung th&#7871; n&#224;y. M&#7895;i trang (page) trong B-tree ch&#7881; ch&#7913;a &#273;&#432;&#7907;c 8KB, v&#224; c&#225;c key ph&#7843;i n&#7857;m s&#7855;p x&#7871;p th&#7913; t&#7921; trong &#273;&#243;. Khi b&#7841;n ch&#232;n v&#224;o m&#7897;t trang &#273;&#227; &#273;&#7847;y, Postgres ph&#7843;i:</p><ol><li><p>Xin m&#7897;t trang m&#7899;i,</p></li><li><p>Chia &#273;&#244;i &#273;&#7889;ng key sang trang m&#7899;i (copy d&#7919; li&#7879;u),</p></li><li><p>S&#7917;a con tr&#7887; &#7903; trang cha &#273;&#7875; tr&#7887; t&#7899;i trang m&#7899;i,</p></li><li><p>V&#224; n&#7871;u trang cha <em>c&#361;ng</em> &#273;&#7847;y &#8212; cha l&#7841;i split, lan l&#234;n tr&#234;n, c&#243; khi t&#7899;i t&#7853;n g&#7889;c c&#226;y.</p></li></ol><p>M&#7897;t c&#250; split l&#224; ghi l&#7841;i 2-3 trang thay v&#236; s&#7917;a nh&#7865; 1 trang. V&#224; <strong>t&#7845;t c&#7843; &#273;&#7873;u ph&#7843;i ghi v&#224;o WAL</strong> (write-ahead log &#8212; Postgres ghi log tr&#432;&#7899;c, ghi data sau, &#273;&#7875; l&#7905; crash c&#242;n kh&#244;i ph&#7909;c &#273;&#432;&#7907;c).</p><p>&#272;&#226;y l&#224; ch&#7895; m&#236;nh v&#7905; ra: v&#7899;i index tr&#234;n c&#7897;t <em>ng&#7851;u nhi&#234;n</em> nh&#432; <code>tenant_id</code> hay <code>event_type</code>, entry m&#7899;i r&#417;i v&#224;o gi&#7919;a c&#226;y, ch&#7895; n&#224;o c&#361;ng c&#243; th&#7875; split. C&#242;n WAL th&#236; l&#224; <strong>m&#7897;t d&#242;ng ghi tu&#7847;n t&#7921; chung cho c&#7843; instance</strong> &#8212; n&#243; l&#224; &#273;i&#7875;m ngh&#7869;n c&#7893; chai, m&#7885;i commit &#273;&#7873;u ph&#7843;i x&#7871;p h&#224;ng qua &#273;&#243;. Th&#234;m index = th&#234;m WAL = fsync l&#226;u h&#417;n = commit l&#226;u h&#417;n = <strong>P99 t&#259;ng cho </strong><em><strong>to&#224;n b&#7897;</strong></em><strong> write</strong>, kh&#244;ng ri&#234;ng g&#236; query m&#7899;i.</p><p>Ch&#432;a h&#7871;t, c&#242;n m&#7897;t c&#250; nh&#226;n &#273;&#244;i ng&#7847;m n&#7919;a t&#234;n l&#224; <strong>full-page write</strong>: sau m&#7895;i checkpoint, l&#7847;n &#273;&#7847;u m&#7897;t trang b&#7883; ch&#7841;m, Postgres ghi <em>nguy&#234;n c&#7843; trang 8KB</em> v&#224;o WAL (ch&#7889;ng l&#7895;i torn-page). Index th&#7913; 5 ch&#7841;m th&#234;m nhi&#7873;u trang m&#7899;i &#8594; WAL ph&#236;nh g&#7845;p m&#7845;y l&#7847;n d&#7919; li&#7879;u th&#7853;t.</p><p>Gi&#7901; quay l&#7841;i ch&#7919; <strong>&#8220;v&#7851;n &#273;ang t&#259;ng&#8221;</strong> &#7903; &#273;&#7873; b&#224;i. &#272;&#243; kh&#244;ng ph&#7843;i chi ti&#7871;t trang tr&#237;. Khi m&#7897;t h&#7879; th&#7889;ng ch&#7841;y g&#7847;n tr&#7847;n I/O (utilization ti&#7871;n t&#7899;i 100%), latency kh&#244;ng t&#259;ng tuy&#7871;n t&#237;nh n&#7919;a &#8212; n&#243; <strong>d&#7921;ng &#273;&#7913;ng</strong> theo l&#253; thuy&#7871;t h&#224;ng &#273;&#7907;i. Ngh&#297;a l&#224; 4 index &#273;ang &#273;&#7849;y h&#7879; t&#7899;i s&#225;t m&#233;p v&#7921;c. Tr&#7921;c gi&#225;c &#8220;&#273;ang c&#243; 4, th&#234;m 1 n&#7919;a ch&#7881; +25% th&#244;i m&#224;&#8221; l&#224; sai ch&#7871;t ng&#432;&#7901;i &#8212; v&#236; b&#7841;n &#273;ang c&#7897;ng th&#234;m t&#7843;i v&#224;o m&#7897;t h&#7879; <em>&#273;&#227;</em> g&#7847;n b&#227;o h&#242;a. &#272;&#243; ch&#237;nh x&#225;c l&#224; l&#253; do ph&#432;&#417;ng &#225;n <strong>A</strong> s&#7853;p.</p><p>C&#242;n <strong>B</strong> th&#236; sao? N&#243; c&#242;n t&#7879; h&#417;n. <code>INCLUDE (payload)</code> &#8212; m&#224; <code>payload</code> th&#432;&#7901;ng l&#224; JSON to &#273;&#249;ng &#8212; nh&#233;t c&#7843; c&#7909;c &#273;&#243; v&#224;o index. Index ph&#236;nh kh&#7911;ng khi&#7871;p, write amplification t&#259;ng v&#7885;t, WAL c&#224;ng n&#7863;ng. B t&#7889;i &#432;u read b&#7857;ng c&#225;ch hy sinh write n&#7863;ng nh&#7845;t trong c&#7843; b&#7889;n. Sai ho&#224;n to&#224;n h&#432;&#7899;ng, v&#236; v&#7845;n &#273;&#7873; c&#7911;a m&#236;nh <em>&#273;ang l&#224; write</em>.</p><p>(C&#242;n m&#7897;t con dao ng&#7847;m n&#7919;a m&#224; m&#236;nh ch&#7881; n&#243;i ng&#7855;n: n&#7871;u b&#7843;ng c&#243; <code>UPDATE</code>, th&#236; m&#7895;i index b&#7841;n th&#234;m v&#224;o s&#7869; thu h&#7865;p c&#417; h&#7897;i <strong>HOT update</strong> &#8212; c&#225;i t&#7889;i &#432;u cho ph&#233;p Postgres c&#7853;p nh&#7853;t tuple ngay trong heap page m&#224; kh&#244;ng &#273;&#7909;ng index n&#224;o. Ch&#7881; c&#7847;n c&#7897;t b&#7883; update l&#7885;t v&#224;o <em>m&#7897;t</em> index th&#244;i l&#224; HOT b&#7883; h&#7911;y, ph&#7843;i c&#7853;p nh&#7853;t <em>t&#7845;t c&#7843;</em> index. Nh&#432;ng &#273;&#7873; n&#224;y thi&#234;n v&#7873; insert, n&#234;n m&#236;nh &#273;&#7875; d&#224;nh chuy&#7879;n &#273;&#243; cho m&#7897;t b&#224;i kh&#225;c.)</p><div><hr></div><h2><strong>V&#236; sao C &#8212; read replica &#8212; nghe hay m&#224; ch&#432;a tr&#250;ng</strong></h2><p>C&#225;i n&#224;y l&#7915;a &#273;&#432;&#7907;c kha kh&#225; ng&#432;&#7901;i, k&#7875; c&#7843; m&#236;nh l&#250;c &#273;&#7847;u. Read replica <em>nghe</em> r&#7845;t &#8220;ki&#7871;n tr&#250;c s&#432;&#8221;: t&#225;ch read kh&#7887;i write, gi&#7919; primary g&#7885;n nh&#7865;. V&#224; v&#7873; l&#226;u d&#224;i th&#236; <strong>&#273;&#250;ng, b&#7841;n s&#7869; c&#7847;n n&#243; th&#7853;t.</strong></p><p>Nh&#432;ng n&#243; kh&#244;ng tr&#7883; &#273;&#432;&#7907;c b&#7879;nh g&#7889;c. Replica l&#224; <strong>b&#7843;n sao y h&#7879;t</strong> c&#7911;a primary &#8212; n&#243; th&#7915;a h&#432;&#7903;ng nguy&#234;n xi c&#7845;u tr&#250;c index. Query tr&#234;n replica <em>v&#7851;n</em> ph&#7843;i qu&#233;t 40 tri&#7879;u d&#242;ng, v&#236; n&#243; <em>v&#7851;n</em> thi&#7871;u &#273;&#250;ng c&#225;i index ph&#249; h&#7907;p. Ch&#7853;m v&#7851;n ho&#224;n ch&#7853;m.</p><p>&#8220;Th&#236; th&#234;m index tr&#234;n replica!&#8221; &#8212; &#7915;, nh&#432;ng index tr&#234;n replica &#273;&#432;&#7907;c replay t&#7915; WAL c&#7911;a primary. Mu&#7889;n c&#243; index tr&#234;n replica th&#236; ph&#7843;i t&#7841;o n&#243; tr&#234;n primary tr&#432;&#7899;c. V&#224; th&#7871; l&#224; ta quay l&#7841;i &#273;&#250;ng c&#225;i v&#242;ng lu&#7849;n qu&#7849;n ban &#273;&#7847;u: &#273;&#7909;ng v&#224;o write path.</p><p>C gi&#7843;i quy&#7871;t b&#224;i to&#225;n <em>scale read</em> (&#273;&#250;ng, nh&#432;ng ch&#432;a ph&#7843;i b&#226;y gi&#7901;). N&#243; kh&#244;ng gi&#7843;i quy&#7871;t b&#224;i to&#225;n <em>thi&#7871;u m&#7897;t index &#273;&#7911; r&#7867;</em>. N&#243; t&#225;ch write v&#224; read ra hai m&#225;y, ch&#7913; kh&#244;ng l&#224;m query t&#7921; nhi&#234;n nhanh l&#234;n.</p><div><hr></div><h2><strong>V&#236; sao D th&#7855;ng: &#273;&#7915;ng &#273;&#225;nh index c&#7843; c&#225;i b&#7843;ng khi gi&#225; tr&#7883; n&#7857;m &#7903; m&#7897;t g&#243;c nh&#7887;</strong></h2><p>Gi&#7901; t&#7899;i c&#225;i &#273;&#7865;p. Nh&#7899; l&#7841;i: <strong>92% event kh&#244;ng ph&#7843;i </strong><code>signup</code><strong>.</strong></p><p>Partial index ch&#7881; &#273;&#225;nh index &#273;&#250;ng ph&#7847;n d&#7919; li&#7879;u th&#7887;a &#273;i&#7873;u ki&#7879;n <code>WHERE</code>:</p><pre><code><code>CREATE INDEX CONCURRENTLY idx_events_signup_hot
ON events (tenant_id, created_at)
WHERE event_type = 'signup'
  AND created_at &gt; '2026-07-05 00:00:00';</code></code></pre><p>Chuy&#7879;n g&#236; x&#7843;y ra khi m&#7897;t d&#242;ng m&#7899;i insert v&#224;o?</p><ul><li><p>N&#7871;u n&#243; l&#224; <code>event_type = 'click'</code> (92% tr&#432;&#7901;ng h&#7907;p) &#8594; Postgres ki&#7875;m tra &#273;i&#7873;u ki&#7879;n <code>WHERE</code>, th&#7845;y kh&#244;ng kh&#7899;p, v&#224; <strong>ho&#224;n to&#224;n kh&#244;ng &#273;&#7909;ng v&#224;o c&#225;i c&#226;y n&#224;y.</strong> Kh&#244;ng &#273;i c&#226;y, kh&#244;ng split, kh&#244;ng ghi WAL cho index, kh&#244;ng ph&#225; HOT. <strong>Chi ph&#237; b&#7857;ng kh&#244;ng.</strong></p></li><li><p>N&#7871;u n&#243; l&#224; <code>signup</code> (8%) &#8594; m&#7899;i t&#7841;o entry. M&#224; c&#226;y th&#236; b&#233; (ch&#7881; ch&#7913;a signup c&#7911;a m&#7845;y ng&#224;y g&#7847;n &#273;&#226;y), n&#244;ng, n&#7857;m g&#7885;n trong cache &#8594; split hi&#7871;m, full-page write hi&#7871;m.</p></li></ul><p>So s&#225;nh cho r&#245;: ph&#432;&#417;ng &#225;n A b&#7855;t <strong>100% s&#7889; write</strong> tr&#7843; ph&#237; cho c&#226;y th&#7913; 5. Partial index ch&#7881; b&#7855;t <strong>8%</strong> tr&#7843; ph&#237;, v&#224; tr&#7843; r&#7867; h&#417;n nhi&#7873;u. &#272;&#226;y l&#224; to&#224;n b&#7897; tr&#242; &#7843;o thu&#7853;t, v&#224; n&#243; g&#243;i g&#7885;n trong m&#7897;t c&#226;u m&#224; m&#236;nh mu&#7889;n kh&#7855;c l&#234;n t&#432;&#7901;ng:</p><blockquote><p><strong>Write amplification t&#7881; l&#7879; v&#7899;i ph&#7847;n d&#7919; li&#7879;u b&#7841;n &#273;&#225;nh index &#8212; kh&#244;ng ph&#7843;i v&#7899;i vi&#7879;c b&#7841;n </strong><em><strong>c&#243;</strong></em><strong> index hay kh&#244;ng.</strong></p></blockquote><p>Index nh&#7887; c&#242;n k&#233;o theo read nhanh: c&#226;y b&#233; th&#236; n&#7857;m trong RAM, cache hit cao, dashboard bay. V&#7915;a c&#7913;u write, v&#7915;a c&#7913;u read. &#272;&#7893;i l&#7841;i m&#7897;t c&#7889;c cafe. Qu&#225; h&#7901;i.</p><div><hr></div><h2><strong>Nh&#432;ng D c&#243; m&#7897;t c&#225;i b&#7851;y (v&#224; &#273;&#226;y m&#7899;i l&#224; ch&#7895; ph&#7887;ng v&#7845;n th&#7853;t s&#7921; b&#7855;t &#273;&#7847;u)</strong></h2><p>Anh em c&#243; &#273;&#7875; &#253; m&#236;nh l&#233;n &#273;&#7893;i c&#225;i &#273;i&#7873;u ki&#7879;n kh&#244;ng? &#272;&#7873; b&#224;i vi&#7871;t <code>created_at &gt; now() - interval '7 days'</code>, nh&#432;ng c&#226;u l&#7879;nh m&#236;nh g&#245; l&#7841;i l&#224; m&#7897;t m&#7889;c <em>c&#7913;ng</em>: <code>'2026-07-05'</code>. Kh&#244;ng ph&#7843;i m&#236;nh g&#245; nh&#7847;m.</p><p>&#272;&#226;y l&#224; ch&#7895; ng&#432;&#7901;i ph&#7887;ng v&#7845;n nh&#7871;ch m&#233;p h&#7887;i: <em>&#8220;C&#7853;u vi&#7871;t </em><code>now()</code><em> v&#224;o predicate &#224;? Ch&#7855;c ch&#432;a?&#8221;</em></p><p>H&#243;a ra c&#243; hai v&#7845;n &#273;&#7873;.</p><p><strong>Th&#7913; nh&#7845;t, Postgres kh&#244;ng cho.</strong> <code>now()</code> l&#224; h&#224;m <code>STABLE</code>, kh&#244;ng ph&#7843;i <code>IMMUTABLE</code>. M&#224; predicate c&#7911;a partial index b&#7855;t bu&#7897;c ph&#7843;i immutable. G&#245; <code>now()</code> v&#224;o l&#224; n&#243; n&#233;m l&#7895;i th&#7859;ng m&#7863;t:</p><pre><code><code>ERROR: functions in index predicate must be marked IMMUTABLE</code></code></pre><p><strong>Th&#7913; hai, k&#7875; c&#7843; g&#245; &#273;&#432;&#7907;c th&#236; c&#361;ng sai v&#7873; m&#7863;t ng&#7919; ngh&#297;a.</strong> Predicate c&#7911;a partial index &#273;&#432;&#7907;c <strong>&#273;&#225;nh gi&#225; &#273;&#250;ng m&#7897;t l&#7847;n, t&#7841;i th&#7901;i &#273;i&#7875;m </strong><code>CREATE INDEX</code><strong>, r&#7891;i &#273;&#243;ng b&#259;ng v&#297;nh vi&#7877;n</strong> v&#224;o catalog. N&#243; kh&#244;ng ph&#7843;i bi&#7875;u th&#7913;c &#8220;s&#7889;ng&#8221; &#273;&#432;&#7907;c t&#237;nh l&#7841;i m&#7895;i query.</p><p>Ngh&#297;a l&#224;: h&#244;m nay b&#7841;n t&#7841;o index v&#7899;i &#8220;7 ng&#224;y g&#7847;n nh&#7845;t&#8221; = t&#7915; 28/06. M&#432;&#7901;i ng&#224;y sau, th&#7871; gi&#7899;i &#273;&#227; tr&#244;i t&#7899;i 15/07, nh&#432;ng c&#225;i index v&#7851;n <em>&#273;&#243;ng b&#259;ng</em> &#7903; m&#7889;c 28/06. Query c&#7911;a dashboard v&#7851;n h&#7887;i &#8220;7 ng&#224;y g&#7847;n nh&#7845;t&#8221; (gi&#7901; l&#224; t&#7915; 08/07) &#8212; v&#224; planner nh&#236;n v&#224;o index, th&#7845;y n&#243; <em>kh&#244;ng ch&#7855;c</em> ch&#7913;a &#273;&#7911; d&#7919; li&#7879;u cho kho&#7843;ng &#273;&#243;, n&#234;n <strong>b&#7887; kh&#244;ng d&#249;ng index n&#7919;a</strong>, quay v&#7873; qu&#233;t tu&#7847;n t&#7921; 40 tri&#7879;u d&#242;ng. Index v&#7851;n n&#7857;m &#273;&#243;, v&#7851;n &#259;n chi ph&#237; write, nh&#432;ng <strong>v&#244; d&#7909;ng</strong>. T&#7879; nh&#7845;t c&#7911;a c&#7843; hai th&#7871; gi&#7899;i.</p><p>N&#234;n partial index th&#7901;i gian l&#224; m&#7897;t th&#7913; <strong>&#8220;stateful&#8221; &#8212; n&#243; kh&#244;ng t&#7921; tr&#432;&#7907;t.</strong> B&#7841;n bu&#7897;c ph&#7843;i hardcode m&#7889;c c&#7913;ng, r&#7891;i <strong>&#273;&#7883;nh k&#7923; x&#226;y l&#7841;i</strong> v&#7899;i m&#7889;c m&#7899;i. V&#224; c&#226;u h&#7887;i ti&#7871;p theo c&#7911;a ng&#432;&#7901;i ph&#7887;ng v&#7845;n l&#224;: <em>&#8220;X&#226;y l&#7841;i th&#7871; n&#224;o m&#224; kh&#244;ng downtime? C&#7853;u &#273;ang c&#243; 8.000 writes/gi&#226;y &#273;&#7845;y.&#8221;</em></p><div><hr></div><h2><strong>X&#226;y l&#7841;i index m&#224; kh&#244;ng ch&#7863;n write: </strong><code>CREATE INDEX CONCURRENTLY</code></h2><p>N&#7871;u b&#7841;n g&#245; <code>CREATE INDEX</code> b&#236;nh th&#432;&#7901;ng, Postgres s&#7869; <strong>kh&#243;a b&#7843;ng &#7903; ch&#7871; &#273;&#7897; SHARE &#8212; ch&#7863;n to&#224;n b&#7897; write</strong> trong su&#7889;t th&#7901;i gian build. V&#7899;i b&#7843;ng l&#7899;n, &#273;&#243; l&#224; v&#224;i ph&#250;t. V&#7899;i 8.000 writes/gi&#226;y, &#273;&#243; l&#224; th&#7843;m h&#7885;a.</p><p>C&#7913;u tinh l&#224; <code>CREATE INDEX CONCURRENTLY</code> (vi&#7871;t t&#7855;t CIC). N&#243; &#273;&#225;nh &#273;&#7893;i: build l&#226;u h&#417;n, nh&#432;ng <strong>kh&#244;ng ch&#7863;n write</strong>. C&#225;ch n&#243; l&#224;m ph&#233;p l&#224; qu&#233;t b&#7843;ng <em>hai l&#7847;n</em> v&#224; bi&#7871;t ch&#7901; &#273;&#250;ng l&#250;c:</p><ul><li><p><strong>L&#7847;n 1 &#8212; &#273;&#259;ng k&#253;:</strong> ghi index v&#224;o catalog nh&#432;ng &#273;&#225;nh d&#7845;u &#8220;ch&#432;a s&#7861;n s&#224;ng, ch&#432;a h&#7907;p l&#7879;&#8221;. R&#7891;i <strong>&#273;&#7907;i m&#7885;i transaction &#273;ang ch&#7841;y d&#7903; k&#7871;t th&#250;c</strong> (v&#236; ch&#250;ng ch&#432;a bi&#7871;t &#273;&#7871;n index m&#7899;i).</p></li><li><p><strong>L&#7847;n 2 &#8212; build:</strong> ch&#7909;p m&#7897;t snapshot, qu&#233;t c&#7843; b&#7843;ng, d&#7921;ng c&#226;y. T&#7915; &#273;&#226;y m&#7885;i write m&#7899;i b&#7855;t &#273;&#7847;u c&#7853;p nh&#7853;t index, nh&#432;ng query th&#236; <em>ch&#432;a</em> d&#249;ng.</p></li><li><p><strong>L&#7847;n 3 &#8212; validate:</strong> qu&#233;t b&#7843;ng l&#7847;n n&#7919;a &#273;&#7875; nh&#7863;t nh&#7919;ng d&#242;ng &#273;&#432;&#7907;c ghi <em>trong l&#250;c</em> &#273;ang build (snapshot c&#361; ch&#432;a th&#7845;y), r&#7891;i m&#7899;i &#273;&#225;nh d&#7845;u index &#8220;h&#7907;p l&#7879;&#8221; cho planner d&#249;ng.</p></li></ul><p>C&#225;i gi&#225; c&#7911;a &#8220;kh&#244;ng downtime&#8221; l&#224; qu&#233;t b&#7843;ng hai l&#7847;n, t&#7889;n g&#7845;p &#273;&#244;i I/O. Nh&#432;ng write kh&#244;ng bao gi&#7901; b&#7883; ch&#7863;n qu&#225; v&#224;i mili-gi&#226;y.</p><p>C&#243; ba c&#225;i b&#7851;y c&#7911;a CIC m&#224; m&#236;nh h&#7885;c &#273;&#432;&#7907;c (m&#7897;t c&#225;ch &#273;au &#273;&#7899;n):</p><ol><li><p><strong>Kh&#244;ng ch&#7841;y &#273;&#432;&#7907;c trong transaction block.</strong> Nhi&#7873;u framework migration b&#7885;c m&#7885;i th&#7913; trong <code>BEGIN...COMMIT</code> &#8212; ph&#7843;i t&#7855;t c&#225;i &#273;&#243; &#273;i.</p></li><li><p><strong>N&#7871;u CIC th&#7845;t b&#7841;i gi&#7919;a ch&#7915;ng, n&#243; &#273;&#7875; l&#7841;i m&#7897;t index &#8220;invalid&#8221; v&#224; kh&#244;ng t&#7921; d&#7885;n.</strong> C&#225;i x&#225;c &#273;&#243; v&#7851;n &#259;n chi ph&#237; write m&#224; kh&#244;ng ph&#7909;c v&#7909; query n&#224;o. B&#7841;n ph&#7843;i t&#7921; <code>DROP INDEX CONCURRENTLY</code> n&#243; &#273;i. (N&#234;n script x&#226;y l&#7841;i lu&#244;n ph&#7843;i d&#7885;n x&#225;c &#273;&#7847;u ti&#234;n, v&#224; ph&#7843;i ch&#7841;y l&#7841;i &#273;&#432;&#7907;c nhi&#7873;u l&#7847;n &#8212; idempotent.)</p></li><li><p><strong>M&#7897;t transaction s&#7889;ng l&#226;u s&#7869; treo CIC v&#244; h&#7841;n</strong>, v&#236; CIC ph&#7843;i <em>&#273;&#7907;i</em> transaction c&#361; k&#7871;t th&#250;c. M&#7897;t &#244;ng <code>BEGIN</code> r&#7891;i &#273;i &#259;n tr&#432;a, m&#7897;t cursor &#273;&#7875; m&#7903;, m&#7897;t <code>pg_dump</code> ch&#7841;y l&#226;u &#8212; &#273;&#7911; &#273;&#7875; CIC &#273;&#7913;ng h&#236;nh. Tr&#432;&#7899;c khi x&#226;y l&#7841;i, lu&#244;n soi <code>pg_stat_activity</code> xem c&#243; &#244;ng n&#224;o ng&#7891;i l&#226;u kh&#244;ng.</p></li></ol><p>V&#224; pattern x&#226;y-l&#7841;i-kh&#244;ng-downtime ho&#224;n ch&#7881;nh, m&#236;nh g&#7885;i l&#224; <strong>build-then-swap</strong>:</p><pre><code><code>-- 1. D&#7885;n x&#225;c invalid index t&#7915; l&#7847;n tr&#432;&#7899;c (idempotent)
DROP INDEX CONCURRENTLY IF EXISTS idx_events_signup_hot_new;

-- 2. Build index m&#7899;i v&#7899;i m&#7889;c tr&#432;&#7907;t t&#7899;i, KH&#212;NG ch&#7863;n write
CREATE INDEX CONCURRENTLY idx_events_signup_hot_new
ON events (tenant_id, created_at)
WHERE event_type = 'signup'
  AND created_at &gt; '2026-07-05 00:00:00';

-- 3. &#272;&#7893;i t&#234;n &#8212; thao t&#225;c catalog, nhanh nh&#432; ch&#7899;p
BEGIN;
  ALTER INDEX idx_events_signup_hot     RENAME TO idx_events_signup_hot_old;
  ALTER INDEX idx_events_signup_hot_new RENAME TO idx_events_signup_hot;
COMMIT;

-- 4. D&#7885;n index c&#361;
DROP INDEX CONCURRENTLY idx_events_signup_hot_old;</code></code></pre><p>Su&#7889;t b&#432;&#7899;c 2, index c&#361; <strong>v&#7851;n ph&#7909;c v&#7909; query b&#236;nh th&#432;&#7901;ng</strong> &#8212; dashboard kh&#244;ng h&#7909;t m&#7897;t gi&#226;y. &#272;&#243;ng g&#243;i c&#225;i n&#224;y v&#224;o m&#7897;t job <code>pg_cron</code> ch&#7841;y 3 gi&#7901; s&#225;ng m&#7895;i ng&#224;y (nh&#7899; &#273;&#7875; m&#7897;t kho&#7843;ng &#273;&#7879;m &#8212; index ph&#7911; 10 ng&#224;y cho query 7 ng&#224;y, &#273;&#7875; kh&#244;ng bao gi&#7901; ch&#7841;m m&#233;p), v&#224; b&#7841;n c&#243; m&#7897;t &#8220;c&#7917;a s&#7893; n&#243;ng&#8221; t&#7921; tr&#432;&#7907;t theo l&#7883;ch.</p><p>(N&#7871;u ch&#7881; mu&#7889;n d&#7885;n bloat m&#224; <em>kh&#244;ng</em> &#273;&#7893;i predicate th&#236; c&#243; <code>REINDEX INDEX CONCURRENTLY</code> &#8212; n&#243; t&#7921; swap v&#224; t&#7921; d&#7885;n, an to&#224;n h&#417;n. Nh&#432;ng &#273;&#7893;i predicate &#8212; t&#7913;c &#273;&#7893;i m&#7889;c th&#7901;i gian &#8212; th&#236; b&#7855;t bu&#7897;c ph&#7843;i build c&#225;i m&#7899;i, REINDEX kh&#244;ng gi&#250;p &#273;&#432;&#7907;c.)</p><p>T&#7899;i &#273;&#226;y th&#236; m&#236;nh &#273;&#227; c&#243; m&#7897;t l&#7901;i gi&#7843;i ch&#7841;y &#273;&#432;&#7907;c trong production. Nh&#432;ng ng&#7891;i tr&#234;n gh&#7871; ph&#7887;ng v&#7845;n, m&#236;nh nghe c&#226;u h&#7887;i m&#224; m&#7885;i ki&#7871;n tr&#250;c s&#432; &#273;&#7873;u bi&#7871;t s&#7869; t&#7899;i: <em>&#8220;&#7914;, hay &#273;&#7845;y. Th&#7871; 6 th&#225;ng n&#7919;a, b&#7843;ng l&#234;n 1 t&#7881; d&#242;ng, c&#7853;u v&#7851;n &#273;i x&#226;y l&#7841;i index m&#7895;i &#273;&#234;m &#224;?&#8221;</em></p><div><hr></div><h2><strong>C&#7845;p &#273;&#7897; cu&#7889;i: &#273;&#7915;ng v&#225; index n&#7919;a, h&#227;y chia lu&#244;n c&#225;i b&#7843;ng</strong></h2><p>Su&#7889;t n&#227;y gi&#7901;, m&#236;nh v&#7853;t l&#7897;n v&#7899;i m&#7897;t s&#7921; th&#7853;t ng&#7847;m: <strong>b&#7843;ng l&#224; m&#7897;t kh&#7889;i 200 tri&#7879;u d&#242;ng li&#7873;n m&#7841;ch.</strong> M&#7885;i index &#273;&#7873;u ph&#7843;i s&#7889;ng tr&#234;n c&#7843; kh&#7889;i &#273;&#243;. Partial index ch&#7881; l&#224; m&#7865;o &#8220;gi&#7843; v&#7901;&#8221; chia nh&#7887; &#7903; t&#7847;ng index &#8212; c&#242;n c&#225;i heap (d&#7919; li&#7879;u th&#7853;t) v&#7851;n nguy&#234;n m&#7897;t c&#7909;c.</p><p>Partitioning gi&#7843;i quy&#7871;t t&#7853;n g&#7889;c: <strong>chia lu&#244;n c&#225;i heap.</strong></p><p>B&#7843;ng <code>events</code> tr&#7903; th&#224;nh m&#7897;t b&#7843;ng &#8220;cha&#8221; &#8212; ch&#7881; l&#224; &#273;&#7883;nh ngh&#297;a, kh&#244;ng ch&#7913;a d&#7919; li&#7879;u. D&#7919; li&#7879;u th&#7853;t n&#7857;m trong nhi&#7873;u b&#7843;ng con, m&#7895;i con ph&#7911; m&#7897;t kho&#7843;ng th&#7901;i gian:</p><pre><code><code>events (cha, r&#7895;ng)
&#9500;&#9472;&#9472; events_2026_06_w1   (01&#8211;07/06)   &#8592; ngu&#7897;i
&#9500;&#9472;&#9472; ...
&#9500;&#9472;&#9472; events_2026_07_w1   (01&#8211;07/07)   &#8592; N&#211;NG, &#273;ang ghi
&#9492;&#9472;&#9472; events_2026_07_w2   (t&#432;&#417;ng lai)</code></code></pre><pre><code><code>CREATE TABLE events (
    id          bigint GENERATED ALWAYS AS IDENTITY,
    tenant_id   bigint NOT NULL,
    event_type  text   NOT NULL,
    payload     jsonb,
    created_at  timestamptz NOT NULL
) PARTITION BY RANGE (created_at);

CREATE TABLE events_2026_07_w1
    PARTITION OF events
    FOR VALUES FROM ('2026-07-01') TO ('2026-07-08');</code></code></pre><p>Khi &#7913;ng d&#7909;ng <code>INSERT INTO events</code>, Postgres <strong>t&#7921; routing</strong> d&#242;ng &#273;&#243; xu&#7889;ng &#273;&#250;ng partition d&#7921;a tr&#234;n <code>created_at</code>. &#7912;ng d&#7909;ng kh&#244;ng c&#7847;n bi&#7871;t partition t&#7891;n t&#7841;i.</p><p>C&#225;i n&#224;y thay &#273;&#7893;i m&#7885;i th&#7913;, theo ba h&#432;&#7899;ng.</p><p><strong>1. Index tr&#7903; th&#224;nh &#8220;local&#8221; &#8212; m&#7895;i partition m&#7897;t c&#226;y b&#233; x&#237;u.</strong> Khi b&#7841;n t&#7841;o index tr&#234;n b&#7843;ng cha, Postgres t&#7841;o m&#7897;t c&#226;y <em>ri&#234;ng</em> tr&#234;n m&#7895;i partition. Insert c&#7911;a tu&#7847;n n&#224;y ch&#7881; c&#7853;p nh&#7853;t index c&#7911;a <code>events_2026_07_w1</code> &#8212; m&#7897;t c&#226;y ch&#7881; v&#224;i tri&#7879;u d&#242;ng, ch&#7913; kh&#244;ng ph&#7843;i c&#226;y 200 tri&#7879;u. Nh&#7899; l&#7841;i b&#224;i h&#7885;c page split &#7903; tr&#234;n: c&#226;y b&#233; &#8594; n&#244;ng h&#417;n, split hi&#7871;m h&#417;n, n&#7857;m g&#7885;n trong RAM. To&#224;n b&#7897; &#273;&#225;m qu&#225;i v&#7853;t write amplification b&#7883; thu nh&#7887; l&#7841;i. V&#224; index c&#7911;a partition c&#361; th&#236; <strong>ngu&#7897;i h&#7859;n</strong> &#8212; kh&#244;ng ai ghi v&#224;o th&#225;ng tr&#432;&#7899;c n&#7919;a, n&#234;n ch&#250;ng kh&#244;ng split, kh&#244;ng t&#7841;o WAL, kh&#244;ng tranh cache. Write path ch&#7881; c&#242;n n&#243;ng &#7903; &#273;&#250;ng m&#7897;t partition. &#272;&#226;y l&#224; th&#7913; m&#224; c&#7843; A, B, C, D &#273;&#7873;u kh&#244;ng l&#224;m &#273;&#432;&#7907;c: <strong>c&#244; l&#7853;p chi ph&#237; write v&#7873; m&#7863;t v&#7853;t l&#253; theo th&#7901;i gian.</strong></p><p><strong>2. Partition pruning c&#7913;u read &#8212; v&#224; ch&#7919;a lu&#244;n c&#225;i b&#7851;y </strong><code>now()</code><strong>.</strong> Khi dashboard query <code>WHERE created_at &gt; now() - interval '7 days'</code>, planner <strong>lo&#7841;i th&#7859;ng m&#7885;i partition kh&#244;ng th&#7875; ch&#7913;a d&#7919; li&#7879;u &#273;&#243;</strong>, kh&#244;ng th&#232;m m&#7903;. V&#224; quan tr&#7885;ng: pruning n&#224;y ch&#7841;y &#273;&#432;&#7907;c &#7903; <strong>run-time</strong> (Postgres 11+) &#8212; ngh&#297;a l&#224; <code>now()</code> &#273;&#432;&#7907;c t&#237;nh l&#250;c <em>ch&#7841;y</em>, kh&#244;ng c&#7847;n &#273;&#243;ng b&#259;ng nh&#432; partial index. Data t&#7921; &#8220;gi&#224; &#273;i&#8221; khi l&#7883;ch tr&#244;i, partition c&#361; t&#7921; nhi&#234;n r&#417;i kh&#7887;i t&#7847;m ng&#7855;m. C&#225;i b&#7851;y <code>now()</code> b&#7855;t m&#236;nh ph&#7843;i rebuild m&#7895;i &#273;&#234;m &#7903; ph&#432;&#417;ng &#225;n D &#8212; &#7903; &#273;&#226;y <strong>bi&#7871;n m&#7845;t.</strong></p><p><strong>3. X&#243;a data c&#361; t&#7915; &#8220;chi&#7871;n d&#7883;ch h&#224;ng &#273;&#234;m &#273;au kh&#7893;&#8221; th&#224;nh m&#7897;t d&#242;ng l&#7879;nh.</strong> V&#7899;i b&#7843;ng th&#432;&#7901;ng, <code>DELETE FROM events WHERE created_at &lt; ...</code> m&#7845;y ch&#7909;c tri&#7879;u d&#242;ng l&#224; &#273;&#7867; ra m&#7845;y ch&#7909;c tri&#7879;u dead tuple, VACUUM ch&#7841;y v&#227; m&#7891; h&#244;i, bloat, lock, b&#227;o I/O. V&#7899;i partition, &#8220;x&#243;a data 90 ng&#224;y tr&#432;&#7899;c&#8221; ch&#7881; l&#224;:</p><pre><code><code>ALTER TABLE events DETACH PARTITION events_2026_04_w1 CONCURRENTLY;
DROP TABLE events_2026_04_w1;</code></code></pre><p><code>DROP TABLE</code> m&#7897;t partition g&#7847;n nh&#432; t&#7913;c th&#7901;i &#8212; x&#243;a file, kh&#244;ng dead tuple, kh&#244;ng c&#7847;n VACUUM. Ng&#432;&#7901;i ta g&#7885;i l&#224; <strong>partition rotation.</strong></p><div><hr></div><h2><strong>Partitioning c&#361;ng kh&#244;ng mi&#7877;n ph&#237; &#8212; m&#7845;y c&#225;i ph&#7843;i thu&#7897;c l&#242;ng</strong></h2><p>M&#236;nh kh&#244;ng mu&#7889;n v&#7869; ra m&#7897;t b&#7913;c tranh m&#224;u h&#7891;ng. Partitioning tr&#7843; b&#7857;ng &#273;&#7897; ph&#7913;c t&#7841;p, v&#224; &#273;&#226;y l&#224; nh&#7919;ng ch&#7895; m&#236;nh t&#7915;ng v&#7845;p (ho&#7863;c th&#7845;y ng&#432;&#7901;i kh&#225;c v&#7845;p):</p><ul><li><p><strong>Partition key ph&#7843;i n&#7857;m trong m&#7885;i UNIQUE / PRIMARY KEY.</strong> Postgres <em>kh&#244;ng c&#243;</em> unique index to&#224;n c&#7909;c xuy&#234;n partition. Mu&#7889;n c&#243; primary key th&#236; <code>created_at</code> b&#7855;t bu&#7897;c ph&#7843;i l&#224; m&#7897;t ph&#7847;n c&#7911;a n&#243;: <code>PRIMARY KEY (id, created_at)</code>, kh&#244;ng th&#7875; ch&#7881; <code>(id)</code>. N&#7871;u code c&#361; d&#7921;a v&#224;o <code>id</code> unique to&#224;n c&#7909;c hay c&#243; foreign key tr&#7887; t&#7899;i, &#273;&#226;y l&#224; ch&#7895; &#273;au &#273;&#7847;u nh&#7845;t khi migrate.</p></li><li><p><strong>Query kh&#244;ng k&#232;m partition key th&#236; kh&#244;ng prune &#273;&#432;&#7907;c.</strong> N&#7871;u dashboard &#273;&#244;i khi query <code>WHERE tenant_id = 42</code> m&#224; qu&#234;n &#273;i&#7873;u ki&#7879;n th&#7901;i gian &#8594; Postgres qu&#233;t <em>m&#7885;i</em> partition. May l&#224; 92% query c&#7911;a m&#236;nh &#273;&#7873;u c&#243; &#8220;7 ng&#224;y g&#7847;n nh&#7845;t&#8221; &#8212; kh&#7899;p ho&#224;n h&#7843;o.</p></li><li><p><strong>Qu&#225; nhi&#7873;u partition th&#236; planner ph&#236;nh.</strong> V&#224;i tr&#259;m c&#225;i th&#236; &#7893;n, v&#224;i ngh&#236;n th&#236; planning time v&#224; b&#7897; nh&#7899; t&#259;ng. &#272;&#7915;ng partition theo gi&#7901; n&#7871;u retention l&#224; h&#224;ng n&#259;m.</p></li><li><p><strong>Ph&#7843;i t&#7841;o tr&#432;&#7899;c partition t&#432;&#417;ng lai.</strong> &#272;&#7871;n 0h ng&#224;y sang tu&#7847;n m&#7899;i m&#224; ch&#432;a c&#243; partition &#8594; insert l&#7895;i h&#224;ng lo&#7841;t &#8594; ingestion s&#7853;p. Kinh &#273;i&#7875;n. &#272;&#7915;ng t&#7921; vi&#7871;t cron tay &#8212; d&#249;ng <strong>pg_partman</strong>, n&#243; lo pre-create v&#224; retention cho b&#7841;n.</p></li></ul><p>V&#224; c&#225;i kh&#243; nh&#7845;t, th&#7913; m&#224; m&#236;nh ngh&#297; m&#7899;i th&#7921;c s&#7921; ph&#226;n lo&#7841;i ng&#432;&#7901;i trong ph&#7887;ng v&#7845;n: <strong>l&#224;m sao migrate 200 tri&#7879;u d&#242;ng &#273;ang ch&#7841;y 8.000 writes/gi&#226;y sang partitioned m&#224; kh&#244;ng downtime?</strong> B&#7841;n <em>kh&#244;ng th&#7875;</em> <code>ALTER TABLE ... PARTITION BY</code> t&#7841;i ch&#7895; &#8212; Postgres kh&#244;ng cho bi&#7871;n b&#7843;ng th&#432;&#7901;ng th&#224;nh partitioned. C&#243; ba &#273;&#432;&#7901;ng:</p><ul><li><p><strong>Big-bang</strong> (t&#7841;o b&#7843;ng m&#7899;i, copy h&#7871;t, &#273;&#7893;i t&#234;n): lo&#7841;i &#8212; copy 200 tri&#7879;u d&#242;ng m&#7845;t h&#224;ng gi&#7901; trong khi write v&#7851;n &#273;&#7893; v&#224;o.</p></li><li><p><strong>Dual-write + backfill:</strong> cho &#7913;ng d&#7909;ng ghi song song v&#224;o c&#7843; b&#7843;ng c&#361; v&#224; b&#7843;ng m&#7899;i, r&#7891;i backfill d&#7919; li&#7879;u l&#7883;ch s&#7917; theo t&#7915;ng l&#244; nh&#7887; (ng&#7911; gi&#7919;a c&#225;c l&#244; &#273;&#7875; kh&#244;ng gi&#7871;t I/O), verify kh&#7899;p, r&#7891;i m&#7899;i swap. Ch&#7855;c c&#250;, rollback d&#7877;.</p></li><li><p><strong>ATTACH b&#7843;ng c&#361; l&#224;m m&#7897;t partition:</strong> &#273;&#226;y l&#224; m&#7865;o m&#236;nh th&#237;ch nh&#7845;t. T&#7841;o b&#7843;ng cha r&#7895;ng, &#273;&#7893;i t&#234;n b&#7843;ng 200 tri&#7879;u d&#242;ng th&#224;nh <code>events_legacy</code>, th&#234;m m&#7897;t <code>CHECK</code> constraint h&#7907;p l&#7879; &#273;&#7875; Postgres bi&#7871;t kho&#7843;ng c&#7911;a n&#243; (&#273;&#7875; kh&#7887;i ph&#7843;i qu&#233;t c&#7843; b&#7843;ng l&#250;c attach), r&#7891;i <code>ATTACH</code> n&#243; v&#224;o l&#224;m partition qu&#225; kh&#7913;. <strong>Kh&#244;ng copy m&#7897;t byte n&#224;o.</strong> Data m&#7899;i r&#417;i v&#224;o partition m&#7899;i, c&#7909;c legacy n&#7857;m im, ngu&#7897;i d&#7847;n, r&#7891;i t&#7899;i h&#7841;n th&#236; drop. Th&#7921;c d&#7909;ng nh&#7845;t cho t&#236;nh hu&#7889;ng n&#224;y.</p></li></ul><div><hr></div><h2><strong>V&#7853;y r&#7889;t cu&#7897;c ch&#7885;n g&#236;?</strong></h2><p>&#272;&#226;y l&#224; ch&#7895; m&#236;nh ngh&#297; nhi&#7873;u ng&#432;&#7901;i tr&#7843; l&#7901;i ph&#7887;ng v&#7845;n b&#7883; h&#7909;t: h&#7885; t&#236;m <em>m&#7897;t</em> &#273;&#225;p &#225;n &#273;&#250;ng. Nh&#432;ng b&#224;i n&#224;y kh&#244;ng c&#243; m&#7897;t &#273;&#225;p &#225;n &#8212; n&#243; c&#243; m&#7897;t <strong>tr&#236;nh t&#7921; tr&#432;&#7903;ng th&#224;nh</strong>:</p><ul><li><p><strong>H&#244;m nay, &#273;&#7893;i c&#7889;c cafe:</strong> d&#249;ng <strong>D &#8212; partial index</strong>. V&#225; ngay, &#237;t r&#7911;i ro, kh&#244;ng &#273;&#7893;i schema, dashboard nhanh trong chi&#7873;u nay. Nh&#7899; hardcode m&#7889;c th&#7901;i gian v&#224; d&#7921;ng job rebuild-concurrently.</p></li><li><p><strong>Khi b&#7843;ng c&#242;n ph&#236;nh v&#224; retention th&#224;nh v&#7845;n &#273;&#7873;:</strong> chuy&#7875;n sang <strong>partitioning theo th&#7901;i gian</strong>. Index local n&#234;n write r&#7867;, pruning run-time n&#234;n <code>now()</code> ch&#7841;y t&#7921; nhi&#234;n, retention th&#224;nh <code>DROP</code> m&#7897;t partition.</p></li><li><p><strong>Khi read scale v&#432;&#7907;t m&#7897;t node:</strong> l&#250;c &#273;&#243; <strong>C &#8212; read replica</strong> m&#7899;i th&#7921;c s&#7921; v&#224;o &#273;&#250;ng vai, v&#224; n&#243; ch&#7841;y tr&#234;n m&#7897;t b&#7843;ng <em>&#273;&#227;</em> partition n&#234;n c&#242;n nhanh h&#417;n n&#7919;a.</p></li></ul><p>Ba t&#7847;ng n&#224;y <strong>b&#7893; sung</strong> nhau, kh&#244;ng lo&#7841;i tr&#7915;. M&#7897;t b&#7843;ng partitioned v&#7851;n c&#243; th&#7875; c&#243; partial index local tr&#234;n partition n&#243;ng, v&#224; v&#7851;n replicate sang replica. C&#225;i sai duy nh&#7845;t l&#224; nh&#7843;y th&#7859;ng v&#224;o A hay B &#8212; th&#234;m m&#7897;t index full-table v&#224;o m&#7897;t write path &#273;ang h&#7845;p h&#7889;i.</p><p>N&#7871;u ph&#7843;i g&#243;i c&#7843; b&#224;i v&#224;o m&#7897;t c&#226;u &#273;&#7875; mang &#273;i ph&#7887;ng v&#7845;n, m&#236;nh s&#7869; n&#243;i th&#7871; n&#224;y:</p><blockquote><p><strong>&#272;&#7915;ng &#273;&#225;nh index c&#7843; c&#225;i b&#7843;ng khi 92% gi&#225; tr&#7883; n&#7857;m trong m&#7897;t g&#243;c nh&#7887;. Write amplification t&#7881; l&#7879; v&#7899;i ph&#7847;n d&#7919; li&#7879;u b&#7841;n index, kh&#244;ng ph&#7843;i v&#7899;i vi&#7879;c b&#7841;n c&#243; index hay kh&#244;ng. Partial index c&#7855;t chi ph&#237; &#273;&#243; xu&#7889;ng c&#242;n ph&#7847;n n&#243;ng; partitioning c&#244; l&#7853;p lu&#244;n ph&#7847;n n&#243;ng v&#7873; m&#7863;t v&#7853;t l&#253; v&#224; cho </strong><code>now()</code><strong> ch&#7841;y t&#7921; nhi&#234;n kh&#244;ng c&#7847;n &#273;&#243;ng b&#259;ng; read replica l&#224; &#273;&#7875; scale read sau c&#249;ng, kh&#244;ng ph&#7843;i &#273;&#7875; ch&#7919;a m&#7897;t index thi&#7871;u.</strong></p></blockquote><div><hr></div><p>Th&#250; th&#7853;t l&#224; m&#236;nh c&#361;ng m&#7899;i hi&#7875;u t&#7899;i m&#7913;c n&#224;y th&#244;i. C&#242;n kha kh&#225; th&#7913; m&#236;nh c&#7889; t&#236;nh &#273;&#7875; d&#224;nh cho b&#224;i sau &#8212; chuy&#7879;n HOT update b&#7883; index ph&#225; v&#7905;, chuy&#7879;n t&#237;nh <code>fillfactor</code> b&#7857;ng s&#7889; c&#7909; th&#7875; &#273;&#7875; gi&#7843;m split, chuy&#7879;n &#273;&#7885;c <code>EXPLAIN (ANALYZE, BUFFERS)</code> &#273;&#7875; <em>ch&#7913;ng minh</em> pruning c&#243; th&#7853;t s&#7921; x&#7843;y ra hay kh&#244;ng.</p><p>N&#7871;u anh em t&#7915;ng g&#7863;p b&#224;i to&#225;n n&#224;y ngo&#224;i &#273;&#7901;i &#8212; ho&#7863;c c&#243; m&#7897;t g&#243;c nh&#236;n kh&#225;c v&#7873; chuy&#7879;n ch&#7885;n D so v&#7899;i partitioning &#8212; th&#236; k&#7875; m&#236;nh nghe v&#7899;i. M&#236;nh v&#7851;n &#273;ang h&#7885;c, v&#224; m&#7845;y b&#224;i h&#7885;c ki&#7875;u n&#224;y hay nh&#7845;t l&#224; khi ng&#7891;i c&#227;i nhau qua l&#7841;i. &#9749;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: kafka hay rabbitmq (phần kết)]]></title><description><![CDATA[B&#224;i tr&#432;&#7899;c m&#236;nh k&#7875; c&#7843; m&#7897;t h&#224;nh tr&#236;nh d&#224;i th&#432;&#7907;t, &#273;i t&#7915; m&#7897;t c&#226;u h&#7887;i t&#432;&#7903;ng &#273;&#417;n gi&#7843;n l&#224; "l&#224;m h&#7879; th&#7889;ng order th&#236; n&#234;n x&#224;i Kafka hay RabbitMQ" m&#224; l&#242;i ra &#273;&#7911; th&#7913; chuy&#7879;n v&#7873; saga, v&#7873; idempotency, v&#7873; c&#225;i s&#7921; th&#7853;t ph&#361; ph&#224;ng l&#224; trong h&#7879; ph&#226;n t&#225;n kh&#244;ng c&#243; rollback.]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-kafka-hay-rabbitmq</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-kafka-hay-rabbitmq</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Sat, 27 Jun 2026 01:08:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>B&#224;i tr&#432;&#7899;c m&#236;nh k&#7875; c&#7843; m&#7897;t h&#224;nh tr&#236;nh d&#224;i th&#432;&#7907;t, &#273;i t&#7915; m&#7897;t c&#226;u h&#7887;i t&#432;&#7903;ng &#273;&#417;n gi&#7843;n l&#224; "l&#224;m h&#7879; th&#7889;ng order th&#236; n&#234;n x&#224;i Kafka hay RabbitMQ" m&#224; l&#242;i ra &#273;&#7911; th&#7913; chuy&#7879;n v&#7873; saga, v&#7873; idempotency, v&#7873; c&#225;i s&#7921; th&#7853;t ph&#361; ph&#224;ng l&#224; trong h&#7879; ph&#226;n t&#225;n kh&#244;ng c&#243; rollback. Nh&#432;ng m&#224; &#273;&#7875; &#253; nha, su&#7889;t c&#7843; b&#224;i &#273;&#243; m&#236;nh l&#432;&#417;n l&#7865;o ch&#432;a th&#232;m tr&#7843; l&#7901;i c&#225;i c&#226;u g&#7889;c lu&#244;n ;)) c&#7913; h&#7865;n l&#7847;n h&#7865;n l&#7919;a "b&#224;i sau b&#224;i sau". Th&#236; &#273;&#226;y, b&#224;i sau &#273;&#226;y. H&#244;m nay m&#236;nh ch&#7889;t s&#7893;.</p><p></p><p>M&#224; ch&#7889;t s&#7893; ki&#7875;u g&#236; c&#361;ng ph&#7843;i b&#7855;t &#273;&#7847;u b&#7857;ng m&#7897;t c&#250; t&#225;t nh&#7865; v&#224;o m&#7863;t m&#236;nh c&#7911;a m&#7845;y th&#225;ng tr&#432;&#7899;c. H&#7891;i &#273;&#243; m&#236;nh h&#7887;i c&#226;u n&#224;y m&#7897;t c&#225;ch r&#7845;t h&#7891;n nhi&#234;n, ki&#7875;u n&#243; l&#224; m&#7897;t b&#224;i to&#225;n tr&#7855;c nghi&#7879;m c&#243; hai &#273;&#225;p &#225;n A v&#7899;i B, ch&#7885;n m&#7897;t c&#225;i l&#224; xong. C&#224;ng h&#7885;c m&#236;nh c&#224;ng nh&#7853;n ra c&#225;i c&#226;u "Kafka hay RabbitMQ" n&#243; sai ngay t&#7915; trong tr&#7913;ng, kh&#244;ng ph&#7843;i v&#236; hai &#273;&#225;p &#225;n sai, m&#224; v&#236; n&#243; l&#224; m&#7897;t c&#226;u h&#7887;i "ho&#7863;c" trong khi &#273;&#7901;i th&#7853;t l&#224; m&#7897;t c&#226;u h&#7887;i "v&#224;". Nh&#432;ng t&#7915; t&#7915;, &#273;&#7875; m&#236;nh k&#7875; c&#243; &#273;&#7847;u c&#243; &#273;u&#244;i.</p><p></p><p>C&#225;i l&#7895;i to nh&#7845;t, l&#7895;i m&#224; m&#236;nh th&#7845;y 9 tr&#234;n 10 dev m&#7855;c k&#7875; c&#7843; m&#236;nh, l&#224; nh&#7843;y th&#7859;ng v&#224;o ch&#7885;n c&#244;ng c&#7909; tr&#432;&#7899;c khi h&#7887;i xem m&#236;nh c&#243; c&#7847;n n&#243; kh&#244;ng. C&#226;u h&#7887;i &#273;&#250;ng n&#243; ph&#7843;i c&#243; ba t&#7847;ng, &#273;i theo th&#7913; t&#7921;. T&#7847;ng m&#7897;t, m&#236;nh c&#243; th&#7921;c s&#7921; c&#7847;n async kh&#244;ng. T&#7847;ng hai, n&#7871;u c&#7847;n async th&#7853;t th&#236; m&#236;nh c&#7847;n m&#7897;t c&#225;i queue hay c&#7847;n m&#7897;t c&#225;i log. T&#7847;ng ba, m&#7899;i t&#7899;i m&#7845;y chuy&#7879;n chi ti&#7871;t nh&#432; t&#7843;i, ordering, replay, fan-out. &#272;a s&#7889; ng&#432;&#7901;i nh&#7843;y ph&#7855;t v&#224;o t&#7847;ng hai, b&#7887; qua t&#7847;ng m&#7897;t, v&#224; &#273;&#243; l&#224; c&#225;i l&#7895;i ki&#7871;n tr&#250;c &#273;&#7855;t ti&#7873;n nh&#7845;t, m&#236;nh s&#7869; quay l&#7841;i chuy&#7879;n "&#273;&#7915;ng d&#249;ng queue" &#7903; cu&#7889;i b&#224;i v&#236; n&#243; hay l&#7855;m.</p><p></p><p>Gi&#7901; gi&#7843; s&#7917; m&#236;nh &#273;&#227; ch&#7855;c l&#224; c&#7847;n async r&#7891;i, th&#236; t&#7899;i c&#225;i ph&#7847;n ph&#226;n bi&#7879;t hai th&#7857;ng. M&#236;nh k&#7875; l&#7841;i nhanh c&#225;i mental model &#7903; b&#224;i tr&#432;&#7899;c v&#236; n&#243; l&#224; n&#7873;n c&#7911;a m&#7885;i th&#7913;. RabbitMQ n&#243; l&#224; ki&#7875;u broker th&#244;ng minh consumer ngu, message t&#7899;i tay consumer, x&#7917; l&#253; xong ack m&#7897;t ph&#225;t l&#224; n&#243; x&#243;a lu&#244;n, y nh&#432; cu&#7889;n s&#7893; to-do l&#224;m xong g&#7841;ch &#273;i. C&#242;n Kafka th&#236; broker ngu consumer th&#244;ng minh, message ghi v&#224;o m&#7897;t c&#225;i log ch&#7881; ghi th&#234;m kh&#244;ng x&#243;a, n&#243; n&#7857;m &#273;&#243; theo retention, consumer t&#7921; nh&#7899; m&#236;nh &#273;&#7885;c t&#7899;i &#273;&#226;u b&#7857;ng offset, nhi&#7873;u phe &#273;&#7885;c c&#249;ng m&#7897;t &#273;&#7889;ng m&#224; kh&#244;ng &#273;&#7909;ng nhau, th&#237;ch th&#236; tua l&#7841;i t&#7915; &#273;&#7847;u. Hai c&#225;i tri&#7871;t l&#253; ng&#432;&#7907;c nhau ho&#224;n to&#224;n ch&#7913; kh&#244;ng ph&#7843;i hai phi&#234;n b&#7843;n c&#7911;a c&#249;ng m&#7897;t th&#7913;.</p><p></p><p>&#272;&#7875; m&#236;nh n&#243;i c&#7909; th&#7875; RabbitMQ n&#243; t&#7887;a s&#225;ng l&#250;c n&#224;o, v&#236; c&#225;i n&#224;y quan tr&#7885;ng. B&#7843;n ch&#7845;t c&#7911;a n&#243; l&#224; message gi&#7889;ng nh&#432; m&#7897;t c&#225;i task c&#7847;n &#273;&#432;&#7907;c l&#224;m cho xong r&#7891;i bi&#7871;n m&#7845;t. N&#234;n n&#243; h&#7907;p nh&#7845;t v&#7899;i m&#7845;y vi&#7879;c ki&#7875;u job queue, g&#7917;i mail, resize &#7843;nh, xu&#7845;t file PDF, m&#7895;i message l&#224; m&#7897;t vi&#7879;c, l&#224;m xong l&#224; xong, ch&#7859;ng ai c&#7847;n &#273;&#7885;c l&#7841;i l&#224;m g&#236;. N&#243; c&#361;ng m&#7841;nh kinh kh&#7911;ng &#7903; kho&#7843;n routing, n&#243; c&#243; c&#225;i kh&#225;i ni&#7879;m exchange v&#7899;i &#273;&#7911; ki&#7875;u direct topic fanout, n&#234;n n&#7871;u nghi&#7879;p v&#7909; c&#7911;a b&#7841;n ki&#7875;u &#273;&#417;n VIP &#273;&#7849;y qua consumer n&#224;y, &#273;&#417;n th&#432;&#7901;ng &#273;&#7849;y qua consumer kia, &#273;&#417;n l&#7895;i qu&#259;ng v&#224;o dead-letter queue, th&#236; c&#225;i logic &#273;&#7883;nh tuy&#7871;n &#273;&#243; n&#7857;m s&#7861;n trong broker r&#7891;i, b&#7841;n kh&#244;ng ph&#7843;i t&#7921; code. R&#7891;i c&#225;i kho&#7843;n ack t&#7915;ng message v&#7899;i redelivery n&#243; c&#361;ng l&#224;m c&#7921;c m&#432;&#7907;t, consumer ch&#7871;t gi&#7919;a ch&#7915;ng ch&#432;a k&#7883;p ack h&#7843;, message t&#7921; quay l&#7841;i queue cho th&#7857;ng consumer kh&#225;c b&#7889;c, c&#7847;n retry c&#243; delay, c&#7847;n dead-letter, c&#7847;n TTL cho t&#7915;ng message, RabbitMQ lo h&#7871;t. V&#224; n&#243; h&#7907;p v&#7899;i ki&#7875;u nhi&#7873;u worker gi&#224;nh nhau r&#250;t t&#7915; m&#7897;t queue, ai r&#7843;nh th&#236; l&#7845;y, mu&#7889;n nhanh h&#417;n th&#236; &#273;&#7867; th&#234;m worker. Nh&#432;ng nh&#7899; c&#225;i &#273;i&#7875;m y&#7871;u c&#7889;t t&#7917;: message bi&#7871;n m&#7845;t sau khi ack, kh&#244;ng tua l&#7841;i &#273;&#432;&#7907;c, v&#224; v&#7873; c&#417; b&#7843;n l&#224; m&#7897;t phe &#259;n c&#225;i message &#273;&#243; th&#244;i.</p><p></p><p>Kafka th&#236; ng&#432;&#7907;c l&#7841;i ho&#224;n to&#224;n. Message v&#7899;i n&#243; kh&#244;ng ph&#7843;i l&#224; task, m&#224; l&#224; m&#7897;t s&#7921; ki&#7879;n &#273;&#227; x&#7843;y ra v&#224; l&#224; s&#7921; th&#7853;t v&#297;nh vi&#7877;n, ki&#7875;u OrderPlaced, PaymentCaptured. V&#224; c&#225;i si&#234;u n&#259;ng l&#7921;c th&#7853;t s&#7921; c&#7911;a Kafka, c&#225;i m&#224; m&#236;nh ngh&#297; nhi&#7873;u ng&#432;&#7901;i d&#249;ng Kafka m&#224; c&#242;n kh&#244;ng x&#224;i t&#7899;i, l&#224; fan-out. T&#432;&#7903;ng t&#432;&#7907;ng m&#7897;t c&#225;i topic orders, m&#224; c&#243; n&#259;m nh&#243;m consumer kh&#225;c nhau c&#249;ng &#273;&#7885;c, m&#7897;t th&#7857;ng l&#224;m analytics, m&#7897;t th&#7857;ng &#273;&#225;nh index search, m&#7897;t th&#7857;ng ch&#7841;y fraud, m&#7897;t th&#7857;ng b&#7855;n notification, m&#7897;t th&#7857;ng &#273;&#7893; v&#224;o data warehouse, m&#7895;i th&#7857;ng gi&#7919; offset ri&#234;ng c&#7911;a n&#243;, ch&#7859;ng &#273;&#7913;a n&#224;o &#273;&#7909;ng &#273;&#7913;a n&#224;o, ch&#7859;ng &#273;&#7913;a n&#224;o l&#224;m m&#7845;t ph&#7847;n c&#7911;a &#273;&#7913;a n&#224;o. V&#7899;i RabbitMQ th&#236; &#273;&#7875; l&#224;m &#273;&#432;&#7907;c v&#7853;y b&#7841;n ph&#7843;i nh&#226;n b&#7843;n c&#225;i message ra n&#259;m c&#225;i queue. R&#7891;i c&#225;i kho&#7843;n replay n&#7919;a, n&#243; nh&#432; c&#7895; m&#225;y th&#7901;i gian, service bug x&#7917; l&#253; sai ba ng&#224;y event h&#7843;, tua offset v&#7873; cho ch&#7841;y l&#7841;i, hay onboard m&#7897;t service m&#7899;i toanh c&#7847;n d&#7919; li&#7879;u l&#7883;ch s&#7917; h&#7843;, cho n&#243; &#273;&#7885;c topic t&#7915; &#273;&#7847;u, RabbitMQ ch&#7883;u ch&#7871;t kho&#7843;n n&#224;y. C&#7897;ng th&#234;m throughput th&#236; kh&#7911;ng khi&#7871;p, h&#224;ng tri&#7879;u message m&#7897;t gi&#226;y, v&#224; n&#243; &#273;&#7843;m b&#7843;o th&#7913; t&#7921; trong m&#7897;t partition, n&#234;n b&#7841;n key theo orderId l&#224; m&#7885;i event c&#7911;a c&#249;ng m&#7897;t &#273;&#417;n v&#224;o c&#249;ng m&#7897;t partition, &#273;&#250;ng th&#7913; t&#7921; cho &#273;&#417;n &#273;&#243; m&#224; v&#7851;n ch&#7841;y song song gi&#7919;a c&#225;c &#273;&#417;n kh&#225;c nhau. &#272;&#7893;i l&#7841;i th&#236; v&#7853;n h&#224;nh n&#243; n&#7863;ng &#273;&#244; h&#417;n, routing ph&#7913;c t&#7841;p th&#236; n&#243; kh&#244;ng c&#243;, m&#7885;i logic ph&#7843;i n&#7857;m &#7903; consumer.</p><p></p><p>M&#236;nh th&#237;ch c&#244; &#273;&#7885;ng c&#7843; c&#225;i m&#7899; tr&#234;n th&#224;nh &#273;&#250;ng m&#7897;t c&#226;u h&#7887;i &#273;&#7875; kh&#7887;i ph&#7843;i nh&#7899; d&#224;i d&#242;ng, v&#224; m&#236;nh ngh&#297; ae n&#234;n kh&#7855;c c&#225;i n&#224;y v&#244; &#273;&#7847;u: sau khi x&#7917; l&#253; xong, c&#225;i message n&#224;y n&#243; c&#242;n c&#7847;n t&#7891;n t&#7841;i cho ai kh&#225;c ho&#7863;c cho l&#7847;n sau kh&#244;ng. C&#242;n th&#236; Kafka, kh&#244;ng th&#236; RabbitMQ. C&#243; m&#7897;t c&#226;u h&#7887;i ph&#7909; &#273;i k&#232;m c&#361;ng hay, l&#224; c&#243; bao nhi&#234;u phe &#273;&#7897;c l&#7853;p c&#7847;n &#273;&#7885;c c&#249;ng m&#7897;t d&#7919; li&#7879;u n&#224;y. M&#7897;t phe th&#236; RabbitMQ v&#244; t&#432;, nhi&#7873;u phe m&#7895;i phe m&#7897;t nh&#7883;p &#273;&#7897; th&#7881;nh tho&#7843;ng c&#7847;n tua l&#7841;i th&#236; Kafka.</p><p></p><p>Gi&#7901; t&#7899;i c&#225;i ph&#7847;n m&#224; m&#236;nh ngh&#297; l&#224; hay nh&#7845;t c&#7911;a c&#7843; b&#224;i, c&#225;i ph&#7847;n m&#224; &#237;t ai d&#7841;y nh&#432;ng n&#243; m&#7899;i l&#224; d&#7845;u hi&#7879;u c&#7911;a m&#7897;t ng&#432;&#7901;i l&#224;m ki&#7871;n tr&#250;c th&#7853;t ch&#7913; kh&#244;ng ph&#7843;i ng&#432;&#7901;i th&#237;ch ngh&#7883;ch &#273;&#7891; ch&#417;i m&#7899;i: khi n&#224;o th&#236; &#273;&#7915;ng c&#243; d&#249;ng queue cho m&#7879;t. &#272;&#7915;ng nh&#233;t queue v&#224;o khi b&#7841;n c&#7847;n c&#226;u tr&#7843; l&#7901;i ngay, ki&#7875;u user b&#7845;m n&#250;t ph&#7843;i th&#7845;y k&#7871;t qu&#7843; li&#7873;n, check s&#7889; d&#432;, validate c&#225;i form, query c&#225;i gi&#225;, m&#7845;y c&#225;i &#273;&#243; nh&#233;t queue v&#224;o l&#224; t&#7921; &#273;&#7867; ra c&#7843; &#273;&#7889;ng ph&#7913;c t&#7841;p correlation id v&#7899;i callback cho m&#7897;t th&#7913; v&#7889;n d&#297; ch&#7881; c&#7847;n m&#7897;t c&#250; HTTP call, async kh&#244;ng l&#224;m n&#243; nhanh h&#417;n &#273;&#226;u n&#243; ch&#7881; l&#224;m n&#243; kh&#243; h&#417;n th&#244;i. &#272;&#7915;ng d&#249;ng khi b&#7841;n c&#7847;n consistency m&#7841;nh &#273;&#7885;c xong ghi l&#224; ph&#7843;i th&#7845;y ngay, v&#236; queue n&#243; cho b&#7841;n eventual consistency, c&#225;i kho&#7843;ng tr&#7877; "&#273;&#227; ghi m&#224; ch&#432;a th&#7845;y" &#273;&#243; nhi&#7873;u nghi&#7879;p v&#7909; kh&#244;ng ch&#7883;u n&#7893;i. &#272;&#7915;ng d&#249;ng khi t&#7843;i nh&#7887; v&#224; &#273;&#7873;u kh&#244;ng c&#243; spike g&#236;, v&#236; queue sinh ra &#273;&#7875; san t&#7843;i v&#7899;i g&#7905; d&#237;nh, kh&#244;ng c&#243; spike &#273;&#7875; san kh&#244;ng c&#243; g&#236; &#273;&#7875; g&#7905; th&#236; b&#7841;n &#273;ang tr&#7843; c&#225;i gi&#225; v&#7853;n h&#224;nh nu&#244;i th&#234;m m&#7897;t h&#7879; ph&#226;n t&#225;n &#273;&#7875; &#273;&#7893;i l&#7845;y m&#7897;t l&#7907;i &#237;ch b&#7857;ng kh&#244;ng. &#272;&#7915;ng d&#249;ng khi hai service &#273;&#7857;ng n&#224;o c&#361;ng ph&#7843;i s&#7889;ng c&#249;ng nhau, ki&#7875;u A g&#7885;i B m&#224; B ch&#7871;t th&#236; A c&#361;ng v&#244; ngh&#297;a lu&#244;n, th&#236; c&#225;i s&#7921; g&#7905; d&#237;nh n&#243; l&#224; &#7843;o, c&#7913; g&#7885;i th&#7859;ng cho &#273;&#417;n gi&#7843;n.</p><p></p><p>V&#224; &#273;&#226;y l&#224; hai c&#225;i m&#236;nh t&#226;m &#273;&#7855;c nh&#7845;t trong c&#225;i m&#7909;c "&#273;&#7915;ng d&#249;ng queue". Th&#7913; nh&#7845;t, r&#7845;t nhi&#7873;u khi m&#7897;t c&#225;i b&#7843;ng trong database l&#224; qu&#225; &#273;&#7911;. Volume v&#7915;a ph&#7843;i th&#244;i th&#236; m&#7897;t c&#225;i b&#7843;ng jobs v&#7899;i c&#226;u SELECT FOR UPDATE SKIP LOCKED n&#243; l&#224;m queue ngon l&#224;nh, m&#224; l&#7841;i transactional chung v&#7899;i business data lu&#244;n, d&#7877; debug, kh&#244;ng th&#234;m m&#7897;t c&#7909;c h&#7841; t&#7847;ng n&#224;o. &#272;&#7915;ng c&#243; k&#233;o nguy&#234;n con Kafka v&#7873; ch&#7881; &#273;&#7875; x&#7917; l&#253; m&#7897;t ngh&#236;n job m&#7897;t ng&#224;y, n&#243; nh&#432; l&#7845;y dao m&#7893; tr&#226;u gi&#7871;t g&#224; v&#7853;y. Th&#7913; hai, v&#224; c&#225;i n&#224;y n&#7889;i th&#7859;ng v&#224;o ch&#7911; &#273;&#7873; b&#224;i tr&#432;&#7899;c n&#234;n m&#236;nh m&#234;: nhi&#7873;u khi v&#7845;n &#273;&#7873; th&#7853;t c&#7911;a b&#7841;n kh&#244;ng ph&#7843;i l&#224; messaging m&#224; l&#224; dual-write. R&#7845;t nhi&#7873;u ng&#432;&#7901;i nh&#233;t Kafka v&#224;o &#273;&#7875; "ghi DB xong r&#7891;i publish event", r&#7891;i d&#237;nh &#273;&#250;ng c&#225;i khe n&#7913;t m&#224; m&#236;nh k&#7875; b&#224;i tr&#432;&#7899;c, ghi DB xong, publish event th&#236; crash, th&#7871; l&#224; DB c&#243; m&#224; event m&#7845;t, h&#7879; th&#7889;ng l&#7879;ch nhau. Queue kh&#244;ng c&#7913;u &#273;&#432;&#7907;c c&#225;i n&#224;y &#273;&#226;u. Th&#7913; c&#7913;u &#273;&#432;&#7907;c n&#243; l&#224; Transactional Outbox, t&#7913;c l&#224; b&#7841;n ghi business data v&#7899;i c&#225;i event v&#224;o chung m&#7897;t transaction c&#7911;a database, event n&#7857;m trong m&#7897;t c&#225;i b&#7843;ng outbox, r&#7891;i m&#7897;t th&#7857;ng relay &#273;&#7885;c c&#225;i b&#7843;ng outbox &#273;&#243; &#273;&#7849;y sang broker. C&#225;i kho&#7843;nh kh&#7855;c b&#7841;n nh&#7853;n ra "&#224; h&#243;a ra m&#236;nh c&#7847;n outbox ch&#7913; kh&#244;ng ph&#7843;i c&#7847;n &#273;&#7893;i broker" l&#224; m&#7897;t b&#432;&#7899;c tr&#432;&#7903;ng th&#224;nh th&#7853;t s&#7921; &#273;&#243;.</p><p></p><p>R&#7891;i, gi&#7901; m&#7899;i t&#7899;i ph&#7847;n tr&#7843; l&#7901;i c&#225;i c&#226;u h&#7887;i g&#7889;c, c&#225;i c&#226;u m&#224; m&#236;nh n&#7907; ae t&#7915; b&#224;i tr&#432;&#7899;c. Order processing th&#236; n&#234;n Kafka hay RabbitMQ. V&#224; c&#226;u tr&#7843; l&#7901;i c&#7911;a m&#7897;t ng&#432;&#7901;i l&#224;m ki&#7871;n tr&#250;c th&#7853;t n&#243; l&#224; th&#7871; n&#224;y: kh&#244;ng ph&#7843;i m&#7897;t c&#225;i, v&#224; c&#361;ng kh&#244;ng ph&#7843;i v&#236; c&#225;i l&#253; do m&#224; m&#236;nh t&#432;&#7903;ng h&#7891;i &#273;&#7847;u. Nh&#7899; kh&#244;ng, h&#7891;i &#273;&#7847;u m&#236;nh ch&#7885;n Kafka v&#7899;i l&#253; do message c&#242;n l&#432;u n&#234;n rollback d&#7877;, m&#224; c&#225;i &#273;&#243; sai c&#7843; v&#7873; Kafka l&#7851;n sai v&#7873; rollback lu&#244;n. Gi&#7901; v&#7899;i c&#225;i &#273;&#7847;u s&#225;ng h&#417;n m&#7897;t t&#237; th&#236; m&#236;nh th&#7845;y n&#243; ph&#7843;i t&#225;ch l&#224;m hai ph&#7847;n.</p><p></p><p>C&#225;i ph&#7847;n l&#245;i c&#7911;a saga, &#273;&#7863;t h&#224;ng r&#7891;i gi&#7919; kho r&#7891;i tr&#7915; ti&#7873;n r&#7891;i giao v&#7853;n, c&#225;i &#273;&#225;m &#273;&#243; l&#224; m&#7879;nh l&#7879;nh ch&#7913; kh&#244;ng ph&#7843;i s&#7921; ki&#7879;n, v&#224; n&#243; n&#234;n &#273;i b&#7857;ng RabbitMQ, ho&#7863;c &#273;&#250;ng h&#417;n l&#224; &#273;i b&#7857;ng command th&#244;ng qua m&#7897;t c&#225;i orchestrator b&#7873;n b&#7881; ki&#7875;u Temporal hay Step Functions. T&#7841;i sao l&#7841;i RabbitMQ. V&#236; m&#7895;i c&#225;i l&#7879;nh CapturePayment n&#243; ch&#7881; c&#243; ngh&#297;a v&#7899;i &#273;&#250;ng m&#7897;t ng&#432;&#7901;i nh&#7853;n, l&#224;m &#273;&#250;ng m&#7897;t vi&#7879;c, l&#224;m xong l&#224; h&#7871;t gi&#225; tr&#7883;, ch&#7859;ng phe th&#7913; hai n&#224;o quan t&#226;m, v&#224; n&#243; c&#7847;n c&#225;i c&#417; ch&#7871; ack v&#7899;i retry v&#7899;i dead-letter tinh vi, &#273;&#243; &#273;&#250;ng l&#224; s&#226;n nh&#224; c&#7911;a RabbitMQ. C&#242;n c&#225;i si&#234;u n&#259;ng l&#7921;c c&#7911;a Kafka l&#224; fan-out v&#7899;i replay th&#236; &#7903; &#273;&#226;y v&#244; d&#7909;ng ho&#224;n to&#224;n, d&#249;ng Kafka cho m&#7897;t c&#225;i l&#7879;nh ch&#7881; ti&#234;u th&#7909; &#273;&#250;ng m&#7897;t l&#7847;n th&#236; nh&#432; mua nguy&#234;n c&#225;i xe t&#7843;i &#273;&#7875; ch&#7903; m&#7897;t th&#249;ng s&#7919;a v&#7853;y.</p><p></p><p>C&#242;n c&#225;i ph&#7847;n ph&#225;t t&#225;n ra th&#7871; gi&#7899;i th&#236; kh&#225;c. Khi &#273;&#417;n &#273;&#227; ch&#7889;t xong xu&#244;i, b&#7841;n ph&#225;t m&#7897;t c&#225;i event OrderCompleted l&#234;n Kafka, &#273;&#7875; cho analytics, search index, recommendation, data warehouse, email marketing, &#273;&#7911; th&#7913; phe, m&#7895;i phe t&#7921; &#273;&#7885;c theo nh&#7883;p c&#7911;a n&#243;, tua l&#7841;i &#273;&#432;&#7907;c khi c&#7847;n. C&#225;i n&#224;y m&#7899;i &#273;&#250;ng t&#7841;ng Kafka. V&#224; &#273;&#7875; c&#225;i event &#273;&#243; kh&#244;ng bao gi&#7901; l&#7879;ch v&#7899;i database th&#236; l&#7841;i quay v&#7873; Outbox ch&#7913; &#273;&#7915;ng c&#243; tin v&#224;o c&#225;i ki&#7875;u "publish sau khi commit".</p><p></p><p>Th&#7845;y ch&#432;a, ki&#7871;n tr&#250;c th&#7853;t n&#243; l&#224; hybrid, RabbitMQ v&#7899;i orchestrator lo c&#225;i ph&#7847;n ra l&#7879;nh v&#224; &#273;i&#7873;u khi&#7875;n c&#7911;a saga, Kafka lo c&#225;i ph&#7847;n ph&#225;t t&#225;n event ra ngo&#7841;i vi, outbox &#273;&#7875; n&#7889;i database v&#7899;i broker m&#7897;t c&#225;ch an to&#224;n. C&#225;i c&#226;u "Kafka hay RabbitMQ" n&#243; sai v&#236; n&#243; l&#224; c&#226;u h&#7887;i "ho&#7863;c", trong khi s&#7921; th&#7853;t l&#224; "v&#224;", hai th&#7857;ng l&#224;m hai vai kh&#225;c nhau trong c&#249;ng m&#7897;t h&#7879; th&#7889;ng.</p><p></p><p>&#192; m&#224; c&#243; m&#7897;t c&#225;i b&#7851;y t&#432; duy nho nh&#7887; &#7903; &#273;&#226;y m&#236;nh mu&#7889;n k&#7875;, v&#236; h&#7891;i m&#236;nh t&#7921; tr&#7843; l&#7901;i c&#226;u n&#224;y m&#236;nh su&#253;t v&#7845;p. Khi gi&#7843;i th&#237;ch t&#7841;i sao saga l&#245;i d&#249;ng RabbitMQ ch&#7913; kh&#244;ng Kafka, m&#236;nh &#273;&#227; &#273;&#7883;nh n&#243;i "v&#236; saga kh&#244;ng c&#7847;n ordering n&#234;n kh&#244;ng c&#7847;n Kafka". Nghe th&#236; xu&#244;i tai m&#224; sai b&#233;t. Saga c&#7921;c k&#7923; c&#7847;n &#273;&#250;ng th&#7913; t&#7921; ch&#7913;, gi&#7919; kho r&#7891;i m&#7899;i tr&#7915; ti&#7873;n r&#7891;i m&#7899;i giao h&#224;ng, &#273;&#7843;o l&#7897;n l&#224; ch&#7871;t li&#7873;n, th&#7913; t&#7921; l&#224; chuy&#7879;n s&#7889;ng c&#242;n. C&#225;i &#273;&#250;ng ph&#7843;i l&#224;, th&#7913; t&#7921; &#7903; &#273;&#226;y do th&#7857;ng orchestrator n&#243; &#273;&#7843;m b&#7843;o b&#7857;ng c&#225;i m&#225;y tr&#7841;ng th&#225;i c&#7911;a n&#243;, ch&#7913; kh&#244;ng ph&#7843;i do broker &#273;&#7843;m b&#7843;o. Kafka n&#243; cho b&#7841;n ordering &#7903; t&#7847;ng h&#7841; t&#7847;ng, c&#225;i &#273;&#243; h&#7919;u &#237;ch khi b&#7841;n c&#7847;n m&#7897;t d&#242;ng event t&#7921; n&#243; &#273;&#227; &#273;&#250;ng th&#7913; t&#7921; cho nhi&#7873;u phe &#273;&#7885;c l&#7841;i. Nh&#432;ng trong saga, c&#225;i quy&#7871;t &#273;&#7883;nh b&#432;&#7899;c n&#224;o ti&#7871;p theo l&#224; c&#225;i state machine v&#7899;i c&#225;i state trong database, ch&#7913; kh&#244;ng ph&#7843;i v&#7883; tr&#237; c&#7911;a message trong c&#225;i log. Th&#7913; t&#7921; n&#243; &#273;&#432;&#7907;c sinh ra t&#7915; logic, ch&#7913; kh&#244;ng ph&#7843;i &#273;&#432;&#7907;c th&#7915;a k&#7871; t&#7915; broker. N&#234;n Kafka mang c&#225;i ordering c&#7911;a n&#243; t&#7899;i &#273;&#226;y l&#224; th&#7915;a, ch&#7913; kh&#244;ng ph&#7843;i l&#224; "kh&#244;ng c&#7847;n th&#7913; t&#7921;". C&#225;i s&#7921; ph&#226;n bi&#7879;t nh&#7887; x&#237;u n&#224;y m&#224; n&#243;i tr&#7853;t trong m&#7897;t bu&#7893;i design review l&#224; l&#242;i ra ngay m&#236;nh ch&#432;a n&#7855;m ch&#7855;c &#273;&#243;.</p><p></p><p>Th&#244;i m&#236;nh g&#243;i l&#7841;i b&#7857;ng m&#7897;t b&#7897; lu&#7853;t b&#7887; t&#250;i, ae mang &#273;i ph&#7887;ng v&#7845;n hay &#273;i design review x&#224;i &#273;&#432;&#7907;c lu&#244;n. C&#7847;n tr&#7843; l&#7901;i ngay, c&#7847;n consistency m&#7841;nh, hay t&#7843;i nh&#7887; th&#236; &#273;&#7915;ng c&#243; queue, g&#7885;i th&#7859;ng ho&#7863;c x&#224;i m&#7897;t c&#225;i b&#7843;ng database. Task l&#224;m xong l&#224; xong, routing ph&#7913;c t&#7841;p, nhi&#7873;u worker gi&#224;nh nhau, m&#7897;t phe &#273;&#7885;c th&#236; RabbitMQ. S&#7921; ki&#7879;n l&#224; s&#7921; th&#7853;t l&#226;u d&#224;i, nhi&#7873;u phe &#273;&#7885;c &#273;&#7897;c l&#7853;p, c&#7847;n tua l&#7841;i, throughput kh&#7911;ng, th&#7913; t&#7921; theo key th&#236; Kafka. Saga nhi&#7873;u b&#432;&#7899;c c&#7847;n ki&#7875;m so&#225;t v&#224; recovery th&#236; m&#7897;t c&#225;i orchestrator b&#7873;n b&#7881; c&#242;n h&#417;n t&#7921; &#244;m broker. Ph&#225;t event sau khi ghi database th&#236; d&#249;ng Outbox, &#273;&#7915;ng dual-write. V&#224; h&#7879; th&#7853;t th&#236; th&#432;&#7901;ng tr&#7897;n m&#7845;y c&#225;i tr&#234;n l&#7841;i theo t&#7915;ng vai tr&#242; ch&#7913; hi&#7871;m khi thu&#7847;n m&#7897;t lo&#7841;i.</p><p></p><p>Ng&#7891;i nh&#236;n l&#7841;i c&#7843; c&#225;i series n&#224;y th&#236; h&#417;i bu&#7891;n c&#432;&#7901;i, m&#236;nh kh&#7903;i &#273;&#7847;u b&#7857;ng c&#225;i &#253; &#273;&#7883;nh ch&#7885;n Kafka cho d&#7877; rollback, m&#7897;t c&#226;u m&#224; gi&#7901; nh&#236;n l&#7841;i th&#7845;y sai t&#7915; ch&#226;n t&#417; k&#7869; t&#243;c, sai v&#7873; Kafka, sai v&#7873; rollback, sai lu&#244;n c&#7843; v&#7873; c&#225;ch &#273;&#7863;t c&#226;u h&#7887;i. M&#224; cu&#7889;i c&#249;ng c&#225;i m&#236;nh mang v&#7873; kh&#244;ng ph&#7843;i l&#224; &#273;&#225;p &#225;n "ch&#7885;n th&#7857;ng n&#224;o", m&#224; l&#224; kh&#7843; n&#259;ng ph&#226;n bi&#7879;t &#273;&#432;&#7907;c l&#7879;nh v&#7899;i s&#7921; ki&#7879;n, queue v&#7899;i log, th&#7913; t&#7921; do broker v&#7899;i th&#7913; t&#7921; do orchestrator, v&#224; quan tr&#7885;ng nh&#7845;t l&#224; bi&#7871;t l&#250;c n&#224;o th&#236; &#273;&#7915;ng &#273;&#7909;ng v&#224;o queue. M&#236;nh ngh&#297; &#273;&#243; m&#7899;i &#273;&#250;ng l&#224; kh&#225;c bi&#7879;t gi&#7919;a "bi&#7871;t Kafka l&#224; c&#225;i g&#236;" v&#7899;i "bi&#7871;t khi n&#224;o v&#224; t&#7841;i sao n&#234;n d&#249;ng n&#243;". V&#224; h&#236;nh nh&#432; c&#225;i th&#7913; hai m&#7899;i l&#224; th&#7913; ng&#432;&#7901;i ta tr&#7843; ti&#7873;n cho m&#7897;t &#244;ng architect ;)) ch&#7913; c&#225;i th&#7913; nh&#7845;t th&#236; Google ba gi&#226;y ra m&#224;.</p><p></p><p>H&#7865;n ae b&#224;i sau, ch&#7855;c m&#236;nh s&#7869; &#273;&#224;o v&#224;o c&#225;i v&#7909; exactly-once n&#243; l&#224; huy&#7873;n tho&#7841;i hay c&#243; th&#7853;t, ho&#7863;c c&#225;i Outbox v&#7899;i Debezium l&#224;m tay ch&#226;n th&#7871; n&#224;o, t&#7841;i m&#236;nh th&#7845;y n&#7907; ae c&#225;i m&#243;n outbox n&#224;y nh&#7855;c ho&#224;i m&#224; ch&#432;a m&#7893;. Ai h&#243;ng c&#225;i n&#224;o th&#236; comment cho m&#236;nh bi&#7871;t nha.</p>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: kafka hay rabbitmq?]]></title><description><![CDATA[M&#7845;y h&#244;m nay m&#236;nh ng&#7891;i &#273;&#224;o l&#7841;i m&#7897;t c&#226;u h&#7887;i t&#432;&#7903;ng d&#7877; &#7907;t m&#224; cu&#7889;i c&#249;ng l&#242;i ra c&#7843; m&#7897;t m&#7899; ki&#7871;n th&#7913;c l&#224;m m&#236;nh ph&#7843;i ng&#7891;i th&#7859;ng l&#432;ng d&#7853;y.]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-kafka-hay-rabitmq</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-kafka-hay-rabitmq</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Mon, 22 Jun 2026 14:32:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>M&#7845;y h&#244;m nay m&#236;nh ng&#7891;i &#273;&#224;o l&#7841;i m&#7897;t c&#226;u h&#7887;i t&#432;&#7903;ng d&#7877; &#7907;t m&#224; cu&#7889;i c&#249;ng l&#242;i ra c&#7843; m&#7897;t m&#7899; ki&#7871;n th&#7913;c l&#224;m m&#236;nh ph&#7843;i ng&#7891;i th&#7859;ng l&#432;ng d&#7853;y. C&#226;u h&#7887;i l&#224; th&#7871; n&#224;y: l&#224;m h&#7879; th&#7889;ng x&#7917; l&#253; &#273;&#417;n h&#224;ng e-commerce, ki&#7875;u &#273;&#7863;t h&#224;ng r&#7891;i tr&#7915; kho r&#7891;i thanh to&#225;n r&#7891;i giao v&#7853;n &#273;&#243;, th&#236; n&#234;n d&#249;ng Kafka hay RabbitMQ?</p><p></p><p>Nghe th&#236; c&#7913; t&#432;&#7903;ng Google 5 ph&#250;t l&#224; xong, ai d&#232; &#273;&#224;o t&#7899;i &#273;&#225;y th&#236; m&#7899;i th&#7845;y c&#225;i c&#226;u tr&#7843; l&#7901;i "Kafka hay RabbitMQ" n&#243; ch&#7859;ng quan tr&#7885;ng b&#7857;ng &#273;&#7889;ng th&#7913; m&#236;nh v&#7905; ra tr&#234;n &#273;&#432;&#7901;ng &#273;i. M&#224; th&#7853;t ra cho t&#7899;i gi&#7901; m&#236;nh v&#7851;n ch&#432;a tr&#7843; l&#7901;i c&#225;i c&#226;u g&#7889;c &#273;&#243; lu&#244;n ;)) &#273;&#7875; d&#224;nh b&#224;i sau. B&#224;i n&#224;y m&#236;nh k&#7875; c&#225;i h&#224;nh tr&#236;nh th&#244;i, v&#236; n&#243; m&#7899;i l&#224; ph&#7847;n hay.</p><p></p><p>&#272;&#7847;u ti&#234;n ph&#7843;i n&#243;i lu&#244;n c&#225;i n&#224;y, v&#236; h&#7891;i x&#432;a m&#236;nh to&#224;n b&#7883; ng&#432;&#7907;c: &#273;&#7915;ng ch&#7885;n c&#244;ng c&#7909; tr&#432;&#7899;c khi hi&#7875;u v&#7845;n &#273;&#7873;. C&#225;i queue n&#243; sinh ra &#273;&#7875; gi&#7843;i quy&#7871;t g&#236;? T&#432;&#7903;ng t&#432;&#7907;ng c&#225;i API &#273;&#259;ng k&#253; user c&#7911;a b&#7841;n l&#224;m tu&#7847;n t&#7921; &#273;&#7891;ng b&#7897; h&#7871;t: l&#432;u DB, g&#7917;i mail welcome, t&#7841;o profile, sync sang CRM, &#273;&#7849;y event analytics. User b&#7845;m m&#7897;t ph&#225;t ph&#7843;i ng&#7891;i &#273;&#7907;i c&#7843; 5 b&#432;&#7899;c ch&#7841;y xong. G&#7917;i mail lag 800ms, CRM lag 2 gi&#226;y, th&#7871; l&#224; user ng&#7891;i &#273;&#7907;i 3 gi&#226;y ch&#7881; &#273;&#7875; &#273;&#259;ng k&#253;, trong khi th&#7853;t ra vi&#7879;c &#273;&#259;ng k&#253; n&#243; &#273;&#227; xong ngay l&#250;c l&#432;u DB r&#7891;i. T&#7879; h&#417;n l&#224; khi CRM n&#243; s&#7853;p th&#236; c&#7843; c&#225;i API &#273;&#259;ng k&#253; s&#7853;p theo lu&#244;n, v&#244; l&#253; &#273;&#250;ng kh&#244;ng, &#273;&#259;ng k&#253; v&#7899;i sync CRM th&#236; li&#234;n quan g&#236; nhau m&#224; ch&#7871;t ch&#249;m.</p><p></p><p>Queue sinh ra &#273;&#7875; g&#7905; &#273;&#250;ng m&#7845;y c&#225;i &#273;&#243;. N&#243; t&#225;ch c&#225;i producer ra kh&#7887;i consumer, n&#243; h&#7845;p th&#7909; &#273;&#432;&#7907;c spike (ki&#7875;u flash sale 10 ngh&#236;n &#273;&#417;n m&#7897;t gi&#226;y m&#224; DB ghi n&#7893;i c&#243; 1 ngh&#236;n, th&#236; queue l&#224;m c&#225;i h&#7891; ch&#7913;a), consumer ch&#7871;t th&#236; message n&#7857;m ch&#7901; ch&#7913; kh&#244;ng m&#7845;t, v&#224; mu&#7889;n nhanh th&#236; b&#417;m th&#234;m nhi&#7873;u consumer chia t&#7843;i. Nh&#432;ng c&#243; m&#7897;t c&#225;i m&#236;nh mu&#7889;n nh&#7845;n m&#7841;nh v&#236; h&#7891;i &#273;&#7847;u m&#236;nh hi&#7875;u sai: queue n&#243; kh&#244;ng l&#224;m t&#7893;ng c&#244;ng vi&#7879;c c&#7911;a b&#7841;n nhanh h&#417;n &#273;&#226;u. N&#243; ch&#7881; d&#7901;i th&#7901;i &#273;i&#7875;m x&#7917; l&#253; l&#7841;i v&#224; t&#225;ch c&#225;i nh&#7883;p &#273;&#7897; ra th&#244;i. B&#7841;n &#273;ang &#273;&#225;nh &#273;&#7893;i s&#7921; &#273;&#417;n gi&#7843;n v&#224; t&#237;nh t&#7913;c th&#7901;i &#273;&#7875; l&#7845;y kh&#7843; n&#259;ng ch&#7883;u t&#7843;i v&#7899;i ch&#7889;ng l&#7895;i. N&#7871;u b&#7841;n kh&#244;ng th&#7921;c s&#7921; c&#7847;n m&#7845;y c&#225;i &#273;&#243; th&#236; nh&#233;t queue v&#224;o ch&#7881; t&#7893; l&#224;m h&#7879; th&#7889;ng ph&#7913;c t&#7841;p th&#234;m cho vui.</p><p></p><p>R&#7891;i t&#7899;i c&#225;i mental model m&#224; m&#236;nh ngh&#297; l&#224; quan tr&#7885;ng nh&#7845;t, c&#225;i n&#224;y 90% dev hi&#7875;u l&#7847;m, k&#7875; c&#7843; m&#236;nh tr&#432;&#7899;c &#273;&#226;y: Kafka v&#7899;i RabbitMQ kh&#244;ng ph&#7843;i c&#249;ng m&#7897;t lo&#7841;i, n&#243; l&#224; hai tri&#7871;t l&#253; ng&#432;&#7907;c nhau ho&#224;n to&#224;n. RabbitMQ ki&#7875;u broker th&#244;ng minh consumer ngu, message &#273;&#432;&#7907;c &#273;&#7849;y t&#7899;i consumer, x&#7917; l&#253; xong th&#236; ack m&#7897;t c&#225;i l&#224; message b&#7883; x&#243;a kh&#7887;i queue lu&#244;n, y nh&#432; c&#225;i to-do list, l&#224;m xong g&#7841;ch &#273;i. C&#242;n Kafka th&#236; ng&#432;&#7907;c, broker ngu consumer th&#244;ng minh, message ghi v&#224;o m&#7897;t c&#225;i log ch&#7881;-ghi-th&#234;m v&#224; kh&#244;ng b&#7883; x&#243;a sau khi &#273;&#7885;c, n&#243; n&#7857;m &#273;&#243; theo retention ki&#7875;u 7 ng&#224;y, consumer t&#7921; &#273;&#7885;c t&#7921; nh&#7899; m&#236;nh &#273;&#7885;c t&#7899;i &#273;&#226;u b&#7857;ng c&#225;i offset, nhi&#7873;u nh&#243;m consumer kh&#225;c nhau &#273;&#7885;c c&#249;ng m&#7897;t &#273;&#7889;ng d&#7919; li&#7879;u m&#224; ch&#7859;ng &#273;&#7909;ng nhau, th&#237;ch th&#236; tua l&#7841;i &#273;&#7885;c t&#7915; &#273;&#7847;u. C&#243; m&#7897;t c&#226;u h&#7887;i m&#224; h&#7887;i xong l&#224; ph&#226;n bi&#7879;t &#273;&#432;&#7907;c ngay hai th&#7857;ng: "sau khi message x&#7917; l&#253; xong n&#243; c&#242;n t&#7891;n t&#7841;i kh&#244;ng?". RabbitMQ th&#236; kh&#244;ng, Kafka th&#236; c&#242;n. Ch&#7881; m&#7897;t &#273;i&#7875;m &#273;&#243; th&#244;i m&#224; n&#243; quy&#7871;t &#273;&#7883;nh g&#7847;n nh&#432; m&#7885;i th&#7913; ph&#237;a sau.</p><p></p><p>T&#7899;i &#273;&#226;y th&#236; c&#243; c&#250; s&#7889;c &#273;&#7847;u ti&#234;n, v&#224; m&#236;nh b&#7883; d&#237;nh ch&#7845;u th&#7853;t s&#7921;. Khi &#273;&#432;&#7907;c h&#7887;i ch&#7885;n g&#236; cho c&#225;i order kia, m&#236;nh ch&#7885;n Kafka, v&#7899;i c&#225;i l&#253; do nghe r&#7845;t h&#7907;p l&#253; trong &#273;&#7847;u: "v&#236; message c&#242;n l&#432;u l&#7841;i n&#234;n rollback d&#7877; h&#417;n". Sai. Sai b&#233;t nh&#232; lu&#244;n. M&#224; s&#7917;a &#273;&#432;&#7907;c c&#225;i n&#224;y l&#224; m&#7897;t b&#432;&#7899;c nh&#7843;y v&#7873; t&#432; duy ch&#7913; kh&#244;ng &#273;&#249;a.</p><p></p><p>&#272;&#226;y nha, gi&#7843; s&#7917; tr&#7915; kho ok, thanh to&#225;n ok, t&#7899;i giao v&#7853;n th&#236; fail. Gi&#7901; b&#7841;n mu&#7889;n rollback. C&#225;i message order created v&#7851;n n&#7857;m ch&#236;nh &#236;nh trong Kafka &#273;&#243;, n&#243; c&#243; gi&#250;p b&#7841;n ho&#224;n l&#7841;i s&#7889; kho &#273;&#227; tr&#7915; v&#7899;i s&#7889; ti&#7873;n &#273;&#227; charge kh&#244;ng? Kh&#244;ng h&#7873;. V&#236; tr&#7915; kho v&#7899;i charge ti&#7873;n l&#224; c&#225;i side effect &#273;&#227; x&#7843;y ra r&#7891;i, &#7903; h&#7879; th&#7889;ng kh&#225;c r&#7891;i, c&#225;i log c&#7911;a Kafka n&#243; ch&#7881; l&#224; cu&#7889;n s&#7893; ghi l&#7841;i "&#224; vi&#7879;c n&#224;y t&#7915;ng x&#7843;y ra" th&#244;i ch&#7913; n&#243; c&#243; quy&#7873;n l&#7921;c g&#236; m&#224; chui v&#244; DB kho c&#7897;ng l&#7841;i s&#7889; l&#432;&#7907;ng hay g&#7885;i sang Stripe &#273;&#242;i ti&#7873;n v&#7873;. M&#224; b&#7841;n &#273;&#7885;c l&#7841;i c&#225;i message &#273;&#243; (c&#225;i n&#224;y g&#7885;i l&#224; replay) th&#236; n&#243; ch&#7881; l&#224;m quy tr&#236;nh ch&#7841;y l&#7841;i t&#7915; &#273;&#7847;u, t&#7913;c l&#224; tr&#7915; kho th&#234;m l&#7847;n n&#7919;a, charge th&#234;m l&#7847;n n&#7919;a. Th&#7843;m h&#7885;a lu&#244;n ch&#7913; rollback g&#236;.</p><p></p><p>C&#225;i n&#224;y m&#236;nh ghi l&#7841;i &#273;&#7875; kh&#7887;i qu&#234;n: replay kh&#225;c rollback. Replay l&#224; ph&#225;t l&#7841;i s&#7921; ki&#7879;n &#273;&#7875; &#273;i ti&#7871;p v&#7873; ph&#237;a tr&#432;&#7899;c. Rollback l&#224; ho&#224;n t&#225;c c&#225;i &#273;&#227; x&#7843;y ra. Log n&#243; cho b&#7841;n replay ch&#7913; n&#243; kh&#244;ng cho b&#7841;n rollback. V&#224; s&#7921; th&#7853;t ph&#361; ph&#224;ng l&#224; trong h&#7879; ph&#226;n t&#225;n b&#7841;n kh&#244;ng th&#7875; rollback m&#7897;t transaction tr&#7843;i qua nhi&#7873;u service &#273;&#432;&#7907;c. Kh&#244;ng c&#243; c&#225;i BEGIN TRANSACTION ROLLBACK n&#224;o &#244;m n&#7893;i c&#7843; DB kho l&#7851;n c&#7893;ng thanh to&#225;n l&#7851;n service giao v&#7853;n &#273;&#226;u.</p><p></p><p>V&#7853;y ng&#432;&#7901;i ta l&#224;m sao? Ng&#432;&#7901;i ta d&#249;ng Saga, v&#224; c&#225;i &#253; t&#432;&#7903;ng c&#7911;a n&#243; l&#224;m m&#236;nh th&#7845;y h&#417;i ng&#432;&#7907;c &#273;&#7901;i l&#250;c &#273;&#7847;u: ta kh&#244;ng undo, ta &#273;i ti&#7871;p v&#7873; ph&#237;a tr&#432;&#7899;c b&#7857;ng m&#7897;t h&#224;nh &#273;&#7897;ng b&#249; tr&#7915;, m&#224; b&#7843;n th&#226;n c&#225;i h&#224;nh &#273;&#7897;ng b&#249; tr&#7915; &#273;&#243; c&#361;ng l&#224; m&#7897;t s&#7921; ki&#7879;n m&#7899;i. Tr&#7915; kho r&#7891;i &#224;, fail &#224;, th&#236; ph&#225;t m&#7897;t event c&#7897;ng kho l&#7841;i. Charge ti&#7873;n r&#7891;i &#224;, fail &#224;, th&#236; ph&#225;t m&#7897;t event ho&#224;n ti&#7873;n. &#272;&#7875; &#253; c&#225;i n&#224;y nha, ho&#224;n ti&#7873;n n&#243; kh&#244;ng ph&#7843;i l&#224; x&#243;a c&#225;i giao d&#7883;ch charge &#273;i &#273;&#226;u, ti&#7873;n n&#243; ch&#7841;y r&#7891;i, ho&#224;n ti&#7873;n l&#224; m&#7897;t giao d&#7883;ch m&#7899;i toanh, &#273;i t&#7899;i ph&#237;a tr&#432;&#7899;c, v&#224; v&#244; t&#236;nh l&#224;m s&#7889; d&#432; v&#7873; nh&#432; c&#361;. Kh&#225;ch h&#224;ng m&#7903; sao k&#234; ra l&#224; th&#7845;y c&#7843; hai lu&#244;n, v&#7915;a charge v&#7915;a refund. &#272;&#243; m&#7899;i l&#224; s&#7921; th&#7853;t c&#7911;a th&#7871; gi&#7899;i ph&#226;n t&#225;n. C&#243; m&#7897;t c&#226;u m&#236;nh &#273;&#7885;c &#273;&#432;&#7907;c m&#224; th&#7845;y &#273;&#7855;t: trong microservices kh&#244;ng c&#243; rollback, ch&#7881; c&#243; forward recovery.</p><p></p><p>R&#7891;i t&#7899;i m&#7897;t c&#226;u h&#7887;i l&#224;m m&#236;nh kh&#7921;ng l&#7841;i m&#7845;t m&#7897;t l&#250;c. Ki&#7875;u c&#225;i email x&#225;c nh&#7853;n &#273;&#417;n h&#224;ng n&#243; g&#7917;i &#273;i r&#7891;i, sau &#273;&#243; thanh to&#225;n m&#7899;i fail, m&#224; email th&#236; l&#224;m sao thu h&#7891;i &#273;&#432;&#7907;c. L&#250;c &#273;&#7847;u m&#236;nh ngh&#297; h&#432;&#7899;ng k&#7929; thu&#7853;t, nh&#432;ng h&#243;a ra l&#7901;i gi&#7843;i n&#7857;m &#7903; ch&#7895; thi&#7871;t k&#7871; quy tr&#236;nh. M&#7897;t l&#224; ph&#226;n lo&#7841;i l&#7895;i, l&#7895;i t&#7841;m th&#7901;i ki&#7875;u timeout m&#7841;ng ch&#7853;p ch&#7901;n th&#236; retry v&#224;i l&#7847;n, l&#7895;i v&#297;nh vi&#7877;n th&#236; th&#244;i. Hai l&#224; &#273;&#7915;ng h&#7911;y ngay, t&#7841;o ra m&#7897;t c&#225;i tr&#7841;ng th&#225;i trung gian ki&#7875;u ch&#7901; thanh to&#225;n, k&#232;m c&#225;i timeout, h&#7871;t gi&#7901; m&#224; kh&#244;ng tr&#7843; th&#236; m&#7899;i &#273;i b&#249; tr&#7915;. Ba, v&#224; c&#225;i n&#224;y m&#7899;i l&#224; c&#225;i hay, l&#224; s&#7855;p x&#7871;p l&#7841;i th&#7913; t&#7921; c&#225;c b&#432;&#7899;c theo &#273;&#7897; kh&#243; ho&#224;n t&#225;c. M&#7845;y c&#225;i h&#224;nh &#273;&#7897;ng kh&#244;ng th&#7875; ho&#224;n t&#225;c nh&#432; g&#7917;i mail hay giao h&#224;ng v&#7853;t l&#253; th&#236; &#273;&#7849;y n&#243; v&#7873; cu&#7889;i c&#249;ng, sau c&#225;i &#273;i&#7875;m kh&#244;ng th&#7875; quay &#273;&#7847;u. V&#224; ch&#7881; g&#7917;i m&#7845;y c&#225;i email m&#224; d&#249; k&#7871;t qu&#7843; th&#7871; n&#224;o n&#243; v&#7851;n &#273;&#250;ng s&#7921; th&#7853;t th&#244;i, ki&#7875;u "b&#7841;n c&#7847;n thanh to&#225;n &#273;&#7875; ti&#7871;p t&#7909;c" hay "&#273;&#417;n &#273;&#227; h&#7911;y v&#236; kh&#244;ng thanh to&#225;n", ch&#7913; &#273;&#7915;ng c&#243; g&#7917;i "&#273;&#417;n &#273;&#227; x&#225;c nh&#7853;n" khi m&#7885;i th&#7913; c&#242;n ch&#432;a ch&#7855;c.</p><p></p><p>Ti&#7871;p theo l&#224; c&#225;i nguy&#234;n l&#253; m&#224; m&#236;nh g&#7863;p &#273;i g&#7863;p l&#7841;i t&#7899;i ba l&#7847;n trong m&#7897;t bu&#7893;i, l&#250;c &#273;&#7847;u c&#242;n kh&#244;ng nh&#7853;n ra: idempotency. B&#7855;t &#273;&#7847;u t&#7915; ch&#7895; "retry thanh to&#225;n v&#224;i l&#7847;n", nghe v&#244; h&#7841;i l&#7855;m, nh&#432;ng n&#243; ch&#7913;a m&#7897;t qu&#7843; m&#236;n kinh &#273;i&#7875;n. B&#7841;n g&#7885;i sang c&#7893;ng thanh to&#225;n b&#7843;o charge 500k, c&#7893;ng n&#243; tr&#7915; ti&#7873;n ok r&#7891;i, nh&#432;ng c&#225;i response tr&#234;n &#273;&#432;&#7901;ng v&#7873; th&#236; b&#7883; timeout m&#7845;t ti&#234;u. Ph&#237;a b&#7841;n nh&#236;n v&#224;o ch&#7881; th&#7845;y timeout, ngh&#297; "&#224; l&#7895;i t&#7841;m th&#7901;i, retry th&#244;i", th&#7871; l&#224; charge th&#234;m ph&#225;t n&#7919;a. Double charge. V&#7845;n &#273;&#7873; c&#7889;t l&#245;i l&#224; khi g&#7863;p timeout b&#7841;n kh&#244;ng bao gi&#7901; bi&#7871;t &#273;&#432;&#7907;c c&#225;i request kia n&#243; &#273;&#227; th&#7921;c s&#7921; x&#7843;y ra hay ch&#432;a, c&#225;i tr&#432;&#7901;ng h&#7907;p "th&#224;nh c&#244;ng nh&#432;ng m&#7845;t response" v&#7899;i c&#225;i tr&#432;&#7901;ng h&#7907;p "th&#7845;t b&#7841;i th&#7853;t" n&#243; nh&#236;n gi&#7889;ng h&#7879;t nhau t&#7915; ph&#237;a b&#7841;n.</p><p></p><p>L&#7901;i gi&#7843;i l&#224; c&#225;i idempotency key. B&#7841;n g&#7917;i k&#232;m m&#7897;t c&#225;i key duy nh&#7845;t, c&#7893;ng thanh to&#225;n n&#243; l&#432;u l&#7841;i key n&#224;y &#7913;ng v&#7899;i k&#7871;t qu&#7843; n&#224;o, l&#7847;n sau th&#7845;y l&#7841;i &#273;&#250;ng key &#273;&#243; th&#236; n&#243; l&#244;i k&#7871;t qu&#7843; c&#361; ra tr&#7843; ch&#7913; kh&#244;ng x&#7917; l&#253; l&#7841;i. Nh&#432;ng c&#243; hai c&#225;i b&#7851;y nh&#7887; m&#224; &#237;t ng&#432;&#7901;i &#273;&#7875; &#253;. M&#7897;t l&#224; c&#225;i key &#273;&#243; ph&#7843;i &#7893;n &#273;&#7883;nh qua m&#7885;i l&#7847;n retry c&#7911;a c&#249;ng m&#7897;t thao t&#225;c, ngh&#297;a l&#224; ba l&#7847;n retry ph&#7843;i d&#249;ng chung m&#7897;t key, ch&#7913; l&#7905; tay m&#7895;i l&#7847;n retry l&#7841;i generate m&#7897;t c&#225;i UUID m&#7899;i th&#236; coi nh&#432; xong phim, m&#7895;i l&#7847;n l&#224; m&#7897;t giao d&#7883;ch m&#7899;i, v&#7851;n double charge nh&#432; th&#432;&#7901;ng. Cho n&#234;n c&#225;i key n&#243; ph&#7843;i d&#237;nh v&#7899;i &#253; &#273;&#7883;nh nghi&#7879;p v&#7909; ki&#7875;u payment-orderId &#7845;y, ch&#7913; kh&#244;ng d&#237;nh v&#7899;i c&#225;i l&#7847;n th&#7917;. Hai l&#224; idempotency n&#243; c&#243; hai ph&#237;a, kh&#244;ng ch&#7881; ph&#237;a c&#7893;ng thanh to&#225;n, m&#224; ph&#237;a b&#7841;n n&#7919;a, t&#7841;i v&#236; Kafka v&#7899;i RabbitMQ n&#243; giao h&#224;ng ki&#7875;u at-least-once, m&#7897;t c&#225;i message c&#243; th&#7875; t&#7899;i tay consumer c&#7911;a b&#7841;n hai l&#7847;n, n&#234;n c&#225;i consumer c&#7911;a b&#7841;n n&#243; c&#361;ng ph&#7843;i t&#7921; bi&#7871;t dedupe. C&#242;n n&#7871;u c&#225;i th&#7857;ng third party n&#243; kh&#244;ng th&#232;m h&#7895; tr&#7907; idempotency key th&#236; sao, th&#236; b&#7841;n &#273;&#7893;i chi&#7871;n l&#432;&#7907;c, t&#7915; ki&#7875;u "b&#7855;n r&#7891;i th&#7917; l&#7841;i" sang ki&#7875;u "h&#7887;i tr&#432;&#7899;c r&#7891;i m&#7899;i l&#224;m", t&#7913;c l&#224; tr&#432;&#7899;c khi retry th&#236; query c&#225;i c&#7893;ng kia xem "giao d&#7883;ch tham chi&#7871;u n&#224;y c&#7911;a t&#244;i x&#7843;y ra ch&#432;a". &#272;&#226;y c&#361;ng ch&#237;nh l&#224; l&#253; do m&#7845;y c&#244;ng ty cu&#7889;i ng&#224;y &#273;&#7873;u c&#243; c&#225;i quy tr&#236;nh &#273;&#7889;i so&#225;t v&#7899;i ng&#226;n h&#224;ng, kh&#244;ng ph&#7843;i l&#224;m cho c&#243; &#273;&#226;u, m&#224; l&#224; &#273;&#7875; x&#7917; l&#253; &#273;&#250;ng c&#225;i timeout m&#417; h&#7891; n&#224;y &#273;&#7845;y.</p><p></p><p>Xong c&#225;i th&#236; t&#7899;i m&#7897;t ng&#227; ba ki&#7871;n tr&#250;c kh&#225; l&#7899;n, v&#224; m&#236;nh l&#7841;i b&#7883; d&#237;nh c&#225;i b&#7851;y tr&#7921;c gi&#225;c m&#224; nghe n&#243;i t&#7899;i 70% ng&#432;&#7901;i m&#7855;c. L&#250;c n&#224;y c&#225;i l&#7901;i gi&#7843;i c&#7911;a m&#236;nh n&#243; &#273;&#227; ph&#236;nh ra th&#224;nh m&#7897;t c&#225;i m&#225;y tr&#7841;ng th&#225;i c&#243; nh&#7899;, c&#243; timeout, c&#243; r&#7869; nh&#225;nh r&#7891;i. C&#226;u h&#7887;i l&#224; ai &#273;&#7913;ng ra &#273;i&#7873;u ph&#7889;i c&#225;i &#273;&#7889;ng &#273;&#243;. C&#243; hai tr&#432;&#7901;ng ph&#225;i. M&#7897;t l&#224; choreography, kh&#244;ng c&#243; nh&#7841;c tr&#432;&#7903;ng, m&#7895;i service t&#7921; nghe event t&#7921; ph&#7843;n &#7913;ng t&#7921; ph&#225;t event ti&#7871;p. Hai l&#224; orchestration, c&#243; m&#7897;t th&#7857;ng nh&#7841;c tr&#432;&#7903;ng trung t&#226;m n&#7855;m c&#7843; c&#225;i flow.</p><p></p><p>Tr&#7921;c gi&#225;c c&#7911;a m&#236;nh l&#250;c &#273;&#243; l&#224;, flow c&#224;ng ph&#7913;c t&#7841;p nhi&#7873;u r&#7869; nh&#225;nh th&#236; c&#224;ng n&#234;n ch&#7885;n choreography, v&#236; m&#7895;i service t&#7921; lo vi&#7879;c c&#7911;a n&#243; th&#236; code s&#7841;ch d&#7877; hi&#7875;u d&#7877; m&#7903; r&#7897;ng. Nghe h&#7907;p l&#253; &#273;&#250;ng kh&#244;ng. Nh&#432;ng n&#243; ng&#432;&#7907;c. C&#224;ng ph&#7913;c t&#7841;p nhi&#7873;u nh&#225;nh th&#236; choreography c&#224;ng t&#7879;. L&#253; do l&#224; v&#236; v&#7899;i choreography th&#236; kh&#244;ng t&#7891;n t&#7841;i m&#7897;t ch&#7895; n&#224;o m&#244; t&#7843; c&#225;i "quy tr&#236;nh &#273;&#7863;t h&#224;ng l&#224; g&#236;" c&#7843;, n&#243; b&#7883; x&#233; nh&#7887; ra r&#7843;i kh&#7855;p t&#225;m ch&#237;n c&#225;i service, ch&#7881; t&#7891;n t&#7841;i ng&#7847;m trong c&#225;i m&#7841;ng nh&#7879;n "th&#7857;ng n&#224;o subscribe event c&#7911;a th&#7857;ng n&#224;o". Mu&#7889;n hi&#7875;u c&#7843; c&#225;i flow th&#236; ph&#7843;i m&#7903; h&#7871;t c&#225;c service ra &#273;&#7885;c r&#7891;i ng&#7891;i t&#7921; m&#244; ph&#7887;ng trong &#273;&#7847;u xem event n&#243; ch&#7843;y &#273;i &#273;&#226;u. C&#225;i m&#236;nh t&#432;&#7903;ng l&#224; "d&#7877; hi&#7875;u" th&#7853;t ra ch&#7881; l&#224; d&#7877; hi&#7875;u t&#7915;ng m&#7843;nh ch&#7913; b&#7845;t kh&#7843; hi&#7875;u to&#224;n c&#7909;c. V&#224; c&#225;i "decoupled" &#273;&#243; l&#224; &#7843;o gi&#225;c th&#244;i, c&#225;c service v&#7851;n d&#237;nh nhau qua th&#7913; t&#7921; event, ch&#7881; l&#224; c&#225;i s&#7921; d&#237;nh &#273;&#243; b&#7883; gi&#7845;u &#273;i kh&#244;ng nh&#236;n th&#7845;y trong code.</p><p></p><p>Cho d&#7877; h&#236;nh dung th&#236; ki&#7875;u th&#7871; n&#224;y, s&#7871;p b&#7843;o: &#273;&#417;n tr&#234;n 10 tri&#7879;u th&#236; ch&#232;n th&#234;m b&#432;&#7899;c ki&#7875;m tra gian l&#7853;n v&#224;o gi&#7919;a thanh to&#225;n v&#7899;i giao v&#7853;n. N&#7871;u l&#224; orchestration th&#236; b&#7841;n m&#7903; c&#225;i m&#225;y tr&#7841;ng th&#225;i ra, th&#234;m m&#7897;t node fraud check v&#224;o gi&#7919;a, th&#234;m m&#7897;t nh&#225;nh &#273;i&#7873;u ki&#7879;n n&#7871;u l&#7899;n h&#417;n 10 tri&#7879;u, s&#7917;a &#273;&#250;ng m&#7897;t ch&#7895; l&#224; xong, m&#224; b&#7843;n th&#226;n c&#225;i m&#225;y tr&#7841;ng th&#225;i &#273;&#243; n&#243; ch&#237;nh l&#224; t&#224;i li&#7879;u s&#7889;ng c&#7911;a quy tr&#236;nh lu&#244;n. C&#242;n n&#7871;u l&#224; choreography th&#236; r&#7889;i h&#417;n nhi&#7873;u, hi&#7879;n t&#7841;i th&#7857;ng payment ph&#225;t event PaymentCompleted th&#236; th&#7857;ng shipping nghe r&#7891;i giao, gi&#7901; mu&#7889;n ch&#232;n fraud v&#224;o gi&#7919;a th&#236; ph&#7843;i b&#7855;t fraud nghe PaymentCompleted, r&#7891;i ph&#7843;i s&#7917;a c&#7843; th&#7857;ng shipping &#273;&#7875; n&#243; &#273;&#7893;i sang nghe FraudPassed thay v&#236; PaymentCompleted, m&#224; th&#7857;ng shipping n&#243; li&#234;n quan qu&#225;i g&#236; t&#7899;i fraud &#273;&#226;u m&#224; t&#7921; nhi&#234;n ph&#7843;i s&#7917;a n&#243;, r&#7891;i lo th&#7913; t&#7921;, lo tr&#432;&#7901;ng h&#7907;p &#273;&#417;n d&#432;&#7899;i 10 tri&#7879;u th&#236; fraud im l&#7863;ng cho qua, v&#224; cu&#7889;i c&#249;ng ch&#7859;ng c&#243; ch&#7895; n&#224;o ghi l&#7841;i "&#224; quy tr&#236;nh m&#7899;i gi&#7901; n&#243; nh&#432; v&#7847;y" c&#7843;.</p><p></p><p>C&#242;n c&#225;i n&#7895;i lo ph&#7893; bi&#7871;n l&#224; orchestration th&#236; c&#243; m&#7897;t th&#7857;ng trung t&#226;m, single point of failure, d&#7877; qu&#225; t&#7843;i. C&#225;i n&#224;y m&#236;nh t&#7915;ng ngh&#297; v&#7853;y nh&#432;ng ph&#7847;n l&#7899;n l&#224; l&#7847;m t&#432;&#7903;ng. C&#225;i orchestrator n&#243; l&#224; nh&#7841;c tr&#432;&#7903;ng ch&#7913; kh&#244;ng ph&#7843;i nh&#226;n c&#244;ng, vi&#7879;c n&#7863;ng v&#7851;n n&#7857;m &#7903; c&#225;c service, n&#243; ch&#7881; &#273;i&#7873;u ph&#7889;i th&#244;i n&#234;n b&#7843;o n&#243; qu&#225; t&#7843;i l&#224; h&#417;i th&#7893;i ph&#7891;ng. M&#224; c&#225;i state c&#7911;a n&#243; n&#7857;m trong DB ch&#7913; kh&#244;ng n&#7857;m trong RAM, n&#234;n b&#7841;n ch&#7841;y nhi&#7873;u c&#225;i instance song song scale ngang tho&#7843;i m&#225;i, h&#7871;t single point of failure. N&#243;i orchestrator l&#224; &#273;i&#7875;m ch&#7871;t duy nh&#7845;t th&#236; c&#361;ng nh&#432; n&#243;i c&#225;i DB c&#7911;a t&#244;i l&#224; &#273;i&#7875;m ch&#7871;t duy nh&#7845;t v&#7853;y, &#273;&#250;ng v&#7873; m&#7863;t k&#7929; thu&#7853;t nh&#432;ng ng&#432;&#7901;i ta gi&#7843;i quy&#7871;t b&#7857;ng HA v&#7899;i replication ch&#7913; kh&#244;ng ph&#7843;i b&#7857;ng c&#225;ch v&#7913;t c&#225;i DB &#273;i. M&#224; c&#7843; m&#7897;t th&#7871; h&#7879; c&#244;ng c&#7909; sinh ra &#273;&#7875; l&#224;m &#273;&#250;ng c&#225;i vi&#7879;c orchestrator n&#224;y cho chu&#7849;n lu&#244;n r&#7891;i, Temporal, AWS Step Functions, Camunda, Netflix Conductor, m&#7845;y h&#7879; th&#7889;ng t&#7927; &#273;&#244; x&#224;i &#7847;m &#7847;m ch&#7913; c&#243; s&#7907; &#273;&#226;u. Cho n&#234;n c&#225;i heuristic &#273;&#250;ng l&#224;, flow ph&#7913;c t&#7841;p nhi&#7873;u nh&#225;nh hay &#273;&#7893;i c&#7847;n nh&#236;n r&#245; ki&#7875;m so&#225;t th&#236; orchestration, c&#242;n m&#7845;y ph&#7843;n &#7913;ng r&#7901;i r&#7841;c song song kh&#244;ng c&#243; quy tr&#236;nh g&#236; ki&#7875;u order t&#7841;o xong th&#236; &#273;&#7897;c l&#7853;p &#273;&#7849;y analytics v&#7899;i &#273;&#225;nh index search v&#7899;i g&#7917;i mail th&#236; choreography, v&#224; th&#7921;c t&#7871; ngon nh&#7845;t l&#224; lai hai c&#225;i, orchestrate c&#225;i l&#245;i giao d&#7883;ch quan tr&#7885;ng c&#242;n choreograph m&#7845;y th&#7913; ngo&#7841;i vi.</p><p></p><p>R&#7891;i t&#7899;i c&#250; s&#7889;c cu&#7889;i, c&#225;i n&#224;y m&#236;nh th&#237;ch nh&#7845;t. C&#225;i n&#7895;i lo orchestrator ch&#7871;t c&#7911;a m&#236;nh h&#243;a ra kh&#244;ng sai, ch&#7881; l&#224; &#273;&#7863;t nh&#7847;m ch&#7895;. V&#7845;n &#273;&#7873; th&#7853;t kh&#244;ng ph&#7843;i n&#243; qu&#225; t&#7843;i, m&#224; l&#224; c&#225;i n&#224;y: orchestrator &#273;ang ch&#7841;y n&#7917;a ch&#7915;ng, charge xong r&#7891;i, ch&#432;a k&#7883;p g&#7885;i shipping th&#236; n&#243; crash. S&#7889;ng d&#7853;y th&#236; l&#224;m sao bi&#7871;t &#273;&#417;n n&#224;y &#273;ang &#7903; b&#432;&#7899;c n&#224;o &#273;&#7875; &#273;i ti&#7871;p cho &#273;&#250;ng. M&#236;nh &#273;&#7873; xu&#7845;t h&#432;&#7899;ng l&#224; &#273;&#7883;nh ngh&#297;a status cho t&#7915;ng b&#432;&#7899;c, sau m&#7895;i b&#432;&#7899;c th&#224;nh c&#244;ng th&#236; c&#7853;p nh&#7853;t status, r&#7891;i c&#243; m&#7897;t c&#225;i background job qu&#233;t m&#7845;y &#273;&#417;n dang d&#7903; &#273;&#7875; x&#7917; l&#253; ti&#7871;p. &#272;&#250;ng h&#432;&#7899;ng, nh&#432;ng c&#243; m&#7897;t c&#225;i v&#7921;c th&#7859;m n&#7857;m ngay &#273;&#243;.</p><p></p><p>Soi t&#7915;ng nano gi&#226;y nha. B&#432;&#7899;c m&#7897;t, g&#7885;i charge, c&#7893;ng tr&#7915; ti&#7873;n ok. Crash ngay t&#7841;i &#273;&#226;y. B&#432;&#7899;c hai, &#273;&#7883;nh ghi status b&#7857;ng PAID, ch&#432;a k&#7883;p ch&#7841;y. S&#7889;ng d&#7853;y th&#236; DB v&#7851;n ghi l&#224; &#273;ang ch&#7901; thanh to&#225;n, c&#225;i job n&#243; th&#7845;y "&#224; &#273;&#417;n n&#224;y ch&#432;a tr&#7843; ti&#7873;n" th&#7871; l&#224; g&#7885;i charge l&#7847;n n&#7919;a, double charge. M&#224; b&#7841;n &#273;&#7843;o ng&#432;&#7907;c th&#7913; t&#7921; l&#7841;i, ghi status tr&#432;&#7899;c charge sau, th&#236; ch&#7871;t ki&#7875;u kh&#225;c, ghi PAID xong r&#7891;i crash tr&#432;&#7899;c khi charge, &#273;&#417;n ghi l&#224; &#273;&#227; tr&#7843; m&#224; th&#7921;c ra ch&#432;a h&#7873; tr&#7915; &#273;&#7891;ng n&#224;o, th&#7845;t tho&#225;t.</p><p></p><p>&#272;&#226;y l&#224; c&#225;i s&#7921; th&#7853;t n&#7873;n t&#7843;ng m&#224; m&#236;nh ngh&#297; ai l&#224;m h&#7879; ph&#226;n t&#225;n c&#361;ng ph&#7843;i th&#7845;m: b&#7841;n kh&#244;ng th&#7875; n&#224;o l&#224;m c&#225;i side effect b&#234;n ngo&#224;i v&#7899;i c&#225;i vi&#7879;c ghi status trong DB chung m&#7897;t transaction nguy&#234;n t&#7917; &#273;&#432;&#7907;c, v&#236; ch&#250;ng n&#7857;m &#7903; hai h&#7879; th&#7889;ng kh&#225;c nhau. Lu&#244;n lu&#244;n t&#7891;n t&#7841;i m&#7897;t c&#225;i khe n&#7913;t gi&#7919;a "&#273;&#227; l&#224;m" v&#224; "&#273;&#227; ghi l&#7841;i l&#224; &#273;&#227; l&#224;m", v&#224; c&#225;i khe &#273;&#243; b&#7841;n kh&#244;ng bao gi&#7901; &#273;&#243;ng k&#237;n &#273;&#432;&#7907;c. H&#7879; qu&#7843; l&#224; g&#236;, l&#224; l&#432;u status n&#243; ch&#7881; thu h&#7865;p c&#225;i c&#7917;a s&#7893; l&#7895;i l&#7841;i th&#244;i ch&#7913; kh&#244;ng x&#243;a &#273;&#432;&#7907;c n&#243;, v&#224; v&#236; c&#225;i khe n&#7913;t l&#224; b&#7845;t kh&#7843; ti&#234;u di&#7879;t n&#234;n c&#225;i recovery c&#7911;a b&#7841;n b&#7855;t bu&#7897;c ph&#7843;i idempotent. Th&#7845;y ch&#432;a, idempotency n&#243; l&#7841;i quay v&#7873;, l&#7847;n th&#7913; ba trong m&#7897;t bu&#7893;i, &#7903; ba ch&#7895; t&#432;&#7903;ng ch&#7915;ng ch&#7859;ng li&#234;n quan g&#236; nhau l&#224; retry thanh to&#225;n, dedupe &#7903; consumer, v&#224; recovery sau crash. M&#7897;t c&#225;i th&#7913; m&#224; c&#7913; xu&#7845;t hi&#7879;n &#273;i xu&#7845;t hi&#7879;n l&#7841;i nh&#432; v&#7853;y th&#236; n&#243; kh&#244;ng ph&#7843;i m&#7865;o v&#7863;t &#273;&#226;u, n&#243; l&#224; nguy&#234;n l&#253; n&#7873;n t&#7843;ng.</p><p></p><p>C&#243; hai c&#225;i tinh ch&#7881;nh &#273;&#7875; bi&#7871;n c&#225;i &#253; t&#432;&#7903;ng th&#244; c&#7911;a m&#236;nh th&#224;nh chu&#7849;n c&#244;ng nghi&#7879;p. M&#7897;t l&#224; ghi &#253; &#273;&#7883;nh tr&#432;&#7899;c ghi k&#7871;t qu&#7843; sau, &#273;&#7915;ng &#273;&#7907;i th&#224;nh c&#244;ng m&#7899;i ghi, m&#224; tr&#432;&#7899;c khi charge th&#236; ghi lu&#244;n c&#225;i tr&#7841;ng th&#225;i ki&#7875;u &#273;ang th&#7917; thanh to&#225;n k&#232;m c&#225;i idempotency key &#273;&#227; sinh s&#7861;n, xong r&#7891;i m&#7899;i ghi PAID, nh&#7901; v&#7853;y l&#250;c recovery n&#243; th&#7845;y c&#225;i tr&#7841;ng th&#225;i &#273;ang th&#7917; l&#224; bi&#7871;t "&#224; m&#236;nh &#273;&#227; b&#7855;t &#273;&#7847;u vi&#7879;c n&#224;y r&#7891;i, l&#7845;y &#273;&#250;ng c&#225;i key &#273;&#243; retry cho an to&#224;n", c&#225;i n&#224;y &#273;&#250;ng y t&#432; duy Write-Ahead Log trong database. Hai l&#224; c&#225;i job recovery ph&#7843;i lo chuy&#7879;n tranh ch&#7845;p, hai c&#225;i instance c&#249;ng qu&#233;t tr&#250;ng m&#7897;t &#273;&#417;n l&#224; lo&#7841;n, n&#234;n ph&#7843;i kh&#243;a l&#7841;i, SELECT FOR UPDATE hay leasing g&#236; &#273;&#243;.</p><p></p><p>V&#224; c&#225;i twist hay nh&#7845;t l&#224;, t&#7845;t c&#7843; nh&#7919;ng th&#7913; m&#236;nh v&#7915;a h&#236; h&#7909;c ngh&#297; ra &#273;&#243;, l&#432;u state t&#7915;ng b&#432;&#7899;c r&#7891;i recovery r&#7891;i write-ahead r&#7891;i idempotent retry, n&#243; ch&#237;nh l&#224; c&#225;i Temporal n&#243; l&#224;m s&#7861;n cho b&#7841;n r&#7891;i. C&#225;ch n&#243; l&#224;m l&#224; ghi l&#7841;i l&#7883;ch s&#7917; k&#7871;t qu&#7843; c&#7911;a t&#7915;ng b&#432;&#7899;c, ki&#7875;u event-sourced, khi crash s&#7889;ng d&#7853;y n&#243; ch&#7841;y l&#7841;i c&#225;i code workflow nh&#432;ng b&#432;&#7899;c n&#224;o &#273;&#227; xong th&#236; n&#243; tr&#7843; lu&#244;n k&#7871;t qu&#7843; &#273;&#227; l&#432;u ch&#7913; kh&#244;ng l&#224;m l&#7841;i, th&#7871; l&#224; n&#243; tua v&#7873; &#273;&#250;ng ch&#7895; dang d&#7903; r&#7891;i &#273;i ti&#7871;p nh&#432; ch&#432;a h&#7873; crash. B&#224;i h&#7885;c r&#250;t ra cho d&#226;n &#273;&#7883;nh l&#224;m architect nh&#432; m&#236;nh l&#224;, khi n&#224;o th&#7845;y m&#236;nh &#273;ang ng&#7891;i t&#7921; tay code l&#7841;i m&#7897;t c&#225;i m&#225;y tr&#7841;ng th&#225;i b&#7873;n b&#7881; c&#243; recovery, th&#236; &#273;&#243; l&#224; d&#7845;u hi&#7879;u r&#245; r&#224;ng l&#224; m&#236;nh &#273;ang ph&#225;t minh l&#7841;i c&#225;i b&#225;nh xe, n&#234;n c&#226;n nh&#7855;c x&#224;i lu&#244;n m&#7897;t c&#225;i durable execution engine. T&#7845;t nhi&#234;n l&#224; kh&#244;ng c&#243; b&#7919;a tr&#432;a mi&#7877;n ph&#237; &#273;&#226;u, x&#224;i Temporal th&#236; l&#7841;i &#273;&#7867; th&#234;m m&#7897;t c&#7909;c h&#7841; t&#7847;ng ph&#7843;i nu&#244;i.</p><p></p><p>Ng&#7891;i nh&#236;n l&#7841;i th&#236; h&#417;i gi&#7853;t m&#236;nh, ch&#7881; t&#7915; m&#7897;t c&#226;u h&#7887;i Kafka hay RabbitMQ m&#224; m&#236;nh &#273;i qua m&#7897;t m&#7841;ch d&#224;i: queue v&#7899;i log kh&#225;c nhau ra sao, r&#7891;i saga, r&#7891;i c&#225;i chuy&#7879;n kh&#244;ng c&#243; rollback ch&#7881; c&#243; forward recovery, r&#7891;i compensating action, r&#7891;i s&#7855;p x&#7871;p c&#225;c b&#432;&#7899;c theo &#273;&#7897; kh&#243; ho&#224;n t&#225;c, r&#7891;i idempotency xu&#7845;t hi&#7879;n t&#7899;i ba l&#7847;n, r&#7891;i orchestration v&#7899;i choreography v&#224; c&#226;u h&#7887;i quy tr&#236;nh s&#7889;ng &#7903; &#273;&#226;u, r&#7891;i cu&#7889;i c&#249;ng l&#224; durable execution v&#7899;i c&#225;i khe n&#7913;t kh&#244;ng bao gi&#7901; &#273;&#243;ng &#273;&#432;&#7907;c. C&#225;i s&#7907;i ch&#7881; xuy&#234;n su&#7889;t m&#224; m&#236;nh mang v&#7873; l&#224; th&#7871; n&#224;y: trong h&#7879; ph&#226;n t&#225;n th&#236; m&#7885;i th&#7913; &#273;&#7873;u c&#243; th&#7875; fail &#7903; gi&#7919;a ch&#7915;ng, v&#224; hai h&#7879; th&#7889;ng th&#236; kh&#244;ng bao gi&#7901; &#273;&#7891;ng &#253; v&#7899;i nhau m&#7897;t c&#225;ch nguy&#234;n t&#7917;, cho n&#234;n m&#236;nh kh&#244;ng ch&#7889;ng l&#7895;i b&#7857;ng c&#225;ch ng&#259;n kh&#244;ng cho n&#243; x&#7843;y ra, m&#224; b&#7857;ng c&#225;ch thi&#7871;t k&#7871; sao cho m&#7885;i thao t&#225;c c&#243; th&#7875; l&#7863;p l&#7841;i an to&#224;n v&#224; ph&#7909;c h&#7891;i ti&#7871;n t&#7899;i.</p><p></p><p>C&#242;n c&#226;u h&#7887;i Kafka hay RabbitMQ &#225;, m&#236;nh v&#7851;n ch&#432;a tr&#7843; l&#7901;i &#273;&#226;u nha ;)) m&#224; th&#7845;y c&#361;ng hay, h&#243;a ra h&#7887;i &#273;&#250;ng c&#226;u n&#243; c&#242;n quan tr&#7885;ng h&#417;n l&#224; tr&#7843; l&#7901;i nhanh m&#7897;t c&#226;u. H&#7865;n ae b&#224;i sau, l&#250;c &#273;&#243; m&#236;nh &#273;&#243;ng l&#7841;i c&#225;i v&#242;ng tr&#242;n n&#224;y, k&#7875; chuy&#7879;n khi n&#224;o n&#234;n d&#249;ng Kafka, khi n&#224;o d&#249;ng RabbitMQ, v&#224; khi n&#224;o th&#236; t&#7889;t nh&#7845;t l&#224; &#273;&#7915;ng c&#243; d&#249;ng queue cho m&#7879;t.</p>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: bài toán rate limiter]]></title><description><![CDATA[H&#7891;i x&#432;a v&#7899;i m&#236;nh rate limiter &#273;&#417;n gi&#7843;n l&#7855;m: s&#7907; user spam th&#236; &#273;&#7871;m s&#7889; request m&#7895;i ph&#250;t, qu&#225; ng&#432;&#7905;ng th&#236; ch&#7863;n, xong.]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-bai-toan-rate-limiter</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-bai-toan-rate-limiter</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Sat, 20 Jun 2026 00:21:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>H&#7891;i x&#432;a v&#7899;i m&#236;nh rate limiter &#273;&#417;n gi&#7843;n l&#7855;m: s&#7907; user spam th&#236; &#273;&#7871;m s&#7889; request m&#7895;i ph&#250;t, qu&#225; ng&#432;&#7905;ng th&#236; ch&#7863;n, xong. Nh&#432;ng l&#250;c ng&#7891;i &#244;n nghi&#234;m t&#250;c &#273;&#7875; ph&#7887;ng v&#7845;n architect, m&#236;nh m&#7899;i th&#7845;y &#273;&#7857;ng sau c&#225;i "&#273;&#7871;m r&#7891;i ch&#7863;n" &#273;&#243; l&#224; c&#7843; m&#7897;t chu&#7895;i &#273;&#225;nh &#273;&#7893;i, t&#7915; ch&#7885;n thu&#7853;t to&#225;n n&#224;o, l&#432;u &#7903; &#273;&#226;u, t&#7899;i chuy&#7879;n l&#224;m sao cho n&#243; ch&#7841;y &#273;&#250;ng khi c&#243; h&#224;ng ch&#7909;c server c&#249;ng &#273;&#7871;m m&#7897;t l&#250;c. M&#236;nh k&#7875; l&#7841;i c&#7843; m&#7841;ch, v&#236; x&#226;u chu&#7895;i l&#7841;i n&#243; hay h&#417;n m&#236;nh t&#432;&#7903;ng.</p><p>Tr&#432;&#7899;c h&#7871;t, rate limit &#273;&#7875; l&#224;m g&#236;. Kh&#244;ng ch&#7881; &#273;&#7875; ch&#7863;n m&#7845;y &#244;ng spam. N&#243; b&#7843;o v&#7879; h&#7879; th&#7889;ng kh&#7887;i b&#7883; qu&#225; t&#7843;i, ch&#7889;ng abuse v&#224; brute-force, &#273;&#7843;m b&#7843;o c&#244;ng b&#7857;ng gi&#7919;a c&#225;c user &#273;&#7875; m&#7897;t th&#7857;ng kh&#244;ng ng&#7889;n h&#7871;t t&#224;i nguy&#234;n c&#7911;a c&#7843; &#273;&#225;m, v&#224; ki&#7875;m so&#225;t chi ph&#237; khi b&#7841;n g&#7885;i API b&#234;n th&#7913; ba t&#237;nh ti&#7873;n theo l&#432;&#7907;t. N&#243;i c&#225;ch kh&#225;c, n&#243; l&#224; m&#7897;t c&#225;i van b&#7843;o v&#7879;, kh&#244;ng ph&#7843;i ch&#7881; l&#224; c&#225;i kh&#243;a c&#7917;a.</p><p>Gi&#7901; t&#7899;i ph&#7847;n c&#7889;t l&#245;i: c&#225;c thu&#7853;t to&#225;n. M&#236;nh &#273;i t&#7915; &#273;&#417;n gi&#7843;n t&#7899;i tinh t&#7871;.</p><p>&#272;&#7847;u ti&#234;n l&#224; Fixed Window, c&#7917;a s&#7893; c&#7889; &#273;&#7883;nh. &#221; t&#432;&#7903;ng d&#7877; nh&#7845;t: m&#7895;i ph&#250;t cho 100 request, l&#432;u m&#7897;t counter cho m&#7889;c ph&#250;t &#273;&#243;, m&#7895;i request t&#7899;i th&#236; t&#259;ng l&#234;n m&#7897;t, qu&#225; 100 th&#236; ch&#7863;n, h&#7871;t ph&#250;t th&#236; reset. Tr&#234;n Redis m&#236;nh l&#224;m b&#7857;ng m&#7897;t key ki&#7875;u user:123:minute v&#7899;i l&#7879;nh INCR, v&#224; set TTL m&#7897;t ph&#250;t. C&#243; m&#7897;t c&#225;i b&#7851;y nho nh&#7887; m&#224; nhi&#7873;u ng&#432;&#7901;i d&#237;nh: ph&#7843;i set TTL &#273;&#250;ng l&#250;c t&#7841;o key, t&#7913;c l&#224; khi INCR tr&#7843; v&#7873; 1 th&#236; m&#7899;i EXPIRE, ch&#7913; n&#7871;u l&#7847;n n&#224;o c&#361;ng EXPIRE th&#236; key kh&#244;ng bao gi&#7901; h&#7871;t h&#7841;n. C&#225;ch n&#224;y nh&#7865; v&#224; nhanh, nh&#432;ng n&#243; c&#243; m&#7897;t l&#7895; h&#7893;ng kinh &#273;i&#7875;n g&#7885;i l&#224; boundary burst: n&#7871;u user b&#7855;n 100 request v&#224;o gi&#226;y cu&#7889;i c&#7911;a ph&#250;t n&#224;y v&#224; th&#234;m 100 request v&#224;o gi&#226;y &#273;&#7847;u c&#7911;a ph&#250;t sau, th&#236; trong v&#242;ng hai gi&#226;y h&#7885; &#273;&#7849;y &#273;&#432;&#7907;c 200 request, g&#7845;p &#273;&#244;i gi&#7899;i h&#7841;n, d&#249; v&#7873; m&#7863;t s&#7893; s&#225;ch th&#236; m&#7895;i ph&#250;t v&#7851;n &#273;&#250;ng 100. Ch&#237;nh c&#225;i l&#7895; h&#7893;ng n&#224;y &#273;&#7867; ra c&#225;c thu&#7853;t to&#225;n ti&#7871;p theo.</p><p>&#272;&#7875; v&#225; n&#243;, c&#243; Sliding Window Log, c&#7917;a s&#7893; tr&#432;&#7907;t ki&#7875;u ghi log. Thay v&#236; &#273;&#7871;m theo m&#7889;c ph&#250;t c&#7913;ng, m&#236;nh l&#432;u timestamp c&#7911;a t&#7915;ng request, v&#224; m&#7895;i khi c&#243; request m&#7899;i th&#236; &#273;&#7871;m xem trong &#273;&#250;ng 60 gi&#226;y v&#7915;a qua t&#237;nh t&#7899;i hi&#7879;n t&#7841;i c&#243; bao nhi&#234;u c&#225;i. Tr&#234;n Redis m&#236;nh d&#249;ng Sorted Set, score ch&#237;nh l&#224; timestamp. Lu&#7891;ng x&#7917; l&#253; l&#224;: x&#243;a h&#7871;t c&#225;c ph&#7847;n t&#7917; c&#361; h&#417;n 60 gi&#226;y b&#7857;ng ZREMRANGEBYSCORE, &#273;&#7871;m s&#7889; c&#242;n l&#7841;i b&#7857;ng ZCARD, n&#7871;u d&#432;&#7899;i ng&#432;&#7905;ng th&#236; ZADD th&#234;m request m&#7899;i v&#224;o v&#224; set EXPIRE. C&#225;ch n&#224;y ch&#237;nh x&#225;c tuy&#7879;t &#273;&#7889;i, kh&#244;ng c&#242;n burst &#7903; bi&#234;n. C&#225;i gi&#225; l&#224; t&#7889;n b&#7897; nh&#7899;, v&#236; ph&#7843;i l&#432;u t&#7915;ng timestamp m&#7897;t, user n&#224;o b&#7855;n nhi&#7873;u th&#236; Sorted Set ph&#236;nh to.</p><p>&#272;&#7913;ng gi&#7919;a hai c&#225;i tr&#234;n l&#224; Sliding Window Counter, m&#7897;t ki&#7875;u x&#7845;p x&#7881; r&#7845;t kh&#233;o. N&#243; kh&#244;ng l&#432;u t&#7915;ng request, m&#224; gi&#7919; counter c&#7911;a c&#7917;a s&#7893; hi&#7879;n t&#7841;i v&#224; c&#7917;a s&#7893; tr&#432;&#7899;c, r&#7891;i &#432;&#7899;c l&#432;&#7907;ng theo t&#7881; l&#7879; ph&#7847;n &#273;&#227; tr&#244;i qua c&#7911;a c&#7917;a s&#7893; hi&#7879;n t&#7841;i. V&#237; d&#7909; &#273;ang &#7903; gi&#226;y th&#7913; 15 c&#7911;a ph&#250;t m&#7899;i, n&#243; l&#7845;y to&#224;n b&#7897; counter ph&#250;t n&#224;y c&#7897;ng v&#7899;i 75% counter ph&#250;t tr&#432;&#7899;c. N&#243; g&#7847;n nh&#432; ch&#237;nh x&#225;c b&#7857;ng sliding log nh&#432;ng t&#7889;n b&#7897; nh&#7899; ch&#7881; b&#7857;ng fixed window. &#272;&#226;y l&#224; l&#7921;a ch&#7885;n r&#7845;t th&#7921;c d&#7909;ng cho h&#7879; th&#7889;ng l&#7899;n, v&#224; l&#224; c&#225;ch Cloudflare t&#7915;ng chia s&#7867; h&#7885; d&#249;ng.</p><p>T&#7899;i &#273;&#226;y l&#224; hai thu&#7853;t to&#225;n m&#236;nh th&#237;ch nh&#7845;t v&#236; n&#243; &#273;&#7893;i h&#7859;n g&#243;c nh&#236;n, kh&#244;ng c&#242;n l&#224; "&#273;&#7871;m request" n&#7919;a m&#224; l&#224; "m&#244; h&#236;nh h&#243;a d&#242;ng ch&#7843;y".</p><p>Token Bucket, c&#225;i x&#244; &#273;&#7921;ng token. T&#432;&#7903;ng t&#432;&#7907;ng m&#7897;t c&#225;i x&#244; c&#243; s&#7913;c ch&#7913;a t&#7889;i &#273;a, v&#237; d&#7909; 100 token, v&#224; c&#7913; m&#7895;i gi&#226;y h&#7879; th&#7889;ng r&#243;t th&#234;m v&#224;o &#273;&#243;, v&#237; d&#7909; 10 token m&#7897;t gi&#226;y, &#273;&#7847;y th&#236; th&#244;i. M&#7895;i request mu&#7889;n &#273;i qua ph&#7843;i l&#7845;y m&#7897;t token; c&#242;n token th&#236; &#273;i, h&#7871;t th&#236; b&#7883; ch&#7863;n. C&#225;i &#273;&#7865;p c&#7911;a n&#243; l&#224; cho ph&#233;p burst m&#7897;t c&#225;ch c&#243; ki&#7875;m so&#225;t: l&#250;c r&#7843;nh x&#244; &#273;&#7847;y d&#7847;n l&#234;n, n&#234;n khi c&#243; m&#7897;t &#273;&#7907;t cao &#273;i&#7875;m ng&#7855;n, user c&#243; th&#7875; ti&#234;u m&#7897;t l&#250;c t&#7899;i 100 request, sau &#273;&#243; th&#236; b&#7883; gh&#236; l&#7841;i &#273;&#250;ng theo t&#7889;c &#273;&#7897; r&#243;t 10 m&#7895;i gi&#226;y. M&#7897;t m&#7865;o c&#224;i &#273;&#7863;t hay l&#224; kh&#244;ng c&#7847;n m&#7897;t con worker ch&#7841;y n&#7873;n r&#243;t token th&#7853;t, m&#224; d&#249;ng lazy refill: ch&#7881; l&#432;u s&#7889; token c&#242;n l&#7841;i v&#224; th&#7901;i &#273;i&#7875;m ch&#7841;m cu&#7889;i, khi c&#243; request m&#7899;i t&#7899;i th&#236; t&#237;nh xem t&#7915; l&#250;c &#273;&#243; t&#7899;i gi&#7901; tr&#244;i qua bao l&#226;u, nh&#226;n v&#7899;i t&#7889;c &#273;&#7897; r&#243;t &#273;&#7875; bi&#7871;t n&#234;n c&#7897;ng th&#234;m bao nhi&#234;u token, t&#7845;t nhi&#234;n kh&#244;ng v&#432;&#7907;t s&#7913;c ch&#7913;a. C&#225;ch n&#224;y r&#7845;t ph&#7893; bi&#7871;n, AWS hay nhi&#7873;u API gateway d&#249;ng n&#243; v&#236; n&#243; v&#7915;a gi&#7899;i h&#7841;n t&#7889;c &#273;&#7897; trung b&#236;nh v&#7915;a th&#226;n thi&#7879;n v&#7899;i traffic th&#7853;t v&#7889;n hay gi&#7853;t c&#7909;c.</p><p>Ng&#432;&#7901;i anh em c&#7911;a n&#243; l&#224; Leaky Bucket, c&#225;i x&#244; r&#7881; n&#432;&#7899;c. C&#361;ng l&#224; c&#225;i x&#244;, nh&#432;ng l&#7847;n n&#224;y request &#273;&#7893; v&#224;o v&#224; ch&#7843;y ra &#7903; m&#7897;t t&#7889;c &#273;&#7897; c&#7889; &#273;&#7883;nh, &#273;&#7873;u nh&#432; nh&#7887; gi&#7885;t, x&#244; &#273;&#7847;y th&#236; request m&#7899;i b&#7883; tr&#224;n ra ngo&#224;i t&#7913;c l&#224; b&#7883; ch&#7863;n. Kh&#225;c bi&#7879;t c&#7889;t l&#245;i so v&#7899;i token bucket l&#224; token bucket cho ph&#233;p burst c&#242;n leaky bucket th&#236; l&#224;m ph&#7859;ng d&#242;ng ch&#7843;y, &#273;&#7847;u ra lu&#244;n &#273;&#7873;u t&#259;m t&#7855;p. Cho n&#234;n leaky bucket h&#7907;p v&#7899;i nh&#7919;ng ch&#7895; c&#7847;n traffic shaping, c&#7847;n b&#7843;o v&#7879; m&#7897;t h&#7879; th&#7889;ng ph&#237;a sau v&#7889;n ch&#7881; ch&#7883;u &#273;&#432;&#7907;c t&#7889;c &#273;&#7897; &#7893;n &#273;&#7883;nh ch&#7913; kh&#244;ng ch&#7883;u n&#7893;i gi&#7853;t c&#7909;c, v&#237; d&#7909; &#273;&#7849;y message v&#224;o m&#7897;t con service mong manh. Ch&#7885;n token hay leaky th&#7921;c ra l&#224; ch&#7885;n: m&#236;nh mu&#7889;n cho ph&#233;p b&#249;ng n&#7893; ng&#7855;n h&#7841;n, hay mu&#7889;n d&#242;ng ra th&#7853;t m&#432;&#7907;t.</p><p>Ch&#7885;n xong thu&#7853;t to&#225;n r&#7891;i th&#236; t&#7899;i ph&#7847;n m&#224; m&#236;nh th&#7845;y m&#7899;i l&#224; ch&#7895; ph&#226;n bi&#7879;t ng&#432;&#7901;i l&#224;m h&#7879; th&#7889;ng th&#7853;t v&#7899;i ng&#432;&#7901;i &#273;&#7885;c l&#253; thuy&#7871;t: l&#224;m sao cho n&#243; &#273;&#250;ng trong m&#244;i tr&#432;&#7901;ng ph&#226;n t&#225;n.</p><p>V&#7845;n &#273;&#7873; &#273;&#7847;u ti&#234;n l&#224; atomicity. &#272;&#7875; &#253; m&#7845;y thu&#7853;t to&#225;n tr&#234;n &#273;&#7873;u g&#7891;m nhi&#7873;u b&#432;&#7899;c: &#273;&#7885;c gi&#225; tr&#7883;, ki&#7875;m tra ng&#432;&#7905;ng, r&#7891;i ghi l&#7841;i. N&#7871;u hai request c&#7911;a c&#249;ng m&#7897;t user &#273;&#7853;p v&#224;o hai server kh&#225;c nhau c&#249;ng m&#7897;t l&#250;c, c&#7843; hai c&#249;ng &#273;&#7885;c th&#7845;y 99, c&#7843; hai c&#249;ng ngh&#297; "c&#242;n ch&#7895;", c&#7843; hai c&#249;ng cho &#273;i, th&#7871; l&#224; v&#432;&#7907;t ng&#432;&#7905;ng. &#272;&#226;y &#273;&#250;ng l&#224; race condition kinh &#273;i&#7875;n. V&#224; &#273;&#226;y l&#224; ch&#7895; Redis t&#7887;a s&#225;ng: v&#236; Redis x&#7917; l&#253; l&#7879;nh tr&#234;n m&#7897;t lu&#7891;ng duy nh&#7845;t, n&#234;n n&#7871;u m&#236;nh g&#243;i c&#7843; c&#7909;m &#273;&#7885;c-ki&#7875;m tra-ghi v&#224;o m&#7897;t Lua script, Redis s&#7869; ch&#7841;y tr&#7885;n v&#7865;n &#273;o&#7841;n &#273;&#243; m&#224; kh&#244;ng cho l&#7879;nh n&#224;o chen ngang. To&#224;n b&#7897; logic rate limit tr&#7903; th&#224;nh m&#7897;t thao t&#225;c nguy&#234;n t&#7917;. C&#225;i t&#237;nh single-thread m&#224; ng&#432;&#7901;i ta hay n&#243;i l&#224; &#273;i&#7875;m y&#7871;u, &#7903; &#273;&#226;y l&#7841;i bi&#7871;n th&#224;nh si&#234;u n&#259;ng l&#7921;c. M&#236;nh th&#7845;y &#273;&#226;y l&#224; m&#7897;t v&#237; d&#7909; r&#7845;t &#273;&#7865;p cho th&#7845;y hi&#7875;u s&#226;u m&#7897;t c&#244;ng c&#7909; th&#236; s&#7869; t&#7853;n d&#7909;ng &#273;&#432;&#7907;c nh&#7919;ng th&#7913; t&#432;&#7903;ng nh&#432; ngh&#7883;ch l&#253;.</p><p>V&#7845;n &#273;&#7873; th&#7913; hai l&#224; th&#7901;i gian. Nhi&#7873;u thu&#7853;t to&#225;n d&#7921;a v&#224;o timestamp, m&#224; n&#7871;u m&#7895;i server l&#7845;y gi&#7901; m&#225;y c&#7911;a n&#243; th&#236; &#273;&#7891;ng h&#7891; l&#7879;ch nhau v&#224;i tr&#259;m mili gi&#226;y l&#224; chuy&#7879;n th&#432;&#7901;ng, g&#7885;i l&#224; clock skew, v&#224; rate limit s&#7869; sai. C&#225;ch x&#7917; l&#253; g&#7885;n l&#224; &#273;&#7915;ng tin &#273;&#7891;ng h&#7891; c&#7911;a application server, h&#227;y l&#7845;y th&#7901;i gian t&#7915; ch&#237;nh Redis b&#7857;ng l&#7879;nh TIME, &#273;&#7875; m&#7885;i quy&#7871;t &#273;&#7883;nh c&#249;ng quy v&#7873; m&#7897;t ngu&#7891;n th&#7901;i gian duy nh&#7845;t.</p><p>V&#7845;n &#273;&#7873; th&#7913; ba l&#224; khi ch&#237;nh c&#225;i Redis l&#224;m rate limit b&#7883; s&#7921; c&#7889; th&#236; sao. L&#250;c &#273;&#243; m&#236;nh ph&#7843;i ch&#7885;n m&#7897;t trong hai tri&#7871;t l&#253;. Fail-open ngh&#297;a l&#224; n&#7871;u kh&#244;ng ki&#7875;m tra &#273;&#432;&#7907;c th&#236; c&#7913; cho request &#273;i, &#432;u ti&#234;n gi&#7919; d&#7883;ch v&#7909; s&#7889;ng, ch&#7845;p nh&#7853;n r&#7911;i ro b&#7883; v&#432;&#7907;t gi&#7899;i h&#7841;n trong l&#250;c Redis ch&#7871;t. Fail-close th&#236; ng&#432;&#7907;c l&#7841;i, kh&#244;ng ki&#7875;m tra &#273;&#432;&#7907;c th&#236; ch&#7863;n h&#7871;t, &#432;u ti&#234;n b&#7843;o v&#7879; h&#7879; th&#7889;ng ph&#237;a sau b&#7857;ng m&#7885;i gi&#225;. Kh&#244;ng c&#243; &#273;&#225;p &#225;n &#273;&#250;ng tuy&#7879;t &#273;&#7889;i, n&#243; t&#249;y b&#224;i to&#225;n: API public ch&#7889;ng abuse th&#236; c&#243; khi ch&#7885;n fail-close, c&#242;n m&#7897;t t&#237;nh n&#259;ng ph&#7909; kh&#244;ng mu&#7889;n l&#224;m gi&#225;n &#273;o&#7841;n tr&#7843;i nghi&#7879;m th&#236; fail-open. &#272;i&#7873;u quan tr&#7885;ng l&#224; ph&#7843;i ch&#7885;n c&#243; &#253; th&#7913;c, ch&#7913; &#273;&#7915;ng &#273;&#7875; n&#243; x&#7843;y ra ng&#7851;u nhi&#234;n.</p><p>Cu&#7889;i c&#249;ng l&#224; ph&#7847;n giao ti&#7871;p v&#7899;i client, &#273;&#7915;ng qu&#234;n. Khi ch&#7863;n, &#273;&#7915;ng tr&#7843; v&#7873; m&#7897;t l&#7895;i 500 chung chung, h&#227;y tr&#7843; &#273;&#250;ng m&#227; 429 Too Many Requests, k&#232;m header Retry-After &#273;&#7875; client bi&#7871;t ch&#7901; bao l&#226;u th&#236; th&#7917; l&#7841;i, v&#224; n&#234;n k&#232;m c&#225;c header ki&#7875;u X-RateLimit-Remaining &#273;&#7875; client t&#7921; &#273;i&#7873;u ti&#7871;t. M&#7897;t c&#225;i rate limiter t&#7917; t&#7871; kh&#244;ng ch&#7881; bi&#7871;t n&#243;i kh&#244;ng, n&#243; c&#242;n n&#243;i cho &#273;&#7889;i ph&#432;&#417;ng bi&#7871;t khi n&#224;o th&#236; &#273;&#432;&#7907;c n&#243;i c&#243;.</p><p>&#192;, v&#224; c&#243; m&#7897;t th&#7913; d&#7877; nh&#7847;m m&#224; m&#236;nh mu&#7889;n t&#225;ch b&#7841;ch: rate limiting kh&#244;ng ph&#7843;i l&#224; load shedding. Rate limiting l&#224; gi&#7899;i h&#7841;n theo t&#7915;ng ch&#7911; th&#7875;, ki&#7875;u m&#7895;i user m&#7895;i key &#273;&#432;&#7907;c bao nhi&#234;u, mang t&#237;nh c&#244;ng b&#7857;ng v&#224; h&#7907;p &#273;&#7891;ng. C&#242;n load shedding l&#224; khi c&#7843; h&#7879; th&#7889;ng &#273;ang ng&#7897;p th&#236; ch&#7911; &#273;&#7897;ng v&#7913;t b&#7899;t request b&#7845;t k&#7875; c&#7911;a ai &#273;&#7875; t&#7921; c&#7913;u m&#236;nh. M&#7897;t c&#225;i l&#224; lu&#7853;t ch&#417;i &#273;&#7883;nh tr&#432;&#7899;c, m&#7897;t c&#225;i l&#224; ph&#7843;n &#7913;ng kh&#7849;n c&#7845;p. H&#7879; th&#7889;ng tr&#432;&#7903;ng th&#224;nh th&#432;&#7901;ng c&#243; c&#7843; hai.</p><p>Ng&#7891;i vi&#7871;t l&#7841;i h&#7871;t, m&#236;nh r&#250;t ra &#273;i&#7873;u n&#224;y: rate limiter nh&#236;n th&#236; nh&#7887; nh&#432;ng n&#243; &#233;p m&#236;nh ch&#7841;m v&#224;o g&#7847;n nh&#432; m&#7885;i ch&#7911; &#273;&#7873; c&#7889;t l&#245;i c&#7911;a h&#7879; th&#7889;ng ph&#226;n t&#225;n, t&#7915; race condition, atomicity, &#273;&#7891;ng b&#7897; th&#7901;i gian, cho t&#7899;i tri&#7871;t l&#253; x&#7917; l&#253; khi s&#7921; c&#7889;. Ch&#7885;n thu&#7853;t to&#225;n ch&#7881; l&#224; b&#7873; n&#7893;i; ph&#7847;n ch&#236;m l&#224; b&#7841;n hi&#7875;u h&#7879; th&#7889;ng c&#7911;a m&#236;nh ch&#7883;u &#273;&#432;&#7907;c ki&#7875;u traffic n&#224;o, c&#7847;n burst hay c&#7847;n m&#432;&#7907;t, &#432;u ti&#234;n s&#7889;ng s&#243;t hay &#432;u ti&#234;n b&#7843;o v&#7879;. Tr&#7843; l&#7901;i &#273;&#432;&#7907;c m&#7845;y c&#226;u &#273;&#243; th&#236; m&#7899;i g&#7885;i l&#224; thi&#7871;t k&#7871;, c&#242;n l&#7841;i ch&#7881; l&#224; g&#7855;n th&#432; vi&#7879;n.</p><p>M&#236;nh c&#361;ng m&#7899;i hi&#7875;u t&#7899;i m&#7913;c n&#224;y th&#244;i, ae n&#224;o l&#224;m s&#226;u h&#417;n th&#236; b&#7893; sung, hay mu&#7889;n m&#236;nh &#273;&#224;o k&#7929; ph&#7847;n n&#224;o c&#7913; comment. M&#236;nh s&#7869; vi&#7871;t ti&#7871;p c&#225;c ch&#7911; &#273;&#7873; kh&#225;c tr&#234;n h&#224;nh tr&#236;nh &#244;n l&#234;n architect.</p>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: caching ký sự]]></title><description><![CDATA[H&#7891;i m&#7899;i &#273;i l&#224;m, v&#7899;i m&#236;nh caching &#273;&#417;n gi&#7843;n l&#7855;m: API ch&#7853;m h&#7843;, qu&#259;ng con Redis v&#224;o, cache l&#7841;i, xong.]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-caching-ky-su</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-caching-ky-su</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Fri, 19 Jun 2026 14:42:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>H&#7891;i m&#7899;i &#273;i l&#224;m, v&#7899;i m&#236;nh caching &#273;&#417;n gi&#7843;n l&#7855;m: API ch&#7853;m h&#7843;, qu&#259;ng con Redis v&#224;o, cache l&#7841;i, xong. Nh&#432;ng d&#7841;o n&#224;y ng&#7891;i &#244;n nghi&#234;m t&#250;c &#273;&#7875; ph&#7887;ng v&#7845;n v&#7883; tr&#237; architect, m&#236;nh m&#7899;i v&#7905; ra l&#224; c&#225;i "qu&#259;ng Redis v&#224;o" &#273;&#243; k&#233;o theo c&#7843; m&#7897;t chu&#7895;i b&#224;i to&#225;n m&#224; n&#7871;u kh&#244;ng hi&#7875;u, b&#7841;n s&#7869; v&#225; &#273;&#432;&#7907;c ch&#7895; n&#224;y l&#7841;i l&#242;i ch&#7895; kia. M&#236;nh mu&#7889;n k&#7875; l&#7841;i c&#7843; h&#224;nh tr&#236;nh n&#224;y, v&#236; khi x&#226;u chu&#7895;i l&#7841;i n&#243; &#273;&#7865;p v&#224; logic h&#417;n m&#236;nh t&#432;&#7903;ng nhi&#7873;u.</p><p>Tr&#432;&#7899;c h&#7871;t ph&#7843;i n&#243;i r&#245; m&#7897;t &#273;i&#7873;u: cache c&#243; &#7903; kh&#7855;p m&#7885;i n&#417;i trong c&#225;i stack c&#7911;a ch&#250;ng ta, t&#7915; CPU cache, browser cache, CDN, cache &#7903; API gateway, cho t&#7899;i cache &#7903; t&#7847;ng application. M&#7895;i t&#7847;ng gi&#7843;i m&#7897;t b&#224;i to&#225;n kh&#225;c nhau. Nh&#432;ng ph&#7847;n m&#236;nh k&#7875; h&#244;m nay t&#7853;p trung v&#224;o c&#225;i m&#224; d&#226;n backend &#273;&#7909;ng nhi&#7873;u nh&#7845;t: application-level cache, ki&#7875;u &#273;&#7863;t Redis tr&#432;&#7899;c database &#273;&#7875; g&#225;nh traffic &#273;&#7885;c.</p><p>B&#7855;t &#273;&#7847;u t&#7915; pattern ph&#7893; bi&#7871;n nh&#7845;t, g&#7885;i l&#224; Cache-Aside hay Lazy Loading. Lu&#7891;ng &#273;&#7885;c th&#7871; n&#224;y: request t&#7899;i, ng&#243; v&#224;o cache tr&#432;&#7899;c, c&#243; th&#236; tr&#7843; v&#7873; lu&#244;n (cache hit), kh&#244;ng c&#243; th&#236; xu&#7889;ng database l&#7845;y, nh&#233;t v&#224;o cache r&#7891;i m&#7899;i tr&#7843; v&#7873; (cache miss). C&#242;n lu&#7891;ng ghi, khi d&#7919; li&#7879;u thay &#273;&#7893;i, th&#236; c&#243; m&#7897;t chi ti&#7871;t tinh t&#7871; m&#224; nhi&#7873;u ng&#432;&#7901;i l&#224;m sai: n&#234;n X&#211;A key trong cache, ch&#7913; &#273;&#7915;ng UPDATE l&#7841;i cache. L&#253; do l&#224; n&#7871;u hai request ghi x&#7843;y ra g&#7847;n nh&#432; &#273;&#7891;ng th&#7901;i, th&#7913; t&#7921; update cache c&#243; th&#7875; b&#7883; &#273;&#7843;o, khi&#7871;n cache gi&#7919; l&#7841;i gi&#225; tr&#7883; c&#361; d&#249; database &#273;&#227; l&#224; gi&#225; tr&#7883; m&#7899;i. C&#242;n n&#7871;u ch&#7881; x&#243;a, th&#236; l&#7847;n &#273;&#7885;c k&#7871; ti&#7871;p lu&#244;n l&#7845;y gi&#225; tr&#7883; m&#7899;i nh&#7845;t t&#7915; DB. C&#225;i gi&#225; ph&#7843;i tr&#7843; ch&#7881; l&#224; m&#7897;t l&#7847;n cache miss, r&#7867; h&#417;n nhi&#7873;u so v&#7899;i vi&#7879;c &#244;m d&#7919; li&#7879;u sai.</p><p>Ti&#7871;p theo l&#224; m&#7897;t b&#224;i h&#7885;c m&#236;nh t&#7915;ng xem nh&#7865;: TTL l&#224; b&#7855;t bu&#7897;c, k&#7875; c&#7843; khi b&#7841;n &#273;&#227; ch&#7911; &#273;&#7897;ng x&#243;a cache m&#7895;i l&#7847;n ghi. T&#7841;i sao? V&#236; vi&#7879;c "x&#243;a cache khi c&#243; thay &#273;&#7893;i" d&#7921;a v&#224;o event, m&#224; event th&#236; c&#243; th&#7875; miss, c&#243; th&#7875; bug, c&#243; th&#7875; b&#7883; m&#7845;t. N&#7871;u m&#7897;t event x&#243;a b&#7883; l&#7885;t, cache c&#7911;a b&#7841;n s&#7869; stale v&#297;nh vi&#7877;n. TTL ch&#237;nh l&#224; l&#432;&#7899;i an to&#224;n cu&#7889;i c&#249;ng, &#273;&#7843;m b&#7843;o d&#249; c&#243; sai th&#236; c&#361;ng ch&#7881; sai trong v&#224;i gi&#226;y ho&#7863;c v&#224;i ph&#250;t r&#7891;i t&#7921; s&#7917;a, ch&#7913; kh&#244;ng bao gi&#7901; sai m&#227;i m&#227;i.</p><p>R&#7891;i t&#7899;i b&#7897; ba v&#7845;n &#273;&#7873; kinh &#273;i&#7875;n m&#224; h&#7891;i x&#432;a m&#236;nh kh&#244;ng h&#7873; bi&#7871;t t&#234;n, nh&#432;ng th&#7921;c ra &#273;&#227; t&#7915;ng g&#7863;p ngo&#224;i production. Th&#7913; nh&#7845;t l&#224; cache penetration, t&#7913;c l&#224; query nh&#7919;ng th&#7913; KH&#212;NG t&#7891;n t&#7841;i. Ai &#273;&#243; c&#7913; spam query m&#7897;t id kh&#244;ng c&#243; trong DB, cache miss li&#234;n t&#7909;c, m&#7895;i l&#7847;n l&#7841;i d&#7897;i xu&#7889;ng database, m&#224; database c&#361;ng kh&#244;ng c&#243; g&#236; &#273;&#7875; tr&#7843; n&#234;n ch&#7859;ng cache &#273;&#432;&#7907;c g&#236;, v&#242;ng l&#7863;p &#273;&#243; b&#224;o ch&#7871;t DB. C&#225;ch x&#7917; l&#253; l&#224; cache lu&#244;n c&#7843; k&#7871;t qu&#7843; r&#7895;ng v&#7899;i TTL ng&#7855;n, ho&#7863;c d&#249;ng Bloom filter &#273;&#7875; ch&#7863;n t&#7915; &#273;&#7847;u nh&#7919;ng id ch&#7855;c ch&#7855;n kh&#244;ng t&#7891;n t&#7841;i.</p><p>Th&#7913; hai l&#224; cache avalanche, tuy&#7871;t l&#7903;. T&#432;&#7903;ng t&#432;&#7907;ng b&#7841;n n&#7841;p m&#7897;t tri&#7879;u s&#7843;n ph&#7849;m v&#224;o cache c&#249;ng m&#7897;t l&#250;c v&#7899;i TTL y h&#7879;t nhau l&#224; m&#7897;t gi&#7901;. &#272;&#250;ng m&#7897;t gi&#7901; sau, t&#7845;t c&#7843; h&#7871;t h&#7841;n c&#249;ng m&#7897;t kho&#7843;nh kh&#7855;c, to&#224;n b&#7897; traffic &#273;&#7893; &#7909;p xu&#7889;ng database trong m&#7897;t gi&#226;y, v&#224; DB s&#7853;p. C&#225;ch ch&#7919;a &#273;&#417;n gi&#7843;n m&#224; hi&#7879;u qu&#7843;: th&#234;m m&#7897;t kho&#7843;ng random v&#224;o TTL, &#273;&#7875; c&#225;c key h&#7871;t h&#7841;n r&#7843;i &#273;&#7873;u ra thay v&#236; d&#7891;n c&#7909;c.</p><p>Th&#7913; ba l&#224; cache breakdown, hay c&#242;n g&#7885;i l&#224; hot key. M&#7897;t s&#7843;n ph&#7849;m &#273;ang hot, v&#237; d&#7909; &#273;ang flash sale, c&#243; m&#7845;y ch&#7909;c ngh&#236;n request m&#7895;i gi&#226;y. &#272;&#250;ng c&#225;i gi&#226;y n&#243; h&#7871;t h&#7841;n, h&#224;ng ngh&#236;n request c&#249;ng miss m&#7897;t l&#250;c, c&#249;ng lao xu&#7889;ng DB query &#273;&#250;ng m&#7897;t d&#242;ng d&#7919; li&#7879;u, hi&#7879;n t&#432;&#7907;ng n&#224;y g&#7885;i l&#224; thundering herd. C&#225;ch x&#7917; l&#253; l&#224; d&#249;ng m&#7897;t c&#225;i lock, ch&#7881; cho duy nh&#7845;t m&#7897;t request &#273;i load DB r&#7891;i n&#7841;p l&#7841;i cache, &#273;&#225;m c&#242;n l&#7841;i ch&#7901; m&#7897;t ch&#250;t; ho&#7863;c d&#249;ng logical expiration, t&#7913;c l&#224; kh&#244;ng &#273;&#7875; key h&#7871;t h&#7841;n c&#7913;ng m&#224; c&#243; m&#7897;t worker n&#7873;n &#226;m th&#7847;m refresh.</p><p>Nh&#432;ng ph&#7847;n l&#224;m m&#236;nh t&#226;m &#273;&#7855;c nh&#7845;t, v&#224; c&#361;ng l&#224; ph&#7847;n kh&#243; nh&#7845;t, l&#224; chuy&#7879;n nh&#7845;t qu&#225;n gi&#7919;a cache v&#224; database. C&#226;u h&#7887;i nghe th&#236; &#273;&#417;n gi&#7843;n: khi c&#7853;p nh&#7853;t d&#7919; li&#7879;u, n&#234;n ghi database tr&#432;&#7899;c r&#7891;i x&#243;a cache, hay x&#243;a cache tr&#432;&#7899;c r&#7891;i ghi database? M&#236;nh t&#7915;ng ngh&#297; x&#243;a cache tr&#432;&#7899;c an to&#224;n h&#417;n, v&#236; n&#7871;u ghi DB l&#7895;i th&#236; c&#249;ng l&#7855;m t&#7889;n m&#7897;t cache miss. Nh&#432;ng &#273;&#243; m&#7899;i ch&#7881; l&#224; m&#7897;t n&#7917;a c&#226;u chuy&#7879;n. C&#225;i n&#7917;a quan tr&#7885;ng h&#417;n l&#224; chuy&#7879;n hai request ch&#7841;y &#273;&#7891;ng th&#7901;i. N&#7871;u x&#243;a cache tr&#432;&#7899;c r&#7891;i m&#7899;i ghi DB, s&#7869; c&#243; m&#7897;t c&#7917;a s&#7893; th&#7901;i gian kh&#225; r&#7897;ng gi&#7919;a hai b&#432;&#7899;c &#273;&#243;, v&#224; m&#7897;t request &#273;&#7885;c chen v&#224;o &#273;&#250;ng l&#250;c n&#224;y s&#7869; &#273;&#7885;c &#273;&#432;&#7907;c gi&#225; tr&#7883; C&#360; t&#7915; DB r&#7891;i n&#7841;p ng&#432;&#7907;c n&#243; v&#224;o cache. K&#7871;t qu&#7843; l&#224; cache stale m&#224; ch&#7859;ng c&#242;n event n&#224;o t&#7899;i x&#243;a n&#243; n&#7919;a. Ng&#432;&#7907;c l&#7841;i, n&#7871;u ghi DB tr&#432;&#7899;c r&#7891;i x&#243;a cache sau, c&#7917;a s&#7893; stale do &#273;&#7885;c &#273;&#7891;ng th&#7901;i h&#7865;p h&#417;n nhi&#7873;u, v&#236; thao t&#225;c ghi th&#432;&#7901;ng ch&#7853;m h&#417;n thao t&#225;c &#273;&#7885;c. &#272;&#243; l&#224; l&#253; do khuy&#7871;n ngh&#7883; chu&#7849;n c&#7911;a ng&#224;nh l&#224; ghi DB tr&#432;&#7899;c, x&#243;a cache sau, d&#249; nghe c&#243; v&#7867; ng&#432;&#7907;c tr&#7921;c gi&#225;c.</p><p>M&#224; k&#7875; c&#7843; ch&#7885;n &#273;&#250;ng th&#7913; t&#7921; th&#236; v&#7851;n c&#242;n khe h&#7903; nh&#7887;. &#272;&#7875; v&#225; tri&#7879;t &#273;&#7875;, ng&#432;&#7901;i ta d&#249;ng v&#224;i l&#7899;p: TTL l&#224;m n&#7873;n, r&#7891;i delayed double delete t&#7913;c l&#224; x&#243;a cache, ghi DB, ch&#7901; m&#7897;t ch&#250;t r&#7891;i x&#243;a cache l&#7847;n n&#7919;a &#273;&#7875; d&#7885;n s&#7841;ch d&#7919; li&#7879;u c&#361; m&#224; m&#7897;t request &#273;&#7885;c c&#243; th&#7875; &#273;&#227; k&#7883;p n&#7841;p v&#224;o; n&#7871;u l&#7879;nh x&#243;a cache th&#7845;t b&#7841;i th&#236; &#273;&#7849;y v&#224;o retry queue ch&#7913; kh&#244;ng b&#7887; qua; v&#224; cao c&#7845;p nh&#7845;t l&#224; d&#249;ng CDC, &#273;&#7885;c binlog c&#7911;a database b&#7857;ng c&#244;ng c&#7909; nh&#432; Debezium, h&#7877; DB thay &#273;&#7893;i th&#7853;t l&#224; t&#7921; &#273;&#7897;ng ph&#225;t &#273;i &#273;&#7875; x&#243;a cache, &#273;&#7843;m b&#7843;o kh&#244;ng miss v&#224; t&#225;ch h&#7859;n kh&#7887;i business code.</p><p>T&#7899;i &#273;&#226;y th&#236; m&#236;nh c&#243; m&#7897;t kho&#7843;nh kh&#7855;c v&#7905; &#242;a: b&#224;i to&#225;n "ghi DB r&#7891;i x&#243;a cache" th&#7921;c ra CH&#205;NH L&#192; c&#225;i Dual Write Problem m&#224; m&#236;nh t&#7915;ng g&#7863;p khi l&#224;m vi&#7879;c v&#7899;i message queue. L&#7841;i l&#224; chuy&#7879;n ghi v&#224;o hai h&#7879; th&#7889;ng m&#224; kh&#244;ng c&#243; transaction chung. V&#224; v&#236; b&#7843;n ch&#7845;t gi&#7889;ng nhau, n&#234;n l&#7901;i gi&#7843;i c&#361;ng gi&#7889;ng nhau: &#273;&#7915;ng t&#7921; ghi tay v&#224;o hai n&#417;i trong code, h&#227;y &#273;&#7875; m&#7897;t ngu&#7891;n s&#7921; th&#7853;t duy nh&#7845;t, th&#432;&#7901;ng l&#224; log c&#7911;a database, l&#224;m g&#7889;c &#273;&#7875; &#273;&#7891;ng b&#7897; c&#225;i c&#242;n l&#7841;i. Khi nh&#7853;n ra c&#249;ng m&#7897;t b&#224;i to&#225;n n&#7845;p d&#432;&#7899;i nhi&#7873;u l&#7899;p v&#7887; kh&#225;c nhau, m&#236;nh th&#7845;y m&#236;nh b&#7855;t &#273;&#7847;u t&#432; duy gi&#7889;ng m&#7897;t architect h&#417;n.</p><p>M&#7897;t b&#224;i h&#7885;c n&#7919;a v&#7873; t&#432; duy: kh&#244;ng ph&#7843;i d&#7919; li&#7879;u n&#224;o c&#361;ng n&#234;n cache nh&#432; nhau. T&#234;n s&#7843;n ph&#7849;m, m&#244; t&#7843;, h&#236;nh &#7843;nh th&#236; g&#7847;n nh&#432; kh&#244;ng &#273;&#7893;i, c&#7913; cache TTL d&#224;i tho&#7843;i m&#225;i. Gi&#225; th&#236; &#273;&#7893;i th&#7881;nh tho&#7843;ng, cache v&#7915;a ph&#7843;i v&#224; x&#243;a khi c&#243; thay &#273;&#7893;i. Nh&#432;ng t&#7891;n kho th&#236; kh&#225;c h&#7859;n, n&#243; &#273;&#7893;i li&#234;n t&#7909;c v&#224; c&#7847;n ch&#237;nh x&#225;c. V&#7899;i t&#7891;n kho, m&#236;nh t&#225;ch l&#224;m hai m&#7913;c: con s&#7889; hi&#7875;n th&#7883; tr&#234;n m&#224;n h&#236;nh danh s&#225;ch th&#236; kh&#244;ng c&#7847;n ch&#237;nh x&#225;c tuy&#7879;t &#273;&#7889;i, cache TTL v&#224;i gi&#226;y, ch&#7845;p nh&#7853;n h&#417;i c&#361;; nh&#432;ng l&#250;c kh&#225;ch b&#7845;m ch&#7889;t &#273;&#417;n v&#224; tr&#7915; kho th&#7853;t th&#236; TUY&#7878;T &#272;&#7888;I kh&#244;ng quy&#7871;t &#273;&#7883;nh d&#7921;a tr&#234;n cache, m&#224; &#273;i th&#7859;ng v&#224;o ngu&#7891;n s&#7921; th&#7853;t b&#7857;ng m&#7897;t thao t&#225;c atomic, v&#236; b&#225;n qu&#225; s&#7889; l&#432;&#7907;ng (oversell) l&#224; l&#7895;i kh&#244;ng &#273;&#432;&#7907;c ph&#233;p. Cache l&#224; &#273;&#7875; t&#259;ng t&#7889;c &#273;&#7885;c, kh&#244;ng ph&#7843;i &#273;&#7875; quy&#7871;t &#273;&#7883;nh nh&#7919;ng nghi&#7879;p v&#7909; c&#7847;n ch&#237;nh x&#225;c t&#7915;ng &#273;&#417;n v&#7883;.</p><p>Ph&#7847;n cu&#7889;i, m&#236;nh mu&#7889;n n&#243;i ri&#234;ng v&#7873; Redis, v&#236; nhi&#7873;u ng&#432;&#7901;i (c&#243; c&#7843; m&#236;nh ng&#224;y x&#432;a) ch&#7881; coi n&#243; l&#224; c&#225;i kho key-value cho nhanh, trong khi n&#243; th&#7921;c ra l&#224; m&#7897;t data structure server. N&#243; c&#243; Hash &#273;&#7875; l&#432;u object theo t&#7915;ng field, n&#234;n b&#7841;n update &#273;&#250;ng m&#7897;t field t&#7891;n kho m&#224; kh&#244;ng ph&#7843;i ghi l&#7841;i c&#7843; object. C&#243; Sorted Set &#273;&#7875; l&#224;m leaderboard hay rate limiter. C&#243; HyperLogLog &#273;&#7871;m s&#7889; l&#432;&#7907;ng unique x&#7845;p x&#7881; h&#224;ng t&#7927; ph&#7847;n t&#7917; ch&#7881; t&#7889;n m&#432;&#7901;i m&#7845;y KB. C&#243; Bitmap, c&#243; Geo &#273;&#7875; t&#236;m theo b&#225;n k&#237;nh, c&#243; Stream l&#224;m h&#224;ng &#273;&#7907;i. Bi&#7871;t ch&#7885;n &#273;&#250;ng c&#7845;u tr&#250;c cho &#273;&#250;ng b&#224;i to&#225;n l&#224; m&#7897;t n&#7917;a s&#7913;c m&#7841;nh c&#7911;a Redis.</p><p>C&#242;n v&#224;i th&#7913; v&#7873; Redis m&#224; m&#236;nh th&#7845;y n&#234;n n&#7855;m &#273;&#7875; hi&#7875;u s&#226;u. Khi RAM &#273;&#7847;y, Redis ph&#7843;i quy&#7871;t &#273;&#7883;nh x&#243;a key n&#224;o, &#273;&#243; l&#224; eviction policy, v&#224; b&#7841;n n&#234;n ph&#226;n bi&#7879;t LRU (x&#243;a c&#225;i l&#226;u r&#7891;i kh&#244;ng d&#249;ng) v&#7899;i LFU (x&#243;a c&#225;i &#237;t &#273;&#432;&#7907;c d&#249;ng th&#432;&#7901;ng xuy&#234;n) t&#249;y theo access pattern c&#7911;a m&#236;nh. Redis x&#7917; l&#253; l&#7879;nh tr&#234;n m&#7897;t lu&#7891;ng duy nh&#7845;t, &#273;&#243; l&#224; l&#253; do n&#243; nhanh v&#224; c&#361;ng l&#224; l&#253; do tuy&#7879;t &#273;&#7889;i tr&#225;nh c&#225;c l&#7879;nh n&#7863;ng nh&#432; KEYS tr&#234;n production. Nh&#432;ng c&#225;i t&#237;nh single-thread &#273;&#243; l&#7841;i bi&#7871;n th&#224;nh si&#234;u n&#259;ng l&#7921;c khi b&#7841;n c&#7847;n atomic: g&#243;i m&#7897;t &#273;o&#7841;n logic v&#224;o Lua script, Redis s&#7869; ch&#7841;y tr&#7885;n v&#7865;n &#273;o&#7841;n &#273;&#243; m&#224; kh&#244;ng cho l&#7879;nh n&#224;o chen v&#224;o, nh&#7901; v&#7853;y m&#224; nh&#7919;ng th&#7913; nh&#432; rate limiter hay tr&#7915; kho m&#7899;i ch&#7889;ng &#273;&#432;&#7907;c race condition gi&#7919;a h&#224;ng ngh&#236;n server. V&#7873; &#273;&#7897; b&#7873;n th&#236; Redis c&#243; RDB (ch&#7909;p snapshot, g&#7885;n, restore nhanh, nh&#432;ng m&#7845;t d&#7919; li&#7879;u gi&#7919;a hai l&#7847;n ch&#7909;p) v&#224; AOF (ghi log m&#7885;i l&#7879;nh, b&#7873;n h&#417;n nh&#432;ng n&#7863;ng h&#417;n), production th&#432;&#7901;ng b&#7853;t c&#7843; hai. V&#224; khi m&#7897;t con Redis kh&#244;ng g&#225;nh n&#7893;i n&#7919;a th&#236; c&#243; replication &#273;&#7875; scale &#273;&#7885;c, sentinel &#273;&#7875; t&#7921; &#273;&#7897;ng failover, v&#224; cluster &#273;&#7875; sharding d&#7919; li&#7879;u ra nhi&#7873;u node.</p><p>Ng&#7891;i vi&#7871;t l&#7841;i h&#7871;t nh&#7919;ng th&#7913; n&#224;y, m&#236;nh nh&#7853;n ra &#273;i&#7873;u quan tr&#7885;ng nh&#7845;t kh&#244;ng ph&#7843;i l&#224; t&#7915;ng k&#7929; thu&#7853;t ri&#234;ng l&#7867;, m&#224; l&#224; c&#225;ch nh&#236;n: caching kh&#244;ng bao gi&#7901; mi&#7877;n ph&#237;. M&#7895;i l&#7847;n b&#7841;n th&#234;m m&#7897;t l&#7899;p cache, b&#7841;n &#273;&#7893;i t&#7889;c &#273;&#7897; l&#7845;y s&#7921; ph&#7913;c t&#7841;p v&#7873; nh&#7845;t qu&#225;n. Vi&#7879;c c&#7911;a m&#7897;t ng&#432;&#7901;i l&#224;m h&#7879; th&#7889;ng kh&#244;ng ph&#7843;i l&#224; cache th&#7853;t nhi&#7873;u cho nhanh, m&#224; l&#224; bi&#7871;t cache c&#225;i g&#236;, cache bao l&#226;u, ch&#7845;p nh&#7853;n sai l&#7879;ch t&#7899;i &#273;&#226;u, v&#224; ch&#7895; n&#224;o th&#236; tuy&#7879;t &#273;&#7889;i kh&#244;ng &#273;&#432;&#7907;c tin v&#224;o cache. Hi&#7875;u &#273;&#432;&#7907;c ranh gi&#7899;i &#273;&#243;, theo m&#236;nh, m&#7899;i l&#224; c&#225;i ph&#226;n bi&#7879;t m&#7897;t ng&#432;&#7901;i bi&#7871;t d&#249;ng Redis v&#7899;i m&#7897;t ng&#432;&#7901;i thi&#7871;t k&#7871; &#273;&#432;&#7907;c h&#7879; th&#7889;ng.</p><p>M&#236;nh c&#361;ng m&#7899;i hi&#7875;u &#7903; m&#7913;c n&#224;y th&#244;i, ae n&#224;o r&#224;nh h&#417;n th&#236; b&#7893; sung, hay mu&#7889;n m&#236;nh &#273;&#224;o s&#226;u ph&#7847;n n&#224;o c&#7913; comment nh&#233;. M&#236;nh s&#7869; vi&#7871;t ti&#7871;p c&#225;c ch&#7911; &#273;&#7873; kh&#225;c tr&#234;n h&#224;nh tr&#236;nh &#244;n l&#234;n architect n&#224;y.</p>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: monolith to microservices ]]></title><description><![CDATA[H&#417;n 10 n&#259;m l&#224;m backend, m&#236;nh t&#7921; tin v&#7899;i m&#7899; code m&#236;nh vi&#7871;t ra, nh&#432;ng d&#7841;o n&#224;y ng&#7891;i &#244;n &#273;&#7875; ph&#7887;ng v&#7845;n v&#7883; tr&#237; architect m&#7899;i ng&#7897; ra m&#7897;t chuy&#7879;n: l&#234;n level kh&#244;ng n&#7857;m &#7903; chuy&#7879;n vi&#7871;t code gi&#7887;i h&#417;n, m&#224; &#7903; ch&#7895; t&#432; duy &#273;&#432;&#7907;c c&#7843; h&#7879; th&#7889;ng.]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-monolith-to-microservices</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-monolith-to-microservices</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Thu, 18 Jun 2026 12:34:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>H&#417;n 10 n&#259;m l&#224;m backend, m&#236;nh t&#7921; tin v&#7899;i m&#7899; code m&#236;nh vi&#7871;t ra, nh&#432;ng d&#7841;o n&#224;y ng&#7891;i &#244;n &#273;&#7875; ph&#7887;ng v&#7845;n v&#7883; tr&#237; architect m&#7899;i ng&#7897; ra m&#7897;t chuy&#7879;n: l&#234;n level kh&#244;ng n&#7857;m &#7903; chuy&#7879;n vi&#7871;t code gi&#7887;i h&#417;n, m&#224; &#7903; ch&#7895; t&#432; duy &#273;&#432;&#7907;c c&#7843; h&#7879; th&#7889;ng. V&#224; c&#243; m&#7897;t m&#7841;ch ki&#7871;n th&#7913;c m&#224; khi gh&#233;p &#273;&#7911; c&#225;c m&#7843;nh l&#7841;i, m&#236;nh th&#7845;y n&#243; &#273;&#7865;p nh&#432; m&#7897;t c&#226;u chuy&#7879;n, n&#234;n mu&#7889;n k&#7875; l&#7841;i cho ae nghe.</p><p>M&#7885;i chuy&#7879;n b&#7855;t &#273;&#7847;u t&#7915; c&#226;u h&#7887;i kinh &#273;i&#7875;n: "c&#243; n&#234;n t&#225;ch monolith ra microservices kh&#244;ng?". H&#7891;i x&#432;a m&#236;nh s&#7869; tr&#7843; l&#7901;i ki&#7875;u "t&#225;ch &#273;i cho hi&#7879;n &#273;&#7841;i", nh&#432;ng gi&#7901; m&#236;nh hi&#7875;u c&#226;u h&#7887;i &#273;&#250;ng ph&#7843;i l&#224; "v&#7845;n &#273;&#7873; th&#7921;c s&#7921; &#273;ang l&#224; g&#236;?". Microservices th&#7853;t ra ch&#7881; gi&#7843;i quy&#7871;t &#273;&#250;ng 3 th&#7913;: scale &#273;&#7897;c l&#7853;p, release nhanh h&#417;n, v&#224; c&#244; l&#7853;p l&#7895;i. N&#7871;u c&#225;i &#273;au c&#7911;a b&#7841;n kh&#244;ng n&#7857;m &#7903; 3 ch&#7895; &#273;&#243; th&#236; nhi&#7873;u khi m&#7897;t modular monolith g&#7885;n g&#224;ng c&#242;n r&#7867; h&#417;n nhi&#7873;u. Architect l&#224; ng&#432;&#7901;i b&#7855;t &#273;&#7847;u t&#7915; problem, kh&#244;ng ph&#7843;i b&#7855;t &#273;&#7847;u t&#7915; solution.</p><p>Nh&#432;ng &#273;&#7901;i kh&#244;ng cho kh&#244;ng c&#225;i g&#236;. T&#225;ch microservices xong l&#224; m&#7845;t lu&#244;n transaction. H&#7891;i c&#242;n monolith, tr&#7915; ti&#7873;n + tr&#7915; kho + t&#7841;o &#273;&#417;n n&#7857;m g&#7885;n trong m&#7897;t transaction, l&#7895;i c&#225;i l&#224; rollback s&#7841;ch s&#7869;. T&#225;ch ra, m&#7895;i service m&#7897;t database ri&#234;ng, gi&#7901; kh&#225;ch &#273;&#7863;t h&#224;ng m&#224; tr&#7915; ti&#7873;n th&#224;nh c&#244;ng nh&#432;ng tr&#7915; kho fail th&#236; sao, ch&#7859;ng l&#7869; &#273;&#7875; kh&#225;ch m&#7845;t ti&#7873;n oan? "bi&#7871;t l&#224; ph&#7843;i x&#7917; l&#253; nh&#432;ng l&#250;c &#273;&#7847;u ngh&#297; m&#227;i kh&#244;ng ra c&#225;ch cho &#273;&#7865;p" &#128512;</p><p>C&#225;i t&#234;n &#273;&#7847;u ti&#234;n hi&#7879;n ra trong &#273;&#7847;u l&#224; 2PC (Two-Phase Commit), ki&#7875;u transaction ph&#226;n t&#225;n trong s&#225;ch gi&#225;o khoa. Nghe th&#236; hay, n&#243; h&#7913;a nh&#7845;t qu&#225;n t&#7913;c th&#236;. Nh&#432;ng &#273;&#224;o k&#7929; m&#7899;i th&#7845;y c&#225;i gi&#225; c&#7911;a n&#243;: kh&#243;a t&#224;i nguy&#234;n k&#233;o d&#224;i ch&#7901; t&#7845;t c&#7843; c&#225;c b&#234;n, m&#224; ch&#7881; c&#7847;n &#244;ng coordinator &#273;i&#7873;u ph&#7889;i ch&#7871;t gi&#7919;a ch&#7915;ng l&#224; c&#7843; &#273;&#225;m k&#7865;t c&#7913;ng gi&#7919; lock v&#244; th&#7901;i h&#7841;n. N&#243;i c&#225;ch kh&#225;c 2PC &#273;&#225;nh &#273;&#7893;i t&#237;nh s&#7861;n s&#224;ng v&#224; kh&#7843; n&#259;ng scale &#273;&#7875; l&#7845;y nh&#7845;t qu&#225;n, m&#224; &#273;&#226;y l&#7841;i &#273;&#250;ng l&#224; 2 th&#7913; microservices kh&#244;ng th&#7875; hy sinh. Th&#7871; l&#224; lo&#7841;i.</p><p>L&#7901;i gi&#7843;i th&#7921;c t&#7871; l&#224; Saga. Thay v&#236; m&#7897;t transaction to &#273;&#249;ng, ng&#432;&#7901;i ta d&#249;ng m&#7897;t chu&#7895;i giao d&#7883;ch nh&#7887;, b&#432;&#7899;c n&#224;o l&#7895;i th&#236; ch&#7841;y m&#7897;t giao d&#7883;ch b&#249; tr&#7915; &#273;&#7875; ho&#224;n t&#225;c c&#225;i &#273;&#227; l&#224;m. C&#243; m&#7897;t m&#7865;o m&#236;nh th&#7845;y r&#7845;t hay: thanh to&#225;n th&#7853;t kh&#244;ng tr&#7915; ti&#7873;n ngay, m&#224; gi&#7919; ti&#7873;n tr&#432;&#7899;c (authorize) r&#7891;i m&#7899;i tr&#7915; th&#7853;t (capture) khi m&#7885;i b&#432;&#7899;c &#273;&#7873;u &#7893;n. Nh&#7901; v&#7853;y l&#250;c l&#7895;i ch&#7881; c&#7847;n nh&#7843; ti&#7873;n ra, kh&#225;ch kh&#244;ng m&#7845;t m&#7897;t xu.</p><p>&#272;&#224;o ti&#7871;p t&#7899;i Saga th&#236; &#273;&#7909;ng ngay CAP theorem, v&#224; &#273;&#226;y l&#224; ch&#7895; m&#236;nh t&#7915;ng hi&#7875;u sai bao n&#259;m. Ai c&#361;ng thu&#7897;c l&#242;ng c&#226;u "ch&#7885;n 2 trong 3", nh&#432;ng s&#7921; th&#7853;t l&#224; c&#225;i Partition (m&#7841;ng &#273;&#7913;t) n&#243; l&#224; th&#7913; b&#7855;t bu&#7897;c ph&#7843;i ch&#7845;p nh&#7853;n, v&#236; m&#7841;ng th&#236; ch&#7855;c ch&#7855;n s&#7869; c&#243; l&#250;c l&#7895;i. N&#234;n l&#7921;a ch&#7885;n th&#7853;t s&#7921; ch&#7881; c&#242;n l&#224;: khi m&#7841;ng &#273;&#7913;t, b&#7841;n hy sinh nh&#7845;t qu&#225;n hay hy sinh t&#237;nh s&#7861;n s&#224;ng. M&#224; ti&#7879;n th&#7875;, ch&#7919; C trong CAP v&#7899;i ch&#7919; C trong ACID l&#224; hai th&#7913; ho&#224;n to&#224;n kh&#225;c nhau, h&#7891;i x&#432;a m&#236;nh c&#7913; t&#432;&#7903;ng l&#224; m&#7897;t ;))</p><p>R&#7891;i t&#7899;i m&#7897;t c&#225;i bug kinh &#273;i&#7875;n m&#224; r&#7845;t nhi&#7873;u ng&#432;&#7901;i m&#7855;c m&#224; kh&#244;ng bi&#7871;t, g&#7885;i l&#224; Dual Write Problem. Service l&#224;m xong vi&#7879;c th&#236; ph&#7843;i ph&#225;t event &#273;&#7875; b&#225;o cho b&#432;&#7899;c sau, nh&#432;ng ghi v&#224;o database v&#224; ph&#225;t event l&#234;n message broker l&#224; hai h&#7879; th&#7889;ng kh&#225;c nhau, kh&#244;ng c&#243; transaction n&#224;o bao tr&#249;m c&#7843; hai. Crash ngay khe h&#7903; gi&#7919;a hai b&#432;&#7899;c &#273;&#243; l&#224; d&#7919; li&#7879;u l&#7879;ch nhau ngay. L&#7901;i gi&#7843;i l&#224; Outbox pattern: ghi lu&#244;n c&#225;i event v&#224;o chung database trong c&#249;ng m&#7897;t transaction v&#7899;i d&#7919; li&#7879;u nghi&#7879;p v&#7909;, r&#7891;i &#273;&#7875; m&#7897;t ti&#7871;n tr&#236;nh ri&#234;ng &#273;&#7885;c ra v&#224; ph&#225;t &#273;i sau. &#272;&#417;n gi&#7843;n m&#224; th&#244;ng minh.</p><p>V&#224; m&#7843;nh gh&#233;p cu&#7889;i c&#249;ng l&#224; idempotency, c&#225;i n&#224;y th&#236; qu&#225; quen v&#7899;i ae backend. Trong h&#7879; ph&#226;n t&#225;n kh&#244;ng bao gi&#7901; &#273;&#7843;m b&#7843;o &#273;&#432;&#7907;c "exactly-once", client retry, broker g&#7917;i tr&#249;ng, Saga th&#7917; l&#7841;i, &#273;&#7911; ki&#7875;u khi&#7871;n c&#249;ng m&#7897;t l&#7879;nh ch&#7841;y nhi&#7873;u l&#7847;n. C&#225;ch x&#7917; l&#253; l&#224; idempotency key: client g&#7917;i k&#232;m m&#7897;t id duy nh&#7845;t, server th&#7845;y tr&#249;ng th&#236; tr&#7843; l&#7841;i k&#7871;t qu&#7843; c&#361; thay v&#236; l&#224;m l&#7841;i t&#7915; &#273;&#7847;u, nh&#7901; v&#7853;y kh&#244;ng bao gi&#7901; tr&#7915; ti&#7873;n kh&#225;ch hai l&#7847;n. &#272;&#226;y c&#361;ng ch&#237;nh l&#224; l&#253; do Stripe hay PayPal &#273;&#7873;u c&#243; c&#225;i header Idempotency-Key.</p><p>&#272;i&#7873;u l&#224;m m&#236;nh t&#226;m &#273;&#7855;c nh&#7845;t kh&#244;ng ph&#7843;i t&#7915;ng m&#243;n ri&#234;ng l&#7867;, m&#224; l&#224; khi x&#226;u chu&#7895;i l&#7841;i: t&#225;ch microservices n&#234;n m&#7845;t transaction, m&#7845;t transaction n&#234;n c&#7847;n Saga, Saga th&#236; d&#237;nh eventual consistency n&#234;n ph&#7843;i hi&#7875;u CAP, Saga c&#7847;n ph&#225;t event an to&#224;n n&#234;n c&#7847;n Outbox, Outbox g&#7917;i tr&#249;ng n&#234;n ph&#7843;i c&#243; Idempotency. H&#243;a ra kh&#244;ng ph&#7843;i 6 ch&#7911; &#273;&#7873; r&#7901;i r&#7841;c &#273;&#7875; h&#7885;c v&#7865;t, m&#224; l&#224; m&#7897;t c&#226;u chuy&#7879;n logic n&#7889;i li&#7873;n nhau. V&#224; m&#236;nh ngh&#297; &#273;&#243; m&#7899;i &#273;&#250;ng l&#224; th&#7913; ph&#226;n bi&#7879;t m&#7897;t ng&#432;&#7901;i bi&#7871;t keyword v&#7899;i m&#7897;t ng&#432;&#7901;i th&#7921;c s&#7921; hi&#7875;u h&#7879; th&#7889;ng.</p>]]></content:encoded></item><item><title><![CDATA[Dành cho ai mới bắt đầu với LLM!]]></title><description><![CDATA[C&#243; qu&#225; nhi&#7873;u n&#7897;i dung v&#7873; LLM tr&#234;n internet, v&#224; m&#7895;i ng&#224;y l&#7841;i c&#243; th&#234;m th&#7913; m&#7899;i.]]></description><link>https://duthaho.substack.com/p/danh-cho-ai-moi-bat-au-voi-llm</link><guid isPermaLink="false">https://duthaho.substack.com/p/danh-cho-ai-moi-bat-au-voi-llm</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Fri, 13 Mar 2026 10:34:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!SJmI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>C&#243; qu&#225; nhi&#7873;u n&#7897;i dung v&#7873; LLM tr&#234;n internet, v&#224; m&#7895;i ng&#224;y l&#7841;i c&#243; th&#234;m th&#7913; m&#7899;i. M&#236;nh &#273;&#227; t&#7893;ng h&#7907;p c&#225;c kh&#225;i ni&#7879;m quan tr&#7885;ng nh&#7845;t v&#224;o m&#7897;t b&#224;i vi&#7871;t n&#224;y, &#273;&#226;y l&#224; nh&#7919;ng kh&#225;i ni&#7879;m m&#224; m&#236;nh cho l&#224; thi&#7871;t y&#7871;u n&#7871;u b&#7841;n m&#7899;i b&#7855;t &#273;&#7847;u h&#7885;c l&#297;nh v&#7921;c n&#224;y.</p><p>M&#236;nh khuy&#7871;n kh&#237;ch b&#7841;n &#273;&#7885;c th&#234;m c&#225;c b&#224;i vi&#7871;t g&#7889;c &#273;&#7875; hi&#7875;u s&#226;u h&#417;n.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2><strong>Agent l&#224; g&#236;?</strong></h2><p>&#8220;Agent&#8221; c&#243; th&#7875; &#273;&#432;&#7907;c &#273;&#7883;nh ngh&#297;a theo nhi&#7873;u c&#225;ch. M&#7895;i c&#244;ng ty c&#243; m&#7897;t &#273;&#7883;nh ngh&#297;a ri&#234;ng:</p><blockquote><p><em>&#8220;Agent l&#224; c&#225;c h&#7879; th&#7889;ng <strong>ho&#224;n th&#224;nh nhi&#7879;m v&#7909; m&#7897;t c&#225;ch &#273;&#7897;c l&#7853;p</strong> thay cho b&#7841;n.&#8221;</em> &#8212; OpenAI</p></blockquote><blockquote><p><em>&#8220;&#7902; d&#7841;ng c&#417; b&#7843;n nh&#7845;t, m&#7897;t Agent AI t&#7841;o sinh c&#243; th&#7875; &#273;&#432;&#7907;c &#273;&#7883;nh ngh&#297;a l&#224; m&#7897;t &#7913;ng d&#7909;ng c&#7889; g&#7855;ng <strong>&#273;&#7841;t &#273;&#432;&#7907;c m&#7909;c ti&#234;u</strong> b&#7857;ng c&#225;ch quan s&#225;t th&#7871; gi&#7899;i v&#224; h&#224;nh &#273;&#7897;ng d&#7921;a tr&#234;n n&#243; <strong>s&#7917; d&#7909;ng c&#225;c c&#244;ng c&#7909;</strong> c&#243; s&#7861;n. Agent c&#243; t&#237;nh <strong>t&#7921; ch&#7911;</strong> v&#224; c&#243; th&#7875; <strong>h&#224;nh &#273;&#7897;ng &#273;&#7897;c l&#7853;p</strong> m&#224; kh&#244;ng c&#7847;n s&#7921; can thi&#7879;p c&#7911;a con ng&#432;&#7901;i.&#8221;</em> &#8212; Google</p></blockquote><blockquote><p><em>&#8220;T&#7841;i Anthropic, ch&#250;ng t&#244;i ph&#226;n lo&#7841;i t&#7845;t c&#7843; c&#225;c bi&#7871;n th&#7875; n&#224;y th&#224;nh <strong>h&#7879; th&#7889;ng agentic</strong>, nh&#432;ng ph&#226;n bi&#7879;t r&#245; r&#224;ng v&#7873; ki&#7871;n tr&#250;c gi&#7919;a <strong>workflow</strong> (h&#7879; th&#7889;ng &#273;&#432;&#7907;c &#273;i&#7873;u ph&#7889;i theo <strong>lu&#7891;ng code &#273;&#7883;nh s&#7861;n</strong>) v&#224; <strong>agent</strong> (h&#7879; th&#7889;ng m&#224; LLM t&#7921; &#273;&#7897;ng <strong>&#273;i&#7873;u khi&#7875;n quy tr&#236;nh v&#224; s&#7917; d&#7909;ng c&#244;ng c&#7909;</strong>).&#8221;</em> &#8212; Anthropic</p></blockquote><p>D&#249; &#273;&#7883;nh ngh&#297;a kh&#225;c nhau, c&#7843; ba &#273;&#7873;u &#273;&#7891;ng &#253; r&#7857;ng agent:</p><ul><li><p><strong>Ho&#7841;t &#273;&#7897;ng t&#7921; ch&#7911;</strong> &#273;&#7875; th&#7921;c hi&#7879;n nhi&#7879;m v&#7909;</p></li><li><p><strong>T&#7921; &#273;&#432;a ra quy&#7871;t &#273;&#7883;nh</strong> v&#7873; b&#432;&#7899;c ti&#7871;p theo</p></li><li><p><strong>S&#7917; d&#7909;ng c&#244;ng c&#7909;</strong> &#273;&#7875; &#273;&#7841;t m&#7909;c ti&#234;u</p></li></ul><h3><strong>Ba th&#224;nh ph&#7847;n ch&#237;nh c&#7911;a m&#7897;t Agent</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!SJmI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!SJmI!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png 424w, /__u/substackcdn.com/image/fetch/$s_!SJmI!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png 848w, /__u/substackcdn.com/image/fetch/$s_!SJmI!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png 1272w, /__u/substackcdn.com/image/fetch/$s_!SJmI!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!SJmI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png" width="410" height="369" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:369,&quot;width&quot;:410,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!SJmI!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png 424w, /__u/substackcdn.com/image/fetch/$s_!SJmI!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png 848w, /__u/substackcdn.com/image/fetch/$s_!SJmI!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png 1272w, /__u/substackcdn.com/image/fetch/$s_!SJmI!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f11124b-cad1-4007-8362-4fd0a936a6a5_410x369.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p><strong>Model: </strong>M&#244; h&#236;nh ng&#244;n ng&#7919; t&#7841;o ra output</p></li><li><p><strong>Instructions / Orchestration: </strong>H&#432;&#7899;ng d&#7851;n v&#224; quy t&#7855;c &#273;&#7883;nh ngh&#297;a c&#225;ch agent ho&#7841;t &#273;&#7897;ng</p></li><li><p><strong>Tools: </strong>Cho ph&#233;p agent t&#432;&#417;ng t&#225;c v&#7899;i d&#7919; li&#7879;u v&#224; d&#7883;ch v&#7909; b&#234;n ngo&#224;i</p></li></ul><div><hr></div><h2><strong>1. Model &#8212; M&#244; h&#236;nh ng&#244;n ng&#7919;</strong></h2><p>Model (m&#244; h&#236;nh) ch&#237;nh l&#224; m&#244; h&#236;nh ng&#244;n ng&#7919; (Language Model). N&#243;i &#273;&#417;n gi&#7843;n, n&#243; d&#7921; &#273;o&#225;n t&#7915; ho&#7863;c chu&#7895;i t&#7915; ti&#7871;p theo d&#7921;a tr&#234;n nh&#7919;ng t&#7915; &#273;&#227; th&#7845;y tr&#432;&#7899;c &#273;&#243;.</p><h3><strong>Agent kh&#225;c Model nh&#432; th&#7871; n&#224;o?</strong></h3><p>Agent v&#224; model <strong>kh&#244;ng ph&#7843;i l&#224; m&#7897;t</strong>. Model ch&#7881; l&#224; m&#7897;t th&#224;nh ph&#7847;n b&#234;n trong agent. Trong khi model b&#7883; gi&#7899;i h&#7841;n &#7903; vi&#7879;c d&#7921; &#273;o&#225;n ph&#7843;n h&#7891;i d&#7921;a tr&#234;n d&#7919; li&#7879;u hu&#7845;n luy&#7879;n, agent m&#7903; r&#7897;ng kh&#7843; n&#259;ng n&#224;y b&#7857;ng c&#225;ch h&#224;nh &#273;&#7897;ng &#273;&#7897;c l&#7853;p &#273;&#7875; &#273;&#7841;t m&#7909;c ti&#234;u c&#7909; th&#7875;.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!MlMd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!MlMd!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png 424w, /__u/substackcdn.com/image/fetch/$s_!MlMd!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png 848w, /__u/substackcdn.com/image/fetch/$s_!MlMd!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png 1272w, /__u/substackcdn.com/image/fetch/$s_!MlMd!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!MlMd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png" width="909" height="357" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:357,&quot;width&quot;:909,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44009,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://duthaho.substack.com/i/190818960?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!MlMd!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png 424w, /__u/substackcdn.com/image/fetch/$s_!MlMd!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png 848w, /__u/substackcdn.com/image/fetch/$s_!MlMd!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png 1272w, /__u/substackcdn.com/image/fetch/$s_!MlMd!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F097f6acb-39fc-4e4c-a4e2-43d5174da02d_909x357.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Large Language Models (LLM) &#8212; M&#244; h&#236;nh ng&#244;n ng&#7919; l&#7899;n</strong></h3><p>Ch&#7919; &#8220;Large&#8221; trong LLM ch&#7881; s&#7889; l&#432;&#7907;ng tham s&#7889; (parameters) m&#224; m&#244; h&#236;nh &#273;&#432;&#7907;c hu&#7845;n luy&#7879;n. C&#225;c m&#244; h&#236;nh n&#224;y c&#243; th&#7875; c&#243; h&#224;ng tr&#259;m t&#7927; ho&#7863;c th&#7853;m ch&#237; h&#224;ng ngh&#236;n t&#7927; tham s&#7889;, &#273;&#432;&#7907;c hu&#7845;n luy&#7879;n tr&#234;n l&#432;&#7907;ng d&#7919; li&#7879;u kh&#7893;ng l&#7891; v&#224; c&#7847;n s&#7913;c m&#7841;nh t&#237;nh to&#225;n r&#7845;t l&#7899;n.</p><blockquote><p><strong>V&#237; d&#7909; LLM:</strong> GPT-5o, Gemini Pro 2.5, Claude 4.6 Sonnet.</p></blockquote><h3><strong>Small Language Models (SLM) &#8212; M&#244; h&#236;nh ng&#244;n ng&#7919; nh&#7887;</strong></h3><p>SLM &#273;&#432;&#7907;c s&#7917; d&#7909;ng cho c&#225;c t&#225;c v&#7909; &#273;&#417;n gi&#7843;n h&#417;n, c&#7847;n &#237;t d&#7919; li&#7879;u v&#224; tham s&#7889; h&#417;n, nh&#7865; h&#417;n khi ch&#7841;y v&#224; d&#7877; ki&#7875;m so&#225;t h&#417;n. SLM th&#432;&#7901;ng c&#243; d&#432;&#7899;i 10 t&#7927; tham s&#7889;, gi&#7843;m &#273;&#225;ng k&#7875; chi ph&#237; t&#237;nh to&#225;n v&#224; n&#259;ng l&#432;&#7907;ng.</p><blockquote><p><strong>V&#237; d&#7909; SLM:</strong> Llama 3.1 8B (Meta), Gemma2 9B (Google), Mistral 7B (Mistral AI).</p></blockquote><h3><strong>Open Source vs Closed Source</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!rVvf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!rVvf!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png 424w, /__u/substackcdn.com/image/fetch/$s_!rVvf!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png 848w, /__u/substackcdn.com/image/fetch/$s_!rVvf!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png 1272w, /__u/substackcdn.com/image/fetch/$s_!rVvf!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!rVvf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png" width="907" height="313" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:313,&quot;width&quot;:907,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:36912,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://duthaho.substack.com/i/190818960?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!rVvf!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png 424w, /__u/substackcdn.com/image/fetch/$s_!rVvf!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png 848w, /__u/substackcdn.com/image/fetch/$s_!rVvf!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png 1272w, /__u/substackcdn.com/image/fetch/$s_!rVvf!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa3c66c5-9ce0-4973-8d11-9aef77d302c0_907x313.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>B&#7841;n c&#243; th&#7875; t&#236;m c&#225;c m&#244; h&#236;nh open source tr&#234;n <a href="https://huggingface.co/models?pipeline_tag=text-generation&amp;sort=trending">Hugging Face</a>.</p></blockquote><div><hr></div><h2><strong>2. Instructions / Orchestration &#8212; H&#432;&#7899;ng d&#7851;n v&#224; &#273;i&#7873;u ph&#7889;i</strong></h2><p>Instructions l&#224; c&#225;c h&#432;&#7899;ng d&#7851;n v&#224; r&#224;o ch&#7855;n r&#245; r&#224;ng &#273;&#7883;nh ngh&#297;a c&#225;ch agent ho&#7841;t &#273;&#7897;ng. Google g&#7885;i th&#224;nh ph&#7847;n n&#224;y l&#224; &#8220;Orchestration&#8221; (&#273;i&#7873;u ph&#7889;i), bao g&#7891;m ba l&#7899;p:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!z7s0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!z7s0!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png 424w, /__u/substackcdn.com/image/fetch/$s_!z7s0!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png 848w, /__u/substackcdn.com/image/fetch/$s_!z7s0!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png 1272w, /__u/substackcdn.com/image/fetch/$s_!z7s0!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!z7s0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png" width="505" height="421" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:421,&quot;width&quot;:505,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!z7s0!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png 424w, /__u/substackcdn.com/image/fetch/$s_!z7s0!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png 848w, /__u/substackcdn.com/image/fetch/$s_!z7s0!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png 1272w, /__u/substackcdn.com/image/fetch/$s_!z7s0!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f74e1fc-0742-4d8f-82b5-eb6c3f5d45ba_505x421.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p><strong>Instructions</strong> &#8212; H&#432;&#7899;ng d&#7851;n c&#7909; th&#7875;</p></li><li><p><strong>Memory</strong> &#8212; B&#7897; nh&#7899;</p></li><li><p><strong>Model-based Reasoning/Planning</strong> &#8212; Suy lu&#7853;n v&#224; l&#7853;p k&#7871; ho&#7841;ch</p></li></ul><p>Orchestration tu&#226;n theo m&#7897;t m&#244; h&#236;nh tu&#7847;n ho&#224;n: agent thu th&#7853;p th&#244;ng tin, x&#7917; l&#253; n&#7897;i b&#7897;, r&#7891;i s&#7917; d&#7909;ng k&#7871;t qu&#7843; &#273;&#7875; x&#225;c &#273;&#7883;nh b&#432;&#7899;c ti&#7871;p theo.</p><h3><strong>2.1 Instructions &#8212; H&#432;&#7899;ng d&#7851;n</strong></h3><p>Instructions c&#243; th&#7875; bao g&#7891;m m&#7909;c ti&#234;u, h&#7891; s&#417;, vai tr&#242;, quy t&#7855;c, v&#224; th&#244;ng tin b&#7841;n cho l&#224; quan tr&#7885;ng &#273;&#7875; n&#226;ng cao h&#224;nh vi c&#7911;a model.</p><pre><code><code>system_prompt = """
You are a friendly and a programming tutor.
Always explain concepts in a simple and clear way, using examples when possible.
If the user asks something unrelated to programming, politely bring the conversation back to programming topics.
"""
</code></code></pre><p>Trong v&#237; d&#7909; n&#224;y, ch&#250;ng ta &#273;&#227;:</p><ul><li><p>&#272;&#7883;nh ngh&#297;a <strong>vai tr&#242;</strong> c&#7911;a LLM (gia s&#432; l&#7853;p tr&#236;nh th&#226;n thi&#7879;n)</p></li><li><p>M&#244; t&#7843; <strong>h&#224;nh vi mong mu&#7889;n</strong> (gi&#7843;i th&#237;ch &#273;&#417;n gi&#7843;n, k&#232;m v&#237; d&#7909;)</p></li><li><p>&#272;&#7863;t <strong>gi&#7899;i h&#7841;n</strong> v&#7873; ch&#7911; &#273;&#7873; &#273;&#432;&#7907;c ph&#233;p n&#243;i</p></li></ul><h3><strong>2.2 Reasoning/Planning &#8212; Suy lu&#7853;n v&#224; l&#7853;p k&#7871; ho&#7841;ch</strong></h3><p>M&#7897;t s&#7889; k&#7929; thu&#7853;t suy lu&#7853;n gi&#250;p agent c&#243; c&#225;ch ti&#7871;p nh&#7853;n th&#244;ng tin, suy lu&#7853;n n&#7897;i b&#7897; v&#224; &#273;&#432;a ra quy&#7871;t &#273;&#7883;nh c&#243; c&#7845;u tr&#250;c.</p><h4><strong>Chain-of-Thought (CoT) &#8212; Chu&#7895;i suy ngh&#297;</strong></h4><p>CoT l&#224; k&#7929; thu&#7853;t prompt engineering cho ph&#233;p model suy lu&#7853;n qua c&#225;c b&#432;&#7899;c trung gian. Thay v&#236; nh&#7843;y th&#7859;ng &#273;&#7871;n c&#226;u tr&#7843; l&#7901;i, model &#273;&#432;&#7907;c y&#234;u c&#7847;u gi&#7843;i th&#237;ch t&#7915;ng b&#432;&#7899;c tr&#432;&#7899;c khi &#273;&#432;a ra k&#7871;t lu&#7853;n cu&#7889;i c&#249;ng.</p><pre><code><code>system_prompt = f"""
You are the assistant for a tiny candle shop.

Step 1: Check whether the user mentions either of our candles:
   &#8226; Forest Breeze (woodsy scent, 40 h burn, $18)
   &#8226; Vanilla Glow (warm vanilla, 35 h burn, $16)

Step 2: List any assumptions the user makes
   (e.g. "Vanilla Glow lasts 50 h" or "Forest Breeze is unscented").

Step 3: If an assumption is wrong, correct it politely.
   Then answer the question in a friendly tone.

Use exactly this output format:
Step 1: &lt;your reasoning&gt;
Step 2: &lt;your reasoning&gt;
Step 3: &lt;your reasoning&gt;
Response to user: &lt;final answer&gt;
"""
</code></code></pre><blockquote><p><strong>T&#7841;i sao CoT hi&#7879;u qu&#7843;?</strong> N&#243; bu&#7897;c model ph&#7843;i chia nh&#7887; v&#7845;n &#273;&#7873;, kh&#244;ng b&#7887; qua b&#432;&#7899;c trung gian n&#224;o, gi&#7843;m thi&#7875;u l&#7895;i suy lu&#7853;n.</p></blockquote><h4><strong>ReAct &#8212; K&#7871;t h&#7907;p suy lu&#7853;n v&#224; h&#224;nh &#273;&#7897;ng</strong></h4><p>ReAct k&#7871;t h&#7907;p reasoning (suy lu&#7853;n) v&#224; acting (h&#224;nh &#273;&#7897;ng). Agent ti&#7871;p t&#7909;c v&#242;ng l&#7863;p Thought &#8594; Action &#8594; Observation cho &#273;&#7871;n khi ho&#224;n th&#224;nh nhi&#7879;m v&#7909;. K&#7929; thu&#7853;t n&#224;y kh&#7855;c ph&#7909;c &#273;i&#7875;m y&#7871;u c&#7911;a CoT (nh&#432; hallucination) v&#236; n&#243; suy lu&#7853;n d&#7921;a tr&#234;n th&#244;ng tin th&#7921;c t&#7871; thu &#273;&#432;&#7907;c t&#7915; c&#225;c h&#224;nh &#273;&#7897;ng.</p><pre><code><code>system_prompt = """You are an agent that can call two tools:

1. CurrencyAPI:
   &#8226; input: {base_currency, quote_currency}
   &#8226; returns: exchange rate (float)

2. Calculator:
   &#8226; input: {arithmetic_expression}
   &#8226; returns: result (float)

Follow strictly this response format:

Thought: &lt;your reasoning&gt;
Action: &lt;ToolName&gt;[&lt;arguments&gt;]
Observation: &lt;tool result&gt;
&#8230; (repeat as needed)
Answer: &lt;final answer for the user&gt;
"""
</code></code></pre><p><strong>Lu&#7891;ng ho&#7841;t &#273;&#7897;ng:</strong></p><ol><li><p><strong>Thought</strong> &#8212; Model suy ngh&#297; v&#7873; nh&#7919;ng g&#236; c&#7847;n l&#224;m</p></li><li><p><strong>Action</strong> &#8212; G&#7885;i c&#244;ng c&#7909; ph&#249; h&#7907;p</p></li><li><p><strong>Observation</strong> &#8212; Nh&#7853;n k&#7871;t qu&#7843; t&#7915; c&#244;ng c&#7909;</p></li><li><p>L&#7863;p l&#7841;i cho &#273;&#7871;n khi c&#243; &#273;&#7911; th&#244;ng tin &#273;&#7875; tr&#7843; l&#7901;i</p></li></ol><p>C&#225;c k&#7929; thu&#7853;t n&#224;y h&#7919;u &#237;ch khi b&#7841;n c&#7847;n <strong>t&#237;nh minh b&#7841;ch</strong> v&#224; <strong>kh&#7843; n&#259;ng ki&#7875;m so&#225;t</strong> &#273;&#7889;i v&#7899;i l&#253; do agent &#273;&#432;a ra c&#226;u tr&#7843; l&#7901;i ho&#7863;c th&#7921;c hi&#7879;n h&#224;nh &#273;&#7897;ng.</p><h3><strong>2.3 Memory &#8212; B&#7897; nh&#7899;</strong></h3><p>LLM <strong>kh&#244;ng c&#243; b&#7897; nh&#7899; t&#237;ch h&#7907;p s&#7861;n</strong>. &#8220;Memory&#8221; &#7903; &#273;&#226;y l&#224; n&#7897;i dung b&#7841;n &#273;&#432;a v&#224;o prompt &#273;&#7875; cung c&#7845;p ng&#7919; c&#7843;nh cho model. C&#243; hai lo&#7841;i:</p><ul><li><p><strong>Short-term memory (B&#7897; nh&#7899; ng&#7855;n h&#7841;n):</strong> Ng&#7919; c&#7843;nh tr&#7921;c ti&#7871;p m&#224; model truy c&#7853;p &#273;&#432;&#7907;c trong cu&#7897;c h&#7897;i tho&#7841;i &#8212; tin nh&#7855;n g&#7847;n nh&#7845;t, N tin nh&#7855;n cu&#7889;i, ho&#7863;c b&#7843;n t&#243;m t&#7855;t. Khi &#273;&#7841;t gi&#7899;i h&#7841;n context window, c&#225;c tin nh&#7855;n c&#361; s&#7869; b&#7883; lo&#7841;i b&#7887;.</p></li><li><p><strong>Long-term memory (B&#7897; nh&#7899; d&#224;i h&#7841;n):</strong> L&#432;u tr&#7919; th&#244;ng tin quan tr&#7885;ng v&#432;&#7907;t ngo&#224;i context window &#273;&#7875; s&#7917; d&#7909;ng sau. Th&#432;&#7901;ng d&#249;ng vector database v&#224; k&#7929; thu&#7853;t RAG &#273;&#7875; truy xu&#7845;t khi c&#7847;n.</p></li></ul><pre><code><code># Qu&#7843;n l&#253; short-term memory &#273;&#417;n gi&#7843;n
chat_history = []

# Tin nh&#7855;n th&#7913; nh&#7845;t
user_input = "I would like to buy 1 Forest Breeze. Can I pay $10?"
full_content = f"System instructions: {system_prompt}\n\n Chat History: {chat_history} \n\n User message: {user_input}"
response = client.models.generate_content(model="gemini-2.0-flash", contents=full_content)

# Th&#234;m v&#224;o l&#7883;ch s&#7917;
chat_history.append({"role": "user", "content": user_input})
chat_history.append({"role": "assistant", "content": response.text})

# Tin nh&#7855;n th&#7913; hai &#8212; model nh&#7899; &#273;&#432;&#7907;c n&#7897;i dung tr&#432;&#7899;c nh&#7901; chat_history
user_input = "What did I say I wanted to buy?"
full_content = f"System instructions: {system_prompt}\n\n Chat History: {chat_history} \n\n User message: {user_input}"
response = client.models.generate_content(model="gemini-2.0-flash", contents=full_content)
</code></code></pre><p>Bi&#7871;n <code>full_content</code> bao g&#7891;m: <code>system_prompt</code> (h&#432;&#7899;ng d&#7851;n + k&#7929; thu&#7853;t suy lu&#7853;n) + <code>chat_history</code> (b&#7897; nh&#7899;) + <code>user_input</code> (c&#226;u h&#7887;i m&#7899;i). &#272;&#226;y ch&#237;nh l&#224; c&#225;ch k&#7871;t h&#7907;p c&#7843; ba l&#7899;p c&#7911;a Orchestration.</p><div><hr></div><h2><strong>3. Tools &#8212; C&#244;ng c&#7909;</strong></h2><p>Model r&#7845;t gi&#7887;i x&#7917; l&#253; th&#244;ng tin, nh&#432;ng b&#7883; gi&#7899;i h&#7841;n b&#7903;i d&#7919; li&#7879;u hu&#7845;n luy&#7879;n. V&#7899;i c&#244;ng c&#7909;, model c&#243; th&#7875; t&#432;&#417;ng t&#225;c v&#7899;i h&#7879; th&#7889;ng b&#234;n ngo&#224;i v&#224; truy c&#7853;p ki&#7871;n th&#7913;c v&#432;&#7907;t ngo&#224;i d&#7919; li&#7879;u hu&#7845;n luy&#7879;n.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!fY6e!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!fY6e!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png 424w, /__u/substackcdn.com/image/fetch/$s_!fY6e!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png 848w, /__u/substackcdn.com/image/fetch/$s_!fY6e!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png 1272w, /__u/substackcdn.com/image/fetch/$s_!fY6e!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!fY6e!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png" width="467" height="422" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:422,&quot;width&quot;:467,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!fY6e!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png 424w, /__u/substackcdn.com/image/fetch/$s_!fY6e!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png 848w, /__u/substackcdn.com/image/fetch/$s_!fY6e!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png 1272w, /__u/substackcdn.com/image/fetch/$s_!fY6e!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfe9aaa6-dd54-46f9-85fd-a39d65d9ff37_467x422.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>3.1 Function Calling &#8212; G&#7885;i h&#224;m</strong></h3><p>Khi tri&#7875;n khai function calling, b&#7841;n <strong>k&#7871;t n&#7889;i model v&#7899;i c&#225;c h&#224;m</strong>. B&#7841;n cung c&#7845;p t&#7853;p h&#7907;p c&#225;c h&#224;m &#273;&#227; &#273;&#7883;nh ngh&#297;a s&#7861;n, v&#224; model t&#7921; quy&#7871;t &#273;&#7883;nh <strong>khi n&#224;o</strong> s&#7917; d&#7909;ng h&#224;m n&#224;o v&#224; <strong>tham s&#7889; n&#224;o</strong> c&#7847;n thi&#7871;t.</p><p><strong>Quan tr&#7885;ng:</strong> Model <strong>kh&#244;ng t&#7921; th&#7921;c thi h&#224;m</strong>. N&#243; ch&#7881; cho bi&#7871;t h&#224;m n&#224;o n&#234;n &#273;&#432;&#7907;c g&#7885;i v&#224; truy&#7873;n tham s&#7889; d&#7921;a tr&#234;n c&#226;u h&#7887;i c&#7911;a user. B&#7841;n c&#7847;n vi&#7871;t code &#273;&#7875; th&#7921;c thi h&#224;m &#273;&#243;.</p><p>Quy tr&#236;nh t&#7841;o function call:</p><ol><li><p><strong>&#272;&#7883;nh ngh&#297;a h&#224;m</strong> v&#224; m&#244; t&#7843; h&#224;m (t&#234;n, tham s&#7889;, m&#7909;c &#273;&#237;ch)</p></li><li><p><strong>G&#7885;i LLM</strong> k&#232;m m&#244; t&#7843; h&#224;m</p></li><li><p><strong>Th&#7921;c thi h&#224;m</strong> d&#7921;a tr&#234;n output c&#7911;a model</p></li><li><p><strong>Tr&#7843; l&#7901;i</strong> user</p></li></ol><pre><code><code># Danh s&#225;ch mua s&#7855;m
shopping_list: List[str] = []

# &#272;&#7883;nh ngh&#297;a h&#224;m
def add_shopping_items(items: List[str]):
    """Add multiple items to the shopping list."""
    for item in items:
        shopping_list.append(item)
    return {"status": "ok", "added": items}

def list_shopping_items():
    """Return all items currently in the shopping list."""
    return {"shopping_list": shopping_list}

# M&#244; t&#7843; h&#224;m (function declaration) &#8212; &#273;&#7875; model hi&#7875;u h&#224;m l&#224;m g&#236;
add_shopping_items_declaration = {
    "name": "add_shopping_items",
    "description": "Add one or more items to the shopping list",
    "parameters": {
        "type": "object",
        "properties": {
            "items": {
                "type": "array",
                "items": {"type": "string"},
                "description": "A list of shopping items to add"
            }
        },
        "required": ["items"]
    }
}

# G&#7917;i &#273;&#7871;n model
response = client.models.generate_content(
    model="gemini-2.0-flash",
    contents=user_input,
    config=config,
)

# Model ch&#7885;n h&#224;m v&#224; tr&#237;ch xu&#7845;t tham s&#7889;
tool_call = response.candidates[0].content.parts[0].function_call
# -&gt; name: "add_shopping_items", args: {"items": ["flour", "chocolate chips"]}

# Th&#7921;c thi h&#224;m d&#7921;a tr&#234;n output c&#7911;a model
if tool_call.name == "add_shopping_items":
    result = add_shopping_items(**tool_call.args)
</code></code></pre><h3><strong>3.2 RAG &#8212; Retrieval Augmented Generation</strong></h3><p>RAG c&#243; ngh&#297;a l&#224;:</p><ul><li><p><strong>Retrieval</strong> &#8212; Khi user h&#7887;i, h&#7879; th&#7889;ng t&#236;m ki&#7871;m ngu&#7891;n b&#234;n ngo&#224;i &#273;&#7875; l&#7845;y th&#244;ng tin li&#234;n quan</p></li><li><p><strong>Augmented</strong>  &#8212; Th&#244;ng tin &#273;&#432;&#7907;c &#273;&#432;a v&#224;o prompt</p></li><li><p><strong>Generation</strong> &#8212; LLM t&#7841;o ph&#7843;n h&#7891;i d&#7921;a tr&#234;n c&#7843; prompt g&#7889;c v&#224; ng&#7919; c&#7843;nh b&#7893; sung</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!1QHP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!1QHP!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png 424w, /__u/substackcdn.com/image/fetch/$s_!1QHP!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png 848w, /__u/substackcdn.com/image/fetch/$s_!1QHP!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png 1272w, /__u/substackcdn.com/image/fetch/$s_!1QHP!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!1QHP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png" width="1024" height="576" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/17fa98af-809f-4d43-999c-b1850734de59_1024x576.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:576,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!1QHP!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png 424w, /__u/substackcdn.com/image/fetch/$s_!1QHP!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png 848w, /__u/substackcdn.com/image/fetch/$s_!1QHP!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png 1272w, /__u/substackcdn.com/image/fetch/$s_!1QHP!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17fa98af-809f-4d43-999c-b1850734de59_1024x576.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4><strong>Storing Pipeline</strong></h4><ol><li><p><strong>Load</strong> &#8212; T&#7843;i t&#224;i li&#7879;u</p></li><li><p><strong>Split</strong> &#8212; Chia th&#224;nh c&#225;c &#273;o&#7841;n nh&#7887; (chunks). Quan tr&#7885;ng v&#236; gi&#250;p truy xu&#7845;t ch&#237;nh x&#225;c h&#417;n v&#224; LLM c&#243; gi&#7899;i h&#7841;n context window</p></li><li><p><strong>Embed</strong> &#8212; T&#7841;o vector s&#7889; h&#7885;c cho m&#7895;i chunk. Embedding c&#7889; g&#7855;ng n&#7855;m b&#7855;t &#253; ngh&#297;a &#8212; text c&#243; n&#7897;i dung t&#432;&#417;ng t&#7921; s&#7869; c&#243; vector t&#432;&#417;ng t&#7921;</p></li><li><p><strong>Store</strong> &#8212; L&#432;u v&#224;o vector database</p></li></ol><h4><strong>Retrieving Pipeline</strong></h4><ol><li><p><strong>Embed query</strong> &#8212; Chuy&#7875;n c&#226;u h&#7887;i user th&#224;nh vector</p></li><li><p><strong>Retrieve</strong> &#8212; T&#236;m c&#225;c chunks li&#234;n quan nh&#7845;t b&#7857;ng semantic similarity ho&#7863;c MMR</p></li><li><p><strong>Combine</strong> &#8212; Gh&#233;p c&#225;c chunks li&#234;n quan</p></li><li><p><strong>Generate</strong> &#8212; &#272;&#432;a v&#224;o LLM prompt &#273;&#7875; sinh c&#226;u tr&#7843; l&#7901;i</p></li></ol><p>RAG quan tr&#7885;ng v&#236; n&#243; <strong>gi&#7843;m hallucination</strong> v&#224; cho ph&#233;p LLM tr&#7843; l&#7901;i v&#7873; c&#225;c domain c&#7909; th&#7875; ho&#7863;c d&#7919; li&#7879;u ri&#234;ng t&#432; m&#224; kh&#244;ng c&#7847;n hu&#7845;n luy&#7879;n l&#7841;i model.</p><div><hr></div><h2><strong>N&#226;ng cao hi&#7879;u su&#7845;t Model</strong></h2><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!-LPX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!-LPX!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png 424w, /__u/substackcdn.com/image/fetch/$s_!-LPX!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png 848w, /__u/substackcdn.com/image/fetch/$s_!-LPX!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png 1272w, /__u/substackcdn.com/image/fetch/$s_!-LPX!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!-LPX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png" width="1024" height="603" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:603,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!-LPX!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png 424w, /__u/substackcdn.com/image/fetch/$s_!-LPX!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png 848w, /__u/substackcdn.com/image/fetch/$s_!-LPX!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png 1272w, /__u/substackcdn.com/image/fetch/$s_!-LPX!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5aa62e8b-4a8c-40aa-99aa-c34ebfd2cfd8_1024x603.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>C&#243; ba chi&#7871;n l&#432;&#7907;c ch&#237;nh:</p><h3><strong>1. In-context Learning &#8212; H&#7885;c trong ng&#7919; c&#7843;nh</strong></h3><p>&#8220;D&#7841;y&#8221; model c&#225;ch th&#7921;c hi&#7879;n t&#225;c v&#7909; b&#7857;ng c&#225;ch &#273;&#432;a v&#237; d&#7909; tr&#7921;c ti&#7871;p <strong>trong prompt</strong>, kh&#244;ng thay &#273;&#7893;i tr&#7885;ng s&#7889; c&#7911;a model.</p><p>C&#225;c lo&#7841;i in-context learning:</p><ul><li><p><strong>Zero-shot: </strong>Kh&#244;ng c&#243; v&#237; d&#7909;, ch&#7881; c&#243; h&#432;&#7899;ng d&#7851;n</p></li><li><p><strong>One-shot: </strong>M&#7897;t v&#237; d&#7909; minh h&#7885;a</p></li><li><p><strong>Few-shot: </strong>V&#224;i v&#237; d&#7909; minh h&#7885;a</p></li><li><p><strong>CoT: </strong>H&#432;&#7899;ng d&#7851;n suy lu&#7853;n t&#7915;ng b&#432;&#7899;c</p></li><li><p><strong>ReAct: </strong>K&#7871;t h&#7907;p suy lu&#7853;n v&#224; h&#224;nh &#273;&#7897;ng</p></li></ul><p>V&#237; d&#7909; One-shot:</p><pre><code><code>system_prompt = f"""You are a helpful assistant that extracts action items.

Example 1
Transcript:
'John will draft the budget by Friday. Sarah volunteers to review the marketing deck next week.'

Actions:
- John: Draft budget (due Friday)
- Sarah: Review marketing deck (next week)

Now you
Transcript: {user_query}

Actions:
"""
</code></code></pre><h3><strong>2. Retrieval-based In-context Learning &#8212; H&#7885;c d&#7921;a tr&#234;n truy xu&#7845;t</strong></h3><p>Model truy xu&#7845;t <strong>ng&#7919; c&#7843;nh b&#234;n ngo&#224;i</strong> (nh&#432; t&#224;i li&#7879;u) v&#224; th&#234;m n&#7897;i dung li&#234;n quan v&#224;o <strong>prompt</strong> khi suy lu&#7853;n. &#272;&#226;y ch&#237;nh l&#224; RAG &#273;&#227; n&#243;i &#7903; tr&#234;n.</p><h3><strong>3. Fine-tuning &#8212; Tinh ch&#7881;nh</strong></h3><p><strong>Hu&#7845;n luy&#7879;n th&#234;m</strong> model tr&#234;n t&#7853;p d&#7919; li&#7879;u c&#7909; th&#7875; &#273;&#7875; model &#8220;n&#7897;i h&#243;a&#8221; h&#224;nh vi ho&#7863;c ki&#7871;n th&#7913;c m&#7899;i. Tr&#7885;ng s&#7889; c&#7911;a model &#273;&#432;&#7907;c c&#7853;p nh&#7853;t.</p><h3><strong>V&#237; d&#7909; &#273;&#7875; so s&#225;nh ba chi&#7871;n l&#432;&#7907;c</strong></h3><p>H&#227;y t&#432;&#7903;ng t&#432;&#7907;ng b&#7841;n &#273;ang &#273;&#224;o t&#7841;o m&#7897;t h&#432;&#7899;ng d&#7851;n vi&#234;n du l&#7883;ch &#7903; &#272;&#224; N&#7861;ng:</p><ul><li><p><strong>In-Context Learning:</strong> B&#7841;n &#273;&#432;a cho h&#432;&#7899;ng d&#7851;n vi&#234;n v&#224;i t&#7901; ghi ch&#250; v&#7899;i v&#237; d&#7909;: &#8220;N&#7871;u ai h&#7887;i v&#7873; c&#7847;u R&#7891;ng, n&#243;i th&#7871; n&#224;y. N&#7871;u h&#7887;i v&#7873; &#273;&#7891; &#259;n, n&#243;i th&#7871; kia.&#8221; Anh ta kh&#244;ng hi&#7875;u s&#226;u v&#7873; th&#224;nh ph&#7889;, nh&#432;ng c&#243; th&#7875; <strong>l&#224;m theo v&#237; d&#7909;</strong> c&#7911;a b&#7841;n.</p></li></ul><ul><li><p><strong>Retrieval-Based Learning (RAG):</strong> B&#7841;n trang b&#7883; cho h&#432;&#7899;ng d&#7851;n vi&#234;n &#273;i&#7879;n tho&#7841;i + b&#7843;n &#273;&#7891; + Google search. Anh ta kh&#244;ng c&#7847;n nh&#7899; h&#7871;t nh&#432;ng bi&#7871;t c&#225;ch <strong>tra c&#7913;u th&#244;ng tin ngay l&#7853;p t&#7913;c</strong> khi &#273;&#432;&#7907;c h&#7887;i.</p></li><li><p><strong>Fine-Tuning:</strong> B&#7841;n cho h&#432;&#7899;ng d&#7851;n vi&#234;n &#273;&#224;o t&#7841;o chuy&#234;n s&#226;u nhi&#7873;u th&#225;ng t&#7841;i th&#224;nh ph&#7889;. Ki&#7871;n th&#7913;c &#273;&#227; <strong>n&#7857;m s&#7861;n trong &#273;&#7847;u</strong> khi b&#7855;t &#273;&#7847;u d&#7851;n tour.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Aa3Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Aa3Y!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png 424w, /__u/substackcdn.com/image/fetch/$s_!Aa3Y!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png 848w, /__u/substackcdn.com/image/fetch/$s_!Aa3Y!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Aa3Y!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Aa3Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png" width="903" height="403" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:403,&quot;width&quot;:903,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:47921,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://duthaho.substack.com/i/190818960?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!Aa3Y!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png 424w, /__u/substackcdn.com/image/fetch/$s_!Aa3Y!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png 848w, /__u/substackcdn.com/image/fetch/$s_!Aa3Y!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png 1272w, /__u/substackcdn.com/image/fetch/$s_!Aa3Y!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5a78aefa-8842-41f1-b556-d6e7cbf4ef4f_903x403.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2><strong>LangChain l&#224; g&#236; v&#224; v&#224;o cu&#7897;c &#7903; &#273;&#226;u?</strong></h2><p>LangChain l&#224; framework &#273;&#432;&#7907;c thi&#7871;t k&#7871; &#273;&#7875; &#273;&#417;n gi&#7843;n h&#243;a vi&#7879;c ph&#225;t tri&#7875;n &#7913;ng d&#7909;ng d&#7921;a tr&#234;n LLM.</p><p>H&#7879; sinh th&#225;i LangChain bao g&#7891;m:</p><ul><li><p><strong>LangChain</strong> &#8212; Framework c&#417; b&#7843;n, cho ph&#233;p chuy&#7875;n &#273;&#7893;i gi&#7919;a c&#225;c provider (Gemini, GPT...) m&#224; kh&#244;ng c&#7847;n thay &#273;&#7893;i code</p></li><li><p><strong>LangGraph</strong> &#8212; X&#226;y d&#7921;ng, tri&#7875;n khai v&#224; qu&#7843;n l&#253; workflow agent</p></li><li><p><strong>LangSmith</strong> &#8212; Debug, test v&#224; gi&#225;m s&#225;t &#7913;ng d&#7909;ng LLM</p></li></ul><p>Ngo&#224;i LangChain, b&#7841;n c&#361;ng c&#243; th&#7875; xem x&#233;t OpenAI Agents SDK ho&#7863;c Google Agent Development Kit (ADK).</p><h3><strong>X&#226;y d&#7921;ng Agent v&#7899;i LangChain</strong></h3><p>So v&#7899;i function calling th&#7911; c&#244;ng, LangChain &#273;&#417;n gi&#7843;n h&#243;a &#273;&#225;ng k&#7875;:</p><pre><code><code># Kh&#244;ng c&#7847;n vi&#7871;t function declaration th&#7911; c&#244;ng n&#7919;a
# Decorator @tool t&#7921; &#273;&#7897;ng chuy&#7875;n h&#224;m th&#224;nh m&#244; t&#7843; c&#243; c&#7845;u tr&#250;c

@tool
def add_shopping_items(items: List[str]):
    """Add multiple items to the shopping list."""
    for item in items:
        shopping_list.append(item)
    return {"status": "ok", "added": items}

@tool
def list_shopping_items():
    """Return all items currently in the shopping list."""
    return {"shopping_list": shopping_list}

# C&#7845;u h&#236;nh
llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
tools = [add_shopping_items, list_shopping_items]

# ChatPromptTemplate t&#7893; ch&#7913;c th&#244;ng tin trong prompt
# agent_scratchpad = b&#7897; nh&#7899; l&#224;m vi&#7879;c c&#7911;a agent (l&#7883;ch s&#7917; suy ngh&#297;, tool calls, k&#7871;t qu&#7843;)
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant that manages shopping lists."),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad")
])

# T&#7841;o agent &#8212; t&#7921; &#273;&#7897;ng x&#7917; l&#253; logic g&#7885;i h&#224;m
agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# Ch&#7841;y
response = agent_executor.invoke({"input": user_input})
</code></code></pre><p><strong>Kh&#225;c bi&#7879;t ch&#237;nh so v&#7899;i c&#225;ch th&#7911; c&#244;ng:</strong></p><ul><li><p>Kh&#244;ng c&#7847;n vi&#7871;t function declaration</p></li><li><p>Kh&#244;ng c&#7847;n <code>if/elif</code> &#273;&#7875; x&#225;c &#273;&#7883;nh h&#224;m n&#224;o c&#7847;n g&#7885;i</p></li><li><p><code>AgentExecutor</code> t&#7921; &#273;i&#7873;u ph&#7889;i to&#224;n b&#7897;: agent suy lu&#7853;n &#8594; ch&#7885;n tool &#8594; executor th&#7921;c thi &#8594; tr&#7843; k&#7871;t qu&#7843;</p></li></ul><div><hr></div><h2><strong>Khi n&#224;o n&#234;n x&#226;y d&#7921;ng Agent?</strong></h2><p>X&#226;y d&#7921;ng agent kh&#244;ng ph&#7843;i l&#250;c n&#224;o c&#361;ng l&#224; gi&#7843;i ph&#225;p hi&#7879;u qu&#7843; nh&#7845;t. M&#7897;t gi&#7843;i ph&#225;p deterministic (t&#7845;t &#273;&#7883;nh &#8212; theo quy t&#7855;c r&#245; r&#224;ng, kh&#244;ng c&#243; &#8220;h&#7897;p &#273;en&#8221;) c&#243; th&#7875; &#273;&#7911; t&#7889;t.</p><p>C&#7843; OpenAI v&#224; Anthropic &#273;&#7873;u khuy&#7871;n ngh&#7883;: <strong>b&#7855;t &#273;&#7847;u t&#7915; gi&#7843;i ph&#225;p &#273;&#417;n gi&#7843;n nh&#7845;t, ch&#7881; t&#259;ng &#273;&#7897; ph&#7913;c t&#7841;p khi c&#7847;n thi&#7871;t.</strong></p><p>H&#227;y c&#226;n nh&#7855;c x&#226;y d&#7921;ng agent khi:</p><ul><li><p><strong>Quy&#7871;t &#273;&#7883;nh ph&#7913;c t&#7841;p</strong> &#8212; Quy tr&#236;nh &#273;&#242;i h&#7887;i &#273;&#225;nh gi&#225; tinh t&#7871;, x&#7917; l&#253; ngo&#7841;i l&#7879;, ho&#7863;c quy&#7871;t &#273;&#7883;nh ph&#7909; thu&#7897;c nhi&#7873;u v&#224;o ng&#7919; c&#7843;nh (v&#237; d&#7909;: x&#225;c &#273;&#7883;nh kh&#225;ch h&#224;ng c&#243; &#273;&#7911; &#273;i&#7873;u ki&#7879;n ho&#224;n ti&#7873;n kh&#244;ng)</p></li><li><p><strong>Quy t&#7855;c kh&#243; b&#7843;o tr&#236;</strong> &#8212; Workflow d&#7921;a tr&#234;n b&#7897; quy t&#7855;c ph&#7913;c t&#7841;p, kh&#243; c&#7853;p nh&#7853;t, v&#224; thay &#273;&#7893;i li&#234;n t&#7909;c</p></li><li><p><strong>D&#7919; li&#7879;u phi c&#7845;u tr&#250;c</strong> &#8212; T&#225;c v&#7909; c&#7847;n hi&#7875;u ng&#244;n ng&#7919; t&#7921; nhi&#234;n, tr&#237;ch xu&#7845;t th&#244;ng tin t&#7915; PDF, email, h&#236;nh &#7843;nh, trang web...</p></li></ul><div><hr></div><h2><strong>T&#243;m l&#7841;i</strong></h2><p>Agent l&#224; h&#7879; th&#7889;ng &#273;&#432;&#7907;c thi&#7871;t k&#7871; &#273;&#7875; ho&#224;n th&#224;nh nhi&#7879;m v&#7909; thay con ng&#432;&#7901;i m&#7897;t c&#225;ch &#273;&#7897;c l&#7853;p. Ch&#250;ng bao g&#7891;m ba th&#224;nh ph&#7847;n: h&#432;&#7899;ng d&#7851;n (instructions), m&#244; h&#236;nh (model), v&#224; c&#244;ng c&#7909; (tools). C&#243; nhi&#7873;u c&#225;ch n&#226;ng cao hi&#7879;u su&#7845;t model: c&#7843;i thi&#7879;n prompt v&#7899;i v&#237; d&#7909;, d&#249;ng RAG &#273;&#7875; b&#7893; sung ng&#7919; c&#7843;nh, ho&#7863;c fine-tuning. LangChain gi&#250;p &#273;&#417;n gi&#7843;n h&#243;a code, nh&#432;ng b&#7841;n n&#234;n hi&#7875;u nh&#7919;ng g&#236; abstraction &#273;ang l&#224;m b&#234;n d&#432;&#7899;i. Lu&#244;n nh&#7899; r&#7857;ng <strong>&#273;&#417;n gi&#7843;n l&#224; c&#225;ch t&#7889;t nh&#7845;t</strong> &#273;&#7875; x&#226;y d&#7921;ng h&#7879; th&#7889;ng agentic.</p><div><hr></div><p>N&#7871;u b&#7841;n m&#7899;i ti&#7871;p x&#250;c v&#7899;i n&#7897;i dung n&#224;y, h&#227;y ti&#234;u h&#243;a h&#7871;t ki&#7871;n th&#7913;c tr&#432;&#7899;c, &#273;&#7885;c l&#7841;i v&#224;i l&#7847;n, v&#224; &#273;&#7885;c th&#234;m c&#225;c b&#224;i vi&#7871;t g&#7889;c &#273;&#7875; c&#243; n&#7873;n t&#7843;ng v&#7919;ng ch&#7855;c. Sau &#273;&#243;, h&#227;y b&#7855;t tay v&#224;o x&#226;y d&#7921;ng th&#7913; g&#236; &#273;&#243; &#8212; m&#7897;t &#7913;ng d&#7909;ng &#273;&#417;n gi&#7843;n &#8212; &#273;&#7875; t&#7841;o c&#7847;u n&#7889;i gi&#7919;a l&#253; thuy&#7871;t v&#224; th&#7921;c h&#224;nh. <strong>B&#7855;t &#273;&#7847;u build l&#224; c&#225;ch t&#7889;t nh&#7845;t &#273;&#7875; h&#7885;c.</strong></p><div><hr></div><h2><strong>Tham kh&#7843;o c&#225;c b&#224;i vi&#7871;t sau</strong></h2><ul><li><p><a href="https://www.anthropic.com/engineering/building-effective-agents">Building effective agents</a> &#8212; Anthropic</p></li><li><p><a href="https://www.kaggle.com/whitepaper-agents">Agents</a> &#8212; Google</p></li><li><p><a href="https://cdn.openai.com/business-guides-and-resources/a-practical-guide-to-building-agents.pdf">A practical guide to building agents</a> &#8212; OpenAI</p></li><li><p><a href="https://arxiv.org/pdf/2201.11903">Chain of Thought Prompting Elicits Reasoning in Large Language Models</a> &#8212; Google Research</p></li><li><p><a href="https://arxiv.org/pdf/2210.03629">ReAct: Synergizing Reasoning and Acting in Language Models</a> &#8212; Google Research</p></li><li><p><a href="https://www.datacamp.com/blog/small-language-models">Small Language Models: A Guide With Examples</a> &#8212; DataCamp</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: bài toán "Ngày này năm xưa"]]></title><description><![CDATA[D&#432;&#7899;i &#273;&#226;y l&#224; m&#244; ph&#7887;ng m&#7897;t bu&#7893;i ph&#7887;ng v&#7845;n System Design t&#7841;i m&#7897;t c&#244;ng ty c&#244;ng ngh&#7879; l&#7899;n gi&#7919;a Interviewer (Anh T&#249;ng - Principal Engineer) v&#224; &#7912;ng vi&#234;n (Duthaho).]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-bai-toan-ngay-nay</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-bai-toan-ngay-nay</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Sat, 06 Dec 2025 08:56:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>D&#432;&#7899;i &#273;&#226;y l&#224; m&#244; ph&#7887;ng m&#7897;t bu&#7893;i ph&#7887;ng v&#7845;n System Design t&#7841;i m&#7897;t c&#244;ng ty c&#244;ng ngh&#7879; l&#7899;n gi&#7919;a Interviewer (Anh T&#249;ng - Principal Engineer) v&#224; &#7912;ng vi&#234;n (Duthaho).</p><div><hr></div><p><strong>Anh T&#249;ng:</strong> Ch&#224;o Duthaho. Anh th&#7845;y CV c&#7911;a em c&#243; kinh nghi&#7879;m s&#226;u v&#7873; System Design. H&#244;m nay ch&#250;ng ta s&#7869; gi&#7843;i b&#224;i to&#225;n thi&#7871;t k&#7871; t&#237;nh n&#259;ng <strong>&#8220;On This Day&#8221; c&#7911;a Facebook</strong>. Em h&#227;y h&#236;nh dung h&#7879; th&#7889;ng ph&#7843;i ph&#7909;c v&#7909; l&#432;&#7907;ng user kh&#7893;ng l&#7891;. Em b&#7855;t &#273;&#7847;u &#273;i.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>Duthaho:</strong> Ch&#224;o anh T&#249;ng. B&#224;i to&#225;n r&#7845;t th&#250; v&#7883;. Tr&#432;&#7899;c khi &#273;i v&#224;o ki&#7871;n tr&#250;c, em mu&#7889;n l&#224;m r&#245; m&#7897;t s&#7889; requirements &#273;&#7875; khoanh v&#249;ng ph&#7841;m vi:</p><ol><li><p><strong>Scale:</strong> Ch&#250;ng ta &#273;ang n&#243;i v&#7873; quy m&#244; c&#7905; Facebook, t&#7913;c l&#224; kho&#7843;ng 2-3 t&#7927; users?</p></li><li><p><strong>T&#237;nh n&#259;ng:</strong> Ch&#7881; hi&#7875;n th&#7883; b&#224;i vi&#7871;t (Posts), &#7843;nh (Photos) hay c&#7843; quan h&#7879; b&#7841;n b&#232; (Friends)?</p></li><li><p><strong>Latency:</strong> Y&#234;u c&#7847;u &#273;&#7897; tr&#7877; th&#7871; n&#224;o khi user load feed n&#224;y?</p></li></ol><p><strong>Anh T&#249;ng:</strong> T&#7889;t. Gi&#7843; s&#7917; 2 t&#7927; user active. T&#7853;p trung v&#224;o Posts v&#224; Photos. &#272;&#7897; tr&#7877; ph&#7843;i c&#7921;c th&#7845;p, d&#432;&#7899;i 200ms user ph&#7843;i th&#7845;y n&#7897;i dung. V&#224; quan tr&#7885;ng nh&#7845;t: <strong>H&#7879; th&#7889;ng n&#224;y kh&#244;ng &#273;&#432;&#7907;c l&#224;m s&#7853;p Core Feed ch&#237;nh</strong>.</p><div><hr></div><p><strong>Duthaho:</strong> C&#225;c h&#432;&#7899;ng ti&#7871;p c&#7853;n &amp; trade-offs:</p><h4>C&#225;ch 1: Query tr&#7921;c ti&#7871;p (Real-time)</h4><p>Khi user login, th&#7921;c hi&#7879;n c&#226;u query SQL ki&#7875;u: <code>SELECT * FROM posts WHERE user_id = X AND MONTH(created_at) = M AND DAY(created_at) = D</code></p><ul><li><p><strong>&#431;u &#273;i&#7875;m:</strong> D&#7919; li&#7879;u lu&#244;n t&#432;&#417;i m&#7899;i (real-time). Kh&#244;ng t&#7889;n storage l&#432;u cache.</p></li><li><p><strong>Nh&#432;&#7907;c &#273;i&#7875;m:</strong></p><ul><li><p><strong>Hi&#7879;u n&#259;ng th&#7843;m h&#7885;a:</strong> V&#7899;i b&#7843;ng <code>posts</code> ch&#7913;a h&#224;ng ngh&#236;n t&#7927; b&#7843;n ghi, vi&#7879;c query kh&#244;ng theo index cluster (th&#432;&#7901;ng l&#224; Time-series t&#259;ng d&#7847;n) m&#224; query theo &#8220;ng&#224;y/th&#225;ng&#8221; b&#7887; qua &#8220;n&#259;m&#8221; l&#224; c&#7921;c n&#7863;ng.</p></li><li><p><strong>DB Load:</strong> 2 t&#7927; queries ph&#7913;c t&#7841;p m&#7895;i s&#225;ng s&#7869; &#273;&#225;nh s&#7853;p b&#7845;t k&#7923; database n&#224;o.</p></li></ul></li></ul><h4>C&#225;ch 2: Pre-computation (Batch Processing - Offline)</h4><p>Ch&#7841;y m&#7897;t job h&#224;ng ng&#224;y qu&#233;t qua d&#7919; li&#7879;u, t&#237;nh to&#225;n tr&#432;&#7899;c k&#7871;t qu&#7843; cho <em>t&#7845;t c&#7843;</em> user v&#224; l&#432;u v&#224;o m&#7897;t b&#7843;ng ri&#234;ng ho&#7863;c Cache.</p><ul><li><p><strong>&#431;u &#273;i&#7875;m:</strong> Read c&#7921;c nhanh (O(1)). Kh&#244;ng &#7843;nh h&#432;&#7903;ng DB ch&#237;nh v&#224;o gi&#7901; cao &#273;i&#7875;m.</p></li><li><p><strong>Nh&#432;&#7907;c &#273;i&#7875;m:</strong></p><ul><li><p><strong>L&#227;ng ph&#237; t&#224;i nguy&#234;n:</strong> R&#7845;t nhi&#7873;u user kh&#244;ng online ng&#224;y h&#244;m &#273;&#243; nh&#432;ng v&#7851;n t&#7889;n c&#244;ng t&#237;nh to&#225;n v&#224; l&#432;u tr&#7919;.</p></li><li><p><strong>Stale Data:</strong> N&#7871;u user v&#7915;a x&#243;a b&#224;i vi&#7871;t 5 ph&#250;t tr&#432;&#7899;c, job ch&#7841;y t&#7915; &#273;&#234;m qua v&#7851;n hi&#7879;n b&#224;i &#273;&#243; -&gt; Tr&#7843;i nghi&#7879;m t&#7879; (v&#224; r&#7911;i ro privacy).</p></li></ul></li></ul><h4>C&#225;ch 3: Hybrid (Lazy Loading + Caching)</h4><p>Kh&#244;ng t&#237;nh tr&#432;&#7899;c cho t&#7845;t c&#7843;. Ch&#7881; khi user c&#243; d&#7845;u hi&#7879;u active (login) ho&#7863;c g&#7847;n &#273;&#7871;n gi&#7901; g&#7917;i noti, h&#7879; th&#7889;ng m&#7899;i t&#237;nh to&#225;n v&#224; cache l&#7841;i v&#7899;i TTL (Time To Live) l&#224; 24h.</p><div><hr></div><p>V&#7899;i y&#234;u c&#7847;u kh&#244;ng &#7843;nh h&#432;&#7903;ng Core Feed v&#224; latency th&#7845;p, em s&#7869; lo&#7841;i b&#7887; ngay ph&#432;&#417;ng &#225;n query tr&#7921;c ti&#7871;p (Real-time Query) v&#224;o b&#7843;ng <code>Posts</code> ch&#237;nh. Query ki&#7875;u <code>WHERE user_id = ? AND MONTH(date) = ?</code> tr&#234;n b&#7843;ng h&#224;ng t&#7927; d&#242;ng l&#224; th&#7843;m h&#7885;a v&#7873; hi&#7879;u n&#259;ng.</p><p>Em &#273;&#7873; xu&#7845;t ki&#7871;n tr&#250;c <strong>Hybrid</strong> v&#7899;i vi&#7879;c t&#237;nh to&#225;n tr&#432;&#7899;c (Pre-computation):</p><ul><li><p><strong>Memory Service:</strong> Microservice ch&#7883;u tr&#225;ch nhi&#7879;m logic nghi&#7879;p v&#7909; &#8220;Ng&#224;y n&#224;y n&#259;m x&#432;a&#8221;.</p></li><li><p><strong>Activity Log DB (Source of Truth):</strong> Database ch&#7913;a t&#7845;t c&#7843; activities (Posts, Likes, Photos).</p></li><li><p><strong>Search Index / Secondary Index:</strong> H&#7879; th&#7889;ng &#273;&#225;nh index &#273;&#7863;c bi&#7879;t &#273;&#7875; query theo <code>(User_ID, Month, Day)</code>.</p></li><li><p><strong>Cache Layer (Redis/Memcached):</strong> L&#432;u k&#7871;t qu&#7843; &#273;&#227; t&#237;nh to&#225;n (&#8221;Memories Feed&#8221;) c&#7911;a user trong ng&#224;y.</p></li><li><p><strong>Notification Service:</strong> Qu&#7843;n l&#253; vi&#7879;c &#273;&#7849;y th&#244;ng b&#225;o.</p></li></ul><div><hr></div><p><strong>Anh T&#249;ng:</strong> H&#7907;p l&#253;. V&#7853;y em s&#7869; l&#432;u tr&#7919; d&#7919; li&#7879;u &#8220;lookup&#8221; n&#224;y nh&#432; th&#7871; n&#224;o &#273;&#7875; query nhanh nh&#7845;t?</p><p><strong>Duthaho</strong>: V&#7845;n &#273;&#7873; kh&#243; nh&#7845;t &#7903; &#273;&#226;y l&#224;: <strong>L&#224;m sao query nhanh d&#7919; li&#7879;u c&#361;?</strong></p><p>D&#7919; li&#7879;u Facebook th&#432;&#7901;ng l&#432;u theo Time-series (Append only). D&#7919; li&#7879;u m&#7899;i n&#7857;m tr&#234;n RAM/Hot storage, d&#7919; li&#7879;u c&#361; (Cold data) n&#7857;m &#7903; &#273;&#297;a c&#7913;ng ch&#7853;m h&#417;n. Query random access v&#224;o d&#7919; li&#7879;u 5 n&#259;m tr&#432;&#7899;c l&#224; r&#7845;t t&#7889;n k&#233;m (Disk I/O).</p><p><strong>Gi&#7843;i ph&#225;p:</strong> Thay v&#236; scan b&#7843;ng <code>posts</code>, ta c&#7847;n m&#7897;t b&#7843;ng Index ri&#234;ng (Lookup table):</p><ul><li><p><strong>Key:</strong> <code>User_ID</code></p></li><li><p><strong>Columns:</strong> List c&#225;c <code>Post_ID</code> k&#232;m <code>Timestamp</code>.</p></li><li><p>Tuy nhi&#234;n, c&#225;i n&#224;y v&#7851;n ch&#432;a t&#7889;i &#432;u cho query &#8220;Ng&#224;y n&#224;y&#8221;.</p></li><li><p>Ta c&#7847;n m&#7897;t c&#7845;u tr&#250;c index d&#7841;ng: <code>Bucket_Map&lt;User_ID, List&lt;Post_Metadata&gt;&gt;</code>.</p></li><li><p>Facebook th&#7921;c t&#7871; s&#7917; d&#7909;ng h&#7879; th&#7889;ng graph database c&#7911;a h&#7885; (TAO) v&#224; l&#7899;p caching c&#7921;c m&#7841;nh. Nh&#432;ng v&#7899;i thi&#7871;t k&#7871; t&#7893;ng qu&#225;t, ta c&#243; th&#7875; d&#249;ng <strong>Cassandra</strong> ho&#7863;c <strong>HBase</strong> v&#7899;i RowKey: <code>UserID#Month#Day</code>.</p><ul><li><p>Khi query <code>UserID=123, Month=12, Day=06</code>, ta l&#7845;y &#273;&#432;&#7907;c ngay l&#7853;p t&#7913;c t&#7845;t c&#7843; post ID c&#7911;a c&#225;c n&#259;m.</p></li></ul></li></ul><p><strong>Quy tr&#236;nh l&#7885;c (Filtering Pipeline):</strong></p><ol><li><p><strong>Fetch:</strong> L&#7845;y list Post IDs.</p></li><li><p><strong>Hydrate:</strong> G&#7885;i sang Post Service &#273;&#7875; l&#7845;y n&#7897;i dung chi ti&#7871;t.</p></li><li><p><strong>Privacy Check (Quan tr&#7885;ng):</strong> Ki&#7875;m tra ACL hi&#7879;n t&#7841;i. (User A post l&#234;n t&#432;&#7901;ng User B 5 n&#259;m tr&#432;&#7899;c, nay A block B -&gt; B kh&#244;ng &#273;&#432;&#7907;c th&#7845;y).</p></li><li><p><strong>Rank:</strong> D&#249;ng ML model nh&#7865; &#273;&#7875; ch&#7885;n ra post nhi&#7873;u like/comment nh&#7845;t &#273;&#7875; hi&#7875;n th&#7883; &#273;&#7847;u ti&#234;n.</p></li></ol><div><hr></div><p>N&#7871;u d&#249;ng MySQL, em s&#7869; t&#7841;o m&#7897;t b&#7843;ng ri&#234;ng l&#224; <strong>memories_lookup</strong>.</p><p>Schema s&#7869; tr&#244;ng nh&#432; th&#7871; n&#224;y:</p><pre><code><code>CREATE TABLE memories_lookup (
    user_id BIGINT,
    mm_dd SMALLINT, -- V&#237; d&#7909;: 1206 (Ng&#224;y 6 th&#225;ng 12)
    post_id BIGINT,
    post_year SMALLINT,
    PRIMARY KEY (user_id, mm_dd, post_id)
);
</code></code></pre><p>Em ch&#7885;n Primary Key l&#224; <strong>(user_id, mm_dd, post_id)</strong> v&#236; n&#243; l&#224; Clustered Index.</p><p>Khi query <code>SELECT * FROM memories_lookup WHERE user_id = X AND mm_dd = 1206</code>, d&#7919; li&#7879;u n&#7857;m li&#7873;n nhau v&#7853;t l&#253; tr&#234;n &#273;&#297;a c&#7913;ng. &#7892; c&#7913;ng ch&#7881; c&#7847;n seek 1 l&#7847;n v&#224; &#273;&#7885;c tu&#7847;n t&#7921; (Sequential Read). T&#7889;c &#273;&#7897; s&#7869; c&#7921;c nhanh.</p><p><strong>Anh T&#249;ng:</strong> T&#7841;i sao em kh&#244;ng d&#249;ng Index tr&#234;n b&#7843;ng <code>Posts</code> c&#243; s&#7861;n m&#224; ph&#7843;i &#273;&#7867; th&#234;m b&#7843;ng n&#224;y? T&#7889;n storage l&#7855;m.</p><p><strong>Duthaho</strong>: V&#236; b&#7843;ng <code>Posts </code>th&#432;&#7901;ng &#273;&#432;&#7907;c t&#7889;i &#432;u cho Time-series (m&#7899;i nh&#7845;t hi&#7879;n tr&#432;&#7899;c). Index th&#432;&#7901;ng l&#224; <strong>(user_id, created_at)</strong>.</p><p>N&#7871;u em query t&#236;m ng&#224;y 06/12 tr&#234;n b&#7843;ng &#273;&#243;, DB engine ph&#7843;i scan qua to&#224;n b&#7897; l&#7883;ch s&#7917; c&#7911;a user, nh&#7843;y c&#243;c qua c&#225;c n&#259;m, t&#7841;o ra Random I/O r&#7845;t l&#7899;n.</p><p>Vi&#7879;c t&#225;ch b&#7843;ng lookup gi&#250;p c&#244; l&#7853;p t&#7843;i &#273;&#7885;c (Read Load) kh&#7887;i b&#7843;ng ch&#237;nh. Storage r&#7867; h&#417;n Compute I/O nhi&#7873;u, n&#234;n &#273;&#226;y l&#224; trade-off &#273;&#225;ng gi&#225;.</p><div><hr></div><p><strong>Anh T&#249;ng</strong>: OK, &#273;&#7891;ng &#253; v&#7873; Schema. Gi&#7901; sang v&#7845;n &#273;&#7873; v&#7853;n h&#224;nh.</p><p>8:00 s&#225;ng, h&#7879; th&#7889;ng b&#7855;n Notification cho 500 tri&#7879;u ng&#432;&#7901;i d&#249;ng. 50 tri&#7879;u ng&#432;&#7901;i c&#361;ng l&#250;c b&#7845;m v&#224;o xem. Database c&#7911;a em ch&#7883;u n&#7893;i kh&#244;ng?</p><p><strong>Duthaho:</strong> &#272;&#7875; gi&#7843;i quy&#7871;t v&#7845;n &#273;&#7873; n&#224;y, ch&#250;ng ta c&#7847;n chi&#7871;n l&#432;&#7907;c kh&#225;c nhau:</p><p><strong>1. K&#7929; thu&#7853;t &#8220;Jitter&#8221; (Ph&#226;n t&#225;n ng&#7851;u nhi&#234;n):</strong> &#272;&#7915;ng bao gi&#7901; g&#7917;i 100 tri&#7879;u th&#244;ng b&#225;o v&#224;o &#273;&#250;ng <code>08:00:00</code>.</p><ul><li><p><strong>Thi&#7871;t k&#7871;:</strong> Notification Service s&#7869; chia user th&#224;nh c&#225;c l&#244; (batches).</p></li><li><p><strong>Th&#7921;c thi:</strong> G&#7917;i r&#7843;i r&#225;c trong khung gi&#7901; t&#7915; <code>08:00</code> &#273;&#7871;n <code>09:00</code>. V&#237; d&#7909;: User A nh&#7853;n l&#250;c 8:05, User B nh&#7853;n l&#250;c 8:42.</p></li><li><p><strong>Hi&#7879;u qu&#7843;:</strong> Gi&#7843;m t&#7843;i &#273;&#7881;nh (peak load) xu&#7889;ng 60 l&#7847;n (n&#7871;u r&#7843;i &#273;&#7873;u trong 60 ph&#250;t).</p></li></ul><p><strong>2. Cache Warming (L&#224;m n&#243;ng Cache):</strong></p><ul><li><p><strong>Logic:</strong> Khi h&#7879; th&#7889;ng ch&#7841;y job ban &#273;&#234;m &#273;&#7875; t&#236;m ra &#8220;Ai c&#243; k&#7927; ni&#7879;m h&#244;m nay&#8221; &#273;&#7875; &#273;&#432;a v&#224;o danh s&#225;ch g&#7917;i Notification, h&#7879; th&#7889;ng <strong>&#273;&#227; c&#243; d&#7919; li&#7879;u trong tay</strong>.</p></li><li><p><strong>H&#224;nh &#273;&#7897;ng:</strong> Ngay l&#250;c t&#237;nh to&#225;n xong (v&#237; d&#7909; 4:00 AM), h&#227;y &#273;&#7849;y lu&#244;n d&#7919; li&#7879;u &#273;&#243; v&#224;o <strong>Redis</strong> v&#7899;i TTL l&#224; 24h.</p></li><li><p><strong>K&#7871;t qu&#7843;:</strong> Khi User b&#7845;m v&#224;o th&#244;ng b&#225;o l&#250;c 8:00 AM, request &#273;i th&#7859;ng v&#224;o Redis l&#7845;y d&#7919; li&#7879;u ra. Database g&#7847;n nh&#432; kh&#244;ng ch&#7883;u t&#7843;i g&#236; c&#7843; (Zero DB Hit cho lu&#7891;ng n&#224;y).</p></li></ul><p><strong>3. Circuit Breaker &amp; Fallback:</strong> Gi&#7843; s&#7917; Redis ch&#7871;t ho&#7863;c qu&#225; t&#7843;i?</p><ul><li><p>&#272;&#7915;ng &#273;&#7875; request ch&#7885;c th&#7859;ng xu&#7889;ng Database (s&#7869; l&#224;m s&#7853;p lu&#244;n DB).</p></li><li><p>K&#237;ch ho&#7841;t <strong>Circuit Breaker</strong>: Tr&#7843; v&#7873; m&#7897;t th&#244;ng b&#225;o nh&#7865; nh&#224;ng &#8220;H&#7879; th&#7889;ng &#273;ang b&#7853;n, h&#227;y quay l&#7841;i sau&#8221; ho&#7863;c hi&#7875;n th&#7883; m&#7897;t phi&#234;n b&#7843;n cached c&#361; h&#417;n, thay v&#236; &#273;&#7875; user ch&#7901; &#273;&#7907;i g&#226;y t&#7855;c ngh&#7869;n th&#234;m.</p></li></ul><p><strong>Anh T&#249;ng:</strong> Kh&#225; l&#7855;m. Cache Warming l&#224; key &#7903; &#273;&#226;y.</p><div><hr></div><p><strong>Anh T&#249;ng</strong>: Gi&#7901; anh l&#224;m kh&#243; em m&#7897;t ch&#250;t.</p><p>Em l&#432;u d&#7919; li&#7879;u v&#224;o b&#7843;ng <strong>memories_lookup</strong> l&#250;c 3:00 s&#225;ng.</p><p>L&#250;c 7:00 s&#225;ng, User A x&#243;a b&#224;i vi&#7871;t g&#7889;c &#7903; b&#7843;ng Posts.</p><p>L&#250;c 8:00 s&#225;ng, User A v&#224;o xem &#8220;On This Day&#8221;.</p><p>L&#224;m sao em &#273;&#7843;m b&#7843;o b&#224;i vi&#7871;t &#273;&#243; bi&#7871;n m&#7845;t? &#272;&#7891;ng b&#7897; d&#7919; li&#7879;u ki&#7875;u g&#236;?</p><p><strong>Duthaho:</strong> Em c&#243; 2 l&#7921;a ch&#7885;n: <strong>Dual Write</strong> (Ghi &#273;&#7891;ng b&#7897;) ho&#7863;c <strong>CDC</strong> (B&#7845;t &#273;&#7891;ng b&#7897;).</p><p>Em <strong>t&#7915; ch&#7889;i Dual Write</strong> (t&#7913;c l&#224; code App v&#7915;a x&#243;a Post v&#7915;a x&#243;a Lookup trong 1 transaction) v&#236; n&#243; l&#224;m ch&#7853;m thao t&#225;c x&#243;a c&#7911;a user v&#224; gh&#233;p ch&#7863;t 2 h&#7879; th&#7889;ng (Tight Coupling).</p><p>Em ch&#7885;n <strong>CDC (Change Data Capture)</strong> k&#7871;t h&#7907;p v&#7899;i chi&#7871;n l&#432;&#7907;c <strong>Check-on-Read</strong>:</p><ol><li><p><strong>Async deletion:</strong> Khi Post b&#7883; x&#243;a, DB b&#7855;n log update v&#224;o Kafka. M&#7897;t Worker s&#7869; &#273;&#7885;c Kafka &#273;&#7875; x&#243;a d&#242;ng t&#432;&#417;ng &#7913;ng trong <code>memories_lookup</code>. S&#7869; c&#243; &#273;&#7897; tr&#7877; v&#224;i gi&#226;y.</p></li><li><p><strong>Safety Net (L&#432;&#7899;i b&#7843;o v&#7879;):</strong> &#272;&#226;y l&#224; m&#7845;u ch&#7889;t.</p><ul><li><p>Khi User A load &#8220;On This Day&#8221;, em l&#7845;y &#273;&#432;&#7907;c list <code>Post_ID</code> t&#7915; <code>lookup</code> (c&#243; th&#7875; v&#7851;n c&#242;n s&#243;t b&#224;i &#273;&#227; x&#243;a).</p></li><li><p>Nh&#432;ng tr&#432;&#7899;c khi tr&#7843; v&#7873; cho Client, em s&#7869; c&#243; b&#432;&#7899;c <strong>Hydration</strong> (l&#7845;y chi ti&#7871;t n&#7897;i dung). Em g&#7885;i sang <code>Post Service</code> v&#7899;i list ID &#273;&#243;.</p></li><li><p><code>Post Service</code> check DB ch&#237;nh, th&#7845;y b&#224;i &#273;&#227; x&#243;a -&gt; Tr&#7843; v&#7873; <code>null</code>.</p></li><li><p>Service &#8220;On This Day&#8221; th&#7845;y <code>null</code> th&#236; filter b&#7887; &#273;i.</p></li></ul></li></ol><p>User s&#7869; kh&#244;ng bao gi&#7901; th&#7845;y &#8220;Ghost Data&#8221; (d&#7919; li&#7879;u ma), d&#249; h&#7879; th&#7889;ng lookup c&#243; ch&#432;a k&#7883;p c&#7853;p nh&#7853;t.</p><div><hr></div><p><strong>Anh T&#249;ng</strong>: C&#225;ch gi&#7843;i quy&#7871;t &#8220;Check-on-Read&#8221; r&#7845;t th&#244;ng minh. N&#243; &#273;&#7843;m b&#7843;o Consistency m&#224; kh&#244;ng hy sinh Availability c&#7911;a lu&#7891;ng Write.</p><p>C&#226;u h&#7887;i cu&#7889;i: Em &#432;&#7899;c t&#237;nh h&#7879; th&#7889;ng n&#224;y c&#7847;n bao nhi&#234;u Storage cho 2 t&#7927; user?</p><p><strong>Duthaho:</strong></p><ul><li><p>2 t&#7927; user. Trung b&#236;nh 500 posts/user -&gt; 1,000 t&#7927; b&#7843;n ghi (records).</p></li><li><p>M&#7895;i record <code>lookup</code> (User 8B + Date 2B + PostID 8B + Year 2B + Overhead) ~ 30-40 Bytes.</p></li><li><p>T&#7893;ng raw data = 1,000 t&#7927; * 40 Bytes = <strong>40 TB</strong>.</p></li><li><p>V&#7899;i Replication Factor = 3 (&#273;&#7875; an to&#224;n) -&gt; <strong>120 TB</strong>.</p></li><li><p>Con s&#7889; n&#224;y ho&#224;n to&#224;n kh&#7843; thi &#273;&#7875; l&#432;u tr&#7919; tr&#234;n c&#7909;m Cassandra ho&#7863;c HBase, th&#7853;m ch&#237; Sharded MySQL (kho&#7843;ng 60-100 instances).</p></li></ul><p><strong>Anh T&#249;ng:</strong> C&#7843;m &#417;n Duthaho. Anh r&#7845;t &#7845;n t&#432;&#7907;ng v&#7873; c&#225;ch em &#273;i t&#7915; Schema chi ti&#7871;t l&#234;n &#273;&#7871;n Architecture t&#7893;ng th&#7875; v&#224; x&#7917; l&#253; c&#225;c Edge cases. Em c&#243; t&#432; duy c&#7911;a m&#7897;t SA th&#7921;c th&#7909;.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Giải mã MySQL: InnoDB Buffer Pool]]></title><description><![CDATA[B&#7889;i c&#7843;nh:]]></description><link>https://duthaho.substack.com/p/giai-ma-mysql-innodb-buffer-pool</link><guid isPermaLink="false">https://duthaho.substack.com/p/giai-ma-mysql-innodb-buffer-pool</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Mon, 10 Nov 2025 08:28:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zwRJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!zwRJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!zwRJ!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png 424w, /__u/substackcdn.com/image/fetch/$s_!zwRJ!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png 848w, /__u/substackcdn.com/image/fetch/$s_!zwRJ!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png 1272w, /__u/substackcdn.com/image/fetch/$s_!zwRJ!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!zwRJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png" width="700" height="386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:386,&quot;width&quot;:700,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;MySQL: innodb &#20869;&#23384;&#27169;&#22411; &#183; Issue #215 &#183; lihongjie0209/myblog&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="MySQL: innodb &#20869;&#23384;&#27169;&#22411; &#183; Issue #215 &#183; lihongjie0209/myblog" title="MySQL: innodb &#20869;&#23384;&#27169;&#22411; &#183; Issue #215 &#183; lihongjie0209/myblog" srcset="/__u/substackcdn.com/image/fetch/$s_!zwRJ!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png 424w, /__u/substackcdn.com/image/fetch/$s_!zwRJ!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png 848w, /__u/substackcdn.com/image/fetch/$s_!zwRJ!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png 1272w, /__u/substackcdn.com/image/fetch/$s_!zwRJ!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc6d4fa14-d5ce-4877-9f47-58b6d606ad44_700x386.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>B&#7889;i c&#7843;nh:</h3><ul><li><p><strong>Ng&#432;&#7901;i ph&#7887;ng v&#7845;n:</strong> Anh Khoa (Solution Architect c&#7845;p cao)</p></li><li><p><strong>&#7912;ng vi&#234;n:</strong> duthaho</p></li><li><p><strong>Vai tr&#242;:</strong> K&#7929; s&#432; Backend/Database c&#7845;p cao</p></li><li><p><strong>M&#7909;c ti&#234;u:</strong> &#272;&#225;nh gi&#225; ki&#7871;n th&#7913;c chuy&#234;n s&#226;u v&#7873; ki&#7871;n tr&#250;c b&#234;n trong c&#7911;a MySQL InnoDB.</p></li></ul><div><hr></div><p><strong>Anh Khoa:</strong> Ch&#224;o duthaho, anh l&#224; Khoa. C&#7843;m &#417;n em &#273;&#227; tham gia bu&#7893;i ph&#7887;ng v&#7845;n h&#244;m nay. Anh xem qua CV th&#236; th&#7845;y em c&#243; nhi&#7873;u kinh nghi&#7879;m l&#224;m vi&#7879;c v&#7899;i MySQL. H&#244;m nay ch&#250;ng ta s&#7869; trao &#273;&#7893;i s&#226;u m&#7897;t ch&#250;t v&#7873; ki&#7871;n tr&#250;c c&#7911;a InnoDB nh&#233;. Em s&#7861;n s&#224;ng ch&#7913;?</p><p><strong>duthaho:</strong> D&#7841;, em s&#7861;n s&#224;ng. Ch&#224;o anh Khoa.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>Anh Khoa:</strong> OK, b&#7855;t &#273;&#7847;u nh&#233;. C&#226;u h&#7887;i &#273;&#7847;u ti&#234;n, theo em, th&#224;nh ph&#7847;n n&#224;o l&#224; quan tr&#7885;ng nh&#7845;t quy&#7871;t &#273;&#7883;nh hi&#7879;u n&#259;ng c&#7911;a InnoDB v&#224; t&#7841;i sao?</p><p><strong>duthaho:</strong> D&#7841;, theo em &#273;&#243; l&#224; <strong>InnoDB Buffer Pool</strong>. L&#253; do l&#224; v&#236; n&#243; l&#224; m&#7897;t v&#249;ng nh&#7899; cache l&#7899;n trong RAM, d&#249;ng &#273;&#7875; l&#432;u tr&#7919; c&#225;c trang (pages) d&#7919; li&#7879;u v&#224; ch&#7881; m&#7909;c (index) &#273;&#432;&#7907;c truy c&#7853;p th&#432;&#7901;ng xuy&#234;n. Vi&#7879;c n&#224;y gi&#250;p gi&#7843;m thi&#7875;u t&#7889;i &#273;a c&#225;c thao t&#225;c I/O &#273;&#7885;c/ghi xu&#7889;ng &#273;&#297;a, v&#7889;n l&#224; n&#250;t th&#7855;t c&#7893; chai l&#7899;n nh&#7845;t c&#7911;a database. H&#7847;u h&#7871;t c&#225;c thao t&#225;c &#273;&#7885;c s&#7869; &#273;&#432;&#7907;c ph&#7909;c v&#7909; tr&#7921;c ti&#7871;p t&#7915; RAM, nhanh h&#417;n h&#224;ng ngh&#236;n l&#7847;n so v&#7899;i &#273;&#7885;c t&#7915; SSD.</p><p><strong>Anh Khoa:</strong> R&#7845;t t&#7889;t. V&#7853;y khi m&#7897;t c&#226;u l&#7879;nh <code>UPDATE</code> ch&#7841;y, n&#243; s&#7869; l&#224;m thay &#273;&#7893;i m&#7897;t trang trong Buffer Pool. Trang &#273;&#243; &#273;&#432;&#7907;c g&#7885;i l&#224; g&#236;, v&#224; n&#243; kh&#225;c g&#236; v&#7899;i m&#7897;t trang &#8220;s&#7841;ch&#8221; (clean page)?</p><p><strong>duthaho:</strong> D&#7841;, khi m&#7897;t trang b&#7883; thay &#273;&#7893;i trong b&#7897; nh&#7899;, n&#243; &#273;&#432;&#7907;c &#273;&#225;nh d&#7845;u l&#224; <strong>&#8220;dirty page&#8221;</strong> (trang b&#7849;n).</p><ul><li><p><strong>Dirty Page:</strong> L&#224; trang trong RAM c&#243; n&#7897;i dung <em>m&#7899;i h&#417;n</em> (kh&#225;c) so v&#7899;i n&#7897;i dung tr&#234;n &#273;&#297;a.</p></li><li><p>Clean Page: L&#224; trang trong RAM c&#243; n&#7897;i dung gi&#7889;ng h&#7879;t v&#7899;i n&#7897;i dung tr&#234;n &#273;&#297;a.</p><p>S&#7921; kh&#225;c bi&#7879;t n&#224;y r&#7845;t quan tr&#7885;ng, v&#236; InnoDB c&#243; th&#7875; lo&#7841;i b&#7887; (evict) m&#7897;t clean page b&#7845;t c&#7913; l&#250;c n&#224;o &#273;&#7875; l&#7845;y ch&#7895; tr&#7889;ng, nh&#432;ng ph&#7843;i ghi (flush) m&#7897;t dirty page xu&#7889;ng &#273;&#297;a tr&#432;&#7899;c khi lo&#7841;i b&#7887; n&#243;.</p></li></ul><p><strong>Anh Khoa:</strong> Ch&#237;nh x&#225;c. Gi&#7901; h&#227;y n&#243;i v&#7873; vi&#7879;c t&#7889;i &#432;u ghi. Gi&#7843; s&#7917; em <code>INSERT</code> m&#7897;t b&#7843;n ghi m&#7899;i v&#224;o m&#7897;t b&#7843;ng c&#243; 5 secondary index (ch&#7881; m&#7909;c ph&#7909;). N&#7871;u kh&#244;ng c&#243; c&#417; ch&#7871; &#273;&#7863;c bi&#7879;t, vi&#7879;c n&#224;y s&#7869; r&#7845;t t&#7889;n k&#233;m I/O. InnoDB s&#7917; d&#7909;ng c&#225;i g&#236; &#273;&#7875; gi&#7843;i quy&#7871;t v&#7845;n &#273;&#7873; n&#224;y?</p><p><strong>duthaho:</strong> D&#7841;, &#273;&#243; ch&#237;nh l&#224; vai tr&#242; c&#7911;a <strong>Change Buffer</strong>. Thay v&#236; ngay l&#7853;p t&#7913;c &#273;&#7885;c c&#225;c trang c&#7911;a 5 ch&#7881; m&#7909;c ph&#7909; &#273;&#243; t&#7915; &#273;&#297;a v&#224;o Buffer Pool ch&#7881; &#273;&#7875; th&#234;m m&#7897;t b&#7843;n ghi m&#7899;i (g&#226;y ra 5 thao t&#225;c &#273;&#7885;c ng&#7851;u nhi&#234;n), InnoDB s&#7869; &#8220;ghi ch&#250;&#8221; l&#7841;i s&#7921; thay &#273;&#7893;i n&#224;y v&#224;o m&#7897;t v&#249;ng &#273;&#7863;c bi&#7879;t trong Buffer Pool. Thao t&#225;c <code>INSERT</code> &#273;&#432;&#7907;c tr&#7843; v&#7873; &#8220;th&#224;nh c&#244;ng&#8221; ngay l&#7853;p t&#7913;c. C&#225;c thay &#273;&#7893;i n&#224;y s&#7869; &#273;&#432;&#7907;c &#8220;tr&#7897;n&#8221; (merge) v&#224;o c&#225;c trang ch&#7881; m&#7909;c th&#7921;c s&#7921; sau &#273;&#243;, th&#432;&#7901;ng l&#224; b&#7903;i m&#7897;t lu&#7891;ng n&#7873;n ho&#7863;c khi trang &#273;&#243; &#273;&#432;&#7907;c &#273;&#7885;c v&#236; m&#7897;t l&#253; do kh&#225;c.</p><p><strong>Anh Khoa:</strong> M&#7897;t &#273;i&#7875;m r&#7845;t quan tr&#7885;ng: T&#7841;i sao Change Buffer l&#7841;i ch&#7881; ho&#7841;t &#273;&#7897;ng hi&#7879;u qu&#7843; v&#7899;i <strong>non-unique</strong> secondary index? &#272;i&#7873;u g&#236; x&#7843;y ra n&#7871;u ch&#7881; m&#7909;c &#273;&#243; l&#224; <code>UNIQUE</code>?</p><p><strong>duthaho:</strong> D&#7841;, v&#236; v&#7899;i m&#7897;t ch&#7881; m&#7909;c <code>UNIQUE</code>, khi <code>INSERT</code> ho&#7863;c <code>UPDATE</code>, InnoDB <em>b&#7855;t bu&#7897;c</em> ph&#7843;i ki&#7875;m tra xem gi&#225; tr&#7883; &#273;&#243; &#273;&#227; t&#7891;n t&#7841;i hay ch&#432;a &#273;&#7875; &#273;&#7843;m b&#7843;o t&#237;nh duy nh&#7845;t. &#272;&#7875; l&#224;m vi&#7879;c n&#224;y, n&#243; <em>bu&#7897;c ph&#7843;i</em> &#273;&#7885;c trang ch&#7881; m&#7909;c li&#234;n quan t&#7915; &#273;&#297;a v&#224;o. V&#236; &#273;&#7857;ng n&#224;o c&#361;ng ph&#7843;i &#273;&#7885;c t&#7915; &#273;&#297;a, n&#234;n vi&#7879;c d&#249;ng Change Buffer kh&#244;ng c&#242;n &#253; ngh&#297;a t&#7889;i &#432;u n&#7919;a. C&#242;n v&#7899;i non-unique index, n&#243; c&#243; th&#7875; &#8220;ghi m&#249;&#8221; (blind write) m&#224; kh&#244;ng c&#7847;n ki&#7875;m tra, n&#234;n Change Buffer ph&#225;t huy t&#7889;i &#273;a t&#225;c d&#7909;ng.</p><p><strong>Anh Khoa:</strong> R&#7845;t r&#245; r&#224;ng. OK, v&#7853;y b&#226;y gi&#7901; Buffer Pool c&#7911;a ch&#250;ng ta ch&#7913;a r&#7845;t nhi&#7873;u &#8220;dirty pages&#8221;. L&#224;m th&#7871; n&#224;o InnoDB &#273;&#432;a c&#225;c trang n&#224;y xu&#7889;ng &#273;&#297;a m&#7897;t c&#225;ch an to&#224;n m&#224; kh&#244;ng l&#224;m &#8220;&#273;&#7913;ng&#8221; to&#224;n b&#7897; h&#7879; th&#7889;ng?</p><p><strong>duthaho:</strong> InnoDB s&#7917; d&#7909;ng m&#7897;t c&#417; ch&#7871; g&#7885;i l&#224; <strong>&#8220;Fuzzy Checkpointing&#8221;</strong> (Checkpointing m&#7901;). &#272;&#226;y l&#224; m&#7897;t ti&#7871;n tr&#236;nh ch&#7841;y n&#7873;n, li&#234;n t&#7909;c v&#224; t&#7915; t&#7915;. C&#225;c lu&#7891;ng Page Cleaner s&#7869; qu&#233;t Buffer Pool v&#224; &#8220;x&#7843;&#8221; (flush) m&#7897;t l&#432;&#7907;ng nh&#7887; c&#225;c dirty pages c&#361; nh&#7845;t xu&#7889;ng &#273;&#297;a theo th&#7901;i gian. N&#243; &#273;&#432;&#7907;c g&#7885;i l&#224; &#8220;fuzzy&#8221; v&#236; n&#243; kh&#244;ng d&#7915;ng to&#224;n b&#7897; ho&#7841;t &#273;&#7897;ng c&#7911;a CSDL l&#7841;i. N&#243; &#273;&#7889;i l&#7853;p ho&#224;n to&#224;n v&#7899;i &#8220;Sharp Checkpoint&#8221; &#8211; m&#7897;t c&#417; ch&#7871; s&#7869; d&#7915;ng m&#7885;i th&#7913;, x&#7843; <em>t&#7845;t c&#7843;</em> dirty pages xu&#7889;ng &#273;&#297;a c&#249;ng l&#250;c, g&#226;y ra m&#7897;t c&#417;n b&#227;o I/O v&#224; l&#224;m h&#7879; th&#7889;ng b&#7883; &#8220;kh&#7921;ng&#8221; l&#7841;i.</p><p><strong>Anh Khoa:</strong> &#272;&#226;y l&#224; m&#7897;t c&#226;u h&#7887;i t&#236;nh hu&#7889;ng. Gi&#7843; s&#7917; em c&#243; Buffer Pool 10GB, &#273;ang ch&#7913;a 8GB d&#7919; li&#7879;u &#8220;n&#243;ng&#8221; (hot data) c&#7911;a ng&#432;&#7901;i d&#249;ng. M&#7897;t l&#7853;p tr&#236;nh vi&#234;n l&#7905; ch&#7841;y m&#7897;t c&#226;u l&#7879;nh <code>SELECT COUNT(*)</code> tr&#234;n m&#7897;t b&#7843;ng <code>log</code> r&#7845;t l&#7899;n (v&#237; d&#7909; 20GB). V&#7873; l&#253; thuy&#7871;t, vi&#7879;c n&#224;y s&#7869; &#273;&#7885;c 20GB d&#7919; li&#7879;u v&#224;o cache v&#224; &#8220;&#273;&#225; v&#259;ng&#8221; (evict) to&#224;n b&#7897; 8GB d&#7919; li&#7879;u n&#243;ng kia. T&#236;nh hu&#7889;ng n&#224;y g&#7885;i l&#224; g&#236; v&#224; InnoDB ng&#259;n ch&#7863;n n&#243; nh&#432; th&#7871; n&#224;o?</p><p><strong>duthaho:</strong> D&#7841;, t&#236;nh hu&#7889;ng &#273;&#243; g&#7885;i l&#224; <strong>&#8220;cache pollution&#8221;</strong> (&#244; nhi&#7877;m cache). InnoDB gi&#7843;i quy&#7871;t v&#7845;n &#273;&#7873; n&#224;y r&#7845;t th&#244;ng minh. N&#243; kh&#244;ng d&#249;ng m&#7897;t danh s&#225;ch LRU (Least Recently Used) &#273;&#417;n gi&#7843;n, m&#224; d&#249;ng m&#7897;t <strong>&#8220;midpoint insertion strategy&#8221;</strong>.</p><ol><li><p>Danh s&#225;ch LRU &#273;&#432;&#7907;c chia l&#224;m 2 ph&#7847;n: <strong>&#8220;young&#8221;</strong> (kho&#7843;ng 63%, ch&#7913;a d&#7919; li&#7879;u n&#243;ng) v&#224; <strong>&#8220;old&#8221;</strong> (kho&#7843;ng 37%, ch&#7913;a d&#7919; li&#7879;u m&#7899;i v&#224;o).</p></li><li><p>Khi m&#7897;t trang &#273;&#432;&#7907;c &#273;&#7885;c t&#7915; &#273;&#297;a l&#7847;n &#273;&#7847;u (nh&#432; trong l&#250;c qu&#233;t b&#7843;ng), n&#243; &#273;&#432;&#7907;c ch&#232;n v&#224;o <em>&#273;&#7847;u</em> c&#7911;a danh s&#225;ch &#8220;old&#8221; (t&#7913;c l&#224; &#8220;midpoint&#8221;).</p></li><li><p>Khi qu&#225; tr&#236;nh qu&#233;t b&#7843;ng di&#7877;n ra, c&#225;c trang n&#224;y s&#7869; b&#7883; &#273;&#7849;y d&#7847;n xu&#7889;ng &#273;u&#244;i danh s&#225;ch &#8220;old&#8221; v&#224; b&#7883; lo&#7841;i b&#7887; (evict) t&#7915; &#273;&#243;. Ch&#250;ng <em>kh&#244;ng bao gi&#7901;</em> &#273;&#432;&#7907;c &#8220;th&#259;ng h&#7841;ng&#8221; l&#234;n danh s&#225;ch &#8220;young&#8221; ch&#7881; v&#7899;i m&#7897;t l&#7847;n truy c&#7853;p.</p></li><li><p>Ch&#7881; khi m&#7897;t trang trong danh s&#225;ch &#8220;old&#8221; &#273;&#432;&#7907;c truy c&#7853;p l&#7841;i sau m&#7897;t kho&#7843;ng th&#7901;i gian (m&#7863;c &#273;&#7883;nh l&#224; 1 gi&#226;y), n&#243; m&#7899;i &#273;&#432;&#7907;c coi l&#224; &#8220;n&#243;ng&#8221; v&#224; &#273;&#432;&#7907;c th&#259;ng h&#7841;ng l&#234;n danh s&#225;ch &#8220;young&#8221;.</p><p>K&#7871;t qu&#7843; l&#224;, to&#224;n b&#7897; b&#7843;ng log 20GB &#273;&#243; s&#7869; &#273;i qua v&#249;ng &#8220;old&#8221; v&#224; b&#7883; th&#7843;i ra m&#224; kh&#244;ng h&#7873; &#7843;nh h&#432;&#7903;ng &#273;&#7871;n 8GB d&#7919; li&#7879;u n&#243;ng trong v&#249;ng &#8220;young&#8221;.</p></li></ol><p><strong>Anh Khoa:</strong> Tuy&#7879;t v&#7901;i, r&#7845;t chi ti&#7871;t. C&#226;u h&#7887;i cu&#7889;i c&#249;ng. V&#7899;i t&#432; c&#225;ch l&#224; ng&#432;&#7901;i v&#7853;n h&#224;nh h&#7879; th&#7889;ng, em s&#7869; d&#249;ng c&#226;u l&#7879;nh n&#224;o v&#224; nh&#236;n v&#224;o c&#225;c ch&#7881; s&#7889; (metrics) quan tr&#7885;ng n&#224;o &#273;&#7875; &#273;&#225;nh gi&#225; s&#7913;c kh&#7887;e c&#7911;a Buffer Pool?</p><p><strong>duthaho:</strong> D&#7841;, em s&#7869; d&#249;ng c&#226;u l&#7879;nh <code>SHOW ENGINE INNODB STATUS;</code>. Trong &#273;&#243;, em s&#7869; quan t&#226;m nh&#7845;t &#273;&#7871;n:</p><ol><li><p><code>Buffer pool hit rate</code><strong>:</strong> N&#7857;m trong ph&#7847;n <code>BUFFER POOL AND MEMORY</code>. T&#7927; l&#7879; n&#224;y ph&#7843;i lu&#244;n &#7903; m&#7913;c r&#7845;t cao, l&#253; t&#432;&#7903;ng l&#224; <code>&gt; 990 / 1000</code> (t&#7913;c 99.9%). N&#7871;u n&#243; th&#7845;p, c&#243; ngh&#297;a l&#224; Buffer Pool qu&#225; nh&#7887;.</p></li><li><p><code>Modified db pages</code><strong> (Dirty Pages):</strong> C&#361;ng trong ph&#7847;n &#273;&#243;. N&#7871;u con s&#7889; n&#224;y qu&#225; cao v&#224; li&#234;n t&#7909;c t&#259;ng, n&#243; cho th&#7845;y h&#7879; th&#7889;ng I/O (&#273;&#297;a) &#273;ang kh&#244;ng &#8220;x&#7843;&#8221; k&#7883;p l&#432;&#7907;ng ghi (write) c&#7911;a h&#7879; th&#7889;ng.</p></li><li><p><code>---LOG---</code><strong> section:</strong> Em s&#7869; xem <code>Log sequence number</code> v&#224; <code>Last checkpoint at</code>. Kho&#7843;ng c&#225;ch gi&#7919;a hai s&#7889; n&#224;y (Checkpoint Age) cho bi&#7871;t redo log &#273;ang &#273;&#432;&#7907;c s&#7917; d&#7909;ng nhi&#7873;u &#273;&#7871;n &#273;&#226;u. N&#7871;u kho&#7843;ng c&#225;ch n&#224;y g&#7847;n b&#7857;ng t&#7893;ng k&#237;ch th&#432;&#7899;c redo log, h&#7879; th&#7889;ng s&#7855;p b&#7883; &#8220;ngh&#7869;n&#8221; v&#236; checkpointing ph&#7843;i ho&#7841;t &#273;&#7897;ng h&#7871;t c&#244;ng su&#7845;t.</p></li></ol><p><strong>Anh Khoa:</strong> OK, duthaho. Ph&#7847;n c&#7911;a anh h&#244;m nay l&#224; v&#7853;y. R&#7845;t c&#7843;m &#417;n em v&#7873; ph&#7847;n trao &#273;&#7893;i r&#7845;t s&#226;u. Em c&#243; c&#226;u h&#7887;i n&#224;o cho anh kh&#244;ng?</p><p><strong>duthaho:</strong> D&#7841;, hi&#7879;n t&#7841;i em ch&#432;a c&#243;. Em c&#7843;m &#417;n anh Khoa &#273;&#227; d&#224;nh th&#7901;i gian &#7841;.</p><p><strong>Anh Khoa:</strong> C&#7843;m &#417;n em. B&#7897; ph&#7853;n HR s&#7869; li&#234;n h&#7879; v&#7899;i em v&#7873; c&#225;c b&#432;&#7899;c ti&#7871;p theo nh&#233;. Ch&#224;o em.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Giải mã MySQL: Page/Block]]></title><description><![CDATA[B&#7889;i c&#7843;nh: duthaho &#273;ang ph&#7887;ng v&#7845;n cho v&#7883; tr&#237; Senior Backend Developer t&#7841;i m&#7897;t c&#244;ng ty c&#244;ng ngh&#7879; l&#7899;n.]]></description><link>https://duthaho.substack.com/p/giai-ma-mysql-pageblock</link><guid isPermaLink="false">https://duthaho.substack.com/p/giai-ma-mysql-pageblock</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Thu, 23 Oct 2025 11:20:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!l2Wh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!l2Wh!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png 424w, /__u/substackcdn.com/image/fetch/$s_!l2Wh!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png 848w, /__u/substackcdn.com/image/fetch/$s_!l2Wh!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png 1272w, /__u/substackcdn.com/image/fetch/$s_!l2Wh!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!l2Wh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png" width="261" height="193" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:193,&quot;width&quot;:261,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;InnoDB &#8211; Jeremy Cole - Page 4&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="InnoDB &#8211; Jeremy Cole - Page 4" title="InnoDB &#8211; Jeremy Cole - Page 4" srcset="/__u/substackcdn.com/image/fetch/$s_!l2Wh!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png 424w, /__u/substackcdn.com/image/fetch/$s_!l2Wh!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png 848w, /__u/substackcdn.com/image/fetch/$s_!l2Wh!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png 1272w, /__u/substackcdn.com/image/fetch/$s_!l2Wh!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F97e76cea-c433-480c-8a2f-2c5055f44a1f_261x193.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p><strong>B&#7889;i c&#7843;nh:</strong> duthaho &#273;ang ph&#7887;ng v&#7845;n cho v&#7883; tr&#237; Senior Backend Developer t&#7841;i m&#7897;t c&#244;ng ty c&#244;ng ngh&#7879; l&#7899;n. Ng&#432;&#7901;i ph&#7887;ng v&#7845;n l&#224; Anh Khoa, m&#7897;t Solution Architect.</p><div><hr></div><h3>B&#7854;T &#272;&#7846;U BU&#7892;I PH&#7886;NG V&#7844;N</h3><p><strong>Anh Khoa:</strong> Ch&#224;o duthaho, t&#244;i l&#224; Khoa. C&#7843;m &#417;n b&#7841;n &#273;&#227; d&#224;nh th&#7901;i gian tham gia bu&#7893;i ph&#7887;ng v&#7845;n h&#244;m nay. Trong CV b&#7841;n c&#243; ghi kinh nghi&#7879;m l&#224;m vi&#7879;c s&#226;u v&#7899;i MySQL, t&#244;i r&#7845;t mu&#7889;n &#273;&#224;o s&#226;u v&#7873; ph&#7847;n n&#224;y. Ch&#250;ng ta b&#7855;t &#273;&#7847;u lu&#244;n nh&#233;?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>duthaho:</strong> V&#226;ng, ch&#224;o anh Khoa. T&#244;i &#273;&#227; s&#7861;n s&#224;ng.</p><p><strong>Anh Khoa:</strong> T&#7889;t. C&#226;u &#273;&#7847;u ti&#234;n nh&#233;. Khi n&#243;i v&#7873; InnoDB, ch&#250;ng ta hay nh&#7855;c &#273;&#7871;n &#8220;page&#8221;. B&#7841;n c&#243; th&#7875; gi&#7843;i th&#237;ch &#8220;page&#8221; l&#224; g&#236; v&#224; t&#7841;i sao k&#237;ch th&#432;&#7899;c m&#7863;c &#273;&#7883;nh c&#7911;a n&#243; l&#7841;i l&#224; 16KB kh&#244;ng?</p><p><strong>duthaho:</strong> D&#7841; v&#226;ng. Page (hay c&#242;n g&#7885;i l&#224; block) l&#224; &#273;&#417;n v&#7883; I/O nh&#7887; nh&#7845;t m&#224; InnoDB d&#249;ng &#273;&#7875; &#273;&#7885;c v&#224; ghi d&#7919; li&#7879;u xu&#7889;ng &#273;&#297;a. Thay v&#236; &#273;&#7885;c t&#7915;ng row, InnoDB s&#7869; &#273;&#7885;c/ghi nguy&#234;n m&#7897;t page 16KB v&#224;o Buffer Pool.</p><p>Vi&#7879;c ch&#7885;n 16KB l&#224; m&#7897;t s&#7921; c&#226;n b&#7857;ng c&#243; t&#237;nh to&#225;n:</p><ol><li><p><strong>Hi&#7879;u su&#7845;t I/O:</strong> N&#243; &#273;&#7911; l&#7899;n &#273;&#7875; gom nhi&#7873;u block 4KB c&#7911;a h&#7879; &#273;i&#7873;u h&#224;nh/&#7893; &#273;&#297;a, gi&#250;p gi&#7843;m s&#7889; l&#7847;n I/O v&#7853;t l&#253;, &#273;&#7863;c bi&#7879;t quan tr&#7885;ng v&#7899;i HDD.</p></li><li><p><strong>C&#7845;u tr&#250;c B+Tree:</strong> K&#237;ch th&#432;&#7899;c 16KB cho ph&#233;p m&#7895;i node (page) ch&#7913;a &#273;&#432;&#7907;c nhi&#7873;u key v&#224; con tr&#7887;, l&#224;m cho c&#226;y B+Tree &#8220;r&#7897;ng&#8221; v&#224; &#8220;n&#244;ng&#8221; h&#417;n. &#272;i&#7873;u n&#224;y gi&#250;p gi&#7843;m s&#7889; l&#7847;n &#273;&#7885;c page c&#7847;n thi&#7871;t &#273;&#7875; &#273;i t&#7915; root &#273;&#7871;n leaf, t&#259;ng t&#7889;c &#273;&#7897; truy v&#7845;n.</p></li><li><p><strong>Amortization (Chia &#273;&#7873;u chi ph&#237;):</strong> M&#7895;i page &#273;&#7873;u c&#243; header v&#224; trailer (ch&#7913;a metadata, checksum...). V&#7899;i page 16KB, ph&#7847;n overhead n&#224;y chi&#7871;m t&#7927; l&#7879; nh&#7887;, t&#7889;i &#432;u kh&#244;ng gian cho d&#7919; li&#7879;u.</p></li></ol><p><strong>Anh Khoa:</strong> Gi&#7843;i th&#237;ch r&#7845;t r&#245; r&#224;ng. Gi&#7901; h&#227;y &#273;i s&#226;u h&#417;n. Gi&#7843; s&#7917; t&#244;i c&#243; m&#7897;t page &#273;&#227; &#273;&#7847;y 100% v&#224; t&#244;i <code>INSERT</code> m&#7897;t row m&#7899;i thu&#7897;c v&#7873; page &#273;&#243;. Chuy&#7879;n g&#236; s&#7869; x&#7843;y ra?</p><p><strong>duthaho:</strong> &#272;&#243; l&#224; l&#250;c x&#7843;y ra &#8220;page split&#8221;.</p><p><strong>Anh Khoa:</strong> B&#7841;n n&#243;i r&#245; h&#417;n &#273;&#432;&#7907;c kh&#244;ng? &#8220;Page split&#8221; &#7843;nh h&#432;&#7903;ng th&#7871; n&#224;o &#273;&#7871;n hi&#7879;u n&#259;ng? T&#244;i kh&#244;ng ch&#7881; mu&#7889;n bi&#7871;t v&#7873; &#273;&#7897; tr&#7877; c&#7911;a query &#273;&#243;, m&#224; l&#224; &#7843;nh h&#432;&#7903;ng l&#226;u d&#224;i &#273;&#7871;n to&#224;n h&#7879; th&#7889;ng.</p><p><strong>duthaho:</strong> V&#226;ng. Khi m&#7897;t page (v&#237; d&#7909; Page 100) b&#7883; &#273;&#7847;y:</p><ol><li><p>InnoDB s&#7869; &#8220;kh&#243;a&#8221; (latch) page &#273;&#243; l&#7841;i.</p></li><li><p>N&#243; t&#7841;o m&#7897;t page m&#7899;i (Page 101).</p></li><li><p>N&#243; di chuy&#7875;n kho&#7843;ng m&#7897;t n&#7917;a s&#7889; row t&#7915; Page 100 sang Page 101.</p></li><li><p>N&#243; c&#7853;p nh&#7853;t l&#7841;i c&#225;c con tr&#7887; &#7903; page cha (non-leaf) &#273;&#7875; tr&#7887; &#273;&#7871;n c&#7843; hai page n&#224;y, v&#224; c&#7853;p nh&#7853;t linked-list &#7903; m&#7913;c leaf.</p></li></ol><p>&#7842;nh h&#432;&#7903;ng hi&#7879;u n&#259;ng l&#224; r&#7845;t r&#245;:</p><ol><li><p><strong>T&#7913;c th&#236;:</strong> Query <code>INSERT</code> (ho&#7863;c <code>UPDATE</code>) g&#226;y ra split s&#7869; b&#7883; tr&#7877; (latency spike) v&#236; n&#243; ph&#7843;i l&#224;m r&#7845;t nhi&#7873;u vi&#7879;c (c&#7845;p ph&#225;t page, di chuy&#7875;n d&#7919; li&#7879;u, ghi log...) thay v&#236; ch&#7881; ghi v&#224;o b&#7897; nh&#7899;.</p></li><li><p><strong>L&#226;u d&#224;i (&#272;&#226;y l&#224; v&#7845;n &#273;&#7873; l&#7899;n):</strong> N&#243; g&#226;y ra <strong>fragmentation</strong> (ph&#226;n m&#7843;nh). Hai page c&#361; v&#224; m&#7899;i gi&#7901; ch&#7881; c&#242;n &#273;&#7847;y kho&#7843;ng 50%. &#272;i&#7873;u n&#224;y l&#224;m l&#227;ng ph&#237; kh&#244;ng gian &#273;&#297;a v&#224; quan tr&#7885;ng h&#417;n l&#224; l&#227;ng ph&#237; <strong>Buffer Pool</strong>. H&#7879; th&#7889;ng s&#7869; ph&#7843;i &#273;&#7885;c nhi&#7873;u page h&#417;n &#273;&#7875; l&#7845;y c&#249;ng m&#7897;t l&#432;&#7907;ng d&#7919; li&#7879;u, l&#224;m gi&#7843;m hi&#7879;u qu&#7843; c&#7911;a cache.</p></li></ol><p><strong>Anh Khoa:</strong> R&#7845;t t&#7889;t. B&#7841;n c&#243; nh&#7855;c &#273;&#7871;n &#8220;fragmentation&#8221;. V&#7853;y t&#236;nh hu&#7889;ng th&#7921;c t&#7871; n&#224;o th&#432;&#7901;ng g&#226;y ra fragmentation nhi&#7873;u nh&#7845;t?</p><p><strong>duthaho:</strong> Nguy&#234;n nh&#226;n ph&#7893; bi&#7871;n nh&#7845;t l&#224; s&#7917; d&#7909;ng Primary Key kh&#244;ng tu&#7847;n t&#7921;, v&#237; d&#7909; nh&#432; <code>UUID</code> (v4). C&#225;c <code>INSERT</code> s&#7869; &#273;i v&#224;o c&#225;c page ng&#7851;u nhi&#234;n trong B-Tree, khi&#7871;n c&#225;c page n&#224;y li&#234;n t&#7909;c b&#7883; split v&#224; &#273;&#7875; l&#7841;i 50% tr&#7889;ng. Ng&#432;&#7907;c l&#7841;i, <code>AUTO_INCREMENT</code> th&#236; lu&#244;n ghi v&#224;o page cu&#7889;i c&#249;ng (append-only), r&#7845;t hi&#7879;u qu&#7843; v&#224; g&#7847;n nh&#432; kh&#244;ng g&#226;y fragmentation.</p><p><strong>Anh Khoa:</strong> Ch&#237;nh x&#225;c. Gi&#7901; h&#227;y n&#243;i v&#7873; schema. T&#244;i c&#243; m&#7897;t b&#7843;ng <code>articles</code> v&#7899;i c&#7897;t <code>content</code> ki&#7875;u <code>TEXT</code>. Khi t&#244;i <code>SELECT id, title FROM articles WHERE id = 123;</code>, InnoDB c&#243; &#273;&#7885;c d&#7919; li&#7879;u c&#7911;a c&#7897;t <code>content</code> kh&#244;ng?</p><p><strong>duthaho:</strong> D&#7841; kh&#244;ng. &#272;&#226;y l&#224; m&#7897;t c&#417; ch&#7871; t&#7889;i &#432;u quan tr&#7885;ng. V&#7899;i c&#225;c ki&#7875;u d&#7919; li&#7879;u LOB (Large Object) nh&#432; <code>TEXT</code> hay <code>BLOB</code>, InnoDB s&#7869; l&#432;u tr&#7919; ch&#250;ng &#8220;off-page&#8221;.</p><p><strong>Anh Khoa:</strong> &#8220;Off-page&#8221;? T&#7913;c l&#224; sao?</p><p><strong>duthaho:</strong> T&#7913;c l&#224; tr&#234;n page l&#225; (leaf page) c&#7911;a B-Tree (n&#417;i ch&#7913;a d&#7919; li&#7879;u row), thay v&#236; l&#432;u to&#224;n b&#7897; 5MB <code>TEXT</code>, InnoDB ch&#7881; l&#432;u m&#7897;t <strong>con tr&#7887; 20-byte</strong>. Con tr&#7887; n&#224;y s&#7869; tr&#7887; &#273;&#7871;n m&#7897;t chu&#7895;i c&#225;c page &#273;&#7863;c bi&#7879;t kh&#225;c chuy&#234;n d&#249;ng &#273;&#7875; l&#432;u d&#7919; li&#7879;u BLOB.</p><p>V&#236; v&#7853;y, khi anh <code>SELECT id, title</code>, InnoDB ch&#7881; c&#7847;n &#273;&#7885;c leaf page ch&#237;nh, l&#7845;y <code>id</code> v&#224; <code>title</code>. N&#243; ho&#224;n to&#224;n kh&#244;ng c&#7847;n &#273;&#7885;c c&#225;c page off-page kia, gi&#250;p truy v&#7845;n c&#7921;c k&#7923; nhanh v&#224; kh&#244;ng l&#224;m &#244; nhi&#7877;m Buffer Pool v&#7899;i d&#7919; li&#7879;u LOB kh&#7893;ng l&#7891;.</p><p><strong>Anh Khoa:</strong> V&#7853;y anti-pattern &#7903; &#273;&#226;y l&#224; g&#236;?</p><p><strong>duthaho:</strong> R&#245; r&#224;ng l&#224; <code>SELECT * FROM articles;</code>. C&#226;u l&#7879;nh n&#224;y bu&#7897;c InnoDB ph&#7843;i &#273;i theo <em>t&#7915;ng</em> con tr&#7887; 20-byte c&#7911;a <em>m&#7895;i</em> row &#273;&#7875; &#273;&#7885;c <em>to&#224;n b&#7897;</em> d&#7919; li&#7879;u <code>TEXT</code> off-page, k&#7875; c&#7843; khi &#7913;ng d&#7909;ng kh&#244;ng c&#7847;n. &#272;i&#7873;u n&#224;y gi&#7871;t ch&#7871;t hi&#7879;u su&#7845;t I/O v&#224; &#273;&#7849;y to&#224;n b&#7897; index/data page n&#243;ng ra kh&#7887;i Buffer Pool.</p><p><strong>Anh Khoa:</strong> OK. V&#7853;y gi&#7919;a <code>VARCHAR(20000)</code> v&#224; <code>TEXT</code>, c&#225;i n&#224;o t&#7889;t h&#417;n?</p><p><strong>duthaho:</strong> N&#243; t&#249;y v&#224;o m&#7909;c &#273;&#237;ch. <code>TEXT</code> &#273;&#432;&#7907;c <em>thi&#7871;t k&#7871;</em> &#273;&#7875; l&#432;u off-page (v&#7899;i con tr&#7887; 20-byte). <code>VARCHAR</code> th&#236; <em>c&#7889; g&#7855;ng</em> l&#432;u inline (tr&#234;n page ch&#237;nh). Tuy nhi&#234;n, <code>VARCHAR(20000)</code> n&#7871;u ch&#7913;a d&#7919; li&#7879;u l&#7899;n c&#361;ng s&#7869; b&#7883; &#273;&#7849;y ra off-page, ho&#7841;t &#273;&#7897;ng t&#432;&#417;ng t&#7921; <code>TEXT</code>.</p><p>Nh&#432;ng c&#243; m&#7897;t c&#225;i b&#7851;y v&#7899;i <code>VARCHAR</code> l&#7899;n: N&#7871;u MySQL c&#7847;n t&#7841;o b&#7843;ng t&#7841;m trong b&#7897; nh&#7899; (v&#237; d&#7909; khi <code>GROUP BY</code>), n&#243; c&#243; th&#7875; c&#7845;p ph&#225;t b&#7897; nh&#7899; d&#7921;a tr&#234;n k&#237;ch th&#432;&#7899;c <em>t&#7889;i &#273;a</em> (20000), g&#226;y l&#227;ng ph&#237; RAM. V&#236; v&#7853;y, n&#7871;u d&#7919; li&#7879;u ch&#7855;c ch&#7855;n l&#7899;n, t&#244;i c&#243; xu h&#432;&#7899;ng d&#249;ng <code>TEXT</code> &#273;&#7875; r&#245; r&#224;ng v&#7873; m&#7863;t ki&#7871;n tr&#250;c l&#224; n&#243; s&#7869; &#273;&#432;&#7907;c l&#432;u off-page.</p><p><strong>Anh Khoa:</strong> Ch&#250;ng ta &#273;&#227; n&#243;i nhi&#7873;u v&#7873; leaf page. V&#7853;y &#8220;leaf page&#8221; v&#224; &#8220;non-leaf page&#8221; trong B-Tree kh&#225;c nhau &#273;i&#7875;m n&#224;o?</p><p><strong>duthaho:</strong> &#8220;Non-leaf page&#8221; (page kh&#244;ng ph&#7843;i l&#225;) l&#224; c&#225;c &#8220;bi&#7875;n ch&#7881; d&#7851;n&#8221;. Ch&#250;ng ch&#7881; ch&#7913;a (Key, Con tr&#7887;) &#273;&#7875; &#273;i&#7873;u h&#432;&#7899;ng. V&#237; d&#7909;: &#8220;C&#225;c key &lt; 100 th&#236; &#273;i page 201&#8221;, &#8220;C&#225;c key 100-200 th&#236; &#273;i page 202&#8221;. Page tr&#234;n c&#249;ng l&#224; Root.</p><p>C&#242;n &#8220;Leaf page&#8221; l&#224; &#8220;&#273;&#237;ch &#273;&#7871;n&#8221;. Ch&#250;ng n&#7857;m &#7903; t&#7847;ng d&#432;&#7899;i c&#249;ng, ch&#7913;a d&#7919; li&#7879;u th&#7921;c t&#7871; (v&#7899;i Clustered Index) ho&#7863;c ch&#7913;a (Key, Primary Key) (v&#7899;i Secondary Index). V&#224; &#273;i&#7875;m kh&#225;c bi&#7879;t quan tr&#7885;ng nh&#7845;t: <strong>T&#7845;t c&#7843; c&#225;c leaf page &#273;&#432;&#7907;c li&#234;n k&#7871;t v&#7899;i nhau b&#7857;ng m&#7897;t doubly-linked list</strong>.</p><p><strong>Anh Khoa:</strong> T&#7841;i sao c&#225;i linked-list &#273;&#243; l&#7841;i quan tr&#7885;ng?</p><p><strong>duthaho:</strong> N&#243; c&#7921;c k&#7923; quan tr&#7885;ng cho <strong>range scan</strong>. Khi anh ch&#7841;y <code>SELECT * FROM users WHERE id &gt; 100 AND id &lt; 150</code>, MySQL s&#7869; duy&#7879;t B-Tree 1 l&#7847;n &#273;&#7875; t&#236;m <code>id = 100</code> (&#7903; Leaf Page X). Sau &#273;&#243;, thay v&#236; duy&#7879;t l&#7841;i c&#226;y, n&#243; ch&#7881; c&#7847;n &#273;i theo con tr&#7887; <code>NEXT_PAGE</code> c&#7911;a linked-list (t&#7915; Page X sang Page Y,...) &#273;&#7875; scan l&#7845;y d&#7919; li&#7879;u m&#7897;t c&#225;ch tu&#7847;n t&#7921; v&#224; hi&#7879;u qu&#7843;.</p><p><strong>Anh Khoa:</strong> R&#7845;t t&#7889;t. C&#226;u cu&#7889;i c&#249;ng. B&#7841;n &#273;&#227; nh&#7855;c &#273;&#7871;n &#8220;Buffer Pool&#8221;. V&#7853;y &#8220;dirty page&#8221; l&#224; g&#236;, v&#224; n&#243; li&#234;n quan g&#236; &#273;&#7871;n vi&#7879;c <code>UPDATE</code> c&#7911;a t&#244;i &#273;&#432;&#7907;c tr&#7843; v&#7873; &#8220;OK&#8221; g&#7847;n nh&#432; ngay l&#7853;p t&#7913;c?</p><p><strong>duthaho:</strong> D&#7841;. &#8220;Buffer Pool&#8221; l&#224; cache trong RAM ch&#7913;a c&#225;c page 16KB.</p><ul><li><p><strong>Clean Page:</strong> L&#224; page trong Buffer Pool gi&#7889;ng h&#7879;t page tr&#234;n &#273;&#297;a.</p></li><li><p><strong>Dirty Page:</strong> L&#224; page trong Buffer Pool &#273;&#227; b&#7883; thay &#273;&#7893;i (v&#237; d&#7909; do <code>UPDATE</code>) nh&#432;ng <strong>ch&#432;a</strong> &#273;&#432;&#7907;c ghi xu&#7889;ng &#273;&#297;a.</p></li></ul><p>Khi anh <code>UPDATE</code> m&#7897;t row, InnoDB th&#7921;c hi&#7879;n 2 vi&#7879;c song song:</p><ol><li><p>N&#243; t&#236;m page &#273;&#243; trong Buffer Pool, thay &#273;&#7893;i d&#7919; li&#7879;u <em>trong RAM</em>, v&#224; &#273;&#225;nh d&#7845;u page &#273;&#243; l&#224; &#8220;dirty&#8221;.</p></li><li><p>N&#243; ghi l&#7841;i s&#7921; thay &#273;&#7893;i n&#224;y v&#224;o Redo Log (WAL) v&#224; &#8220;fsync&#8221; file log &#273;&#243; xu&#7889;ng &#273;&#297;a.</p></li></ol><p>B&#7903;i v&#236; vi&#7879;c ghi v&#224;o Redo Log (ghi tu&#7847;n t&#7921;) r&#7845;t nhanh, v&#224; vi&#7879;c thay &#273;&#7893;i trong RAM (Buffer Pool) c&#361;ng t&#7913;c th&#7901;i, n&#234;n InnoDB c&#243; th&#7875; tr&#7843; v&#7873; &#8220;OK&#8221; cho anh ngay. C&#244;ng vi&#7879;c n&#7863;ng nh&#7885;c l&#224; &#8220;flush&#8221; (ghi) c&#225;i <strong>dirty page</strong> 16KB kia (ghi ng&#7851;u nhi&#234;n) xu&#7889;ng file d&#7919; li&#7879;u <code>.ibd</code> s&#7869; &#273;&#432;&#7907;c m&#7897;t thread n&#7873;n x&#7917; l&#253; sau m&#7897;t c&#225;ch t&#7915; t&#7915;. &#272;&#226;y l&#224; c&#417; ch&#7871; gi&#250;p InnoDB c&#243; write performance cao.</p><p><strong>Anh Khoa:</strong> C&#7843;m &#417;n duthaho. B&#7841;n hi&#7875;u r&#7845;t s&#226;u v&#7873; c&#225;c th&#224;nh ph&#7847;n c&#7889;t l&#245;i. T&#244;i kh&#244;ng c&#243; c&#226;u h&#7887;i n&#224;o th&#234;m. B&#7841;n c&#243; c&#226;u h&#7887;i g&#236; cho t&#244;i kh&#244;ng?</p><p><strong>duthaho:</strong> D&#7841;, anh c&#243; th&#7875; chia s&#7867; th&#234;m v&#7873;...</p><div><hr></div><h3>K&#7870;T TH&#218;C BU&#7892;I PH&#7886;NG V&#7844;N</h3><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: bài toán Bulk Download]]></title><description><![CDATA[Ch&#224;o m&#7885;i ng&#432;&#7901;i, l&#7841;i l&#224; duthaho &#273;i ph&#7887;ng v&#7845;n &#273;&#226;y.]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-bai-toan-bulk-download</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-bai-toan-bulk-download</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Wed, 22 Oct 2025 04:17:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Udj_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!Udj_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!Udj_!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!Udj_!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!Udj_!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!Udj_!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!Udj_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg" width="864" height="338" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:338,&quot;width&quot;:864,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Bulk Download and Deletion of Session Files&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Bulk Download and Deletion of Session Files" title="Bulk Download and Deletion of Session Files" srcset="/__u/substackcdn.com/image/fetch/$s_!Udj_!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg 424w, /__u/substackcdn.com/image/fetch/$s_!Udj_!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg 848w, /__u/substackcdn.com/image/fetch/$s_!Udj_!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg 1272w, /__u/substackcdn.com/image/fetch/$s_!Udj_!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c11bfdb-4775-4a5e-8349-064ad2f552f1_864x338.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ch&#224;o m&#7885;i ng&#432;&#7901;i, l&#7841;i l&#224; duthaho &#273;i ph&#7887;ng v&#7845;n &#273;&#226;y. H&#244;m nay ch&#250;ng ta c&#249;ng &#273;&#224;o s&#226;u t&#236;m hi&#7875;u v&#7873; b&#224;i to&#225;n th&#7921;c t&#7871; r&#7845;t hay g&#7863;p: Bulk Download. M&#7885;i ng&#432;&#7901;i nh&#7899; &#7911;ng h&#7897; m&#236;nh b&#7857;ng c&#225;ch like v&#224; subscribe nh&#233;!</p><div><hr></div><h3>B&#7889;i c&#7843;nh bu&#7893;i ph&#7887;ng v&#7845;n</h3><ul><li><p><strong>Ng&#432;&#7901;i ph&#7887;ng v&#7845;n:</strong> Anh Khoa (Senior Solution Architect)</p></li><li><p><strong>&#7912;ng vi&#234;n:</strong> duthaho</p></li><li><p><strong>V&#7883; tr&#237;:</strong> Senior Backend Engineer / Junior Solution Architect</p></li><li><p><strong>Th&#7901;i l&#432;&#7907;ng:</strong> 30 ph&#250;t</p></li><li><p><strong>Ch&#7911; &#273;&#7873;:</strong> System Design - T&#237;nh n&#259;ng Download file h&#224;ng lo&#7841;t</p></li></ul><div><hr></div><h3>B&#7855;t &#273;&#7847;u bu&#7893;i ph&#7887;ng v&#7845;n</h3><p><strong>(Anh Khoa):</strong> Ch&#224;o duthaho, t&#244;i l&#224; Khoa. C&#7843;m &#417;n b&#7841;n &#273;&#227; d&#224;nh th&#7901;i gian tham gia bu&#7893;i h&#244;m nay. Nh&#432; &#273;&#227; trao &#273;&#7893;i, ch&#250;ng ta s&#7869; c&#243; m&#7897;t phi&#234;n v&#7873; System Design.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>(duthaho):</strong> D&#7841;, em ch&#224;o anh Khoa. Em &#273;&#227; s&#7861;n s&#224;ng &#7841;.</p><p><strong>(Anh Khoa):</strong> T&#7889;t. Ch&#250;ng ta b&#7855;t &#273;&#7847;u lu&#244;n nh&#233;. Gi&#7843; s&#7917; h&#7879; th&#7889;ng c&#7911;a b&#7841;n l&#224; m&#7897;t n&#7873;n t&#7843;ng l&#432;u tr&#7919; media, t&#432;&#417;ng t&#7921; nh&#432; Google Drive ho&#7863;c Dropbox. Ng&#432;&#7901;i d&#249;ng l&#432;u tr&#7919; h&#224;ng tri&#7879;u file audio/video tr&#234;n &#273;&#243;. Y&#234;u c&#7847;u nghi&#7879;p v&#7909; m&#7899;i l&#224;: &#8220;Ng&#432;&#7901;i d&#249;ng mu&#7889;n c&#243; m&#7897;t n&#250;t &#8216;Bulk Download&#8217;. H&#7885; c&#243; th&#7875; ch&#7885;n h&#224;ng ngh&#236;n file m&#7897;t l&#250;c&#8212;tr&#432;&#7901;ng h&#7907;p x&#7845;u nh&#7845;t c&#243; th&#7875; l&#234;n &#273;&#7871;n 500GB&#8212;v&#224; h&#7879; th&#7889;ng ph&#7843;i n&#233;n c&#225;c file n&#224;y l&#7841;i v&#224; cho ph&#233;p h&#7885; t&#7843;i v&#7873;.&#8221;</p><p>B&#7841;n s&#7869; thi&#7871;t k&#7871; t&#237;nh n&#259;ng n&#224;y nh&#432; th&#7871; n&#224;o?</p><p><strong>(duthaho):</strong> D&#7841;, &#273;&#226;y l&#224; m&#7897;t case study r&#7845;t hay &#7841;. Tr&#432;&#7899;c khi &#273;&#432;a ra gi&#7843;i ph&#225;p, em xin ph&#233;p &#273;&#432;&#7907;c &#273;&#7863;t m&#7897;t v&#224;i c&#226;u h&#7887;i &#273;&#7875; l&#224;m r&#245; y&#234;u c&#7847;u, v&#236; c&#225;c y&#234;u c&#7847;u n&#224;y s&#7869; &#7843;nh h&#432;&#7903;ng tr&#7921;c ti&#7871;p &#273;&#7871;n l&#7921;a ch&#7885;n ki&#7871;n tr&#250;c.</p><ol><li><p><strong>V&#7873; ch&#7913;c n&#259;ng:</strong> C&#225;c file ngu&#7891;n n&#224;y &#273;ang &#273;&#432;&#7907;c l&#432;u tr&#7919; &#7903; &#273;&#226;u? Tr&#234;n server c&#7911;a m&#236;nh hay tr&#234;n m&#7897;t object storage nh&#432; S3 &#7841;?</p></li><li><p><strong>V&#7873; phi ch&#7913;c n&#259;ng (NFR):</strong> Ng&#432;&#7901;i d&#249;ng c&#243; th&#7875; ch&#7901; bao l&#226;u? H&#7885; nh&#7845;n n&#250;t v&#224; ch&#7901; m&#224;n h&#236;nh loading (&#273;&#7891;ng b&#7897;), hay h&#7885; ch&#7845;p nh&#7853;n nh&#7853;n th&#244;ng b&#225;o sau (b&#7845;t &#273;&#7891;ng b&#7897;)? V&#7899;i 500GB th&#236; em &#273;o&#225;n ch&#7855;c ch&#7855;n l&#224; b&#7845;t &#273;&#7891;ng b&#7897;.</p></li><li><p><strong>V&#7873; t&#7843;i (Load):</strong> T&#7847;n su&#7845;t s&#7917; d&#7909;ng t&#237;nh n&#259;ng n&#224;y c&#243; cao kh&#244;ng? V&#237; d&#7909;, ch&#250;ng ta c&#243; bao nhi&#234;u request &#8220;Bulk Download&#8221; &#273;&#7891;ng th&#7901;i trong m&#7897;t ph&#250;t?</p></li><li><p><strong>V&#7873; &#273;&#7847;u ra:</strong> Y&#234;u c&#7847;u l&#224; &#8220;1 ho&#7863;c nhi&#7873;u file zip&#8221;. Logic chia file zip l&#224; g&#236;? V&#237; d&#7909;: chia theo m&#7895;i 10GB, hay c&#7913; n&#233;n th&#224;nh m&#7897;t file l&#7899;n duy nh&#7845;t?</p></li></ol><p><strong>(Anh Khoa):</strong> R&#7845;t t&#7889;t, &#273;&#243; ch&#237;nh x&#225;c l&#224; nh&#7919;ng c&#226;u h&#7887;i t&#244;i mong &#273;&#7907;i. Ch&#250;ng ta h&#227;y gi&#7843; &#273;&#7883;nh th&#7871; n&#224;y:</p><ol><li><p>File &#273;ang n&#7857;m tr&#234;n <strong>AWS S3</strong>.</p></li><li><p>Ch&#237;nh x&#225;c, &#273;&#226;y ph&#7843;i l&#224; m&#7897;t quy tr&#236;nh <strong>b&#7845;t &#273;&#7891;ng b&#7897; (asynchronous)</strong>. Ng&#432;&#7901;i d&#249;ng kh&#244;ng ph&#7843;i ch&#7901;.</p></li><li><p>T&#7847;n su&#7845;t &#7903; m&#7913;c trung b&#236;nh, kho&#7843;ng <strong>100-200 request/ph&#250;t</strong>.</p></li><li><p>Hi&#7879;n t&#7841;i, c&#7913; g&#7897;p th&#224;nh <strong>m&#7897;t file zip l&#7899;n nh&#7845;t c&#243; th&#7875;</strong>. N&#7871;u c&#243; v&#7845;n &#273;&#7873;, ch&#250;ng ta s&#7869; t&#237;nh &#273;&#7871;n vi&#7879;c chia nh&#7887; sau.</p></li></ol><p>M&#7901;i b&#7841;n tr&#236;nh b&#224;y ki&#7871;n tr&#250;c t&#7893;ng quan.</p><p><strong>(duthaho):</strong> D&#7841;, v&#7899;i c&#225;c y&#234;u c&#7847;u n&#224;y, em s&#7869; &#273;&#7873; xu&#7845;t m&#7897;t ki&#7871;n tr&#250;c x&#7917; l&#253; n&#7873;n (background processing) s&#7917; d&#7909;ng Message Queue.</p><p>Lu&#7891;ng ho&#7841;t &#273;&#7897;ng s&#7869; nh&#432; sau:</p><ol><li><p><strong>Client</strong> g&#7885;i &#273;&#7871;n <strong>API Server</strong> v&#7899;i danh s&#225;ch c&#225;c file ID c&#7847;n n&#233;n.</p></li><li><p><strong>API Server</strong> s&#7869; kh&#244;ng x&#7917; l&#253; ngay. Thay v&#224;o &#273;&#243;, n&#243; s&#7869;:</p><ul><li><p>T&#7841;o m&#7897;t <code>job_id</code>.</p></li><li><p>L&#432;u th&#244;ng tin job (danh s&#225;ch file, <code>user_id</code>, status l&#224; <code>PENDING</code>) v&#224;o m&#7897;t <strong>Job Database</strong> (v&#237; d&#7909; DynamoDB ho&#7863;c PostgreSQL).</p></li><li><p>&#272;&#7849;y m&#7897;t message ch&#7913;a <code>job_id</code> v&#224;o m&#7897;t <strong>Message Queue</strong> (v&#237; d&#7909; AWS SQS).</p></li><li><p>Tr&#7843; v&#7873; <code>job_id</code> cho client ngay l&#7853;p t&#7913;c.</p></li></ul></li><li><p>Client s&#7869; d&#249;ng <code>job_id</code> n&#224;y &#273;&#7875; polling (h&#7887;i) API, ho&#7863;c ch&#250;ng ta c&#243; th&#7875; n&#226;ng c&#7845;p d&#249;ng WebSocket &#273;&#7875; &#273;&#7849;y (push) tr&#7841;ng th&#225;i real-time.</p></li><li><p>M&#7897;t c&#7909;m <strong>Worker Service</strong> (c&#243; th&#7875; l&#224; c&#225;c container tr&#234;n ECS/Kubernetes) s&#7869; l&#7855;ng nghe t&#7915; Queue.</p></li><li><p>M&#7897;t worker nh&#7853;n &#273;&#432;&#7907;c <code>job_id</code>, n&#243; s&#7869; c&#7853;p nh&#7853;t status trong DB th&#224;nh <code>PROCESSING</code>.</p></li><li><p>Worker th&#7921;c hi&#7879;n vi&#7879;c n&#233;n, sau &#273;&#243; upload file zip k&#7871;t qu&#7843; l&#234;n S3.</p></li><li><p>Cu&#7889;i c&#249;ng, worker c&#7853;p nh&#7853;t status th&#224;nh <code>COMPLETED</code> v&#224; l&#432;u l&#7841;i URL c&#7911;a file zip.</p></li></ol><p><strong>(Anh Khoa):</strong> Ok, ki&#7871;n tr&#250;c t&#7893;ng quan n&#224;y kh&#225; chu&#7849;n. Gi&#7901; ch&#250;ng ta &#273;i s&#226;u v&#224;o &#273;i&#7875;m m&#7845;u ch&#7889;t. Con worker c&#7911;a b&#7841;n s&#7869; x&#7917; l&#253; 500GB d&#7919; li&#7879;u nh&#432; th&#7871; n&#224;o m&#224; kh&#244;ng b&#7883; Out Of Memory (OOM)?</p><p><strong>(duthaho):</strong> D&#7841;, &#273;&#226;y l&#224; &#273;i&#7875;m m&#7845;u ch&#7889;t c&#7911;a b&#224;i to&#225;n. Tuy&#7879;t &#273;&#7889;i kh&#244;ng &#273;&#432;&#7907;c t&#7843;i to&#224;n b&#7897; 500GB v&#7873; b&#7897; nh&#7899; c&#7911;a worker. Em s&#7869; s&#7917; d&#7909;ng k&#7929; thu&#7853;t <strong>Streaming</strong>.</p><p>Lu&#7891;ng x&#7917; l&#253; c&#7911;a worker s&#7869; l&#224;:</p><ol><li><p>M&#7903; m&#7897;t <strong>Write Stream</strong> (lu&#7891;ng ghi) &#273;&#7875; t&#7841;o file zip (c&#243; th&#7875; ghi t&#7841;m ra &#273;&#297;a c&#7911;a worker).</p></li><li><p>V&#7899;i m&#7895;i file trong danh s&#225;ch:</p><ul><li><p>M&#7903; m&#7897;t <strong>Read Stream</strong> (lu&#7891;ng &#273;&#7885;c) t&#7915; S3.</p></li><li><p>&#272;&#7885;c t&#7915;ng <strong>chunk</strong> nh&#7887; (v&#237; d&#7909; 10MB) t&#7915; stream S3.</p></li><li><p>N&#233;n (compress) chunk &#273;&#243;.</p></li><li><p>Ghi (pipe) chunk &#273;&#227; n&#233;n v&#224;o Write Stream c&#7911;a file zip.</p></li></ul></li><li><p>Sau khi l&#7863;p h&#7871;t c&#225;c file, worker s&#7869; &#273;&#243;ng stream zip l&#7841;i.</p></li><li><p>Upload file zip t&#7841;m &#273;&#243; l&#234;n S3.</p></li></ol><p>B&#7857;ng c&#225;ch n&#224;y, b&#7897; nh&#7899; s&#7917; d&#7909;ng t&#7841;i m&#7885;i th&#7901;i &#273;i&#7875;m ch&#7881; b&#7857;ng k&#237;ch th&#432;&#7899;c c&#7911;a chunk (10MB) ch&#7913; kh&#244;ng ph&#7843;i 500GB.</p><p><strong>(Anh Khoa):</strong> T&#7889;t. Gi&#7901; t&#244;i th&#234;m m&#7897;t y&#7871;u t&#7889; ph&#7913;c t&#7841;p. Gi&#7843; s&#7917; c&#225;c file ng&#432;&#7901;i d&#249;ng ch&#7885;n kh&#244;ng ch&#7881; n&#7857;m tr&#234;n S3 c&#7911;a ch&#250;ng ta, m&#224; c&#242;n c&#243; th&#7875; &#273;&#7871;n t&#7915; c&#225;c ngu&#7891;n b&#234;n ngo&#224;i, v&#237; d&#7909;: m&#7897;t link HTTP c&#244;ng khai, ho&#7863;c t&#7915; Google Drive c&#7911;a h&#7885;. H&#7879; th&#7889;ng c&#7911;a b&#7841;n s&#7869; thay &#273;&#7893;i th&#7871; n&#224;o?</p><p><strong>(duthaho):</strong> D&#7841;, khi l&#224;m vi&#7879;c v&#7899;i nhi&#7873;u ngu&#7891;n d&#7919; li&#7879;u kh&#225;c nhau, code c&#7911;a worker r&#7845;t d&#7877; b&#7883; r&#7889;i b&#7903;i c&#225;c c&#226;u <code>if-else</code>. &#272;&#7875; gi&#7843;i quy&#7871;t, em s&#7869; &#225;p d&#7909;ng <strong>Adapter Pattern</strong>.</p><ol><li><p>Em s&#7869; &#273;&#7883;nh ngh&#297;a m&#7897;t interface chung, v&#237; d&#7909; <code>IStreamReader</code>, v&#7899;i c&#225;c ph&#432;&#417;ng th&#7913;c chu&#7849;n nh&#432; <code>open()</code>, <code>read(chunk_size)</code>, <code>close()</code>.</p></li><li><p>Sau &#273;&#243;, em s&#7869; t&#7841;o c&#225;c class &#8220;adapter&#8221; c&#7909; th&#7875; cho t&#7915;ng ngu&#7891;n: <code>S3StreamReader</code>, <code>HttpStreamReader</code>, <code>GoogleDriveStreamReader</code>...</p></li><li><p>Khi worker nh&#7853;n job, m&#7897;t <strong>Factory</strong> s&#7869; xem <code>source_type</code> (l&#224; &#8216;s3&#8217;, &#8216;http&#8217;, hay &#8216;gdrive&#8217;) &#273;&#7875; kh&#7903;i t&#7841;o c&#225;c &#273;&#7889;i t&#432;&#7907;ng Adapter t&#432;&#417;ng &#7913;ng.</p></li><li><p>Logic n&#233;n file c&#7911;a worker s&#7869; kh&#244;ng thay &#273;&#7893;i. N&#243; ch&#7881; l&#224;m vi&#7879;c v&#7899;i interface <code>IStreamReader</code> chung m&#224; kh&#244;ng c&#7847;n quan t&#226;m chi ti&#7871;t file &#273;&#7871;n t&#7915; &#273;&#226;u.</p></li></ol><p><strong>(Anh Khoa):</strong> R&#7845;t hay. Nh&#432;ng &#273;i&#7873;u &#273;&#243; d&#7851;n &#273;&#7871;n m&#7897;t v&#7845;n &#273;&#7873; m&#7899;i. Gi&#7843; s&#7917; 99 file tr&#234;n S3 c&#7911;a ta r&#7845;t nhanh, nh&#432;ng c&#243; 1 file 50GB n&#7857;m tr&#234;n m&#7897;t server HTTP ch&#7853;m &#7903; ch&#226;u &#194;u. N&#243; s&#7869; tr&#7903; th&#224;nh <strong>bottleneck</strong> (n&#250;t th&#7855;t c&#7893; chai) v&#224; &#8220;ch&#7863;n&#8221; to&#224;n b&#7897; job trong nhi&#7873;u gi&#7901;. B&#7841;n x&#7917; l&#253; sao?</p><p><strong>(duthaho):</strong> &#272;&#226;y l&#224; m&#7897;t v&#7845;n&#234; &#273;&#7873; v&#7873; hi&#7879;u n&#259;ng. N&#7871;u server HTTP &#273;&#243; h&#7895; tr&#7907; <strong>HTTP Range Requests</strong> (cho ph&#233;p t&#7843;i t&#7915;ng ph&#7847;n), em s&#7869; &#225;p d&#7909;ng k&#7929; thu&#7853;t <strong>Parallel Chunking</strong> (chia nh&#7887; song song).</p><p>M&#7897;t <strong>Orchestrator</strong> (b&#7897; &#273;i&#7873;u ph&#7889;i) s&#7869; thay th&#7871; worker &#273;&#417;n gi&#7843;n.</p><ol><li><p>Orchestrator s&#7869; th&#7845;y file 50GB n&#224;y v&#224; chia n&#243; th&#224;nh 50 sub-task, m&#7895;i sub-task x&#7917; l&#253; 1GB.</p></li><li><p>N&#243; &#273;&#7849;y 50 sub-task n&#224;y v&#224;o Queue.</p></li><li><p>Nhi&#7873;u worker s&#7869; c&#249;ng l&#250;c l&#7845;y c&#225;c sub-task n&#224;y v&#7873;, download v&#224; n&#233;n song song c&#225;c ph&#7847;n.</p></li><li><p>K&#7871;t qu&#7843; s&#7869; l&#224; 50 file zip nh&#7887;.</p></li><li><p>Orchestrator s&#7869; theo d&#245;i khi n&#224;o c&#7843; 50 sub-task ho&#224;n th&#224;nh v&#224; cung c&#7845;p cho ng&#432;&#7901;i d&#249;ng m&#7897;t danh s&#225;ch 50 file zip &#273;&#243; (thay v&#236; c&#7889; g&#7855;ng merge ch&#250;ng l&#7841;i, v&#7889;n c&#361;ng r&#7845;t t&#7889;n k&#233;m).</p></li></ol><p><strong>(Anh Khoa):</strong> Ok, t&#244;i th&#237;ch &#253; t&#432;&#7903;ng &#273;&#243;. Gi&#7901; l&#224; c&#226;u h&#7887;i cu&#7889;i c&#249;ng v&#7873; &#273;&#7897; tin c&#7853;y. Gi&#7843; s&#7917; trong 50 sub-task &#273;&#243;, c&#243; m&#7897;t sub-task li&#234;n t&#7909;c b&#7883; l&#7895;i. Worker th&#7917; l&#7841;i (retry) nhi&#7873;u l&#7847;n v&#7851;n l&#7895;i. Ch&#250;ng ta kh&#244;ng th&#7875; &#273;&#7875; n&#243; retry m&#227;i, v&#7915;a t&#7889;n t&#224;i nguy&#234;n, v&#7915;a c&#243; th&#7875; l&#224;m s&#7853;p server c&#7911;a &#273;&#7889;i t&#225;c (n&#7871;u l&#224; l&#7895;i 429). B&#7841;n s&#7869; thi&#7871;t k&#7871; c&#417; ch&#7871; ch&#7883;u l&#7895;i (fault tolerance) nh&#432; th&#7871; n&#224;o?</p><p><strong>(duthaho):</strong> D&#7841;, &#273;&#7889;i v&#7899;i x&#7917; l&#253; l&#7895;i tinh vi, em s&#7869; k&#7871;t h&#7907;p 3 pattern:</p><ol><li><p><strong>Exponential Backoff:</strong> Khi c&#243; l&#7895;i, worker s&#7869; kh&#244;ng retry ngay. N&#243; s&#7869; &#273;&#7907;i 1s, r&#7891;i 2s, 4s, 8s... &#272;i&#7873;u n&#224;y gi&#250;p h&#7879; th&#7889;ng &#8220;th&#7903;&#8221; v&#224; tr&#225;nh t&#7841;o b&#227;o retry.</p></li><li><p><strong>Ph&#226;n t&#237;ch m&#227; l&#7895;i:</strong> Khi retry, worker s&#7869; ki&#7875;m tra m&#227; l&#7895;i. N&#7871;u l&#224; l&#7895;i 404 (Not Found) hay 403 (Forbidden), &#273;&#226;y l&#224; l&#7895;i v&#297;nh vi&#7877;n, vi&#7879;c retry l&#224; v&#244; &#237;ch -&gt; worker s&#7869; b&#225;o l&#7895;i ngay. N&#7871;u l&#224; 429 (Too Many Requests), logic backoff s&#7869; t&#7921; nhi&#234;n gi&#250;p gi&#7843;m t&#7843;i.</p></li><li><p><strong>Circuit Breaker Pattern:</strong> N&#7871;u m&#7897;t ngu&#7891;n (v&#237; d&#7909;: server HTTP &#7903; ch&#226;u &#194;u) li&#234;n t&#7909;c tr&#7843; v&#7873; l&#7895;i 500, Orchestrator s&#7869; k&#237;ch ho&#7841;t &#8220;ng&#7855;t m&#7841;ch&#8221;. T&#7913;c l&#224; n&#243; s&#7869; ch&#7911; &#273;&#7897;ng t&#7915; ch&#7889;i m&#7885;i sub-task m&#7899;i &#273;&#7871;n ngu&#7891;n &#273;&#243; trong 5 ph&#250;t. &#272;i&#7873;u n&#224;y b&#7843;o v&#7879; h&#7879; th&#7889;ng c&#7911;a ta kh&#7887;i l&#227;ng ph&#237; t&#224;i nguy&#234;n v&#224; b&#7843;o v&#7879; c&#7843; h&#7879; th&#7889;ng c&#7911;a &#273;&#7889;i t&#225;c.</p></li></ol><p>Sau khi Circuit Breaker &#273;&#432;&#7907;c k&#237;ch ho&#7841;t, Orchestrator s&#7869; &#273;&#225;nh d&#7845;u to&#224;n b&#7897; job l&#224; <code>FAILED</code> v&#224; th&#244;ng b&#225;o r&#245; cho ng&#432;&#7901;i d&#249;ng l&#224; &#8220;Kh&#244;ng th&#7875; x&#7917; l&#253; file X do ngu&#7891;n Y g&#7863;p s&#7921; c&#7889;&#8221;.</p><p><strong>(Anh Khoa):</strong> R&#7845;t t&#7889;t, duthaho. B&#7841;n &#273;&#227; ph&#226;n t&#237;ch v&#7845;n &#273;&#7873; r&#7845;t c&#243; c&#7845;u tr&#250;c, &#273;i t&#7915; y&#234;u c&#7847;u c&#417; b&#7843;n, x&#226;y d&#7921;ng ki&#7871;n tr&#250;c, sau &#273;&#243; &#273;&#224;o s&#226;u v&#224;o c&#225;c v&#7845;n &#273;&#7873; kh&#243; nh&#432; qu&#7843;n l&#253; b&#7897; nh&#7899;, m&#7903; r&#7897;ng h&#7879; th&#7889;ng v&#224; thi&#7871;t k&#7871; ch&#7883;u l&#7895;i. C&#226;u tr&#7843; l&#7901;i c&#7911;a b&#7841;n r&#7845;t r&#245; r&#224;ng v&#224; logic.</p><p>C&#7843;m &#417;n b&#7841;n. Bu&#7893;i ph&#7887;ng v&#7845;n c&#7911;a ch&#250;ng ta k&#7871;t th&#250;c &#7903; &#273;&#226;y. B&#7841;n c&#243; c&#226;u h&#7887;i n&#224;o cho t&#244;i kh&#244;ng?</p><p><strong>(duthaho):</strong> D&#7841;, em c&#7843;m &#417;n anh Khoa. Em mu&#7889;n h&#7887;i l&#224; trong c&#225;c d&#7921; &#225;n th&#7921;c t&#7871; t&#7841;i c&#244;ng ty, v&#259;n h&#243;a k&#7929; thu&#7853;t c&#7911;a m&#236;nh &#432;u ti&#234;n vi&#7879;c d&#249;ng c&#225;c managed service (nh&#432; SQS, DynamoDB) &#273;&#7875; ph&#225;t tri&#7875;n nhanh, hay &#432;u ti&#234;n vi&#7879;c t&#7921; v&#7853;n h&#224;nh (self-hosted) c&#225;c gi&#7843;i ph&#225;p open-source (nh&#432; RabbitMQ, PostgreSQL) &#273;&#7875; t&#7889;i &#432;u chi ph&#237; v&#7873; l&#226;u d&#224;i &#7841;?</p><p><strong>(Anh Khoa):</strong> ...</p><div><hr></div><h3>Project th&#7921;c t&#7871; c&#7911;a m&#7885;i ng&#432;&#7901;i nh&#432; th&#7871; n&#224;o, h&#227;y comment tr&#7843; l&#7901;i gi&#250;p anh Khoa nh&#233; :D</h3><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Tôi đi phỏng vấn: bài toán Unique ID]]></title><description><![CDATA[&#272;&#432;&#7907;c r&#7891;i, b&#224;i to&#225;n c&#7911;a ch&#250;ng ta nh&#432; sau: "H&#227;y thi&#7871;t k&#7871; m&#7897;t d&#7883;ch v&#7909; c&#243; kh&#7843; n&#259;ng t&#7841;o ra c&#225;c ID duy nh&#7845;t (unique ID) cho m&#7897;t h&#7879; th&#7889;ng quy m&#244; l&#7899;n, v&#237; d&#7909; nh&#432; YouTube ho&#7863;c m&#7897;t s&#224;n th&#432;&#417;ng m&#7841;i &#273;i&#7879;n t&#7917;. Y&#234;u c&#7847;u l&#224; ID ph&#7843;i ng&#7855;n g&#7885;n v&#224; h&#7879; th&#7889;ng ph&#7843;i &#273;&#225;p &#7913;ng &#273;&#432;&#7907;c l&#432;u l&#432;&#7907;ng truy c&#7853;p r&#7845;t cao."Em &#273;&#227; hi&#7875;u r&#245; &#273;&#7873; b&#224;i ch&#432;a?]]></description><link>https://duthaho.substack.com/p/toi-i-phong-van-bai-toan-unique-id</link><guid isPermaLink="false">https://duthaho.substack.com/p/toi-i-phong-van-bai-toan-unique-id</guid><dc:creator><![CDATA[duthaho]]></dc:creator><pubDate>Fri, 05 Sep 2025 09:01:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!03qT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fccf2afa9-98da-4871-91ea-d3ba4203ab1c_64x64.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!3slY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!3slY!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png 424w, /__u/substackcdn.com/image/fetch/$s_!3slY!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png 848w, /__u/substackcdn.com/image/fetch/$s_!3slY!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png 1272w, /__u/substackcdn.com/image/fetch/$s_!3slY!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_webp, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!3slY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png" width="640" height="184" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:184,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Snowflake ID - Wikiwand&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Snowflake ID - Wikiwand" title="Snowflake ID - Wikiwand" srcset="/__u/substackcdn.com/image/fetch/$s_!3slY!, /__u/duthaho.substack.com/w_424, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png 424w, /__u/substackcdn.com/image/fetch/$s_!3slY!, /__u/duthaho.substack.com/w_848, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png 848w, /__u/substackcdn.com/image/fetch/$s_!3slY!, /__u/duthaho.substack.com/w_1272, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png 1272w, /__u/substackcdn.com/image/fetch/$s_!3slY!, /__u/duthaho.substack.com/w_1456, /__u/duthaho.substack.com/c_limit, /__u/duthaho.substack.com/f_auto, /__u/duthaho.substack.com/q_auto:good, /__u/duthaho.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ea6a3cf-4605-4ad6-b3ab-b115050c8da7_640x184.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><h3>B&#7855;t &#273;&#7847;u bu&#7893;i ph&#7887;ng v&#7845;n</h3><p><strong>(Anh Minh b&#7855;t &#273;&#7847;u cu&#7897;c g&#7885;i, m&#7881;m c&#432;&#7901;i th&#226;n thi&#7879;n)</strong></p><p><strong>Anh Minh:</strong> Ch&#224;o duthaho, anh l&#224; Minh. C&#7843;m &#417;n em &#273;&#227; d&#224;nh th&#7901;i gian tham gia bu&#7893;i ph&#7887;ng v&#7845;n h&#244;m nay. Em nghe r&#245; anh n&#243;i ch&#7913;?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><strong>duthaho:</strong> D&#7841; em ch&#224;o anh Minh, em nghe r&#7845;t r&#245; &#7841;. Em c&#361;ng c&#7843;m &#417;n anh &#273;&#227; t&#7841;o &#273;i&#7873;u ki&#7879;n cho em c&#243; bu&#7893;i trao &#273;&#7893;i n&#224;y.</p><p><strong>Anh Minh:</strong> Ok t&#7889;t r&#7891;i. H&#244;m nay ch&#250;ng ta s&#7869; c&#243; m&#7897;t b&#224;i to&#225;n v&#7873; thi&#7871;t k&#7871; h&#7879; th&#7889;ng. Anh mu&#7889;n t&#236;m hi&#7875;u v&#7873; c&#225;ch em t&#432; duy, ph&#226;n t&#237;ch v&#7845;n &#273;&#7873; v&#224; &#273;&#432;a ra c&#225;c l&#7921;a ch&#7885;n ki&#7871;n tr&#250;c. Em c&#7913; tho&#7843;i m&#225;i trao &#273;&#7893;i, v&#7869; v&#7901;i tr&#234;n whiteboard nh&#233;. &#272;&#226;y l&#224; cu&#7897;c th&#7843;o lu&#7853;n hai chi&#7873;u th&#244;i.</p><p><strong>duthaho:</strong> D&#7841; v&#226;ng &#7841;.</p><p><strong>Anh Minh:</strong> &#272;&#432;&#7907;c r&#7891;i, b&#224;i to&#225;n c&#7911;a ch&#250;ng ta nh&#432; sau: "H&#227;y thi&#7871;t k&#7871; m&#7897;t d&#7883;ch v&#7909; c&#243; kh&#7843; n&#259;ng t&#7841;o ra c&#225;c ID duy nh&#7845;t (unique ID) cho m&#7897;t h&#7879; th&#7889;ng quy m&#244; l&#7899;n, v&#237; d&#7909; nh&#432; YouTube ho&#7863;c m&#7897;t s&#224;n th&#432;&#417;ng m&#7841;i &#273;i&#7879;n t&#7917;. Y&#234;u c&#7847;u l&#224; ID ph&#7843;i ng&#7855;n g&#7885;n v&#224; h&#7879; th&#7889;ng ph&#7843;i &#273;&#225;p &#7913;ng &#273;&#432;&#7907;c l&#432;u l&#432;&#7907;ng truy c&#7853;p r&#7845;t cao."</p><p>Em &#273;&#227; hi&#7875;u r&#245; &#273;&#7873; b&#224;i ch&#432;a?</p><h3>Giai &#273;o&#7841;n 1: L&#224;m r&#245; y&#234;u c&#7847;u</h3><p><strong>duthaho:</strong> D&#7841; em &#273;&#227; hi&#7875;u &#273;&#7873; b&#224;i. Tr&#432;&#7899;c khi &#273;i v&#224;o gi&#7843;i ph&#225;p, em xin ph&#233;p h&#7887;i m&#7897;t v&#224;i c&#226;u &#273;&#7875; l&#224;m r&#245; h&#417;n c&#225;c y&#234;u c&#7847;u c&#7911;a h&#7879; th&#7889;ng &#7841;.</p><p><strong>Anh Minh:</strong> R&#7845;t t&#7889;t, em h&#7887;i &#273;i.</p><p><strong>duthaho:</strong></p><ol><li><p><strong>V&#7873; ch&#7913;c n&#259;ng:</strong> ID n&#224;y s&#7869; &#273;&#432;&#7907;c d&#249;ng &#7903; &#273;&#226;u &#7841;? V&#237; d&#7909; nh&#432; trong URL hay ch&#7881; trong n&#7897;i b&#7897; h&#7879; th&#7889;ng? &#272;i&#7873;u n&#224;y s&#7869; &#7843;nh h&#432;&#7903;ng &#273;&#7871;n &#273;&#7883;nh d&#7841;ng c&#7911;a ID, v&#237; d&#7909; n&#243; c&#243; c&#7847;n ph&#7843;i "URL-safe" kh&#244;ng?</p></li><li><p><strong>V&#7873; phi ch&#7913;c n&#259;ng:</strong></p><ul><li><p><strong>Th&#244;ng l&#432;&#7907;ng (Throughput):</strong> Khi anh n&#243;i "l&#432;u l&#432;&#7907;ng r&#7845;t cao", con s&#7889; c&#7909; th&#7875; ch&#250;ng ta h&#432;&#7899;ng &#273;&#7871;n l&#224; kho&#7843;ng bao nhi&#234;u ID m&#7895;i gi&#226;y &#7841;? V&#237; d&#7909; 10,000 hay 100,000 ID/gi&#226;y?</p></li><li><p><strong>&#272;&#7897; tr&#7877; (Latency):</strong> Th&#7901;i gian ph&#7843;n h&#7891;i &#273;&#7875; t&#7841;o m&#7897;t ID c&#243; y&#234;u c&#7847;u kh&#7855;t khe kh&#244;ng &#7841;? V&#237; d&#7909; d&#432;&#7899;i 10ms hay 50ms?</p></li><li><p><strong>T&#237;nh duy nh&#7845;t:</strong> Y&#234;u c&#7847;u l&#224; duy nh&#7845;t 100% hay ch&#7881; c&#7847;n x&#225;c su&#7845;t tr&#249;ng l&#7863;p c&#7921;c k&#7923; th&#7845;p l&#224; &#273;&#432;&#7907;c &#7841;?</p></li><li><p><strong>Th&#7913; t&#7921;:</strong> ID &#273;&#432;&#7907;c t&#7841;o ra c&#243; c&#7847;n ph&#7843;i s&#7855;p x&#7871;p &#273;&#432;&#7907;c theo th&#7901;i gian kh&#244;ng &#7841;?</p></li></ul></li></ol><p><strong>Anh Minh:</strong> (G&#7853;t &#273;&#7847;u, t&#7887; v&#7867; h&#224;i l&#242;ng) Nh&#7919;ng c&#226;u h&#7887;i r&#7845;t x&#225;c &#273;&#225;ng. Gi&#7843; s&#7917; c&#225;c y&#234;u c&#7847;u c&#7909; th&#7875; nh&#432; sau nh&#233;:</p><ul><li><p>ID c&#7847;n ph&#7843;i ng&#7855;n v&#224; an to&#224;n cho URL (URL-safe).</p></li><li><p>Th&#244;ng l&#432;&#7907;ng m&#7909;c ti&#234;u l&#224; 50,000 ID/gi&#226;y &#7903; m&#7913;c &#273;&#7881;nh &#273;i&#7875;m.</p></li><li><p>&#272;&#7897; tr&#7877; ph&#7843;i d&#432;&#7899;i 10ms.</p></li><li><p>Y&#234;u c&#7847;u duy nh&#7845;t 100%.</p></li><li><p>Vi&#7879;c s&#7855;p x&#7871;p &#273;&#432;&#7907;c theo th&#7901;i gian l&#224; m&#7897;t l&#7907;i th&#7871; l&#7899;n, nh&#432;ng kh&#244;ng b&#7855;t bu&#7897;c.</p></li></ul><h3>Giai &#273;o&#7841;n 2: Th&#7843;o lu&#7853;n c&#225;c h&#432;&#7899;ng ti&#7871;p c&#7853;n</h3><p><strong>duthaho:</strong> D&#7841; em c&#7843;m &#417;n anh. V&#7899;i c&#225;c y&#234;u c&#7847;u n&#224;y, em ngh&#297; &#273;&#7871;n m&#7897;t v&#224;i h&#432;&#7899;ng ti&#7871;p c&#7853;n v&#7899;i c&#225;c s&#7921; &#273;&#225;nh &#273;&#7893;i kh&#225;c nhau.</p><p>&#272;&#7847;u ti&#234;n l&#224; ph&#432;&#417;ng &#225;n &#273;&#417;n gi&#7843;n nh&#7845;t: <strong>d&#249;ng Auto-Increment c&#7911;a Database</strong>.</p><ul><li><p><strong>&#431;u &#273;i&#7875;m:</strong> R&#7845;t d&#7877; tri&#7875;n khai, &#273;&#7843;m b&#7843;o duy nh&#7845;t v&#224; tu&#7847;n t&#7921;.</p></li><li><p><strong>Nh&#432;&#7907;c &#273;i&#7875;m:</strong> &#272;&#226;y s&#7869; l&#224; m&#7897;t n&#250;t c&#7893; chai (bottleneck) c&#7921;c l&#7899;n v&#224; l&#224; &#273;i&#7875;m l&#7895;i duy nh&#7845;t (Single Point of Failure). N&#243; kh&#244;ng th&#7875; &#273;&#225;p &#7913;ng &#273;&#432;&#7907;c 50,000 request/gi&#226;y. Do &#273;&#243;, em xin ph&#233;p lo&#7841;i ph&#432;&#417;ng &#225;n n&#224;y.</p></li></ul><p><strong>Anh Minh:</strong> Anh &#273;&#7891;ng &#253;. V&#7853;y c&#242;n ph&#432;&#417;ng &#225;n n&#224;o kh&#225;c kh&#244;ng?</p><p><strong>duthaho:</strong> D&#7841; c&#243; &#7841;. M&#7897;t ph&#432;&#417;ng &#225;n ph&#226;n t&#225;n ho&#224;n to&#224;n l&#224; d&#249;ng <strong>UUID</strong>.</p><ul><li><p><strong>&#431;u &#273;i&#7875;m:</strong> Kh&#244;ng c&#7847;n m&#7897;t d&#7883;ch v&#7909; t&#7853;p trung, c&#225;c m&#225;y ch&#7911; t&#7921; sinh ID n&#234;n kh&#7843; n&#259;ng m&#7903; r&#7897;ng l&#224; v&#244; h&#7841;n, kh&#244;ng c&#243; &#273;i&#7875;m l&#7895;i duy nh&#7845;t.</p></li><li><p><strong>Nh&#432;&#7907;c &#273;i&#7875;m:</strong> UUID qu&#225; d&#224;i (36 k&#253; t&#7921;), kh&#244;ng &#273;&#225;p &#7913;ng y&#234;u c&#7847;u "ID ng&#7855;n g&#7885;n". H&#417;n n&#7919;a, UUID phi&#234;n b&#7843;n 4 ph&#7893; bi&#7871;n th&#236; ho&#224;n to&#224;n ng&#7851;u nhi&#234;n, kh&#244;ng s&#7855;p x&#7871;p &#273;&#432;&#7907;c theo th&#7901;i gian, g&#226;y &#7843;nh h&#432;&#7903;ng x&#7845;u &#273;&#7871;n hi&#7879;u n&#259;ng index c&#7911;a database.</p></li></ul><p><strong>Anh Minh:</strong> Ok. V&#7853;y c&#243; v&#7867; UUID c&#361;ng ch&#432;a ph&#249; h&#7907;p.</p><p><strong>duthaho:</strong> D&#7841; &#273;&#250;ng &#7841;. &#272;&#7875; c&#226;n b&#7857;ng c&#225;c y&#7871;u t&#7889;, em &#273;&#7873; xu&#7845;t x&#226;y d&#7921;ng m&#7897;t <strong>d&#7883;ch v&#7909; t&#7841;o ID t&#7853;p trung (Centralized Service)</strong>. C&#243; hai h&#432;&#7899;ng tri&#7875;n khai ph&#7893; bi&#7871;n cho d&#7883;ch v&#7909; n&#224;y:</p><ol><li><p><strong>D&#249;ng Redis INCR:</strong> Ta c&#243; th&#7875; d&#249;ng l&#7879;nh INCR nguy&#234;n t&#7917; c&#7911;a Redis &#273;&#7875; t&#7841;o ra m&#7897;t chu&#7895;i s&#7889; tu&#7847;n t&#7921;. Sau &#273;&#243; m&#227; h&#243;a s&#7889; n&#224;y sang Base62 &#273;&#7875; c&#243; ID ng&#7855;n v&#224; URL-safe.</p></li><li><p><strong>D&#249;ng thu&#7853;t to&#225;n ki&#7875;u Twitter Snowflake:</strong> X&#226;y d&#7921;ng m&#7897;t d&#7883;ch v&#7909; sinh ra s&#7889; 64-bit duy nh&#7845;t, sau &#273;&#243; c&#361;ng m&#227; h&#243;a sang Base62.</p></li></ol><p><strong>Anh Minh:</strong> R&#7845;t hay. Em c&#243; th&#7875; ph&#226;n t&#237;ch s&#226;u h&#417;n v&#7873; &#432;u v&#224; nh&#432;&#7907;c &#273;i&#7875;m c&#7911;a vi&#7879;c d&#249;ng Redis v&#224; Snowflake kh&#244;ng?</p><p><strong>duthaho:</strong> D&#7841; &#273;&#432;&#7907;c &#7841;.</p><ul><li><p><strong>V&#7899;i Redis INCR:</strong></p><ul><li><p><strong>&#431;u &#273;i&#7875;m:</strong> R&#7845;t nhanh (in-memory), th&#244;ng l&#432;&#7907;ng cao tr&#234;n m&#7897;t node, tri&#7875;n khai &#273;&#417;n gi&#7843;n h&#417;n Snowflake.</p></li><li><p><strong>Nh&#432;&#7907;c &#273;i&#7875;m:</strong> V&#7851;n c&#243; nguy c&#417; l&#224; &#273;i&#7875;m l&#7895;i duy nh&#7845;t. M&#7863;c d&#249; c&#243; th&#7875; d&#249;ng Redis Sentinel/Cluster &#273;&#7875; t&#259;ng t&#237;nh s&#7861;n s&#224;ng, nh&#432;ng v&#7851;n c&#243; &#273;&#7897; tr&#7877; khi failover. Quan tr&#7885;ng nh&#7845;t, kh&#7843; n&#259;ng m&#7903; r&#7897;ng b&#7883; gi&#7899;i h&#7841;n b&#7903;i n&#259;ng l&#7921;c c&#7911;a m&#7897;t node Redis duy nh&#7845;t, v&#236; l&#7879;nh INCR tr&#234;n m&#7897;t key kh&#244;ng th&#7875; ph&#226;n t&#225;n ra nhi&#7873;u node.</p></li></ul></li><li><p><strong>V&#7899;i Snowflake:</strong></p><ul><li><p><strong>&#431;u &#273;i&#7875;m:</strong> &#272;&#432;&#7907;c thi&#7871;t k&#7871; &#273;&#7875; m&#7903; r&#7897;ng theo chi&#7873;u ngang v&#224; c&#243; t&#237;nh s&#7861;n s&#224;ng cao ngay t&#7915; &#273;&#7847;u. ID sinh ra c&#243; th&#7875; s&#7855;p x&#7871;p theo th&#7901;i gian, &#273;&#226;y l&#224; l&#7907;i th&#7871; r&#7845;t l&#7899;n cho vi&#7879;c truy v&#7845;n v&#224; ph&#226;n t&#237;ch d&#7919; li&#7879;u. T&#7893;ng th&#244;ng l&#432;&#7907;ng c&#7911;a h&#7879; th&#7889;ng r&#7845;t l&#7899;n.</p></li><li><p><strong>Nh&#432;&#7907;c &#273;i&#7875;m:</strong> Ph&#7913;c t&#7841;p h&#417;n trong vi&#7879;c tri&#7875;n khai v&#224; v&#7853;n h&#224;nh, &#273;&#242;i h&#7887;i c&#225;c th&#224;nh ph&#7847;n ph&#7909; tr&#7907; nh&#432; Zookeeper &#273;&#7875; qu&#7843;n l&#253; machine ID v&#224; NTP &#273;&#7875; &#273;&#7891;ng b&#7897; th&#7901;i gian.</p></li></ul></li></ul><p>V&#7899;i y&#234;u c&#7847;u v&#7873; th&#244;ng l&#432;&#7907;ng l&#7899;n v&#224; t&#237;nh s&#7861;n s&#224;ng cao, em ngh&#297; <strong>ph&#432;&#417;ng &#225;n Snowflake l&#224; ph&#249; h&#7907;p nh&#7845;t</strong> cho b&#224;i to&#225;n n&#224;y trong d&#224;i h&#7841;n.</p><h3>Giai &#273;o&#7841;n 3: &#272;i s&#226;u v&#224;o gi&#7843;i ph&#225;p</h3><p><strong>Anh Minh:</strong> Anh &#273;&#7891;ng &#253; v&#7899;i l&#7921;a ch&#7885;n c&#7911;a em. Em c&#243; th&#7875; m&#244; t&#7843; c&#7845;u tr&#250;c c&#7911;a m&#7897;t ID theo ki&#7875;u Snowflake v&#224; ki&#7871;n tr&#250;c t&#7893;ng quan c&#7911;a d&#7883;ch v&#7909; n&#224;y kh&#244;ng?</p><p><strong>duthaho:</strong> D&#7841;. ID 64-bit c&#7911;a Snowflake &#273;&#432;&#7907;c c&#7845;u th&#224;nh t&#7915; 3 ph&#7847;n ch&#237;nh &#7841;:</p><ol><li><p><strong>Timestamp (41 bits):</strong> S&#7889; mili gi&#226;y t&#237;nh t&#7915; m&#7897;t m&#7889;c th&#7901;i gian (epoch), cho ph&#233;p h&#7879; th&#7889;ng ho&#7841;t &#273;&#7897;ng trong ~69 n&#259;m.</p></li><li><p><strong>Machine ID (10 bits):</strong> M&#227; &#273;&#7883;nh danh cho m&#225;y ch&#7911; t&#7841;o ID, cho ph&#233;p c&#243; t&#7889;i &#273;a 1024 m&#225;y ch&#7911; trong c&#7909;m.</p></li><li><p><strong>Sequence Number (12 bits):</strong> S&#7889; th&#7913; t&#7921; cho c&#225;c ID &#273;&#432;&#7907;c t&#7841;o ra trong c&#249;ng 1 mili gi&#226;y tr&#234;n c&#249;ng 1 m&#225;y ch&#7911;, cho ph&#233;p t&#7841;o t&#7889;i &#273;a 4096 ID/ms/m&#225;y.</p></li></ol><p>V&#7873; ki&#7871;n tr&#250;c t&#7893;ng quan, h&#7879; th&#7889;ng s&#7869; bao g&#7891;m:</p><ul><li><p><strong>M&#7897;t c&#7909;m c&#225;c ID Generator Server:</strong> Ch&#7841;y logic t&#7841;o ID Snowflake.</p></li><li><p><strong>M&#7897;t Load Balancer:</strong> &#272;&#7913;ng tr&#432;&#7899;c &#273;&#7875; ph&#226;n ph&#7889;i request.</p></li><li><p><strong>M&#7897;t d&#7883;ch v&#7909; Coordination (nh&#432; Zookeeper/Etcd):</strong> &#272;&#7875; &#273;&#7843;m b&#7843;o m&#7895;i Generator Server khi kh&#7903;i &#273;&#7897;ng s&#7869; &#273;&#432;&#7907;c c&#7845;p m&#7897;t Machine ID duy nh&#7845;t.</p></li></ul><p><strong>Anh Minh:</strong> Em &#273;&#227; &#273;&#7873; c&#7853;p &#273;&#7871;n 2 v&#7845;n &#273;&#7873; v&#7853;n h&#224;nh kh&#225; kh&#243; c&#7911;a Snowflake. M&#7897;t l&#224; qu&#7843;n l&#253; Machine ID, hai l&#224; &#273;&#7891;ng b&#7897; th&#7901;i gian. Em s&#7869; gi&#7843;i quy&#7871;t ch&#250;ng nh&#432; th&#7871; n&#224;o?</p><p><strong>duthaho:</strong> D&#7841;, v&#7899;i <strong>Machine ID</strong>, em s&#7869; d&#249;ng Zookeeper. M&#7895;i server khi kh&#7903;i &#273;&#7897;ng s&#7869; k&#7871;t n&#7889;i t&#7899;i Zookeeper v&#224; &#273;&#259;ng k&#253; m&#7897;t "ephemeral node" &#273;&#7875; nh&#7853;n m&#7897;t Machine ID duy nh&#7845;t t&#7915; m&#7897;t pool &#273;&#227; &#273;&#7883;nh s&#7861;n. V&#236; l&#224; ephemeral node, n&#7871;u server b&#7883; m&#7845;t k&#7871;t n&#7889;i, Zookeeper s&#7869; t&#7921; &#273;&#7897;ng thu h&#7891;i Machine ID &#273;&#243; &#273;&#7875; c&#7845;p ph&#225;t l&#7841;i.</p><p>C&#242;n v&#7899;i <strong>&#273;&#7891;ng b&#7897; th&#7901;i gian</strong>, t&#7845;t c&#7843; c&#225;c server trong h&#7879; th&#7889;ng b&#7855;t bu&#7897;c ph&#7843;i &#273;&#432;&#7907;c c&#224;i &#273;&#7863;t v&#224; c&#7845;u h&#236;nh d&#7883;ch v&#7909; NTP (Network Time Protocol) &#273;&#7875; &#273;&#7843;m b&#7843;o &#273;&#7891;ng h&#7891; h&#7879; th&#7889;ng lu&#244;n &#273;&#432;&#7907;c &#273;&#7891;ng b&#7897; ch&#237;nh x&#225;c.</p><p><strong>Anh Minh:</strong> R&#7845;t r&#245; r&#224;ng. Gi&#7901; l&#224; m&#7897;t c&#226;u h&#7887;i m&#7903; r&#7897;ng. Gi&#7843; s&#7917; h&#7879; th&#7889;ng ph&#225;t tri&#7875;n, v&#224; ch&#250;ng ta c&#7847;n nhi&#7873;u h&#417;n 1024 m&#225;y ch&#7911; t&#7841;o ID. Em s&#7869; l&#224;m g&#236;?</p><p><strong>duthaho:</strong> D&#7841; &#273;&#226;y l&#224; m&#7897;t b&#224;i to&#225;n &#273;&#225;nh &#273;&#7893;i kinh &#273;i&#7875;n. V&#236; kh&#244;ng gian 64-bit l&#224; c&#7889; &#273;&#7883;nh, &#273;&#7875; t&#259;ng s&#7889; bits cho Machine ID (v&#237; d&#7909; l&#234;n 11 bits &#273;&#7875; c&#243; 2048 m&#225;y), em bu&#7897;c ph&#7843;i l&#7845;y 1 bit t&#7915; n&#417;i kh&#225;c.</p><ul><li><p><strong>L&#7921;a ch&#7885;n 1: L&#7845;y 1 bit t&#7915; Sequence Number.</strong> Machine ID s&#7869; l&#224; 11 bits, Sequence c&#242;n 11 bits.</p><ul><li><p><strong>&#272;&#225;nh &#273;&#7893;i:</strong> Th&#244;ng l&#432;&#7907;ng tr&#234;n m&#7895;i m&#225;y gi&#7843;m c&#242;n 2048 ID/ms. Tuy nhi&#234;n, t&#7893;ng th&#244;ng l&#432;&#7907;ng h&#7879; th&#7889;ng v&#7851;n t&#259;ng. L&#7921;a ch&#7885;n n&#224;y th&#432;&#7901;ng an to&#224;n h&#417;n.</p></li></ul></li><li><p><strong>L&#7921;a ch&#7885;n 2: L&#7845;y 1 bit t&#7915; Timestamp.</strong> Machine ID l&#224; 11 bits, Timestamp c&#242;n 40 bits.</p><ul><li><p><strong>&#272;&#225;nh &#273;&#7893;i:</strong> Tu&#7893;i th&#7885; c&#7911;a h&#7879; th&#7889;ng gi&#7843;m m&#7897;t n&#7917;a, c&#242;n kho&#7843;ng 34.5 n&#259;m. &#272;&#226;y l&#224; r&#7911;i ro l&#7899;n trong d&#224;i h&#7841;n.</p></li></ul></li></ul><p>V&#236; v&#7853;y, em s&#7869; ch&#7885;n ph&#432;&#417;ng &#225;n 1, hy sinh m&#7897;t ch&#250;t th&#244;ng l&#432;&#7907;ng tr&#234;n t&#7915;ng node &#273;&#7875; &#273;&#7893;i l&#7845;y kh&#7843; n&#259;ng m&#7903; r&#7897;ng h&#7879; th&#7889;ng v&#224; gi&#7919; &#273;&#432;&#7907;c tu&#7893;i th&#7885; l&#226;u d&#224;i.</p><h3>Giai &#273;o&#7841;n 4: K&#7871;t th&#250;c</h3><p><strong>Anh Minh:</strong> C&#7843;m &#417;n duthaho. Ph&#7847;n tr&#236;nh b&#224;y c&#7911;a em r&#7845;t c&#243; c&#7845;u tr&#250;c, m&#7841;ch l&#7841;c v&#224; cho th&#7845;y s&#7921; hi&#7875;u bi&#7871;t s&#226;u v&#7873; c&#225;c trade-off trong thi&#7871;t k&#7871; h&#7879; th&#7889;ng. Anh kh&#244;ng c&#242;n c&#226;u h&#7887;i n&#224;o th&#234;m. Em c&#243; c&#226;u h&#7887;i n&#224;o cho anh kh&#244;ng?</p><p><strong>duthaho:</strong> D&#7841; hi&#7879;n t&#7841;i th&#236; em ch&#432;a c&#243; c&#226;u h&#7887;i n&#224;o &#7841;. Em c&#7843;m &#417;n anh Minh &#273;&#227; d&#224;nh th&#7901;i gian &#7841;.</p><p><strong>Anh Minh:</strong> Ok, c&#7843;m &#417;n em. B&#7897; ph&#7853;n tuy&#7875;n d&#7909;ng s&#7869; li&#234;n h&#7879; v&#7899;i em v&#7873; k&#7871;t qu&#7843; trong v&#224;i ng&#224;y t&#7899;i nh&#233;. Ch&#224;o em.</p><p><strong>duthaho:</strong> D&#7841;, em ch&#224;o anh.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://duthaho.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>