每个奇数小时,一台廉价共享主机上的定时任务就会醒来,向YouTube数据接口请求美国、英国、德国、法国、印度、巴西、澳大利亚、加拿大这八个地区的热门视频元数据,然后全部写进同一个SQLite数据库里。这就是TrendVidStream——一个多地区视频发现站点的全部数据摄入管线。网站用PHP 8.4渲染页面,靠SQLite的全文搜索扩展FTS5驱动搜索,部署走的就是主机商提供的纯FTP通道。这套东西跑得很稳,单调,而且成本几乎可以忽略。
真正让这套架构吃力的,是作者在深夜十一点冒出来的那种问题:“这周有哪些视频在四个及以上的地区同时上了热门?它们的排名爬升速度有多快?音乐这个品类是不是正在悄悄吞掉其他所有内容?”这些都是分析型查询——全表扫描、窗口函数、跨快照关联。要是直接在线上SQLite文件上跑,要么就是跟正在接客的共享CPU抢资源,要么就是每次好奇心发作,都往笔记本上拷贝一个几百MB的数据库文件。
最终落地的方案其实出奇地轻巧:每天晚上把热表导出成按地区分区的Parquet文件,沿着部署已经在用的同一条FTP通道拉下来,然后在本地用DuckDB查询。整套逻辑加起来不过120行PHP和SQL,却把那种“我好奇一下”的问题,从一件二十分钟的苦差事,变成了一次四秒钟就能出结果的查询。
为什么不在SQLite上直接分析?SQLite在站点需要的场景下确实非常称职:按视频ID的点查、FTS5全文搜索、为分类页面做小范围排序扫描。它是一个单写入模型的按行存储引擎,而一个视频发现站点的访问模式跟这套设计完美贴合。但分析查询完全是另一种动物:它们喜欢扫全表——“每个视频在每个地区的播放量增量、横跨十四天”这种查询会把窗口内的每一行都碰一遍,行存储却必须把十八列全部读出来,哪怕用到的只有其中三列;它们还跑在错误的机器上,共享主机只给你可怜巴巴的一点CPU时间,还有一百八十秒的执行上限,一个四十秒的聚合查询既慢,也对自己站点的访客不礼貌;它们渴望历史数据,而生产表是当前状态表——每次拉取都会直接更新最新统计数据,你一旦想知道“它爬得有多快”,就需要快照,可要是硬把历史表塞进生产库,又会撑大那个被全文搜索和页面缓存共用的数据库文件;最关键的一点是,这些查询有一半都是死胡同,而死胡同理应留在笔记本上,不该留在正在接请求的服务器上。
DuckDB恰好就是那个明摆着的搭档:一个进程内运行的列式引擎,原生读取Parquet,正经支持窗口函数和分组集,用一条pip install duckdb命令或者一个静态二进制文件就能装好。没有服务端,没有集群,没有需要伺候的基础设施——当你的整个运维故事不过是定时任务加FTP时,这点显得格外要紧。在搭建任何导出管线之前,值得知道的是DuckDB可以通过它的sqlite扩展直接读取SQLite数据库。如果只是针对拷贝下来的.db文件做一次性探索,连导出这一步都可以省掉。但作者的选择很明确:每晚稳定的Parquet快照,让所有后续的探索都拥有完整的列式加速和历史追溯能力,而不必每次去碰那个几百MB的生产库副本。
这个案例没有引入新奇的组件,也没有用任何重型数据栈。它只是把已经存在的FTP通道和本地分析能力焊在了一起,用一百多行胶水代码,让“我想看看”这种冲动立刻就能得到回应。对于一个所有运维都建立在cron和FTP之上的个人项目来说,这大概就是最不折腾又最管用的答案了。
热门跟贴