多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

牌组爆炸、复习积压:我从 Anki 弃用边缘爬回来的踩坑实录

牌组爆炸、复习积压:我从 Anki 弃用边缘爬回来的踩坑实录 牌组爆炸、复习积压我从 Anki 弃用边缘爬回来的踩坑实录【免费下载链接】ankiAnki is a smart spaced repetition flashcard program项目地址: https://gitcode.com/GitHub_Trending/an/anki如果你从未体会过打开 Anki 看到 1,200 张到期卡片的窒息感恭喜你这篇踩坑实录或许可以帮你永远不用体会。我的故事并不特殊从下载第一份共享牌组时的兴奋到疯狂新建二十多个主题牌组再到某天清晨发现整个系统已经变成一台失控的复习永动机——新卡永远放不完旧账永远还不清每天在刷不完→点简单→更记不住→再次→更多积压的循环里越陷越深最终差点删库弃坑。这篇文章不是劝退指南而是我从崩溃边缘爬回来的完整复盘前兆怎么识别、积压为什么会滚雪球、以及基于 Anki 源码机制设计的三步止血与长期治理方案。所有结论都锚定在 Anki 仓库的真实实现上希望能帮你避开我踩过的每一个坑。一、牌组爆炸的三个前兆回头看牌组爆炸从来不是一夜之间发生的它有三个非常清晰的前兆信号。前兆一每天都在插队的新卡Anki 每次构建学习队列时并不是今天该看什么就取什么这么简单。在队列构建器 rslib/src/scheduler/queue/builder/gathering.rs 中收集顺序是这样的先收日内学习卡再收到期学习卡与到期复习卡最后才收集新卡pub(super) fn gather_cards(mut self, col: mut Collection) - Result() { self.gather_intraday_learning_cards(col)?; self.gather_due_cards(col, DueCardKind::Learning)?; self.gather_due_cards(col, DueCardKind::Review)?; self.gather_new_cards(col)?; Ok(()) }真正的问题不在新卡排最后而在于配额。默认配置下Anki 每天放行的新卡数量是有限的对应 proto/anki/deck_config.proto 里的new_per_day字段但当你不断新建牌组、不断导入内容而某个牌组的插入顺序又设成了随机或按牌组优先时新卡会以你意想不到的方式插队进队列。更隐蔽的是new_mix选项——新卡可以被混在复习卡中间REVIEW_MIX_MIX_WITH_REVIEWS也可以排在复习之前REVIEW_MIX_BEFORE_REVIEWS。如果你为了先把新内容过一遍把新卡排在前面那么每天真正能留给复习的时间就被新卡挤占了到期卡片越积越多。我当时的状态是每天新卡 20复习 -20看起来收支平衡实际上新卡每天都在挤压复习配额——这就是牌组爆炸的第一个前兆新卡永远在插队。前兆二一张笔记长出三张卡第二个前兆是我当时完全没察觉的笔记与卡片的 1:N 关系。在 Anki 的数据模型里一张笔记Note可以对应多张卡片Card取决于笔记类型模板的数量。一个单词笔记如果挂了三个模板正面释义、拼写、例句填空它就会变成三张卡。这本身不是坏事问题出在兄弟卡片的互相遮蔽。在队列构建的遮蔽逻辑 rslib/src/scheduler/queue/builder/burying.rs 中如果开启bury_new/bury_reviews同一张笔记下的兄弟卡片会被隐藏起来self.context .seen_note_ids .entry(card.note_id()) .and_modify(|entry| { previous_mode Some(*entry); entry.bury_new | new_mode.bury_new; entry.bury_reviews | new_mode.bury_reviews; entry.bury_interday_learning | new_mode.bury_interday_learning; }) .or_insert(new_mode);表面上看埋没兄弟卡是在保护你——同一天不重复刷同一知识点的多张卡。但它带来一个经典陷阱你每天刷完了今天的队列以为自己复习了 50 个知识点实际上很多只是被埋没了真正的到期卡根本没有出现。等到埋没解除的那天你会发现涌出来的是两倍、三倍的卡片。我当时就是在这种虚假的清零感中不断加牌组、不断导新卡直到某天埋没解除积压数字直接翻倍。前兆三从共享牌组下载的大礼包第三个前兆最致命共享牌组。下载一个 5,000 张卡的考研词汇大礼包感觉像捡到了宝藏但这份宝藏的每一张卡都是未来某天的债务。社区里关于下载牌组的讨论几乎一致地指出共享牌组是结构性缺陷的重灾区——卡片粒度不统一、干扰项设计粗糙、大量低质量卡最终变成永久滞涨的僵尸卡。更麻烦的是嵌套牌组的配额传导。Anki 的限额是按牌组树逐层递减的decrement_deck_and_parent_limits会同时扣减父牌组配额一个大礼包如果被拆成几十个子牌组每层都在争抢配额队列构建时甚至会出现子牌组抢光父牌组配额、导致其他牌组当天颗粒无收的情况。牌组爆炸的第三个前兆就是你的牌组树里躺着超过三个此生不可能刷完的巨型牌组。二、复习积压的滚雪球机制如果说牌组爆炸是入口失控复习积压就是出口堵塞。理解积压的滚雪球机制必须先看 Anki 是怎么决定先刷哪张卡的。逾期优先越欠越多越排越前到期复习卡的收集顺序由review_order决定proto/anki/deck_config.proto 中枚举了十多种排序策略按到期日、按牌组、按间隔升/降序、按简洁度、甚至按相对逾期程度REVIEW_CARD_ORDER_RELATIVE_OVERDUENESS。默认情况下逾期越久的卡优先级越高——这本来是合理的但它的副作用是只要你缺席一天第二天出现在你面前的永远是最烂的账而那些本可以轻松刷掉的近期待复习卡被不断往后推。于是积压有了第一个自我强化的特征一旦开始拖欠你面对的不再是均匀分布的复习任务而是一条越来越长的逾期长尾。刷完今天新增的到期卡昨天的逾期卡还在等你清掉昨天的前天的又沉底了。队列永远是满的你永远在还债且利息——以逾期天数——持续增长。点再次的代价每张失误卡都变成三次债积压的第二个引擎是再次Again按钮。在 rslib/src/scheduler/states/review.rs 中可以看到每点一次 Again卡片的间隔会被压缩、进入重学队列同时失误次数lapses1fn answer_again(self, ctx: StateContext) - CardState { let lapses self.lapses 1; let leeched leech_threshold_met(lapses, ctx.leech_threshold); ... ease_factor: (self.ease_factor EASE_FACTOR_AGAIN_DELTA).max(MINIMUM_EASE_FACTOR),注意一个残酷的数字EASE_FACTOR_AGAIN_DELTA -0.2。每点一次 Again这张卡未来复习的间隔上限就被永久削减 20%。我当时的典型操作是积压太多 → 时间不够 → 点简单Easy赶进度 → 大脑根本没记牢 → 下次到期直接 Again → 间隔被压缩 → 更频繁地出现在队列里。一次赶进度换来的是未来三到四次的复习债。更隐蔽的是 rslib/src/scheduler/answering/mod.rs 里的 leech 机制当一张卡的失误次数达到阈值默认 8 次见 rslib/src/deckconfig/mod.rs 中的leech_threshold: 8它会被标记为 leech而如果配置了挂起Suspend这张卡会直接从队列中消失——这不是清除而是隐藏。被挂起的卡不再出现在统计里你的待复习数因此显得没那么可怕但它们并没有消失只是变成了未来某次清理时才会浮出水面的暗礁。FSRS 时代的积压新变量期望保留率如果你已经切到 FSRSFree Spaced Repetition Scheduler积压的第三个引擎是期望保留率desired retention。FSRS 的核心思想是不预设固定间隔而是基于记忆稳定性模型为每个目标保留率计算最优间隔。目标保留率设得越高默认 0.9系统就越保守间隔越短、复习频率越高、每日负荷越大。在 rslib/src/scheduler/fsrs/retention.rs 中Anki 甚至内置了一个最优保留率计算器通过模拟未来若干天的复习流程求解目标值并把结果收敛在 0.70 ~ 0.95 之间Ok(fsrs::optimal_retention( config, req.params, ... )? .clamp(0.7, 0.95))这组数字本身就是个信号0.95 的保留率几乎必然带来不可持续的每日负荷。如果你的积压数字像我当时一样直逼四位数先别急着怪自己意志力差——先想想是不是把期望保留率设到了不现实的档位。三、我的止血三步与长期治理方案从崩溃边缘爬回来的过程我把它抽象成三步止血、清账、重建系统。每一步都有对应的源码机制支撑。止血第一步停掉新卡流入止血的第一动作不是多刷一点而是不再新增。把新卡配额new_per_day直接设为 0等于从源头切断了插队入口。这一步在 rslib/src/scheduler/queue/builder/gathering.rs 中的效果很直观——新卡收集阶段检查limit_reached(deck_id, LimitKind::New)配额为 0 时新卡一张都不会进队列复习队列反而获得完整的配额窗口。同时建议检查新卡忽略复习上限开关如果开着它即使复习配额已满新卡仍会强行入队。止血期务必关掉让复习优先成为唯一原则。止血第二步用数据调参而不是硬扛止血期最有效的参数调整有三个全部有源码依据调整期望保留率。在 rslib/src/scheduler/fsrs/retention.rs 里跑一次最优保留率模拟或手动把desired_retention从 0.9 降到 0.85 甚至 0.8。代价是长期记忆率轻微下降收益是每日复习负荷显著下降——对处于积压漩涡中的人来说后者远比前者重要。重新优化 FSRS 参数。Anki 会把你的历史复习记录revlog喂给训练器重新拟合参数训练逻辑在 rslib/src/scheduler/fsrs/params.rs。注意它要求足够的数据量代码注释明确说明少于 400 条复习记录时不会直接报错需要由调用方自行判断结果可信度。所以如果历史记录不够优先攒数据别急着调参。优化后还有个健康检查health check只有当调整后的模型损失显著优于当前参数时才会采纳新参数——这保证了越调越差的情况不会发生。处理 ignore 日期。如果你的 revlog 里混着早期不科学的复习史比如我下载大礼包后的无脑 Easy 阶段可以设置忽略某日期之前的复习记录让 FSRS 从靠谱的时间点重新起算记忆状态。清账第三步给积压卡分诊而不是蛮干积压数字摆在那里靠每天硬刷是清不完的——我试过三天后情绪就崩了。真正的清账手段是 Anki 的自定义学习Custom Study机制入口逻辑在 rslib/src/scheduler/filtered/custom_study.rs找回忘记的卡ForgotDays选项会把你指定天数内点过 Again 的卡拉出来重刷其搜索条件正是按按钮评分1 的卡fn forgot_config(deck_name: String, days: u32) - FilteredDeck { let search SearchNode::Rated { days, ease: RatingKind::AnswerButton(1), } .and(SearchNode::from_deck_name(deck_name)) .write(); ... }这比我当时把所有过期卡全部重排温和得多——它只针对真正没记住的卡。预览Preview把最近新加入的卡集中过一遍不做进度改动只做混个脸熟防止新卡积压成未来的逾期债务。建立过滤牌组做分诊用搜索条件把积压卡分成救得回来的和该放弃的。过滤牌组构建时默认排除挂起与埋没的卡-is:suspended -is:buried这意味着你分诊出来的每一张卡都是真实需要决策的对象。我当时做了三件事真正有价值的卡留下并调整间隔反复 Again 的 leech 卡直接挂起——与其让它们每天出现在队列里消耗意志力不如先冷冻起来低质量共享牌组的卡批量删除。删除不是失败是回收自己的注意力。长期治理把 Anki 当成系统来运维清账之后我给自己立了几条不再破防的规矩每一条都能在源码里找到对应新卡配额用可完成而非可接受的标准。每天只放行自己确定能刷完的数量宁可保守。配额字段就在 proto/anki/deck_config.proto 的new_per_day/reviews_per_day改一次成本极低。默认关闭新卡插队。把new_mix设为复习之后新卡永远只能出现在复习完成后的空隙里从机制上杜绝插队。理解埋没善用埋没。兄弟卡埋没不是 bug是防重复机制但要意识到今日清零不等于任务清零把统计页的预测曲线当作真实工作量看待。定期体检 leech 清单。默认 8 次失误阈值会持续生产 leech 卡rslib/src/scheduler/answering/mod.rs 中挂起动作只是默认策略之一。对长期治理来说leech 是信号要么卡片粒度太粗要么释义太差要么就是该删的卡——诊断卡而不是跟它死磕。让 FSRS 参数优化成为季度例行。数据攒够后定期重训让模型贴合自己真实遗忘规律而不是永远用一份通用的初始参数。回头看我最深的教训不是没用对工具而是把 Anki 当成内容仓库而不是记忆系统无节制地导入无规划地放行无原则地赶进度。Anki 的队列、配额、遮蔽与 FSRS 模型都不是摆设——它们是一整套节流阀唯一的开关在你的设置里。牌组爆炸与复习积压本质上都是入口流量超过出口处理能力的系统性失衡。如果你此刻正被红色数字压得喘不过气停掉新卡调低一点期望保留率给积压卡分诊然后接受清账需要以周为单位的节奏。这个从弃用边缘爬回来的过程本身就是一次关于自我认知与自我管理的复利——而这一次复利的方向是好的。【免费下载链接】ankiAnki is a smart spaced repetition flashcard program项目地址: https://gitcode.com/GitHub_Trending/an/anki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表