产品设计

Attio:让客户自动跟进,也让每一步都能查清

Attio把公司、联系人和交易进展组织成客户记录,并通过工作流处理更新、分配和提醒。运行历史帮助团队追查某次跟进做了什么,以及卡在了哪一步。

先尝试拆解问题、设计和实现,再展开思路,检查状态、边界与取舍,并设计验证用例。思路提供分析路径,不给出完整实现。

01
代码思考进阶

乱序和重复日志,怎样还原每次尝试的状态?

练习场景

为自己的运行日志页设计一个事件归并器:网络会把事件乱序、重复送到前端,你要生成稳定的“每次尝试的各步骤状态”。只处理已收到的内存数组,不发送通知、不控制执行、不实现数据库。事件流不是 Attio 内部协议。

输入、规则与输出

  1. 输入是最多100,000项的数组,每项为非null对象,包含非空字符串 eventId、runId、stepId;attempt、seq 为正安全整数;status 为 running、succeeded、failed 之一。忽略其他字段。字符串总长度记为L。
  2. 同一 eventId 的所有已列字段完全一致时是重复投递,仅计一次;若任一已列字段不同,整批失败并指出双方数组下标。字符串按原值比较,不去空格、不忽略大小写。
  3. 按(runId, attempt, stepId)分组,seq只在组内排序。不同eventId可以有相同seq,但同组相同seq即使status相同也算冲突。不要用简单分隔符拼键,因为ID本身可含分隔符。
  4. 完整组的合法序列是零个或多个running,随后至多一个终态succeeded或failed;终态后不得再有任何事件。可以只有终态,表示较早事件尚未收到;空档seq合法。不推断未收到的步骤和尝试,不因只有B步骤就制造A失败。
  5. 成功返回各组最大seq及其status、组内去重事件数,以及全部组的唯一事件总数;按runId、attempt、stepId确定性排序。字符串顺序采用Unicode码点字典序,不用地区排序。失败不返回部分结果,不修改输入。报告一个足以定位的错误即可,不要求所有错误的优先级一致。

小例子

依次收到 (e2,R,1,A,2,succeeded)、(e1,R,1,A,1,running)、与e2完全一致的重复、(e3,R,2,A,1,running)。请写出两次尝试的摘要与去重数量。再分别追加:(a) (e4,R,1,A,3,running);(b) eventId仍为e2但status改成failed;(c) (e5,R,1,A,2,succeeded)。三个变体独立运行,说明各自冲突与定位。

交付物

提出校验、去重、分组排序、状态归并和一次性交付的思路,推演例子并列至少四个不变量。分析n项事件、L字符串长度、g个组的时间与空间;区分比较次数与字符串比较成本。可讨论JS Map或Rust集合,不给完整实现或逐行伪代码。

查看思路
  1. 完整校验字段并建立eventId索引。 保留首次出现下标;重复ID逐个比较已列字段,一致则跳过,不一致定位两项。未知字段不参与相等判断。安全整数和合法status应在分组前检查。
  2. 用结构化键隔离不同尝试。 嵌套映射或真正的元组键能避免ID中分隔符碰撞。按组收集唯一事件,按数值seq排序;相邻seq相等时定位双方,即使状态相同也不能当重复。
  3. 沿序列检查终态。 running可连续出现;遇到终态后仍有任何后续事件就报错。只有终态或seq有空档不构成本题错误。不要拿到达顺序当执行顺序,也不要把attempt1的成功覆盖到attempt2。
  4. 推演例子。 原例得到R/1/A最大seq2、succeeded、2条唯一事件;R/2/A最大seq1、running、1条,合计3条。a违反终态后无后续事件;b是同eventId字段冲突;c是同组同seq冲突。三种失败都不能返回部分摘要。
  5. 检查确定性和成本。 打乱输入、增加完全一致重复不应改变成功摘要;不同尝试独立;每组至多一个终态;失败输入不变。组内排序总计不超过O(n log n)次数值比较,摘要排序O(g log g)次键比较。哈希构建需计入L,字符串比较可能扫描共同前缀;JS要按码点处理而非默认UTF-16排序。空间O(n+L)量级用于索引和事件引用,不复制无关数据。