benbjohnson/litestream · error
delete rows: %w
Error message
delete rows: %w
What it means
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`.
Source
Thrown at cmd/litestream-test/shrink.go:233
ORDER BY RANDOM()
LIMIT %d
)
`, table, table, rowsToDelete)
} else {
deleteQuery = fmt.Sprintf(`
DELETE FROM %s
WHERE rowid IN (
SELECT rowid FROM %s
ORDER BY RANDOM()
LIMIT %d
)
`, table, table, rowsToDelete)
}
startTime := time.Now()
result, err := db.Exec(deleteQuery)
if err != nil {
return 0, fmt.Errorf("delete rows: %w", err)
}
rowsDeleted, _ := result.RowsAffected()
duration := time.Since(startTime)
slog.Debug("Deleted rows from table",
"table", table,
"rows_deleted", rowsDeleted,
"duration", duration,
)
return rowsDeleted, nil
}
func (c *ShrinkCommand) runCheckpoint(db *sql.DB) error {
slog.Info("Running checkpoint", "mode", c.CheckpointMode)
startTime := time.Now()View on GitHub (pinned to 4ed7a308f6)
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
Example fix
// before
deleteQuery := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", table, strings.Join(ids, ","))
_, err := db.Exec(deleteQuery)
// after
for chunk := range slices.Chunk(ids, 500) {
deleteQuery := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", table, strings.Join(chunk, ","))
if _, err := db.Exec(deleteQuery); err != nil {
return 0, fmt.Errorf("delete rows: %w", err)
}
} Defensive patterns
Strategy: validation
Validate before calling
// pre-check locks and batch size
if len(rowsToDelete) > 500 { return errors.New("delete batch too large; chunk it") } Try / catch
if _, err := db.Exec(deleteQuery); err != nil {
var sqliteErr *sqlite.Error
if errors.As(err, &sqliteErr) && strings.Contains(err.Error(), "locked") {
// retry after backoff or ensure single writer
}
return fmt.Errorf("delete rows: %w", err)
} Prevention
- Chunk large IN(...) deletes to ~500 IDs
- Ensure single-writer access during shrink
- Watch disk free space before bulk deletes
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- count rows: %w
- get table info: %w
- checkpoint failed: %w
- vacuum failed: %w
- begin passive checkpoint barrier: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/c9f566d581d19a95.
Report an issue: GitHub.