Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: repeated ActiveRecord::RecordNotUnique when creating objects with Postgres?

I'm working with a Rails 4 app that needs to create a large number of objects in response to events from another system. I am getting very frequent ActiveRecord::RecordNotUnique errors (caused by PG::UniqueViolation) on the primary key column when I call create! on one of my models.

I found other answers on SO that suggest rescuing the exception and calling retry:

begin
  TableName.create!(data: 'here')
rescue ActiveRecord::RecordNotUnique => e
  if e.message.include? '_pkey' # Only retry primary key violations
    log.warn "Retrying creation: #{e}"
    retry
  else
    raise
  end
end

While this seems to help, I am still getting tons of ActiveRecord::RecordNotUnique errors, for sequential IDs that already exist in the database (log entries abbreviated):

WARN -- Retrying creation: PG::UniqueViolation: DETAIL: Key (id)=(3067) already exists.
WARN -- Retrying creation: PG::UniqueViolation: DETAIL: Key (id)=(3068) already exists.
WARN -- Retrying creation: PG::UniqueViolation: DETAIL: Key (id)=(3069) already exists.
WARN -- Retrying creation: PG::UniqueViolation: DETAIL: Key (id)=(3070) already exists.

The IDs it's trying are in the 3000-4000 range, even though there are over 90000 records in the table in question.

Why is ActiveRecord or PostgreSQL wasting so much time sequentially trying existing IDs?


The original exception (simplified/removed query string):

{
  "exception": "ActiveRecord::RecordNotUnique",
  "message": "PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint \"table_name_pkey\"\nDETAIL:  Key (id)=(3023) already exists."
}
like image 505
nitrogen Avatar asked Feb 20 '15 22:02

nitrogen


People also ask

How to set up active record for PostgreSQL in rails?

To get started with PostgreSQL have a look at the configuring Rails guide . It describes how to properly set up Active Record for PostgreSQL. PostgreSQL offers a number of specific datatypes. Following is a list of types, that are supported by the PostgreSQL adapter. You need to enable the hstore extension to use hstore.

What is the difference between PostgreSQL and Ruby on rails?

Postgres (or PostgreSQL) is an open source database. Ruby on Rails is an open source web framework written in Ruby. Rails is database agnostic, meaning it can be used with a variety of different databases. By default it assumes that MySQL is being used, but it’s quite easy to use with Postgres instead.

What is Gen_random_UUID () in PostgreSQL?

gen_random_uuid () (from pgcrypto) is assumed if no :default option was passed to create_table. Generated columns are supported since version 12.0 of PostgreSQL. Optionally, you can store the vector as automatically generated column (from PostgreSQL 12.0): Imagine you need to work with a legacy database containing the following table:

What are the requirements to use the PostgreSQL adapter?

In order to use the PostgreSQL adapter you need to have at least version 9.3 installed. Older versions are not supported. To get started with PostgreSQL have a look at the configuring Rails guide .


2 Answers

I'm not sure how it happened, but it turned out that the PostgreSQL sequence for the table's primary key was somehow reset or got out of sync with the table:

SELECT nextval('table_name_id_seq');
-- 3456

SELECT max(id) FROM table_name;
-- 95123

I had to restart the primary key sequence at the table's last ID:

ALTER SEQUENCE table_name_id_seq RESTART 95124;

Update: here's a Rake task to reset the ID sequence for most models on a Rails 4 with PostgreSQL project:

desc 'Resets Postgres auto-increment ID column sequences to fix duplicate ID errors'
task :reset_sequences => :environment do
  Rails.application.eager_load!

  ActiveRecord::Base.descendants.each do |model|
    unless model.attribute_names.include?('id')
      Rails.logger.debug "Not resetting #{model}, which lacks an ID column"
      next
    end

    begin
      max_id = model.maximum(:id).to_i + 1
      result = ActiveRecord::Base.connection.execute(
        "ALTER SEQUENCE #{model.table_name}_id_seq RESTART #{max_id};"
      )
      Rails.logger.info "Reset #{model} sequence to #{max_id}"
    rescue => e
      Rails.logger.error "Error resetting #{model} sequence: #{e.class.name}/#{e.message}"
    end
  end
end

The following references proved useful:

  • https://stackoverflow.com/a/1427188
  • http://apidock.com/rails/ActiveRecord/Relation/find_or_create_by
  • https://stackoverflow.com/a/10712838
  • https://stackoverflow.com/a/16533829
like image 123
nitrogen Avatar answered Oct 16 '22 06:10

nitrogen


You can also reset a sequence of a table 'table_name' using rails console

> ActiveRecord::Base.connection.reset_pk_sequence!('table_name')

(tested in rails 3.2, rails 5.0.1)

like image 30
adc Avatar answered Oct 16 '22 06:10

adc