Repository navigation
[Bug]: Whole-UI lag during chat + O(n²) checkpoint file growth #482
Description
What happened?
Environment
- Smart Second Brain: v2.0.5
- Obsidian:
[fill in — Help → About], Windows 10/11 (Electron) - Model:
deepseek-v4-pro:cloudvia Ollama (a reasoning model) - Vault: ~6,500 markdown notes; embedding index disabled (lexical + direct reads)
Summary
Two related issues make S2B chat unusable on a real-world vault after sustained use:
- The whole Obsidian UI freezes/stutters for the entire duration of an assistant response (not just the chat pane), and recovers the instant streaming ends.
.chatfiles grow O(n²) and reach hundreds of MB — one reached 535 MB uncompressed (89 MB gzipped) — which is what makes issue 1 catastrophic and drives 5–9 GB renderer RAM swings + OOM crashes.
Issue 1 — per-token full re-render + forced synchronous layout
During streaming, S2B appears to re-render the message (and read layout) on every token, forcing a full style+layout recalculation of the thread each frame. This blocks the renderer's main thread, so the entire app (editor, sidebars, other windows) stutters while a response streams.
Evidence — Chrome DevTools performance trace (10 s of an active session):
| Signal | Cost | Note |
|---|---|---|
plugin:smart-second-brain JS self-time |
7,357 ms | #1 plugin by ~150× (next plugin = 49 ms) |
UpdateLayoutTree (style/layout recalc) |
3,146 ms | dominant render-pipeline cost |
get offsetParent ×2 + serviceScriptedAnimations |
~700 ms | reading layout while mutating DOM = layout thrashing |
| dataview / tasknotes / metadataCache | 9 / 25 ms | vault work is negligible — this is pure rendering |
Suggested fix: render streamed tokens incrementally/append-only instead of re-rendering the whole message per chunk; never read layout (offsetParent, scrollHeight, etc.) synchronously inside the token loop — batch any layout reads via requestAnimationFrame and separate reads from writes to avoid forced reflow. Consider throttling markdown re-parse to e.g. every N ms rather than per token.
Issue 2 — O(n²) checkpoint storage
.chat files are gzip'd LangGraph checkpoint stores. Each checkpoint under checkpoints{} stores a full copy of the conversation state (channel_values.messages). A long agentic session with many tool calls accumulates hundreds of checkpoints, each holding the full growing message set — so file size grows with the square of the conversation, and each large tool output is retained in full.
Evidence:
gzip -lon one chat: 92,977,311 → 535,298,992 bytes (535 MB uncompressed).- 10 of ~40 chats exceeded 20 MB on disk; several 50–90 MB.
- Opening such a chat parses the full structure into JS objects → 5–9 GB renderer RAM oscillation per second and OOM crashes.
- The actual conversation content is tiny: extracting only the latest checkpoint's messages (resolving the
messageTable{$msg:N}refs) and truncating tool outputs yields ~11–60 KB transcripts — a ~1000× reduction. So the bloat is entirely redundant checkpoint state + retained raw tool outputs.
Suggested fix: store checkpoint deltas rather than full snapshots; and/or prune/cap historical checkpoints (keep last-N or last-viewed); and/or stop persisting full raw tool outputs in the checkpoint (store a reference or a truncated preview). A per-thread size guard with a warning would also prevent the pathological case.
Repro
- Open a chat with a reasoning model; run a long agentic task with many tool calls (reading several large notes).
- Watch the
.chatfile size grow into tens/hundreds of MB. - Reopen that chat and send another message → the whole Obsidian UI stutters for the full response; renderer RAM swings multi-GB.
Impact
On a mature vault, every S2B session degrades the entire app and eventually crashes Obsidian (renderer OOM). Workarounds (archiving giant .chat files, CSS contain/content-visibility on the chat view, collapsing the thinking panel) reduce but do not remove it, because the per-token re-render and full-state checkpointing are internal.
Console output
Plugin version
2.0.5
Platform
Windows
Provider and model (if relevant)
deepseek-v4-pro:cloud` via Ollama (a reasoning model)
Obsidian debug info
SYSTEM INFO:
Obsidian version: 1.13.7
Installer version: 1.12.7
Operating system: Windows 11 Home 10.0.26200
Login status: logged in
Language: en-GB
Catalyst license: none
Insider build toggle: off
Live preview: on
Base theme: adapt to system
Community theme: Minimal 9.0.2
Snippets enabled: 3
Restricted mode: off
Plugins installed: 28
Plugins enabled: 15
1: Templater v2.25.0
2: MCP Connector v2.4.0
3: Git v2.39.0
4: Claudian v2.2.5
5: Data Files Editor v1.3.0
6: Iconize v2.14.7
7: Dataview v0.5.68
8: Minimal Theme Settings v9.0.0
9: Homepage v4.4.4
10: TaskNotes v4.12.5
11: Style Settings v1.0.9
12: Charts v3.9.0
13: Smart Second Brain v2.0.5
14: S2B Shell v1.0.0
15: Cadence v0.15.0
RECOMMENDATIONS:
Custom theme and snippets: for cosmetic issues, please first try updating your theme and disabling your snippets. If still not fixed, please try to make the issue happen in the Sandbox Vault or disable community theme and snippets.
Community plugins: for bugs, please first try updating all your plugins to latest. If still not fixed, please try to make the issue happen in the Sandbox Vault or disable community plugins.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
Direct-Launch commentedon Sep 12, 2026
Dug into this on a large vault. Two distinct causes:
1. Runtime callbacks serialised into every checkpoint (the big one).
parentConfigwas persisting LangChain'scallbacks(StreamMessagesHandler+ its growingseenmap) andtagsinto each checkpoint (~1.5 MB each). One.chatwas 440 MB uncompressed, 99% callbacks, vs 1.8 MB of actual conversation. PR incoming that stripsparentConfigtoconfigurable-only on deflate (also shrinks old files on next save).2. No checkpoint retention cap. Even without callbacks, N checkpoints hold N-length message-pointer arrays (O(N²) materialised on load). I have a fix that caps checkpoints/thread (~100), pruning only old main-line links — never fork points or side branches, so branch navigation is preserved (the oldest survivor's dangling parent is already treated as a root by the timeline builder). Since retention is a design decision I wanted to check the approach before opening a PR: is a cap acceptable, and what default would you want?
Leo310 commentedon Sep 14, 2026
2.1.1 ships both of your PRs: the per-frame render coalescing (#484) and the callbacks strip that was behind the huge
.chatfiles (#483). Existing chats shrink the next time they are saved.Could you confirm on 2.1.1 that the UI stays responsive while a reply streams and that your large chats come down in size? Incremental rendering of the streaming message is the remaining root-cause work on the lag side; I'll keep this issue open to track it. Thanks for the diagnosis and the fixes.
perf(chat): render the streaming reply incrementally instead of re-pa…
Leo310 commentedon Sep 15, 2026
@Direct-Launch coming back to your cap question now that s2b-dev/smart-second-brain#503 closed this issue with the incremental streaming render. (It ships in the next release)
I'd hold off on a retention cap for now. Since #433 every message lives once in the thread's message table, and on load each checkpoint gets the same object back, so N checkpoints cost N² small references rather than N² message copies. With the callbacks gone (#483) that is a few MB at worst for a very long thread. Pruning main-line links is the kind of change I'd only want to make against a file that actually shows load time or memory scaling with checkpoint count.
If you still have one of your big chats from before 2.1.1, could you check its size and open time once it has been re-saved on the current version? If that still looks bad, I'm happy to take the cap PR.
release: 2.1.2
Leo310 commentedon Sep 15, 2026
@Direct-Launch 2.1.2 is out with the incremental streaming render (#503): https://github.com/s2b-dev/smart-second-brain/releases/tag/2.1.2. If you get a chance to try a long reply on your vault, I'd be glad to hear whether the stutter is gone for you.
Direct-Launch commentedon Sep 15, 2026
@Leo310 Just tested after updating to version 2.1.2 and stutter has resolved on original test machine, can follow up with feedback using other devices such as lower power laptop/mobile with current set up.