Планировщик, который запускал один job четыре раза
withoutOverlapping жил в Memcached. Вытеснение HTML снимало mutex. Четыре пересборки писали один ключ листинга.
Контейнер schedule каждую минуту делает php artisan schedule:run. catalog:rebuild стоял на everyMinute() с withoutOverlapping(). Job занимал четыре минуты на грязном множестве из нескольких тысяч id. Четыре процесса писали одни ключи Memcached. Redis catalog:dirty не пустел, потому что каждый запуск добавлял id, которые предыдущий ещё не дописал.
withoutOverlapping() кладёт lock в кэш по умолчанию. CACHE_STORE был Memcached. Slab листинга и mutex делили slab-класс. Когда каталог забивал кэш, ключ lock уходил. Следующая минута не видела mutex и стартовала снова.
Проблема была в mutex внутри хранилища, которое HTML выбрасывает нарочно. Четыре пересборки дрались за один ключ листинга. Грязное множество не сходило. Lock нужен рядом с Horizon, в Redis, с TTL длиннее самой медленной пересборки.
Где живёт lock
Lock планировщика переехал в Redis. SET schedule:rebuild NX EX 600. Horizon уже там. Вытеснение HTML в Memcached этот ключ не снимет. Job по-прежнему читает грязные id из множества Redis, собирает slab и пишет Memcached. Строитель один.
- onOneServer() ходит в тот же Redis, поэтому две реплики schedule не стартуют вместе.
- Timeout lock длиннее самой медленной пересборки, которую мерили.
- Пропущенная минута нормальна. Двойная пересборка нет.
Грязное множество теперь сходит. Memcached видит одного писателя. CPU контейнера schedule между тиками снова простой. Баг был не в cron. Баг был в lock внутри хранилища, которое страницы выбрасывает нарочно.
Какой опыт из этого
withoutOverlapping() ровно настолько хорош, насколько хранилище за CACHE_STORE. Memcached это не сервис lock.
TTL mutex короче job это второй старт. EX 600 скучный и верный.
Минуту пропущу раньше, чем запущу две пересборки. Перекрытие дороже задержки.
