Skip to content
返回

Cloudflare Workers + D1 数据库踩坑记录

在初次开发 getcheapai.com 时,我使用的是 Astro,架构很简单:build 的时候用爬虫抓取一次数据,然后纯静态渲染出来。

但后来随着不断的加功能,我发现 Astro 的瓶颈越来越明显,因为网站逐渐从一个【纯静态内容站】变成了【比价工具】,而工具就难免涉及到很多交互,此时基于 Astro 的纯静态内容站点就有点力不从心了。

问了 AI,给了我两个方案:

  1. 继续用 Astro。虽然它是个纯静态网站生成器,但也支持嵌入 React 组件。
  2. 一步到位改为使用 Next.js。

我毫不犹豫选择了第二个。

这也是个典型的 Astro / Next.js 的选择案例。业务是会随着时间增长的,感觉但凡有点犹豫,那选择 Next.js 准没错。

之前的 Astro 架构是 build 时调用爬虫获取数据,现在用了 Next.js,我觉得这样做已经不太适用了,然后就想到引入一个数据库,爬虫负责写数据,Next.js 改为从数据库里读数据生成页面。

本来以为要单独买服务器,没想到 Cloudflare Workers 本身提供了 D1 数据库(本质是 SQLite),免费也能用,不得不说 Cloudflare 真香。

于是架构就变成了:

开始踩坑。

一、本地开发环境搭建:生成本地数据库

既然生产环境依赖 D1 数据库,那么本地开发时也要模拟一个这样的环境,所以需要先生成一个本地数据库。

具体步骤忘了,AI 干的 :joy: 大概就是 AI 先设计了一个数据库 Schema,然后用 Wangler CLI 用这个 Schema 生成了一个本地的 D1 环境。

二、本地开发环境搭建:爬虫数据写入本地数据库

Wangler 可以在本地开发时生成一个本地的数据库,但问题是现在爬虫在另一个目录作为一个单独的项目存在,所以第一个坑就是让爬虫往 Next.js 项目里的本地数据库写数据。

这一点可以通过设置配置文件和数据库路径来实现:

const { webRoot, webWranglerState } = resolveProjectPaths();
// webRoot 是一个绝对路径(相对路径试了,没用),类似 /Users/xxx/path/to/projects/next-js-project
// webWranglerState 指向的是 wrangler 在本地生成的数据库,类似 /Users/xxx/path/to/projects/next-js-project/.wrangler/state/v3

// 使用 Next.js 项目的本地 D1 数据库
// configPath 指向 Next.js 项目的配置文件
const { join } = await import('node:path');
this.proxy = await getPlatformProxy({
  configPath: join(webRoot, 'wrangler.jsonc'),
  persist: {
    path: webWranglerState
  }
})

console.log(`🔧 [开发] 已连接到本地数据库: ${webWranglerState}`);

等到正式爬数据的时候,就是通过 REST API 直接写入生产环境的 D1 数据库的。

三、生产部署:opennextjs-cloudflare

Next.js 的最佳部署平台是 Vercel,但是太贵了,现在甚至给请求数计费了。Cloudflare Workers 也能部署,但是需要一个中间层来做转换——opennextjs-cloudflare

它的大致原理就是等 next build 之后,将输出的目录转换成 Cloudfalre Workers 能跑的代码,其中把图片处理等能力都改为用 Cloudflare 的同等能力替代。

最好在一开始创建项目时就用 opennextjs-cloudflare 来创建。

四、opennextjs-cloudflare 的坑:支持度不够快

目前,opennextjs-cloudflare 支持到了 Next.js v15。v16 虽然也能用它,但是没有经过它的测试。而且实际使用时,发现了两个问题。

一是 next.js v16 建议将 middleware.ts 重命名为 proxy.ts,但是 opennextjs-cloudflare 不认识 proxy.ts。

二是 Cloudflare Worker 本身的问题导致只能固定 Next.js 的版本为 v16.0.10,见:[BUG] Next.js 16.1.0 crashes on Cloudflare Workers (Error 1101): Cannot assign to read only property ‘setImmediate’ of [object Module]

没办法,既然用了中间层转换,那就一定会有支持度落后的情况,但是 Cloudflare Workers 提供的免费套餐太香了,可以接受。

五、Next.js 生产环境报错问题

Next.js 如果在生产环境报错了,它不会把错误详情显示出来,所以后来只能让 AI 给可能报错的地方全都加上了日志,后来才发现是 next-intl 的报错,在静态渲染时必须额外配置,见 Setup local-based routing: Static rendering

这个错误也是蛮坑的,在本地开发环境不出现,到了生产才出现。

六:Cloudflare Workers 本身的 CPU 时间限制

进入模型详情页的时候,出现了这个报错:Error 1102: Worker exceeded resource limits.

查了一下,Cloudflare Workers 免费计划有一个限制:每个请求占用的 CPU 时间不能超过 10 毫秒。CPU 时间只计入计算所需的时间,比如查询数据库然后数据库在 100 毫秒之后才返回,这个 100 毫秒是不计入的。

照例交给 AI 分析,然后我自己也在翻看 Cloudflare 关于 CPU 时间的文档,然后注意到这句话:

… if you have CPU-bound tasks, such as large JSON payloads that need to be serialized, …

一下子就明白了。

为了省事,我的 SQLite 数据库是直接把爬取到的数据作为 JSON 字符串存进了一个字段当中,而爬虫的数据特别大,有 1 MB 多,问了下 AI,JSON.parse() 1 MB 多的字符串得 15 毫秒左右。

看来问题就是这里了,所以我又让 AI 重新设计表结构去了。

七:Cloudflare D1 数据库的坑

于是又是一段 Vibe Coding,让 AI 哼哧哼哧改好了表结构,本地开发也通过测试了,就在我以为一切终于大功告成的时候,问题来了。

每一次抓取的数据有 1.4 MB,之前是当成一个纯字符串上传到 D1 的,调用一次 REST API 就行。但是,在拆分成了多个表之后,这 1.4MB 的数据要分批次调用 REST API 了(AI 说 D1 的 REST API 无法做到批量上传数据),举例:

我硬着头皮运行了一下 AI 写的上传代码,现在已经上传了 20 分钟了,每一次插入 100 条价格数据,现在的进度是【已插入 8300 条价格数据…】

还有一种方式是先把数据导入本地 Wangler 数据库,然后导出整个数据库的数据,再用 Wangler CLI 上传。但是,这本质上是把整个数据库给导出导入了,而未来这个数据量会越来越多,估计会越来越慢。

难道我还是得单独做一个 API 服务器当作数据层?但是这样的话架构又复杂起来了。

回想起刚才用 Wangler CLI 上传数据的方案,我寻思能不能只上传我最近抓取的数据。没想到 AI 居然真的做到了,它先是把抓取的数据导入到本地 D1 数据库,然后在数据库里查询刚才导入的数据并生成一个 SQL 文件,最后再用 Wangler CLI 上传,整个过程不到 10 秒!

八:噩梦般的 CPU Time limit 以及 Cloudflare Workers 虽然免费但免费的不太多的问题

完成前面的改造之后,我大概试了一下,终于不再有 Worker exceeded CPU time limit. 问题了……吗?

而事实上是,前一段时间可能还好好的,可是多点几次之后,就会发现这个错误仍然存在。除此之外,我还遇到了别的问题,比如 Cloudflare D1 数据库在免费版的限制下,每天只能进行 10 万条数据插入。乍看之下很多,但是在我将 1.4 MB 的数据拆分成很多张表之后,每次抓取都会生成近 3 万条数据,3 次就会达到这个上限。

Cloudflare D1 的本地数据库也有一些问题,比如 3 万多条数据不能用同一个 db 实例来插入,中途会莫名其妙报错【Fetch failed.】,后来试了每插入一万条数据就销毁 db 实例并重新创建 db 实例,才成功完成。

上网查了一下,Cloudflare Workers 的 10ms 的 CPU 时间限制其实很苛刻,在运行 Next.js 时基本不能有任何外部调用(例如数据库),网络上遇到这个问题的人最后基本都是升级到了付费版。可是如果升级到付费版(有 30 秒的 CPU 时间,是 10 毫秒的 3 万倍,这里面也有个坑,文档上写着的是 10ms30s,在字很多的情况下,没仔细看我还以为是 30 毫秒,只是 3 倍)就能解决,那我当初做了这么多优化不就白费了 :joy:

而且付费版一年要 60 美元,这个价格都能买台服务器了,可以自由选择技术栈。

总结

最初只花费 3 块钱的 AI Coding 就开发完了基于 Astro 的初版,但是后来改造成 Next.js 的花费早已超过了 100 块(感觉快 200 块了),结果结论就是用 Cloudflare Workers 的付费版不如自己买服务器……感觉有点唏嘘。


分享这篇文章:

上一篇
Next.js 客户端初始值和服务器组件初始值不一致导致的闪烁问题解决方案
下一篇
全网 AI 中转站价格对比 getcheapai.com