У меня есть сценарий, который выполняется в течение нескольких минут как задание в кластере в производственной среде. В кластере одновременно выполняется от 0 до 100 таких заданий, каждое из которых содержит по одному сценарию на задание. Обычно таких заданий нет или их бывает всплеск из 4-8.
Я хочу предотвратить запуск таких заданий при развертывании новой версии кода в рабочей среде.
Как мне это сделать, чтобы оптимизировать ремонтопригодность?
Моя первоначальная идея была такая:
Меня беспокоят возможные условия гонки и я рассматриваю возможность использования решения на основе базы данных. В случае моего приложения я бы использовал postgreSQL. Это решение на основе базы данных может быть более сложным в реализации и обслуживании, но может быть менее чувствительным к условиям гонки.
Возможно, в Capistrano есть стандартный механизм достижения этой цели, который используется для развертывания этого кода?
Примечания:
Когда вы ответите на вопрос, сравните ремонтопригодность предложенного вами решения с простым решением, которое я предлагаю выше (с использованием файлов блокировки).
Я не уверен, нужно ли мне учитывать условия гонки. То есть действительно ли эта система (с файлами блокировки) подвержена гонкам? Или это маловероятно?
Часто задаваемые вопросы:
Есть ли особая причина, по которой эти задания не следует запускать во время развертывания?
У меня были случаи, когда несколько заданий запускались в середине развертывания и из-за этого завершались сбоем. Поиск и повторный запуск таких неудачных заданий отнимает много времени. Задержка их во время развертывания приводит лишь к небольшому и редкому снижению производительности и является, безусловно, наиболее приемлемым решением. Для нашей системы ремонтопригодность является приоритетом номер один.
Как я описал в комментариях, эту функцию можно интегрировать как флаг функции. Очень популярное решение для рейлингов — драгоценный камень флиппер .
Псевдокод вашей вакансии может выглядеть так (пока я не знаю вашего кода вакансии)
class ProcessingJob < ApplicationJob
queue_as :default
def perform
return unless Flipper.enabled?(:jobs_processing)
... job's code
end
end
Flipper имеет пользовательский интерфейс администратора для включения/отключения флажков функций. Так, например, вы можете создать функцию jobs_processing
, включить ее, а затем в какой-то момент перед развертыванием отключить ее.
Пока флаг функции отключен во время развертывания, вы можете быть уверены, что никакие задания не выполняются. И после развертывания вы можете включить его снова.
Вы можете подумать, что Flipper — это сложное решение для вашей функции, поэтому вы можете сделать что-то проще без драгоценного камня и просто создать таблицу в базе данных, обновить свою страницу администратора, включив/отключив функции.
class CreateFeatureFlags < ActiveRecord::Migration[7.1]
def change
create_table :feature_flags do |t|
t.string :name, null: false, index: { unique: true }
t.timestamps
end
end
end
class FeatureFlag < ApplicationRecord
def self.enabled?(name)
where(name: name).exists?
end
end
FeatureFlag.enabled?('jobs_processing')
Спасибо большое за интересное, изящное и полезное решение! Мне это нравится больше, чем решение, которое я предложил в своем вопросе (блокировка на основе файлов).
У меня был один вопрос, который важен для нашей группы. Как говорится в моем вопросе: «Для нашей системы ремонтопригодность является приоритетом номер один». Не могли бы вы сравнить ремонтопригодность предлагаемого вами решения с простым решением, которое я предложил в вопросе (с использованием файлов блокировки). Неспециалисту может показаться, что решение на основе файла блокировки проще в обслуживании. Решение на основе файлов блокировки не требует внешних (потенциально, а позже и неподдерживаемых) драгоценных камней, базы данных или дополнительных таблиц. Решением на основе файлов блокировки также можно манипулировать в файловой системе без необходимости доступа к базе данных.
Другой вопрос, нужно ли учитывать условия гонки? То есть действительно ли эта система (с файлами блокировки) подвержена гонкам? Или это маловероятно? В последнем случае, возможно, более подходящим будет более простое решение на основе файлов блокировки. Обратите внимание: хотя я и пытаюсь критически оценить оба решения, лично я, как уже упоминал, предпочитаю ваше решение. Еще раз спасибо!
Условие гонки не имеет значения — в обоих случаях (установка флага функции или какой-либо файл блокировки) при отключении заданий вы просто ждете, пока завершатся уже запущенные задания, и запускаете развертывание.
О файле блокировки. Представьте, что вам нужно будет предоставить не-разработчику возможность включать/отключать флаги функций (возможно, в будущем будет больше флагов), что вы выберете — установить флаг в простом пользовательском интерфейсе или предоставить доступ к серверу?
Спасибо! Предоставление доступа к этому лицу, не являющемуся разработчиком, является хорошим и убедительным аргументом против решения с использованием файла блокировки.
Работа с консультативными замками на самом простом уровне с помощью psql
.
Сессия 1
select pg_advisory_lock(3752667);
Содержимое файла Advisory_lock_test.sql:
select pg_advisory_lock(3752667);
select "VendorID" from nyc_taxi_pl limit 10;
Затем сеанс 2:
psql -d test -U postgres -p 5452 -f advisory_lock_text.sql
Null display is "NULL".
Затем в сеансе 1:
select pg_advisory_unlock(3752667);
Вернемся к сеансу 2:
Null display is "NULL".
pg_advisory_lock
------------------
(1 row)
VendorID
----------
1
2
2
2
2
2
1
1
2
2
(10 rows)
Примечание:
Ниже используются блокировки уровня сеанса. Блокировки транзакций также доступны с помощью
pg_advisory_xact_lock
По сути, вы создаете блокировку в сеансе с помощью pg_advisory_lock(3752667)
, где число может быть одним 64-битным целым числом из двух 32-битных целых чисел. Они могут быть получены из значений, которые вы извлекаете из таблицы, поэтому число привязано к определенному действию, например. select pg_advisory_lock((select lock_number from a_lock where action = 'deploy'));
. Затем во втором или других сеансах вы пытаетесь заблокировать тот же номер. Если номер используется, не разблокирован или исходный сеанс не завершился, другие сеансы будут ждать, пока исходный сеанс не снимет блокировку. В этот момент будут выполнены остальные команды.
В вашем случае создайте номер, возможно, в таблице, связанный с развертыванием. При запуске развертывания заблокируйте номер перед выполнением изменений, а затем разблокируйте его в конце развертывания. Если развертывание завершится неудачей и сеанс завершится, блокировка также будет снята. Другие сценарии также должны будут начаться с попытки заблокировать этот номер. Если он используется, они подождут, пока он будет освобожден, а затем запустят остальные команды сценария и разблокируют его. Насколько это осуществимо, зависит от количества сценариев, с которыми вы имеете дело, и от того, насколько сильно люди будут придерживаться этого процесса.
Большое спасибо за ответ с подробными примерами! Как я могу снять блокировку, если она осталась позади из-за развертывания, которому не удалось снять блокировку? В случае с файлами блокировки я предложил сделать так: "Этот файл блокировки также автоматически удаляется отдельным заданием cron через, например, 30 мин, если при деплое не удается удалить этот файл. Например, если деплой в грубо убитом виде , этот файл не должен вечно блокировать задания. То есть файл удаляется отдельным заданием cron, если его возраст превышает 30 минут».
1) Если развертывание не удалось и сеанс завершился, блокировка будет снята. 2) Вы все равно можете использовать задание cron в качестве резервной копии. В нем сделайте select pg_advisory_unlock(3752667);
, который выдаст предупреждение, если блокировка уже снята. Если вы хотите выглядеть так, прежде чем прыгнуть, вы можете выбрать один из pg_locks: select * from pg_locks where locktype = 'advisory' and objid = 3752667;
Вы также упомянули блокировки транзакций. Когда мне следует использовать блокировки транзакций, а именно? ситуация, которую я описываю (развертывание)? Я нашел это, но никаких дальнейших указаний: «Блокировки могут быть установлены на уровне сеанса (чтобы они удерживались до тех пор, пока они не будут освобождены или пока не завершится сеанс) или на уровне транзакции (чтобы они удерживались до тех пор, пока не завершится текущая транзакция; никаких условий не существует». для выпуска вручную).", PostgreSQL: Документация: 16: 9.27. Функции системного администрирования
1) Запрос на блокировку должен быть select * from pg_locks where locktype = 'advisory' and objid = 3752667 and database = (select oid from pg_database where datname = 'test');
, поскольку рекомендательные блокировки предусмотрены для каждой базы данных. 2) Прочтите это Консультативные блокировки, где более подробно описаны блокировки транзакций и сеансов. Блокировки транзакций автоматически снимаются в конце транзакции. Я предполагаю, что вам понадобятся блокировки сеансов.
«Задание cron в качестве резервной копии. В нем выполните select pg_advisory_unlock(3752667);
», вы не можете pg_advisory_unlock()
заблокировать эту блокировку из cron. За пределами сеанса, в котором удерживается блокировка, он всегда возвращается false
с предупреждением. Вы можете проверить, находится ли он в pg_locks
, но чтобы освободить его из-за пределов сеанса владельца, вам придется запустить pg_terminate_backend()
, убивая весь сеанс, удерживающий блокировку. Если это уровень транзакции, вы можете pg_cancel_backend()
настроить таргетинг на транзакцию. Или вместо этого установите таймауты
Я расскажу о подходе к файлу флагов на основе ОС:
Высокая ремонтопригодность и переносимость: вам просто нужен современный дистрибутив Unix, который поставляется со стандартом flock
, а это значит, что большинство из них. Вы можете взаимодействовать с ним из оболочки, но он также доступен в большинстве современных языков программирования: Python fcntl.flock , Ruby File.flock , и т. д..
Никаких условий гонки: если вы планируете обрабатывать файлы напрямую, рассмотрите возможность использования вместо этого стада . Вместо того, чтобы создавать/искать/удалять файлы для повышения/понижения флага, он использует для них реальную систему блокировки на уровне ОС. Он предлагает встроенную поддержку общих/эксклюзивных блокировок, блокировки/блокировки с тайм-аутом/неблокировки и переноса команд.
Очистка: используйте на в тандеме с cron
. Как отметил @Джейкоб Миллер, если вы позволите cron
обрабатывать удаление напрямую, его циклы могут не синхронизироваться с вашими развертываниями, что приведет к преждевременной очистке флагов. Если вместо этого вы скажете ему обнаруживать только файлы флагов для удаления и запланируете их удаление через 30 минут после обнаружения, вы гарантируете, что владелец флага сможет завершить свою работу в течение >=30 минут.
Если вы часто меняете идентификаторы рекомендательной блокировки (имена файлов в этом сценарии), вы можете выполнить периодическую очистку, которая удалит самые старые разблокированные файлы, чтобы предотвратить беспорядок. Если вы повторно используете идентификаторы, любые оставшиеся файлы от прошлых исполнителей будут повторно использованы и в конечном итоге удалены будущими исполнителями.
Помимо очистки файлов флагов, вы можете рассмотреть возможность мониторинга и очистки связанных с ними рабочих процессов, которые все еще могут находиться поблизости.
@VonC Спасибо за ваш ответ ниже. Я нашел это полезным. Читайте также: Я хотел наградить ответ, но он был удален CommunityBot, а автор временно заблокирован. Что я должен делать? - Обмен мета-стеками и Каковы доказательства того, что этот конкретный ответ сгенерирован ИИ? - Переполнение мета-стека.
@VonC Я собирался наградить ваш (теперь удаленный) ответ ниже +200 наградой, но, как я объяснил здесь, мне помешали это сделать. Очевидно, я разочарован. С тех пор я уже разместил следующую награду (+400), но она, скорее всего, уйдет в другой ответ (извините!). Тем не менее, мне все равно хотелось бы, чтобы ваш ответ каким-то образом был восстановлен/спасен, а затем за него проголосовали как я, так и остальная часть сообщества (это полезный ответ).
@VonC Не могли бы вы опубликовать отдельный ответ на основе удаленного, но без текста, вызвавшего удаление? Возможно, это можно как-то перефразировать? Я знаю, что, возможно, прошу многого, и все ради потенциальной награды в виде всего лишь одного положительного голоса (моего). Но ответ хороший, ИМХО. И я думаю, что другие проголосуют за это, возможно, не сразу. Заранее спасибо за ваше время! И, разумеется, я рад видеть, что ты вернулся. :)
@TimurShtatland Спасибо и, еще раз, извиняюсь. По понятной причине я не могу опубликовать незаконный ответ, даже в перефразированном виде. Но вы должны иметь возможность увидеть удаленное и опубликовать свое слово, основываясь на своих настройках и опыте, что-то, что поможет другим читателям.
@VonC Я тоже думаю, что твой ответ стоило бы восстановить. Он продемонстрировал уникальный, индивидуальный подход, четкий и конкретный вариант использования, а также провел небольшое сравнение с другими ответами. Я думаю, что к тому времени, когда вы его удалили, человеческий вклад перевесил или даже вытеснил части, созданные ИИ.
@Zegarek Спасибо за этот отзыв. Поймите, я не удалил ответ. Для меня оно было удалено модераторами, которые считают (справедливо), что любое количество частей, созданных ИИ, перевешивает все остальное.
@VonC Спасибо за быстрый ответ! Я действительно вижу удаленный ответ. Ввиду отсутствия у меня знаний в этой теме, я воздержусь от публикации удаленного ответа своими словами. Это потому, что некоторые детали у меня над головой. Если это сделает кто-то другой (кто-то более квалифицированный), я, конечно, буду только приветствовать.
@VonC Что касается твоего недавнего поста: в моем случае извинений не требуется! Я благодарен вам как за оригинальный ответ, так и за размещение объяснений произошедшего на Meta SO. На мой взгляд, объяснение этого было правильным, и оно также показывает сообществу, как бороться с прошлыми ошибками и продуктивно двигаться вперед.
Re: Файл блокировки. Имейте в виду, что cron может запускаться каждые 30 минут, но вы можете начать развертывание на минуте (например) 29. Ваш файл блокировки будет существовать только минуту, прежде чем cron сотрет его, и вы вернетесь к исходному состоянию. проблема.