База данных ActiveRecord Postgres не блокируется - получение условий гонки - PullRequest
0 голосов
/ 12 мая 2018

Я борюсь с блокировкой таблицы PostgreSQL, над которой я работаю. В идеале я хочу заблокировать всю таблицу, но отдельные строки будут работать до тех пор, пока они действительно работают.

У меня есть несколько одновременных сценариев ruby, которые все запрашивают центральную базу данных заданий в AWS (через класс DatabaseAccessor), находят работу, которая еще не была запущена, меняют статус на started и выполняют ее. Проблема в том, что, поскольку все они запускаются одновременно, они, как правило, все сразу находят одну и ту же незапущенную работу и начинают ее выполнять, тратя время и запутывая результаты.

Я пробовал кучу вещей, .lock, .transaction, драгоценный камень fatalistic, но они, похоже, не работают, по крайней мере, не в pry.

Мой код выглядит следующим образом:

class DatabaseAccessor
  require 'pg'
  require 'pry'
  require 'active_record'
  class Jobs < ActiveRecord::Base
    enum status: [ :unstarted, :started, :slow, :completed]
  end

  def initialize(db_credentials)
    ActiveRecord::Base.establish_connection(
      adapter:  db_credentials[:adapter],
      database: db_credentials[:database],
      username: db_credentials[:username],
      password: db_credentials[:password],
      host:     db_credentials[:host]
    )
  end

  def find_unstarted_job
    job = Jobs.where(status: 0).limit(1)
    job.started!
    job
  end
end

У кого-нибудь есть предложения?

РЕДАКТИРОВАТЬ : Кажется, что LOCK TABLE jobs IN ACCESS EXCLUSIVE MODE; является способом сделать это - однако я борюсь с тем, чтобы потом вернуть результаты этого после обновления. RETURNING * вернет результаты после обновления, но не внутри транзакции.

1 Ответ

0 голосов
/ 13 мая 2018

РЕШИТЬ!

Таким образом, ключ здесь фиксируется в Postgres. Есть несколько различных блокировок на уровне таблицы, подробно здесь .

При принятии решения здесь есть три фактора:

  1. Чтения не являются потокобезопасными. Два потока, читающие одну и ту же запись, приведут к тому, что это задание будет выполняться несколько раз одновременно.
  2. Записи обновляются только один раз (помечаются как завершенные) и создаются, кроме первоначального чтения и обновления до запуска. Скрипты, которые создают новые записи, не будут читать таблицу.
  3. Чтение варьируется по частоте. Ожидание разблокировки некритично.

Учитывая эти факторы, если бы была блокировка чтения, которая все еще позволяла запись, это было бы приемлемо, однако, нет, поэтому ACCESS EXCLUSIVE - наш лучший вариант.

Учитывая это, как мы имеем дело с блокировкой? Поиск в документации ActiveRecord не упоминает об этом.

К счастью, существуют другие методы работы с PostgreSQL, а именно гем ruby-pg. Немного поиграем с SQL позже, и проверю блокировку, и я получаю следующий метод:

def converter
  result_hash = {}
  conn = PG::Connection.open(:dbname => 'my_db')
  conn.exec("BEGIN WORK;
      LOCK TABLE jobs IN ACCESS EXCLUSIVE MODE;")
  conn.exec("UPDATE jobs SET status = 1 WHERE id = 
      (SELECT id FROM jobs WHERE status = 0 ORDER BY ID LIMIT 1)
      RETURNING *;") do |result|
          result.each { |row| result_hash = row }
      end
  conn.exec("COMMIT WORK;")
  result_hash.transform_keys!(&:to_sym)
end

Это приведет к:

  • Вывод пустого хэша, если нет заданий с status из 0

  • Вывод символизированного хэша, если он найден и обновлен

  • Спящий режим, если база данных в данный момент заблокирована, перед возвратом вышеуказанного, как только он разблокирован.

Таблица будет оставаться заблокированной до оператора COMMIT WORK.

Кроме того, хотелось бы, чтобы был более чистый способ преобразования результата в хеш. Если у кого-то есть какие-либо предложения, пожалуйста, дайте мне знать в комментариях! :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...