我的备份爬得比爬虫还慢,因为我在用别人的 API 配额

· 6 分钟 · notes rclonebackupgoogle-driveoauthrestic

订阅到期了,我得搬一个 TB

这件事以最不方便的方式找上门:承载我每晚备份的那个 OneDrive 账号已经没有订阅了,而手上还躺着一个 1.5 年没用的 Google 订阅。把备份搬过去、保住历史、别丢一晚。

第一件撞上的不是技术问题:我记不清这套备份到底是谁在写。rclone?Duplicati?还是群晖自带的什么?在一套 78 个 compose 栈、三台机器的环境里(就是《一个人跑生产基础设施》里那套),「我的备份是谁写的」本该是我张口就答的问题,而它当时不是。所以第一步是去读机器。

现场到底有什么

两份 rclone 配置,正是那种会咬人的细节:Backrest(restic 的前端)用的是 GUI 那份,而另外还有一份陈旧的 CLI 副本,被某个 cron 或某次人手 shell 用过。改了一份,等于只改了一半系统。

搬迁移本身在顺序对的时候很无聊,而我是先读后动的:

rclone.conf                     追加 [gdrive-*] + [gdrive-*-crypt]
rclone/rclone.conf(CLI 副本)   同样两个 remote
Portainer stack 177             env → 新的 crypt remote;配置 :ro → :rw
restic init                     在 Google Drive 上建新仓库
Backrest SetConfig              dsm-repo 的 URI → 新位置

在动计划 URI 之前,我先去源码里确认了一件一切依赖它的事:restic 仓库的身份是 guid = sha256(id) —— 一个由仓库 id 派生的稳定值,不是从 URL 推出来的。所以把计划指到新后端,不可能悄悄把历史分叉。六个计划事后逐字节与原版一致,仓库的 id/guid 未被动过。

然后种子停住了,而日志里点了一个陌生人的名字

新仓库灌到 1075 GB 里的大约 2.88 GB —— 0.27% —— 然后慢成爬行。实时日志里有答案:

'Read requests per minute' ... reason: RATE_LIMIT_EXCEEDED
consumer: projects/202264815644        <- 不是我的项目
quota_limit_value: 840000

202264815644 是 rclone 自己的共享 OAuth 项目。我那个 TB 级种子吃的是每分钟读取配额,和地球上每一个 rclone 用户共池——这就是为什么它从约 750 KB/s 掉到零,而我的 NAS、我的磁盘、我的线路全都闲着。等我去量的时候,种子已经完全停摆:90 秒内 0 字节、159 个错误、依旧 rateLimitExceeded;而六个夜间计划也全撞同一堵墙:

Fatal: unable to save snapshot: unexpected HTTP response (500)
googleapi: Error 403: Quota exceeded for quota metric 'Queries'
  and limit 'Requests per minute' ...

在进度条里,「慢」和「被挡住」看起来一样,而它们是两个完全不同的问题。采样 90 秒得到的零,才是让我做出这个判断的东西。

你必须有自己的 OAuth 客户端,而控制台绕不过去

唯一的真修法只有一个:别再用共享客户端,用你自己的。也就是一个 Google Cloud 项目、一个 OAuth 同意屏、一个凭据。两个设置花掉了真正的时间:

我查过能不能用机器上已经认证好的 CLI 抄近路。不能,而原因值得记下来:这里的 gcloud 只以一个别人的项目里的服务账号登录,而服务账号无法创建同意屏或 OAuth 客户端 ID。凭据必须属于一个真人拥有的项目。这是一条死路——我是验证出来的,不是猜的。

授权流程里的三个坑

  1. 未经验证就发布的应用,可能会硬性堵死受限 scope。 .../auth/drive 是受限 scope,一个已发布但未完成 Google 审核的应用有时会被直接拒绝且没有旁路。我这一次旁路是存在的——「Google hasn’t verified this app」页面,然后 Advanced → Go to rclone-backup (unsafe)——但那取决于 scope 与审核状态,是一枚硬币,而不是保证;而「unsafe」只是个标签,意思是从没走过付费审核。
  2. 回调只接受一次请求。 刷新页面,或者开第二个标签,都会在没有 code 的情况下打到 127.0.0.1:53682,把这次尝试弄死。我第一次就是这么死的(no code returned),而它看起来像凭据错了。一个标签,别刷新。
  3. 同意屏必须在跑 rclone 的那台机器的浏览器里完成。 回调指向 localhost,所以手机或另一台电脑完不成——浏览器必须和等待那个端口的进程在同一台主机上。

之后就简单了:把 token 读出来,把 client_id + client_secret + token 写进两份配置(GUI 那份和陈旧的 CLI 副本),并恢复两个对长时无人值守上传很关键的开关:use_trash = false 与 stop_on_upload_limit = true。

旧的那一半:靠读,不靠问

清空旧 remote 时报了错——itemNotFound——而空间照样回来了:

Used:   1.012 TiB  ->  3.800 GiB
Free:   ~0         ->  1.007 TiB
DSM 文件夹: 在 -> 不在        回收站: 0 B

那个错误是 rclone 在干完活之后发的抱怨。另外值得记一笔:工具的文档说个人账号无法 permanentDelete,按它你就得手动清回收站——实际是回收站已经是空的,约 1 TB 就这么回来了,不需要任何浏览器步骤。文档描述的是 API,而 API 不总是你拿到的东西。

还有一条量出来的事,因为它改变了我下一个决定:把那台机器上共享的同步容器暂停之后,负载均值直接减半——16.70 → 8.89。这就是「网络慢」和「机器忙」的区别。

如果重来一次

  1. 从第一天起就用自己的 OAuth 客户端。 共享客户端 ID 是一条你排在里面、却看不到进度、也找不到人负责的队伍。
  2. 「我的备份是谁写的」要写在文档里,不靠记忆。 两份 rclone 配置,对一个一年没人画过图的系统来说是很正常的漂移量。
  3. 读目的地,别读退出码。 那次清空「失败」了,却释放了一个 TB;而一次看起来成功的改名,可能什么都没落地。跟《它报告成功,其实什么都没变》是同一课——这套环境一直在教这一课。
  4. 不要在注意力只剩最后百分之几的时候启动一个要跑几天的上传。 在 0.27% 时发现 RATE_LIMIT_EXCEEDED 只花几分钟;跑了两天才发现,花掉的是整个窗口。

结果,诚实地说

管道是通的:计划现在住在 Google Drive 上、仓库身份没变、种子花的是我的配额而不是共享池、旧的那一侧退回了大约 1 TB。但它现在还不是一个备份:写这篇的时候,第一轮种子才上传了约 1075 GB 里的 2.88 GB,而且新仓库里快照数为零——在第一张快照落地之前,什么都还原不了。那是我盯着的数字,因为「传输在跑」和「你能还原」是两句不同的话。

Lee Teong Hoe

Lee Teong Hoe · Mr Hoelee

Full-stack developer & DevOps engineer based in Malaysia. I run my own production infrastructure and build web systems for small businesses — this blog is the living record.

Hiring? Profile & stack · Malaysian SME? Website design & development ↗ · All posts