<?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: Shunya Shida</title>
    <description>The latest articles on DEV Community by Shunya Shida (@shunya_shida).</description>
    <link>https://dev.to/shunya_shida</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%2F4035963%2Fb3a3afda-3c1b-47fd-8b29-10fe8049fe1b.png</url>
      <title>DEV Community: Shunya Shida</title>
      <link>https://dev.to/shunya_shida</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shunya_shida"/>
    <language>en</language>
    <item>
      <title>Why My Cache Has No TTL</title>
      <dc:creator>Shunya Shida</dc:creator>
      <pubDate>Mon, 03 Aug 2026 14:14:35 +0000</pubDate>
      <link>https://dev.to/shunya_shida/why-my-cache-has-no-ttl-oo7</link>
      <guid>https://dev.to/shunya_shida/why-my-cache-has-no-ttl-oo7</guid>
      <description>&lt;p&gt;Last time, I wrote about the dilemma of feeding specs scattered across multiple repositories to AI (&lt;a href="https://dev.to/shunya_shida/index-everything-or-read-everything-the-dilemma-of-feeding-specs-to-ai-in-multi-repo-development-52h7"&gt;Index Everything, or Read Everything?&lt;/a&gt;). This post is a devlog for the caching layer of the service I mentioned there (&lt;a href="https://repospec.dev" rel="noopener noreferrer"&gt;Repospec&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;My answer in that post was "outsource just the search" — which means the spec files live in PostgreSQL.&lt;/p&gt;

&lt;p&gt;In other words, PostgreSQL holds a cache of files that have already been pushed to GitHub. And that cache has &lt;strong&gt;no TTL&lt;/strong&gt;. Once a file goes in, it stays. Indefinitely, by design.&lt;/p&gt;

&lt;p&gt;If you've ever built a cache, this probably makes your skin crawl. It made mine crawl too — the first version absolutely had a TTL. This post is my personal record of how I talked myself out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  "It's a cache, so... Redis, right?"
&lt;/h2&gt;

&lt;p&gt;I'll be honest: a lot of this was figured out as I went, not planned up front.&lt;/p&gt;

&lt;p&gt;The one thing I knew from the start was that fetching from GitHub on every request was a bad idea, purely for speed (the previous post covers the broader reasoning). So caching was always part of the plan. And my thought process for choosing the store was, in its entirety: "It's a cache, so... Redis, right?" Not my proudest engineering decision, but I suspect it's the same reflex most of us have.&lt;/p&gt;

&lt;p&gt;The TTL went in just as automatically. Files fetched from GitHub would expire after a fixed window. Why? Because that's what caches do. That was the whole reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  I forgot about search
&lt;/h2&gt;

&lt;p&gt;Here's the embarrassing part. The entire point of this service is &lt;em&gt;"find the spec you can't remember where you wrote."&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Search is not a feature — it's the product.&lt;/p&gt;

&lt;p&gt;And yet there I was, picking a cache store before thinking about how search would work at all. I got absorbed in the architecture and drifted away from the actual problem.&lt;/p&gt;

&lt;p&gt;Once search finally got my attention, the answer more or less picked itself: query PostgreSQL directly. Redis is excellent at being fast and excellent at being a cache, but for the kind of fine-grained text search I needed, Postgres was simply the better fit for this use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait — do I actually need to delete anything?
&lt;/h2&gt;

&lt;p&gt;With the store settled, I looked at the TTL again. And I stalled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why am I deleting this data at all?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I tried putting into words what this cache actually needed to guarantee, and it came out like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What matters is staying in sync with the latest data on GitHub — not that cache entries get deleted on some appropriate schedule.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A TTL doesn't guarantee freshness. It just guarantees deletion. Sure, the entry gets re-fetched afterward and ends up fresh — but "eventually re-fetched" is not the same thing as "in sync." Worse, in this service, a file that expires from the cache &lt;em&gt;also disappears from search results&lt;/em&gt;. "I searched for it and it wasn't there" is the single failure mode I refuse to accept.&lt;/p&gt;

&lt;p&gt;So the TTL went.&lt;/p&gt;

&lt;p&gt;There's a flip side, though. Removing the TTL means holding onto files from people's private repositories indefinitely. So I also decided what &lt;em&gt;not&lt;/em&gt; to hold: the search logs store no spec content and no snippets — queries only. I'd call it less a privacy stance and more a "this is confidential material and I should act like it" stance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not both?
&lt;/h2&gt;

&lt;p&gt;Of course I considered the two-store setup: Redis for caching, PostgreSQL for search. It's a perfectly reasonable architecture. I just couldn't find a benefit that applied &lt;em&gt;here&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The thing is, &lt;strong&gt;this cache doesn't behave like a network cache&lt;/strong&gt;. A network cache grows without bound unless you evict — eviction is load-bearing. This cache has a natural ceiling: it can never hold more than the spec files users have registered. Nothing else ever gets in. The thing a TTL usually protects you from simply doesn't exist here.&lt;/p&gt;

&lt;p&gt;The second reason is plumbing. With two stores, every GitHub update has to be synced to &lt;em&gt;both&lt;/em&gt; Redis and Postgres. Two sync targets means two ways to drift. I wanted exactly one.&lt;/p&gt;

&lt;p&gt;Does Postgres lose to Redis on raw speed? Yes, clearly. But Postgres offered a much wider range of search capabilities, and this whole thing runs behind MCP — an AI agent reading spec files. On that path, a few tens or hundreds of milliseconds are effectively invisible. Shaving milliseconds off a route where the consumer is an LLM buys you nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freshness now rides on webhooks
&lt;/h2&gt;

&lt;p&gt;With the TTL gone, freshness has exactly one owner: GitHub's push webhook.&lt;/p&gt;

&lt;p&gt;This was a design commitment from day one: &lt;strong&gt;keeping the cache fresh must never be a human's job.&lt;/strong&gt; Imagine pushing a commit, then having to open a dashboard and click a "re-sync" button. That's real friction, and I know myself — I wouldn't do it. With webhooks, there's a small lag, but the chore simply doesn't exist.&lt;/p&gt;

&lt;p&gt;That said, the implementation turned out to be a lot more than "receive notification, update row"...&lt;br&gt;
That story deserves its own post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search is a deeper rabbit hole than I expected
&lt;/h2&gt;

&lt;p&gt;The webhook wasn't the only bill that arrived later. The other one was chunking.&lt;/p&gt;

&lt;p&gt;The first version stuffed each spec file into a single column, whole. That fit my usual approach — get something working, then improve it — so in it went. But a column holding a massive blob of text is not a healthy thing to run search queries against. The files needed to be split into reasonably sized pieces.&lt;/p&gt;

&lt;p&gt;Then the questions started. What granularity actually gives the best search precision and usability? What about files without clean structural boundaries, unlike Markdown? Turns out "just split the text" is not a thing — I only learned that by trying. This rabbit hole goes as deep as you're willing to dig, so for now the chunk size is a hardcoded guess. There's a very real chance this ends up delegated to something like Elasticsearch eventually, but I'd rather tune it against real usage than against my imagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should NOT copy this
&lt;/h2&gt;

&lt;p&gt;Let me be clear about where this setup does &lt;em&gt;not&lt;/em&gt; belong.&lt;/p&gt;

&lt;p&gt;It only works because two preconditions happen to hold:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The cached data is finite.&lt;/strong&gt; There's a hard ceiling — "however many spec files got registered" — so I can get away with never evicting. If your data grows without bound, you need eviction, which means you need a TTL (or something like it).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency requirements are loose.&lt;/strong&gt; On a path where an AI agent reads spec files, a few hundred milliseconds might as well be zero. If your path is measured in milliseconds, Postgres-as-cache is not an option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If either one fails for you, just use Redis. It's the right default for a reason.&lt;/p&gt;

&lt;p&gt;And to be clear, this post says nothing about network-level caching in general — there are far better articles on that. Everything here only applies because the data had an unusual shape &lt;em&gt;and&lt;/em&gt; MCP made latency nearly irrelevant.&lt;/p&gt;

&lt;p&gt;"Cache means Redis" is a reflex most of us carry. It's a good reflex. But depending on what you're actually protecting, a setup like this one can be enough.&lt;/p&gt;

&lt;p&gt;I'd genuinely like to hear how others have reasoned about this. Have you ever dropped a TTL — or deliberately kept one where it wasn't strictly needed? And if you've dealt with chunking for search, I'd love to know where you landed on granularity. That part is still a guess on my side.&lt;/p&gt;




&lt;p&gt;Originally written in Japanese (&lt;a href="https://zenn.dev/shida_dev/articles/a4dfcec6c08ad5" rel="noopener noreferrer"&gt;https://zenn.dev/shida_dev/articles/a4dfcec6c08ad5&lt;/a&gt;). Translated with AI assistance and reviewed by the author.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>database</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Index Everything, or Read Everything? The Dilemma of Feeding Specs to AI in Multi-Repo Development</title>
      <dc:creator>Shunya Shida</dc:creator>
      <pubDate>Sun, 19 Jul 2026 02:30:25 +0000</pubDate>
      <link>https://dev.to/shunya_shida/index-everything-or-read-everything-the-dilemma-of-feeding-specs-to-ai-in-multi-repo-development-52h7</link>
      <guid>https://dev.to/shunya_shida/index-everything-or-read-everything-the-dilemma-of-feeding-specs-to-ai-in-multi-repo-development-52h7</guid>
      <description>&lt;h2&gt;
  
  
  The specs exist. The AI just can't see them.
&lt;/h2&gt;

&lt;p&gt;I've always been the type who builds hobby projects, gets satisfied halfway through, and never actually finishes. For a long time I wanted to change that — to build something all the way to the end and actually ship it. So a bit over a year ago, I started building small, microservice-style pieces: if each piece is small, I can actually finish it, reuse it later, and it won't eat months of my life. It might sound like overkill for a solo project, but for me it was a trick to "cut things down to a finishable size."&lt;/p&gt;

&lt;p&gt;Then AI-assisted development became the norm. To give the AI context, I started writing proper spec documents even for personal projects. So far, so good.&lt;/p&gt;

&lt;p&gt;The problem: with microservices, my repos are separate. The repo I have open in Cursor right now is Service A. The spec I want to reference lives in Service B's repo. The spec exists. It's written down. The AI just can't see it.&lt;/p&gt;

&lt;p&gt;"How do I hand the AI a spec that lives outside the workspace I currently have open?" This article is a record of me taking far longer than I needed to turn that problem over in my head.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual dilemma: two options, both with a price tag
&lt;/h2&gt;

&lt;p&gt;When you boil it down, there are really only two options (probably).&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Add it to the workspace (index everything)
&lt;/h3&gt;

&lt;p&gt;Use Cursor's multi-root workspaces and add the entire spec repo to your workspace. I actually tried this.&lt;/p&gt;

&lt;p&gt;It wasn't terrible, but it wasn't exactly pleasant either. Irrelevant files get pulled into context. Files from a repo I'm not even working on show up in search. The symptoms I remember: edits landing in places I didn't ask to change, and token usage ballooning. All I wanted was to read a few spec files, and instead I was paying the tax of putting an entire repo into the AI's field of view.&lt;br&gt;
(AI models keep getting better, so maybe this is less of an issue today.)&lt;/p&gt;

&lt;p&gt;To be fair, multi-root workspaces themselves have evolved. In its April 2026 release, Cursor made it so a single agent session can target a multi-folder workspace and make cross-repo changes (&lt;a href="https://cursor.com/changelog/04-24-26" rel="noopener noreferrer"&gt;changelog&lt;/a&gt;). For "fix something across the frontend and the backend" workflows, it's genuinely better now. But the structure hasn't changed: every folder you open becomes part of the index. For "I just want to read a few spec files," it's still overkill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Don't add it — fetch on demand (read everything)
&lt;/h3&gt;

&lt;p&gt;Keep the repo out of your workspace and have the AI go fetch files when needed. For example, by writing the paths into your rules file.&lt;/p&gt;

&lt;p&gt;If you know the file path, this works fine. The problem is the "wait, I know I wrote this down &lt;em&gt;somewhere&lt;/em&gt;" case. Since the AI doesn't know the path, it has to read through the candidate files to find it. Which means loading full file contents into context just to run a search. That's a lot of tokens.&lt;/p&gt;

&lt;p&gt;So the dilemma is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pay in noise to index everything, or pay in tokens to read everything.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The options I considered — and my false assumption
&lt;/h2&gt;

&lt;p&gt;"If it doesn't exist, build it" is a fine spirit, but if there's a giant's shoulder to stand on, you should stand on it. Before jumping into building my own thing, I did look at existing solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-hosted docs tools:&lt;/strong&gt; I found there were tools that let you run a GitBook-like setup locally with Docker. But they needed customization to fit my workflow, felt inflexible, and didn't offer the polished experience of the paid tools. Which led me to the classic thought: "If it's not that hard to build, maybe I'll just build it myself?" (Ah yes, the person who was going to build it no matter what.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;External SaaS:&lt;/strong&gt; Documentation-management services were either shockingly expensive or way too much for a solo project's needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub MCP Server:&lt;/strong&gt; In my research chats with an AI at the time, there was a line saying it "needs to run locally," and based on that I ruled it out from the start. If I have to run a container locally anyway, I figured, it's no different from the self-hosted option.&lt;/p&gt;

&lt;p&gt;— And here's where I have to make an embarrassing confession. &lt;strong&gt;That exclusion was based on a false assumption.&lt;/strong&gt; GitHub MCP Server has a remote-hosted version, and it went GA in September 2025 (&lt;a href="https://github.blog/changelog/2025-09-04-remote-github-mcp-server-is-now-generally-available/" rel="noopener noreferrer"&gt;GitHub Changelog&lt;/a&gt;). GitHub hosts it, you authenticate once with OAuth, and that's it. No Docker, no PAT management. I only learned this while researching for this very article.&lt;/p&gt;

&lt;p&gt;In other words: if I had been a little more skeptical of the AI's research back then and gone to the primary sources, there's a good chance I never would have started building this product. I'm always telling my nephew not to blindly trust what AI says, and yet here we are...&lt;br&gt;
AI research can be based on outdated or plain wrong premises — I re-learned that lesson only after building an entire product on top of it. That said, I still think it was a good experience (I know how that sounds, but as I'll explain in the next section, what I built didn't turn out to be pointless either).&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem that remained: search that never feels quite right
&lt;/h2&gt;

&lt;p&gt;The remote GitHub MCP Server is genuinely capable. It can fetch files from private repos without a local clone. Honestly, if you know the file path, listing your spec paths in a rules file and letting GitHub MCP read them solves most of my use case, for free.&lt;/p&gt;

&lt;p&gt;What about search, then? Turns out that works too. GitHub MCP Server has a code search tool, and GitHub's code search indexes the private repos you have access to (&lt;a href="https://docs.github.com/en/search-github/github-code-search/using-github-code-search" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;). If I wrote "you can't do cross-repo search with GitHub MCP," that would simply be false.&lt;/p&gt;

&lt;p&gt;But when I map it onto the daily "where did I write that part of the spec again?" use case, a few things snag. All of these are documented behavior (&lt;a href="https://docs.github.com/en/rest/search/search" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Only the default branch is searched, and only files under 384 KB.&lt;/strong&gt; Specs usually fit, but if you keep spec variants on different branches, those are out of scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The code search API is rate-limited to 10 requests per minute, even authenticated.&lt;/strong&gt; That's a separate, much tighter bucket than the regular API (5,000/hour). AI agents love to fire off search queries in trial-and-error loops, so you feel this limit quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search results are mostly file metadata. (The API can hand back short highlight fragments if you ask for them, but that's about it.)&lt;/strong&gt; To actually read the content, you need a follow-up fetch. So it's a minimum of two round trips: search, then fetch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are "it doesn't work." They're all "it never feels quite right for daily use." But "I know I wrote this somewhere" happens multiple times a day. Do I really want to step on that friction every single time?&lt;/p&gt;

&lt;p&gt;My answer was to &lt;strong&gt;hand search off to a search engine (of sorts).&lt;/strong&gt; Put the spec contents in PostgreSQL and do the search in a single DB query (eventually vector search too). Return to the AI only a snippet: the matching lines plus a few lines of context. No punishing rate limits, no extra round trips, and the only thing landing in the AI's context is the fragment it actually needs. I'm still mid-development, so it's too early to make big claims about the results — but at minimum, the "now where did I write that" hunts seem to be going away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm building: a small, read-only window onto your specs
&lt;/h2&gt;

&lt;p&gt;That's what led to &lt;a href="https://repospec.dev" rel="noopener noreferrer"&gt;Repospec&lt;/a&gt;, which I'm building now. You register "this file in this repo is a spec" on a dashboard, and then from Cursor or Claude Code, via MCP, you get fetch and cross-repo search over just your registered specs. (Note: it's still pre-launch — only a landing page for now, where you can sign up for release notifications.)&lt;/p&gt;

&lt;p&gt;The design principle was to strip away everything unnecessary and keep it simple. Which means there are things I deliberately did not build: &lt;strong&gt;editing features and version control, among others.&lt;/strong&gt; I wanted to stay focused on my own actual problem, and if this was going to be a product, I did not want it to become "a worse GitBook." The source of truth stays in your GitHub repos; this thing is strictly read-only plumbing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do you actually need this?
&lt;/h2&gt;

&lt;p&gt;Write your spec repo paths into &lt;code&gt;.cursor/rules&lt;/code&gt; and let GitHub MCP Server read them. If that covers you, that's completely fine. If your project is small enough that you can remember your file paths, you don't need another tool.&lt;/p&gt;

&lt;p&gt;This dilemma only really starts to hurt when "multiple repos" × "I can't remember where I wrote what" becomes your daily reality. At that point you have three choices: index everything, read everything, or move just the search outside. I wrote this article because I think there's value in simply knowing the options exist.&lt;/p&gt;

&lt;p&gt;When it comes to actually "mastering" AI-assisted development, I feel like there's still a lot of room for craft. The itch is never quite scratched. But what I've gained from building this — that's turning out to be not so small.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally written in Japanese (&lt;a href="https://zenn.dev/shida_dev/articles/8304bc8dc63b10" rel="noopener noreferrer"&gt;https://zenn.dev/shida_dev/articles/8304bc8dc63b10&lt;/a&gt;). Translated with AI assistance and reviewed by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>github</category>
      <category>cursor</category>
    </item>
  </channel>
</rss>
