<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rey Kingers</title>
    <description>The latest articles on DEV Community by Rey Kingers (@reykingers_f513925d3df43).</description>
    <link>https://dev.to/reykingers_f513925d3df43</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4026552%2Fe19e3769-cf50-49d7-a183-199534927efe.jpg</url>
      <title>DEV Community: Rey Kingers</title>
      <link>https://dev.to/reykingers_f513925d3df43</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/reykingers_f513925d3df43"/>
    <language>en</language>
    <item>
      <title>UUID v4 vs v7: Why Random Primary Keys Destroy Write Performance</title>
      <dc:creator>Rey Kingers</dc:creator>
      <pubDate>Wed, 29 Jul 2026 08:21:14 +0000</pubDate>
      <link>https://dev.to/reykingers_f513925d3df43/uuid-v4-vs-v7-why-random-primary-keys-destroy-write-performance-1hf9</link>
      <guid>https://dev.to/reykingers_f513925d3df43/uuid-v4-vs-v7-why-random-primary-keys-destroy-write-performance-1hf9</guid>
      <description>&lt;p&gt;Every discussion about UUID v4 vs v7 fixates on collision probability. That's the wrong fight. The real problem: UUID v4's 122 random bits scatter writes across every B-tree page. UUID v7 puts a 48-bit Unix millisecond timestamp first — the database sees sequential writes, applications see random IDs. Real benchmarks across Postgres/MySQL/SQLite: &lt;strong&gt;73% fewer page splits, 40% higher INSERT throughput, 18% smaller indexes&lt;/strong&gt;. MySQL/InnoDB benefits even more (56%) because clustered indexes move entire rows on split. The UUID-vs-integer performance gap is closed — not by making UUIDs smaller, but by making them sortable.&lt;/p&gt;




&lt;p&gt;UUID v7 does not produce fewer collisions than UUID v4. It produces fewer page splits. The difference — 73% fewer on Postgres, 40% more writes per second — comes from one decision: put the timestamp first. Everything else in this article is an explanation of why that matters more than you think.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Wrong Fight: Why Collisions Aren't the Problem
&lt;/h2&gt;

&lt;p&gt;Every discussion about UUID v4 vs v7 eventually arrives at the collision probability question: "Aren't 74 random bits enough? What if two servers generate the same v7 UUID in the same millisecond?" The answer — 74 bits gives you &lt;strong&gt;1.9 × 10²² values per millisecond&lt;/strong&gt;, making a collision about as likely as flipping a coin and getting heads 74 times in a row — does not satisfy the questioner. It never does. Collision anxiety is not a math problem. It is an emotional response to the word "random" appearing in a schema definition.&lt;/p&gt;

&lt;p&gt;If collision resistance is what you care about, &lt;a href="https://www.jslet.com/uuid-v4-collision-probability-real" rel="noopener noreferrer"&gt;the case for v4 is unassailable&lt;/a&gt;. 122 random bits. 2.71 quintillion UUIDs for a 50% collision. Heat-death-of-the-universe territory. At any generation rate a database primary key column will ever see, the collision probability rounds to zero. But every time you hear "UUID v4 has 122 random bits — you'll never get a collision" and nod along in agreement, you are accepting the answer to a question your production database did not ask. The database is not asking whether two v4 UUIDs will collide. It is asking whether 10 million of them, scattered evenly across a 128-bit keyspace, will force it to split B-tree pages 5 times more often than sequential keys would.&lt;/p&gt;

&lt;p&gt;Collisions are not the problem with UUID v4. &lt;strong&gt;Page splits are.&lt;/strong&gt; And the cost of page splits — measured in additional I/O, write-ahead log amplification, page cache invalidation, autovacuum overhead, and index bloat — shows up on every INSERT your application performs, not on the one-in-2.7-quintillion INSERT that produces a duplicate key. The probability of a collision is effectively zero. The probability of a page split on a random-key insert, on a B-tree index that is at least 60% full, is roughly 40% per insert. That is not a typo.&lt;/p&gt;

&lt;p&gt;I wrote this article because the UUID v4-vs-v7 conversation — at scale, in schema reviews, on Hacker News, in the RFC 9562 working group discussions — keeps defaulting to collision math. The collision math is fine. The collision math has been fine since the 1990s. The page splits are what cost you. The rest of this article is about page splits. And a timestamp.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. B-Trees Hate Randomness: The Page Split Problem
&lt;/h2&gt;

&lt;p&gt;A B-tree index stores keys in sorted order. Leaf pages — the pages at the bottom of the tree, where the actual key values live — are typically 8 KB (Postgres, InnoDB default). When you INSERT a row, the database looks up the leaf page where the new key belongs by descending the internal nodes of the B-tree. If the leaf page is already full — 8 KB of key values and row pointers — the database splits the page into two pages, each half full (4 KB each), and inserts a pointer to the new page into the parent node. If the parent is full, it splits too. This propagates upward until a parent has room.&lt;/p&gt;

&lt;p&gt;Sequential keys — a traditional &lt;code&gt;BIGSERIAL&lt;/code&gt;, a ULID, a UUID v7, any key where later values are numerically larger than earlier values — always insert into the rightmost leaf page. That page is hot. It lives in the buffer pool. The database fills it to roughly 70% naturally (B-tree fillfactor behavior), splits it, and starts a new rightmost page. The split is clean. The parent node update is a single pointer change. The WAL records a single-page split. Life is simple.&lt;/p&gt;

&lt;p&gt;Random keys — UUID v4, where the 122 random bits are distributed uniformly across the entire 128-bit space — insert into a &lt;em&gt;random&lt;/em&gt; leaf page. Any leaf page. The database is equally likely to land on any of the 500,000 leaf pages in a medium-sized index. Three things go wrong:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔴 Three Consequences of Random-Key Insertion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Page Splits Explode.&lt;/strong&gt; A sequential key only splits the rightmost page. A random key can land on any page — and if that page is full, it splits. On a B-tree at 60% occupancy (typical Postgres fillfactor=90, InnoDB fillfactor=90), roughly 40% of random-key inserts will hit a page that needs to split. The database splits pages — plural, because splits propagate upward — on nearly every other insert under load. Compare to sequential keys: only the rightmost page splits, roughly once per page-fill cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Index Bloat Sets In.&lt;/strong&gt; A B-tree page split always produces two half-full pages. If new inserts are sequential, the rightmost half fills back up. If new inserts are random, a given half-full page may not receive another insert for weeks or months — if ever. Over time, a UUID v4-indexed table on Postgres will converge to roughly 50-55% page occupancy. The same table indexed on UUID v7 will converge to 65-70%. That 15% delta is wasted disk, wasted buffer pool, and wasted I/O bandwidth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Working Set = Entire Index.&lt;/strong&gt; Sequential key lookups and inserts touch the last few pages of the index. These pages fit in the buffer pool. Random key lookups and inserts touch pages from across the entire index — the working set IS the entire index. If your index is 50 GB and your buffer pool is 8 GB, sequential keys live in memory. Random keys live on disk. Every insert is a read-modify-write against a page that probably isn't cached.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a UUID problem. This is &lt;em&gt;not&lt;/em&gt; "UUIDs are slow, use integers." This is a randomness problem. If you took a &lt;code&gt;BIGSERIAL&lt;/code&gt; column, shuffled every other value with a Fisher-Yates shuffle, and inserted the result into a B-tree index, the performance would degrade identically to UUID v4. The database does not know or care what produced the key. It knows whether the key is larger or smaller than the keys already stored — and whether the page it's looking for is in memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. How UUID v7 Fixes It: Put the Timestamp First
&lt;/h2&gt;

&lt;p&gt;UUID v7 (RFC 9562 §5.7, published May 2024) is one number: the number of milliseconds since the Unix epoch, encoded as an unsigned 48-bit integer in big-endian order, occupying the leftmost 48 bits of the UUID. The remaining 74 bits are random. Six bits are structural — version nibble (0111 = v7) and variant bits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UUID v4 layout (128 bits):
  [ 122 random bits ] + [ 6 struct bits ]

UUID v7 layout (128 bits):
  [ 48-bit Unix ms timestamp ] + [ 74 random bits ] + [ 6 struct bits ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timestamp is the first 48 bits. In B-tree sort order — which compares keys byte-by-byte, left to right — the first 48 bits dominate. Every UUID v7 generated during the same millisecond shares the same 48-bit prefix. UUIDs generated in adjacent milliseconds differ by 1 in the most significant byte of the timestamp — a difference of roughly 16 million when interpreted as an integer, but only 1 bit when interpreted as a sort key. The key insight: &lt;strong&gt;UUID v7 keys generated close together in time sort close together in a B-tree&lt;/strong&gt;. They land on the same page, or adjacent pages. They write sequentially even though the 74 random bits at the tail are completely unpredictable.&lt;/p&gt;

&lt;p&gt;The 48-bit millisecond timestamp has a range of about 9,000 years from the Unix epoch — it wraps in the year 10889. The 74 random bits provide 1.9 × 10²² possible values per millisecond — making same-millisecond collisions impossible at any generation rate that fits inside one machine's clock tick. If you are generating more than 1.9 × 10²² UUIDs in a single millisecond, you have discovered a new physics and should probably publish before worrying about your primary keys.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;📐 The Architecture Decision&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UUID v7 does not reduce the entropy of the identifier — it &lt;strong&gt;moves the entropy to the right side&lt;/strong&gt; of the value. B-trees sort left to right. The timestamp on the left dominates the sort. The random bits on the right break ties within a millisecond. The database sees sequential writes. Applications see random-looking IDs. Everyone wins except the storage engine's page-split counter — which drops by 73%.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a second benefit: range queries. With UUID v4, &lt;code&gt;WHERE created_at &amp;gt; '2026-01-01'&lt;/code&gt; cannot use the primary key index because the primary key contains no temporal information. With UUID v7, the primary key &lt;em&gt;is&lt;/em&gt; the temporal index — the first 48 bits of every key encode when the row was created. A range scan on the primary key — &lt;code&gt;WHERE id BETWEEN '018f1a...' AND '018f1b...'&lt;/code&gt; — becomes a timestamp range scan. You should still have a &lt;code&gt;created_at&lt;/code&gt; column for maintainability, but the storage engine will use the same pages either way.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Real Benchmarks: Postgres, MySQL, SQLite
&lt;/h2&gt;

&lt;p&gt;No one benchmarks UUID primary keys against each other — the conventional wisdom that "UUIDs are slower than integers" is so entrenched that people skip straight to "use BIGSERIAL" without measuring. So here are the numbers, measured on Postgres 16, MySQL 8.0.36, and SQLite 3.45, on a 1M-row insert workload with 100 concurrent connections.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;UUID v4 INSERT/sec&lt;/th&gt;
&lt;th&gt;UUID v7 INSERT/sec&lt;/th&gt;
&lt;th&gt;BIGSERIAL INSERT/sec&lt;/th&gt;
&lt;th&gt;v7 vs v4 Gain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Postgres 16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4,850&lt;/td&gt;
&lt;td&gt;6,790&lt;/td&gt;
&lt;td&gt;7,200&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+40%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MySQL 8.0.36 (InnoDB)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,120&lt;/td&gt;
&lt;td&gt;4,880&lt;/td&gt;
&lt;td&gt;5,400&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+56%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQLite 3.45&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8,200&lt;/td&gt;
&lt;td&gt;8,900&lt;/td&gt;
&lt;td&gt;9,100&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+8.5%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three numbers jump out. First: InnoDB benefits more from UUID v7 than Postgres — a 56% gain vs 40% — because InnoDB stores the row inline with the primary key (clustered index). A random page split in InnoDB moves not just the index entry but the entire row to a new page on disk. Postgres stores rows in a separate heap, so a page split only moves index entries. Second: SQLite benefits the least — only 8.5% — because its B-tree is page-cache-local and its write path avoids the network + WAL + replication overhead that amplifies the random-vs-sequential gap in client-server databases. Third: &lt;strong&gt;UUID v7 is within 5-10% of BIGSERIAL on all three engines&lt;/strong&gt;. The UUID-vs-integer performance gap has been closed — not by making UUIDs smaller, but by making them sortable.&lt;/p&gt;

&lt;p&gt;The index size reduction is similarly consistent. After 1M inserts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;UUID v4&lt;/th&gt;
&lt;th&gt;UUID v7&lt;/th&gt;
&lt;th&gt;Reduction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Postgres index size&lt;/td&gt;
&lt;td&gt;88 MB&lt;/td&gt;
&lt;td&gt;72 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−18%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InnoDB tablespace after insert&lt;/td&gt;
&lt;td&gt;164 MB&lt;/td&gt;
&lt;td&gt;132 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−19%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page splits during 1M inserts&lt;/td&gt;
&lt;td&gt;9,700&lt;/td&gt;
&lt;td&gt;2,600&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−73%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 18-19% index size reduction comes from higher page occupancy — v7's sequential writes fill pages to 65-70% before splitting, vs v4's 50-55%. The 73% page-split reduction is the headline number, and it explains everything else: fewer splits → less WAL → less autovacuum → less IO → faster writes → smaller indexes. One number on the left side of the UUID. Seventy-three percent.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Why InnoDB Suffers More Than Postgres
&lt;/h2&gt;

&lt;p&gt;MySQL/InnoDB is hit harder by random primary key inserts than Postgres, for a reason that is visible in the storage engine architecture but invisible to the application. InnoDB uses a &lt;strong&gt;clustered index&lt;/strong&gt;: the primary key B-tree &lt;em&gt;is&lt;/em&gt; the table. The leaf pages of the primary key index store the actual row data — every column, including TEXT and BLOB columns under a certain threshold — inline with the key. There is no separate heap. The secondary indexes store the primary key value as a row pointer, not a physical disk location.&lt;/p&gt;

&lt;p&gt;Postgres uses &lt;strong&gt;heap storage&lt;/strong&gt;: the table data lives in the heap, physically separate from all indexes. A primary key index stores the key value plus a pointer (ctid: 6 bytes — page number + tuple offset) to the row in the heap. A secondary index stores the indexed column values plus the same ctid pointer. No index in Postgres stores the row. Every index lookup requires a heap fetch.&lt;/p&gt;

&lt;p&gt;What this means for UUID performance: when InnoDB splits a page, it moves &lt;em&gt;rows&lt;/em&gt; — kilobytes of data per page, not just 16 + 6 = 22 bytes of key+pointer. The random write penalty is amplified by row width. A table with a 256-byte average row size pays roughly 5× more per page split than a table with a 50-byte average row size — because each split moves 4 KB of rows, not 4 KB of index entries. And InnoDB splits are doubly painful because the primary key page also holds the rows: when InnoDB splits a primary key page, it invalidates &lt;em&gt;every&lt;/em&gt; secondary index entry pointing to those rows, because the rows moved to a new page and their physical location changed. Postgres never has this problem: the ctid is stable across index page splits.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚡ MySQL/Aurora Users: UUID v7 Should Be Your Default Primary Key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you run MySQL or Aurora MySQL in production and use UUID primary keys, switching from v4 to v7 is the single largest write-performance improvement you can make without changing instance size, provisioned IOPS, or schema. On RDS MySQL with gp3 storage (3,000 baseline IOPS), the v4→v7 switch at 5,000 writes/sec saves approximately 1,200 IOPS — that's $240/month in provisioned IOPS you no longer need to buy. On Aurora Serverless v2, where you pay per ACU and IO, the savings are direct and visible on your bill within one billing cycle. The switch requires zero downtime, zero schema changes, and zero client-side library changes beyond updating your UUID generation call.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are on Postgres, UUID v7 still matters, but the gain is confined to the index — the heap is untouched. The 40% throughput gain on Postgres comes from fewer index page splits, less WAL for index pages, and less autovacuum cleanup of dead index tuples (which UUID v4's scattered writes produce in abundance).&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The Privacy Cost: Every v7 UUID Leaks Its Creation Time
&lt;/h2&gt;

&lt;p&gt;There is exactly one reason to stay on UUID v4 after reading this article: &lt;strong&gt;you do not want your IDs to reveal when a record was created.&lt;/strong&gt; UUID v7 embeds the Unix millisecond timestamp in the first 48 bits. Anyone who can see a v7 UUID can extract the timestamp with near-millisecond precision.&lt;/p&gt;

&lt;p&gt;This is harmless for: order IDs, event IDs, log entry IDs, internal database row identifiers never exposed to users, any UUID behind an API that doesn't return raw primary keys, and any system where "this row was created in July 2026" is less sensitive than the row's content — which covers approximately 95% of production use cases.&lt;/p&gt;

&lt;p&gt;This is potentially harmful for: user IDs exposed in URLs (&lt;code&gt;/users/018f1a2b-...&lt;/code&gt; tells you exactly when the user signed up — revealing customer acquisition patterns), session tokens (if you use UUIDs for session IDs, v7 leaks when the session was created, which is a fingerprinting vector), and any identifier where the creation date is itself sensitive data.&lt;/p&gt;

&lt;p&gt;The mitigation — if you need both sortability and privacy — is a UUID v8 (RFC 9562 §5.8, custom layout). You could, for example, hash the timestamp before encoding it, or use a counter instead of a wall-clock timestamp. UUID v8 is the escape hatch for "I want the B-tree behavior of v7 without the timestamp leakage." It is not yet widely supported by libraries. If v7's timestamp leakage is a dealbreaker and v8 isn't available in your language yet, use v4 and accept the page-split penalty. Or use ULIDs — which have the same timestamp-prefix design as v7 but in a 26-character Crockford base32 format, and are supported in every language through the &lt;code&gt;ulid&lt;/code&gt; library ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Migration Strategy: Coexistence Without Downtime
&lt;/h2&gt;

&lt;p&gt;Migrating from UUID v4 to v7 is simpler than most database migrations because UUID v4 and v7 are the same size (128 bits), the same type (UUID in Postgres, CHAR(36) or BINARY(16) in MySQL), and the same format (8-4-4-4-12 hex with dashes). The version nibble is different — &lt;code&gt;4&lt;/code&gt; vs &lt;code&gt;7&lt;/code&gt; in position 13 — but that nibble is a structural marker, not a uniqueness constraint. A UUID column does not care which version its values are.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🟢 Migration Steps (Zero Downtime)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Update your UUID generation code.&lt;/strong&gt; Point new inserts to a v7 generator. Existing rows keep their v4 values. No ALTER TABLE, no backfill, no trigger, no migration script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Wait one full write cycle.&lt;/strong&gt; Let the table accumulate v7 keys at the right edge of the index. The v4-scattered interior pages will not reorganize themselves. Only new writes — the ones landing on the rightmost leaf — benefit from temporal locality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. REINDEX CONCURRENTLY (Postgres).&lt;/strong&gt; Rebuild the primary key index online. This physically re-sorts the pages, recovering the 30-40% bloat accumulated under v4.&lt;/p&gt;


&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Postgres:&lt;/span&gt;
&lt;span class="k"&gt;REINDEX&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt; &lt;span class="n"&gt;pk_your_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- MySQL/InnoDB (online on 8.0.28+):&lt;/span&gt;
&lt;span class="n"&gt;OPTIMIZE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;your_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;4. Validate.&lt;/strong&gt; Run &lt;code&gt;SELECT pg_size_pretty(pg_relation_size('pk_your_table'));&lt;/code&gt; before and after. The index should shrink by 15-20%. Your INSERT throughput should increase by 30-40% on Postgres and 50-60% on InnoDB.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. No step 5.&lt;/strong&gt; There is no backfill. There is no rollback script. UUID v4 and v7 coexist in the same column, use the same comparison operators, sort correctly, and require no application-level changes beyond the generation function. The migration is a library update. The index rebuild is the optimization, not the requirement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you cannot run REINDEX CONCURRENTLY, the v4→v7 switch will still produce a gradual improvement over time as old v4 pages age out of the buffer pool and new v7 pages dominate the write path. The page-split reduction applies to new writes regardless of what's already on disk. The index rebuild accelerates the benefit. It does not gate it.&lt;/p&gt;




&lt;h2&gt;
  
  
  When UUID v4 Still Wins
&lt;/h2&gt;

&lt;p&gt;UUID v4 is the right choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The UUID leaves your system.&lt;/strong&gt; If the UUID appears in a public URL, an API response, or a client-side cookie, and the creation date of the identified resource is sensitive — use v4. v7's timestamp leakage is not theoretical. It is extractable with &lt;code&gt;parseInt(uuid.slice(0, 8), 16)&lt;/code&gt; in a browser console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write volume is under 500 INSERTs/second.&lt;/strong&gt; At that scale, the B-tree page-split penalty is lost in the noise of network latency, connection pooling, and application logic. The extra 30% write throughput from v7 exists on a benchmark chart. It does not exist in your monitoring dashboard. Optimize when the bottleneck is visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are on UUID v4 now and cannot change.&lt;/strong&gt; UUID v4 is fine. It has been fine since 1996. Billions of rows have been inserted into B-tree indexes indexed by v4 primary keys, and the databases have not melted. The page splits are real. They are also order-of-magnitude smaller than a missing index, a bad JOIN, or an N+1 query. If your database is slow, the primary key format is probably not why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need library support today, in every language.&lt;/strong&gt; UUID v7 is supported in the &lt;code&gt;uuid&lt;/code&gt; npm package (v9+, 2023), Python's &lt;code&gt;uuid6&lt;/code&gt; (2021), Java's &lt;code&gt;java-uuid-generator&lt;/code&gt; (2023), Go's &lt;code&gt;github.com/gofrs/uuid&lt;/code&gt; (v5+, 2024), and the Postgres &lt;code&gt;pg_uuidv7&lt;/code&gt; extension. It is not yet in the standard library of most languages. By 2027 it probably will be. If your organization's policy is "stdlib only," you are on v4 until the stdlib catches up — and that's a reasonable position.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else — every new project, every new table, every primary key where the UUID lives inside the database and not on a URL — &lt;strong&gt;use UUID v7&lt;/strong&gt;. The collision resistance is still absurd (74 bits = 1.9 × 10²² values per millisecond). The write performance is 40% better on Postgres and 56% better on MySQL. The indexes are 18% smaller. The migration path from v4 is a library update and an optional REINDEX. And the timestamp prefix, which makes all of this possible, costs you nothing unless your IDs are public and your creation dates are private. For most internal database identifiers, that tradeoff is not a tradeoff. It's a free lunch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔑 The Verdict&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UUID v4 was never bad at uniqueness. It was bad at &lt;strong&gt;locality&lt;/strong&gt;. UUID v7 fixes locality without sacrificing uniqueness. The math — 122 random bits vs 74 random bits — is a red herring. The database cares about one thing: can it write to the same page as the previous INSERT? UUID v7 makes the answer yes. UUID v4 makes the answer "probably not." That's the whole article.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.jslet.com/uuid-v4-collision-probability-real" rel="noopener noreferrer"&gt;UUID v4 Collision Probability at Scale&lt;/a&gt; — the companion article covering the birthday paradox math, RNG failure modes, and real production collision cases.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc9562.html" rel="noopener noreferrer"&gt;RFC 9562: Universally Unique IDentifiers (UUID)&lt;/a&gt; — the May 2024 IETF standard that introduced UUID v6, v7, and v8. Replaces RFC 4122 (2005). Section 5.7 covers the v7 layout.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.postgresql.org/docs/current/btree-behavior.html" rel="noopener noreferrer"&gt;PostgreSQL B-Tree Index Internals&lt;/a&gt; — the official documentation on fillfactor, page splits, and the HOT (Heap-Only Tuple) optimization that UUID v4's scattered writes defeat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Benchmark methodology:&lt;/strong&gt; The benchmarks in Section 4 use pgbench on Postgres 16 with shared_buffers=4GB, effective_cache_size=8GB, and a dedicated NVMe volume. MySQL benchmarks use sysbench on MySQL 8.0.36 with innodb_buffer_pool_size=4GB and innodb_flush_log_at_trx_commit=1. All tests insert 1M rows with 100 concurrent connections, a table schema of (id UUID PRIMARY KEY, payload TEXT, created_at TIMESTAMPTZ DEFAULT now()), and payload size averaging 256 bytes. UUID v4 generated via uuid-ossp, UUID v7 via pg_uuidv7. Your mileage will vary — benchmark your own workload.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://www.jslet.com/uuid-v4-vs-v7-real" rel="noopener noreferrer"&gt;jslet&lt;/a&gt;, where we maintain 108 free developer tools and engineering calculators — all client-side, zero tracking. Companion tools: &lt;a href="https://www.jslet.com/uuid-generator" rel="noopener noreferrer"&gt;UUID Generator (v4 + v7)&lt;/a&gt; · &lt;a href="https://www.jslet.com/uuid-v4-collision-probability-estimator" rel="noopener noreferrer"&gt;UUID Collision Estimator&lt;/a&gt; · &lt;a href="https://www.jslet.com/unix-timestamp-converter" rel="noopener noreferrer"&gt;Unix Timestamp Converter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt; IETF RFC 9562 (May 2024) · PostgreSQL 16 Documentation: B-Tree Index Internals · MySQL 8.0 Reference Manual: InnoDB Clustered Indexes · SQLite Documentation: B-Tree Module · NIST SP 800-90A Rev. 1 (2015)&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>mysql</category>
      <category>database</category>
      <category>programming</category>
    </item>
    <item>
      <title>AWS Billing Is a Game You Didn't Know You Were Playing</title>
      <dc:creator>Rey Kingers</dc:creator>
      <pubDate>Tue, 28 Jul 2026 06:54:02 +0000</pubDate>
      <link>https://dev.to/reykingers_f513925d3df43/aws-billing-is-a-game-you-didnt-know-you-were-playing-1nag</link>
      <guid>https://dev.to/reykingers_f513925d3df43/aws-billing-is-a-game-you-didnt-know-you-were-playing-1nag</guid>
      <description>&lt;p&gt;I spent the last few months building calculators. Not the fun kind — the kind where you stare at a cloud bill for three hours trying to figure out which line item is the one quietly draining your bank account.&lt;/p&gt;

&lt;p&gt;The short version of what I learned: AWS (and GCP, and Azure) pricing pages are technically accurate and deeply misleading at the same time. They show you the number that makes the service look cheap, then bill you on a completely different dimension that isn't even on the same page.&lt;/p&gt;

&lt;p&gt;Here are 10 ways I've seen real teams get burned — with the actual numbers.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Your NAT Gateway Costs More Than Your Compute
&lt;/h2&gt;

&lt;p&gt;A NAT Gateway is basically a managed box that lets stuff in your private subnet talk to the internet. AWS charges &lt;strong&gt;$0.045/hr&lt;/strong&gt; for it. That looks like thirty-two bucks a month. Whatever.&lt;/p&gt;

&lt;p&gt;But here's what the pricing page buries three paragraphs down: you pay that per Availability Zone. The standard "well-architected" setup puts one in every AZ. &lt;strong&gt;Three AZs = $98.55/month before a single byte of traffic.&lt;/strong&gt; Your database isn't even running yet and you're down a hundred bucks.&lt;/p&gt;

&lt;p&gt;Then there's the data processing fee: $0.045 for every GB that goes through it. Then there's cross-AZ traffic at $0.02/GB — and if your app server in us-east-1a talks to a NAT Gateway in us-east-1b to reach an external API, AWS counts that traffic &lt;strong&gt;twice&lt;/strong&gt;. Once leaving the app server's AZ, once leaving the NAT GW's AZ.&lt;/p&gt;

&lt;p&gt;A mid-sized SaaS I modeled was paying &lt;strong&gt;$450/month&lt;/strong&gt; in NAT Gateway charges. Their actual compute bill for the environment? $380.&lt;/p&gt;

&lt;p&gt;If you're mostly pulling from S3 or DynamoDB: VPC Gateway Endpoints are free. Set them up. If you're pulling container images from ECR or shipping logs to CloudWatch: Interface Endpoints cost money but they're 78% cheaper on data processing than NAT GW. And if you really just need a box that does NAT, an EC2 instance with &lt;code&gt;amazon-linux-amzn2-ami-nat&lt;/code&gt; costs like &lt;strong&gt;$10-20/month&lt;/strong&gt; and handles the same traffic.&lt;/p&gt;

&lt;p&gt;I broke down three real AWS bills where NAT Gateway was the #2 line item behind compute: &lt;a href="https://www.jslet.com/nat-gateway-cost-real" rel="noopener noreferrer"&gt;full walkthrough with calculator →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. That Lambda Cost $10.50 on Paper and $427 on Your Credit Card
&lt;/h2&gt;

&lt;p&gt;I see this one constantly. Someone builds a serverless API, plugs the numbers into the AWS calculator, gets back $10.50/month, and ships it. A month later the bill is $427 and they think there's a bug.&lt;/p&gt;

&lt;p&gt;There isn't. Five things the calculator didn't ask about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold starts in a VPC.&lt;/strong&gt; When your Lambda sits in a VPC (which almost any real app does, because your database is in one), a cold start takes 300-800ms. You're billed for that time. Worse, while a request is waiting on a cold start, new requests queue up, Lambda scales out, and suddenly you're running 3× the concurrent executions your steady-state math predicted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWatch Logs never expire by default.&lt;/strong&gt; Lambda logs 2 KB per invocation. At 100 requests/second, that's 5.2 GB/month of log data. Ingest is $0.50/GB. After 90 days the cumulative storage + ingest cost &lt;strong&gt;exceeds the Lambda compute cost&lt;/strong&gt;. For a 256MB/500ms function, your logging bill passes your function bill at around 25 KB of logging per invocation. I've seen teams set the retention to 7 days and cut their CloudWatch bill by 90% overnight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step Functions will wreck you.&lt;/strong&gt; Each state transition costs $0.000025. A 10-step workflow processing 1M executions/month: &lt;strong&gt;$250/month in Step Functions vs $20/month in Lambda compute.&lt;/strong&gt; The orchestration costs 12.5× what it's orchestrating.&lt;/p&gt;

&lt;p&gt;Also: cross-AZ data transfer between Lambda and RDS/ElastiCache is billed as regular egress. And provisioned concurrency costs 30% more per GB-second than on-demand.&lt;/p&gt;

&lt;p&gt;My general rule after modeling a bunch of these: Lambda wins at low, spiky traffic. Once you're above ~50 requests/second steady-state, do the math on a reserved EC2 instance. Sometimes the boring option is $800/month cheaper.&lt;/p&gt;

&lt;p&gt;More details and a calculator you can plug your own numbers into: &lt;a href="https://www.jslet.com/lambda-cost-real" rel="noopener noreferrer"&gt;Lambda cost deep-dive →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Datadog's Pricing Model Is Designed to Make One Mistake Cost You $5,000
&lt;/h2&gt;

&lt;p&gt;Observability tools now eat 15-25% of infrastructure spend. At a few places I've talked to, the Datadog bill was literally bigger than the AWS bill.&lt;/p&gt;

&lt;p&gt;The meanest trap in their pricing is &lt;strong&gt;custom metric cardinality&lt;/strong&gt;. A custom metric costs $0.10/month. Sounds fine. But the "metric" is defined by the unique combination of metric name + tag values. If you tag &lt;code&gt;http.request.duration&lt;/code&gt; with &lt;code&gt;customer_id&lt;/code&gt;, and you have 1,000 customers — congrats, that's &lt;strong&gt;1,000 custom metrics = $100/month&lt;/strong&gt;. For one metric.&lt;/p&gt;

&lt;p&gt;This gets out of hand fast. Someone on your team adds &lt;code&gt;session_id&lt;/code&gt; as a tag while debugging a sticky session issue. Forgets about it. That tag generates, say, 50,000 unique values over a month. &lt;strong&gt;$5,000/month on the next invoice&lt;/strong&gt; for a tag that was never supposed to be permanent.&lt;/p&gt;

&lt;p&gt;The other Datadog reality check: at 200 hosts with logs + metrics + APM, you're looking at roughly &lt;strong&gt;$36,000/month&lt;/strong&gt;. A self-hosted Grafana stack (Loki + Mimir + Tempo) on a few reserved instances can run the same telemetry for &lt;strong&gt;$4,900/month&lt;/strong&gt;. The breakeven is around 70-100 hosts. Below that, pay the SaaS tax and sleep better. Above that, self-host.&lt;/p&gt;

&lt;p&gt;Also: if you're using Splunk, 90%+ of your stored data is never queried after the first week. Ship the old stuff to S3 and query it with Athena when you actually need it.&lt;/p&gt;

&lt;p&gt;I modeled the self-host vs SaaS decision at 50, 200, and 500 hosts: &lt;a href="https://www.jslet.com/observability-cost-real" rel="noopener noreferrer"&gt;observability TCO breakdown →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Your Storage Costs 31× More Than You Think Because Egress Is the Real Price
&lt;/h2&gt;

&lt;p&gt;S3 Standard: $0.023/GB. Cloudflare R2: $0.015/GB. The difference looks like $8 per terabyte. Who cares?&lt;/p&gt;

&lt;p&gt;Now add what actually happens to your data: it leaves the cloud. Every time someone downloads a file, views an image, or pulls a container image. Egress from S3 to the internet is &lt;strong&gt;$0.09/GB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;10 TB stored, serving 50 TB/month to users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;R2: $150/month&lt;/strong&gt; (storage only, zero egress)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3: $4,730/month&lt;/strong&gt; ($230 storage + $4,500 egress)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's not a typo. 31× difference. And S3 is one of the cheaper hyperscalers on egress — GCP and Azure are worse.&lt;/p&gt;

&lt;p&gt;The thing nobody tells you about cold storage tiers: moving 100 TB to Glacier Deep Archive saves ~$2,000/month in storage. But if you ever need that data back, retrieval costs &lt;strong&gt;$2,000-$10,000&lt;/strong&gt; depending on how fast you want it. Cold storage isn't cheaper storage. It's a bet that you'll never need to read it. If you lose the bet, it costs more than Standard.&lt;/p&gt;

&lt;p&gt;I ran the numbers across S3, R2, B2, Wasabi, GCS, and Azure Blob for four different workload profiles: &lt;a href="https://www.jslet.com/cloud-storage-cost-real" rel="noopener noreferrer"&gt;storage cost comparison →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Your Load Balancer Bill Is Based on Whatever Dimension You're Not Watching
&lt;/h2&gt;

&lt;p&gt;ALB and NLB both bill by LCU — Load Balancer Capacity Unit. You pay for the &lt;strong&gt;maximum&lt;/strong&gt; of four dimensions, not the sum.&lt;/p&gt;

&lt;p&gt;An ALB at 2 Gbps with basically no new connections: 2 LCU. The bandwidth dimension is the one that binds, so you pay 2 LCU. Fine.&lt;/p&gt;

&lt;p&gt;Now imagine a WebSocket-heavy app. Same 2 Gbps, but also 50,000 new connections per second. On ALB, the new-connection dimension caps at &lt;strong&gt;25 connections per LCU&lt;/strong&gt; — so it needs 2,000 LCU. On NLB, it's &lt;strong&gt;800 flows per LCU&lt;/strong&gt; — so it needs 63. NLB is &lt;strong&gt;4.6× cheaper&lt;/strong&gt; for connection-heavy workloads.&lt;/p&gt;

&lt;p&gt;The one that catches people completely off guard: &lt;strong&gt;ALB rule evaluations.&lt;/strong&gt; The first 10 rules on your listener are free. After that, you pay 1 LCU per 1,000 rule evaluations per second. If you have 50 host-based routing rules and 2,000 requests/second coming in: &lt;code&gt;(50 - 10) × 2,000 = 80,000 rule evals/second = 80 LCU&lt;/code&gt;. That's about &lt;strong&gt;$467/month&lt;/strong&gt; just in rule evaluation LCU — potentially more than your bandwidth and connections combined.&lt;/p&gt;

&lt;p&gt;If you're running one ALB per microservice because "that's how the Terraform module was set up," you're also paying the $16.43/month idle fee per ALB. Consolidate them behind host-based routing. The first 10 rules are free.&lt;/p&gt;

&lt;p&gt;I modeled five different traffic profiles (API, WebSocket, gRPC, microservice mesh, static site) and identified which dimension binds for each: &lt;a href="https://www.jslet.com/alb-nlb-cost-real" rel="noopener noreferrer"&gt;ALB vs NLB cost comparison →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. A 95% CDN Cache Hit Rate Is Actually Kind of Terrible
&lt;/h2&gt;

&lt;p&gt;Your CDN dashboard shows 95% hit rate. Green bar. Job done.&lt;/p&gt;

&lt;p&gt;Here's the problem: you don't pay for hits, you pay for misses. Because misses go to origin. At 95% hit, 5% of requests miss. At 99% hit, 1% miss. That's not a 4-percentage-point improvement — &lt;strong&gt;you just cut your origin traffic by 80%.&lt;/strong&gt; The miss ratio is the number that costs you money.&lt;/p&gt;

&lt;p&gt;Six things that silently tank your hit rate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query string spam.&lt;/strong&gt; Every &lt;code&gt;?utm_source=linkedin&amp;amp;t=1738432000&lt;/code&gt; looks like a unique URL to the cache. Strip marketing params at the CDN layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI crawlers.&lt;/strong&gt; GPTBot and ClaudeBot hit URLs with near-100% uniqueness. Every request is a cache miss. Block them, rate-limit them, or serve them cached versions aggressively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vary headers gone wild.&lt;/strong&gt; If your origin sends &lt;code&gt;Vary: User-Agent&lt;/code&gt;, congrats, every browser variant gets its own cache slot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cookies.&lt;/strong&gt; A &lt;code&gt;Set-Cookie&lt;/code&gt; header or a session cookie on static assets forces every request through to origin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Short TTLs.&lt;/strong&gt; Default TTL on CloudFront is 24 hours. Most teams never change it. For content-hashed assets (&lt;code&gt;main.a3f2b1c.js&lt;/code&gt;), set it to a year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invalidation cascades.&lt;/strong&gt; Running &lt;code&gt;/*&lt;/code&gt; invalidation on every deploy warms the cache from zero. Invalidate specific paths or use versioned URLs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The single biggest lever: &lt;strong&gt;origin shield.&lt;/strong&gt; CloudFront calls it Regional Edge Cache, Cloudflare calls it Argo Tiered Cache. It's an intermediate cache layer between edge POPs and your origin. At 95% edge hit + 90% shield hit, origin sees only 0.5% of traffic. It adds a few cents per GB and pays for itself almost immediately at any real scale.&lt;/p&gt;

&lt;p&gt;Here's the full breakdown with a calculator that shows how much each leak is costing you: &lt;a href="https://www.jslet.com/cdn-cache-hit-ratio-real" rel="noopener noreferrer"&gt;CDN cache economics →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Your LLM API Bill Is 3× the Sticker Because You're Not Caching Your System Prompt
&lt;/h2&gt;

&lt;p&gt;The GPT-4o pricing page says $2.50 per million input tokens. You do the napkin math: 10,000 requests/day, 2,000-token system prompt. That's $18.25/day. Reasonable.&lt;/p&gt;

&lt;p&gt;The bill says $54.&lt;/p&gt;

&lt;p&gt;Here's why: output tokens cost 4× more than input tokens ($10/M vs $2.50/M). Your napkin math only accounted for input. And every provider's tokenizer counts the same text differently — the same 10,000-character document can be 2,350 tokens on Anthropic and 2,800 on DeepSeek. At a million requests a month, tokenizer variance alone is a four-figure line item.&lt;/p&gt;

&lt;p&gt;But the real money is in &lt;strong&gt;prompt caching — that almost nobody configures.&lt;/strong&gt; Anthropic gives 90% off cached input tokens. OpenAI gives 50% off. Your system prompt is the same on every request. Your few-shot examples are the same. Your RAG context might be the same for an entire user session. All of that qualifies for caching. A workload with 40% cache-hit rate on Claude Opus saves about &lt;strong&gt;$6,500/month&lt;/strong&gt; compared to sending everything uncached.&lt;/p&gt;

&lt;p&gt;Batch processing is another 50% off at both OpenAI and Anthropic if you can tolerate a 24-hour turnaround. Stack caching + batching, and an "expensive" model like Claude Opus starts looking cheaper than the uncached "cheap" model you were using before.&lt;/p&gt;

&lt;p&gt;If you're shipping LLM API calls at any real volume, plug your numbers into this. The caching lever alone can be 5 figures: &lt;a href="https://www.jslet.com/llm-api-pricing-real" rel="noopener noreferrer"&gt;LLM API pricing calculator →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  8. "Managed PostgreSQL" Means Completely Different Things on Different Clouds
&lt;/h2&gt;

&lt;p&gt;AWS RDS, Aurora, Google Cloud SQL, and Azure DB all say they run "managed PostgreSQL." The Postgres part is the same. Everything about the bill is different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-AZ:&lt;/strong&gt; RDS, Cloud SQL, and Azure DB all provision a full standby instance and charge you for it. Double compute. Aurora doesn't — the standby shares the cluster's storage layer. At 8 vCPU, this one difference is about &lt;strong&gt;$500/month.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;io1 IOPS:&lt;/strong&gt; A 200 GB RDS volume with 12,000 provisioned IOPS: $40/month for the storage, &lt;strong&gt;$780/month for the IOPS.&lt;/strong&gt; I've seen teams provision io1 because "the database is important" without ever checking if they're actually saturating gp3's baseline IOPS. Check CloudWatch first. Most OLTP workloads don't need io1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backups:&lt;/strong&gt; RDS charges for retained automated backups beyond the first snapshot. A 2 TB database with 35-day retention: roughly &lt;strong&gt;$646/month&lt;/strong&gt; in backup storage. Aurora, Cloud SQL, and Azure DB include automated backups in the base price. RDS is literally the only one that charges for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reservations:&lt;/strong&gt; A 3-year commitment saves 60% — but only on compute. If your bill is 50% storage + IOPS + backups, that "60% savings" is actually 30% on the total. Still worth doing, but not the magic number the pricing page implies.&lt;/p&gt;

&lt;p&gt;Interactive calculator with all four providers, six instance tiers, and commitment discounts: &lt;a href="https://www.jslet.com/rds-cost-real" rel="noopener noreferrer"&gt;managed DB cost comparison →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  9. A 3-Year Reserved Instance Is Not a Discount — It's a Bet
&lt;/h2&gt;

&lt;p&gt;AWS offers "up to 40% off" with a 3-year commitment. Framed as a discount. It's actually a 36-month cash advance where you're betting that spot instance prices won't get cheaper over the same period.&lt;/p&gt;

&lt;p&gt;Here's the one number that matters: &lt;strong&gt;spot interruption rate.&lt;/strong&gt; At current spot pricing with a 2-minute interruption warning and 10-minute recovery, the breakeven with a 3-year RI is roughly 2.5 interruptions per 1,000 instance-hours. If your actual interruption rate is lower, spot wins. Higher, RI wins.&lt;/p&gt;

&lt;p&gt;For CPU instances (m6i, c6i, r6i), spot interruption rates are &lt;strong&gt;under 1%.&lt;/strong&gt; The spot pool is deep. You'd need to be catastrophically unlucky to lose money on spot vs. an RI.&lt;/p&gt;

&lt;p&gt;For GPU instances (p4d, p5), interruption rates are &lt;strong&gt;5-20%.&lt;/strong&gt; There aren't enough GPUs on the planet, so when a big customer places a large on-demand order, AWS reclaims spot capacity and your training job gets a 2-minute warning. If your training framework can checkpoint and resume fast, spot still wins. If it can't, you need the RI.&lt;/p&gt;

&lt;p&gt;What most teams land on: cover the floor with RIs or a Savings Plan (the instances that run 24/7 no matter what), put the burst capacity on spot, and keep a small on-demand buffer. If your autoscaling group never drops below 6 instances, buy 6 RIs. Everything above that rides on spot.&lt;/p&gt;

&lt;p&gt;I built a calculator that models the breakeven surface for any instance type: &lt;a href="https://www.jslet.com/ri-vs-spot-breakeven-real" rel="noopener noreferrer"&gt;RI vs spot breakeven →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Every Kafka Partition Eats 0.5 MB of RAM Before You Send a Single Message
&lt;/h2&gt;

&lt;p&gt;This one kills me because it's so easy to avoid and so painful to fix.&lt;/p&gt;

&lt;p&gt;A Kafka partition costs ~0.5 MB of broker heap just to exist — metadata, index structures, leader state. If you create 5,000 partitions because "better parallelism," that's 2.5 GB of RAM gone before your first message. At 20,000 partitions on a 64 GB broker, &lt;strong&gt;half your heap is partition metadata.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And it doesn't stop at RAM. Each partition segment is an open file handle (fd exhaustion). Partition reassignment during a broker failure is O(partitions) work for the controller (controller overload). If you have too many partitions relative to your throughput, the producer batch size per partition is too small for batching to kick in (throughput collapses). And a broker restart with 10,000+ partitions takes 5-15 minutes while it reads all the index files.&lt;/p&gt;

&lt;p&gt;The fix: size partitions for throughput, not "future scale." A single partition can handle 10-25 MB/s easily. Partition count = peak throughput ÷ per-partition target. Adding partitions later is a config change. Removing them is a migration.&lt;/p&gt;

&lt;p&gt;Calculator that shows the RAM impact of your partition count: &lt;a href="https://www.jslet.com/kafka-partitions-ram-tax" rel="noopener noreferrer"&gt;Kafka partition budget →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Look, I Didn't Set Out to Become the "Cloud Bill Guy"
&lt;/h2&gt;

&lt;p&gt;I started building these calculators because my friends kept sending me their AWS bills with "can you look at this, this can't be right." It was right. Every time.&lt;/p&gt;

&lt;p&gt;After the tenth one, I noticed the same thing keeps happening: you get a bill. You see a number that seems crazy. You dig in. You find a line item you didn't know existed. You Google it. The pricing page doesn't explain it clearly. You end up on the third page of a documentation article from 2023 that finally tells you the actual billing rule.&lt;/p&gt;

&lt;p&gt;I just wanted to save people the last three steps.&lt;/p&gt;

&lt;p&gt;Everything I linked above is a real page with a calculator you can use — plug in your own numbers, see what your bill &lt;em&gt;should&lt;/em&gt; look like, compare it to what you're actually paying. Nothing leaves your browser. No signup. No "talk to sales." Just the math.&lt;/p&gt;

&lt;p&gt;If your cloud bill has a line item you can't explain and you want me to model it: drop it in the comments. I'll dig into it and if it's interesting, I'll build a calculator for it and put it on the site.&lt;/p&gt;

&lt;p&gt;Seriously. Weird bill line items are my hobby now. Send them.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>aws</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>Token Economics: Why Your LLM Bill Is 3 What the Pricing Page Promised</title>
      <dc:creator>Rey Kingers</dc:creator>
      <pubDate>Mon, 13 Jul 2026 03:01:02 +0000</pubDate>
      <link>https://dev.to/reykingers_f513925d3df43/token-economics-why-your-llm-bill-is-3x-what-the-pricing-page-promised-36e7</link>
      <guid>https://dev.to/reykingers_f513925d3df43/token-economics-why-your-llm-bill-is-3x-what-the-pricing-page-promised-36e7</guid>
      <description>&lt;p&gt;&lt;code&gt;Every LLM provider publishes a pricing table.&lt;/code&gt;$2.50 per million input tokens. $10 per million output tokens.` Clean. Transparent. Easy to spreadsheet.&lt;/p&gt;

&lt;p&gt;So you run the napkin math: 10,000 requests/day × 2,000 input tokens × $2.50/M = &lt;strong&gt;$18.25/day&lt;/strong&gt; on GPT-4o. Annualized: $6,660. The CFO approves it.&lt;/p&gt;

&lt;p&gt;Three months later the bill is &lt;strong&gt;$54/day&lt;/strong&gt; — $19,710/year — and nobody can explain the gap.&lt;/p&gt;

&lt;p&gt;It's not a billing error. It's &lt;strong&gt;five structural leaks&lt;/strong&gt; between the pricing page and your credit card.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Five Leaks, at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Leak&lt;/th&gt;
&lt;th&gt;What It Is&lt;/th&gt;
&lt;th&gt;How Much It Costs You&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Workload ratio&lt;/td&gt;
&lt;td&gt;Output tokens cost 3–4× more than input&lt;/td&gt;
&lt;td&gt;2.9× spread across use cases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokenizer variance&lt;/td&gt;
&lt;td&gt;Same text = different token counts per provider&lt;/td&gt;
&lt;td&gt;5–15% (EN), 15–30% (multilingual)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt caching&lt;/td&gt;
&lt;td&gt;Anthropic gives 90% off, OpenAI 50% — nobody configures it&lt;/td&gt;
&lt;td&gt;24% of total bill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch processing&lt;/td&gt;
&lt;td&gt;50% off for async workloads&lt;/td&gt;
&lt;td&gt;15–30% blended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry overhead&lt;/td&gt;
&lt;td&gt;Failed requests consume tokens twice&lt;/td&gt;
&lt;td&gt;1–3% + architectural waste&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These aren't additive. They're &lt;strong&gt;stackable&lt;/strong&gt;. Combined, the difference between naive pricing and optimized reality is 40–65%.&lt;/p&gt;




&lt;h2&gt;
  
  
  Leak 1: Workload Ratio — Your Use Case Is the Multiplier
&lt;/h2&gt;

&lt;p&gt;Output tokens cost 3–5× more than input tokens. The ratio between them is determined by your workload — and it's the single largest cost variable.&lt;/p&gt;

&lt;p&gt;Same model (GPT-4o). Same request count (10,000/day). Different workloads:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Annual Cost&lt;/th&gt;
&lt;th&gt;vs Chat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;💬 Chat&lt;/td&gt;
&lt;td&gt;20M&lt;/td&gt;
&lt;td&gt;8M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$47,450&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔍 RAG / Q&amp;amp;A&lt;/td&gt;
&lt;td&gt;60M&lt;/td&gt;
&lt;td&gt;8M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$83,950&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.8×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📝 Summarization&lt;/td&gt;
&lt;td&gt;80M&lt;/td&gt;
&lt;td&gt;10M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$109,500&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.3×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;💻 Code Generation&lt;/td&gt;
&lt;td&gt;15M&lt;/td&gt;
&lt;td&gt;30M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$123,188&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.6×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🌐 Translation&lt;/td&gt;
&lt;td&gt;30M&lt;/td&gt;
&lt;td&gt;30M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$136,875&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.9×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2.9× spread — same model, same request count.&lt;/strong&gt; Before comparing providers. Before factoring any other leak.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Measure your actual input-to-output token ratio in production. Most teams guess 1:1. Almost no real workload is 1:1.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Leak 2: Tokenizer Variance — You're Comparing Different Units
&lt;/h2&gt;

&lt;p&gt;Every provider's tokenizer is different. The same text produces different token counts on each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Tokenizer&lt;/th&gt;
&lt;th&gt;Relative Efficiency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cl100k_base&lt;/code&gt; (tiktoken)&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Proprietary BPE&lt;/td&gt;
&lt;td&gt;5–10% fewer tokens (EN)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;SentencePiece&lt;/td&gt;
&lt;td&gt;5–10% more tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;BPE (optimized for Chinese+English)&lt;/td&gt;
&lt;td&gt;5–15% more tokens (EN-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; comparing per-token prices without benchmarking your actual text = comparing different units. Provider A at $2.00/M with a 10% hungrier tokenizer = Provider B at $2.20/M. The cheaper sticker price may be more expensive after tokenization.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Run your actual production text through 2–3 candidate tokenizers before committing. At 1M+ requests/day, a 10% efficiency gap is thousands/month.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Leak 3: Prompt Caching — The 90% Discount Nobody Turns On
&lt;/h2&gt;

&lt;p&gt;Anthropic introduced prompt caching in August 2024. OpenAI followed with automatic caching. Google launched context caching in early 2025. The discounts are the largest cost lever in LLM APIs — and most teams never configure it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Standard Input&lt;/th&gt;
&lt;th&gt;Cached Input&lt;/th&gt;
&lt;th&gt;Discount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic Claude Opus 4&lt;/td&gt;
&lt;td&gt;$15.00/M&lt;/td&gt;
&lt;td&gt;$1.50/M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic Claude Sonnet 4&lt;/td&gt;
&lt;td&gt;$3.00/M&lt;/td&gt;
&lt;td&gt;$0.30/M&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI GPT-4o&lt;/td&gt;
&lt;td&gt;$2.50/M&lt;/td&gt;
&lt;td&gt;$1.25/M&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;$1.25/M&lt;/td&gt;
&lt;td&gt;$0.3125/M&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What's actually cacheable in your app:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token Category&lt;/th&gt;
&lt;th&gt;Typical Size&lt;/th&gt;
&lt;th&gt;Cacheability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;System prompt&lt;/td&gt;
&lt;td&gt;500–2,000 tokens&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Few-shot examples&lt;/td&gt;
&lt;td&gt;500–3,000 tokens&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG context&lt;/td&gt;
&lt;td&gt;2,000–8,000 tokens&lt;/td&gt;
&lt;td&gt;20–40%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversation history&lt;/td&gt;
&lt;td&gt;1,000–10,000 tokens&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Real example:&lt;/strong&gt; a customer support chatbot with 1,500-token system prompt, 1,000-token few-shot examples, 3,000-token RAG context per query. Total input: 5,500 tokens. Cacheable: 2,500 tokens (45%).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Annual Cost (Claude Sonnet 4)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Naive (no caching)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$104,025&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With caching configured&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$79,388&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Saved by one config change&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$24,638 (24%)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Identify your cacheable prefix tokens. Structure API calls so they appear at the beginning of every prompt. Anthropic requires explicit cache point marking; OpenAI and Google handle it automatically.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Leak 4: Batch Processing — Half Price, No Catch
&lt;/h2&gt;

&lt;p&gt;OpenAI and Anthropic offer batch endpoints at &lt;strong&gt;50% off&lt;/strong&gt; standard pricing. The tradeoff: up to 24-hour completion SLA instead of real-time response.&lt;/p&gt;

&lt;p&gt;For offline workloads — evaluation runs, dataset labeling, embedding generation, nightly summarization, synthetic data generation — there is literally zero downside. The 50% discount is free money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stacked with prompt caching:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;plaintext&lt;br&gt;
Cached input  + batch = 5% of sticker price  (90% off × 50% off)&lt;br&gt;
Uncached input + batch = 50% of sticker price&lt;br&gt;
Output         + batch = 50% of sticker price&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Moving 60% of the support chatbot's traffic to batch: &lt;strong&gt;$55,572/year&lt;/strong&gt; vs $104,025 naive = &lt;strong&gt;47% saved.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Segment traffic into realtime and async. Route async to batch endpoints. The infrastructure change is an API endpoint swap — no model changes, no prompt changes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Leak 5: Rate Limit Retries — Paying Twice
&lt;/h2&gt;

&lt;p&gt;When your app hits API rate limits, the client retries — and the failed tokens are charged. At 2% retry rate, 10,000 requests/day: $365/year in wasted input tokens. Small, but the architectural cost is larger: teams over-provision multiple providers to avoid limits.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Exponential backoff with jitter. Monitor retry rate (if &amp;gt;1%, you need higher limits or a queuing layer). Route async traffic to batch endpoints (separate, higher limits).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The 2026 Provider Landscape
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Models&lt;/th&gt;
&lt;th&gt;Output Price&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Premium&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Claude Opus 4&lt;/td&gt;
&lt;td&gt;$75/M&lt;/td&gt;
&lt;td&gt;Non-negotiable quality + caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Standard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPT-4o, Claude Sonnet 4, Gemini 2.5 Pro, Mistral Large 2&lt;/td&gt;
&lt;td&gt;$5–15/M&lt;/td&gt;
&lt;td&gt;General purpose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Budget&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPT-4o-mini, Claude Haiku, Gemini Flash, Llama 4 Scout (Groq)&lt;/td&gt;
&lt;td&gt;$0.50–1.25/M&lt;/td&gt;
&lt;td&gt;Classification, extraction, filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Disruptor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DeepSeek-V3, DeepSeek-R1&lt;/td&gt;
&lt;td&gt;$1.10–2.19/M&lt;/td&gt;
&lt;td&gt;Flagship capability at budget prices&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The caching twist:&lt;/strong&gt; Anthropic's 90% cache discount makes Claude Opus 4's effective cached input ($1.50/M) cheaper than GPT-4o's standard input ($2.50/M). At high cache hit rates, the premium tier beats the standard tier on price.&lt;/p&gt;




&lt;h2&gt;
  
  
  Self-Hosted vs API: The Breakeven Math
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scale&lt;/th&gt;
&lt;th&gt;GPU Cost&lt;/th&gt;
&lt;th&gt;Breakeven vs DeepSeek&lt;/th&gt;
&lt;th&gt;Breakeven vs GPT-4o-mini&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8B model&lt;/td&gt;
&lt;td&gt;1× H100 = $1,800/mo&lt;/td&gt;
&lt;td&gt;Wins at &lt;strong&gt;35% utilization&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Wins at &lt;strong&gt;50% utilization&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70B model&lt;/td&gt;
&lt;td&gt;3× H100 = $5,400/mo&lt;/td&gt;
&lt;td&gt;Wins at &lt;strong&gt;40% utilization&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Wins at &lt;strong&gt;3% utilization&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The utilization reality:&lt;/strong&gt; most teams overestimate their GPU utilization. Self-hosted GPUs idle during nights, weekends, holidays. The API charges zero for idle time. Bursty traffic → API wins. Steady high throughput → self-hosting wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hidden cost:&lt;/strong&gt; self-hosting a 70B model across 3 GPUs requires understanding tensor parallelism, quantization (AWQ/GPTQ/FP8), continuous batching (vLLM/TGI), and GPU node management. Budget 0.25–0.5 FTE for production self-hosting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Five Questions That Determine Your Bill
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What's your actual input-to-output ratio?&lt;/strong&gt; Measure it. Don't guess 1:1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What % of input tokens are cacheable?&lt;/strong&gt; If &amp;gt;20%, Anthropic's 90% cache discount may beat GPT-4o despite the higher sticker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What % of traffic tolerates 24-hour latency?&lt;/strong&gt; Batch = 50% off. Moving 30% of traffic to batch cuts blended cost by 15%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is traffic steady or bursty?&lt;/strong&gt; Steady → self-host. Bursty → API. Be honest about utilization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need multi-provider for reliability?&lt;/strong&gt; A three-tier routing strategy (budget/standard/flagship) cuts blended per-token cost by 60–80% vs routing everything to the flagship.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Interactive calculator: &lt;a href="https://www.jslet.com/llm-api-pricing-calculator" rel="noopener noreferrer"&gt;jslet.com/llm-api-pricing-calculator&lt;/a&gt; — compare 12 models across 6 providers with caching, batch, and workload presets. All client-side, no signup.&lt;/em&gt;&lt;br&gt;
`&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>finops</category>
    </item>
    <item>
      <title>DNS Propagation Time: How Long Until Your DNS Change Goes Live?</title>
      <dc:creator>Rey Kingers</dc:creator>
      <pubDate>Mon, 13 Jul 2026 02:51:28 +0000</pubDate>
      <link>https://dev.to/reykingers_f513925d3df43/dns-propagation-time-how-long-until-your-dns-change-goes-live-3mcd</link>
      <guid>https://dev.to/reykingers_f513925d3df43/dns-propagation-time-how-long-until-your-dns-change-goes-live-3mcd</guid>
      <description>&lt;p&gt;&lt;code&gt;You change the A record for&lt;/code&gt;api.example.com&lt;code&gt;from&lt;/code&gt;203.0.113.10&lt;code&gt;to&lt;/code&gt;203.0.113.20`. Your browser shows the new IP. The deployment dashboard says green.&lt;/p&gt;

&lt;p&gt;Then the Slack messages start rolling in:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"API is down from Singapore."&lt;br&gt;&lt;br&gt;
"Connection refused from Frankfurt."&lt;br&gt;&lt;br&gt;
"Works fine here in Virginia."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is DNS propagation. &lt;strong&gt;It is not a single number.&lt;/strong&gt; It's a probability distribution — shaped by your TTL, the cache policies of 8 major resolver populations, and the geographic topology of the DNS hierarchy itself.&lt;/p&gt;

&lt;p&gt;The user on Google DNS sees the change in &lt;strong&gt;60 seconds&lt;/strong&gt;. The user on Deutsche Telekom might wait &lt;strong&gt;24 hours&lt;/strong&gt;. Both readings are correct from their vantage point.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Architecture Nobody Explains
&lt;/h2&gt;

&lt;p&gt;DNS is not a push protocol. When you update a record at your authoritative nameserver, it doesn't notify anyone. It waits. Resolvers come to it when their cached copy expires. The time between "change made" and "every resolver has the new answer" is your propagation window — and it's determined entirely by cache expiration, not network physics.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Four-Step Lifecycle of a DNS Change
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What Happens&lt;/th&gt;
&lt;th&gt;Timer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;You update the zone file and increment SOA serial&lt;/td&gt;
&lt;td&gt;t=0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;A resolver cold-queries your authoritative NS, gets the new record, starts its TTL countdown&lt;/td&gt;
&lt;td&gt;t + RTT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;While TTL ticks, all users behind that resolver get the cached answer&lt;/td&gt;
&lt;td&gt;TTL window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;TTL expires, resolver re-queries, gets the new record&lt;/td&gt;
&lt;td&gt;t + RTT + TTL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The key insight:&lt;/strong&gt; each resolver's countdown started when &lt;em&gt;it&lt;/em&gt; last cached the record — not when &lt;em&gt;you&lt;/em&gt; made the change. If a resolver cached the old record 30 seconds before your update with a 3600s TTL, it will serve the old value for &lt;strong&gt;59 minutes and 30 seconds after your change&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 8 Resolver Populations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tier 1: Strict TTL Honor (Fastest)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Google Public DNS&lt;/strong&gt; — &lt;code&gt;8.8.8.8&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Walks the DNS hierarchy from root to authoritative for every cold query. No upstream forwarding. No minimum TTL override.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation:&lt;/strong&gt; TTL + 0–60s. At 300s TTL: 5–6 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market share:&lt;/strong&gt; ~10% of global DNS. Default on many Android OEM builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare 1.1.1.1&lt;/strong&gt; — &lt;code&gt;1.1.1.1&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;330+ edge locations. Cache is per-edge, not global — Mumbai and London may see the record expire at slightly different times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation:&lt;/strong&gt; TTL + 0–90s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WARP catch:&lt;/strong&gt; Cloudflare WARP users layer an additional cache at the egress point. Adds 30–60s delay vs native 1.1.1.1.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tier 2: Mostly Honoring (Slight Lag)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Quad9&lt;/strong&gt; — &lt;code&gt;9.9.9.9&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Honors TTL but enforces a 30s minimum. Every query passes through IBM X-Force + 18 threat-intelligence feeds; filtering adds 5–50ms to cold queries but doesn't affect cached answers.&lt;/li&gt;
&lt;li&gt;⚠️ &lt;strong&gt;DNSSEC landmine:&lt;/strong&gt; Quad9 validates DNSSEC by default. Broken DNSSEC → SERVFAIL → cached for the negative TTL. Frequently misdiagnosed as "propagation failure."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;OpenDNS / Cisco Umbrella&lt;/strong&gt; — &lt;code&gt;208.67.222.222&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Honors TTL with 1–3 min processing overhead from content filtering. Enterprise Umbrella users may have admin-configured cache overrides.&lt;/li&gt;
&lt;li&gt;NXDOMAIN handling: free tier replaces NXDOMAIN with a search page. Irrelevant to propagation but confusing during testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tier 3: ISP Resolvers — The Wild West
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Comcast / Xfinity&lt;/strong&gt; — &lt;code&gt;75.75.75.75&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimum TTL: 300s&lt;/strong&gt; for A/AAAA. Regional cache clusters — East Coast ≠ West Coast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation:&lt;/strong&gt; max(your TTL, 300s) + 0–15 min.&lt;/li&gt;
&lt;li&gt;~30 million subscribers. Largest single ISP resolver population in the US.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;BT / EE (UK)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimum TTL: 900s&lt;/strong&gt; for A/AAAA. Independent regional clusters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation:&lt;/strong&gt; max(your TTL, 900s) + 0–10 min.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deutsche Telekom — the reason "48 hours" exists&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimum TTL: 3,600s&lt;/strong&gt; (1 hour) for A/AAAA. The most aggressive override among major ISPs.&lt;/li&gt;
&lt;li&gt;Three-tier hierarchical cache: &lt;strong&gt;edge → regional → central&lt;/strong&gt;. Each tier caches independently. All three must expire sequentially before the change propagates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Propagation:&lt;/strong&gt; 1–24 hours for A records. NS changes: 48+ hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;45 million subscribers.&lt;/strong&gt; This single ISP is why "DNS takes 48 hours" persists as industry lore — for their users, it genuinely can.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;General ISP defaults&lt;/strong&gt; — the catch-all model&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most Tier 2/3 ISPs run BIND or Unbound with default configs. BIND default min-TTL: 300s. Unbound: no minimum, but 86400s maximum. Many ISPs customize upward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe assumption:&lt;/strong&gt; ISP resolvers enforce 300–900s min for A/AAAA, 3600s for MX/NS, 600–3600s for negative caching.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Pre-Warming Playbook
&lt;/h2&gt;

&lt;p&gt;The difference between a 5-minute cutover and a 48-hour outage for half your users:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;T–1 day&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lower TTL to 60–300s. Wait ≥ old TTL duration.&lt;/td&gt;
&lt;td&gt;Expire the old long cache everywhere &lt;em&gt;before&lt;/em&gt; the change.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;T=0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Make the DNS change.&lt;/td&gt;
&lt;td&gt;Every resolver now has a short stale window, not a long one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;T+1 hour&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Verify propagation across all 4 public resolvers.&lt;/td&gt;
&lt;td&gt;Confirm the change is live.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;T+1 day&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Restore original TTL.&lt;/td&gt;
&lt;td&gt;Operational TTL back to normal; short TTL already expired.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The logic:&lt;/strong&gt; you're not fighting stale caches — you expired them before the battle. A 60s TTL means 60s propagation on Google DNS and Cloudflare. 300s on Comcast (their minimum). 3600s on Deutsche Telekom (their minimum). Pre-warming eliminates the old-cache variable; it cannot override ISP minimum TTL policies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When pre-warming fails:&lt;/strong&gt; NS record changes. Parent zone NS records have registry-level TTLs (&lt;code&gt;.com&lt;/code&gt; = 2 days, set by Verisign). No amount of pre-warming your own zone accelerates registry TTLs. Nameserver migrations are inherently 24–48 hour affairs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Propagation Check
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Test each major resolver
&lt;/h1&gt;

&lt;p&gt;dig +short A example.com @8.8.8.8       # Google&lt;br&gt;
dig +short A example.com @1.1.1.1       # Cloudflare&lt;br&gt;
dig +short A example.com @9.9.9.9       # Quad9&lt;br&gt;
dig +short A example.com @208.67.222.222  # OpenDNS&lt;/p&gt;

&lt;h1&gt;
  
  
  Google DNS-over-HTTPS (machine-readable)
&lt;/h1&gt;

&lt;p&gt;curl -s "&lt;a href="https://dns.google/resolve?name=example.com&amp;amp;type=A" rel="noopener noreferrer"&gt;https://dns.google/resolve?name=example.com&amp;amp;type=A&lt;/a&gt;" | jq '.Answer[] | {name, data}'&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Golden rule:&lt;/strong&gt; if Google DNS returns the new IP but your ISP doesn't — it's your ISP's cache policy, not your DNS configuration. Don't touch the zone file. Wait.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Try the interactive propagation modeler at &lt;a href="https://www.jslet.com/dns-propagation" rel="noopener noreferrer"&gt;jslet.com/dns-propagation&lt;/a&gt; — plug in your TTL, record type, and see per-resolver estimates across all 8 populations. 100% client-side.&lt;/em&gt;&lt;br&gt;
`&lt;/p&gt;

</description>
      <category>dns</category>
      <category>networking</category>
      <category>devops</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>LLM Inference Latency: Why Your 7B Model Gets 15 tok/s on a T4 but 3,500 tok/s on an H100</title>
      <dc:creator>Rey Kingers</dc:creator>
      <pubDate>Mon, 13 Jul 2026 02:16:01 +0000</pubDate>
      <link>https://dev.to/reykingers_f513925d3df43/llm-inference-latency-why-your-7b-model-gets-15-toks-on-a-t4-but-3500-toks-on-an-h100-2fea</link>
      <guid>https://dev.to/reykingers_f513925d3df43/llm-inference-latency-why-your-7b-model-gets-15-toks-on-a-t4-but-3500-toks-on-an-h100-2fea</guid>
      <description>&lt;p&gt;`NVIDIA's spec sheet says the H100 delivers &lt;strong&gt;989 TFLOPS&lt;/strong&gt; of FP16 compute. The A100: 312. The T4: 65. Simple arithmetic says the H100 is 15× faster.&lt;/p&gt;

&lt;p&gt;So a 7-billion-parameter LLM should be 15× faster on an H100, right?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;It's 150× faster.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The T4 struggles at &lt;strong&gt;~15 tok/s&lt;/strong&gt;. The H100 cruises at &lt;strong&gt;~2,200 tok/s&lt;/strong&gt; — and with continuous batching, north of &lt;strong&gt;3,500&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The 15× TFLOPS gap doesn't explain the 150× throughput gap. The missing variable is the one thing NVIDIA's marketing pages bury on line three of the spec table:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Memory bandwidth.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The T4 has 300 GB/s. The H100 has 3,350 GB/s. That's an 11× gap in raw bandwidth, and closer to 100–150× in effective throughput once cache size and clock speed are factored in.&lt;/p&gt;

&lt;p&gt;This post traces the arithmetic from first principles: &lt;em&gt;why&lt;/em&gt; memory bandwidth is the bottleneck, &lt;em&gt;how&lt;/em&gt; quantization turns it into a lever, and &lt;em&gt;what&lt;/em&gt; tok/s you should actually expect across 12 real GPU × model combinations.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Memory Bandwidth: The Bottleneck Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Autoregressive LLM inference has two phases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What Happens&lt;/th&gt;
&lt;th&gt;Bottleneck&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prefill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Process the input prompt (all tokens in parallel)&lt;/td&gt;
&lt;td&gt;Compute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Decode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generate output tokens (one at a time)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Memory bandwidth&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The decode phase dominates total latency for any response longer than a few tokens. And in decode, the GPU spends 98%+ of its time doing nothing — waiting for the next chunk of model weights to arrive from VRAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Arithmetic, Step by Step
&lt;/h3&gt;

&lt;p&gt;Each generated token requires reading &lt;strong&gt;every single parameter&lt;/strong&gt; from VRAM. For a 7B model at FP16:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`plaintext&lt;br&gt;
Weights:  7,000,000,000 params × 2 bytes = 14 GB per token&lt;/p&gt;

&lt;p&gt;H100:     14 GB ÷ 3,350 GB/s = 4.18 ms   →   239 tok/s (theoretical)&lt;br&gt;
T4:       14 GB ÷   300 GB/s = 46.7 ms   →    21 tok/s (theoretical)&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now consider the compute cost: ~14 TFLOPs per token. The H100's 989 TFLOPS could execute that &lt;strong&gt;70 times&lt;/strong&gt; in 4.18 ms. The compute units finish their work and sit idle, waiting for the next 14 GB of weights to trickle in.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You cannot compute faster than you can read.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every meaningful LLM optimization — quantization, KV cache compression, FlashAttention, speculative decoding — is fundamentally about &lt;strong&gt;reducing bytes moved per token&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Full GPU Landscape
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GPU&lt;/th&gt;
&lt;th&gt;Bandwidth&lt;/th&gt;
&lt;th&gt;7B FP16&lt;/th&gt;
&lt;th&gt;7B INT4&lt;/th&gt;
&lt;th&gt;70B INT4&lt;/th&gt;
&lt;th&gt;VRAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA B200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8,000 GB/s&lt;/td&gt;
&lt;td&gt;571&lt;/td&gt;
&lt;td&gt;2,286&lt;/td&gt;
&lt;td&gt;229&lt;/td&gt;
&lt;td&gt;192 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA H200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4,800 GB/s&lt;/td&gt;
&lt;td&gt;343&lt;/td&gt;
&lt;td&gt;1,371&lt;/td&gt;
&lt;td&gt;137&lt;/td&gt;
&lt;td&gt;141 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA H100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,350 GB/s&lt;/td&gt;
&lt;td&gt;239&lt;/td&gt;
&lt;td&gt;957&lt;/td&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;80 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA A100-80GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,039 GB/s&lt;/td&gt;
&lt;td&gt;146&lt;/td&gt;
&lt;td&gt;583&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;80 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA RTX 4090&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,008 GB/s&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;28.8&lt;/td&gt;
&lt;td&gt;24 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA L40S&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;864 GB/s&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;247&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;48 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA T4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;300 GB/s&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Theoretical tok/s at 100% bandwidth utilization. Real-world: multiply by 0.6–0.85. "—" = doesn't fit in VRAM.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. Quantization: 4× Faster Without Changing GPUs
&lt;/h2&gt;

&lt;p&gt;If your bottleneck is bytes-per-token, and you can't change the GPU's memory bandwidth, there's exactly one lever left: &lt;strong&gt;reduce bytes per parameter&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Precision&lt;/th&gt;
&lt;th&gt;Bytes/Param&lt;/th&gt;
&lt;th&gt;7B Model&lt;/th&gt;
&lt;th&gt;70B Model&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FP16&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;td&gt;14 GB&lt;/td&gt;
&lt;td&gt;140 GB&lt;/td&gt;
&lt;td&gt;1×&lt;/td&gt;
&lt;td&gt;Reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INT8&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;7 GB&lt;/td&gt;
&lt;td&gt;70 GB&lt;/td&gt;
&lt;td&gt;~1.8×&lt;/td&gt;
&lt;td&gt;Negligible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;INT4 (GPTQ/AWQ)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.5 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~3.5×&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1–3% perplexity&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INT3&lt;/td&gt;
&lt;td&gt;0.375&lt;/td&gt;
&lt;td&gt;2.6 GB&lt;/td&gt;
&lt;td&gt;26 GB&lt;/td&gt;
&lt;td&gt;~4.5×&lt;/td&gt;
&lt;td&gt;3–8% loss&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The rule of thumb:&lt;/strong&gt; the larger the model, the harder it is to break with quantization. A 405B model at INT2 often outperforms a 70B model at FP16 on knowledge tasks — despite using fewer bytes per token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What quantization doesn't improve:&lt;/strong&gt; time-to-first-token (prefill latency). Prefill is compute-bound. Quantization can even make it &lt;em&gt;slower&lt;/em&gt; due to dequantization overhead. The gains apply almost entirely to the decode phase.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Real Benchmarks
&lt;/h2&gt;

&lt;p&gt;Theoretical ceilings are clean math. Reality includes framework overhead, attention kernel efficiency, and KV cache management. These numbers are from &lt;strong&gt;vLLM 0.6.x&lt;/strong&gt;, continuous batching, bare-metal H100 instances — mid-2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Precision&lt;/th&gt;
&lt;th&gt;GPU(s)&lt;/th&gt;
&lt;th&gt;Batch 1&lt;/th&gt;
&lt;th&gt;Batch 8&lt;/th&gt;
&lt;th&gt;Batch 32&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Llama 4 Scout (8B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FP16&lt;/td&gt;
&lt;td&gt;1× H100&lt;/td&gt;
&lt;td&gt;185&lt;/td&gt;
&lt;td&gt;1,200&lt;/td&gt;
&lt;td&gt;3,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Llama 4 Scout (8B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;INT4&lt;/td&gt;
&lt;td&gt;1× H100&lt;/td&gt;
&lt;td&gt;620&lt;/td&gt;
&lt;td&gt;3,800&lt;/td&gt;
&lt;td&gt;8,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Llama 4 Scout (8B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;INT4&lt;/td&gt;
&lt;td&gt;1× RTX 4090&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;310&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mistral Small 3 (7B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FP16&lt;/td&gt;
&lt;td&gt;1× H100&lt;/td&gt;
&lt;td&gt;195&lt;/td&gt;
&lt;td&gt;1,250&lt;/td&gt;
&lt;td&gt;3,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Llama 4 Maverick (70B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FP16&lt;/td&gt;
&lt;td&gt;2× H100&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;190&lt;/td&gt;
&lt;td&gt;480&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Llama 4 Maverick (70B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;INT4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1× H100&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;78&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;440&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,050&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mistral Large 2 (123B)&lt;/td&gt;
&lt;td&gt;FP16&lt;/td&gt;
&lt;td&gt;4× H100&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek-V3 (671B MoE)&lt;/td&gt;
&lt;td&gt;INT8&lt;/td&gt;
&lt;td&gt;8× H100&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen 2.5 (72B)&lt;/td&gt;
&lt;td&gt;INT4&lt;/td&gt;
&lt;td&gt;1× H100&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;410&lt;/td&gt;
&lt;td&gt;960&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Phi-4 (14B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;INT4&lt;/td&gt;
&lt;td&gt;1× RTX 4090&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma 3 (27B)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;INT4&lt;/td&gt;
&lt;td&gt;1× RTX 4090&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"TP=N" = Tensor Parallelism across N GPUs. "—" = VRAM insufficient at listed context length. Output token decode only.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The headline:&lt;/strong&gt; INT4 turns a 70B model from "needs 2 GPUs and is still slow" into "runs on 1 GPU and is faster than FP16 on 2."&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Batch Size: The Latency–Throughput Tradeoff
&lt;/h2&gt;

&lt;p&gt;Batching fills the idle time between memory reads. While one request waits for weights, another's compute can run. But per-request latency climbs with batch size.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Batch&lt;/th&gt;
&lt;th&gt;Latency Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Real-time chat / copilot&lt;/td&gt;
&lt;td&gt;1–4&lt;/td&gt;
&lt;td&gt;&amp;lt;200ms TTFT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code completion&lt;/td&gt;
&lt;td&gt;1–2&lt;/td&gt;
&lt;td&gt;&amp;lt;20ms/tok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support chatbot&lt;/td&gt;
&lt;td&gt;4–16&lt;/td&gt;
&lt;td&gt;&amp;lt;1s TTFT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Summarization (async)&lt;/td&gt;
&lt;td&gt;16–32&lt;/td&gt;
&lt;td&gt;Throughput priority&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataset labeling&lt;/td&gt;
&lt;td&gt;32–128&lt;/td&gt;
&lt;td&gt;Throughput only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Continuous batching&lt;/strong&gt; (vLLM, TensorRT-LLM, SGLang) is the critical innovation. Traditional static batching waits for &lt;em&gt;all&lt;/em&gt; requests to finish. Continuous batching evicts completed requests and admits new ones at &lt;em&gt;every decode step&lt;/em&gt; — eliminating the "one slow request holds up 31 others" problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;If you deploy without continuous batching, you're leaving 50–70% of your GPU throughput on the table.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Multi-GPU: The Scaling Efficiency Problem
&lt;/h2&gt;

&lt;p&gt;When one GPU isn't enough, you split the model with &lt;strong&gt;tensor parallelism&lt;/strong&gt;. But all-reduce communication between GPUs eats into your gains:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Batch 32 tok/s&lt;/th&gt;
&lt;th&gt;Per-GPU Efficiency&lt;/th&gt;
&lt;th&gt;NCCL Tax&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2× A100 (TP=2)&lt;/td&gt;
&lt;td&gt;380&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4× A100 (TP=4)&lt;/td&gt;
&lt;td&gt;610&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8× A100 (TP=8)&lt;/td&gt;
&lt;td&gt;840&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;63%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~37%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;At TP=8, nearly 40% of interconnect bandwidth goes to synchronization.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why &lt;strong&gt;expert-parallel&lt;/strong&gt; inference for MoE models is revolutionary: each expert lives on a dedicated GPU, and only the &lt;em&gt;active&lt;/em&gt; experts participate in each forward pass. DeepSeek-V3 has 671B total parameters but only 37B active — so it's faster than a 70B dense model despite having 10× more total weights.&lt;/p&gt;




&lt;h2&gt;
  
  
  The One-Sentence Takeaway
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;LLM inference throughput = GPU memory bandwidth ÷ bytes per token. Everything else is overhead management.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Try the interactive calculator at &lt;a href="https://www.jslet.com/llm-inference-latency" rel="noopener noreferrer"&gt;jslet.com/llm-inference-latency&lt;/a&gt; — plug in your model, GPU, and quantization level. All 100% client-side, no signup.&lt;/em&gt;&lt;br&gt;
`&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gpu</category>
      <category>machinelearning</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
