<?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"><channel><title><![CDATA[Blog của Jasper]]></title><description><![CDATA[Hi, các bạn có thể gọi tôi là Jasper. Blog này sẽ là nơi tôi take note và chia sẻ về các kiến thức và trải nghiệm thú vị của tôi về đời sống cũng như là công ng]]></description><link>https://blog.jasperisme.io.vn</link><generator>RSS for Node</generator><lastBuildDate>Tue, 12 May 2026 10:20:03 GMT</lastBuildDate><atom:link href="https://blog.jasperisme.io.vn/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Thông báo]]></title><description><![CDATA[Như các bạn đã biết đây là nền tảng Blog của HashNode, tuy nhiên thời gian vừa rồi HashNode có cập nhật dẫn đến lag và trải nghiệm không mượt mà, nên mình quyết định chuyển qua viết Blog chính thức tr]]></description><link>https://blog.jasperisme.io.vn/th-ng-b-o</link><guid isPermaLink="true">https://blog.jasperisme.io.vn/th-ng-b-o</guid><dc:creator><![CDATA[Jasper]]></dc:creator><pubDate>Sat, 04 Apr 2026 04:08:50 GMT</pubDate><content:encoded><![CDATA[<p>Như các bạn đã biết đây là nền tảng Blog của HashNode, tuy nhiên thời gian vừa rồi HashNode có cập nhật dẫn đến lag và trải nghiệm không mượt mà, nên mình quyết định chuyển qua viết Blog chính thức trên Sub stack. Rất mong các bạn có thể follow mình và ủng hộ trên sub stack nha!!!</p>
<p>Link <a href="https://substack.com/@jasperisme">sub stack</a>!! Thank mọi người</p>
]]></content:encoded></item><item><title><![CDATA[Consistent Hashing: người lính thầm lặng của Distributed system]]></title><description><![CDATA[Mình đang muốn làm một series: Cái gì không biết thì cùng bóc tách và tự implement nó(Build something from scratch) → Mục đích để hiểu nó mà mang đi chém gió thôi :))
Series này sẽ bắt đầu từ Consistent Hashing nhé. Nhưng trước hết cần phải biết Cons...]]></description><link>https://blog.jasperisme.io.vn/consistent-hashing-nguoi-linh-tham-lang-cua-distributed-system</link><guid isPermaLink="true">https://blog.jasperisme.io.vn/consistent-hashing-nguoi-linh-tham-lang-cua-distributed-system</guid><category><![CDATA[algorithms]]></category><category><![CDATA[distributed system]]></category><dc:creator><![CDATA[Jasper]]></dc:creator><pubDate>Mon, 24 Nov 2025 11:51:27 GMT</pubDate><content:encoded><![CDATA[<p>Mình đang muốn làm một series: <strong><em>Cái gì không biết thì cùng bóc tách và tự implement nó</em></strong>(<em>Build something from scratch</em>) → Mục đích để hiểu nó mà mang đi chém gió thôi :))</p>
<p>Series này sẽ bắt đầu từ Consistent Hashing nhé. Nhưng trước hết cần phải biết Consistent Hashing là gì đã :v.</p>
<h2 id="heading-consistent-hashing-la-gi">Consistent Hashing là gì?</h2>
<p>Trong thế giới phân tán - <strong>Distributed system</strong>, mọi người chắc hẳn đã từng làm việc với vô vàn các service kiểu như Load Balancer (Nginx), hay Distributed Caching (Redis cluster), hay sharding database, K8s …. Những service này có một đặc điểm chung là: chúng quản lý các node khác nhau kiểu gì? Hay cụ thể hơn:</p>
<ul>
<li><p>Làm sao Load balancer lại có thể phân phối traffic đến các Node nhanh mà không bị loạn?</p>
</li>
<li><p>Làm sao Redis cluster có thể phân phối key vào cách hash slot?</p>
</li>
<li><p>Làm sao Database sharding nhưng vẫn query rất nhanh?</p>
</li>
<li><p>Làm sao cụm k8s có thể scale mà session vẫn sticky?</p>
</li>
</ul>
<p>Ẩn dưới những thứ hào nhoáng mà chúng ta đang sử dụng, đó là một thuật toán: <strong>Consistent Hashing</strong>. Nó là một thứ thầm lặng đứng đằng sau tất cả dịch vụ trong thế giới phân tán này.</p>
<h3 id="heading-tac-dung-cua-no-la-gi">Tác dụng của nó là gì?</h3>
<p>Để cho bớt dài dòng, mình sẽ nói nhanh qua về lí do ra đời của Consistent Hashing nhé.</p>
<p>Trong hệ thống phân tán (<strong>Distributed system</strong>), cứ mỗi khi 1 server không thể phục vụ nhu cầu của user nữa, nó cần phải được scale, có 2 cách scale:</p>
<ul>
<li><p><strong><em>Vertical scaling</em></strong>: Nôm nay là bạn đi update Disk, Ram, CPU cho nó xịn hơn, khoẻ hơn</p>
</li>
<li><p><strong><em>Horizontal scaling</em></strong>: Thay vì một server phục vụ user, hãy tuyển thêm nhiều server chạy song song - đây là điểm quan trọng của Distributed System, giúp đỡ tốn kém hơn, phục vụ được hàng triệu người dùng</p>
</li>
</ul>
<p>Vậy tức là đối với Horizontal scaling, cần phải có một cơ chế để giúp cho các node có thể hoạt động được với nhau một cách mượt mà đúng không? Đây là bài toán không phải làm với một, mà làm với nhiều node cùng một lúc.</p>
<p>Oke, để mình nói dễ hiểu hơn nhé:</p>
<p>Ví dụ về redis cluster, nó sẽ là 1 cluster gồm nhiều redis kết hợp lại với nhau, mục đích để làm gì? Đúng vậy, nó giúp <strong><em>partitioning</em></strong> cho database của các bạn.</p>
<p>(<strong><em>Note</em></strong>: Thực tế ra Redis cluster dùng cơ chế Hash slot, nó hơi khác một chút so với Consistent Hashing, nhưng mình mượn nó để lấy ví dụ trong bài này nhé ^^)</p>
<p>Nếu như bạn chỉ có 1 service redis thôi, và bạn có 1 triệu key thì sao? 1 triệu key đó sẽ ở trên cùng 1 node. Tư duy ở đây là 1 triệu key đó ta có thể phân chia đều đặn vào nhiều node, vậy mỗi node sẽ chỉ cần chứa 1 lượng key nhỏ hơn → giảm tải, giảm không gian lưu trữ, giảm cả traffic nữa.</p>
<p>Bài toán: Chúng ta có <strong><em>N</em></strong> node, và có 1.000.000 keys. Hãy cùng nghĩ xem chúng ta sẽ làm thế nào để key được chia đều sang N node đã có?</p>
<h3 id="heading-phuong-phap-naive-hashing">Phương pháp: Naive hashing</h3>
<p>Phương pháp đơn giản, đó là chúng ta sẽ hash các key ra 1 con số, rồi lấy số đó % N là sẽ tìm ra node tương ứng, tiến hành lưu key đó vào node đã tìm được</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763958140624/824e24d4-efcf-4acb-b28d-e64f0a31b747.png" alt class="image--center mx-auto" /></p>
<p>Nhược điểm: Phương pháp trên rất tốt về lý thuyết, nhưng trên thực tế, các node có thể bị down, hoặc có thể scale thêm nhiều node → N thay đổi, hàm <strong><em>get_node</em></strong> sẽ bị thay đổi → chúng ta cần rehashing lại khá nhiều key. Hãy xem hình bên dưới:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763958344634/9cb25b51-d399-4b40-bda3-3dbe425fd725.png" alt class="image--center mx-auto" /></p>
<p>Có thể nhìn thấy, với Naive Hashing, chúng ta sẽ phải Rehashing lại 300k keys, điều này dẫn tới việc mỗi khi cần phải thêm 1 node, hay bớt 1 node đi, các kĩ sư sẽ rất vất vả. Consistent Hashing sinh ra để khắc phục nhược điểm này.</p>
<h3 id="heading-phuong-phap-consistent-hashing">Phương pháp: Consistent Hashing</h3>
<p>Tưởng tượng chúng ta có một vòng tròn - <strong><em>HashRing</em></strong> (kéo dài từ 0 - (2³² - 1)), thông qua <strong>hash function</strong>, chúng ta sẽ sắp xếp các node trên vòng tròn này.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763982389984/983e4289-0177-44aa-9975-82034ceadbf8.png" alt /></p>
<p>Cứ mỗi key đi vào, chúng ta sẽ tìm hash key trên vòng trong <strong><em>Hash Ring</em></strong>, đi theo chiều kim đồng hồ để tìm node gần nhất gặp được, như ở hình trên, key <em>user_1111</em> sau khi hash sẽ đi tìm được node 1 gần nhất mình, nó sẽ được lưu tại node 1</p>
<p>Vậy, khi thêm một node mới là node 4 vào giữa node 3 và node 1 thì sao? Sẽ có 1 số ít key thay vì vào node 1 sẽ vào node 4, còn lại thì sẽ vẫn ở node 1. Bài toán là làm thế nào để số lượng key phải rehashing là giảm thiểu đi chỉ còn khoảng <strong><em>10 - 20%</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763982656825/e1239184-0f8b-4648-b452-dc78317ba175.png" alt class="image--center mx-auto" /></p>
<p>Nhìn hình vẽ trên, chúng ta có thể thấy rằng key <em>user_121</em> sẽ vào node 4 thay vì node 1, vậy nên vị trí hash trên <strong><em>Hash Ring</em></strong> sẽ chỉ cần đổi từ range (node3, node4] vào node 4 thay vì sẽ vào node 1 đúng không? Đó là ý tưởng của <strong><em>Consistent Hashing</em></strong> mà mình muốn nói.</p>
<h2 id="heading-implement-consistent-hashing">Implement Consistent Hashing</h2>
<p>Trông vậy nhưng mình nghĩ khá đơn giản thôi, cái chính là chúng ta sẽ chọn Data Structure nào để quản lý và sắp xếp trật tự các node trên Hash Ring vậy. Trong project này mình sẽ chọn Rust ( tiện cũng để học viết rust luôn thôi )</p>
<h3 id="heading-tao-struct-consistenthashing">Tạo struct ConsistentHashing</h3>
<p>Mình sẽ chọn BTreeMap của Rust để quản lý order của từng node trên vòng tròn Hash Ring. Các bạn có thể hình dung BTreeMap là 1 hashmap (key-value) nhưng có thể sắp xếp được các Item dựa trên Key của nó thôi.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">const</span> MAX_RING_SLOT: <span class="hljs-built_in">u64</span> = <span class="hljs-built_in">u32</span>::MAX <span class="hljs-keyword">as</span> <span class="hljs-built_in">u64</span>;

<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ConsistentHashRing</span></span> {
    <span class="hljs-comment">/// The hash ring storing virtual node positions</span>
    ring: BTreeMap&lt;<span class="hljs-built_in">u32</span>, <span class="hljs-built_in">String</span>&gt;,
    <span class="hljs-comment">/// Hash function used for mapping keys and nodes</span>
    hash_fn: <span class="hljs-function"><span class="hljs-keyword">fn</span></span>(&amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">u32</span>,
    <span class="hljs-comment">/// Number of virtual nodes (replicas) per physical node</span>
    replicas: <span class="hljs-built_in">usize</span>,
}
</code></pre>
<ul>
<li><p>ring: là BTreeMap để quản lý Hash Ring</p>
</li>
<li><p>hash_fn: đây là hàm hash trong thuật toán mà mình dùng, để tìm vị trí key trên vòng tròn hash ring</p>
</li>
<li><p>replicas: đây là số lượng Virtual node, tưởng tượng rằng nếu trên vòng tròn Hash Ring nếu có 3 node thôi, thì 1 node có thể sẽ là <strong>Hot spot</strong> (chứa nhiều key hơn các node còn lại). Có nhiều Virtual Node để sao cho key được phân chia đều hơn và công bằng hơn</p>
</li>
</ul>
<h3 id="heading-cac-method-can-thiet">Các method cần thiết</h3>
<p>Chúng ta sẽ có các method như sau:</p>
<ul>
<li><p>add_node(node: &amp;str): thêm 1 node vào vòng tròn Hash Ring</p>
</li>
<li><p>remove_node(node: &amp;str): xoá 1 node trong vòng tròn Hash Ring</p>
</li>
<li><p>get_node(key: &amp;str): tìm 1 node gần nhất theo chiều kim đồng hồ theo key</p>
</li>
<li><p>etc…: các method khác như: size của rings, print_ring, …</p>
</li>
</ul>
<pre><code class="lang-rust"><span class="hljs-keyword">impl</span> ConsistentHashRing {
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">new</span></span>(replicas: <span class="hljs-built_in">usize</span>) -&gt; <span class="hljs-keyword">Self</span> {
        ConsistentHashRing {
            ring: BTreeMap::new(),
            hash_fn: ConsistentHashRing::default_hash_fn,
            replicas,
        }
    }
    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">with_hash_fn</span></span>(replicas: <span class="hljs-built_in">usize</span>, hash_fn: <span class="hljs-function"><span class="hljs-keyword">fn</span></span>(&amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">u32</span>) -&gt; <span class="hljs-keyword">Self</span> {
        ConsistentHashRing {
            ring: BTreeMap::new(),
            hash_fn,
            replicas,
        }
    }

    <span class="hljs-comment">/// Default hash function using Rust's DefaultHasher.</span>
    <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">default_hash_fn</span></span>(key: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">u32</span> {
        <span class="hljs-keyword">let</span> <span class="hljs-keyword">mut</span> hasher = DefaultHasher::new();
        key.hash(&amp;<span class="hljs-keyword">mut</span> hasher);
        (hasher.finish() % MAX_RING_SLOT) <span class="hljs-keyword">as</span> <span class="hljs-built_in">u32</span>
    }

    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">add_node</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, node: &amp;<span class="hljs-built_in">str</span>) {
        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-keyword">self</span>.replicas {
            <span class="hljs-keyword">let</span> virtual_key = <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{}#{}"</span>, node, i);
            <span class="hljs-keyword">let</span> hash_key = (<span class="hljs-keyword">self</span>.hash_fn)(&amp;virtual_key);
            <span class="hljs-keyword">self</span>.ring.insert(hash_key, node.to_string());
        }
    }

    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">get_node</span></span>(&amp;<span class="hljs-keyword">self</span>, key: &amp;<span class="hljs-built_in">str</span>) -&gt; <span class="hljs-built_in">Option</span>&lt;<span class="hljs-built_in">String</span>&gt; {
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">self</span>.ring.is_empty() {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>;
        }

        <span class="hljs-keyword">let</span> hash_key = (<span class="hljs-keyword">self</span>.hash_fn)(key);

        <span class="hljs-comment">// Find the first node with hash &gt;= key's hash (clockwise on ring)</span>
        <span class="hljs-keyword">self</span>.ring
            .range(hash_key..)
            .next()
            .map(|(_, node)| node.clone())
            .or_else(|| {
                <span class="hljs-comment">// Wrap around to the first node if no node found</span>
                <span class="hljs-keyword">self</span>.ring.values().next().cloned()
            })
    }

    <span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">remove_node</span></span>(&amp;<span class="hljs-keyword">mut</span> <span class="hljs-keyword">self</span>, node: &amp;<span class="hljs-built_in">str</span>) {
        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-number">0</span>..<span class="hljs-keyword">self</span>.replicas {
            <span class="hljs-keyword">let</span> virtual_key = <span class="hljs-built_in">format!</span>(<span class="hljs-string">"{}#{}"</span>, node, i);
            <span class="hljs-keyword">let</span> hash_key = (<span class="hljs-keyword">self</span>.hash_fn)(&amp;virtual_key);
            <span class="hljs-keyword">self</span>.ring.remove(&amp;hash_key);
        }
    }
}
</code></pre>
<p>Cả source code mình để ở đây: <a target="_blank" href="https://github.com/fifteen1309/consistent_hashing_XV5">Source</a> (các bạn tiện cho mình xin một ⭐️⭐️ nhé :) )</p>
<h2 id="heading-nhuoc-diem">Nhược điểm:</h2>
<p>Cách implement của mình có nhiều nhược điểm vẫn chưa cover hết được.</p>
<ol>
<li><p><strong>Nhược điểm 1</strong>: Trong thực tế, việc downtime khi thêm 1 node vào Hash Ring sẽ được handle thế nào?</p>
<p> Trả lời: Theo ý kiến cá nhân, khi thêm 1 node F vào Hash Ring, cần có cơ chế để migrate key vốn nằm ở node E sang node F, mình sẽ cho call ở node F trước, nếu chưa có thì sẽ vòng ngược lại để lấy key ở node E</p>
</li>
<li><p><strong>Nhược điểm 2</strong>: Nhược điểm của việc dùng virtual nodes là gì? Tăng bộ nhớ, nhiều virtual node thì việc find node sẽ phức tạp hơn. Hơn nữa khi thêm node hay bớt node sẽ phải handle tất cả các virtual node đó để move data sang các node bên cạnh…</p>
</li>
</ol>
<h2 id="heading-ket-luan">Kết luận</h2>
<p>Bài viết này có thể hoặc không giúp bạn nhiều, nó có thể là một chủ đề cần bàn luận. Theo mình tìm hiểu được thì có khá nhiều service cũng không áp dụng Consistent Hashing này (redis cluster, mongodb) và source code còn nhiều thiếu sót, nên mình khá vui khi được mọi người góp ý, chỉnh sửa và thảo luận. Mình cảm ơn !!</p>
]]></content:encoded></item><item><title><![CDATA[Tự sự về Big tech]]></title><description><![CDATA[Tôi vừa trải qua lần đầu interview với BigTech, và đó là 1 trải nghiệm thú vị, từ việc tôi chuẩn bị kiến thức, cho đến vòng coding interview… Mọi thứ thật khó nói nhưng cũng đáng để chia sẻ.
Trong series: “Chinh phục BigTech” này, tôi sẽ tâm sự cả tr...]]></description><link>https://blog.jasperisme.io.vn/tu-su-ve-big-tech</link><guid isPermaLink="true">https://blog.jasperisme.io.vn/tu-su-ve-big-tech</guid><category><![CDATA[big tech]]></category><category><![CDATA[algorithms]]></category><dc:creator><![CDATA[Jasper]]></dc:creator><pubDate>Wed, 30 Jul 2025 03:23:06 GMT</pubDate><content:encoded><![CDATA[<p>Tôi vừa trải qua lần đầu interview với BigTech, và đó là 1 trải nghiệm thú vị, từ việc tôi chuẩn bị kiến thức, cho đến vòng coding interview… Mọi thứ thật khó nói nhưng cũng đáng để chia sẻ.</p>
<p>Trong series: “Chinh phục BigTech” này, tôi sẽ tâm sự cả trải nghiệm, kinh nghiệm của tôi, cũng như phân tích khía cạnh Thuật toán, kĩ thuật thông qua các câu hỏi mà tôi đã nhận được trong quá trình ôn luyện và interview.</p>
<p>Yepp, nó là 1 cuộc chiến dài hơi và ngốn rất nhiều công sức, tuy nhiên mọi sự khó khăn chắc chắn sẽ được đền đáp. Đối với tôi chưa phải bây giờ, nhưng sẽ là tương lai.</p>
<p>Mong rằng series này sẽ mang đến tư duy mở hơn cho các bạn - những ai đang chưa có mục tiêu xa hơn trong sự nghiệp của mình ( giống tôi bây giờ ), hoặc kể cả có rồi nhưng chưa thể đạt được nó. Trong cuộc sống này, niềm tin là thứ quyết định khả năng của mỗi người, họ có thể tin mình đi xa đến mức nào, họ sẽ đặt chân đến đó.</p>
<p>Thank kiu !!!!</p>
]]></content:encoded></item><item><title><![CDATA[Race condition: rất dễ hiểu qua một lần 
thiết kế game]]></title><description><![CDATA[Hôm nay là 1 kiến thức khá thú vị, dễ hiểu và không hề khô khan nhé :v, cũng có thể thay đổi tư duy của các bạn Backend junior và thậm chí Middle nếu chưa thực sự hiểu đấy.
Race condition là gì vậy?
Oke, nói về race condition nào? Hmm, một thuật ngữ ...]]></description><link>https://blog.jasperisme.io.vn/race-condition-rat-de-hieu-qua-mot-lan-thiet-ke-game</link><guid isPermaLink="true">https://blog.jasperisme.io.vn/race-condition-rat-de-hieu-qua-mot-lan-thiet-ke-game</guid><category><![CDATA[jasperisme]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Jasper]]></dc:creator><pubDate>Fri, 11 Jul 2025 17:28:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752254742558/95163403-cccc-44ec-9b05-80a83b318c34.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hôm nay là 1 kiến thức khá thú vị, dễ hiểu và không hề khô khan nhé :v, cũng có thể thay đổi tư duy của các bạn <strong><em>Backend junior</em></strong> và thậm chí <strong><em>Middle</em></strong> nếu chưa thực sự hiểu đấy.</p>
<h2 id="heading-race-condition-la-gi-vay">Race condition là gì vậy?</h2>
<p>Oke, nói về race condition nào? Hmm, một thuật ngữ khá quen thuộc trong lập trình đúng không nào? Từ khái niệm trong ngôn ngữ lập trình, cách quản lý bộ nhớ cho đến những hệ thống người dùng đều sẽ phải gặp vấn đề này.</p>
<p>Hiểu đơn giản thì như thế này:</p>
<ul>
<li><p>Anh A và anh B cùng là admin của 1 Fanpage Facebook</p>
</li>
<li><p>Anh A thì thiết kế Fanpage sao cho đẹp</p>
</li>
<li><p>Anh B thì thiết kế Fanpage sao cho hầm hố, ngầu</p>
</li>
</ul>
<p>Và bùm, kết quả là Fanpage nó ra 1 kiểu “đẹp + hầm hố” → “đẹp hố” ha :D.</p>
<p>Nói chung, <strong>race condition</strong> là trạng thái mà 1 bản ghi, 1 ô nhớ, 1 giá trị được thay đổi bởi nhiều bên không kiểm soát. (Đây là mình giải thích theo ý của mình hiểu, không phải định nghĩa chuẩn nhé :v)</p>
<p>Ôi!! Nói luyên thuyên giông dài rồi, đáng lẽ tôi nên đặt nó vào bài toán cụ thể mà tôi đã gặp phải - thiết kế Game</p>
<h2 id="heading-case-thuc-te-trong-game-chuong-bo">Case thực tế trong game chuồng bò 🐮</h2>
<p>Yeah, công ty tôi đang làm game đó, game <strong><em>telegram mini app</em></strong> (🤫 suỵt, giữ bí mật vì game chưa ra nha :D).</p>
<p>Tôi đã gặp 1 case khá thú vị để viết ra đây, mong mọi người có thể đọc được và cho 1 like</p>
<p>Bài toán như sau:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752242331779/0d98ed2f-a0d2-45f1-878d-44d387495744.png" alt="Chuồng bò " class="image--center mx-auto" /></p>
<p>Game này giống game nông trại, nhưng chúng ta chỉ nói xung quanh chuồng bò nhé (đáng yêu nhỉ)</p>
<p>Chuồng bò thì có từng con bò, nhiệm vụ cho bò ăn, thu hoạch → đơn giản đúng không ?</p>
<p>Tôi đã tạo 2 bảng trong database: UserShelter (chuồng động vật của user) và AnimalInstance (các thực thể động vật) và có mối quan hệ 1 - N (1 chuồng nhiều con).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752243124503/a0eda289-a842-4332-b158-4817dc67ed85.png" alt class="image--center mx-auto" /></p>
<p>Hình ảnh trên là mô phỏng đơn giản để các bạn dễ dàng hình dung về relationship</p>
<p>Ý tưởng của game là như sau:</p>
<ul>
<li><p>Bò thì ăn cỏ, UI sẽ để user kéo thả cỏ để cho bò ăn</p>
</li>
<li><p>Sau khi đợi 1 thời gian khi hoàn thành ăn cỏ, bò sẽ cho ra sữa, mình chỉ cần claim về thôi</p>
</li>
</ul>
<p>Việc của chúng ta là hãy tập trung viết hàm FeedAnimal nhé.</p>
<p>Bây giờ, hãy pha 1 cốc cà phê trong 10s để cho các bạn hình dung viết API thế nào để làm feature này nhé :D</p>
<p><s>☕️☕️☕️</s></p>
<p>Chắc hẳn các bạn hình dung ra rồi, đơn giản đúng không?</p>
<p>Flow sẽ như sau:</p>
<pre><code class="lang-plaintext">-- FindAnimalInstanceById 
SELECT * FROM animal_instances 
INNER JOIN user_shelters ON ...
INNER JOIN shelters ON ... 
WHERE id = $1; // $1 = animalInstanceId, lấy ra được animal_instance

-- Kiểm tra nếu animal instance tìm được đã được cho ăn chưa? 
-- Nếu đã cho ăn rồi, hoặc ăn xong rồi nhưng chưa claim thì trả ra error: can't feed at this time
if animal_instance.last_feed_at != null &amp;&amp; ... {
    return err 
} // đoạn này phụ thuộc xử lý logic của bạn

-- Nếu thoả mãn điều kiên, let's feed this animal
BEGIN transaction;
-- UpdateAnimalInstance
UPDATE animal_instances SET last_feed_at = now() WHERE id = $1;

-- UpdateUserItemBalance
UPDATE user_item_balance SET total = $1 WHERE user_id = $2 AND item_id = $3;
COMMIT;
</code></pre>
<p>Có cần tôi giải thích không :D</p>
<ul>
<li><p>Vì chúng ta cho 1 con bò ăn nên sẽ có id của nó</p>
</li>
<li><p>Fetch con bò đó từ database, có thể join các bảng để lấy thông tin cần thiết</p>
</li>
<li><p>Check điều kiện xem bò này có thể cho ăn được vào thời điểm này không?</p>
</li>
<li><p>Mở transaction và thực hiện update</p>
<ul>
<li><p>Update field last_feed_at cho animal_instance</p>
</li>
<li><p>Vì cỏ cho bò ăn là 1 item trong game, nên sau khi cho ăn phải trừ đi 1 trong kho, hàm UpdateUserItemBalance ý nghĩa là vậy</p>
</li>
</ul>
</li>
</ul>
<p>Còn ở đây ai hỏi transaction là gì và vì sao cần thiết thì Google nhé :D</p>
<p>(bên trên là tôi mô phỏng lại thôi, thực tế hàm rất nhiều thứ để xử lý, vì là game mà)</p>
<p>Xong rồi, đơn giản đúng không :)). End ở đây nhé 😂.</p>
<h2 id="heading-van-de-o-dau-nhi">Vấn đề ở đâu nhỉ?</h2>
<p>Nếu chỉ dừng ở bên trên thôi thì chắc trình độ Fresher hoặc Junior, maybe viết CRUD thôi.</p>
<p>Vậy đoạn code bên trên vấn đề nằm ở đâu nhỉ?</p>
<p>Thật may là tôi có 1 người đồng hành cùng trong dự án này và làm Frontend (dự án có 3 người :)) )</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752252453606/9ac32d60-85d0-40b4-bb6d-4f89c19c8b5d.png" alt class="image--center mx-auto" /></p>
<p>Nhìn trên hình thì các bạn sẽ thấy, UI sẽ là kéo nhưng không lắng nghe sự kiện thả mà là bôi cả 1 hàng, cỏ đi đến đâu thì bò ăn đến đó (ý tưởng ông nghĩ ra cái game này T-T ).</p>
<p>Tuy nhiên nó lại trở thành cái may mắn vì tôi có thể dùng nó để viết ra bài này, mở ra cho chúng ta cơ hội nói về <strong><em>Race condition</em></strong></p>
<p>Vấn đề ở chỗ khi bôi, người anh em frontend đã check khi cỏ vào miệng con bò nào thì sẽ call API feedAnimal, và vô hình chung nó bắn đâu đó tầm 3 - 4 request cùng 1 lúc với time rất nhỏ, khoảng vài milliseconds, và yebb, chúng ta cùng đi phân tích thôi.</p>
<p>Đoạn Logic xử lý chúng ta bàn ở trên về cơ bản không sai, nếu chỉ có 1 request tới độc lập, nhưng sẽ sai khi có nhiều request đến gần như đồng thời.</p>
<p>Vậy thì, nó sai ở đâu ? Yes, đó là đoạn check điều kiện để được cho ăn</p>
<p>“Tưởng tượng rằng 2 request đến đồng thời, cùng select ra animal instance trong trạng thái chưa ăn, và cùng check điều kiện hợp lệ để cho ăn, cùng cập nhật field <strong>last_feed_at</strong>, tuy nhiên, user đã mất không phải <strong>1</strong> mà là <strong>2</strong> item cỏ” - đây là hiện tượng bị duplicate nếu không biết cách xử lý - rất dễ gặp trong các hệ thống thanh toán hoặc xử lý transactions</p>
<p>Ha ha, dễ hiểu đúng không ?</p>
<h2 id="heading-giai-phap-thoiiii">Giải pháp thôiiii!!!</h2>
<p>Keyword ở đây là Lock, và tất nhiên rất nhiều loại, tuy nhiên tôi sẽ phân tích các loại mà tôi đã áp dụng thôi, không áp dụng thì không nói đến</p>
<p>Có 2 loại Lock mà tôi đã sử dụng trong hệ thống backend này:</p>
<ul>
<li><p>Application lock: Lock ở tầng ứng dụng, sử dụng mutex trong hệ thống distrubuted cache</p>
</li>
<li><p>Database lock: Lock ở tầng database</p>
</li>
</ul>
<h3 id="heading-database-lock">Database lock</h3>
<p>Về lock ở tầng database, có vẻ nó sẽ dễ dàng và thuận tiện hơn, ở đây tôi sẽ sử dụng kĩ thuật row locking</p>
<p>Row locking cho phép ta khoá 1 row lại cho request thứ nhất đến, cập nhật xong rồi request thứ 2 mới được Select và update</p>
<p>Sẽ chỉnh lại câu lệnh Select như sau</p>
<pre><code class="lang-plaintext">BEGIN TRANSACTION;

SELECT * FROM animal_instances 
INNER JOIN user_shelters ON ...
INNER JOIN shelters ON ... 
WHERE id = $1
FOR UPDATE animal_instances;
...

COMMIT;
</code></pre>
<p>Keyword <strong>FOR UPDATE</strong> là gì?</p>
<p>Request nào đến trước và call For Update, nó sẽ lock row đó lại và khi request thứ 2 đến, sẽ phải chờ cho request đầu tiên hoặc là transaction rollback, hoặc là commit, nó mới mở lại.</p>
<p>Tất nhiên ngoài <strong>For Update</strong> ra chúng ta còn nhiều loại khoá khác như <strong>For Share, For Key Share</strong>,… các bạn có thể tự tìm hiểu</p>
<p>Hoặc ngoài cách này ra có 1 cách là thêm 1 column flag version cho animal instance để Update theo điều kiện, thì không cần phải Lock Row (nhưng như thế lại phải thêm column đúng không :v)</p>
<p>Về phần Database Locking này thì còn rất nhiều cái để bàn nhé, nhưng hẹn các bạn bài sau.</p>
<h3 id="heading-application-lock">Application lock</h3>
<p>Tại sao lại dùng Application lock?</p>
<p>Như tôi có nói thì hàm thực tế sẽ rất dài và nặng logic, cập nhật các bản ghi trên các table khác nhau rất nhiều chứ không chỉ có 2 bảng như chúng ta đang bàn. Và tôi thấy là khi lock column như Database locking, transaction sẽ mở khá lâu, dẫn đến có thể row sẽ bị lock lâu =&gt; app slow</p>
<p>Giải pháp: Tôi dùng Mutex của redis để lock request. Đây là 1 kĩ thuật khi dùng với distributed cache mà tôi đã dùng trong dự án này ( tôi dùng redis-cluster )</p>
<pre><code class="lang-plaintext">lockKey := fmt.Sprintf("lock_feed_animal_instance:%s", animalInstanceId);
mutex := redSync.NewMutex(lockKey, time.Second*20);
if err := mutex.TryLock(); err != nil {
    return err;
}

-- FindAnimalInstanceById
Select như bình thường, không cần lock
</code></pre>
<p>Và tôi chỉ cần mở transaction cuối hàm để update các data theo ý muốn thôi.</p>
<h2 id="heading-summarize">Summarize</h2>
<p>Tóm lại, đây là 1 kiến thức khá thú vị nhưng cũng hết sức cơ bản trên con đường trở thành <strong>Senior Software Engineer.</strong> Tuy nhiên cách nào cũng có <strong><em>tradeoff</em></strong> cả và chúng ta cần suy nghĩ kĩ lưỡng để lựa chọn phương án sao cho phù hợp với quy mô cũng như là business của mình</p>
<p>Thôi kết lại ở đây nhé. Nếu thấy hữu ích thì tôi thấy vui và mừng cho bạn rồi :D.</p>
]]></content:encoded></item><item><title><![CDATA[Lời nói đầu]]></title><description><![CDATA[Hi, nói sao nhỉ, mình mới chỉ tập tành viết blog thôi nên cũng chưa có nhiều kinh nghiệm viết hay. Nhưng hãy cứ để nó tự nhiên và là mình nhất nhé :D, vì mình là dev nên cũng chẳng muốn trau chuốt lắm.
Vậy vì sao lại bắt đầu với Blog?
Mình là một Sof...]]></description><link>https://blog.jasperisme.io.vn/loi-noi-dau</link><guid isPermaLink="true">https://blog.jasperisme.io.vn/loi-noi-dau</guid><category><![CDATA[jasperisme]]></category><category><![CDATA[technology]]></category><dc:creator><![CDATA[Jasper]]></dc:creator><pubDate>Fri, 11 Jul 2025 11:41:02 GMT</pubDate><content:encoded><![CDATA[<p>Hi, nói sao nhỉ, mình mới chỉ tập tành viết blog thôi nên cũng chưa có nhiều kinh nghiệm viết hay. Nhưng hãy cứ để nó tự nhiên và là mình nhất nhé :D, vì mình là dev nên cũng chẳng muốn trau chuốt lắm.</p>
<h3 id="heading-vay-vi-sao-lai-bat-dau-voi-blog">Vậy vì sao lại bắt đầu với Blog?</h3>
<p>Mình là một Software Engineer và có thể nói mình bị “nghiện” cảm giác được học những thứ mới, và có những kiến thức mình được vô tình biết, hoặc trong quá trình làm việc phát hiện được nó và cảm thấy rất vui, vui lắm luôn ấy(chả hiểu sao :v). Có lẽ vì mình thích cái ngành này thật, dù cho nó đôi khi mệt (sorry vì đã nói không tích cực cho lắm)</p>
<p>Cho đến một ngày, impact của mình ở công ty càng ngày càng lớn, mình cũng muốn có thể được share những kiến thức mình đã học được cho nhiều người hơn, nhưng mặt khác lại lo rằng nói ra thì không cần thiết, nên mình đã bắt đầu bằng các viết lại trước để mình không quên, và ai đó đọc được cũng giúp họ được phần nào.</p>
<h3 id="heading-noi-dung-ve-blog-nay">Nội dung về blog này</h3>
<p>Mình cũng sẽ không giới hạn blog này chỉ là về tech thuần, vì nó có vẻ rất khô khan và sẽ gắn chặt mình với các nội dung này. Mình sẽ muốn mở rộng blog này ra để có thể share về đời sống, con người, mình cũng thích kiến thức về Kinh tế nữa :D, và hơn hết mình sẽ tập trung ban đầu về Công nghệ, yebb thế mạnh của mình</p>
<p>Oke! Nói đến đây thôi để mọi người hiểu hơn về mình, mình sẽ bắt đầu nuôi cái Page blog này ngày 1 thú vị hơn và mong 1 ngày nào đó nó sẽ giúp đỡ các bạn 1 phần nào.</p>
]]></content:encoded></item></channel></rss>