Three silent failures: an unreported mkdir, a capped delete, an unbounded batch #169
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
qwc-open/asiakirjat!169
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/startup-and-index-robustness"
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?
Three independent findings from debugging a Kubernetes startup failure. Each is a commit of its own and they can be reviewed separately.
1. A directory we cannot create says so (
e3a9bb8)Both startup
MkdirAllcalls discarded their error. The database one let the failure fall through to the SQLite driver, which reportsSQLITE_CANTOPENas:The
out of memoryis the driver's fallbackerrmsgstring, not a diagnosis (vendor/modernc.org/sqlite/conn.go:626-639formats errors aserrstr: errmsg (rc)). It sends the reader after RAM and PVC sizes. The actual cause was a volume mounted over/app/dataarriving root-owned while the container runs as uid 1000. The storage directory had the same hole and failed even later — on first upload.Deriving the directory also needed a driver check:
filepath.Diron a PostgreSQL or MySQL DSN treats a connection string as a path, so the old code would create a directory named after a fragment of the DSN, password included.sqliteDataDirreturns a directory only for SQLite, and skips in-memory databases andfile:URIs with query parameters.Both resolved absolute paths are now logged at startup, so a DSN relative to an unexpected working directory is visible without a shell in the container.
2. Deleting from the search index deletes all of it (
b500468)Both deletion paths found their documents with a single search request of
Size = 10000and ignored everything past that page. On an index holding more than 10000 documents:DeleteVersiondeleted whichever of a version's pages happened to land in the first page of an unordered match-all result and left the rest searchable, so pages of a deleted version kept appearing in search results, linking to documentation that is gone. Especially visible now that #164 removes storage on project delete.ReindexAllWithProgresscould not clear the documents a reindex exists to clear, so "rebuild from scratch" was not one. It also discarded the search error and ignored the batch error, so it could report success having done nothing.A search request always has a size, so it is the wrong tool. The fix walks the index reader (
DocIDReaderAll) instead: no cap, no scoring, each document visited once.project_id/version_idare mapped withIndex = false, so they cannot be queried — hence the id-prefix match stays, now over a complete enumeration.The reindex clear is now a hard failure, since the state it leaves behind on partial success is exactly the one a reindex is meant to repair.
3. Indexing a version no longer holds all of its text in memory (
316ef41)IndexVersionaccumulated one bleve batch across an entire version and flushed it once at the end. A batch holds the full extracted text of every document in it, so peak memory was a function of how much documentation somebody uploaded — a large generated tree, or a PDF contributing one document per page, grew it without bound. That peak lands immediately after an upload, on an instance sized for steady state. Now flushed every 500 documents, inside the PDF page loop as well as per file.Verification
indexBatchSize-1,indexBatchSizeandindexBatchSize+1files, so a document stranded in an unflushed batch at a boundary fails them, plus the empty-directory case where there is no final batch.sqliteDataDiris tested across relative, absolute, bare-filename,file:URI,:memory:, and PostgreSQL/MySQL DSN forms;ensureDirfor nested creation, idempotence, and that the error names both the purpose and the path.go test ./...(12 packages) andnpm test(19 tests).search-indexing.mddescribed the deletion step as a query, which is what it used to be and the reason it was incomplete; updated to describe the walk and the batching.🤖 Generated with Claude Code
Both startup MkdirAll calls discarded their error. When the directory could not be created, the database one let the failure fall through to the SQLite driver, which reports SQLITE_CANTOPEN as pinging database: unable to open database file: out of memory (14) The "out of memory" is the driver's fallback errmsg string, not a diagnosis, and it sends the reader after RAM and PVC sizes instead of at the mount. The real cause in the case that prompted this was a Kubernetes volume mounted over /app/data arriving owned by root while the container runs as uid 1000. The storage directory had the same hole, and failed even later — on first upload. Report both, naming the purpose and the path. Deriving the directory also needed a driver check. filepath.Dir on a PostgreSQL or MySQL DSN treats a connection string as a path, so the old code would create a directory named after a fragment of the DSN, password included. sqliteDataDir returns a directory only for SQLite, and skips in-memory databases and file: URIs with query parameters. The resolved absolute path of both directories is now logged at startup, so a DSN that is relative to a working directory you did not expect is visible without a shell in the container. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Marcel M. Otte <marcel.otte@mmo.to>View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.