逐日AI

面试题库

共 328 题,当前筛选 4 题。

30 天从前端工程师到 Agent 工程师

D14 部署运维:compose 多 worker、心跳、健康检查、优雅停机、dev/prod 隔离;W2 复盘

  • 什么是优雅停机?为什么直接 kill 进程有风险?请说出具体步骤。What is graceful shutdown, and why is killing a process outright risky? Walk through the steps.
    国内高频海外高频进阶#deployment#reliability#operations

    分析过程 · 先想清楚再作答

    1. 这题考的是「你有没有真的发过版」。答「等任务跑完再退出」只是定义,面试官要的是代价、步骤和顺序。
    2. 先把代价说具体。发版、缩容、机器维护、抢占式实例回收都会先发 SIGTERM、等宽限期、超时 SIGKILL。SIGKILL 拦不住,落到正在跑 Agent 循环的 Worker 身上:这次的 run 永远停在 running,用户界面一直转圈;模型调用的钱已经付了,回复却没落库;没确认的消息要等空闲阈值到了才被别人接手,用户白等一轮。一次发版掐断几十次对话,这就是日常代价。
    3. 然后给三步,强调顺序不能变:第一步拒新——把开关拨过去,消费循环下一轮不再从流里取消息(已经读到手上还没开始的那几条,留在 pending 里由别人接手,比硬扛完一整批更快);第二步等手头这次执行跑完,但要有上限;第三步主动交还租约、从心跳面板注销,然后退出。
    4. 第二步的上限是加分点:一次卡死的模型调用会让你永远等不到,而宽限期一到照样 SIGKILL。与其被动挨刀,不如自己认输退出——没确认的消息还在 pending 里,别人会接手重做。本课取 20 秒,取法是「一次正常执行的耗时上限」再留余量。
    5. 第三步也是加分点:租约本来靠 TTL 到期自然易主,但那是为进程猝死准备的。计划内下线你明知道自己要走,主动交还能让接手方下一轮扫描就上岗,而不是白等一个 TTL。交还必须带条件——只删还写着自己名字的那把牌子,否则租约已过期、别人刚抢到时,你就把对方的值班牌撕了。
    6. 最后两件配套的事,漏一件前面全白做:宽限期的配置必须大于代码里的等待上限(代码等 20 秒而 compose 默认只等 10 秒,第 10 秒就 SIGKILL,三步只走到一半);以及信号得真的传到你的进程(启动命令写成包管理器,PID 1 就是包管理器,SIGTERM 未必传得到,停机代码一次都不会执行)。

    How to reason about it · think before answering

    1. This question tests whether you have actually shipped a release. Reciting finish in-flight work before exiting is just the definition; the interviewer wants the cost, the steps, and the ordering.
    2. Make the cost concrete. Deploys, scale-downs, host maintenance and spot reclamation all send SIGTERM, wait a grace period, then SIGKILL. SIGKILL cannot be trapped, and landing it on a worker mid-agent-loop means: the run is stuck in running forever while the user watches a spinner; you already paid for the model call but never persisted the reply; the unacked message waits for the idle threshold before anyone claims it. One deploy cuts off dozens of conversations — that is the everyday cost.
    3. Then give three steps and stress that the order is fixed. One, stop accepting work: flip a flag so the consume loop stops reading from the stream (messages already fetched but not started stay in pending for someone else, which is faster than forcing a whole batch through). Two, wait for the in-flight execution, but with a ceiling. Three, proactively release leases, deregister from the heartbeat dashboard, and exit.
    4. The ceiling in step two earns points: a hung model call means you wait forever, and the grace period will SIGKILL you anyway. Better to concede and exit — the unacked message is still pending and someone will redo it. This course uses 20 seconds, derived from the upper bound of a normal execution plus margin.
    5. Step three also earns points: leases normally change hands via TTL expiry, but that path exists for sudden death. On a planned shutdown you know you are leaving, so releasing proactively lets the successor take over on its next scan instead of waiting out a full TTL. The release must be conditional — delete only the badge that still bears your name, or you will tear down the badge of whoever just claimed it after your lease expired.
    6. Finish with two companions; miss either and the rest is wasted. The configured grace period must exceed the wait ceiling in code (code waits 20s while compose defaults to 10s, so SIGKILL lands at second 10 and your three steps only half-run). And the signal must actually reach your process (if the entrypoint is a package manager, PID 1 is the package manager, SIGTERM may never arrive, and your shutdown code never runs once).

    答题要点

    • 直接 kill 的具体代价:run 永远停在 running、用户界面一直转圈、模型的钱已付但回复没落库、没确认的消息要等空闲阈值才被接手
    • 三步且顺序不能变:拒绝新任务 → 等手头的跑完(有上限)→ 主动交还租约并注销心跳,然后退出
    • 等待必须有上限(本课 20 秒):卡死的模型调用会让你永远等不到,宽限期一到照样被 SIGKILL,不如自己认输,消息还在 pending 里
    • 主动交还租约让接手方下一轮就上岗,而不是白等一个 TTL;交还必须条件化,只删还写着自己名字的那把
    • 宽限期配置必须大于代码里的等待上限,否则三步只执行到一半(compose 的 stop_grace_period / K8s 的 terminationGracePeriodSeconds)
    • 信号要真传到进程:用 exec 形式直接起业务进程,别让包管理器当 PID 1

    Key points

    • Concrete cost of a hard kill: the run is stuck in running, the user stares at a spinner, the model call is paid for but the reply is unsaved, and the unacked message waits out the idle threshold
    • Three steps in a fixed order: refuse new work, wait for in-flight work with a ceiling, then release leases and deregister before exiting
    • The wait needs a ceiling (20s here): a hung model call never returns and the grace period kills you anyway, so concede — the message is still pending for someone else
    • Releasing leases proactively lets the successor start on its next scan instead of waiting a full TTL; the release must be conditional on still owning it
    • The configured grace period must exceed the in-code wait ceiling, or the three steps only half-run (stop_grace_period / terminationGracePeriodSeconds)
    • Make sure the signal reaches your process: exec the business process directly rather than letting a package manager be PID 1
  • 滚动发布时,如何避免正在处理的任务被打断?During a rolling deploy, how do you keep in-flight tasks from being interrupted?
    国内高频海外高频进阶#deployment#reliability#operations

    分析过程 · 先想清楚再作答

    1. 这题是上一题的应用题,区别在于它要求你把编排系统那一侧也讲进来——只讲进程内的三步只答了一半。
    2. 完整骨架是两侧配合:编排系统先摘流量(把就绪探针转红,让负载均衡不再把新请求打过来)、再发 SIGTERM、然后等宽限期;进程在这段时间里把手头的活做完、交还所有权、干净退出。这一句话就是答案的主干,剩下都是细节。
    3. 然后区分两类副本,这是拿分点。Gateway 有入站连接,摘流量对它有意义;Worker 没有任何入站连接,它是自己去总线取活的,所谓「摘流量」对它就是「自己不再取新消息」——也就是停机三步的第一步。**同一个词在两类副本上是两种机制**,能说清这一点说明你理解拉与推的差别。
    4. 接着讲批次与顺序:一次只换一部分副本(compose 里手动分批,K8s 里靠 maxUnavailable / maxSurge),保证任何时刻都有足够的存活副本接得住流量。心跳面板上的版本号字段这时派上用场——你能看到新旧两批各剩几个,而不是盲发。
    5. 还要提一句状态兼容:滚动发布期间新旧代码同时在线,所以数据库迁移必须向后兼容(先加可空列、再双写、最后才删旧列),消息格式也不能一次性改。这是很多人漏掉的一层——进程停得再优雅,新旧版本读不了同一份数据照样出事故。
    6. 可以预期的追问:如果一次执行本来就要跑 5 分钟,宽限期不可能等那么久怎么办?答案不是把宽限期拉到 5 分钟,而是让任务可中断可重入——把长任务切成可保存进度的小步(D11 的 run 状态机和 D9 的 at-least-once 加幂等正好提供了这个基础),被打断的那一步由下一个副本接着做。

    How to reason about it · think before answering

    1. This is the applied version of the previous question, and the difference is that it demands the orchestrator's side too — describing only the in-process steps answers half of it.
    2. The full skeleton is both sides cooperating: the orchestrator first removes traffic (turns readiness red so the load balancer stops sending new requests), then sends SIGTERM, then waits out the grace period; the process uses that window to finish in-flight work, hand back ownership, and exit cleanly. That sentence is the trunk; everything else is detail.
    3. Then distinguish the two kinds of replica, which is where the points are. A gateway has inbound connections, so draining traffic means something for it. A worker has no inbound connections at all — it pulls work from the bus, so draining for it means stop fetching new messages, which is step one of graceful shutdown. The same word is two different mechanisms on the two replica types, and saying so shows you understand pull versus push.
    4. Next, batching and ordering: replace only a subset at a time (manual batches in compose, maxUnavailable / maxSurge in Kubernetes) so enough replicas are always alive to absorb traffic. This is where the version field in the heartbeat payload pays off — you can see how many old and new replicas remain instead of deploying blind.
    5. Also mention state compatibility: during a rolling deploy old and new code run simultaneously, so schema migrations must be backward compatible (add a nullable column, dual-write, drop the old column last) and message formats cannot change in one shot. Many candidates miss this layer — however gracefully processes stop, two versions that cannot read the same data will still cause an incident.
    6. Expect: what if a single execution legitimately takes five minutes and the grace period cannot wait that long? The answer is not to stretch the grace period to five minutes but to make the task interruptible and resumable — break long work into steps that checkpoint progress (the run state machine from D11 plus at-least-once with idempotency from D9 give you exactly this), so the next replica continues the interrupted step.

    答题要点

    • 完整骨架是两侧配合:编排系统先摘流量、再发 SIGTERM、等宽限期;进程在这段时间做完手头的活、交还所有权、干净退出
    • Gateway 和 Worker 的「摘流量」是两种机制:前者靠就绪探针转红让负载均衡停止转发,后者靠自己不再从总线取新消息
    • 分批替换(maxUnavailable / maxSurge 或手动分批),保证任何时刻有足够存活副本;心跳里的版本号让你看到新旧两批各剩几个
    • 新旧代码同时在线,所以数据库迁移必须向后兼容(加可空列 → 双写 → 最后删旧列),消息格式不能一次性改
    • 长任务不该靠拉长宽限期解决,而要做成可中断可重入:切成能保存进度的小步,被打断的那步由下一个副本接着做

    Key points

    • The full skeleton is both sides: orchestrator drains traffic, sends SIGTERM, waits the grace period; the process finishes in-flight work, hands back ownership, exits cleanly
    • Draining means two different things for gateways and workers: readiness turning red versus the worker itself stopping its fetch from the bus
    • Replace in batches (maxUnavailable / maxSurge or manual) so enough replicas stay alive; the version field in heartbeats shows how many old and new remain
    • Old and new code run concurrently, so migrations must be backward compatible (nullable column, dual-write, drop last) and message formats cannot change in one step
    • Long tasks are not solved by a longer grace period but by being interruptible and resumable — checkpointed steps that the next replica can continue
  • 怎么设计 dev 与 prod 的隔离,防止本地开发影响线上数据?How do you isolate dev from prod so local development cannot touch production data?
    国内高频海外高频基础#operations#security#configuration

    分析过程 · 先想清楚再作答

    1. 这题看着基础,但它筛的是「有没有踩过」。踩过的人第一句会说事故形态,没踩过的人第一句说「用不同的配置文件」。
    2. 先说事故形态:同一套代码、经常还是同一个 Redis,你在本机起一个 Worker 调试,它连的却是线上那条流,把真实用户的消息捞走执行了。**这类事故没有任何报错,两边日志都显示一切正常**——从代码角度看它确实老老实实处理了一条消息。正因为没有报错,它可能持续很久才被发现。
    3. 然后按成本分层给方案:命名空间(同一套基础设施,键名带前缀)、独立实例(各自的 Redis 与数据库)、独立环境(网络、凭证、账号全分开)。生产系统最终要走到第三层,但第一层成本最低也最容易漏,所以是重点。
    4. 第一层的关键实现细节是拿分点:前缀只能在一个函数里拼。散落到各处去拼字符串,二十个键名里漏掉一个就等于没隔离,而漏掉的那个通常是最新加、最没被测过的功能。这一点比「要加前缀」本身更能体现工程经验。
    5. 再补三件必须一起做的事:凭证分开(本机那把 key 只能连开发库,配置写错也波及不到线上);破坏性操作要认环境(清库、重放死信、重算索引这类脚本第一行先读环境变量,生产上要求显式确认);生产禁止降级实现(离线用的内存实现在生产上一旦因配置疏漏被走到,进程会安静起来、各自在自己内存里干活,健康检查还全是绿的,这类故障能藏好几个小时——启动时直接报错退出比事后排查便宜得多)。
    6. 可以预期的追问:为什么不干脆只用独立实例,省掉前缀这一层?答:独立实例解决的是「连错了地址」,前缀解决的是「连对了地址但走错了命名空间」——两者失效的方式不同。而且前缀几乎零成本,在共享测试环境、多人并行开发时还能顺带隔离每个人的数据。防御要分层,最便宜那层没理由不做。

    How to reason about it · think before answering

    1. This looks basic, but it screens for whether you have been burned. People who have start with the failure shape; people who have not start with use different config files.
    2. Describe the failure: same codebase, often the same Redis, and you start a worker locally to debug — except it is connected to the production stream and it claims and executes a real user's message. There is no error anywhere and both sides log business as usual, because from the code's point of view it did dutifully process one message. Precisely because nothing errors, this can run for a long time before anyone notices.
    3. Then give layered options by cost: namespacing (shared infrastructure, prefixed keys), separate instances (its own Redis and database), and separate environments (network, credentials, accounts all split). Production eventually wants the third layer, but the first is the cheapest and the easiest to get wrong, so that is where the focus belongs.
    4. The implementation detail in layer one is where the points are: the prefix may only be assembled in one function. Scatter string concatenation around the codebase, miss one key out of twenty, and you have no isolation at all — and the one you missed is usually the newest, least tested feature. This point signals real experience more than add a prefix does.
    5. Add three companions. Split credentials, so the local key can only reach the dev database and a misconfiguration cannot reach production. Make destructive operations environment-aware: scripts that truncate tables, replay dead letters or rebuild indexes read the environment variable on their first line and demand explicit confirmation in production. And forbid fallback implementations in production: if a config slip makes production take the in-memory path, processes come up quietly, each working in its own memory, with every health check green — that kind of fault hides for hours, so failing fast at startup is far cheaper than diagnosing it later.
    6. Expect: why not just use separate instances and skip prefixes? Because separate instances solve connected to the wrong address while prefixes solve connected to the right address but the wrong namespace — the two fail differently. Prefixes are also nearly free, and they incidentally isolate each developer's data in a shared test environment. Defence should be layered, and there is no reason to skip the cheapest layer.

    答题要点

    • 先说事故形态:本机 Worker 连上线上流,把真实用户消息捞走执行,且两边日志都显示正常、没有任何报错,所以能藏很久
    • 按成本分三层:命名空间(键名前缀)、独立实例(各自 Redis 与库)、独立环境(网络凭证账号全分开)
    • 前缀只能在一个函数里拼——散落各处漏掉一个键就等于没隔离,而漏掉的通常是最新加、最没测过的功能
    • 凭证分开,本机 key 只能连开发库;破坏性脚本第一行读环境变量并在生产要求显式确认
    • 生产禁止降级到内存实现:配置疏漏时进程会安静起来、健康检查全绿,故障能藏几小时,应在启动时直接报错退出
    • 独立实例防「连错地址」、前缀防「地址对了但命名空间错了」,失效方式不同,最便宜那层没理由不做

    Key points

    • Lead with the failure shape: a local worker attached to the production stream claims and runs a real user's message, with normal logs on both sides and no error, so it hides for a long time
    • Three layers by cost: namespacing (key prefixes), separate instances (own Redis and DB), separate environments (network, credentials, accounts)
    • The prefix must be assembled in exactly one function — scattered concatenation misses one key and voids the isolation, usually the newest and least tested feature
    • Split credentials so the local key only reaches dev; destructive scripts read the environment first and require explicit confirmation in production
    • Forbid the in-memory fallback in production: on a config slip processes come up quietly with green health checks and the fault hides for hours — fail fast at startup instead
    • Separate instances prevent wrong address, prefixes prevent right address wrong namespace — different failure modes, and the cheapest layer is free
  • 系统设计:请设计一个 IM Agent 平台——用户在即时通讯软件里和一个 AI 助手对话,助手能调用工具、记住长期偏好、还能定时主动推送。要求支撑十万日活。System design: design an IM agent platform where users chat with an AI assistant inside a messaging app. The assistant calls tools, remembers long-term preferences, and proactively pushes scheduled messages. Target 100k daily active users.
    国内高频海外高频深入#system-design#distributed-systems#cost#operations

    分析过程 · 先想清楚再作答

    1. 先别画图。系统设计题最常见的死法是听完就开始画框,二十分钟后面试官发现你解的是另一道题。花三到五分钟问清四件事:一是流量形状(十万日活对应多少并发会话、峰谷比多少),二是延迟要求(首字节要多快,是否必须流式),三是工具的性质(只读查询还是有写操作和副作用),四是主动推送的合规边界(能不能在深夜推、每天上限几条)。这四个答案会实质改变架构,问它们本身就是分数。
    2. 然后给主干,一句话先定形状:**接入层无状态、消息总线解耦、Worker 有状态且按用户分片、状态全在数据库**。接着按数据流走一遍:IM 平台的 webhook 打到接入层,接入层只做鉴权、限流、落库、投递四件事,立刻返回 202;执行侧从总线取活、跑 Agent 循环、把输出片段回传;主动推送由一个中心调度器按时间投递进同一条总线。**关键论点是接入层耗时确定、执行层耗时不确定,把它们放在一个进程里意味着一次慢的模型调用会占住一个本该毫秒级返回的连接**——这是整道题的立论基础,要主动说出来。
    3. 再逐个模块给出选择和理由。存储:sessions / runs / messages 三张表,runs 单独存在是因为只有它能回答「这次到底跑完没有」,幂等靠 runs 上的唯一约束而不是先查后插。总线:Redis Streams 的消费组做分摊,语义是至少一次,恰好一次靠消费端幂等做出来;反复失败的消息投递三次后进死信流。顺序:消费组的分配单位是一条消息而业务要求的串行单位是一个用户,所以按 userId 哈希到固定数量分片,每个分片同一时刻只有一个 Worker 持有租约。记忆:pgvector 存 embedding,检索包成一个工具交给模型自己决定要不要查,且不给它身份参数——身份只能来自会话。
    4. 主动推送这一块要单独讲透,因为它是这道题区别于普通聊天服务的地方。中心调度器命中时间点后只投一条消息,执行侧照旧;幂等键锚在「计划触发的那一分钟」,所以调度器崩溃重启后回看重放不会重复推送。合规上要有时区、静默时段、每日上限三道闸,而且这三道闸必须在投递前判断而不是在推送时判断——否则你已经花了模型调用的钱才发现不该推。
    5. 然后主动给出容量和成本的数字感,这是高级候选人的分水岭。十万日活、人均十轮对话是一百万次模型调用;按输入输出各一千 token、每百万 token 输入 0.15 美元输出 0.60 美元估算,一天大约七百五十美元。这个数字立刻推出三件事必须做:token 用量要按调用记账并换算成美元(否则你无法定位是哪个用户或哪个功能在烧钱)、要有分层降级(超预算的用户切便宜模型而不是直接拒绝)、以及上下文长度是主要成本杠杆(所以要压缩历史、控制检索条数)。
    6. 最后收在可运维性上,也就是这一周的落点:多副本部署、心跳发现假死、就绪探针只查自己必需的依赖、优雅停机让发版不掐断对话、dev 与 prod 用键名前缀隔离。**每个机制都要配一句「它失效时会怎样」**——租约会脑裂所以要有自杀规则和护栏令牌、心跳会误判所以面板转红只告警不自动摘流量、停机会超时所以等待要有上限。说不出失效模式的机制,面试官会认为你只是读过。
    7. 可以预期的追问,按出现频率排:单点在哪(调度器无状态可重启,Redis 和 Postgres 靠托管服务的主备);怎么灰度(新旧 Worker 同时在线,靠消息里的版本字段决定走哪套提示词);用户在助手回复中途又发一句怎么办(三十秒内的改口合并进同一次执行,而不是并发开两个);成本再降一半怎么做(缓存高频问答、压缩历史、把简单意图路由到小模型)。

    How to reason about it · think before answering

    1. Do not start drawing. The most common way to fail a design question is to hear the prompt and immediately sketch boxes, only for the interviewer to realise twenty minutes later that you solved a different problem. Spend three to five minutes on four questions: traffic shape (how many concurrent sessions does 100k DAU imply, and what is the peak-to-trough ratio), latency (how fast must first byte be, is streaming required), the nature of the tools (read-only lookups, or writes with side effects), and the compliance boundary on proactive pushes (may you push at night, what is the daily cap). All four change the architecture materially, so asking them is itself worth points.
    2. Then state the trunk in one sentence: stateless ingress, a message bus for decoupling, stateful workers sharded by user, all state in the database. Walk the data flow: the messaging platform's webhook hits ingress, which does only auth, rate limiting, persistence and publish, and returns 202 immediately; the execution side pulls work, runs the agent loop, and streams output fragments back; proactive pushes come from a central scheduler publishing onto the same bus. The load-bearing argument is that ingress latency is bounded while execution latency is not, so putting them in one process means one slow model call occupies a connection that should have returned in milliseconds — say this out loud, it is the premise of the whole answer.
    3. Then justify each module. Storage: sessions, runs and messages, with runs existing separately because only it can answer whether this attempt actually finished; idempotency comes from a unique constraint on runs, not from check-then-insert. Bus: Redis Streams consumer groups for fan-out, at-least-once semantics, with exactly-once manufactured by consumer-side idempotency, and messages that fail three times moved to a dead-letter stream. Ordering: the consumer group's unit of assignment is one message while the business requires serialisation per user, so hash userId into a fixed set of shards and let exactly one worker hold each shard's lease. Memory: embeddings in pgvector, retrieval wrapped as a tool the model chooses to call, with no identity parameter — identity only ever comes from the session.
    4. Treat proactive push as its own section, because it is what separates this from an ordinary chat service. The central scheduler publishes one message on a time match and the execution side is unchanged; the idempotency key is anchored to the scheduled minute, so replaying after a scheduler restart cannot double-send. For compliance you need timezone, quiet hours and a daily cap — and all three must be evaluated before publishing rather than at send time, or you have already paid for the model call before discovering you should not have pushed.
    5. Then volunteer capacity and cost numbers, which is what separates senior candidates. 100k DAU at ten turns each is a million model calls; at roughly a thousand tokens in and out, with input at $0.15 and output at $0.60 per million tokens, that is about $750 a day. That number immediately implies three requirements: meter token usage per call and convert to dollars (otherwise you cannot tell which user or feature is burning money), build tiered degradation (push over-budget users to a cheaper model rather than refusing them), and recognise that context length is the dominant cost lever (so compress history and cap retrieved items).
    6. Land on operability, which is this week's payoff: multiple replicas, heartbeats to surface zombies, readiness probes that only check their own hard dependencies, graceful shutdown so deploys do not cut conversations, and dev/prod isolation via key prefixes. Pair every mechanism with what happens when it fails — leases can split-brain so you need a self-fencing rule and fencing tokens, heartbeats produce false positives so a red dashboard alerts a human rather than auto-draining, shutdown can time out so the wait needs a ceiling. A mechanism without a stated failure mode reads as something you only read about.
    7. Expect, in rough order of frequency: where are the single points (the scheduler is stateless and restartable; Redis and Postgres rely on managed primary/replica); how do you roll out safely (old and new workers coexist and a version field in the message selects the prompt set); what if the user sends another message mid-reply (merge a change of mind within thirty seconds into the same execution rather than running two concurrently); and how would you halve the cost (cache frequent answers, compress history, route simple intents to a smaller model).

    答题要点

    • 先用三到五分钟问清四件事:流量形状、延迟要求、工具是否有副作用、主动推送的合规边界——它们会实质改变架构
    • 主干一句话:接入层无状态、消息总线解耦、Worker 有状态且按用户分片、状态全在数据库;立论是接入层耗时确定而执行层不确定
    • 存储 sessions / runs / messages 三张表,幂等靠 runs 上的唯一约束;总线用 Redis Streams 消费组,至少一次加消费端幂等,三次失败进死信
    • 顺序靠 userId 哈希分片加租约:消费组的分配单位是一条消息,而业务要求的串行单位是一个用户
    • 记忆用 pgvector 并包成工具交给模型自己决定是否检索,不给身份参数——身份只能来自会话
    • 主动推送由中心调度器投递,幂等键锚在计划触发的那一分钟;时区、静默时段、每日上限三道闸必须在投递前判断
    • 给出成本数字感:十万日活人均十轮约一百万次调用、一天约七百五十美元,由此推出计量记账、分层降级、压上下文三件事
    • 收在可运维性:多副本、心跳查假死、就绪探针只查自己的依赖、优雅停机、dev/prod 前缀隔离
    • 每个机制都配一句失效模式:租约会脑裂、心跳会误判、停机会超时——说不出失效模式等于只是读过

    Key points

    • Spend three to five minutes clarifying four things: traffic shape, latency targets, whether tools have side effects, and the compliance boundary on proactive pushes
    • State the trunk in one sentence: stateless ingress, bus for decoupling, stateful workers sharded by user, all state in the database — premised on bounded ingress latency versus unbounded execution latency
    • Storage is sessions/runs/messages with idempotency from a unique constraint on runs; the bus is Redis Streams consumer groups, at-least-once plus consumer idempotency, dead-lettering after three failures
    • Ordering comes from hashing userId into shards plus leases: the consumer group assigns per message while the business serialises per user
    • Memory is pgvector exposed as a tool the model may call, with no identity parameter — identity comes only from the session
    • Proactive push flows through a central scheduler with the idempotency key anchored to the scheduled minute; timezone, quiet hours and daily caps are enforced before publishing
    • Bring numbers: 100k DAU at ten turns is ~1M calls and ~$750/day, which implies metering, tiered degradation, and context length as the main cost lever
    • Land on operability: replicas, heartbeats for zombies, readiness probes scoped to own dependencies, graceful shutdown, dev/prod prefix isolation
    • Pair each mechanism with its failure mode — leases split-brain, heartbeats false-positive, shutdown times out; a mechanism without one reads as book knowledge