Оптимизация 1С на реальном примере. История №2 - deadlock

11.06.18

База данных - HighLoad оптимизация

Статья о том, как я расследовал взаимоблокировки (deadlock) в 1С.

Наверное, каждый из нас сталкивался (или столкнется) с проблемой взаимоблокировок (deadlock), это когда 1С настойчиво нам выдает сообщения вида: "Транзакция (идентификатор процесса XX) вызвала взаимоблокировку ресурсов блокировка с другим процессом и стала жертвой взаимоблокировки".

Вот и мне удостоилась честь столкнуться с этим феноменом. Обратился клиент, с жалобой на подобного рода сообщения. Раньше косвенно слышал об этом "звере", но никогда не погружался с головой.

 
 Сведения об исследуемой системе:

Сервер СУБД: MS SQL 2008R2
ОС: Windows Server 2008R2
ОЗУ: 32GB
ЦП: Intel Core i7
Дисковая подсистема: Raid 1 на HDD
Платформа: 8.3.7.1845
Режим совместимости: 8.2.13
Режим блокировок: Автоматический
Конфигурация: Управление торговым предприятием для Казахстана (аналог КА 1.1)
Пиковая загрузка: 20+ пользователей, + 30 торговых представителей постоянно обменивающихся информацией с ЦБ.
Объем БД: ~35GB

 
 Немного теории

Но кто когда идет читать специальную литературу сразу встретив проблему? Правильно, у нас есть проблема, и ее решение наверняка есть в интернете. Немного погуглив, я понял, что "причин может быть множество , а последствия могут быть разными" (с).
Первым что советуют в интернете посмотреть на статистику, индексы и итоги, однако все регламентные операции были настроены и успешно выполнялись по расписанию, итоги были актуальными.
Симптомы были ясны, но непонятно было с кем бороться. Поэтому было принято решение понять как анализировать взаимоблокировки, появилось несколько вариантов:
- Включение технологического журнала, и его ручной анализ вместе с трассировкой СУБД - очень сложно
- 1С:Центр управления производительностью (ЦУП) - дорого и сложно
- Сервисы Гилева http://www.gilev.ru/deadlock/ - бесплатно и просто, это кажется то, что нужно
Повозившись с настройкой, включил мониторинг на 15 минут в самый нагруженный момент времени. Вот что удалось словить:

 
 Пойманный дедлок

Хорошо, дедлок словили, но что делать дальше с этой информацией? А теперь нам понадобится теория. Чтобы понять причину дедлока, нужно выяснить, какие таблицы были заблокированы, затем проанализировать код. Также желательно научиться воспроизводить взаимоблокировку, чтобы изучить ее подробнее.

  • С помощью обработки СтруктураХраненияБазыДанных (или глобальный метод ПолучитьСтруктуруХраненияБазыДанных) выясняем, что _AccRgED652 это таблица РегистрБухгалтерии.Типовой.ЗначенияСубконто, а _AccRgAT2618 это РегистрБухгалтерии.Налоговый.ИтогиПоСчетамССубконто2.
  • По времени процессов, и журналу регистрации выясняем, что проводились параллельно 2 документа: РеализацияТоваровУслуг и КомплектацияТМЗ. Также удалось смоделировать дедлок: проводим документ КомплектацияТМЗ под отладкой, ставим точку останова, параллельно проводим документ РеализацияТоваровУслуг, снимает точку останова и получаем ошибку. В обратном порядке ошибка не воспроизводится.

Теперь попробуем понять, что в итоге случилось:
Второй процесс строкой кода ОбщийМодуль.УправлениеЗапасамиПартионныйУчет.Модуль : 3383 читает остатки товаров из регистра бухгалтерии Типовой, а первый процесс строкой ОбщийМодуль.УправлениеЗапасамиПартионныйУчет.Модуль : 3664 читает остатки из регистра бухгалтерии Налоговый. На первый взгляд, ошибка "захват ресурсов в разном порядке". Но если проанализировать порядок вызова процедур из общего модуля по списанию товаров, то вроде как он одинаковый для всех документов (сначала упр. регистры, затем регистр по бух.учету, затем регистр по нал.учету). Хм, хорошо, анализируем код дальше, в этих запросах, по чтению остатков, нет конструкции "ДЛЯ ИЗМЕНЕНИЯ", которая позволила бы однозначно заблокировать остатки на чтение, казалось бы, добавляем ее в запросы, и дело в шляпе. Редактируем тексты запросов, блокируем данные на чтение, но чуда не случается, дедлок также устойчиво воспроизводится, причем оказывается и с другими документами, типа СписаниеТМЗ, ПеремещениеТМЗ и т.д. Таким образом проблема принимает глобальный характер. Заходим в тупик.
От безысходности делаются различные тестирования ИБ, пересчеты итогов и так далее, но уже на копии. От нее же, взор устремляется на свойство режим разделения итогов для регистров бухгалтерии, так как что-то было не так с остатками, были предположения что разделитель каким-либо образом "портит" запрос. Разделение итогов для регистров бухгалтерии было включено. Выключаем, проверяем. Что-то изменилось, а именно дедлока теперь нет, но транзакции ждут друг друга, и одна отваливается по таймауту (то есть не дождавшись освобождения ресурсов, один процесс освобождает свои ресурсы), deadlock заменился на ожидание на блокировке, но его суть осталась, раньше сервер СУБД явно видел такие ошибки, и пресекал их сразу, а теперь перестал. Что это нам дает? В общем-то не особо много, но есть инструмент для анализа ожиданий на блокировках в тех же сервисах Гилева http://www.gilev.ru/latch/, почему бы не попробовать, терять нечего.
Настраиваем, проводим опыты, ждем пока все данные будут обработаны.

 
 Ожидания на блокировках

Так, а здесь уже что-то новое. Расшифровка нам показывает ожидания в двух местах на таблице _AccumRg12498 (РегистрНакопления.ТоварыОрганизацийБУ), казалось бы причем здесь это, если проблемы были с регистрами бухгалтерии? Но может сервис что-то не то показывает, посмотрим код. В общем модуле НомераГТДСервер читаются остатки из вышеназванного регистра, но чтение также неблокирующее. Добавляем конструкцию "ДЛЯ ИЗМЕНЕНИЯ", тестируем - документы проводятся как надо, ожидание не заканчивается по таймауту. Неужели это оно? Но что-то не сходится, из сознания никак не выходит чувство того, что это костыль.
У нас еще одна строка с проблемой. Попробуем посмотреть что в общем модуле ПолныеПрава. Выполняется процедура ОпределитьНаличиеДвиженийПоРегистратору. Зачем она нужна и откуда вызывается? Запускаем отладку и ставим точку останова. Через Стек вызовов смотрим откуда вызывается эта процедура: ОбработкаПроведения -> ОбщегоНазначения.РучнаяКорректировкаОбработкаПроведения -> ОбщегоНазначения.УдалитьДвиженияРегистратора. Ага, перед началом проведения очищаются движения документа, а процедура нужна чтобы определить по каким регистрам проведен документ. Определяет она это следующим кодом:

Для Каждого Движение ИЗ МетаданнныеДокумента.Движения Цикл
        
        Если счетчик_таблиц > 0 Тогда
            счетчик_таблиц = счетчик_таблиц - 1;
            Продолжить;
        КонецЕсли;
        
        ТекстЗапроса = ТекстЗапроса + "
        |" + ?(ТекстЗапроса = "", "", "ОБЪЕДИНИТЬ ВСЕ ") + "
        |ВЫБРАТЬ ПЕРВЫЕ 1 """ + Движение.ПолноеИмя() +  """ КАК Имя ИЗ " 
        + Движение.ПолноеИмя() + " ГДЕ Регистратор = &Регистратор";            
        
КонецЦикла;
 
Запрос.Текст = ТекстЗапроса;
Выборка = Запрос.Выполнить().Выбрать();

То есть читаем в транзакции и накладываем блокировку в самом ее начале, но зачем? Можно же безусловно записать пустые наборы по всем регистрам. Корректируем процедуру ОбщегоНазначения.УдалитьДвиженияРегистратора

//****Как было
// получение списка регистров, по которым существуют движения
//ТаблицаДвижений = ПолныеПрава.ОпределитьНаличиеДвиженийПоРегистратору(ДокументОбъект.Ссылка);

//****Как стало	
//безусловно очищаем все регистры
ТаблицаДвижений = Новый ТаблицаЗначений;
ТаблицаДвижений.Колонки.Добавить("Имя");
	
Для Каждого Движение ИЗ ДокументОбъект.Метаданные().Движения Цикл
	НоваяСтрока = ТаблицаДвижений.Добавить();
	НоваяСтрока.Имя = Движение.ПолноеИмя();
КонецЦикла;

Дальше ТаблицаДвижений перебирается и производится запись пустых наборов, эту часть я не менял. Все предыдущие изменения откатываем. Итак, момент истины, пытаемся воспроизвести дедлок и он не происходит.
В следующие несколько дней изменения были сделаны на продуктивном сервере и производились измерения в часы активной нагрузки с помощью выше названных сервисов Гилева (также был использован сервис http://www.gilev.ru/status/ в качестве комплексного анализа).
Взаимоблокировки прекратились, но уверен что они еще возникнут, и были устранены лишь те, что лежали на поверхности. 
 

P.S Статья не является руководством, а отражает лишь личный опыт, который был новым и интересным.
Не рассмотрен вопрос о наложенных блокировках (инструмент Просмотр заблокированных строк в 1С) - какие были до изменения, какие стали, я их смотрел, но интерпретировать полноценно не смог.
Основной целью статьи является побудить молодых специалистов не бояться решать проблемы различного рода, несмотря на отсутствие должного опыта.
Конструктивная критика приветствуется.

deadlock взаимоблокировки дедлок

См. также

HighLoad оптимизация Технологический журнал Системный администратор Программист Бесплатно (free)

Обсудим поиск и разбор причин длительных серверных вызовов CALL, SCALL.

24.06.2024    5981    ivanov660    12    

56

HighLoad оптимизация Программист Платформа 1С v8.3 Бесплатно (free)

Метод очень медленно работает, когда параметр приемник содержит намного меньше свойств, чем источник.

06.06.2024    10426    Evg-Lylyk    61    

45

HighLoad оптимизация Программист Платформа 1С v8.3 1C:Бухгалтерия Бесплатно (free)

Анализ простого плана запроса. Оптимизация нагрузки на ЦП сервера СУБД используя типовые индексы.

13.03.2024    5631    spyke    28    

49

HighLoad оптимизация Программист Платформа 1С v8.3 Бесплатно (free)

Оказывается, в типовых конфигурациях 1С есть, что улучшить!

13.03.2024    8336    vasilev2015    20    

42

HighLoad оптимизация Инструменты администратора БД Системный администратор Программист Платформа 1С v8.3 1C:Бухгалтерия Абонемент ($m)

Обработка для простого и удобного анализа настроек, нагрузки и проблем с SQL сервером с упором на использование оного для 1С. Анализ текущих запросов на sql, ожиданий, конвертация запроса в 1С и рекомендации, где может тормозить.

2 стартмани

15.02.2024    13389    268    ZAOSTG    87    

115

HighLoad оптимизация Системный администратор Программист Платформа 1С v8.3 1C:Бухгалтерия Абонемент ($m)

Принимать, хранить и анализировать показания счетчиков (метрики) в базе 1С? Почему бы нет? Но это решение быстро привело к проблемам с производительностью при попытках построить какую-то более-менее сложную аналитику. Переход на PostgresSQL только временно решил проблему, т.к. количество записей уже исчислялось десятками миллионов и что-то сложное вычислить на таких объемах за разумное время становилось все сложнее. Кое-что уже практически невозможно. А что будет с производительностью через пару лет - представить страшно. Надо что-то предпринимать! В этой статье поделюсь своим первым опытом применения СУБД Clickhouse от Яндекс. Как работает, что может, как на нее планирую (если планирую) переходить, сравнение скорости работы, оценка производительности через пару лет, пример работы из 1С. Все это приправлено текстами запросов, кодом, алгоритмами выполненных действий и преподнесено вам для ознакомления в этой статье.

1 стартмани

24.01.2024    6397    glassman    20    

42

HighLoad оптимизация Программист Платформа 1С v8.3 1C:Бухгалтерия Абонемент ($m)

Встал вопрос: как быстро удалить строки из ТЗ? Рассмотрел пять вариантов реализации этой задачи. Сравнил их друг с другом на разных объёмах данных с разным процентом удаляемых строк. Также сравнил с выгрузкой с отбором по структуре.

09.01.2024    17267    doom2good    49    

71
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. PerlAmutor 155 11.06.18 06:43 Сейчас в теме
ОбщегоНазначения.РучнаяКорректировкаОбработкаПроведения -> ОбщегоНазначения.УдалитьДвиженияРегистратора

Как я понимаю это часть механизма БСП. Если по какой-то причине этот код вставили в обработку проведения значит есть какая-то проблема с движениями ручных корректировок. В момент проведения они, видимо, не должны очищаться. Предлагаю изучить этот вопрос детальней, иначе могут быть интересные последствия уже с остатками.
2. Dream_kz 129 11.06.18 07:30 Сейчас в теме
(1)
движениями ручных корректировок

В первой процедуре проверяется ручная корректировка, и если она есть, то процедура очищения движений не вызывается.
3. PerlAmutor 155 11.06.18 08:30 Сейчас в теме
(2) "В первой" это в какой, там где "ОБЪЕДИНИТЬ ВСЕ"? Если есть ручная корректировка, то вообще никакие движения не очищаются, или только движения ручной корректировки?
4. Dream_kz 129 11.06.18 08:55 Сейчас в теме
(3) ОбщегоНазначения.РучнаяКорректировкаОбработкаПроведения
5. pahalovo 11.06.18 09:10 Сейчас в теме
Не могу понять почему пропала взаимоблокировка.
Судя по статье, причина кроется в процедуре УдалитьДвиженияРегистратора. Когда определяем наличие движений по регистру устанавливается S блокировка, а когда записываем пустой набор записей устанавливается X блокировка.

Но УдалитьДвиженияРегистратора - это типовая процедура, во многих типовых конфигурациях встречается, которые работают себе без взаимоблокировок.
6. vasilev2015 2739 11.06.18 20:09 Сейчас в теме
При автоматическом режиме блокировки очистка пустой таблицы блокирует всю таблицу исключительной блокировкой (SERIALIZABLE). Когда у меня возникла такая ситуация, я поставил исключения: не записывать конкретные регистры. Это было еще до того, как прочитал Е.Филипова "Настольная книга эксперта" )))
Попробуйте посмотреть в технологическом журнале, с помощью инструментов разработчика или в текстовом редакторе. Там подробнее.
7. Dream_kz 129 11.06.18 20:20 Сейчас в теме
(6) Так они и раньше записывались, просто до этого еще и читались.

не записывать конкретные регистры

Так если движения не очистим, запросы к остаткам буду "грязные", с учетом текущих движений, разве нет?
8. vasilev2015 2739 12.06.18 10:53 Сейчас в теме
(7) в конфигурации УТ 10 часто используется очистка заведомо пустых регистров. Это недопустимо.
11. СергейК 51 13.06.18 13:39 Сейчас в теме
(8)
...очистка заведомо пустых регистров. Это недопустимо.

Т.е. так как сделал автор темы это не правильно? Но вроде стало то заметно лучше?
Может ли быть что критична версия платформы, и с какого то релиза
очистка пустых регистров не мешает по крайней мере (хотя стало мешать чтение...)?
12. Wrols 90 09.09.18 08:52 Сейчас в теме
(8) Николай, добрый день!

1. Ответьте, пожалуйста, СергеюК - "Т.е. так как сделал автор темы это не правильно?"
(Тоже интересно).

2. И всё-таки типовая функция "ПолныеПрава.ОпределитьНаличиеДвиженийПоРегистратору(ДокументОбъект.Ссылка)" УТ 10.3 построена правильно или нет?

3. "Очистка заведомо пустых регистров" - для исключения этого по-вашему необходимо перед очисткой обязательно выполнять чтение набора записей, и - если он не пустой - очищать?
17. vasilev2015 2739 09.09.18 20:20 Сейчас в теме
(12) Здравствуйте !

Изначально проблема возникла, поскольку режим автоматических блокировок, избыточные блокировки. Запись в пустые регистры здесь помогла, поскольку нет повышения уровня изоляции, не возникает дедлок. Возникает ожидание, но его не заметно. Если автору помогло и он сам доволен - то он все делает правильно )))

Когда переведете на управляемые блокировки - уберите излишнюю запись пустых регистров. Станет чуть быстрее, светлее, добрее. О том же пост 9.

Функция ОпределитьНаличиеДвиженийПоРегистратору в режиме управляемых блокировок будет отрабатывать без ошибок.
triviumfan; Wrols; +2 Ответить
18. СергейК 51 10.09.18 04:50 Сейчас в теме
9. o.nikolaev 216 13.06.18 10:42 Сейчас в теме
С автоматического режим блокировок надо уводить базу. Уйдут и взаимоблокировки и таймауты.
abadonna83; Somebody1; +2 Ответить
13. Wrols 90 09.09.18 08:56 Сейчас в теме
(9) Олег, добрый день!

Сколько по-вашему времени требуется на перевод типовой УТ 10.3 на управляемые блокировки?
Насколько сильно будет модифицирована конфигурация?
После таких изменений последующие обновления релиза возможны?
14. o.nikolaev 216 09.09.18 09:35 Сейчас в теме
1-2 дня. У тех кто специализируется - 1. Не сильно, только места где (Блокировать для изменения в запросах). Да возможны.
10. gavks 9 13.06.18 13:07 Сейчас в теме
Хм, это был типовой код. Почему же раньше все было норм и проводилось без дедлоков?
15. o.nikolaev 216 09.09.18 09:35 Сейчас в теме
(10) Нагрузка выросла видимо на систему.
16. Fox-trot 163 09.09.18 12:25 Сейчас в теме
Все это лишь от незнания типовой или используемого по
19. yarsort 141 08.02.21 00:15 Сейчас в теме
В общем, суть проблемы так и не решена. А я расскажу почему так было: у Вас скорее всего пропал индекс в таблице накопления.

Последнее время, особенно после обновлений участились случаи слета индексов по таблицам партионного учета.

В итоге:
1. Тупит механизм проведения документа по партиям в процедурах получения остатков партий.
2. Начинает жутко тупить получение движений документа, когда код выбирает по одной записи каждого регистра и пытается понять есть ли движения вообще.
3. Тупит запись документа в регистры.

Надо искать в каком именно регистре пропал или слетел индекс.

Как правило это индекс на вид выглядит вот так: [_AccumRg13684_ByRecorder_RN], где AccumRg13684 - это имя регистра и оно может менятся.

Совет: в идеале вручную проверьте индексы таблиц регистров накоплений. Там у всех должен быть этот индекс.

Как только поставите его назад, все будет летать. Вроде написал простым языком для неопытных.
20. Smollsan 07.05.24 12:21 Сейчас в теме
(19) Случилась такая же ошибка на Платформе 8.3.22.2239. Докопаться до блокируемых таблиц не успел, поскольку на удивление помогло полное ТИИ (Реиндексация таблиц через MS SQL не помогло)
Оставьте свое сообщение