Nothing is written until the whole pass completes #22
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Passaccumulates every item, stream and image for all libraries in memory andthe caller writes once at the end. RSS climbed to ~170 MB through the scan and
the database stayed at its 4 seed rows until the final commit.
A crash or a restart part-way through loses everything and starts over, and
nothing is visible until the whole thing lands.
Committing per library — or per N items — bounds memory, makes a crash
resumable, and with a backgrounded scan lets one library be browsable while the
next is still reading.
Found by scanning
/mnt/media(285 movies, 289 shows, 74 music artists —~13 000 media files on a CIFS mount) with both this server and upstream
Jellyfin at the pinned commit, remote metadata and image fetchers disabled on
upstream so both were reading files only. Upstream was given the same mount
path so
MD5(UTF-16LE(path))agrees and rows compare directly.Fixed in
c9245f8.What changed
A pass no longer holds the library it read.
ScanWriteris a new port injellyfin-core: a pass hands over a batch of a hundred rows — the items, theancestor chains they hang from, and what each carries beside its row — and
empties it. The composition root writes each batch in one transaction.
Passcame down to three counts plus the ids a removal is scoped by.Two things fell out of the port that were worth having anyway:
record. Without that, a renamed library — or a second library added over a
folder already scanned — showed up empty until every file under it happened
to change, because the folder a library is browsed as is derived from its
name. A rescan reads the chains on record once and compares them as sets —
AncestorIdsrecords no order, and upstream writes one a row at a time — soa rescan that changed nothing still writes nothing.
one call after the pass — and after the removal, which is what takes the last
mention some of them had. It is an anti-join over the whole table; running it
a hundred and thirty times a scan was work for nothing.
Measured
A synthetic four-thousand-film library on local disk, so the probing is fast
enough to watch:
Both builds produce the same database:
BaseItemsandAncestorIdsdigestsmatch, and the only rows that differ are the library folders' own
DateCreated.A rescan that changes nothing writes nothing:
AncestorIdscomes out of it atthe same count and the same maximum rowid it went in with.
Three rounds of review went into this; what they turned up is in the tests —
crates/jellyfin-app/tests/scan_incremental.rscovers a library larger than onebatch, a rename, a rename back to a name once used, two libraries trading
names, a library added over a folder already scanned, a folder two libraries
read as different things, and a chain stored in the order upstream leaves
behind.
On
/mnt/mediathe first hundred rows land 8m33s into the scan at 27 MB, wherebefore the database stayed empty for the length of it.
What this does not fix
Resolving a library still materialises every
Writefor it before the firstbatch is filed, and the walk holds the whole listing. What the batching takes
off the peak is the enriched rows and their streams, chapters and images —
the bulk of it, but a large library still allocates its own skeleton up front.
Filed separately as #32.
Two things the review turned up that are older than this branch:
last, and a renamed library leaves its old collection folder behind.
ParentIdnaming a folder that has stoppedbeing a row, and goes with it for one scan.
Both want an upstream comparison before anything is changed; the tests assert
what the port does today either way.