Я нашел 4 "правильных" способа сделать это:
- В шпаргалке для пользователей ActiveRecord предполагаются заменители ActiveRecord
increment
иincrement_counter
бытьalbum.values[:column] -= 1 # or += 1 for increment
иalbum.update(:counter_name=>Sequel.+(:counter_name, 1))
- В решении SO
update_sql
предлагается тот же эффектs[:query_volume].update_sql(:queries => Sequel.expr(3) + :queries)
- В случайной ветке я нашел вот это
dataset.update_sql(:exp => 'exp + 10'.lit)
- В документах Sequels API для обновления я нашел это решение
http://sequel.jeremyevans.net/rdoc/classes/Sequel/Dataset.html#method-i-update
однако ни одно из решений фактически не обновляет значение и не возвращает результат безопасным атомарным способом.
Решения, основанные на «добавлении значения, а затем сохранении», должны, на самом деле, недетерминированно давать сбой в многопроцессорных средах, что приводит к таким ошибкам, как:
- счетчик альбома равен 0
- поток A и поток B извлекают
album
- поток A и поток B увеличивают значение в хэше/модели/и т. д.
- поток A и поток B обновляют счетчик до одного и того же значения
- в результате: A и B оба устанавливают счетчик на 1 и работают со значением счетчика 1
Sequel.expr
и Sequel.+
, с другой стороны, на самом деле не возвращают значение, но Sequel::SQL::NumericExpression
и (на самом деле) у вас нет возможности получить его, кроме как выполнить еще один круговой обход БД, что означает, что это может произойти:
- счетчик альбома равен 0
- поток A и B увеличивают значение, значение увеличивается на 2
- поток A и B извлекают строку из БД
- в результате: A и B оба устанавливают счетчик на 2 и работают со значением счетчика 2
Итак, за исключением написания пользовательского кода блокировки, какое решение? Если его нет, если не считать написания пользовательского кода блокировки :), как лучше всего это сделать?
Обновление 1
Обычно меня не устраивают ответы о том, что я хочу слишком многого от жизни, как предполагает 1 ответ :)
Альбомы являются просто примером из документов.
Представьте, например, что у вас есть счетчик транзакций в POS-терминале электронной коммерции, который может одновременно принимать 2 транзакции на разных хостах, и в банк вам нужно отправить их с целочисленным счетчиком, уникальным за 24 часа (называемым systan), отправить 2 trx с тем же систаном и 1 будет отклонен, или, что еще хуже, будут предупреждены пробелы в подсчетах (потому что они намекают на «отсутствующие транзакции»), поэтому невозможно использовать значение идентификатора БД.
Менее серьезный пример, но больше относящийся к моему варианту использования: экспорт нескольких файлов запускается одновременно в фоновом воркере, у каждого места назначения файла есть собственный счетчик. Пробелы в счетчиках предупреждают, воркеры находятся на разных хостах (поэтому мьютексы бесполезны). И у меня есть чувство, что я все равно скоро решу более серьезную проблему.
Последовательности DB также не годятся, потому что это означало бы выполнение DDL при добавлении каждого терминала, а здесь мы говорим о тысячах. Даже в моем менее серьезном случае использования DDLing для действий веб-портала по-прежнему является PITA и может даже не работать в зависимости от приведенной ниже схемы кэширования (из-за реализации ActiveRecord
и Sequel
- и в моем случае я использую оба - может потребоваться перезапуск сервера только зарегистрировать продавца).
Redis может это сделать, но кажется безумием добавлять еще один компонент инфраструктуры только для счетчиков, когда вы работаете с базой данных, совместимой с ACID.