Pydantic 不是免费的——聊聊数据校验的边界
最近给一个推理后端服务做 perf,跑了一发 py-spy 出来火焰图,盯着看了一会儿,发现 pydantic.main.__init__ 这一条在主热路径上占了一个让人挺难绷的比例。做了几轮调整之后顺手把这件事记一下——核心想说的是:对一个 web 服务来说,数据校验从来不是免费的,pydantic 也一样。它有它的边界,越界使用就要付出对应的代价。 起因:火焰图里看到的东西某个推荐链路服务,写法上挺标准——FastAPI + uvicorn + pydantic + psycopg,业务里所有”长得像数据”的东西,从 DB 行、内部传递的中间态、到 API response,全部都用 BaseModel 表达。看起来一致、规整,review 起来也舒服。 直到我们因为 P95 不太好看,跑了一发 py-spy: 1py-spy record --duration 60 --rate 100 --output flame.svg --pid <uvicorn-worker-pid> 火焰图大约 8,700 个采样点,里面让我多看了一眼的是: pydantic/...
当我们在维护模型 API 服务时我们在维护什么
过去这一年我们团队陆陆续续做了几个 AI Service——大多是拿 FastAPI 包一个模型,再对外出 API。做到第三四个的时候我意识到一个尴尬的事实:每个项目长得都差不多,但每次起新项目还是在从零开始复制粘贴,而且不同的人写出来的目录结构、配置方式、metrics 命名都不太一样。一个新人接手第二个服务的时候,几乎要把第一个服务的认知全部重学一遍。 这篇文章想聊的不是某个具体框架,而是把这几个项目踩过的坑、迭代下来的设计、留下来的规范集中讲一遍——也算是给”维护一个模型 API 服务”这件事做一次复盘。 正文把”维护一个模型 API 服务”拆开看,其实是几个相互独立但又互相影响的小问题:项目长什么样、配置怎么分层、模型版本怎么迭代、可观测性怎么做、日常开发流程怎么走、CI 和镜像怎么发。下面按这几个维度逐个聊。 一个 AI Service 该长什么样先看目录结构。我们最终收敛到的样子是: 12345678910111213141516171819src/<service_name>/├── __init__.py # __version_...
怎么样 tracing 你的 SQL?
过年是个好时候,可以猛猛干活或者看论文。不过上了几天磨,看了几天论文后觉得还是需要整点活 所以这篇文章来聊聊一个经典话题,How to trace your SQL? 正文在 OpenTelementry 这一套开始铺展开来后,我们对于代码整个生命周期的 tracing 有了一个较为成熟的方案。包括各类 auto-instrumentation 的库,能够让我们在不修改代码的情况下就能对整个调用链进行 tracing。 但是始终有一朵乌云盘绕着 Tracing 世界的大厦上,我们怎么样将 SQL 的执行如同业务代码一样从黑盒中拆出来 在现阶段,我们对于整个 Tracing 的引入都是在做加法,我们选择构建一个 context,注入一些 metadata,让代码中不同的环节都可以获取到上下文。但是还是有一个问题,怎么样在 SQL 中做加法呢? 最直观的想法是,我们可以尝试一个类似机制 1234begin;set saka.tracing_id = '1234567890';select * from users where id = 1;commit; 我们...
笑ってほしくて
每年都会选一句话作为年终总结的标题,去年是“本当の僕らをありがとう”,今年我选择“笑ってほしくて”。 这句话出自 《葬送的芙莉莲》的片尾曲《Anytime anywhere》。 愿你露出笑容 可能是我想对逝去之人,逝去的猫说的,可能也是他们想对我说的 也是我想对所有看到这篇文章的人说的 开篇实际上一度想放弃写这篇文章,因为总是会害怕。今年在生死边缘搏战了许久。但终究是没有挽留住。每次想起点点滴滴时,总会不免破防 但是就如同歌词所说 こんなに胸が痛いのは/胸口传来的痛楚 あなたといた証かな/是与你同在的证明 那么所以还是要写点什么来纪念这特殊的2025, 也是 saka 的2025 生活今年生活中最大的事件就是猫咪的过世。小熊是我们在2023年从小区收养的猫咪。一最开始就给上了强度,重度口炎+肾衰。然后后续情况稳定后带回家进行日常的治疗。 而在24年经过一整年的折腾(开腹两次,病危三次),25年年初医生终于说可以两月复查后,我们以为小熊还能陪伴我们很长时间 但是事与愿违,在6月份过完到家纪念日后,小熊的状态就时好时坏。在8月份最后一次住进医院后,虽然期间还是有好转的时候...
成为榜样,但不要成为偶像
最近刚把空轨 1st 打通关。差不多国庆假期也要结束了,要开始准备接下来的学习和工作了。趁着这个机会,写点东西,当个迟到的 PyCon China 2025 的总结吧 正文其实去年在结束去年的会议后,我就在考虑要不要彻底退出 PyCon China 一段时间。原因其实也很简单,太累了。不过我向来是个很拖延癌的人。恰逢那时候正在职业的变动期,所以想着要不要再看看。 今年来了一家 AIGC 公司做 infra,说实话我干的还蛮开心的。每天都会学习新的东西。状态相比于去年好了不少(除了每天都需要和该死的 Any 做斗争),所以一度准备继续参加今年的 PyCon China 不过到了筹备期后,原本的计划突生变故。陪伴需求的猫咪病危,去世。一直在医院通宵,陪护以及最终要面临的生死别离。对我的精神和体力造成了极大的消耗。是否继续参加今年的 PyCon China 也成为一个问题。 不过最终还是决定参加了。今年突然出版社那边给我加了一个活,我翻译的书要在现场签售。这就又成为我心里一个新的疑问:嘛,真的会有人来买吗? 嘛,其实这也是每年在参加 PyCon 时都会有的自我怀疑,我真的有资格在这...
Further Performance Evolution in Python 3.14: Tail Call Interpreter
I’ve been overwhelmed by security work lately, so let me switch to something lighter to relax my mind. Python 3.14 has officially introduced a new mechanism called Tail Call Interpreter (Made by Ken Jin), which is undoubtedly another major milestone that lays the foundation for the future. Main ContentBefore discussing Python 3.14’s Tail Call Interpreter, we need to talk about the most basic syntax in C - switch-case. 12345678910switch (x) { case 1: // do something bre...
Python 3.14 的进一步性能进化: Tail Call Interpreter
最近做安全做的我头晕脑胀,来点轻松的换换脑子,让自己放松下 Python 3.14 正式引入了一个新的机制叫作 Tail Call Interpreter(Made by Ken Jin),这无疑又是一个奠定未来基础的重大工作 正文在聊 Python 3.14 的 Tail Call Interpreter 之前,我们先要来聊 C 语言中最基本的语法 switch-case 12345678910switch (x) { case 1: // do something break; case 2: // do something else break; default: // do default thing} 对于这样的代码我们最终的汇编会是什么样的呢?可能大家第一反应是先 cmp 然后 je ,不等式秒了,我们编译一个版本来看看 对于这样一段代码 12345678void small_switch(int x) { switch(x) { ...
Python 3.14: Python 世界的一大步
Python 3.14 目前主要的一些主要的特性其实已经固定了,在我看来,Python 3.14 是一个未来很多年的一个核心版本。因为其确定了是时代的 Python调试生态的基准,这篇文章将会来聊聊这个 Python 世界中的史诗级改进 正文在我们日常调试 Python 代码的时候,我们经常会遇到这样一个问题,我们需要采样当前的 Python Runtime 的状态,进而进一步调试我们的 Python 进程 常见的手段莫过于两种 通过 eBPF + UProbe 等手段来触发 通过 process_vm_readv 等 Syscall 来直接整块读取内存 无论这两种方式都有一个核心的问题,我们怎么样来解析内存中的数据? 用 https://github.com/jschwinger233/perf-examples/blob/main/cpython310_backtrace/bpf.c 来做一个例子,在之前的很多年的时候,我们会怎么做 12345678910111213141516171819202122232425262728293031323334353637383...
简单聊聊常见的负载均衡算法
这篇文章鸽了很久,最终决定还是老老实实写完,来介绍一下常见的一些负载均衡算法实现。本文的代码最终都会放在 load-balancer-algorithm1 这个 repo 中 我从来没有觉得写博客快乐过 正文先行准备既然是讲 LoadBalancer 中常用的一些负载均衡算法,我们先来对一些前置准备做一些讨论 我们目前需要两个基础的数据结构 代表着 Backend 节点的结构 代表着请求上下文的结构 那么我们可以得出下面一些基础代码 1234567891011121314151617181920import dataclasses@dataclasses.dataclassclass Node: host: str = "" port: int = 0 node_available: bool = True @property def available(self) -> bool: return self.node_availableimport dataclasses@dataclasses.dat...
简单吐槽一下摇曳露营的台配
看了一下台配摇曳露营的 PV 放松,有些地方很想吐槽,写个短文聊一下 正文先放出两版本对比 下面是原版 var dplayer0 = new DPlayer({"element":document.getElementById("dplayer0"),"autoplay":false,"theme":"#FADFA3","loop":true,"screenshot":true,"hotkey":true,"preload":"auto","video":{"url":"/videos/yuanban_yuru_S1E1.mp4"}}); 下面是台配 var dplayer1 = new DPlayer({"element":document.getElementById("dplayer1"),"autoplay":false,"theme":"#FADFA3","loop":true,"screenshot":true,"hotkey":true,"preload":"auto","video":{"url":"/videos/taipei_yuru_S1E1.mp4"}...








