← Blog

Managing the STL library after the first scan

What changes once a library is indexed: scan cost, taxonomy rot, browsing performance at scale, and the backup boundary that catches people out.

guideorganizationself-hostedworkflow

Adopting a large archive is a project with an end. Running one is not, and the failures are different. The first scan is loud and finishes; everything after it is slow drift, where the taxonomy stops matching how you search, scans quietly cost more than they return, and the backup you have never restored turns out to exclude the files you cared about most.

If you are still at the adoption stage, organizing thousands of STL files covers the migration. This picks up afterwards.

The first scan is the expensive one, and it should stay that way

A full scan hashes and parses every supported file and generates previews for meshes, which is why the initial pass on a large root is where memory limits show up. Steady-state scans should be far cheaper, because almost nothing has changed.

That only holds if the schedule matches how the folder actually behaves. A NAS share that receives a handful of files a week does not benefit from hourly scans, and the cost is real: every pass still walks the tree. Where the filesystem supports it, local watching picks up changes as they happen and makes frequent scheduled scans redundant. Set the schedule from the rate of change rather than from a sense that more often is safer.

One safety behaviour is worth knowing before it triggers. If the mounted root comes back unexpectedly empty, reconciliation aborts rather than treating every file as deleted, which is what stops an unmounted NFS share from wiping an index in a single pass.

Taxonomy rot is the slow one

Tags decay in a specific way. The list grows, synonyms accumulate, and eventually you stop trusting a filter because you cannot remember whether the tag you want is needs-supports or support-needed or both. At that point the taxonomy is costing you retrieval time rather than saving it.

The fix is deletion, and it is less painful than it sounds. Sort tags by how many models carry them, and look at the tail. A tag on two models out of eight thousand is not a category, it is a note you wrote once, and deleting it costs you nothing you were using. Merging the survivors into one spelling is a filter and a bulk edit, up to 500 models per pass.

Resist re-tagging anything the parser already knows. Material, nozzle, layer height, temperatures, and estimates come out of the G-code itself and update when you reslice, whereas a tag saying the same thing does not, so the two disagree within a month.

What large actually costs at the interface

Browsing a big library used to mean the browser pulling every page before it could sort. In v0.11.4 that changed: the complete filtered library sorts by date, name, print success, last print, duration, filament, or cost through cursor pagination, without draining pages first. Facet counts and vault totals each take one aggregate query, and background job polling stops when there is no work rather than running forever.

The practical consequence is that saved views become the main interface rather than scrolling. A filter you rebuild every time is a filter you eventually stop using, and the combinations worth saving are the ones you have now typed three times.

Mesh preview stays the memory-hungry part at any size. If the host is small, expect that to be where large libraries push back first, which surviving out-of-memory conditions on large meshes covers in detail.

Check the index against reality occasionally

A library that has run for a year accumulates inconsistencies: missing thumbnails, metadata that failed to parse, revision state that no longer makes sense. A Quick or Full vault audit finds those, groups them by severity, and repairs the eligible ones, and it can verify a backup archive’s structure and manifest membership before you rely on it.

That last part is the one to actually use. Verifying an archive is not the same as restoring it, but it is the cheap check you can run often, against the expensive one you should still run occasionally.

The backup boundary that surprises people

A PrintStash full backup covers the database, the blobs it owns, documents, thumbnails, and a manifest. It does not copy source bytes indexed from a shared volume, and that is deliberate rather than an oversight: those files are yours, sitting on your NAS at their original paths, and copying tens of thousands of meshes into every backup would make the archive unusable.

The consequence is that a large library needs two backups, not one. PrintStash backs up what it owns, and your NAS still needs its own routine for the files it holds. A restore that brings back the index against source files that no longer exist gives you a catalogue of missing models.

Two more boundaries worth knowing at scale. The built-in backup and restore flow supports SQLite only, so Postgres installations use pg_dump and normal operator tooling. And one API process per vault is the supported topology, enforced at startup by a vault lock, so scaling a busy library means a bigger host rather than more replicas.

Questions that come up

How often should scheduled scans run on a big library?

Match the schedule to how often the folder changes, not to how large it is. A share that gains a few files a week is served fine by a nightly or even weekly scan, because every pass walks the whole tree regardless of how little has changed, and on a large root that walk is the cost. Where the filesystem supports local watching, enable it and let the schedule drop to an infrequent safety net, since the watcher catches changes as they happen. Reserve frequent scans for a folder that genuinely receives files throughout the day.

My tag list has grown to two hundred entries. What now?

Delete most of them, starting from the least used. Sort by how many models carry each tag and treat the long tail as notes rather than categories, because a tag applied to two models out of several thousand will never be the thing you filter by. Then merge the duplicates and near-synonyms into one spelling each, which is a filter plus a bulk edit rather than manual work. Expect the healthy end state to be well under thirty tags, all of them judgments a parser could not have made, since anything describing material or settings is already a structured field.

Does the library get slower as it grows?

Browsing and sorting should not, because sorting the complete filtered library runs through cursor pagination rather than making the browser fetch every page first, and facet counts and totals each resolve in a single aggregate query. What does scale with size is scanning and preview generation, since both do real work per file. Mesh rendering is where a small host actually runs out of room, so if you are sizing hardware for a large library, budget for memory during indexing rather than for query load afterwards.

What is not in my backup?

Source bytes that live on a shared volume, which for most large libraries is the overwhelming majority of the data by size. A full backup holds the database, the blobs PrintStash owns, documents, thumbnails, and a manifest, so restoring it rebuilds the catalogue, the metadata, the taxonomy, and the print history, and it points at files that must still exist on your NAS. Back up that storage separately. It is also worth knowing that the built-in backup and restore path covers SQLite only, so a Postgres install needs pg_dump and your own restore procedure instead.

Sources

  • PrintStash capabilities for cursor browsing, saved views, bulk edit limits, and the vault audit, and known limitations for the single-process topology and the PostgreSQL backup boundary.
  • Backup and restore for what a full backup contains, and shared volumes for scan scheduling, watching, and the empty-root abort.
  • Performance behaviour described above shipped in v0.11.4.

For the restore rehearsal rather than the backup, backups that actually restore. For choosing the approach in the first place, how to organize an STL library.