<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Caching on WebDevStation</title>
    <link>https://webdevstation.com/tags/caching/</link>
    <description>4 articles tagged Caching — tutorials, code examples and notes from building real systems, newest first.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <managingEditor>Alex</managingEditor>
    <webMaster>Alex</webMaster>
    <copyright>© 2026 WebDevStation</copyright>
    <lastBuildDate>Mon, 31 Aug 2026 11:15:00 +0200</lastBuildDate>
    <atom:link href="https://webdevstation.com/tags/caching/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Prompt Caching: The Cheapest Win in Your LLM Bill</title>
      <link>https://webdevstation.com/posts/prompt-caching-llm-cost/</link>
      <pubDate>Mon, 31 Aug 2026 11:15:00 +0200</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/prompt-caching-llm-cost/</guid>
      <description>Prompt caching is a prefix match, and one stray timestamp can silently disable it. How cache breakpoints, TTLs and the usage fields actually work — and how to build…</description>
      <content:encoded><![CDATA[<p>I have written on this blog about caching database reads with <a href="/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/">Ristretto</a> and about teaching <a href="/posts/how-to-make-nginx-cookie-aware/">Nginx to cache by cookie</a>. Prompt caching belongs in the same family, with one difference that makes it far more interesting: the thing you are caching costs real money per byte, and when the cache stops working, nothing breaks. No error, no alert, no failed request. Just a bigger invoice next month.</p>
<h2 id="one-invariant-everything-follows-from-it">One Invariant, Everything Follows From It</h2>
<p><strong>Prompt caching is a prefix match. Any change anywhere in the prefix invalidates everything after it.</strong></p>
<p>That is the whole model. The cache key is derived from the exact bytes of your rendered prompt up to each breakpoint. One byte different at position N — a timestamp, a reordered JSON key, an extra tool in the list — and every cached position at or after N is gone.</p>
<p>The render order matters and is fixed:</p>
<pre tabindex="0"><code>tools  →  system  →  messages
</code></pre><p>Tools render first, at position zero. That has a consequence people discover the expensive way: <strong>change the tool list and you have invalidated everything</strong>, system prompt and entire conversation included. More on that below.</p>
<h2 id="what-it-costs">What It Costs</h2>
<p>Two numbers govern whether caching pays:</p>
<ul>
<li>A cache <strong>read</strong> costs about <strong>0.1×</strong> the base input price.</li>
<li>A cache <strong>write</strong> costs <strong>1.25×</strong> for the 5-minute TTL, <strong>2×</strong> for the 1-hour TTL.</li>
</ul>
<p>So with the default 5-minute TTL, two requests already break even: <code>1.25 + 0.1 = 1.35</code> against <code>2.0</code> uncached. By the third request you are well ahead. With the 1-hour TTL you need three requests to break even, because the write costs double.</p>
<p>Which is why the TTL question is <em>not</em> &ldquo;how long do I want this cached&rdquo; but &ldquo;how far apart do requests sharing this prefix start&rdquo;:</p>
<table>
	<thead>
			<tr>
					<th>Start-to-start gap</th>
					<th>Use</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Under 5 minutes</td>
					<td>5-minute TTL. Every read refreshes the timer, so continuous traffic keeps it warm indefinitely and it is strictly cheaper.</td>
			</tr>
			<tr>
					<td>5–60 minutes</td>
					<td>1-hour TTL. This is the only window where the doubled write price earns its keep.</td>
			</tr>
			<tr>
					<td>Over an hour</td>
					<td>Neither, directly. Re-warm on a schedule or accept the cold miss.</td>
			</tr>
	</tbody>
</table>
<p>The subtlety in row one: a read refreshes the entry at no extra cost, and the lifetime is measured from the <em>start</em> of the request. A four-minute generation leaves about one minute for the next request to begin before a five-minute entry expires. For a chat endpoint under steady load, the 5-minute TTL is the right answer and the 1-hour TTL just doubles your write bill.</p>
<h2 id="making-it-work-in-go">Making It Work in Go</h2>
<p>The syntax is a <code>CacheControl</code> on the last block of whatever you want cached. Because tools render before system, a marker on the final system block caches <strong>both</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">params</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">MessageNewParams</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Model</span>:     <span style="color:#e6db74">&#34;claude-opus-5&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">MaxTokens</span>: <span style="color:#ae81ff">16000</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Tools</span>:     <span style="color:#a6e22e">tools</span>, <span style="color:#75715e">// deterministic order — see below</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">System</span>: []<span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">TextBlockParam</span>{{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Text</span>:         <span style="color:#a6e22e">systemPrompt</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">CacheControl</span>: <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">NewCacheControlEphemeralParam</span>(), <span style="color:#75715e">// 5-minute default</span>
</span></span><span style="display:flex;"><span>    }},
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Messages</span>: <span style="color:#a6e22e">messages</span>,
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>For the 1-hour TTL:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">CacheControl</span>: <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">CacheControlEphemeralParam</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">TTL</span>: <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">CacheControlEphemeralTTLTTL1h</span>,
</span></span><span style="display:flex;"><span>},
</span></span></code></pre></div><p>There is also a top-level <code>CacheControl</code> on <code>MessageNewParams</code> that automatically places a breakpoint on the last cacheable block and moves it forward as the conversation grows. For multi-turn chat that is the right default — no marker bookkeeping, and the growing history caches incrementally.</p>
<p><strong>The robust combination for anything agentic:</strong> one explicit breakpoint at the end of the static system prefix, so the expensive shared part has a guaranteed read point no matter what happens later in <code>messages</code>, plus top-level automatic caching for the growing tail.</p>
<p>You get four breakpoints per request, so there is no need to be frugal — place them at genuine stability boundaries.</p>
<h2 id="where-automatic-caching-is-the-wrong-tool">Where Automatic Caching Is the Wrong Tool</h2>
<p>Automatic placement puts the breakpoint at the very end of your prompt. When the prompt <em>ends</em> with something unique per request — a retrieved document, the user&rsquo;s actual question — that is a pure surcharge: every request writes a new cache entry that nothing will ever read.</p>
<p>The signature is unmistakable once you know it: <code>cache_creation_input_tokens</code> is non-zero on every single request, while <code>cache_read_input_tokens</code> never covers the shared prefix.</p>
<p>The fix is an explicit marker at the end of the <strong>shared</strong> portion:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">Messages</span>: []<span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">MessageParam</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">NewUserMessage</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">TextBlockParam</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Text</span>:         <span style="color:#a6e22e">sharedContext</span>, <span style="color:#75715e">// few-shot examples, retrieved docs</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">CacheControl</span>: <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">NewCacheControlEphemeralParam</span>(),
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">NewTextBlock</span>(<span style="color:#a6e22e">userQuestion</span>), <span style="color:#75715e">// no marker — differs every time</span>
</span></span><span style="display:flex;"><span>    ),
</span></span><span style="display:flex;"><span>},
</span></span></code></pre></div><p>Same rule, restated: put the breakpoint where the prompt <em>stops</em> being shared, not where the prompt ends.</p>
<h2 id="the-minimum-prefix-which-is-not-monotonic">The Minimum Prefix, Which Is Not Monotonic</h2>
<p>A prompt shorter than the model&rsquo;s minimum will not cache — no error, no warning, <code>cache_creation_input_tokens</code> simply comes back zero. And the minimum does not move in the direction you would guess as models get newer:</p>
<table>
	<thead>
			<tr>
					<th>Model</th>
					<th style="text-align: right">Minimum</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Claude Opus 5, Fable 5</td>
					<td style="text-align: right">512 tokens</td>
			</tr>
			<tr>
					<td>Opus 4.8, Sonnet 5, Sonnet 4.6</td>
					<td style="text-align: right">1024 tokens</td>
			</tr>
			<tr>
					<td>Opus 4.7</td>
					<td style="text-align: right">2048 tokens</td>
			</tr>
			<tr>
					<td>Opus 4.6, Haiku 4.5</td>
					<td style="text-align: right">4096 tokens</td>
			</tr>
	</tbody>
</table>
<p>A 3,000-token system prompt caches on Opus 5 and Opus 4.8, and silently does not on Opus 4.6 or Haiku 4.5. If you switched models and your cache hit rate fell off a cliff, this is the first thing to check — and it cuts the other way too: moving to Opus 5 halves the Opus 4.8 minimum, so prompts that were previously too short start caching with no code change at all.</p>
<h2 id="silent-invalidators">Silent Invalidators</h2>
<p>This is the part worth committing to memory, because every one of these is code that looks perfectly reasonable in review.</p>
<table>
	<thead>
			<tr>
					<th>Pattern</th>
					<th>Why it kills the cache</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>time.Now()</code> in the system prompt</td>
					<td>The prefix differs on every single request</td>
			</tr>
			<tr>
					<td>A request ID or UUID early in the content</td>
					<td>Same — every request is unique</td>
			</tr>
			<tr>
					<td><code>json.Marshal</code> of a <code>map</code> in the prompt</td>
					<td>Go randomises map iteration order; the bytes differ run to run</td>
			</tr>
			<tr>
					<td>Ranging over a map to build tool definitions</td>
					<td>Same problem, at position zero, which is the worst place for it</td>
			</tr>
			<tr>
					<td>User or session ID interpolated into the system prompt</td>
					<td>A per-user prefix; nothing shares anything</td>
			</tr>
			<tr>
					<td><code>if flag { system += ... }</code></td>
					<td>Every flag combination is a distinct prefix</td>
			</tr>
			<tr>
					<td>A tool set that varies per user or per mode</td>
					<td>Tools render first — nothing caches across users</td>
			</tr>
	</tbody>
</table>
<p>The Go-specific ones deserve emphasis. Map iteration order in Go is deliberately randomised, so this is not a cache bug that appears under load — it appears on <strong>every request</strong>, and it is invisible because the rendered prompt is semantically identical each time:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// Bad: iteration order is randomised, so the bytes differ every run.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#a6e22e">name</span>, <span style="color:#a6e22e">def</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">tools</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tools</span> = append(<span style="color:#a6e22e">tools</span>, <span style="color:#a6e22e">def</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Good: deterministic.</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">names</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">slices</span>.<span style="color:#a6e22e">Sorted</span>(<span style="color:#a6e22e">maps</span>.<span style="color:#a6e22e">Keys</span>(<span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">tools</span>))
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">name</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">names</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tools</span> = append(<span style="color:#a6e22e">tools</span>, <span style="color:#a6e22e">h</span>.<span style="color:#a6e22e">tools</span>[<span style="color:#a6e22e">name</span>])
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Sorting keys is a one-line fix that most Go LLM code needs and almost none has.</p>
<h2 id="injecting-dynamic-context-without-breaking-everything">Injecting Dynamic Context Without Breaking Everything</h2>
<p>The usual reason a system prompt has a timestamp in it is that the model genuinely needs to know the date, or the user&rsquo;s plan tier, or the current mode. The instinct is to template it into the system prompt. Don&rsquo;t — that is the front of the prefix, and it invalidates everything behind it.</p>
<p>Put dynamic context <strong>after</strong> the cached history instead. On the newest models there is a first-class channel for this: a <code>system</code>-role message appended to <code>messages</code>, rather than an edit to the top-level <code>system</code> field.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// The top-level system prompt stays byte-identical and stays cached.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// The operator instruction goes after the history, invalidating nothing before it.</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">messages</span> = append(<span style="color:#a6e22e">messages</span>, <span style="color:#a6e22e">userTurn</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">messages</span> = append(<span style="color:#a6e22e">messages</span>, <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">MessageParam</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Role</span>: <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">MessageParamRoleSystem</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Content</span>: []<span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">ContentBlockParamUnion</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">anthropic</span>.<span style="color:#a6e22e">NewTextBlock</span>(<span style="color:#e6db74">&#34;Terse mode enabled — keep responses under 40 words.&#34;</span>),
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>A message at turn five invalidates nothing before turn five. That is the whole trick.</p>
<p>Two constraints: it must follow a user message and be either the last entry or followed by an assistant turn — it cannot be <code>messages[0]</code>, so use the top-level <code>system</code> for the initial prompt. And support is model-dependent; unsupported models return a 400 saying the <code>system</code> role is not supported, so catch that and fall back to putting the instruction in a user turn.</p>
<h2 id="three-rules-that-beat-marker-placement">Three Rules That Beat Marker Placement</h2>
<p>Fix these before you fiddle with breakpoints.</p>
<p><strong>Freeze the system prompt.</strong> No dates, no user names, no modes. It is the front of the prefix and everything downstream depends on it not moving.</p>
<p><strong>Never change tools or model mid-conversation.</strong> Tools render at position zero, so adding, removing or reordering one invalidates the entire cache. Caches are also model-scoped, so switching models mid-conversation starts from cold. If you need &ldquo;modes&rdquo;, do not swap the tool set — pass the mode as message content.</p>
<p><strong>Forked calls must reuse the parent&rsquo;s exact prefix.</strong> Summarisation passes, sub-agents and side computations usually build their own request. If that fork rebuilds <code>system</code>, <code>tools</code> or <code>model</code> with any difference at all, it misses the parent&rsquo;s cache completely. Copy them verbatim and append the fork-specific content at the end.</p>
<p>That last one is also the argument against a &ldquo;cheap model for the easy stuff&rdquo; cascade, at least as a first move. Caches are per model, so routing between two models forfeits cache reuse across them. Measure the capable model at lower effort before you build the cascade — it is often cheaper <em>and</em> simpler, and it keeps one cache namespace.</p>
<h2 id="verifying-it-forever">Verifying It, Forever</h2>
<p>Every response carries the accounting (the same <code>Usage</code> struct I said to log from day one in <a href="/posts/calling-claude-from-go/">calling an LLM from Go</a>):</p>
<table>
	<thead>
			<tr>
					<th>Field</th>
					<th>Meaning</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>CacheCreationInputTokens</code></td>
					<td>Written to cache this request (you paid ~1.25×)</td>
			</tr>
			<tr>
					<td><code>CacheReadInputTokens</code></td>
					<td>Served from cache (you paid ~0.1×)</td>
			</tr>
			<tr>
					<td><code>InputTokens</code></td>
					<td>Full price, uncached</td>
			</tr>
	</tbody>
</table>
<p><code>InputTokens</code> is the <strong>uncached remainder only</strong> — not the prompt size. Total prompt = all three added together. If an agent ran for an hour and <code>InputTokens</code> reads 4K, the rest came from cache; check the sum, not the one field.</p>
<p>In a healthy multi-turn loop you should see, on each request:</p>
<ul>
<li><code>CacheReadInputTokens</code> — the whole prior prefix, growing turn over turn.</li>
<li><code>CacheCreationInputTokens</code> — roughly the last assistant turn plus the new input. Small.</li>
<li><code>InputTokens</code> — just the tail past the last breakpoint.</li>
</ul>
<p>If <code>CacheCreationInputTokens</code> is instead close to the full conversation size every time, the prefix is being rewritten upstream of your breakpoint. Go find the timestamp.</p>
<p><strong>And then keep checking.</strong> The expensive failure here is never the bad first implementation — it is the regression. Caching works the day you write it, then six weeks later somebody adds a dynamic field to the system prompt or a tool list that stopped being sorted, and every request misses. Nothing fails. Nothing pages. You find out from finance.</p>
<p>So make it a standing assertion, not a one-time look:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">TestSystemPromptStaysCacheable</span>(<span style="color:#a6e22e">t</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">testing</span>.<span style="color:#a6e22e">T</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Two identical requests: the second must read from cache.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">first</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">callModel</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">fixture</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">first</span>.<span style="color:#a6e22e">Usage</span>.<span style="color:#a6e22e">CacheCreationInputTokens</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Fatalf</span>(<span style="color:#e6db74">&#34;nothing was cached: prompt may be under the model minimum&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">second</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">callModel</span>(<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">fixture</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">second</span>.<span style="color:#a6e22e">Usage</span>.<span style="color:#a6e22e">CacheReadInputTokens</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">Errorf</span>(<span style="color:#e6db74">&#34;cache miss on an identical prompt — a silent invalidator crept in&#34;</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>That test costs a few cents to run and catches a regression that otherwise runs for months. Put it behind a build tag so it only runs when you mean it — the same treatment I gave integration tests in <a href="/posts/how-to-test-database-interactions-go/">how to test database interactions in Golang</a>. Better still, put a monitor on the ratio of <code>CacheReadInputTokens</code> to total input tokens and alert when it drops.</p>
<h2 id="checklist">Checklist</h2>
<ul>
<li>Frozen system prompt: no dates, IDs, names or conditional sections.</li>
<li>Deterministic tool list, sorted by name, identical across users.</li>
<li>One explicit breakpoint at the end of the static prefix; automatic caching for the tail.</li>
<li>Breakpoint at the end of the <em>shared</em> portion, not the end of the prompt.</li>
<li>5-minute TTL under continuous traffic; 1-hour only for 5–60 minute gaps.</li>
<li>Prompt above the model&rsquo;s minimum, which changes between models.</li>
<li>Dynamic context appended after the history, never templated into the system prompt.</li>
<li>Forks copy the parent&rsquo;s <code>system</code>, <code>tools</code> and <code>model</code> verbatim.</li>
<li>A test or monitor on the usage fields, checked on every change to prompt assembly.</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Prompt caching is the rare optimisation with no quality tradeoff: identical output, roughly a tenth of the input cost, and lower latency as a bonus. It is also unusually fragile, because it hinges on byte-exact prefixes and fails completely silently. Treat the prompt-building path the way you would treat a cache key anywhere else in your system — deterministic, stable, and covered by a test — and it mostly takes care of itself.</p>]]></content:encoded>
      <category>AI Engineering</category>
      <category>Performance Optimization</category>
      <category>Backend Development</category>
    </item>
    <item>
      <title>Example of how Golang generics minimize the amount of code you need to write</title>
      <link>https://webdevstation.com/posts/example-of-how-generics-simplify-golang/</link>
      <pubDate>Thu, 09 Jun 2022 15:41:52 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/example-of-how-generics-simplify-golang/</guid>
      <description>Explore practical examples of Go 1.18 generics in action by refactoring caching logic in a real-world application to write cleaner, more maintainable code with less…</description>
      <content:encoded><![CDATA[<p>I guess that almost everyone in the go community was exciting when Go 1.18 was released, especially because of generics.
Some days ago I decided to try generics in the real-world application, by refactoring some of its pieces, related to a caching logic.</p>
<p>In our web application, we have multiple resolvers that execute some sql queries and return data in different types. Obviously,
we want to prevent the database overloading by caching the same responses.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// MyExampleType1 type to serve example response 1.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MyExampleType1</span> <span style="color:#66d9ef">struct</span> {}
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// MyExampleType2 type to serve example response 2.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">type</span> <span style="color:#a6e22e">MyExampleType2</span> <span style="color:#66d9ef">struct</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// MyCachedResolver1 checks if there are any cached data by specific key.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// If nothing in cache, queries the database and adds result to the redis cache.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">MyCachedResolver1</span>(<span style="color:#a6e22e">redisClient</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Client</span>) ([]<span style="color:#a6e22e">MyExampleType1</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;resolver_result&#34;</span>
</span></span><span style="display:flex;"><span>	  <span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">found</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">key</span>)
</span></span><span style="display:flex;"><span>	  <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">found</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">FetchSomethingHeavyFromDB</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">valueJson</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">value</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">valueJson</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Hour</span><span style="color:#f92672">*</span><span style="color:#ae81ff">12</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">value</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">val</span> []<span style="color:#a6e22e">MyExampleType1</span>
</span></span><span style="display:flex;"><span>	  <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">value</span>.(<span style="color:#66d9ef">string</span>)), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">val</span>)
</span></span><span style="display:flex;"><span>	  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	  }
</span></span><span style="display:flex;"><span>	  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">val</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// MyCachedResolver2 checks if there are any cached data by specific key.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// If nothing in cache, queries the database and adds result to the redis cache.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">MyCachedResolver2</span>(<span style="color:#a6e22e">redisClient</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Client</span>) ([]<span style="color:#a6e22e">MyExampleType2</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;resolver_result_2&#34;</span>
</span></span><span style="display:flex;"><span>	  <span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">found</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">key</span>)
</span></span><span style="display:flex;"><span>	  <span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">found</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">FetchSomethingEvenMoreHeavierFromDB</span>()
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">valueJson</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">value</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">valueJson</span>, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Hour</span><span style="color:#f92672">*</span><span style="color:#ae81ff">12</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">value</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>	  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	  <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">val</span> []<span style="color:#a6e22e">MyExampleType2</span>
</span></span><span style="display:flex;"><span>	  <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">value</span>.(<span style="color:#66d9ef">string</span>)), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">val</span>)
</span></span><span style="display:flex;"><span>	  <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>	  }
</span></span><span style="display:flex;"><span>	  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">val</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>As you can see, there is some repetitive code that should be written due to the different types <code>MyExampleType1</code> and <code>MyExampleType2</code>.</p>
<p>Now, let&rsquo;s see how we can improve this situation by using generics. I&rsquo;m going to write a function <code>WithCache()</code> which will be responsible for
setting and getting data to/from redis cache.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>    <span style="color:#75715e">// WithCache adds exec function result into the redis cache.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Pay attention to &#34;T any&#34; that allows us to pass any type to this function.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">WithCache</span>[<span style="color:#a6e22e">T</span> <span style="color:#66d9ef">any</span>](<span style="color:#a6e22e">redisClient</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Client</span>, <span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>, <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">exec</span> <span style="color:#66d9ef">func</span>() (<span style="color:#a6e22e">T</span>, <span style="color:#66d9ef">error</span>), <span style="color:#a6e22e">ttl</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>) (<span style="color:#a6e22e">T</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>    	<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">found</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">key</span>)
</span></span><span style="display:flex;"><span>    	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">result</span> <span style="color:#a6e22e">T</span>
</span></span><span style="display:flex;"><span>    	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">found</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// exec() is a function that should be executed to fetch needed data.</span>
</span></span><span style="display:flex;"><span>    		<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">exec</span>()
</span></span><span style="display:flex;"><span>    		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    		}
</span></span><span style="display:flex;"><span>    		<span style="color:#a6e22e">jsonValue</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Marshal</span>(<span style="color:#a6e22e">value</span>)
</span></span><span style="display:flex;"><span>    		<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    			<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    		}
</span></span><span style="display:flex;"><span>    		<span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">Set</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">jsonValue</span>, <span style="color:#a6e22e">ttl</span>)
</span></span><span style="display:flex;"><span>    		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">value</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    	}
</span></span><span style="display:flex;"><span>    	<span style="color:#66d9ef">var</span> <span style="color:#a6e22e">val</span> <span style="color:#a6e22e">T</span>
</span></span><span style="display:flex;"><span>    	<span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">Unmarshal</span>([]byte(<span style="color:#a6e22e">value</span>.(<span style="color:#66d9ef">string</span>)), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">val</span>)
</span></span><span style="display:flex;"><span>    	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>    		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>, <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>    	}
</span></span><span style="display:flex;"><span>    	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">val</span>, <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>After that our resolvers could be refactored into this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>     <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">MyCachedResolver1</span>(<span style="color:#a6e22e">redisClient</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Client</span>) ([]<span style="color:#a6e22e">MyExampleType1</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>       <span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;resolver_result_1&#34;</span>
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">WithCache</span>[[]<span style="color:#a6e22e">MyExampleType1</span>](<span style="color:#a6e22e">redisClient</span>, <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">key</span>, <span style="color:#66d9ef">func</span>() ([]<span style="color:#a6e22e">MyExampleType1</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>		 <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">FetchSomethingHeavyFromDB</span>()
</span></span><span style="display:flex;"><span>	   }, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Hour</span><span style="color:#f92672">*</span><span style="color:#ae81ff">12</span>)
</span></span><span style="display:flex;"><span>     }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>     <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">MyCachedResolver2</span>(<span style="color:#a6e22e">redisClient</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">Client</span>) ([]<span style="color:#a6e22e">MyExampleType2</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>       <span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;resolver_result_2&#34;</span>
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">WithCache</span>[[]<span style="color:#a6e22e">MyExampleType2</span>](<span style="color:#a6e22e">redisClient</span>, <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>(), <span style="color:#a6e22e">key</span>, <span style="color:#66d9ef">func</span>() ([]<span style="color:#a6e22e">MyExampleType2</span>, <span style="color:#66d9ef">error</span>) {
</span></span><span style="display:flex;"><span>		 <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">FetchSomethingEvenMoreHeavierFromDB</span>()
</span></span><span style="display:flex;"><span>	   }, <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Hour</span><span style="color:#f92672">*</span><span style="color:#ae81ff">12</span>)
</span></span><span style="display:flex;"><span>     }
</span></span></code></pre></div><p>Now it looks much better and clearer!</p>
<p>The caching code this example refactors is the one from <a href="/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/">Ristretto — the most performant concurrent cache library for Go</a>, if you want the full context. For another place where a little type machinery removes a lot of duplication, see <a href="/posts/implementing-enums-in-golang/">implementing enums in Golang</a>. And since Go 1.27 a method can carry its own type parameters, which removes the package-level helper this article works around — <a href="/posts/whats-new-in-go-1-27/">what&rsquo;s new in Go 1.27</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Backend Development</category>
    </item>
    <item>
      <title>Ristretto - the Most Performant Concurrent Cache Library for Go</title>
      <link>https://webdevstation.com/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/</link>
      <pubDate>Tue, 02 Mar 2021 18:19:53 +0100</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/</guid>
      <description>Learn how to implement Ristretto, a high-performance concurrent memory caching library for Go applications. Includes code examples comparing database access with and…</description>
      <content:encoded><![CDATA[<p>Recently, I discovered a surprisingly reliable memory caching solution, which I&rsquo;m planning to use in all my further applications to increase performance. In this blog post, I will share some code examples of how you can integrate <a href="https://github.com/dgraph-io/ristretto">Ristretto</a> caching library into your application.</p>
<p><code>Ristretto is a fast, concurrent cache library built with a focus on performance and correctness.</code></p>
<p>This library was created by the Dgraph team as a contention-free cache for the Dgraph database.</p>
<p>Let&rsquo;s dive into the practical example. We are going to build a simple application that gets a list of users from the database. In the first iteration, there will be no caching layer at all. In the second iteration, we will add a Ristretto caching and compare execution time.</p>
<p>Below, you can see that I defined a <code>repository</code> package with the <code>Repository</code> interface and with <code>InMemoryRepository</code> implementation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">repository</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Repository interface to handle users data.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">Repository</span> <span style="color:#66d9ef">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">GetUsers</span>() <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">int</span>]<span style="color:#66d9ef">string</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">InMemoryRepository</span> <span style="color:#66d9ef">struct</span>{}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// NewInMemoryRepository constructs and returns InMemoryRepository.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewInMemoryRepository</span>() <span style="color:#f92672">*</span><span style="color:#a6e22e">InMemoryRepository</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">InMemoryRepository</span>{}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// GetUsers returns 50000 dummy users from the in-memory repository.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">InMemoryRepository</span>) <span style="color:#a6e22e">GetUsers</span>() <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">int</span>]<span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">users</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">int</span>]<span style="color:#66d9ef">string</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span><span style="color:#f92672">:=</span><span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">50000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">users</span>[<span style="color:#a6e22e">i</span>] = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;User %d&#34;</span>, <span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">users</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Next, we are going to call a <code>GetUsers()</code> method 100 times to simulate calling of the same function from several places in the real-world applications:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/alexsergivan/blog-examples/ristretto/repository&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span><span style="color:#f92672">:=</span><span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span>&lt;<span style="color:#ae81ff">100</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">UsersGetter</span>(<span style="color:#a6e22e">repository</span>.<span style="color:#a6e22e">NewInMemoryRepository</span>())
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">UsersGetter</span>(<span style="color:#a6e22e">repository</span> <span style="color:#a6e22e">repository</span>.<span style="color:#a6e22e">Repository</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">repository</span>.<span style="color:#a6e22e">GetUsers</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Let&rsquo;s measure how much time it takes to execute it with <code>time go run main.go</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>1.46s user
</span></span><span style="display:flex;"><span>0.34s system
</span></span><span style="display:flex;"><span>106% cpu
</span></span><span style="display:flex;"><span>1.686 total
</span></span></code></pre></div><p>Next, we are going to add a caching layer to our application.</p>
<p>Don&rsquo;t forget to get the Ristretto library:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>  go get github.com/dgraph-io/ristretto
</span></span></code></pre></div><p>Inside <code>repository</code> package we inject Ristretto cache:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">package</span> <span style="color:#a6e22e">repository</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;fmt&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/dgraph-io/ristretto&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;time&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">InMemoryRepository</span> <span style="color:#66d9ef">struct</span>{
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">cache</span>        <span style="color:#f92672">*</span><span style="color:#a6e22e">ristretto</span>.<span style="color:#a6e22e">Cache</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// NewInMemoryRepository constructs and returns InMemoryRepository.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">NewInMemoryRepository</span>(<span style="color:#a6e22e">ristrettoCache</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">ristretto</span>.<span style="color:#a6e22e">Cache</span>) <span style="color:#f92672">*</span><span style="color:#a6e22e">InMemoryRepository</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">InMemoryRepository</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">cache</span>: <span style="color:#a6e22e">ristrettoCache</span>,
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// GetUsers returns 50000 dummy users from the in-memory repository.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">InMemoryRepository</span>) <span style="color:#a6e22e">GetUsers</span>() <span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">int</span>]<span style="color:#66d9ef">string</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">key</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;users&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">found</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">key</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// If the users data not cached yet, get it from the repository.</span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">if</span> !<span style="color:#a6e22e">found</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">users</span> <span style="color:#f92672">:=</span> make(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">int</span>]<span style="color:#66d9ef">string</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span><span style="color:#f92672">:=</span><span style="color:#ae81ff">1</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">50000</span>; <span style="color:#a6e22e">i</span><span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">users</span>[<span style="color:#a6e22e">i</span>] = <span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Sprintf</span>(<span style="color:#e6db74">&#34;User %d&#34;</span>, <span style="color:#a6e22e">i</span>)
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Adds data to the cache for 1h.</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">cache</span>.<span style="color:#a6e22e">SetWithTTL</span>(<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">users</span>, <span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">1</span><span style="color:#f92672">*</span><span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Hour</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">10</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Millisecond</span>)
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">users</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">value</span>.(<span style="color:#66d9ef">map</span>[<span style="color:#66d9ef">int</span>]<span style="color:#66d9ef">string</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Next, inside the <code>main()</code> function we initiate a new Ristretto cache and pass it to the <code>InMemoryRepository</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span>  <span style="color:#f92672">package</span> <span style="color:#a6e22e">main</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/alexsergivan/blog-examples/ristretto/repository&#34;</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e6db74">&#34;github.com/dgraph-io/ristretto&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">ristrettoCache</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ristretto</span>.<span style="color:#a6e22e">NewCache</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">ristretto</span>.<span style="color:#a6e22e">Config</span>{
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">NumCounters</span>: <span style="color:#ae81ff">1e7</span>,     <span style="color:#75715e">// Num keys to track frequency of (10M).</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">MaxCost</span>:     <span style="color:#ae81ff">1</span> <span style="color:#f92672">&lt;&lt;</span> <span style="color:#ae81ff">30</span>, <span style="color:#75715e">// Maximum cost of cache (1GB).</span>
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">BufferItems</span>: <span style="color:#ae81ff">64</span>,      <span style="color:#75715e">// Number of keys per Get buffer.</span>
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> <span style="color:#a6e22e">i</span><span style="color:#f92672">:=</span><span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">i</span>&lt;<span style="color:#ae81ff">100</span>; <span style="color:#a6e22e">i</span> <span style="color:#f92672">++</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">UsersGetter</span>(<span style="color:#a6e22e">repository</span>.<span style="color:#a6e22e">NewInMemoryRepository</span>(<span style="color:#a6e22e">ristrettoCache</span>))
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">UsersGetter</span>(<span style="color:#a6e22e">repository</span> <span style="color:#a6e22e">repository</span>.<span style="color:#a6e22e">Repository</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">repository</span>.<span style="color:#a6e22e">GetUsers</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Let&rsquo;s check how much time it takes to perform the same action:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>0.29s user
</span></span><span style="display:flex;"><span>0.26s system
</span></span><span style="display:flex;"><span>147% cpu
</span></span><span style="display:flex;"><span>0.377 total
</span></span></code></pre></div><p>As you can notice, the total time is 4 times less than in the example without caching layer.</p>
<p>Despite a silly example, I hope you got an idea of how to integrate the Ristretto caching into your application and how it could improve overall performance.</p>
<p>The complete source code you can find <a href="https://github.com/alexsergivan/blog-examples/tree/master/ristretto">here</a>.</p>
<p>Ristretto is safe for concurrent use, which is exactly the problem a plain map does not solve — see <a href="/posts/concurrent-map-writing-and-reading-in-go/">concurrent map writing and reading in Go</a> if you want the failure mode in detail. I also refactored this caching layer with generics in <a href="/posts/example-of-how-generics-simplify-golang/">how Golang generics minimize the amount of code you need to write</a>. For a cache with a very different failure mode — one that costs you money rather than latency when it silently stops working — see <a href="/posts/prompt-caching-llm-cost/">prompt caching</a>.</p>]]></content:encoded>
      <category>Go Programming</category>
      <category>Performance Optimization</category>
    </item>
    <item>
      <title>How to make Nginx cache cookie aware</title>
      <link>https://webdevstation.com/posts/how-to-make-nginx-cookie-aware/</link>
      <pubDate>Tue, 15 Dec 2020 20:12:51 +0000</pubDate>
      <author>Alex</author>
      <guid isPermaLink="true">https://webdevstation.com/posts/how-to-make-nginx-cookie-aware/</guid>
      <description>Learn how to configure Nginx to cache responses based on specific cookie values, enabling proper A/B testing and personalized content delivery while maintaining…</description>
      <content:encoded><![CDATA[<p>In this post, I&rsquo;m going to describe how we can configure nginx to be able to cache responses based on the specific cookie value.</p>
<p>Let&rsquo;s imagine a situation when you want to do an A/B test on your website, where 50% of users should see new headline text on the page. Other 50% of visitors will continue see the old page. In this case, all your server-side manipulation about splitting users to different versions will be ignored by nginx cache (of course, if you have it).</p>
<p>It happens because nginx, by default, has this configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">proxy_cache_key</span> $scheme$proxy_host$request_uri;
</span></span></code></pre></div><p>So, it will use the same cache key for all users, who requests a page with the same url.</p>
<p>Luckily, nginx allows us easily to customize proxy_cache_key! What we need to do, it&rsquo;s just to add a specific cookie to this key:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">proxy_cache_key</span> $scheme$proxy_host$request_uri$cookie_MY_COOKIE_NAME;
</span></span></code></pre></div><p>And that&rsquo;s it! After this, if userA has MY_COOKIE_NAME=A and userB has MY_COOKIE_NAME=B they will receive different versions of page by the same url.</p>
<p>If you need more complex behaviour, where you won&rsquo;t use hardcoded cookie name in your nginx config, you can do something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#e6db74">(</span>$http_cookie ~<span style="color:#e6db74">*</span> <span style="color:#e6db74">&#34;ab_(.*?)=([\w-]+)&#34;</span> <span style="color:#e6db74">)</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">set</span> $abcookie $1$2;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">proxy_cache_key</span> $scheme$proxy_host$request_uri$abcookie; 
</span></span></code></pre></div><p>As you can see, you can use $http_cookie and generate proxy_cache_key based on specific cookie patterns.
In this concrete example we check if http cookies contains cookie with regex <code>pattern ab_(.*?)=([\w-]+)</code> and if this cookie exists, we generate new variable for proxy_cache_key`.</p>
<p>Caching at the proxy is only one layer. For the one inside your Go process, see <a href="/posts/ristretto-the-most-performant-concurrent-cache-library-for-go/">Ristretto — the most performant concurrent cache library for Go</a>, and for shaving latency off the browser side, <a href="/posts/early-hints-in-go-or-the-way-of-how-to-improve-perforamnce-of-web-page/">103 Early Hints in Go</a>.</p>]]></content:encoded>
      <category>DevOps</category>
      <category>Web Development</category>
    </item>
  </channel>
</rss>
