<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Yin</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://yrb127ing.ccwu.cc/</id>
  <link href="https://yrb127ing.ccwu.cc/" rel="alternate"/>
  <link href="https://yrb127ing.ccwu.cc/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Yin</rights>
  <subtitle>愿每一次都有回响</subtitle>
  <title>「云衣花容 晓梦迷蝶」</title>
  <updated>2026-10-20T16:00:00.000Z</updated>
  <entry>
    <author>
      <name>Yin</name>
    </author>
    <category term="技术" scheme="https://yrb127ing.ccwu.cc/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Cloudflare" scheme="https://yrb127ing.ccwu.cc/tags/Cloudflare/"/>
    <category term="踩坑" scheme="https://yrb127ing.ccwu.cc/tags/%E8%B8%A9%E5%9D%91/"/>
    <category term="架构" scheme="https://yrb127ing.ccwu.cc/tags/%E6%9E%B6%E6%9E%84/"/>
    <content>
      <![CDATA[<p>“不绑卡、不租服务器，把知识库跑起来”这句话是对的。但“跑起来”和“稳定跑”之间，隔着七个具体的数字限制。它们不会在写代码时提醒你，只会在批量任务跑到一半时给你一个 400。</p><p>下面每一条都是我在这个项目里真实撞上的，附上绕过方式和代价。</p><h2 id="1-Workers-AI-单请求-60000-token"><a href="#1-Workers-AI-单请求-60000-token" class="headerlink" title="1. Workers AI 单请求 60000 token"></a>1. Workers AI 单请求 60000 token</h2><p>嵌入模型不是按“条”计费，而是按 token。一次把 200 个 chunk 塞进去，最可能的结果是请求被拒，而不是排队。</p><p>我的做法是<strong>先算预算再分批</strong>：用 tokenizer 估算每段文本的 token 数，累加到接近 5 万就切一批，留出 20% 余量给模型自己的模板开销。批内再设一个条数上限，防止“200 条极短文本”这种边界情况。</p><p>代价是：请求数变多，总耗时线性增长，并且必须在失败时能定位到具体批次——所以每一批都带 <code>batch_index</code> 落日志，重试只重跑失败批次。</p><h2 id="2-KV-单个-value-上限-25MiB"><a href="#2-KV-单个-value-上限-25MiB" class="headerlink" title="2. KV 单个 value 上限 25MiB"></a>2. KV 单个 value 上限 25MiB</h2><p>大产物（全量索引 JSON、图谱快照）写不进 KV。这个限制最初表现为 413，后来发现不是请求体太大，而是 value 太大。</p><p>绕法有两种：</p><ul><li>拆成多个 key，用 <code>manifest.json</code> 记录分片顺序；</li><li>干脆不进 KV，把产物写进仓库，由 Pages 直接托管。</li></ul><p>我选了后者：图谱和规范判定的结果本来就需要公开访问，放进 KV 反而多一次鉴权。</p><h2 id="3-D1-单语句绑定参数约-100-个"><a href="#3-D1-单语句绑定参数约-100-个" class="headerlink" title="3. D1 单语句绑定参数约 100 个"></a>3. D1 单语句绑定参数约 100 个</h2><p><code>WHERE id IN (?, ?, ...)</code> 这种写法在 D1 上有绑定参数上限。超过之后不是慢，而是直接报错。图谱的邻域查询一次要查几十个节点、每个节点再查两边边，很容易超。</p><p>处理方式是把一批 ID 切成多个 <code>IN</code> 子句，分批执行再在内存里合并：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="comment">-- 每一批最多 90 个占位符，留一点余量</span></span><br><span class="line"><span class="keyword">SELECT</span> id, src, dst, rel, weight</span><br><span class="line"><span class="keyword">FROM</span> kg_edges</span><br><span class="line"><span class="keyword">WHERE</span> src <span class="keyword">IN</span> (?, ?, ...) <span class="keyword">OR</span> dst <span class="keyword">IN</span> (?, ?, ...);</span><br></pre></td></tr></table></figure><p>注意 <code>src IN</code> 和 <code>dst IN</code> 是两组占位符，所以实际单批只能放 45 个节点。这个数字要写在代码常量里，而不是散落在调用处。</p><h2 id="4-免费账户不支持子域名独立-Zone-路由"><a href="#4-免费账户不支持子域名独立-Zone-路由" class="headerlink" title="4. 免费账户不支持子域名独立 Zone 路由"></a>4. 免费账户不支持子域名独立 Zone 路由</h2><p>想让 <code>api.example.com</code> 指向 Worker，最直觉的做法是配一条 Zone Route。但免费套餐里，子域名的路由权限并不完整，配置会静默失效或直接拒绝。</p><p>最后的方案是<strong>同源转发</strong>：在 Pages 项目里放一个 Function，把 <code>/api/*</code> 转发到 Worker。好处不止是绕开权限：</p><ul><li>门户和 API 同源，Access 的登录凭据可以直接带上，不会反复弹验证码；</li><li>前端不用处理跨域和预检；</li><li>Cookie、CORS、鉴权都少一层心智负担。</li></ul><p>代价是多了一跳转发，Worker 里拿到的客户端 IP 需要从转发头里取，而不是直接信任 <code>CF-Connecting-IP</code>。</p><h2 id="5-配了-ACCESS-AUD-之后，所有请求都-401"><a href="#5-配了-ACCESS-AUD-之后，所有请求都-401" class="headerlink" title="5. 配了 ACCESS_AUD 之后，所有请求都 401"></a>5. 配了 ACCESS_AUD 之后，所有请求都 401</h2><p>这是最容易踩、也最容易被误判为“鉴权失败”的一条。</p><p>Cloudflare Access 校验 JWT 时，需要 <code>ACCESS_AUD</code>。一旦你把它当成唯一凭据，那些走 Bearer Token 的机器调用（CI、脚本、MCP）就全部被拒。正确做法是<strong>双通道</strong>：</p><ol><li>有 <code>Cf-Access-Jwt-Assertion</code> 头就校验 JWT；</li><li>没有就回退校验 <code>Authorization: Bearer &lt;secret&gt;</code>；</li><li>两者都没有才 401。</li></ol><p>关键是第 1 步失败时要明确区分“没带 JWT”和“JWT 无效”：前者应该继续走第 2 步，后者应该直接拒绝。</p><h2 id="6-Workers-域会被边缘拦截"><a href="#6-Workers-域会被边缘拦截" class="headerlink" title="6. Workers 域会被边缘拦截"></a>6. Workers 域会被边缘拦截</h2><p>在 Worker 内部回调 Pages 的接口时，用 <code>*.workers.dev</code> 域名有时会被边缘拦掉，返回 403 或直接超时。原因不在代码，在路由层。</p><p>解决办法是回调一律走 <strong>Pages 项目自己的域名</strong>。这条经验没有文档，只有日志：同一个请求换域名就通了。</p><p>顺带一提，本机访问 Cloudflare API 也存在“部分 IP 不可达”的情况，表现是单个请求卡两分钟才超时。我的处理是启动时并行探测多个可用 IP，之后复用连接，避免每个请求都重新握手。</p><h2 id="7-枢纽节点会让邻域查询爆炸"><a href="#7-枢纽节点会让邻域查询爆炸" class="headerlink" title="7. 枢纽节点会让邻域查询爆炸"></a>7. 枢纽节点会让邻域查询爆炸</h2><p>图谱里总有那么几个节点，连向所有文档：<code>device</code>、<code>ios</code>、<code>tools</code>。从它们出发做 2 跳邻域，结果会瞬间上千条，前端渲染卡死，查询也超时。</p><p>三条约束必须同时存在：</p><ul><li><strong>节点总数上限</strong>（例如 200）；</li><li><strong>每层扩展数上限</strong>（例如 50）；</li><li><strong>禁止穿透枢纽节点</strong>：邻居数超过阈值的节点只返回自身，不再向下展开。</li></ul><p>前两条控制结果规模，第三条控制深度。只做前两条的话，查询还是会先算完再截断，CPU 时间照烧。</p><h2 id="还有一条：把重计算放到离线管线"><a href="#还有一条：把重计算放到离线管线" class="headerlink" title="还有一条：把重计算放到离线管线"></a>还有一条：把重计算放到离线管线</h2><p>规范判定曾经跑在请求里，结果经常因为单请求 CPU 时间限制失败。后来把它整体移到离线管线：构建时算好、写进产物、请求只读结果。</p><p>这是架构层面的取舍：牺牲了“实时性”，换来了稳定性和可预测的延迟。对一个个人知识库来说，这个交换非常划算。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>免费额度不是“没有成本”，而是把成本从账单转移到了工程细节：</p><table><thead><tr><th>限制</th><th>触发表现</th><th>绕法</th></tr></thead><tbody><tr><td>60000 token</td><td>嵌入请求 400</td><td>预算分批 + 批次重试</td></tr><tr><td>25MiB &#x2F; value</td><td>KV 413</td><td>分片或改走 Pages</td></tr><tr><td>~100 绑定参数</td><td>D1 绑定报错</td><td>拆批 IN 查询</td></tr><tr><td>Zone 路由权限</td><td>配置无声失效</td><td>Pages Function 同源转发</td></tr><tr><td>ACCESS_AUD</td><td>机器调用全 401</td><td>JWT + Bearer 双通道</td></tr><tr><td>Workers 域</td><td>回调 403</td><td>走 Pages 域名</td></tr><tr><td>枢纽节点</td><td>邻域爆炸</td><td>限节点 + 禁穿透</td></tr></tbody></table><p>这七条都不难，难的是它们只会在你<strong>忘了</strong>的时候出现。现在它们被写进了项目的约束清单，下次换项目还能直接复用。</p>]]>
    </content>
    <id>https://yrb127ing.ccwu.cc/posts/seven-hard-limits-free-stack/</id>
    <link href="https://yrb127ing.ccwu.cc/posts/seven-hard-limits-free-stack/"/>
    <published>2026-10-21T12:30:00.000Z</published>
    <summary>0 元跑通知识库是真的，但每一个免费额度的上限都会在某天凌晨准时教你做人。七个真实限制，以及绕过去的具体做法。</summary>
    <title>免费栈的七个硬限制：Cloudflare 知识库踩坑清单</title>
    <updated>2026-10-20T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yin</name>
    </author>
    <category term="技术" scheme="https://yrb127ing.ccwu.cc/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="知识图谱" scheme="https://yrb127ing.ccwu.cc/tags/%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1/"/>
    <category term="规范判定" scheme="https://yrb127ing.ccwu.cc/tags/%E8%A7%84%E8%8C%83%E5%88%A4%E5%AE%9A/"/>
    <category term="推理" scheme="https://yrb127ing.ccwu.cc/tags/%E6%8E%A8%E7%90%86/"/>
    <content>
      <![CDATA[<h2 id="起点：一个装饰性图谱"><a href="#起点：一个装饰性图谱" class="headerlink" title="起点：一个装饰性图谱"></a>起点：一个装饰性图谱</h2><p>我的知识库门户里一直有个”图谱”页面：力导向布局、节点按来源着色、能拖拽缩放。<br>看起来很像回事。</p><p>直到我看了一眼数据：</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">nodes=1104  links=3</span><br></pre></td></tr></table></figure><p><strong>1104 个节点，3 条边。</strong> 边全部来自正文里的 <code>[[双链]]</code>，而我几乎不写双链。<br>这个页面唯一的用途是截图好看。</p><h2 id="为什么不直接用-LLM-抽"><a href="#为什么不直接用-LLM-抽" class="headerlink" title="为什么不直接用 LLM 抽"></a>为什么不直接用 LLM 抽</h2><p>2026 年这个方向最热的是 GraphRAG（微软）和 LightRAG（港大），<br>它们的做法都是：<strong>用 LLM 从文本里抽实体和关系</strong>。</p><p>我想过，但算了三笔账：</p><ol><li><strong>额度</strong>：免费 10,000 neurons&#x2F;天。用 LLM 抽 1187 篇文档，一次就可能吃掉大半，<br>而且抽取质量不稳定，重跑一次结果就变；</li><li><strong>可复现</strong>：规范判定要求”同输入同输出”。LLM 抽出来的图，今天和明天不一样，<br>基于它做的判定就没法解释；</li><li><strong>可维护</strong>：词表写错了，我能定位到具体词条；模型抽错了，我只能”再抽一次看看”。</li></ol><p>所以我选了确定性路线：<strong>结构化事实优先，LLM 留到以后按需补充</strong>。</p><h2 id="三层抽取"><a href="#三层抽取" class="headerlink" title="三层抽取"></a>三层抽取</h2><table><thead><tr><th>层</th><th>来源</th><th>产出</th><th>覆盖率</th></tr></thead><tbody><tr><td>L0 结构事实</td><td>路径、front-matter</td><td>文档→目录&#x2F;来源&#x2F;设备&#x2F;标签</td><td>100%</td></tr><tr><td>L1 显式链接</td><td><code>[[双链]]</code>、Markdown 相对链接</td><td>文档→文档</td><td>约 1250 条</td></tr><tr><td>L2 词典实体</td><td>内置词表 + 正则</td><td>文档→实体</td><td>约 540 条</td></tr></tbody></table><p>L0 是覆盖率的关键：每篇文档都”属于”某个目录、来自某个工具、在某台设备上。<br>这些关系不需要”理解”，只需要解析。<strong>先把确定的东西全拿到，再谈语义。</strong></p><p>最终：</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">nodes=1649  edges=15955</span><br><span class="line">belongs_to 9875  tagged 1907  mdlink 1248</span><br><span class="line">from_source 1187  on_device 1187  mentions 539</span><br></pre></td></tr></table></figure><h2 id="三个必须处理的坑"><a href="#三个必须处理的坑" class="headerlink" title="三个必须处理的坑"></a>三个必须处理的坑</h2><h3 id="坑-1：枢纽节点会毁掉可视化"><a href="#坑-1：枢纽节点会毁掉可视化" class="headerlink" title="坑 1：枢纽节点会毁掉可视化"></a>坑 1：枢纽节点会毁掉可视化</h3><p><code>device</code> 和 <code>source</code> 这两个节点几乎连到所有文档。从任意文档出发走两跳：</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">文档 → 设备 → 另外 295 篇文档</span><br></pre></td></tr></table></figure><p>图上一秒变毛线球。解法是<strong>展示但不穿透</strong>：枢纽类节点画出来，但不再从它继续扩展；<br>再加单层 60 个节点的上限。</p><h3 id="坑-2：伪双链"><a href="#坑-2：伪双链" class="headerlink" title="坑 2：伪双链"></a>坑 2：伪双链</h3><p>正文里的 Markdown 示例会被误当成真链接：</p><figure class="highlight markdown"><table><tr><td class="code"><pre><span class="line">比如可以写 [[&#123; &quot;tag&quot;: &quot;md&quot;, &quot;text&quot;: &quot;...&quot; &#125;]]</span><br></pre></td></tr></table></figure><p>原来的 31 条”断链”里，有 24 条是这种噪声。加了一条过滤规则<br>（长度、非法字符、协议前缀）后剩 7 条真实断链。</p><h3 id="坑-3：中文分词"><a href="#坑-3：中文分词" class="headerlink" title="坑 3：中文分词"></a>坑 3：中文分词</h3><p>FTS5 用 <code>unicode61</code>，会把一整串中文当成一个 token。<br>所以「索引管线怎么分批嵌入」这种整句查询，MATCH 命中是 0，LIKE 兜底也难。<br>短期靠”查两个关键词”，中期要接 <code>jieba</code> 预分词。</p><h2 id="规范判定：让规则可写、可复现"><a href="#规范判定：让规则可写、可复现" class="headerlink" title="规范判定：让规则可写、可复现"></a>规范判定：让规则可写、可复现</h2><p>规则放在内容仓的 <code>policy/rules/*.yaml</code>，和笔记一起版本化：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">id:</span> <span class="string">fm.required</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">front-matter</span> <span class="string">必填字段</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">front_matter</span></span><br><span class="line">  <span class="attr">severity:</span> <span class="string">error</span></span><br><span class="line">  <span class="attr">params:</span></span><br><span class="line">    <span class="attr">required:</span> [<span class="string">&quot;title&quot;</span>, <span class="string">&quot;source&quot;</span>]</span><br></pre></td></tr></table></figure><p>关键设计是 <strong>kind 是封闭枚举</strong>，YAML 只传参数、不写 SQL。<br>内容仓是可写的（笔记软件、网页、客户端都能改），如果规则里能塞 SQL，<br>等于给内容仓开了一个注入面。</p><h2 id="结果"><a href="#结果" class="headerlink" title="结果"></a>结果</h2><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">1187 篇 × 13 条规则 → 2068 条判定记录</span><br><span class="line">违规 881 条：warn 125 + info 756，error 0</span><br><span class="line">按规则：doc.orphan 756 / title.dup 107 / naming.no_space 11 / 断链 7</span><br></pre></td></tr></table></figure><p><code>error = 0</code> 说明敏感信息扫描（私钥、API Key）在内容仓里零命中——这是我最想看到的结果。</p><p>至于那 756 条”孤立文档”：它们全是机器导入的会话记录，本来就没有入链。<br><strong>规则的意义不是消灭数字，而是让数字可解释。</strong></p><h2 id="还没做的"><a href="#还没做的" class="headerlink" title="还没做的"></a>还没做的</h2><ul><li>传递闭包目前只到 10 轮，复杂层级还没压测；</li><li>图谱的社区聚类（Leiden）没做——要做也打算用纯 TS，不调 LLM；</li><li>增量判定：现在每次全量重算，规模再大就得改。</li></ul><p>下一步如果做，我会先做<strong>增量</strong>，而不是做更花哨的图算法。<br>因为真实使用中，”改一篇笔记后多久能看到判定更新”比”图好看不好看”重要得多。</p>]]>
    </content>
    <id>https://yrb127ing.ccwu.cc/posts/graph-from-3-to-15955/</id>
    <link href="https://yrb127ing.ccwu.cc/posts/graph-from-3-to-15955/"/>
    <published>2026-10-21T02:00:00.000Z</published>
    <summary>一个只有 3 条边的&quot;图谱&quot;不算图谱。记录我怎么用确定性抽取把它变成 15955 条，以及为什么不用 LLM。</summary>
    <title>知识图谱：从 3 条边到 15955 条边</title>
    <updated>2026-10-22T02:27:37.303Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yin</name>
    </author>
    <category term="技术" scheme="https://yrb127ing.ccwu.cc/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Cloudflare" scheme="https://yrb127ing.ccwu.cc/tags/Cloudflare/"/>
    <category term="Workers" scheme="https://yrb127ing.ccwu.cc/tags/Workers/"/>
    <category term="D1" scheme="https://yrb127ing.ccwu.cc/tags/D1/"/>
    <category term="Vectorize" scheme="https://yrb127ing.ccwu.cc/tags/Vectorize/"/>
    <content>
      <![CDATA[<h2 id="架构一览"><a href="#架构一览" class="headerlink" title="架构一览"></a>架构一览</h2><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">Markdown 仓库（私有）</span><br><span class="line">   │ git push</span><br><span class="line">   ▼</span><br><span class="line">GitHub Actions  ── 抽取/切分/嵌入/判定 ──▶ Cloudflare</span><br><span class="line">                                            ├─ D1（文档、切片、三元组、判定）</span><br><span class="line">                                            ├─ Workers KV（索引产物）</span><br><span class="line">                                            ├─ Vectorize（语义向量）</span><br><span class="line">                                            └─ Worker（查询 API）</span><br><span class="line">                                                   ▲</span><br><span class="line">                                     Pages + Access（只给自己看）</span><br></pre></td></tr></table></figure><p>选这套的原因很朴素：<strong>没有一个组件需要常驻服务器</strong>。<br>Neo4j、OpenSPG 这类图数据库确实更强，但要一台机器一直开着，就不符合”零成本”。</p><h2 id="四个撞过的硬限制"><a href="#四个撞过的硬限制" class="headerlink" title="四个撞过的硬限制"></a>四个撞过的硬限制</h2><h3 id="1-嵌入请求有-6-万-token-的总上限"><a href="#1-嵌入请求有-6-万-token-的总上限" class="headerlink" title="1. 嵌入请求有 6 万 token 的总上限"></a>1. 嵌入请求有 6 万 token 的总上限</h3><p>第一次跑索引，第一批就 400：</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">AiError: Max context reached 99392 tokens but model supports only 60000</span><br></pre></td></tr></table></figure><p>原因是按固定 64 条一批，而中文分片每片接近 900 token，64 × 900 直接顶破。<br>改成<strong>按估算 token 预算动态分批</strong>，并对单条做字符上限保护；仍失败就二分重试。</p><h3 id="2-KV-的单键上限是-25-MiB"><a href="#2-KV-的单键上限是-25-MiB" class="headerlink" title="2. KV 的单键上限是 25 MiB"></a>2. KV 的单键上限是 25 MiB</h3><p>索引产物里有一份 <code>embeddings.jsonl</code>，88 MB，PUT 直接 413：</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">content size of 87905171 bytes exceeds maximum allowed size of 27MiB</span><br></pre></td></tr></table></figure><p>后来发现这个键<strong>运行时根本没人读</strong>——向量在 Vectorize 里，Worker 只读<br><code>docs.json</code> 和 <code>meta.json</code>。删掉即可。<strong>先查清楚谁在读，再决定怎么存。</strong></p><h3 id="3-D1-单条语句的绑定参数有上限"><a href="#3-D1-单条语句的绑定参数有上限" class="headerlink" title="3. D1 单条语句的绑定参数有上限"></a>3. D1 单条语句的绑定参数有上限</h3><p>想在一条 SQL 里从 300 个节点取边：</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line"><span class="keyword">WHERE</span> e.src <span class="keyword">IN</span> (?,?,...) <span class="keyword">OR</span> e.dst <span class="keyword">IN</span> (?,?,...)   <span class="comment">-- 600 个参数</span></span><br></pre></td></tr></table></figure><p>直接 500。官方文档没写这个数，实测大约 100。改成拆两条 SQL、每批 50 个 id，<br>再用 id 去重合并。</p><h3 id="4-免费神经元额度是-10-000-天"><a href="#4-免费神经元额度是-10-000-天" class="headerlink" title="4. 免费神经元额度是 10,000&#x2F;天"></a>4. 免费神经元额度是 10,000&#x2F;天</h3><p>全量重建一次约 3000 neurons（4290 片 × 约 700 token），<br>所以<strong>一天最多重建三次</strong>。这直接决定了：增量索引不是”优化”，是”必需”。</p><h2 id="一个反直觉的设计：判定放在离线"><a href="#一个反直觉的设计：判定放在离线" class="headerlink" title="一个反直觉的设计：判定放在离线"></a>一个反直觉的设计：判定放在离线</h2><p>一开始我想把规则引擎放进 Worker，请求时现算。算了一下：1187 篇 × 13 条规则 ≈ 1.5 万条判定，<br>而 Workers 免费档单请求 CPU 只有 10 ms——跑不完，也写不完。</p><p>于是把判定挪到管线里离线跑（一次约 3 秒），Worker 只做”读”。<br>副产品是：判定结果成了<strong>可查看的历史快照</strong>，而不是每次请求都变的临时值。</p><h2 id="代价与收益"><a href="#代价与收益" class="headerlink" title="代价与收益"></a>代价与收益</h2><table><thead><tr><th>项</th><th>结果</th></tr></thead><tbody><tr><td>月成本</td><td>0 元</td></tr><tr><td>全量索引耗时</td><td>约 4 分钟（Actions）</td></tr><tr><td>查询延迟</td><td>毫秒级（D1 &#x2F; KV 命中）</td></tr><tr><td>规模上限</td><td>Vectorize 5M 维度，当前已用 87.8%</td></tr></tbody></table><p>最后一行是唯一的隐患：知识库再涨约 14%，语义检索就会超限。<br>到那天的退路已经想好了——降级成纯关键词检索，功能不崩，只是没那么”聪明”。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>免费额度的真正价值不是”省钱”，而是<strong>逼你把架构想清楚</strong>：<br>不能堆组件，就得想清楚每一层到底在解决什么问题。<br>这套系统里，真正不可替代的只有两件事——<strong>分片策略</strong>和<strong>规则</strong>。<br>其余的，都是可以替换的实现细节。</p>]]>
    </content>
    <id>https://yrb127ing.ccwu.cc/posts/cloudflare-free-kb-stack/</id>
    <link href="https://yrb127ing.ccwu.cc/posts/cloudflare-free-kb-stack/"/>
    <published>2026-10-20T02:00:00.000Z</published>
    <summary>不绑卡、不租服务器，把检索、图谱、规范判定都跑在免费额度里，以及一路上踩到的四个硬限制。</summary>
    <title>用 Cloudflare 免费额度搭一套私有知识库</title>
    <updated>2026-10-20T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Yin</name>
    </author>
    <category term="杂记" scheme="https://yrb127ing.ccwu.cc/categories/%E6%9D%82%E8%AE%B0/"/>
    <category term="Cloudflare" scheme="https://yrb127ing.ccwu.cc/tags/Cloudflare/"/>
    <category term="知识管理" scheme="https://yrb127ing.ccwu.cc/tags/%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86/"/>
    <category term="自托管" scheme="https://yrb127ing.ccwu.cc/tags/%E8%87%AA%E6%89%98%E7%AE%A1/"/>
    <content>
      <![CDATA[<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>我的笔记散在四个地方：Obsidian 里一部分、几个 AI 工具的会话记录里一部分、浏览器书签里一部分、<br>还有一部分在「等我有空再整理」的文件夹里。</p><p>结果是：<strong>收藏越多，越找不到</strong>。</p><p>真正让我下决心的是一个很具体的问题——我想找半年前写过的一段<br>「Cloudflare Workers 怎么复用连接」的笔记，翻了三个 App 都没找到。<br>那一刻我意识到，我缺的不是存储，是<strong>检索和结构</strong>。</p><h2 id="我想要的到底是什么"><a href="#我想要的到底是什么" class="headerlink" title="我想要的到底是什么"></a>我想要的到底是什么</h2><p>我把需求写成了四条，后来整个系统都是围绕这四条长出来的：</p><ol><li><strong>一个地方</strong>：所有笔记最终汇到一个仓库，不管原来在哪；</li><li><strong>搜得到</strong>：关键词能搜，语义也能搜；</li><li><strong>看得见关系</strong>：这篇笔记和哪些文档有关，为什么有关；</li><li><strong>判得准</strong>：我的知识库有没有违反自己定的规范（缺元数据、断链、过期、含密钥）。</li></ol><p>第 4 条听起来最玄，其实是最后落地时最有用的一条——见下面。</p><h2 id="为什么不用现成的"><a href="#为什么不用现成的" class="headerlink" title="为什么不用现成的"></a>为什么不用现成的</h2><p>我试过几个方向：</p><ul><li><strong>云笔记 + 全平台同步</strong>：同步没问题，但检索只有关键词，没有关系；</li><li><strong>现成的 RAG 工具</strong>：能问答，但索引是黑盒，我改不了它的分片策略；</li><li><strong>笔记软件自带的图谱</strong>：好看，但只是”双链的可视化”，不是可计算的图。</li></ul><p>共同的问题是：<strong>它们不让我碰索引</strong>。</p><p>而知识库的价值恰恰在索引层——分片怎么切、实体怎么抽、规则怎么判。<br>所以我决定自己写一个，只在索引层花功夫，其他都尽量简单。</p><h2 id="最后的形态"><a href="#最后的形态" class="headerlink" title="最后的形态"></a>最后的形态</h2><ul><li><strong>存储</strong>：Markdown 仓库（GitHub 私有仓），跨设备靠 git 同步；</li><li><strong>索引</strong>：GitHub Actions 里跑，产出切片、向量、三元组、判定结果；</li><li><strong>查询</strong>：Cloudflare Worker + D1 + Vectorize，全部落在免费额度内；</li><li><strong>入口</strong>：一个网页门户 + 一个桌面客户端 + 一套 MCP 工具。</li></ul><p>关键取舍是：<strong>不绑卡</strong>。所以没有用 R2，索引产物直接写 Workers KV，<br>原文由 Worker 通过 GitHub API 直读。</p><h2 id="第-4-条需求意外地有用"><a href="#第-4-条需求意外地有用" class="headerlink" title="第 4 条需求意外地有用"></a>第 4 条需求意外地有用</h2><p>我原本以为”规范判定”是个锦上添花的功能，直到它第一次跑出结果：</p><figure class="highlight text"><table><tr><td class="code"><pre><span class="line">warn  标题重复    knowledge/memory/dsh/profiles/desktop/cordis.yml</span><br><span class="line">      与 1 篇文档同名，容易混淆</span><br><span class="line">warn  双链断链    31 处 → 过滤掉代码示例里的伪链接后剩 7 处</span><br><span class="line">info  孤立文档    756 篇没有入链</span><br></pre></td></tr></table></figure><p>756 这个数字很刺眼，但它是对的：那批文档是机器导入的会话记录，本来就没有入链。<br><strong>规则的价值不是”发现问题”，而是”让问题可度量”</strong>。有了度量，才知道先修什么。</p><h2 id="下一步"><a href="#下一步" class="headerlink" title="下一步"></a>下一步</h2><p>语义检索和关系图谱已经能用了，但我还想加两件：</p><ul><li>中文分词后再进全文索引（现在 <code>unicode61</code> 会把整句中文当成一个 token）；</li><li>增量索引（现在每次全量重建都吃一次嵌入额度）。</li></ul><p>这两件事都不难，难的是别把系统搞复杂。这个博客大概会记录它们。</p>]]>
    </content>
    <id>https://yrb127ing.ccwu.cc/posts/why-my-own-kb/</id>
    <link href="https://yrb127ing.ccwu.cc/posts/why-my-own-kb/"/>
    <published>2026-10-18T02:00:00.000Z</published>
    <summary>从「到处收藏」到「能查、能判、能推理」，我做了一个只给自己用的知识库。</summary>
    <title>为什么我要做一个自己的知识库</title>
    <updated>2026-10-20T16:00:00.000Z</updated>
  </entry>
</feed>
