<?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: 강해수</title>
    <description>The latest articles on DEV Community by 강해수 (@riversea).</description>
    <link>https://dev.to/riversea</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3982443%2F067a59e7-d157-4b5a-896c-bb48e103f563.png</url>
      <title>DEV Community: 강해수</title>
      <link>https://dev.to/riversea</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/riversea"/>
    <language>en</language>
    <item>
      <title>I wasted $43 rebuilding a Vectorize index the wrong way — here's the $5.50 fix</title>
      <dc:creator>강해수</dc:creator>
      <pubDate>Mon, 15 Jun 2026 01:11:21 +0000</pubDate>
      <link>https://dev.to/riversea/i-wasted-43-rebuilding-a-vectorize-index-the-wrong-way-heres-the-550-fix-5jp</link>
      <guid>https://dev.to/riversea/i-wasted-43-rebuilding-a-vectorize-index-the-wrong-way-heres-the-550-fix-5jp</guid>
      <description>&lt;p&gt;Last month's Anthropic bill hit $312. Sixty percent of it traced back to a single 6-hour window when I was doing an in-place Vectorize index rebuild.&lt;/p&gt;

&lt;p&gt;Here's the part I didn't expect: the rebuild didn't throw errors. Queries returned results, scores just quietly dropped from 0.78 to 0.61. My agent interpreted that as "no relevant docs found" and fell back to Claude directly — 2,880 times over six hours. At ~5K input tokens per query on Sonnet, that's $43 in LLM fallback costs from what I thought was a routine maintenance task.&lt;/p&gt;

&lt;p&gt;The fix was a Blue/Green swap. I spun up &lt;code&gt;ads-insights-v2&lt;/code&gt; alongside the existing index, rebuilt all 180K vectors into it using 256-token chunks (down from 512), validated against a 20-query golden set, then flipped one environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wrangler deploy &lt;span class="nt"&gt;--var&lt;/span&gt; VECTORIZE_INDEX_NAME:ads-insights-v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actual downtime: 4.19 seconds. The old index kept serving traffic the entire 8.5 hours the new one was being populated. Running two indexes in parallel for roughly a day cost $1.20 in extra Vectorize storage. So the real cost comparison was $43 (in-place) vs $5.50 (Blue/Green). Not a close call.&lt;/p&gt;

&lt;p&gt;The root cause was something I'd been misreading for weeks. My RAG recall had been degrading because 512-token chunks were too coarse for ad insight data — shorter chunks improved top-1 similarity scores measurably. But because the degradation was gradual and silent, I kept blaming prompt quality and LLM behavior instead of the index structure. The $312 bill was the thing that finally made me look at the actual confidence scores in my query logs.&lt;/p&gt;

&lt;p&gt;One practical note on batch sizing: the Vectorize docs say 1,000 vectors per upsert request, but I hit intermittent timeouts above 500. Kept batches at 500 with retry logic and had zero failures across the full 180K rebuild.&lt;/p&gt;

&lt;p&gt;I wrote up the full breakdown — including the step-by-step rebuild procedure, the exact golden-set validation I used before flipping traffic, and where Blue/Green costs would flip negative at larger index sizes — over on riversealab.com.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://riversealab.com/en/posts/vectorize-index-rebuild-cost-downtime/" rel="noopener noreferrer"&gt;Full post →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>aiagents</category>
      <category>mcp</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>I tracked 34 'dead opens' in Obsidian over 3 weeks. That's why I switched to Tana.</title>
      <dc:creator>강해수</dc:creator>
      <pubDate>Mon, 15 Jun 2026 01:05:50 +0000</pubDate>
      <link>https://dev.to/riversea/i-tracked-34-dead-opens-in-obsidian-over-3-weeks-thats-why-i-switched-to-tana-49nn</link>
      <guid>https://dev.to/riversea/i-tracked-34-dead-opens-in-obsidian-over-3-weeks-thats-why-i-switched-to-tana-49nn</guid>
      <description>&lt;p&gt;34 times in three weeks, I opened Obsidian to capture something and closed it without writing a single word. I logged every instance. The tool had become intimidating enough that my brain was quietly vetoing the capture before it started — not because I was lazy, but because the filing overhead had become its own cognitive tax.&lt;/p&gt;

&lt;p&gt;The culprit was the decision stack. With PARA and 23 active plugins, every capture required a small negotiation: tag it, assign a project property, decide if "content pipeline" is a Project or a Resource this week. Tiago Forte's system is elegant in theory. At 20 context switches a day, it's just friction with good branding.&lt;/p&gt;

&lt;p&gt;Switching to Tana cut that down in a measurable way. I ran both tools in parallel for six weeks — same projects, same capture habits — and timed the capture flow directly. Average time to log an action item in Obsidian: 47 seconds, including navigation to the right note. Average in Tana: 11 seconds. The difference is Tana's supertag system: you type the item, hit a shortcut for your Task type, assign a project field, and it's indexed automatically. No filing decision. No folder negotiation. The item surfaces in your project view without you choosing where it lives.&lt;/p&gt;

&lt;p&gt;The tradeoff is real, though, and worth being honest about: Tana's export is not clean markdown. It's a structured JSON-ish format that took me two hours to make partial sense of. My Obsidian vault is a folder of plain text files I can open anywhere, forever. Tana's data lives in Tana. That's not a dealbreaker for me, but it's a genuine portability risk that the enthusiast community tends to wave away too quickly.&lt;/p&gt;

&lt;p&gt;I also lost the backlink graph — which I'll admit I used for more than vanity. It occasionally surfaced real connections: two client projects sharing the same constraint, a theme threading through unrelated research. Tana has no equivalent. You only find what you know to search for.&lt;/p&gt;

&lt;p&gt;I wrote up the full breakdown — including how I rebuilt my daily note template in Tana after four days of failed attempts, and what I'd tell someone still on the fence — over on dailyfocusmag.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dailyfocusmag.com/posts/obsidian-to-tana-migration/" rel="noopener noreferrer"&gt;Full post →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
      <category>workflow</category>
      <category>knowledgework</category>
    </item>
    <item>
      <title>Naver catalog quality score: the field that tanked impression share wasn't the one we were optimizing</title>
      <dc:creator>강해수</dc:creator>
      <pubDate>Mon, 15 Jun 2026 01:04:07 +0000</pubDate>
      <link>https://dev.to/riversea/naver-catalog-quality-score-the-field-that-tanked-impression-share-wasnt-the-one-we-were-57j4</link>
      <guid>https://dev.to/riversea/naver-catalog-quality-score-the-field-that-tanked-impression-share-wasnt-the-one-we-were-57j4</guid>
      <description>&lt;p&gt;A skincare brand I operate spent two weeks stuck at 23% impression share on Naver Shopping. We pushed bids. Raised budget. Nothing moved. The fix turned out to be two feed fields — neither of which was in the optimization checklist we'd been running.&lt;/p&gt;

&lt;p&gt;Naver's catalog quality scorer weights fields in ways that aren't documented anywhere officially. What I know comes from controlled feed tests across accounts running ₩8M–₩35M monthly Naver Shopping spend. The short version: &lt;code&gt;product_name&lt;/code&gt; and &lt;code&gt;category_id&lt;/code&gt; are the fields that actually move impression share. &lt;code&gt;description&lt;/code&gt; — which gets the most optimization time from almost every team I've seen — had under 2% impression share delta in my tests, whether the field was 50 characters or 500 keyword-stuffed characters. That gap between effort and impact is where most Naver Shopping operators bleed time.&lt;/p&gt;

&lt;p&gt;The toner brand incident illustrated this clearly. The Cafe24 default feed export was mapping products to a parent category (&lt;code&gt;화장품/미용 — 스킨케어&lt;/code&gt;) instead of the correct leaf-level category (&lt;code&gt;화장품/미용 — 스킨케어 — 토너/스킨&lt;/code&gt;). Naver's catalog matching runs on leaf-level category IDs. Parent-level mapping doesn't throw an error — it just quietly suppresses catalog inclusion. The quality score was sitting at 3 out of 6 with a category mismatch flag that's easy to miss in the admin UI. Second issue: images were exporting at 700×700, where Naver's catalog display for this category favors 1000×1000 with white background. Two field fixes, feed reprocessed in 36 hours, impression share went from 23% to 51% in 10 days. No bid changes.&lt;/p&gt;

&lt;p&gt;The morning check I now run on day 3 of every new catalog campaign is three questions: impression share below 40%? Quality score at 3 or below? Did the feed go live with default Cafe24 field mapping? If all three are yes, I don't touch bids until the feed is corrected. Bid optimization on a suppressed catalog is just burning budget against a structural ceiling.&lt;/p&gt;

&lt;p&gt;I wrote up the full breakdown — including the complete field impact hierarchy, the brand verification status issue that drops quality score by roughly a point, and the exact feed health checks I added to the daily workflow — over on themedilog.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://themedilog.com/posts/naver-catalog-feed-quality-score-impact/" rel="noopener noreferrer"&gt;Full post →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>d2c</category>
      <category>paidads</category>
      <category>analytics</category>
    </item>
    <item>
      <title>A Durable Object I thought was idle cost me $40/mo — here's exactly why</title>
      <dc:creator>강해수</dc:creator>
      <pubDate>Sat, 13 Jun 2026 09:25:48 +0000</pubDate>
      <link>https://dev.to/riversea/a-durable-object-i-thought-was-idle-cost-me-40mo-heres-exactly-why-2d20</link>
      <guid>https://dev.to/riversea/a-durable-object-i-thought-was-idle-cost-me-40mo-heres-exactly-why-2d20</guid>
      <description>&lt;p&gt;A Durable Object sitting "idle" with no incoming traffic burned $40 in a single month. No KV overages, no Worker invocation spikes — just an alarm rescheduling itself every 30 seconds.&lt;/p&gt;

&lt;p&gt;Here's the mechanic that got me: each alarm firing counts as one billable DO request &lt;em&gt;and&lt;/em&gt; starts the duration meter from zero if the object was evicted between firings. I had a session-aggregation object with this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;alarm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processPendingQueue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAlarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's 2,880 alarm firings per day per object. Across 15 active objects, that's 43,200 requests/day — still inside the free million. The actual damage was duration: each cold-start wakeup paid for warm-up time &lt;em&gt;plus&lt;/em&gt; the handler. When I noticed a missing D1 index, the alarm handler jumped from 12ms to 340ms average overnight. Duration charges tripled before I saw the alert. I found it in under two minutes with &lt;code&gt;wrangler tail my-worker --format pretty --status error,ok&lt;/code&gt; and watched alarm invocations scroll in real time.&lt;/p&gt;

&lt;p&gt;The broader lesson is that Durable Objects have three separate billing meters — requests, duration, and storage — and most billing surprises come from conflating them. A &lt;code&gt;stub.fetch()&lt;/code&gt; to your DO bills a request &lt;em&gt;and&lt;/em&gt; duration for the handler lifetime. A WebSocket upgrade bills one request upfront, then duration for the &lt;em&gt;entire connection lifetime&lt;/em&gt; while the object stays resident. Storage is almost always cheap (I hit $8/mo once, moved anything over 50KB to R2, and never saw it again). The expensive combinations are the ones that keep the object alive longer than you expect: self-rescheduling alarms, open WebSocket connections, and slow downstream calls inside handlers that extend wall-clock duration.&lt;/p&gt;

&lt;p&gt;I wrote up the full breakdown — including a reference table mapping every scenario to which meters tick, the WebSocket duration math, and the &lt;code&gt;idFromName()&lt;/code&gt; gotcha that silently routes traffic to the wrong class — over on dailymanuallab.com.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dailymanuallab.com/posts/durable-objects-pricing-deep-dive/" rel="noopener noreferrer"&gt;Full post →&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>serverless</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
