<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Phạm Khánh Minh Mẫn — Technical Blog</title>
    <link>https://phamkhanhminhman.com</link>
    <description>Backend engineering, e-commerce API integration, and applied AI research notes.</description>
    <language>en</language>
    <atom:link href="https://phamkhanhminhman.com/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>PostgreSQL has four isolation levels. You only get three.</title>
      <link>https://phamkhanhminhman.com/blog/postgresql-isolation-levels-measured</link>
      <guid isPermaLink="true">https://phamkhanhminhman.com/blog/postgresql-isolation-levels-measured</guid>
      <description>The isolation table everyone copies belongs to the SQL standard, not to PostgreSQL. Measured on PostgreSQL 16: READ UNCOMMITTED is accepted and quietly ignored, Repeatable Read blocks phantoms the standard says it should allow, a plain SELECT at Repeatable Read blocks a writer for 1ms against 4003ms for a real lock, and the one anomaly that separates Repeatable Read from Serializable costs you a 40001 you have to retry.</description>
      <category>PostgreSQL / Concurrency</category>
      <pubDate>Thu, 27 Aug 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Three calls get you a Shopee token. Keeping it alive is the hard part.</title>
      <link>https://phamkhanhminhman.com/blog/shopee-oauth-token-lifecycle-at-scale</link>
      <guid isPermaLink="true">https://phamkhanhminhman.com/blog/shopee-oauth-token-lifecycle-at-scale</guid>
      <description>The quick start is three functions long and works first try against your own test shop. What it leaves out: the auth endpoints sign differently from every other call, the refresh token is single-use so two workers will quietly kill each other&apos;s copy, and the thing that actually expires is the seller&apos;s authorization — which you cannot read from the token in your hand.</description>
      <category>OAuth / Distributed Systems</category>
      <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Overselling in a flash sale: which lock to reach for, and when you need none</title>
      <link>https://phamkhanhminhman.com/blog/optimistic-vs-pessimistic-locking</link>
      <guid isPermaLink="true">https://phamkhanhminhman.com/blog/optimistic-vs-pessimistic-locking</guid>
      <description>A flash sale with 100 shirts in stock took 200 orders — and the stock column still read 94. Measured on real PostgreSQL: the one-line fix nobody reaches for first, the lock that makes checkout 149× slower, the case where pessimistic locking cannot be used at all, and why a perfectly correct row lock still loses inventory once a queue is in the path.</description>
      <category>PostgreSQL / Concurrency</category>
      <pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Idempotent isn&apos;t optional: the double-counting bug hiding in every append-only sync</title>
      <link>https://phamkhanhminhman.com/blog/double-counting-in-append-only-projections</link>
      <guid isPermaLink="true">https://phamkhanhminhman.com/blog/double-counting-in-append-only-projections</guid>
      <description>A polling sync detects changed records by updated_at and appends them to a history table. That works fine — until the upstream system is allowed to correct a record after the fact, and &apos;append&apos; quietly becomes &apos;add it again&apos;.</description>
      <category>Django / Idempotency</category>
      <pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Silent by design: why a missing await on run_in_executor survives review</title>
      <link>https://phamkhanhminhman.com/blog/silent-missing-await-run-in-executor</link>
      <guid isPermaLink="true">https://phamkhanhminhman.com/blog/silent-missing-await-run-in-executor</guid>
      <description>Forgetting await on a coroutine is loud. Forgetting it on run_in_executor is completely silent — and shared global state makes the bug heal itself after the first call, which is exactly why nobody catches it.</description>
      <category>Python / asyncio</category>
      <pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Handling Shopee and TikTok Shop webhook pushes safely</title>
      <link>https://phamkhanhminhman.com/blog/safe-webhook-handling</link>
      <guid isPermaLink="true">https://phamkhanhminhman.com/blog/safe-webhook-handling</guid>
      <description>HMAC signature verification fails for a reason that has nothing to do with the key or the algorithm: one JSON parse-and-reserialize step, done before signature check, is enough to change every byte.</description>
      <category>Security</category>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>
