Анализ блокировок СУБД: таблица изменений плана обмена 1С

18.12.20

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

Практический пример анализа типичной проблемы ожидания на блокировках СУБД, возникающих при использовании планов обмена 1С. Сервер СУБД: Microsoft SQL Server.

Данная статья является практическим дополнением теоретической статьи о планах обмена 1С.

Сервер СУБД: Microsoft SQL Server 2014.

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

 

0. Описание задачи.

Периодически обмен между сайтом Интернет-магазина и торговой учётной системой "ложился" неизвестно почему. Изначально обратили на это внимание по причине роста количества входящих сообщений в очереди заказов покупателей. Если количество сообщений превышало настроенный порог, то об этом оповещалась система мониторинга состояния очередей. По началу грешили на зависание фоновых заданий 1С, которые обрабатывали эти очереди. Перезагружали сервер 1С, и это на какое-то время помогало. Затем обратили внимание на возникновение таймаутов ожидания на блокировках СУБД в моменты "падения" обмена. Решили расследовать причины этих блокировок.

 

1. Настройка мониторинга блокировок СУБД.

Для настройки мониторинга блокировок СУБД было выбрано решение от Дмитрия Короткевича Blocking Monitoring Framework, которое он описывает и рекомендует к использованию в своей книге "Expert SQL Server Transactions and Locking". Решение основано на использовании Event Notifications SQL Server, в частности настройке получения blocked process report.

Системная настройкаSQL Server "blocked process threshold" была установлена на 5 секунд, то есть отслеживается любое ожидание на блокировке продолжительностью более 5 секунд.

Установка BMFramework заняла буквально несколько минут. В результате чего на сервере была создана база данных "DBA", которая, сразу же после установки, начала собирать статистику по взаимным блокировкам и ожиданиям на блокировках. Режим мониторинга круглосуточный. Создаваемая при этом нагрузка на сервер фактически незаметна.

Кроме этого была добавлена таблица "tech_log_entries" для сбора событий технологического журнала 1С. Об этом рассказано ниже.

 

 

 

 

 

 

2. Анализ результатов мониторинга.

По результатам мониторинга был получен следующий отчёт "Топ 15 блокировок по хэшу плана запроса".

 

Из этого отчёта, благодаря колонке "BlockedSql" (заблокированное выражение SQL), можно понять, что наиболее частым ожиданием на блокировках является таблица _ReferenceChngR332. Обратившись за помощью к функции платформы "ПолучитьСтруктуруХраненияБазыДанных", удалось выяснить что это таблица изменений справочника "ПартииТоваров".

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

 

В данном случае отчёт уже отфильтрован по таблице _ReferenceChngR332. Если внимательно прочитать статью о планах обмена 1С, то анализ запросов SQL из отчёта подскажет нам, что использовались следующие вызовы методов менеджера планов обмена:

 

1. Запросы при вызове метода ПланыОбмена.ВыбратьИзменения (первая и вторая его части):

UPDATE T1 SET _MessageNo = @P1 ...

и

SELECT T1._IDRRef FROM dbo._ReferenceChngR332 T1 WHERE T1._NodeTRef = 0x0000000B ...

 

2. Запрос при вызове метода ПланыОбмена.УдалитьРегистрациюИзменений.

DELETE FROM T1 FROM dbo._ReferenceChngR332 T1 WHERE T1._IDRRef = @P1 ...

 

3. Запрос при вызове метода ПланыОбмена.ЗарегистрироватьИзменения.

UPDATE T1 SET _MessageNo = CAST(NULL AS NUMERIC(38,8)) FROM dbo._ReferenceChngR332 T1 ...

 

Другими словами становится понятно, что теоретические рассуждения о трёх конкурирующих ролях на таблицах изменений планов обмена 1С имеют в данном случае практическое подтверждение.

Теперь нужно получить контекст кода 1С, который вызывает соответствующие методы менеджера планов обмена, чтобы постараться понять каким образом это происходит и что можно с этим сделать. Для этого нужно настроить сбор событий технологического журнала 1С и получить эту информацию оттуда. Как это было сделано я расскажу немного позже, а сейчас я хочу обратить внимание на запрос SQL, который является корнем цепочки блокировок.

Запрос корня цепочки это выражение SELECT языка SQL. Однако все мы знаем, что 1С:Предприятие 8.3 работает в режиме управляемых блокировок, а, следовательно, использует режим изоляции транзакций READ COMMITTED SNAPSHOT (RCSI), который не является блокирующим чтением. Блокировки быть не должно. Однако, в данном случае SELECT выполняется в транзакции, которая ранее уже заблокировала читаемые строки. Эта транзакция выглядит следующим образом (подробнее в статье "Планы обмена 1С"):

Небольшой нюанс: в целях оптимизации блокировок и обменов выгрузка объектов 1С выполняется порциями по 3000 объектов или менее (такая настройка на уровне узла плана обмена). Таким образом фильтрация метода "ВыбратьИзменения" производится по массиву, передаваемых в него ссылок. То есть реально добавляется ещё фильтр вида ГДЕ Ссылка В (&МассивСсылок), что в свою очередь накладывает блокировки на записи по этим ссылкам на уровне СУБД: WHERE T1._IDRRef IN (@P1, @P2 ...

 

3. Настройка технологического журнала 1С.

Для отлова нужных событий и контекстов 1С файл настроек технологического журнала 1С был сформирован следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
 <dump create="false"/>
 <log location="D:\tech_logs\" history="48">
  <event>
    <eq property="name" value="dbmssql"/>
    <like property="sql" value="SELECT%"/>
    <like property="sql" value="%_ReferenceChngR332%"/>
    <like property="sql" value="%T1._NodeTRef%"/>
    <like property="sql" value="%0x0000000B%"/>
    <like property="sql" value="%T1._IDRRef%"/>
  </event>
  <event>
    <eq property="name" value="dbmssql"/>
    <like property="sql" value="UPDATE%"/>
    <like property="sql" value="%_ReferenceChngR332%"/>
  </event>
  <event>
    <eq property="name" value="dbmssql"/>
    <like property="sql" value="DELETE%"/>
    <like property="sql" value="%_ReferenceChngR332%"/>
  </event>
  <property name="all"/>
 </log>
</config>

Для настройки файла logcfg.xml использовались рекомендации Юрия Пермитина из его статьи "Мониторинг SQL Server с помощью Extended Events (и не только) для 1С. Как держать руку на пульсе?", а также его же утилита для парсинга технологического журнала YY.TechJournalReaderAssistant. Утилита была доработана для того, чтобы загружать события DBMSSQL в таблицу dbo.tech_log_entries базы данных DBA (см. пункт 2 выше).

Недостатком использования ТЖ 1С явилось то, что каждый час генерировалось несколько десятков мегабайт логов. Однако, учитывая тот факт, что было заранее известно какие шаблоны SQL запросов искать, достаточно было запустить сбор логов ТЖ всего на один час или даже на 10-15 минут.

 

4. Анализ технологического журнала 1С.

В результате анализа записей ТЖ были получены контексты, стэки вызовов, кода 1С. О них я расскажу далее. Перед этим мне хотелось бы обратить внимание на одно открытие, которое я сделал при анализе запросов SQL виновника блокировок. В каких-то случаях они имели такой вид:

SELECT
   T1._IDRRef
FROM
   dbo._ReferenceChngR332 T1
WHERE T1._NodeTRef = 0x0000000B
  AND T1._NodeRRef = @P1
  AND (T1._IDRRef IN (@P2, @P3, @P4))

В других случаях такой:

SELECT
   T1._IDRRef
FROM
   dbo._ReferenceChngR332 T1
WHERE T1._NodeTRef = 0x0000000B
  AND T1._NodeRRef = @P1
  AND (T1._IDRRef IN
         (SELECT CASE WHEN T2._INVALUELISTTRef = 0x0000002A
                      THEN T2._INVALUELISTRRef ELSE @P2 END AS INVALUELISTRRef
          FROM
             #tt7 T2 WITH(NOLOCK)
          WHERE
             CASE WHEN T2._INVALUELISTTRef = 0x0000002A
                  THEN T2._INVALUELISTRRef ELSE @P3 END IS NOT NULL))

Разница заключается в использовании временной таблицы для передачи фильтра в виде массива ссылок в метод менеджера планов обмена "ВыбратьИзменения". Это говорит о том, что в каких-то случаях 1С считает, что массив достаточно большой, чтобы начать использовать временную таблицу. Экспериментальным путём удалось выяснить, что количество ссылок в массиве, начиная с которого создаётся временная таблица, равно 129. Создание временной таблицы означает нагрузку на tempdb и дисковую систему сервера СУБД. Это может влиять на продолжительность выполнения всего метода "ВыбратьИзменения" (транзакции СУБД), особенно учитывая тот факт, что в нём выполняются последовательно сначала UPDATE, а только затем SELECT.

Поиск контекстов 1С виновника и жертв ожидания на блокировках выполнялся при помощи таблицы tech_log_entries базы данных DBA, описанных выше.

 

5. Виновник блокировок.

ПланыОбмена.ВыбратьИзменения

Виновник блокировок имеет следующий стэк вызовов. Ясно, что это выгрузка данных РИБ.

ОбщийМодуль.ДлительныеОперации.Модуль : 652 : ВыполнитьПроцедуру(ИмяПроцедуры, ПараметрыПроцедуры);
ОбщийМодуль.ДлительныеОперации.Модуль : 661 : ОбщегоНазначения.ВыполнитьМетод(ИмяПроцедуры, ПараметрыПроцедуры);
ОбщийМодуль.ОбщегоНазначения.Модуль : 4762 : Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
	: 1 : ОбменДаннымиРИБ.ВыгрузитьИзмененныеОбъекты(Параметры[0],Параметры[1])
ОбщийМодуль.ОбменДаннымиРИБ.Модуль : 129 : Пакет = СформироватьПакетОбмена(НастройкиОбмена, СписокМетаданных);
ОбщийМодуль.ОбменДаннымиРИБ.Модуль : 372 : Возврат РаспределитьПакетПоТипамОбъектов(Пакет, НастройкиОбмена);
ОбщийМодуль.ОбменДаннымиРИБ.Модуль : 1021 : Выборка = ПланыОбмена.ВыбратьИзменения(УзелОбмена, НомерСообщения, Фильтр);

 

6. Жертвы блокировок.

 

6.1. Конкурирующая выгрузка данных.

ПланыОбмена.ВыбратьИзменения

ОбщийМодуль.ДлительныеОперации.Модуль : 652 : ВыполнитьПроцедуру(ИмяПроцедуры, ПараметрыПроцедуры);
ОбщийМодуль.ДлительныеОперации.Модуль : 661 : ОбщегоНазначения.ВыполнитьМетод(ИмяПроцедуры, ПараметрыПроцедуры);
ОбщийМодуль.ОбщегоНазначения.Модуль : 4762 : Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
	: 1 : ОбменДаннымиРИБ.ВыгрузитьИзмененныеОбъекты(Параметры[0],Параметры[1])
ОбщийМодуль.ОбменДаннымиРИБ.Модуль : 129 : Пакет = СформироватьПакетОбмена(НастройкиОбмена, СписокМетаданных);
ОбщийМодуль.ОбменДаннымиРИБ.Модуль : 372 : Возврат РаспределитьПакетПоТипамОбъектов(Пакет, НастройкиОбмена);
ОбщийМодуль.ОбменДаннымиРИБ.Модуль : 1021 : Выборка = ПланыОбмена.ВыбратьИзменения(УзелОбмена, НомерСообщения, Фильтр);

Код ровно такой же, как в пункте 5 "Виновник блокировок". Как выяснилось, регламентное задание по выгрузке данных запускалось несколько раз. Таким образом создавались конкурирующие за одни и те же объекты конфигурации 1С фоновые задания.

 

6.2. Удаление регистрации изменений.

ПланыОбмена.УдалитьРегистрациюИзменений

ОбщийМодуль.ДлительныеОперации.Модуль : 652 : ВыполнитьПроцедуру(ИмяПроцедуры, ПараметрыПроцедуры);
ОбщийМодуль.ДлительныеОперации.Модуль : 661 : ОбщегоНазначения.ВыполнитьМетод(ИмяПроцедуры, ПараметрыПроцедуры);
ОбщийМодуль.ОбщегоНазначения.Модуль : 4762 : Выполнить ИмяМетода + "(" + ПараметрыСтрока + ")";
	: 1 : ОбменДаннымиРИБ.ВыгрузитьИзмененныеОбъекты(Параметры[0],Параметры[1])
ОбщийМодуль.ОбменДаннымиРИБ.Модуль : 155 : ЗафиксироватьЗагрузкуОбъекта(ВыгруженныеОбъекты, УзелОбмена);
ОбщийМодуль.ОбменДаннымиРИБ.Модуль : 1882 : ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, Объект);

Кроме этого выяснилось, что после успешной выгрузки объекта 1С выполнялось удаление ссылки на такой объект из плана обмена, что создавало дополнительную конкуренцию за одни и те же записи таблиц изменений. Объяснением такого поведения системы является то, что выгрузка выполняется при помощи http сервисов 1С, квитирование выгрузки не выполняется - достаточно получить код ответа HTTP 200 от принимающего узла обмена.

Данная жертва является косвенным последствием порождения жертвы из пункта 6.1.

 

6.3. Регистрация новых изменений.

ПланыОбмена.ЗарегистрироватьИзменения

ОбщийМодуль.ОбменИнтернетМагазин.Модуль : 92 : ЗагрузитьЗаказыКлиентов(ДанныеСессии);
ОбщийМодуль.Заказы.Модуль : 1306 : ЗаказКлиента.Записать(РежимЗаписиДокумента.Проведение);
ОбщийМодуль.ОбменДаннымиМагазин.Модуль : 12 :
    ОбменДаннымиСобытия.МеханизмРегистрацииОбъектовПередЗаписьюДокумента(ИмяПланаОбмена, Источник, Отказ, РежимЗаписи, РежимПроведения);
ОбщийМодуль.ОбменДаннымиСобытия.Модуль : 24 :
    ЗарегистрироватьИзменениеОбъекта(ИмяПланаОбмена, Источник, Отказ, ДополнительныеПараметры);
ОбщийМодуль.ОбменДаннымиСобытия.Модуль : 1033 :
    ВыполнитьПравилаРегистрацииОбъектовДляПланаОбмена(МассивУзловРезультат, Объект, ИмяПланаОбмена, ДополнительныеПараметры);
ОбщийМодуль.ОбменДаннымиСобытия.Модуль : 1324 :
    ВыполнитьПравилаРегистрацииОбъектовДляПланаОбменаПопыткаИсключение(МассивУзловРезультат, Объект, ИмяПланаОбмена, ДополнительныеПараметры);
ОбщийМодуль.ОбменДаннымиСобытия.Модуль : 1431 :
    ОпределитьПолучателейПоУсловию(МассивУзловРезультат, ПРО, Объект, ИмяПланаОбмена, ДополнительныеПараметры);
ОбщийМодуль.ОбменДаннымиСобытия.Модуль : 1675 :
    ВыполнитьОбработчикПРОПослеОбработки(ПРО, Отказ, Объект, ОбъектМетаданных, Получатели, Выгрузка);
ОбщийМодуль.ОбменДаннымиСобытия.Модуль : 2531 : Выполнить(ПРО.ПослеОбработки);
	: 31 : ПланыОбмена.ЗарегистрироватьИзменения(Получатели, Партия);

На самом деле жертв из этой категории было гораздо больше. Всех их объединяет вызов механизма регистрации объектов в обмен данными. По результатам выполнения ПРО выполняется регистрация объектов, которые в данный момент участвуют в выгрузке. Таким образом возникает ожидание на блокировке СУБД.

 

7. Заключение.

Таким образом была найдена причина возникновения ожиданий и даже ошибок таймаута на блокировках СУБД при выполнении обменов с Интернет-магазином. Выяснилось, что при загрузке заказов клиентов выполнялись правила регистрации для справочника "ПартииТоваров" и, как следствие, возникал конфликт с текущей выгрузкой совпадающих партий товаров в узлы обмена РИБ.

В результате полученной информации архитектор торговой учётной системы получил конкретные знания о причинах возникающих проблем. Был произведён рефакторинг архитектуры и кода. Проблему "падения" обменов с Интернет-магазином удалось решить в кратчайшие сроки.

Кроме этого попутно была выявлена скрытая проблема параллельного запуска фоновых заданий по выгрузке данных РИБ. Появился повод подумать о реорганизации выполнения правил регистрации в обмен для других объектов системы. Задумались об удалении выгруженных объектов из таблиц изменений, о квитировании и гарантировании ...

планы обмена обмен данными РИБ РИБД анализ блокировок технологический журнал ТЖ SQL Server оптимизация производительности

См. также

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

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

06.06.2024    9259    Evg-Lylyk    61    

44

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

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

13.03.2024    5097    spyke    28    

49

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

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

13.03.2024    7573    vasilev2015    20    

42

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

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

2 стартмани

15.02.2024    12419    241    ZAOSTG    80    

115

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

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

1 стартмани

24.01.2024    5669    glassman    18    

40

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

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

09.01.2024    14010    doom2good    49    

71
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. bugagashenka 203 18.12.20 14:03 Сейчас в теме
Ну, найти блокировки достаточно легко. Делитесь рецептами, в них самая клюква
2. trio-tlt 25.12.20 20:15 Сейчас в теме
Столкнулся с такой же проблемой на базе с довольно интенсивном обмене https://partners.v8.1c.ru/forum/message/1959569?onlyMyThemesInput=true#m_1959569

Возникло несколько вопросов:
1) Можно каким-то образом изменить время ожидания блокировки на уровне SQL сервера?
2) Сейчас наблюдаю, что и 1С сервер и sql сервер по нагрузке на железо не сильно напрягаются, а блокировки вида
ПланыОбмена.ВыбратьИзменения(Получатель.Ссылка, Получатель.НомерОтправленного + 1);
по причине:
Конфликт блокировок при выполнении транзакции:
Microsoft SQL Server Native Client 11.0: Превышено время ожидания запроса на блокировку.
HRESULT=80040E31, SQLSrvr: SQLSTATE=HYT00, state=33, Severity=10, native=1222, line=1
продолжают появляться
В какую сторону можно ещ епосмотреть?
3. zhichkin 1524 28.12.20 11:35 Сейчас в теме
(2) 1) Насколько я знаю, ConnectionTimeout и CommandTimeout это настройки клиента OLE DB провайдера, например, а не настройка на уровне SQL Server сервера.
2) Нужно найти причину блокировки. Точнее почему она удерживается так долго. Кроме причин, описанных в статье, эскалацию блокировок SQL Server до уровня таблицы изменений можно посмотреть.
P.S. Извините, но у меня нет доступа на партнёрский форум =) Может быть ответил бы более предметно, зная что там обсуждается по ссылке =)
6. bugagashenka 203 10.05.21 10:34 Сейчас в теме
1) Можно каким-то образом изменить время ожидания блокиров[/IS-QUOTE]
Зачем на уровне SQL сервера, если время таймаута передается из 1С?
Конфигуратор - Администрирование - Параметры информационной базы - Время ожидания блокировки в секундах.

По дефолту 20 сек, что более, чем достаточно. Если увеличивать, то будут тупо простои вхолостую, уж лучше провести исследование в базе, обнаружить и постараться устранить блокировки, пусть и радикальными методами, в том числе изменением архитектуры.
4. Cyberhawk 135 17.01.21 13:46 Сейчас в теме
Был произведён рефакторинг архитектуры и кода
В этом самая мякотка. Отказались по итогу от проставления номера методом "ВыбратьИзменения" или нет?
5. zhichkin 1524 17.01.21 15:46 Сейчас в теме
(4) Глобально решили отказаться от планов обмена в РИБ. Процесс не простой. Какую систему обмена данными выбрали в качестве замены планов обмена я здесь в этом формате сказать не могу.
Для преодоления сложившейся ситуации в основном точечно была сделана отложенная регистрация изменений, чтобы не тормозить критичные для бизнеса процессы.
От метода "ВыбратьИзменения" не так просто избавиться, особенно средствами 1С. В любом случае это будут костыли.
У меня есть ряд статей по альтернативным планам обмена вариантам. Могу порекомендовать мою последнюю статью на эту тему: DaJet Exchange: обмен данными с 1С (часть 1)
9. alexander-lubich 19 07.07.22 15:42 Сейчас в теме
(4) Вы реализовывали эту "Мякотку"?
делали механизм записи номера сообщения в какую то иную связанную таблицу чтобы сохранить квитирование? отказались от номера сообщения и заодно и квитирования?
10. zhichkin 1524 07.07.22 17:21 Сейчас в теме
(9) Конкретно у нас на регистрах сведений сделали регистрацию изменений: одно изменнение - одна запись в регистре сведений. От номера сообщения и квитирования тоже отказались. Работает уже больше года. Полёт нормальный. Решение, да, не новое.
7. androidT1C 76 04.06.21 17:13 Сейчас в теме
Хорошо написано. Но то, что планы обмена парализуют работу, известно с незапамятных времен.
triviumfan; +1 Ответить
8. alexander-lubich 19 07.07.22 15:29 Сейчас в теме
Добрый день!

интересное решение.

отложенная регистрация объекта на прочие узлы если на один из них объект сейчас зарегистрирован

встречал еще такое решение https://www.youtube.com/watch?v=E6wy_UItkdw

тут каждой внешней базе создается не 1 а 2 узла , сперва регистрация перекладывается с узла 1 на узел 2 короткими транзакциями которые не блокируют. а с узла 2 происходит выгрузка уже пакета данных.


http://forum.infostart.ru/forum15/topic228151/message2846012/#message2846012
11. zhichkin 1524 07.07.22 17:23 Сейчас в теме
(8) Ну тоже в принципе вариант. Если хорошо работает, то почему бы и нет.
Оставьте свое сообщение