2010-04-15 2 views
0

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

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

Вот что я до сих пор:

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 

ответ

4

Эта линия:

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

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

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

    Shorts.find (: условия => {: истекло => верно} ,: порядка => 'шорткод')

У этой базы данных будет сложная работа по поиску минимального короткого кода с истекшим сроком действия. Напомним, что в отсутствие параметра : 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 получает эксклюзивную блокировку (как чтение, так и запись).

Проверить this guide для дополнительной блокировки с помощью ActiveRecord.

Смежные вопросы