Блокировка базы данных: ActiveRecord + Heroku - PullRequest
0 голосов
/ 16 апреля 2010

Я создаю приложение на основе Sinatra для развертывания на Heroku. Вы можете представить это как стандартное сокращение URL, но там, где старые короткие коды истекают и становятся доступными для новых URL (я понимаю, что это глупая концепция, но ее проще объяснить таким образом). Я представляю шорткод в моей базе данных как целое число и переопределяю его читатель, чтобы получить красивую короткую и уникальную строку из целого числа.

Поскольку некоторые строки будут удалены, я написал код, который проходит через все целые числа шорткода и выбирает первый свободный, который будет использовать только before_save. К сожалению, я могу заставить свой код создавать две строки с одинаковыми целыми числами шорткода, если я запускаю два экземпляра очень быстро один за другим, что, очевидно, бесполезно! Как мне внедрить систему блокировки, чтобы я мог быстро сохранить свою запись с уникальным целым кодом короткого кода?

Вот что у меня есть:

Chars = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
CharLength = Chars.length

class Shorts < ActiveRecord::Base
  before_save :gen_shortcode
  after_save  :done_shortcode

  def shortcode
    i = read_attribute(:shortcode).to_i

    return '0' if i == 0
    s = ''
    while i > 0
      s << Chars[i.modulo(CharLength)]
      i /= 62
    end
    s
  end

  private
  def gen_shortcode
    shortcode = 0
    self.class.find(:all,:order=>"shortcode ASC").each do |s|
      if s.read_attribute(:shortcode).to_i != shortcode
        # Begin locking?
        break
      end
      shortcode += 1
    end

    write_attribute(:shortcode,shortcode)
  end

  def done_shortcode
    # End Locking?
  end
end

1 Ответ

4 голосов
/ 16 апреля 2010

Эта строка:

self.class.find(:all,:order=>"shortcode ASC").each

выполнит последовательный поиск по всей вашей коллекции записей. Вам придется заблокировать всю таблицу, чтобы, когда один из ваших процессов сканировал следующее целое число, другие ожидали завершения первого. Это будет убийца производительности. Мое предложение, если это возможно, заключается в следующем:

  1. Добавить столбец, который указывает, когда истек срок действия записи (срок действия истекает к моменту создания? Последнее использование?). Индексируйте этот столбец.
  2. Когда вам нужно найти следующий наименьший используемый номер, сделайте что-то вроде

    Shorts.find (: condition => {: expired => true} ,: order => 'shortcode')

У базы данных будет сложная работа по поиску шорткода с наименьшим сроком действия. Напомним, что в отсутствие параметра : all метод find возвращает только первую соответствующую запись.

Теперь, чтобы предотвратить состязание между процессами, вы можете заключить это в транзакцию и заблокировать во время поиска:

Shorts.transaction do
    Shorts.find(:conditions => {:expired => true},:order => 'shortcode', :lock => true)
    #Do your thing here. Be quick about it, the row is locked while you work.
end #on ending the transaction the lock is released

Теперь, когда второй процесс начинает искать бесплатный шорткод, он не читает тот, который заблокирован (так что, вероятно, он найдет следующий). Это связано с тем, что параметр: lock => true получает эксклюзивную блокировку (для чтения и записи).

Проверьте это руководство , чтобы узнать больше о блокировке с помощью ActiveRecord.

...