最新资讯
dev.to 我们在 DolphinBench 上测试记忆检索:纯关键词搜索暴露了自身回归 dev.to 别再把 Agent 写成 while 循环,把它建模为状态机 bloomberg.com Rightmove 与 Scout24 借 AI 产品应对低迷房市并上调展望 stackoverflow.blog 欢迎来到芯片时代:半导体制造、AI 数据中心与芯片设计的未来 www.youtube.com AI Agent 总犯同样的错:CentralAgent AI 将记忆重构为经验系统 pub.towardsai.net Netflix 科学家建议重采样 LLM 评判:5 次仅将误差棒缩小 13% pub.towardsai.net Jev 作为 LLM 前的静默层:构建提示感知的模型路由器 reddit.com OpenAI 称 Astra 仅满足 10% 安全任务标准 dev.to FreeToken:在自有 GPU 上运行前沿 MoE 模型及其内存需求 dev.to 当 AI Agent 说“完成”时,究竟完成了什么? dev.to 部署 Underdog Bark 3.0 Pro:逐字转写、自定义后端与分块设置 dev.to 为艰难对话构建 AI:五个 Agent、一条安全正则与一位盲评监督者
多元动态
登录 注册
AI编码 dev.to · @yureki_lab

我让 Claude Code 写压测,上线前抓到 3 个瓶颈

原文 我的收藏

作者在客户 API(Node.js 22.x、Fastify、Postgres 16、Redis 7)上线前两周发现完全没有压测,于是用 Claude Code 在一个下午搭出 k6 测试套件,并称暴露出三个会在上线首日导致服务宕机的瓶颈。文章承诺给出具体提示词、k6 配置、出错环节以及适用于任何 AI 编码代理做性能测试的五条经验。摘要未披露瓶颈细节与实测数据,结论为作者自述。

AI推荐理由 实践型经验帖:作者在面向客户的 Node.js 22/Fastify/Postgres 16/Redis 7 API 上线前两周,用 Claude Code 生成 k6 压测套件,并称借此发现三个会在上线首日压垮服务的瓶颈。文中承诺给出具体提示词、k6 配置、失败点与五条经验。对用 AI 编码代理做性能测试的开发者有参考价值。局限:瓶颈细节与数据未在摘要中给出,效果为作者自述,缺少可独立验证的证据。

66

正文

TL;DR I had never written a real load test before a launch. I let Claude Code build a k6 suite for my Node.js API in an afternoon, and it surfaced three bottlenecks that would have taken the service down on day one. This post walks through the exact prompts, the k6 setup, what broke, and the five lessons I'd apply to any perf-testing work with an AI coding agent. ⚡ The Problem Two weeks before launching a customer-facing API (Node.js 22.x, Fastify, Postgres 16, Redis 7), I realized I had a pretty embarrassing gap: zero load testing. We had 340 unit tests. We had integration tests. We had a CI pipeline that ran in under ten minutes. What we did not have was any idea what happened when 200 people hit the checkout endpoint at the same time. I knew the theory. Ramp up virtual users, watch p95 latency, look for the cliff. But every time I sat down to write a k6 script, I hit the same wall: the API had 38 endpoints, most of them needed auth tokens and realistic payloads, and I did not want to hand-write 38 scenario files with fake data that looked nothing like production traffic. The constraint that made this interesting: I had about one working day to spend on it. Anything longer and it would eat into the launch buffer. So I decided to run an experiment. I would give Claude Code (v2.1, running in the terminal) the OpenAPI spec and the route handlers, and let it own the load-testing work end to end. My job would be to review, run, and interpret. How I Solved It Step 1: Feed it the shape of the traffic, not just the spec My first attempt was lazy. I pointed Claude Code at openapi.yaml and said "write k6 load tests for every endpoint." It produced 38 files that each hammered one endpoint with a fixed payload. Technically correct, practically useless. Real users don't call GET /orders/{id} in isolation; they log in, browse, add items, and check out. So I backed up and gave it context about the actual user journeys. Here is the prompt that worked: Here are the 3 user journeys that matter for launch, in order of business impact: 1. Browse → view product → add to cart → checkout (60% of traffic) 2. Login → view order history → view single order (30%) 3. Admin: list orders with filters, paginated (10%) Write a k6 test suite that: - Models these as 3 scenarios with the traffic split above - Reuses the auth token per virtual user (login once, not per request) - Generates realistic payloads from the Zod schemas in src/schemas/ - Ramps from 0 to 300 VUs over 5 minutes, holds 10 minutes, ramps down - Fails if p95 > 500ms or error rate > 1% on any scenario Don't write one file per endpoint. Organize by journey. Enter fullscreen mode Exit fullscreen mode The difference between "here's the spec" and "here's who uses this and how" was enormous. The second version produced a suite I actually wanted to run. Step 2: The k6 structure it generated It landed on a layout I have kept since: loadtest/ ├── config.js # thresholds, stages, base URL ├── lib/ │ ├── auth.js # login once per VU, cache token │ └── factories.js # payload generators derived from Zod schemas ├── scenarios/ │ ├── shopper.js │ ├── returning-customer.js │ └── admin.js └── main.js # wires scenarios + traffic split Enter fullscreen mode Exit fullscreen mode The load-bearing part is main.js. Here is the trimmed version: import { shopper } from './scenarios/shopper.js'; import { returningCustomer } from './scenarios/returning-customer.js'; import { admin } from './scenarios/admin.js'; export const options = { scenarios: { shopper: { executor: 'ramping-vus', exec: 'shopper', startVUs: 0, stages: [ { duration: '5m', target: 180 }, { duration: '10m', target: 180 }, { duration: '2m', target: 0 }, ], }, returning: { executor: 'ramping-vus', exec: 'returningCustomer', startVUs: 0, stages: [ { duration: '5m', target: 90 }, { duration: '10m', target: 90 }, { duration: '2m', target: 0 }, ], }, admin: { executor: 'constant-vus', exec: 'admin', vus: 30, duration: '17m', }, }, thresholds: { 'http_req_duration{scenario:shopper}': ['p(95) B[2 API replicas] B --> C[Pool: 10 conns each] C --> D[(Postgres: max 100)] style C fill:#f96,stroke:#333 The Postgres client pool was set to its default of 10 connections per replica. With two replicas, that is 20 concurrent queries max. Once more than about 20 requests needed the DB at the same time, everything else queued. The p95 went from 80ms at 100 VUs to 2,400ms at 140 VUs. Classic cliff. Claude Code spotted it from the log pattern: timeout exceeded when trying to connect spiking exactly when latency did. Fix was a one-liner in the pool config, bumped to 40 per replica, still comfortably under the Postgres limit. That alone got us to 220 VUs. Bottleneck 2: An N+1 hiding on one path At 220 VUs the returning-customer scenario started failing thresholds while shopper stayed fine. That asymmetry was the clue. The order history endpoint used a batching layer to load line items. The single-order endpoint did not. It looked like this: // Before: one query per line item 😬 const order = await db.orders.findById(id); for (const item of order.itemIds) { item.product = await db.products.findById(item.productId); } Enter fullscreen mode Exit fullscreen mode One order with 12 line items meant 13 queries. Under load, that path alone was generating more DB traffic than the entire shopper journey. Claude Code proposed the fix and wrote it: // After: one query, keyed lookup const order = await db.orders.findById(id); const products = await db.products.findByIds(order.itemIds.map(i => i.productId)); const byId = new Map(products.map(p => [p.id, p])); order.items = order.itemIds.map(i => ({ ...i, product: byId.get(i.productId) })); Enter fullscreen mode Exit fullscreen mode I had reviewed that file before. I had tests for that file. None of that caught it, because a 13-query endpoint is fast when one person calls it. Bottleneck 3: Logging was eating CPU The last one was the sneakiest. At 280 VUs, p95 crept up again, but the DB was fine and Redis was fine. CPU on the API replicas was pinned at 100%. Claude Code asked me to run a 30-second CPU profile during load. The top frame was JSON.stringify inside the request logger. Someone (me, six months ago) had added a log line that serialized the full request body at info level for "debugging." On a checkout request with a 40-item cart, that was serializing several kilobytes per request, 200 times a second. // Before logger.info({ body: req.body }, 'incoming request'); // After logger.info({ path: req.url, bodyBytes: req.headers['content-length'] }, 'incoming request'); Enter fullscreen mode Exit fullscreen mode Removing it dropped CPU to 60% at the same load and bought us the last 20 VUs of headroom. The final run After all three fixes: Metric Before After Max VUs before threshold failure 140 300+ (target hit) p95 latency at 300 VUs n/a (failed) 310ms Error rate at 300 VUs 11% 0.2% Wall-clock time spent 0 ~6 hours Six hours, including the runs themselves. Three bugs that would have surfaced on launch day in front of real customers. Lessons Learned 1. The agent is only as good as your traffic model "Write load tests for this API" gets you noise. "Here are the three journeys and their traffic split" gets you a real test. The single most important input was not the spec; it was me writing down who uses the system and in what order. If you can't describe your traffic in three bullet points, do that before touching an agent. 2. Derive payloads from your validation schemas, not from fixtures Letting the factories import the Zod schemas meant the load test could never drift out of sync with the API. This is one of those things an agent will suggest if you ask "how do we keep this from rotting," and it is worth asking every time. 3. Per-scenario thresholds beat global ones If I had used a single global p95 threshold, the N+1 on the order-detail path would have been averaged away by the fast shopper traffic. Tag your scenarios and set thresholds per tag. This was the difference between finding bug 2 and shipping it. 4. Let the agent correlate logs and metrics, but you run the profiler Claude Code was excellent at reading a k6 summary next to a log excerpt and saying "these two spikes line up, here is the likely cause." It could not run the CPU profile for me on staging, and I would not want it to. The division of labor that worked: the agent does the pattern matching and proposes the fix, I execute anything that touches a live environment. 5. Load tests find the bugs your unit tests are structurally blind to All three bottlenecks were invisible at concurrency 1. Pool sizing, N+1s, and hot-path serialization cost only show up under load. If your test pyramid has no load layer, you have a category of bugs you are guaranteed to find in production. An agent makes that layer cheap enough that "no time" stops being an excuse. What's Next Wire it into CI on a nightly schedule against staging, with results posted to a channel. A daily p95 trend line catches regressions before anyone notices. Add a soak test (2 hours at 60% of peak) to hunt memory leaks. The 17-minute run is too short to catch slow growth. Teach the agent to read the flame graph. Right now I run the profiler and paste the top frames. I want to try feeding it the raw profile output and see if it can find the hot path without my summary. Wrap-up If you have a launch coming and no load tests, block one afternoon, write your three traffic journeys on paper, and hand them to Claude Code with your schemas. You will probably find something. I found three somethings. If this was useful, follow me here on Dev.to for more build logs like this one. I write about running AI coding agents on real production work, including the parts that go wrong. And if you have found a bottleneck under load that no unit test could have caught, I would love to hear about it in the comments. 🚀

来源报道 · 1 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容