{"record":{"id":"c9f566d581d19a95","repo":"benbjohnson/litestream","slug":"delete-rows-w","errorCode":null,"errorMessage":"delete rows: %w","messagePattern":"delete rows: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/litestream-test/shrink.go","lineNumber":233,"sourceCode":"\t\t\t\tORDER BY RANDOM()\n\t\t\t\tLIMIT %d\n\t\t\t)\n\t\t`, table, table, rowsToDelete)\n\t} else {\n\t\tdeleteQuery = fmt.Sprintf(`\n\t\t\tDELETE FROM %s\n\t\t\tWHERE rowid IN (\n\t\t\t\tSELECT rowid FROM %s\n\t\t\t\tORDER BY RANDOM()\n\t\t\t\tLIMIT %d\n\t\t\t)\n\t\t`, table, table, rowsToDelete)\n\t}\n\n\tstartTime := time.Now()\n\tresult, err := db.Exec(deleteQuery)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"delete rows: %w\", err)\n\t}\n\n\trowsDeleted, _ := result.RowsAffected()\n\tduration := time.Since(startTime)\n\n\tslog.Debug(\"Deleted rows from table\",\n\t\t\"table\", table,\n\t\t\"rows_deleted\", rowsDeleted,\n\t\t\"duration\", duration,\n\t)\n\n\treturn rowsDeleted, nil\n}\n\nfunc (c *ShrinkCommand) runCheckpoint(db *sql.DB) error {\n\tslog.Info(\"Running checkpoint\", \"mode\", c.CheckpointMode)\n\n\tstartTime := time.Now()","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/cmd/litestream-test/shrink.go#L215-L251","documentation":"This error wraps failure of the batched `DELETE FROM <table> WHERE id IN (...)` statement that `deleteFromTable` executes after computing the rows to delete. It means SQLite could not execute the delete — commonly a lock conflict, a disk I/O error, or a query that grew beyond SQLite's variable/SQL-length limits. The shrink run aborts with the underlying driver error wrapped via `%w`.","triggerScenarios":"Deleting from a table while another connection holds a write transaction; building a DELETE with thousands of IDs exceeding SQLITE_MAX_VARIABLE_NUMBER; table name interpolated with characters producing invalid SQL; disk full during the delete.","commonSituations":"Shrinking a large production-like table in a test where litestream is also replicating and holds the WAL; very wide delete batches on older SQLite builds with low variable limits; read-only database file.","solutions":["Chunk the ID list into smaller batches (e.g. 500 per statement)","Ensure no other process/connection is writing to the DB during shrink","Check wrapped error for 'database is locked' / 'disk I/O error' and address that cause","Ensure the database file and its directory are writable"],"exampleFix":"// before\ndeleteQuery := fmt.Sprintf(\"DELETE FROM %s WHERE id IN (%s)\", table, strings.Join(ids, \",\"))\n_, err := db.Exec(deleteQuery)\n// after\nfor chunk := range slices.Chunk(ids, 500) {\n\tdeleteQuery := fmt.Sprintf(\"DELETE FROM %s WHERE id IN (%s)\", table, strings.Join(chunk, \",\"))\n\tif _, err := db.Exec(deleteQuery); err != nil {\n\t\treturn 0, fmt.Errorf(\"delete rows: %w\", err)\n\t}\n}","handlingStrategy":"validation","validationCode":"// pre-check locks and batch size\nif len(rowsToDelete) > 500 { return errors.New(\"delete batch too large; chunk it\") }","typeGuard":null,"tryCatchPattern":"if _, err := db.Exec(deleteQuery); err != nil {\n\tvar sqliteErr *sqlite.Error\n\tif errors.As(err, &sqliteErr) && strings.Contains(err.Error(), \"locked\") {\n\t\t// retry after backoff or ensure single writer\n\t}\n\treturn fmt.Errorf(\"delete rows: %w\", err)\n}","preventionTips":["Chunk large IN(...) deletes to ~500 IDs","Ensure single-writer access during shrink","Watch disk free space before bulk deletes"],"tags":["sqlite","delete","database-locked","test-tooling"],"backgroundTag":"database-write-failed","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}