Freika/dawarich · error
Database error: please try again.
Error message
Database error: please try again.
What it means
Visits::BulkDestroy runs a bulk soft delete of many visit ids against PostgreSQL and rescues ActiveRecord::StatementInvalid — the wrapper Rails raises when the database rejects a statement (deadlock, lock wait timeout, statement timeout, dropped connection, constraint violation). The service logs the error class and message with the user id and count, appends the localized 'Database error: please try again.' message to errors, and returns false so the caller surfaces it.
Source
Thrown at app/services/visits/bulk_destroy.rb:59
place_ids = visits.reorder(nil).where.not(place_id: nil).distinct.pluck(:place_id)
started_at = Time.current
# Soft delete: rows stay as tombstones (points and place links intact)
# so visit detection never re-suggests what the user removed.
Visit.where(id: ids).update_all(deleted_at: Time.current)
# update_all skips AR callbacks, so the orphan-place check the model
# runs on single soft deletes must be enqueued here by hand.
place_ids.each { |place_id| Places::DeleteIfOrphanJob.perform_later(place_id) }
log_success(ids.length, Time.current - started_at)
{ count: ids.length, started_ats: started_ats }
rescue ActiveRecord::StatementInvalid => e
Rails.logger.warn(
"Visits::BulkDestroy failed user_id=#{user.id} count=#{ids&.length} error=#{e.class}: #{e.message}"
)
errors << I18n.t('services.visits.bulk_destroy.database_error')
false
end
def log_success(count, duration_seconds)
Rails.logger.info(
"Visits::BulkDestroy user_id=#{user.id} count=#{count} duration_ms=#{(duration_seconds * 1000).round}"
)
end
end
end
View on GitHub (pinned to 97fad417c5)
Solutions
- Read the Rails warn line — the error class and PG message name the exact cause (deadlock detected vs lock wait timeout vs statement timeout)
- Reduce the batch size: destroy in chunks of a few hundred ids per statement
- Retry once after a short pause for deadlock/lock-timeout classes — they are transient
- If statement_timeout is the cause, raise it for this job or move the work to a background worker
- Reschedule bulk deletes so they do not overlap imports or recalculations for the same user
Example fix
# before Visit.where(id: ids).update_all(deleted_at: Time.current) # after — smaller statements, shorter row locks ids.each_slice(500) do |slice| Visit.where(id: slice).update_all(deleted_at: Time.current) end
Defensive patterns
Strategy: retry
Validate before calling
return false if ids.blank? raise ArgumentError, 'batch too large' if ids.length > 5000 # force client-side chunking chunked = ids.each_slice(500).to_a
Try / catch
retries = 0
begin
Visit.where(id: ids).update_all(deleted_at: Time.current)
rescue ActiveRecord::StatementInvalid => e
retries += 1
retry if retries <= 2 && e.message =~ /deadlock|lock wait timeout/i
Rails.logger.warn("Visits::BulkDestroy failed user_id=#{user.id} error=#{e.class}: #{e.message}")
errors << I18n.t('services.visits.bulk_destroy.database_error')
false
end Prevention
- Chunk bulk updates so each statement stays well under statement_timeout
- Run bulk destroys in a background job away from request timeouts
- Retry deadlock and lock-timeout errors with jitter — they are usually transient
- Avoid scheduling bulk deletes concurrently with imports for the same user
When it happens
Trigger: Deleting a very large batch of visits producing SQL that exceeds statement_timeout or complexity limits; a concurrent job (points import, visit recalculation) holding row locks so the bulk update hits lock wait timeout or deadlock; the DB connection dropping mid-transaction; constraint failures after schema drift.
Common situations: Users selecting months of visits to delete at once; background imports running simultaneously for the same user; managed Postgres with a low statement_timeout; overlapping bulk operations on the same rows.
Related errors
- name
- latitude
- longitude
- errorData.error || `Failed to ${isEdit ? "update" : "create"
- Failed to create visit
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/0ef346ff52eb178b.
Report an issue: GitHub.