<?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[Văn Vọc Vạch Blog]]></title><description><![CDATA[Mình thích backend và mê vọc vạch các vấn đề công nghệ tới tận gốc thì thôi. Blog này là nơi mình ghi lại trải nghiệm và bài học rút ra sau mỗi lần "nghịch ngu" tới sửa sai.]]></description><link>https://blog.huuvan.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 00:24:17 GMT</lastBuildDate><atom:link href="https://blog.huuvan.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Build Your Own Shell: Mình tìm lại cảm giác code giữa bão AI]]></title><description><![CDATA[Dạo gần đây, lướt FB, Linked in, mình thấy bội thực vì content AI. Đâu đâu cũng thấy “mình mới vibe code 100% app này”, “antigravity làm cho mình từ A-Z”, “dev chuẩn bị ra chuồng gà”,…
Thú thật,mình c]]></description><link>https://blog.huuvan.dev/build-your-own-shell</link><guid isPermaLink="true">https://blog.huuvan.dev/build-your-own-shell</guid><category><![CDATA[shell]]></category><category><![CDATA[codecrafters]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Nguyễn Hữu Văn (Marc)]]></dc:creator><pubDate>Tue, 24 Feb 2026 17:23:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/67ab732dbce3755996295812/28f3cf5b-bf17-4a43-8fcf-daffc88d8381.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Dạo gần đây, lướt FB, Linked in, mình thấy bội thực vì content AI. Đâu đâu cũng thấy “<em>mình mới vibe code 100% app này</em>”, “<em>antigravity làm cho mình từ A-Z</em>”, “<em>dev chuẩn bị ra chuồng gà</em>”,…</p>
<p>Thú thật,mình cũng sử dụng AI rất nhiều, hằng ngày. Nó nhanh, nó tiện, nhưng nó khiến mình thấy... chán. Feature ship nhanh, UI đẹp nhưng không còn cảm thấy “đã cái nư” khi hoàn thành.</p>
<p>Cuối tuần vừa rồi, thay vì chạm cỏ, mình quyết tâm tìm lại cảm giác "chạm code”.</p>
<p>Tiêu chí đơn giản:<br />- Phải <strong>ngắn</strong> và <strong>dễ</strong> <em>(mấy project vô thưởng vô phạt tốt nhất làm trong 1,2 ngày là đẹp, phức tạp quá là bỏ dở ngay. Kinh nghiệm sau khi mình đắp chiếu mấy con pet project 🪦)</em><br /><em>-</em> Gõ code thuần chay, tự tư duy, chỉ tra cú pháp (<em><strong>nói không với AI</strong></em> )<br />- Đủ <strong>thú vị</strong> nhưng phải "<strong>low-level</strong>" một chút để học được gì đó: <em>no CRUD, task trên công ty quá đủ rồi</em> 🤣</p>
<p>Rồi mình tìm được <a href="http://codecrafters.io"><strong>CodeCrafters.io</strong></a>. Họ cho phép build lại các công cụ kinh điển (Redis, Git, Docker...) và tháng này họ đang miễn phí khóa <strong>Build Your Own Shell.</strong> →<em>Chân ái đây rồi.</em> <em>Quất!</em></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768830799623/41c3e149-235b-4099-aff5-bd355bc40087.png" alt="" style="display:block;margin:0 auto" />

<p>Đây là những gì mình học được sau vài tiếng "vật lộn" với Python để tạo ra một chiếc Shell của riêng mình.</p>
<iframe></iframe>

<h2>1. Shell thực chất là gì?</h2>
<p>Đừng để cái terminal đen ngòm đánh lừa. Chả có gì cao siêu cả.</p>
<p>Làm theo hướng dẫn của app, mình mới thấy. Về bản chất, Shell cũng chỉ là một chương trình bình thường. Nó chạy một vòng lặp vô tận (REPL - Read-Eval-Print-Loop) để chờ đợi và xử lý input từ người dùng.</p>
<p>Cấu trúc cốt lõi chỉ đơn giản thế này:</p>
<pre><code class="language-python">while True:
    sys.stdout.write("\( ") # Dấu \) huyền thoại của shell, trên window sẽ là PS - Power Shell
    user_input = input() # Đọc input
    parse(user_input): # Kiểm tra input, xem user muốn làm gì
    execute() # Thực thi
    print() # in output ra màn hình
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770050052255/4ad9d546-3933-46fd-bafe-928de1f2497b.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p>Ví dụ cách shell hoạt động khi nhận input: echo “hello world”</p>
</blockquote>
<hr />
<h2>2. Bài toán tìm kiếm: Lệnh nằm ở đâu?</h2>
<p>Nếu bạn giống mình, là 1 web developer, thì khoảnh khắc wow đầu tiên của mình là chính là:</p>
<p><em>Mở terminal → gõ “code .” → VS Code hiện lên 😮→ hacker 😎</em></p>
<p>Nhưng mình tin là ít nhất trong đời dev, bạn cũng từng vò đầu bứt tai với cái màn hình này:</p>
<img src="https://i.stechies.com/userfiles/images/Python-EV-1.JPG" alt="Python is not recognized as an internal or external command" />

<blockquote>
<p>Vậy có bao giờ bạn thắc mắc: “làm thế méo nào cái màn hình đen này nó hiểu được lệnh đó?”</p>
</blockquote>
<p>Câu trả lời nằm ở một <strong>biến môi trường</strong> mang tên<code>$PATH</code> . Biến chứa list các folder mà Shell sẽ ưu tiên tìm kiếm file có thể <strong>execute</strong> được mỗi khi bạn gõ một lệnh bất kỳ.</p>
<p><em>ở window file execute là đuôi “.exe”, nôm na là double click thì chạy được. Ví dụ</em> <em><strong>python.exe</strong></em></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770131248400/2ecab1d7-40ad-4dce-b0ef-5921b3abfb5a.png" alt="" style="display:block;margin:0 auto" />

<p>Ví dụ, bạn gõ <code>python —version</code> , nếu <code>$PATH</code> không chứa folder dẫn tới <strong><em>python</em>.exe</strong>, Shell sẽ chịu chết và trả về dòng lỗi "<em>not recognized</em>".</p>
<p><strong>CodeCrafters.io</strong> bắt mình tự code chay logic tìm kiếm này. Từ đó mình mới hiểu từ gốc rễ tại sao có những lệnh chỉ cần gõ tên là chạy, còn có những lệnh lại không.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769100703157/708a949f-3d72-4a3b-8ea5-4ba2439f0957.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p>Muốn tái hiện lại màn hình “python <em>not recognized”</em> phía trên không? vào Environment Variables → Path → Delete bất kỳ lệnh nào bạn muốn rồi gõ lại vào terminal xem 😅: nodejs, vscode, jdk,….</p>
</blockquote>
<p>Lý thuyết là thế thôi, tới lúc code cái logic tìm <code>$PATH</code> này, mình mới va phải những trải nghiệm "đau thương" !</p>
<h3>Window vs Linux: Cuộc chiến Path Separator</h3>
<p>Mình thì code trên window nhưng ông <strong>CodeCrafters.io</strong> lại chạy test trên? Linux.<br />App thì báo input để chạy test là <code>/usr/bin/grep</code>.<br />Nhưng window làm méo gì có folder này mà test ở local.<br />Mình lại phải vào biến <code>$PATH</code> copy folder python ra test: <code>D:\apps\Scoop\apps\python\current</code> <em>(mình cài bằng Scoop)</em></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769101206634/651f94c8-e9b1-401c-84b0-8952aec61e1e.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p><em>Code xong mỗi bài,</em> CodeCrafters.io sẽ <em>chạy test trên môi trường Linux</em></p>
</blockquote>
<p>Khổ nỗi tới khâu cộng chuỗi để tìm full path cho file .exe: <code>D:\apps\Scoop\apps\python\current</code> + <code>“\python.exe”</code>.</p>
<p>Test local thì ngon ăn, nhưng subtmit và chạy test trên <strong>CodeCrafters</strong> thì fail. 🤡</p>
<p>Ra là ông Window thì dùng <code>\</code>, ông Linux thì dùng <code>/</code> . Hard code string thì đúng ăn hành luôn.<br />Bảo sao chạy test mãi ko qua.</p>
<p>Đọc comment thì thấy các pros bảo phải xử lý đặc biệt bằng thư viện <code>os</code> :<br />- <code>os.pathsep</code> : tự nhận path separator theo môi trường, khỏi lăn tăn <code>\</code> hay <code>/</code><br />- <code>os.path.join</code>: uỷ thác khâu join cho thư viện, nhàn.</p>
<p>Một bài học nhỏ thôi, nhưng nhắc nhở mình về tính tương thích của hệ thống.<br />Và đấy mới là thử thách dạo đầu thôi.</p>
<h3>Cú lừa mang tên <code>code.cmd</code></h3>
<p>Vẫn là trong lúc code cái tìm <code>$PATH</code>. Thử lệnh <code>python —version</code> ngon rồi, mình hào hứng test thêm lệnh <code>code</code>.</p>
<p>Hí hửng tưởng mở được vscode trong cái shell tự chế thì ngon hơn HDPE luôn. Nhưng thứ nhận lại chỉ có <code>Command not found</code> 🤡</p>
<p>Hoá ra,trên Windows, khi tìm file thực thi, không phải lúc nào nó cũng là <code>.exe</code>.<br />Kiểm tra kỹ trong <code>D:\apps\Microsoft VS Code\bin</code>, mình mới thấy file thực thi thực chất tên <code>code.cmd</code>. Ok, giờ mới để ý có đuôi .cmd nữa.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769102471319/deb15161-5eff-400c-946d-3cd295886abd.png" alt="" style="display:block;margin:0 auto" />

<p>Từ đó, mình rút ra logic: Khi tìm kiếm một lệnh, Shell phải check đồng thời cả bộ extension: <code>[.exe, .bat, .cmd, ...]</code>. List này dài lắm, mình ghi mấy cái phổ biến thôi.</p>
<p>Code trên Window thì đau đầu vậy, còn trên Linux thì đơn giản hơn. File có 3 quyền cơ bản: read, write, execute. Tương ứng <strong>rwx</strong>.</p>
<p>Muốn biết file nào chạy được thì chỉ cần gõ <code>ls -la</code> xem có quyền thực thi ( có chữ <strong>X</strong>) không:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770131475729/2aed49a9-a9ed-468b-94c5-5a271e93f7b8.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>3. "Wow Moment": Khi cái app biến thành Shell thật</h2>
<p>Sau khi xong tính năng Execute, mình đọc được 1 comment ở dưới: <em><strong>“ê, sau bài này, chúng mày thử commit bằng chính cái Shell này đi, vì git nằm trong PATH mà. Đã lắm”</strong></em></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770131764249/6ee9775d-84a3-40ab-a08d-75b9c3914f76.png" alt="" style="display:block;margin:0 auto" />

<p>Ê hay nha, test thử <strong>git version</strong> và <strong>git status</strong>. Ngon! dính liền</p>
<img src="https://cdn.hashnode.com/uploads/covers/67ab732dbce3755996295812/1d827ca0-a77b-43e4-9d7f-52807de8f2f6.png" alt="" style="display:block;margin:0 auto" />

<blockquote>
<p>Terminal gốc của mình là phần màu mè, còn sau khi chạy file <strong>main</strong> là lên hình con Shell $ rồi đó.</p>
</blockquote>
<p>Đoạn này là phê nhất luôn. Mình chắc chắn là bạn cũng từng trải nghiệm cảm giác này: <strong><em>fix đc con bug khó, chạy đc feature lần đầu,..</em>.</strong></p>
<p>Chứ giờ có AI rồi, có khi cảm giác này lại hơi xa xỉ😁.</p>
<hr />
<h2>4. Kết luận</h2>
<p>Dành ra một cuối tuần để build "đồ cổ" như Shell không hề lãng phí. Nó giúp mình củng cố lại kiến thức về Process, PATH, và cách OS vận hành bên dưới.</p>
<p>Nếu bạn <em><s>là real dev</s></em> cũng đang thấy "ngán" dùng AI, mất lửa với code dần, hãy thử dành 1 buổi với CodeCrafters để tìm lại cảm giác nhé.</p>
<p><strong>Repo của dự án:</strong> <a href="https://github.com/vanbeonhv/my-own-shell">https://github.com/vanbeonhv/my-own-shell</a></p>
]]></content:encoded></item><item><title><![CDATA[Tối Ưu Cache: Từ 7 Request Đồng Thời Xuống Chỉ 1 Request Với Single Flight Pattern]]></title><description><![CDATA[TL;DR: Từ việc gặp phải hiệu ứng thundering herd trong production, mình đã áp dụng Single Flight pattern với cơ chế lock để đạt được perfect deduplication - từ 7 request đồng thời xuống chỉ 1 request thực sự được gửi đi.
Trong quá trình phát triển ứn...]]></description><link>https://blog.huuvan.dev/build-cache-voi-single-flight-pattern</link><guid isPermaLink="true">https://blog.huuvan.dev/build-cache-voi-single-flight-pattern</guid><category><![CDATA[cache]]></category><category><![CDATA[race-condition]]></category><category><![CDATA[#ThreadSafety ]]></category><dc:creator><![CDATA[Nguyễn Hữu Văn (Marc)]]></dc:creator><pubDate>Mon, 01 Sep 2025 11:51:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756727307148/71e65a4f-34fb-489e-a906-88b5abf5f028.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>TL;DR</strong>: Từ việc gặp phải hiệu ứng thundering herd trong production, mình đã áp dụng Single Flight pattern với cơ chế lock để đạt được <strong>perfect deduplication</strong> - từ 7 request đồng thời xuống chỉ 1 request thực sự được gửi đi.</p>
<p>Trong quá trình phát triển ứng dụng microservice, mình đã gặp phải một tình huống thú vị: <strong>tại sao cache hit rate lại thấp đến thế khi có nhiều request cùng lúc?</strong><br />Câu chuyện này không chỉ giúp mình hiểu sâu hơn về <strong>Single Flight pattern</strong>, mà còn học được cách xử lý <strong>hot spot</strong>, <strong>thread-safe</strong> và <strong>race condition</strong> trong thực tế.</p>
<h2 id="heading-1-use-case-thuc-te">1. Use case thực tế:</h2>
<p>App của mình nằm trong một hệ microservice. Do đặc thù của app, mọi API đều cần kiểm tra quyền truy cập bằng cách gửi request tới Auth service.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756699327237/ca99f2a9-66b0-4676-894e-ac3016c9328c.png" alt class="image--center mx-auto" /></p>
<p><strong>Vấn đề phát sinh</strong>: Khi lần đầu access app, sẽ có nhiều API cần gửi cùng một request kiểm tra "View" permission. Điều này tạo ra một <strong>hot spot</strong> - nhiều request cùng "săn đón" một resource.</p>
<p>Ngay lập tức mình nhớ tới blog của anh Quang Hoang: <a target="_blank" href="https://quanghoang.substack.com/p/50-days-of-sd-hotspot">#6 - Điểm nóng</a>, lần đầu tiên mình nghe về khái niệm <strong>Request Collapsing</strong> hay <strong>Singleflight</strong>. (btw, blog a này viết khá đỉnh, dễ đọc, recommend ae follow)</p>
<p><strong>Ý tưởng cốt lõi</strong>: Thay vì mỗi request gửi riêng, ta gom tất cả các request giống nhau lại, chỉ gửi 1 request đi. Các request còn lại sẽ chờ response từ request "đại diện".</p>
<h2 id="heading-2-trien-khai-ban-dau">2. Triển khai ban đầu</h2>
<h3 id="heading-hien-trang-cache-system">Hiện Trạng Cache System</h3>
<p>Trên server mình đã triển khai in-memory cache cho permission của từng user. Tuy nhiên, khi có nhiều request cùng access một key, tỉ lệ cache miss cực cao.</p>
<p>Flow ban đầu đơn giản như sau: <em>( viết dạng concept để dễ hình dung nhé )</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756699765578/3ab7952d-e5af-4536-830a-0c925e31a876.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">CheckPermissions</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> permission</span>)
    <span class="hljs-comment">// Tạo cache key từ user token + permission</span>
    <span class="hljs-keyword">var</span> cacheKey</span> = userToken + <span class="hljs-string">"-"</span> + permission

    <span class="hljs-comment">// Kiểm tra cache trước</span>
    IF cache.exists(cacheKey) <span class="hljs-function">THEN
        <span class="hljs-title">log</span>(<span class="hljs-params"><span class="hljs-string">"Thread ID - Cache hit - trả về kết quả từ cache"</span></span>)
        RETURN cache.<span class="hljs-title">get</span>(<span class="hljs-params">cacheKey</span>)
    END IF

    <span class="hljs-comment">// Gọi API kiểm tra quyền</span>
    apiUrl</span> = <span class="hljs-string">"Http://api/permission-check"</span>
    <span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> POST(apiUrl, {permission: permission})
    <span class="hljs-comment">// Xử lý response và lấy kết quả</span>
    <span class="hljs-keyword">var</span> result = parseAuthResponse(response)
    <span class="hljs-comment">// Lưu vào cache 1 phút</span>
    cache.<span class="hljs-keyword">set</span>(cacheKey, result, <span class="hljs-number">1</span>minute)

    RETURN result
</code></pre>
<p>Đây là kết quả mình nhận được với 7 request cùng lúc: 6 cache miss, 1 cache hit.<br /><strong>Tỉ lệ hit</strong>: 1/7 ~ 14% - quá thấp</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756376552511/e9c45084-aa92-44a1-8954-a9e0171a83f7.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-nguyen-nhan-thundering-herd-problem">Nguyên Nhân: Thundering Herd Problem</h3>
<p>Hiện tượng này gọi là <strong>thundering herd</strong> - khi một key hết hạn và đồng thời nhiều request cùng truy cập vào DB/service, dẫn tới quá tải. Đôi khi là nhiều key cùng hết hạn 1 lúc do đặt chung TTL (Time to live). Giải pháp thường là đặt key có TTL random.</p>
<p>Trong case của mình, Auth service bị "tấn công" bởi hàng loạt request kiểm tra permission giống hệt nhau, tại thời điểm user access app lần đầu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767376655375/a2b1e6f2-4003-4b68-a7d3-8676f121bad5.png" alt="thundering herd" class="image--center mx-auto" /></p>
<p><em>(nguồn: bytebytego)</em></p>
<h2 id="heading-3-ap-dung-single-flight-pattern">3. Áp Dụng Single Flight Pattern</h2>
<p>Để bảo kê cho Auth service khỏi quá tải, cùng triển khai <strong>Single flight</strong> nhé. Mình thích cái tên này vì rất dễ liên tưởng.</p>
<blockquote>
<p>Giống như một nhóm anh em chiến hữu của bạn book vé máy bay tới Phú Quốc. Thay vì điều 1 chuyến bay riêng cho từng ông, <em>Vietnam Airline</em> chắc chắn sẽ gom hết cả đám lên cùng 1 chuyến bay rồi chờ 1 thể</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756700709667/953c75ec-c7f0-48ee-a903-64c77fc7cce2.png" alt class="image--center mx-auto" /></p>
<p>“Chuyến bay” ở đây chính là request check permission gửi tới Auth service đó.<br /><em>( Còn tại sao lại là Vietnam Airline? Vì lần trước chuyến bay Vietjet tới Phú Quốc của mình bị delay 2 tiếng lận, request thì ko được phép delay như thế 🤣)</em></p>
<h3 id="heading-kien-truc-single-flight">Kiến Trúc Single Flight</h3>
<p>Ta sẽ có 3 component chính ở đây:</p>
<ul>
<li><p><strong>Request coordination</strong>: Nơi điều phối request. Khi 1 request tới, kiểm tra xem có request tương đương nào đang “bay” không?<br />  - Nếu có → xếp hàng chờ<br />  - Ngược lại → cho "cất cánh", gửi tới Auth service.</p>
</li>
<li><p><strong>In-flight Registry</strong>: Dictionary lưu trữ các "chuyến bay".<br />  - Key: <code>user_token + permission</code><br />  - Value: <code>Task</code>(C#)/<code>Promise</code>(JS), đại diện cho async request function tới Auth service</p>
</li>
<li><p><strong>Clean Up:</strong> Xoá khai báo “chuyến bay” khỏi Registry bên trên sau khi request hoàn thành.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756702259843/ad664cbe-373c-4c14-8167-364d7640ebb2.png" alt class="image--center mx-auto" /></p>
<p>Flow sẽ như này. Chỉ gồm flow mới, code cũ đã được đơn giản hoá</p>
<pre><code class="lang-csharp"><span class="hljs-comment">//In-flight Registry mình nhắc bên trên đây. Chú ý value là 1 Task nhé.</span>
Dictionary&lt;<span class="hljs-keyword">string</span>, Task&lt;<span class="hljs-keyword">bool</span>&gt;&gt; _inFlightRequests;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">CheckPermissions</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> permission</span>)</span> {
    <span class="hljs-keyword">var</span> cacheKey = userToken + <span class="hljs-string">"-"</span> + <span class="hljs-function">permission

    <span class="hljs-title">if</span> (<span class="hljs-params">cache.Hit</span>) return cachedValue</span>;
    <span class="hljs-comment">// NEW: Nếu request đang "bay"</span>
    <span class="hljs-keyword">if</span> (_inFlightRequests.ContainsKey(key))
        log(<span class="hljs-string">"ThreadId - Permission - Cache in-flight hit"</span>) 
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> _inFlightRequests[key]; <span class="hljs-comment">// Chờ request "bay" về</span>

    <span class="hljs-comment">// Tạo request mới và đăng ký. Nhớ là mình chỉ tạo 1 async function,</span>
    <span class="hljs-comment">// chứ chưa await nó nhé. Request chưa thực sự chạy đâu. </span>
    <span class="hljs-keyword">var</span> task = ExecuteActualRequest(permissions); 
    _inFlightRequests[key] = task;

    <span class="hljs-keyword">try</span> { 
        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> task; <span class="hljs-comment">// Tới đây mới await, cho request "bay"</span>
        <span class="hljs-keyword">return</span> result; 
    } <span class="hljs-keyword">finally</span> { 
        _inFlightRequests.Remove(key); <span class="hljs-comment">// Bay về rồi thì clean đi </span>
    }
}
</code></pre>
<h3 id="heading-ket-qua-cai-thien">Kết quả cải thiện</h3>
<p>Với 7 request đồng thời: 4 cache hit, 2 cache miss, 1 cache in-flight.<br />Bingo! Từ 6/7 request miss xuống chỉ 2/7 request miss - <strong>giảm 60-70%</strong>! (Vẫn còn race condition cần xử lý)<br />Giả sửa nếu ko phải <strong>7</strong> mà là <strong>100, 200, 1k</strong> request, lượng giảm còn nhiều hơn nhiều.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756659161079/7b343ec8-0a6a-49e4-b22c-74cfc382128c.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-van-de-con-ton-tai-race-condition">Vấn Đề Còn Tồn Tại: Race Condition</h3>
<p>Tại sao vẫn có 2 cache miss? Đó là <strong>race condition</strong> - các thread đồng thời kiểm tra cache:</p>
<pre><code class="lang-csharp">ThreadId: <span class="hljs-number">17</span> - cache <span class="hljs-keyword">in</span>-flight hit    <span class="hljs-comment">// Thread đầu tiên, tìm thấy in-flight request</span>
ThreadId: <span class="hljs-number">14</span> - cache miss            <span class="hljs-comment">// Race: chưa kịp add vào in-flight registry  </span>
ThreadId: <span class="hljs-number">24</span> - cache miss            <span class="hljs-comment">// Race: chưa kịp add vào in-flight registry</span>
</code></pre>
<p>Với quy mô app của mình thì thực ra dừng ở đây là cũng oke. Nhưng tới đây rồi, tại sao lại không tối ưu thêm để với 1 key chỉ có đúng 1 in-flight request thôi nhỉ?</p>
<h2 id="heading-4-giai-phap-thread-safe-hoan-chinh">4. Giải Pháp Thread-Safe Hoàn Chỉnh</h2>
<p>Nhắc tới giải quyết vấn đề truy cập đồng thời cao, phải nhắc tới <strong>lock</strong>. Ở đây để đơn giản, mình sẽ sử dụng 1 local lock, được C# built-in.<br />Với mỗi key sẽ tạo ra 1 lock object . Chỉ key nào acquire được lock mới cho “bay”.</p>
<pre><code class="lang-csharp">Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">object</span>&gt; _lock;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">CheckPermissions</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> permission</span>)</span> {
    <span class="hljs-keyword">var</span> cacheKey = userToken + <span class="hljs-string">"-"</span> + <span class="hljs-function">permission
    <span class="hljs-title">if</span> (<span class="hljs-params">cache.Hit</span>) return cachedValue</span>;
    <span class="hljs-comment">//Tạo lock theo từng key</span>
    <span class="hljs-keyword">var</span> lockKey = _lock.GetOrAdd(cacheKey)

    <span class="hljs-keyword">var</span> task;
    <span class="hljs-comment">// Chỉ 1 thread lấy được lock</span>
    <span class="hljs-keyword">lock</span> (keyLock) {
        <span class="hljs-comment">// double check vì cache có thể hit khi thread trước đó vừa ghi cache</span>
        <span class="hljs-keyword">if</span> (cache.Hit) <span class="hljs-keyword">return</span> cachedValue;

    <span class="hljs-comment">// Đem nguyên đống logic cũ bỏ vào trong này</span>
        <span class="hljs-keyword">if</span> (_inFlightRequests.ContainsKey(key)) 
            task = _inFlightRequests[key]
        <span class="hljs-keyword">else</span>
            <span class="hljs-keyword">var</span> task = ExecuteActualRequest(permissions); 
            _inFlightRequests[key] = task;
    }

    <span class="hljs-keyword">try</span> { 
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> task;
    } <span class="hljs-keyword">finally</span> { 
        _inFlightRequests.Remove(cacheKey);
        _lock.Remove(lockKey); <span class="hljs-comment">// Nhớ nhả lock đó ae </span>
    }
}
</code></pre>
<p><strong>Kết quả:</strong> Perfect! Với lock mechanism, giờ đây chỉ có <strong>duy nhất 1 request</strong> được gửi tới Auth service cho mỗi key cache, bất kể có bao nhiều request đồng thời.</p>
<p><strong>Test với 7 request đồng thời</strong>: Chỉ 1 request thực sự "bay", 6 request còn lại đều chờ và nhận kết quả từ request đầu tiên.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1756663722713/dd8c5351-18ea-4817-b3fd-2dabcd0b6b39.png" alt class="image--center mx-auto" /></p>
<p>Lưu ý:<br />- Trong bài mình khai báo Dictionary đơn giản cho In-flight Request và Lock. Nhưng thực tế đây là các object cần share state giữa các request, nên các bạn nhớ khai báo dạng <strong>Static</strong> hoặc scope <strong>Singleton (.net)</strong> nhé.<br />- C# có 1 builtin type là <code>ConcurrentDictionary</code> chuyên để support thread-safe. Nên sử dụng type này.<br />- Bài viết tập trung vào triển khai pattern. Còn về kiến trúc thì ko phải là best practise, nên sử dụng API Gateway cho việc authen.</p>
<h3 id="heading-ket">Kết</h3>
<ol>
<li><p><strong>Single Flight Pattern</strong> giúp đạt được <strong>perfect deduplication</strong> - chỉ 1 request duy nhất cho mỗi key</p>
</li>
<li><p><strong>Thread safety</strong> với lock mechanism là then chốt để loại bỏ hoàn toàn race condition</p>
</li>
</ol>
<p><em>Bạn đã từng gặp phải thundering herd problem chưa? Chia sẻ experience của bạn trong comments nhé!</em></p>
<h3 id="heading-reference">Reference:</h3>
<ol>
<li><p><a target="_blank" href="https://quanghoang.substack.com/p/50-days-of-sd-hotspot">#6 - Điểm nóng - by Qreuang Hoang</a></p>
</li>
<li><p><a target="_blank" href="https://youtu.be/wh98s0XhMmQ?si=OedDzpla7G37Q-cl">Bytebytego - Caching Pitfalls Every Developer Should Know</a></p>
</li>
<li><p><a target="_blank" href="https://200lab.io/blog/cach-discord-luu-tru-hang-nghin-ty-tin-nhan-voi-scylladb#xay-dung-data-services">Cách Discord Lưu Trữ Hàng Nghìn Tỷ Tin Nhắn Với ScyllaDB | 200Lab Blog</a> (quá trình migrate db của discord cũng sử dụng pattern này)</p>
</li>
<li><p><a target="_blank" href="https://discord.com/blog/how-discord-stores-trillions-of-messages">https://discord.com/blog/how-discord-stores-trillions-of-messages</a> (link gốc)</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Lỗi DBeaver không tìm thấy pg_dump – Debug thú vị với Flatpak trên Linux]]></title><description><![CDATA[Một lỗi tưởng vặt lại giúp mình hiểu rõ hơn:

Lần đầu biết pg_dump là gì và vì sao DBeaver cần nó khi backup

Biết vì sao Flatpak gây lỗi khi app cần truy cập trực tiếp file hệ thống

Tránh mất thời gian debug khi dùng tool dev với Flatpak


Mình mới...]]></description><link>https://blog.huuvan.dev/loi-dbeaver-khong-tim-thay-pgdump-debug-thu-vi-voi-flatpak-tren-linux</link><guid isPermaLink="true">https://blog.huuvan.dev/loi-dbeaver-khong-tim-thay-pgdump-debug-thu-vi-voi-flatpak-tren-linux</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[flatpak]]></category><category><![CDATA[linux for beginners]]></category><category><![CDATA[dbeaver]]></category><dc:creator><![CDATA[Nguyễn Hữu Văn (Marc)]]></dc:creator><pubDate>Wed, 23 Jul 2025 06:11:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753249431196/9a155057-fd17-48b5-ae6a-da0c5cae5aaa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Một lỗi tưởng vặt lại giúp mình hiểu rõ hơn:</p>
<ul>
<li><p>Lần đầu biết pg_dump là gì và vì sao DBeaver cần nó khi backup</p>
</li>
<li><p>Biết vì sao Flatpak gây lỗi khi app cần truy cập trực tiếp file hệ thống</p>
</li>
<li><p>Tránh mất thời gian debug khi dùng tool dev với Flatpak</p>
</li>
</ul>
<p>Mình mới chuyển sang dùng Fedora (linux) được 1 tháng. Hôm nay cần backup data, nên mở Dbeaver lên để backup nhưng gặp lỗi này: <code>"Local client is not specified for connection.”</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753234456294/c13c8a64-59d8-4c94-b008-dc33c397613f.png" alt class="image--center mx-auto" /></p>
<p><strong>Local client</strong> là gì cơ? Search 1 lúc, mình hiểu Local client là chương trình dòng lệnh đi kèm với PostgreSQL, như <code>pg_dump</code> để backup hoặc <code>psql</code> để query.</p>
<p>Theo như Dbeaver, mình cần thêm dẫn path tới <code>pg_dump</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753241476081/97d887b6-c93b-434e-8e4a-ac6b045035e7.png" alt class="image--center mx-auto" /></p>
<p>Hóa ra trước giờ mình toàn connect tới database ở cloud chứ máy local không hề cài Postgres. Bây giờ từ cloud mà cần thao tác với local disk (<strong>backup/restore</strong>) thì cần cài Postgres là hiển nhiên rồi 😂.<br />Sau khi cài, mình kiểm tra path của <code>pg_dump</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753252489814/70c84574-a0f4-4f02-96d2-bdc72e886228.png" alt class="image--center mx-auto" /></p>
<p>Got it: <code>/usr/bin/pg_dump</code> .Giờ thêm path này vào <strong>Local client</strong> là xong. Khoan, trên page của Dbeaver có cho mình 1 tip, cài package <code>libpq</code> có thể giúp Dbeaver tìm được client tự động.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753236684671/04f7286d-b263-4012-8c95-a5aea7096fb0.png" alt class="image--center mx-auto" /></p>
<p>Nào thì thử cài:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753252589146/0417af01-3c49-4ab2-bb79-724ff0e1a8f9.png" alt class="image--center mx-auto" /></p>
<p>Cái kết là không work. Đọc kỹ mới thấy hóa ra chỉ áp dụng cho <strong>macOS</strong> thôi. 😑 Cho chừa thói hấp tấp.</p>
<p>Giờ thì mình đoán thêm path của pg_dump là xong: .<br />Hmm, sao rõ ràng thêm đúng path rồi <code>/usr/bin/pg_dump</code>.<br />Mà Dbeaver hiển thị path lạ thế nhỉ: <code>/run/user/1000/doc/3de82b55/bin</code> - 1 path “ảo”.<br /><em>Phải chăng do môi trường nào đó tạo ra?</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753237036557/143cc165-aa67-4da8-bd78-c88f3213c8e9.png" alt class="image--center mx-auto" /></p>
<p>Lúc đó mình nghĩ chắc lỗi hiển thị gì đó thôi. Cứ bấm Ok xem sao.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753237201411/751aae99-be98-4e7a-b341-8cf14c210ce3.png" alt class="image--center mx-auto" /></p>
<p>Nope. Không work.<br />Sau đó mình thử đi thử lại,từ paste thẳng tới bấm browse để chọn path → Vẫn ra như cũ.<br />Follow từng bước trên Dbeaver Wiki → cũng không chạy được!!<br />Hẳn là vấn đề ở đâu đó chứ ko phải tại Dbeaver. Vì trước đó chạy trên Window ko bị. Mình đã nghĩ tới dùng <code>pg_dump</code> ở cli. Vì đúng bản chất của nó mà.<br /><em>Nhưng, thử cố tìm vấn đề xem. Chắc liên quan tới linux rồi 🤔</em></p>
<hr />
<p>Cuối cùng, không ngờ tới là vấn đề lại nằm ở <strong>Flatpak.</strong> Đây là 1 kho ứng dụng trên Linux, giống như Microsoft Store hay CH Play vậy. Mình có tải Dbeaver từ đây, cứ nghĩ là Store bình thường giống như <code>apt</code> trên Ubuntu vậy thôi.</p>
<p>Flatpak lại chạy kiêu khác. Thông thường các file <code>.exe, .deb, .rpm</code> , để cài được thì phải đúng hệ điều hành (.dev với Ubuntu, Debian, .rpm với Fedora, Centos ).<br /><em>Flatpak đóng gói app lại, chạy trong</em> <strong><em>sandbox</em></strong>*, bị “bọc” trong môi trường riêng*. Nghe quen quen đúng không? Đúng, giống y như <strong>Docker</strong> vậy 😉. Tức là chả cần biết bạn chạy distribution nào: Ubuntu, Fedora, Centos,… chạy được hết.</p>
<p>Quay lại vấn đề, vì chạy trong sandbox, cơ bản thì app trên Flatpak ko thể đọc (đúng hơn là không có quyền đọc) path của hệ thống. Lý giải cho việc dù mình nhập <code>/usr/bin/pg_dump</code> , Dbeaver lại đọc thành <code>/run/user/1000/doc/3de82b55/bin</code> - 1 path ảo.</p>
<p>Vậy cách fix như nào? Linux cho phép quản lý phân quyền rất chi tiết cho từng folder. <strong>Cấp quyền</strong> cho Flatpak đọc folder đó là được.</p>
<p>Nhưng mình ko muốn mỗi khi có vấn đề lại phải cấp quyền, nên chọn giải pháp đơn giản hơn: “Tải file .rpm từ trang chủ Dbeaver” 😅 . Nhanh gọn, dễ dàng đọc path bên ngoài.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753238934846/e12db4cd-4d00-46a3-b0a8-8031821844e4.png" alt class="image--center mx-auto" /></p>
<p>Qua trải nghiệm này, mình rút ra một số nguyên tắc nhỏ khi làm việc với GUI app trên Linux:</p>
<ul>
<li><p>Nếu app cần tương tác trực tiếp với file hệ thống (như pg_dump), hãy <strong>tránh cài qua Flatpak</strong> vì sandbox sẽ gây lỗi khó đoán.</p>
</li>
<li><p>Với PostgreSQL, các thao tác như backup/restore cần công cụ CLI (<code>pg_dump</code>, <code>psql</code>...) — nên đảm bảo cài đầy đủ và biết nó nằm ở đâu.</p>
</li>
<li><p>Flatpak rất tiện, nhưng không phải lúc nào cũng hợp với tool dành cho dev.</p>
</li>
</ul>
<p>Reference:<br /><a target="_blank" href="https://dbeaver.com/docs/dbeaver/Local-Client-Configuration/">https://dbeaver.com/docs/dbeaver/Local-Client-Configuration/</a><br /><a target="_blank" href="https://flathub.org/">https://flathub.org/</a></p>
]]></content:encoded></item></channel></rss>