enhance(import): optimize file graph import path by tiensonqin · Pull Request #13130 · logseq/logseq · GitHub
Skip to content

enhance(import): optimize file graph import path - #13130

Open
tiensonqin wants to merge 56 commits into
masterfrom
logseq/optimize-markdown-import-d899
Open

enhance(import): optimize file graph import path#13130
tiensonqin wants to merge 56 commits into
masterfrom
logseq/optimize-markdown-import-d899

Conversation

@tiensonqin

@tiensonqin tiensonqin commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Speeds up markdown file-graph import. Import stays sequential, one file at a time. Search indexes are not built during import.

The new DB graph is created before import. File-graph import does not broadcast renderer deltas to other clients. Assets are written in the worker.

The import indicator opens immediately as Importing. After files are written, it shows Finishing graph import and validating. Creating the empty graph no longer redirects away from that UI.

After import, page properties and :block/refs are rebuilt in one finalize-imported-graph! transact. The current client restores the graph from the worker on web and desktop. There is no full page reload. Search index rebuild runs once after restore.

Desktop progress uses the db-worker-node event channel (set-ui-state over SSE), not the main-thread stub. A single file failure is recorded and skipped; later files still import.

:thread-api/import-file-graph returns a small terminal contract over the same RPC (:run-id, :status, :persisted?, :validation, :issue-count, plus compact counts). The renderer uses that RPC return value. HTTP 200 replies without a transit result fail instead of decoding as nil.

File timestamps come from Node fs.stat on the path string the exporter already passed. Transact failures log :tx-count and the original error instead of printing full :tx-data. Lazy file-read failures are ignored as failed files, not imported as empty pages.

If the desktop invoke drops after the worker has persisted the graph (:validating), the new graph is kept. A drop during Finishing, or a plain transact/js/Error without transport metadata, switches back to the previous graph.

Test plan

  • Targeted import tests: frontend.components.imports-test (keep-graph only after :validating; finishing drop switches back; Electron lazy descriptors; terminal notifications), frontend.worker.db-core-test import/transact-log/lazy-read cases, frontend.worker.db-worker-node-test file-graph import cases, frontend.persist-db.remote-test (missing resultTransit rejects)
  • Skip-result leftover tests (encodes-terminal-result, decodes-terminal-result, :graph/importing-result negatives) were removed
  • Split file-graph import renderer tests so bb lint:large-vars stays under the 100-line limit
  • Electron-lazy import of ls-movies-4k from logseq/db-benchmark-graphs completed in-process (~10 min) without Node crash; one-shot finalize persisted. Not added as a committed :long test.

Notes

PR is behind current master. Import-related files should merge cleanly; rebase separately if desired.

Open in Web Open in Cursor 

@cursor cursor Bot changed the title enhance(import): optimize file graph import and add watchdog enhance(import): desktop lazy read and read-ahead doc import Aug 27, 2026
@tiensonqin
tiensonqin marked this pull request as ready for review August 27, 2026 16:30
Copilot AI lite review requested due to automatic review settings August 27, 2026 16:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cursor cursor Bot changed the title enhance(import): desktop lazy read and read-ahead doc import enhance(import): desktop lazy read and simple-page fast path Aug 27, 2026
@cursor cursor Bot changed the title enhance(import): desktop lazy read and simple-page fast path enhance(import): optimize file graph import path Aug 27, 2026
cursoragent and others added 24 commits August 31, 2026 22:13
- Walk AST once per file; sync block tx for non-asset blocks
- Track placeholder refs for scoped missing-ref cleanup
- Add per-phase import profiling and optional 30s watchdog
- Electron lazy file/asset read via fs-path descriptors
- Integration test enables timeout without changing assertions

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Move export-doc-files before simple-page-property helpers so SCI/nbb can
resolve it at runtime. Splice fast-path helpers from perf branch with
paren fixes, content prefetch on partition fallback, and relaxed
remove-inline-tags guard.

Document that parallel doc import stays opt-in and off for db-worker.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Remove the simple-page-property fast path and direct-import machinery.
Keep desktop lazy file reads and overlap the next file read with the
current file's parse and transact via a read-ahead export-doc-files loop.
Restore the large-graph integration test threshold to 25s.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Reintroduce the simple-page-property bulk import path (sqlite-build +
build-direct-import-db) with single-pass batch accumulation instead of
upfront file partitioning. Remove the read-ahead pipeline and pass
:rpath-key through build-doc-options so the fast path can classify files.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Remove the simple-page-property / direct-import / read-ahead / atomic-batch
machinery that did not improve docs-graph import (~15.8s) and regressed
property-parent-classes validation. Keep the lean e57cb26 import path
(AST walk once, sync block tx, desktop lazy read, watchdog) and make
log-fn opt-in so imports stay quiet by default.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Prefix unused build-block-tx-core args and drop unused import-state binding.
Revert graph-parser nbb-logseq specifier to git+https so frozen-lockfile CI
matches pnpm-lock.yaml and other deps packages.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Extract file partition/steps and main-tx builders so export-file-graph and
<add-file-to-db-graph stay under the 75-line limit.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Rebuild name->uuid from all pages on every file made movies import
pages-tx grow with graph size. Index only pages saved from the current
file. Skip search sync for imported txs.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Pre-seeding :page-names-to-uuids with journal file names made
build-pages-tx treat those journals as already existing, so journal
files skipped build-new-page-or-class and never wrote :block/title.
Date properties then pointed at uuid-only placeholders and cleanup
failed. Keep journals in :journal-page-name-uuids until the page is saved.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
The incremental name->uuid index reused a property uuid when a later
file converted a same-titled tag to a class. That made :user.property/tag
both a Property and a Tag on the docs graph. Keep properties out of
:page-names-to-uuids so class lookup cannot collide with them.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
The incremental name index made a class titled tag look like an
existing page, so tag:: converted that class into :user.property/tag.
Keep the class and create a separate property entity instead.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Split unaccounted work in <add-file-to-db-graph> so parse, prep, pages-tx,
split, prop-tx, clean-tags, main-tx, transact, and upstream can be measured
independently when a log-fn is provided.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Add --profile/--profile-out to bb dev:import so markdown import is measured
on the sqlite-backed CLI path used by Electron, including per-file phases
and datascript sqlite -store time.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Set conn :skip-store? for export-file-graph, then d/store once.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
:block/name is indexed but not unique. Avoid scanning all uuids per tag.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Time d/with, store-after, listener callbacks, and block-refs rebuild
separately from save-from-tx so a 400-file import can show where
per-file transact time goes.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Time get-blocks-and-pages separately from db-rebuild-block-refs so
per-file transact cost is not lumped into one listen bucket.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
mapcat was lazy, so rebuild cost leaked into ldb/transact!.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Skip per-file :block/refs rebuild on import txs. Stamp :block/tx-id
and rebuild refs in one pass at the end of export-file-graph, matching
Desktop finalize plus pipeline.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
nbb only sees existing ^:api outliner.pipeline vars. Rebuild the
one-shot :block/refs pass in the exporter with db-rebuild-block-refs.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
cursoragent and others added 8 commits August 31, 2026 22:13
Outline parse is only for markdown files whose import is headings,
properties, refs and tags. Render, editor, and import conversions that
walk Src/Macro/Quote/Drawer AST keep mldoc full parse.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Outline parse leaves `template::` on the bullet line as title text, so
empty-title template blocks never become #Template.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Property page refs already come from mldoc's drawer AST. Stop
re-parsing every value with parseInlineJson during extract.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Pre-block refs already come from extract-properties drawer AST.
Comma-separated plains use parseInlineJson instead of a full document parse.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Wire worker set-ui-state so import progress numbers update. Skip search
index builds while a file-graph import is running, and skip the giant
web finalize delta so reload stays responsive. Finalize still writes
:block/refs on the worker so page refs and linked-reference counts
survive after reload.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Cover worker conn refs, canonical-block, get-block-refs, published
replacements, and import progress updates after file-graph import.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Outline parse drops quote body, including emails. Detect `- >` and
numbered-list quotes so those files use full parse.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Calling done inside the import test finally raced restoring-worker-state
cleanup and could wipe *main-thread for later search index tests.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
@tiensonqin
tiensonqin force-pushed the logseq/optimize-markdown-import-d899 branch from 2d13c26 to b4ecc4e Compare August 31, 2026 14:13
cursoragent and others added 21 commits August 31, 2026 14:45
File-graph import must not broadcast render deltas to any client; a
large graph posts a giant Comlink payload and can crash after parse.
Stamp remaining import transacts with imported-data, copy page-drawer
refs onto the page, and restore the current desktop graph from the
worker after import.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Do not return file contents, asset bytes, or the full import index through
transit. Write assets in the worker, show the current file during import, and
update later steps such as finishing and validating without dumping internals.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
File-graph import no longer transit-encodes the worker reply. The renderer
reads a counts-only summary from UI state. The progress dialog opens
immediately as Importing and graph create no longer redirects away.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Run the file-graph-import create-db assertions in the existing
async with-redefs test so they do not race the default create path.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
File-graph import now notifies ignored-property counts only, so the
per-property icon and type-mismatch strings are unused.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Create the DB graph first, then import. Desktop progress uses the node
event channel, a single file failure continues the rest, and restore
runs once without a page reload or a second search rebuild.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Keep p/with-redefs active until create-db promises settle so the next async test cannot restore the real persist-db/<new.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Electron already stops the previous db-worker-node when the window binds
to the imported graph, but the renderer EventSource for that old port
kept reconnecting. Mark the old session inactive, serialize runtime
switches, and close native SSE handlers on disconnect.

Also yield during finalize so Finishing can flush, skip worker recovery
while import is running, and keep the imported graph if the invoke
transport drops after files have finished.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Long import must keep the HTTP server from timing out, but writing the
200 headers before invoke finished forced every lock and validation
failure onto HTTP 200. Send the real 409/400/500 after the handler
returns, keep request/header timeouts disabled, and split request
routing so make-server stays under the large-vars limit.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Header RTC uuid, left-sidebar class pulls, custom CSS/JS, and plugin
graph-changed hooks no longer invoke the worker before it is initialized.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Stamp tx-id and rebuild refs in one transact instead of 500-entity
batches. Accept a filesystem path in <get-file-stat so birthtime is
imported. Return a small terminal contract from import-file-graph so
the renderer does not race HTTP completion against SSE UI state.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Failed transact used to prn the full tx-data. On a large file-graph
finalize that overflows Array.join, masks the original error, and can
exit the Node worker. Log tx-count and rethrow the original error.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Keep-graph after a dropped import RPC no longer treats {} as a valid
import. Remove unused serialize/notification wrappers, leftover promise
wrapping around sync finalize, and unused electron-lazy SSE setup.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Finishing and last-file idx are set before sqlite store and refs
finalize. Keep the new graph only after :validating. Treat only
explicit transport codes as recoverable, not bare js/Error. Reject
HTTP 200 replies without transit, rethrow lazy file-read failures,
and log stat errors instead of swallowing them.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Remove stubbed transit encode/decode tests that no longer guard an
import-specific path. Drop dead :graph/importing-result assertions.
Cover Electron lazy descriptors and renderer notifications from a
non-empty terminal result.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Keep the renderer import harness under the 100-line large-vars limit so CI lint can pass.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants