17 июля 2025
1C-RarusTechDay 2025
8-я открытая техническая конференция для специалистов 1С
Зарегистрироваться
От экспертов «1С‑Рарус»: Расследование ошибки пессимистической блокировки и устройство СУБД «1С:Сервер взаимодействия»
От экспертов «1С‑Рарус»: Расследование ошибки пессимистической блокировки и устройство СУБД «1С:Сервер взаимодействия»

От экспертов «1С‑Рарус»: Расследование ошибки пессимистической блокировки и устройство СУБД «1С:Сервер взаимодействия»

30.05.2025
134 мин
2007

Оглавление

  1. Вступление
  2. Описание случая
  3. Тестируем разные объекты и проверяем релиз платформы «1С:Предприятие»
  4. Виды объектных блокировок
  5. Оптимистическая блокировка
  6. Пессимистическая блокировка
  7. Гипотезы
  8. Логирование сервера взаимодействия
  9. Продолжение расследования — изучение логов сервера взаимодействия
  10. Устройство сервера взаимодействия на стороне СУБД
  11. Расследование — изучаем таблицы сервера
  12. Про переход с 1С:Диалог на собственный сервер взаимодействия
  13. Исправление ключа
  14. Истинные причины возникновения ситуации
  15. Послесловие

Вступление

Мы продолжаем цикл статей посвященный Системе взаимодействия. Сегодня хотим рассказать про интересный экспертный случай возникший на стыке разных механизмов платформы. И по ходу расследования погрузимся в детали механизма объектных блокировок и лучше узнаем внутреннее устройство Сервера взаимодействия, а точнее его систему логирования и таблицы СУБД.

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

Описание случая

Представим себе обычную ситуацию. Мы начали редактировать какой-то документ — изменили одно из полей, не закрывая его форму и не сохранив изменения, запустили в том же сеансе какую-то длительную обработку или, например, сеанс завис. А документ отредактировать надо. Ждать мы не хотим, поэтому открываем другой сеанс той же базы, тот же документ, пытаемся что-то отредактировать в нём и получаем сообщение «Не удалось начать редактирование»:

Сообщение «Не удалось начать редактирование»

Со спокойной душой нажимаем «Начать редактировать», получаем доступ к форме и вносим изменения, какие хотели:

Сообщение «Не удалось начать редактирование»

Так вот, к нам обратился пользователь, у которого в таком же сценарии при нажатии на кнопку «Начать редактировать» программа стала выдавать странное сообщение «Обсуждение уже существует», без передачи доступа к документу. Помимо этого, на ту же проблему иногда стали жаловаться сотрудники техподдержки, которые получали то же сообщение при попытке «перехватить» редактирование документом у другого пользователя:

Сообщение с ошибкой «Обсуждение уже существует»

На первый взгляд — это какая-то путаница, одно сообщение явно связано с объектной блокировкой, а второе по слову «обсуждение» намекает на систему взаимодействия. В попытке понять связь этих явлений и началось наше расследование.

Тестируем разные объекты и проверяем релиз платформы «1С:Предприятие»

Сначала мы пробовали локализовать проблему. Протестировали некоторые варианты, проблема воспроизводится на разных объектах — элементах справочника, документах и с разными пользователями.

И что интересно, появилась как будто недавно. Первая мысль «значит в базе изменилось что‑то существенное». И действительно, примерно за месяц до указанных событий в базе была обновлена платформа 1С аж на 4 релиза (!), с версии 8.3.17.2306 на версию 8.3.21.1895.

Может ли проблема быть в релизе платформы?

Чтобы проверить эту гипотезу, мы взяли

  • Другую продуктивную базу.
  • На той же НОВОЙ версии платформы 8.3.21.1895.
  • Подключенную к тому же серверу взаимодействия.

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

Сообщение «Не удалось начать редактирование»

Получается обновление ни при чем? И проблема локализована в конкретной базе? Было понятно, что ничего не понятно.

Само сообщение с заголовком «Не удалось начать редактирование» означает наличие пессимистической блокировки. Для начала вспомним, какие вообще виды объектных блокировок бывают, возможно, в теории найдем ключ к разгадке.

Виды объектных блокировок

Заглянем на ИТС в руководство разработчика «Глава 9. Работа с данными».

При работе с объектными данными (справочники, документы, счета и пр.) система «1С:Предприятие» обеспечивает два вида объектных блокировок — пессимистическую и оптимистическую. Они позволяют выполнять целостные изменения объектов при одновременной работе нескольких пользователей.

Источник: its.1c.ru/db/v8325doc#bookmark:dev:TI000000523

Итак, всего видов объектных блокировок два:

  1. оптимистическая,
  2. пессимистическая.

Обе служат в общем-то одной цели: помочь наиболее безболезненным способом разрешить конфликтную ситуацию одновременного редактирования одного объекта — документа, элемента справочника и т. д.

На всякий случай уточним, не стоит путать объектные блокировки с транзакционными блокировками (управляемыми и СУБД) — это разные механизмы.

Оптимистическая блокировка

ИТС: «Оптимистическая блокировка запрещает запись объекта в базу данных, если после считывания объекта он был изменен в базе данных.»

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

Платформа 1С для каждого объекта хранит в СУБД специальное поле «ВерсияДанных». При чтении данных эта версия запоминается на время редактирования:

  1. В форме при событиях:
    1. ПриСозданииНаСервере.
    2. ПриЧтенииНаСервере.
  2. Программно — в момент вызова метода ПолучитьОбъект().

Далее при записи объекта происходит сравнение ранее закэшированной версии с той, что на текущий момент присутствует в базе. В случае, если версии различны, вызывается исключение. Вид сообщения с исключением зависит от источника записи.

  • При записи в форме:

    Вид сообщения с ошибкой

  • При программном вызове метода объекта Записать();

    Вид сообщения с ошибкой

Схематично это выглядит так:

Сравнение версий объекта: при несоответствии данных возникает исключение (вид сообщения зависит от источника записи)

Воспроизведем блокировку и посмотрим наглядно, что происходит.

Для этого в демо-базе УНФ в форме документа расходной накладной поставим точку останова в процедуре «ПриЧтенииНаСервере» и откроем какой-нибудь документ:

Расходная накладная

Выведем в табло поле «ВерсияДанных»:

  • От поля «Объект» — та версия, что мы закэшировали «AAAAAAABgJl=».
  • От дочернего поля «Ссылка» — та версия, что хранится в базе данных «AAAAAAABgJl=».

На текущий момент они равны:

Визуализация процесса в интерфейсе СУБД

Также можем посмотреть, как это выглядит в СУБД.

С помощью обработки «Структура хранения метаданных» находим для документа «Расходная накладная» имя таблицы СУБД — «_Document203»:

«Расходная накладная» имя таблицы СУБД - «_Document203»

Наша демо-база развернута на MS SQL 19, поэтому мы можем зайти в Microsoft SQL Server Management Studio и найти запросом строку с тем же документом «АСФР-000024» в таблице _Document203. Версия данных хранится в поле «_Version»:

Версия данных хранится в поле «_Version»

Видно, что значение отличается от того, что мы получили в отладке. Это связано с особенностью хранения и преобразования типов, об этом подробнее расскажем чуть ниже.

Далее открываем ещё один сеанс под другим пользователем, открываем тот же самый документ и нажимаем кнопку «Записать»:

Повторное открытие документа под другим пользователем и попытка записи

В СУБД видно, что версия уже изменилась:

  • Была 0×0000000000018092.
  • Стала 0×00000000000180A3.

    В СУБД видно, что версия уже изменилась

Теперь вернемся в сеанс первого пользователя, поставим точку останова в процедуре «ПередЗаписью» на клиенте и также пытаемся записать документ:

Тестирование процедуры «ПередЗаписью»: точка останова в клиентском сеансе первого пользователя при попытке записи документа

При этом видим, что:

  • Закэшированная «ВерсияДанных» объекта осталась той же «AAAAAAABgJl=».
  • Версию данных от ссылки через точку получить нельзя, так как мы находимся в клиентском контексте. Поэтому получаем её через специальную функцию, которая возвращает реквизит «ВерсияДанных» от ссылки, выполняя запрос на сервере. И видим, что версия в БД отличается «AAAAAAABgKM=»:

    Результат проверки версий данных

Как закономерный итог — получаем ошибку:

Как закономерный итог — получаем ошибку

Похожим образом блокировка работает и при программном взаимодействии, меняется только вид сообщения.

Тема хранения версии данных в СУБД не относится к текущей статье, но если интересны подробности, предлагаем ознакомиться с небольшой заметкой. Если не интересны — переходите к следующему разделу «Пессимистическая блокировка».

Заметка про хранение версии в СУБД MSSQL и PostgreSQL

В отладке мы видели строку вида «AAAAAAABgYo=», для начала ответим на нижеследующий вопрос.

Какой тип у версии данных?

Мы нашли следующую информацию. Если в конфигруторе в синтакс-помощнике пройти в описание процедуры ЗаблокироватьДанныеДляРедактирования, можно найти упоминание, что ВерсияДанных это строка в формате Base64:

Описание процедуры «ЗаблокироватьДанныеДляРедактирования» в синтакс-помощнике конфигуратора

«Base64 — стандарт кодирования двоичных данных при помощи только 64 символов ASCII. Алфавит кодирования содержит латинские символы A — Z, a — z, цифры 0–9 (всего 62 знака) и 2 дополнительных символа, зависящих от системы реализации. Каждые 3 исходных байта кодируются четырьмя символами (увеличение на 13).»

Источник: ru.wikipedia.org/wiki/Base64

Однако, на стороне MSSQL мы видели значение другого вида «0×000000000001818A».

MSSQL

На первый взгляд можно сказать, что префикс «0x» указывает на то, что это шестнадцатеричное значение. Если заглянем в свойствах колонки _Version — там мы видим тип timestamp:

Свойства колонки

Касательно этого типа в документации Microsoft можно найти интересную информацию:

  • Каждая база данных имеет счетчик, который увеличивается при каждой операции вставки или обновления в таблице, содержащей столбец типа rowversion в базе данных. Этот счетчик типа rowversion используется для работы с базами данных. Происходит отслеживание относительного времени базы данных, а не действительного времени, которое может быть связано с часами. В таблице может быть только один столбец типа rowversion.
  • Тип данных timestamp является синонимом типа данных rowversion.
  • Столбец типа rowversion, который не может принимать значение NULL, семантически эквивалентен столбцу типа binary(8).
  • С помощью столбца rowversion можно легко определить, выполнялась ли инструкция обновления применительно к строке с момента ее последнего считывания. При выполнении инструкции обновления применительно к строке значение rowversion изменяется.

Источник: learn.microsoft.com/ru-ru/sql/t-sql/data-types/rowversion-transact-sql?view=sql-server-ver16

Получается, что версия данных — это некое число, которое хранится в бинарном виде. При этом в случае MSSQL платформа 1С использует механизм изменения версии при записи строки, который заложен в саму СУБД, то есть дополнительно делать этого не требуется.

Попробуем это проверить.

Зайдем в Management Studio и выполним запрос, получив текущую версию нашего документа «0×000000000001818A»:

Зайдем в Management Studio и выполним запрос

Далее обновим строку прямым запросом в СУБД, в обход 1С:Предприятия. Например, повторно установим тот же самый номер (изменять значение не обязательно, главное — выполнение оператора UPDATE):

Далее обновим строку прямым запросом в СУБД, в обход 1С:Предприятия

После чего снова получаем версию и видим, что она изменилась на «0×0000000000018274»:

Результат повторного получения версии данных: новое значение «0x0000000000018274»

Как получить из значения «0×0000000000018274» строку версии «как в 1С»?

Для нашей версии MSSQL Server 2022 мы нашли только способ, используя функции XML, через метод «xs:base64Binary()», который преобразует бинарные данные в строку типа base64:

SELECT  TOP (1000) [_IDRRef]
,[_Version]
,CAST('' AS XML).value('xs:base64Binary(sql:column("[_Version]"))',  'VARCHAR(MAX)') AS Base64String
,[_Date_Time]
,[_Number]     
  FROM [base_name].[dbo].[_Document203]
  where _Number = 'АСФР-000024';

Реализация конвертации бинарных данных в Base64 для MSSQL Server 2022 с использованием XML-функции xs:base64Binary()

Как именно это преобразование делает платформа 1С не известно, по крайней мере, судя по тексту запроса в событии DBMSSQL из технологического журнала, колонка _Version выбирается, как есть:

Преобразование версии данных в 1С

PostgreSQL

Ради интереса мы развернули ту же самую демо-базу на PostgreSQL, чтобы узнать как обстоят дела с версией данных на этой СУБД.

Заглянули в pgadmin и выполнили запрос к тому же самому документу. Видим, что здесь также есть колонка _version, но в отличии от MSSQL она имеет обычный числовой тип «integer»:

Результат миграции демо-базы на PostgreSQL

На всякий случай проверим со стороны кода 1С — при открытии формы документа версия получается в том же формате base64:

Проверка на стороне 1С

Запишем документ, после этого видим, что версия в таблице СУБД увеличилась на единицу:

Результат записи документа: версия в СУБД увеличилась на 1

И судя по всему в случае PostgreSQL инкрементирование версии данных лежит полностью на плечах платформы. Проведем аналогичный эксперимент с прямым запросом на обновление.

Как в MSSQL перезапишем номер, тем самым обновив строку:

Эксперимент с прямым SQL-запросом

Снова выберем версию, как видно она не поменялась:

Результат проверки версии после прямого SQL-обновления

Так что вероятно за этим следит платформа 1С.

По поводу преобразования типов.

Если заглянуть в событие DBPOSTGRS технологического журнала, в тексте запроса можно увидеть, что версия выбирается через некий метод BINROWVER():

Текст запроса

В документации PostgreSQL такого метода мы, увы, не нашли. Тогда решили поискать в разделе Functions нашей базы. И действительно — там есть такая функция:

Результат поиска в PostgreSQL

Судя по всему функция нужна, как некая «прослойка», чтобы получить из типа integer бинарный тип, аналогично тому, как это было в MSSQL. И похоже, что эта функция добавлена самой платформой 1С.

При желании мы можем её использовать в своих запросах и получить строку в формате base64 с помощью метода encode:

SELECT _idrref, _version, _date_time,  _number,
BINROWVER(_version),  encode(BINROWVER(_version),'base64')
FROM public._document203
where _number = 'АСФР-000024';

Текст запроса

Пессимистическая блокировка

ИТС: «Механизм пессимистической блокировки объектов базы данных предназначен для того, чтобы запретить изменение данных объекта другими сеансами или данным сеансом до тех пор, пока блокировка не будет снята (автоматически или с помощью методов встроенного языка).

В основном механизм пессимистической блокировки используется системой „1С:Предприятие“ для блокировки объектов, редактируемых в форме. В то же время разработчик имеет возможность задействовать этот механизм, используя средства встроенного языка.»

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

Например, один пользователь открыл расходную накладную и изменил реквизит «Склад», далее другой пользователь (или тот же пользователь, но в другом сеансе) открыл тот же самый документ и при попытке что-либо изменить получает предупреждение системы:

Расходная накладнуя

Вид сообщения и его функционал зависят от вида базы (файловая или клиент-серверная) и наличия подключения к системе взаимодействия:

  • В случае, если база не подключена к системе взаимодействия:
    • В файловом варианте — это будет чисто информационное сообщение о том, кто и когда заблокировал объект.
    • В клиент-серверном варианте — у нас появляется возможность начать редактировать объект несмотря на пессимистическую блокировку первого пользователя. При этом, увы, его изменения будут утеряны.
  • Если же база подключена к системе взаимодействия, то и в файловом, и в клиент-серверном вариантах в сообщении также есть возможность начать редактирование.

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

    Вид сообщения и его функционал зависят от вида базы (файловая или клиент-серверная) и наличия подключения к системе взаимодействия

Примечания:

  • Блокировка в форме накладывается на объект, являющийся основным реквизитом формы, это нужно учитывать при разработке нестандартных форм.
  • Блокировка накладывается в момент изменения реквизитов и живет до момента закрытия формы, либо до «перехвата» редактирования по кнопке «Начать редактировать».

Судя по всему именно этот вариант блокировки и срабатывал у пользователя в нашем расследовании, но что-то пошло не по плану.

Занимательный факт — пессимистические блокировки можно увидеть в консоли кластера. К теме статьи это не относится, но если интересно предлагаем ознакомиться с заметкой. Если не интересно — переходите к следующему разделу «Гипотезы».

Заметка про блокировки в консоли кластера

Если открыть консоль кластера, там перейдя в строку конкретной базы данных можно увидеть раздел «Блокировки».

У нас открыт только один сеанс тонкого клиента, поэтому сейчас здесь видим только некие блокировки ИБ и БД. Подробнее про них можно почитать на ИТС в разделе «Клиент-серверный вариант. Руководство администратора» (its.1c.ru/db/v8325doc#bookmark:cs:TI000000179):

Раздел «Блокировка»

Далее откроем документ расходной накладной и изменим реквизит «склад»:

Модификация документа «Расходная накладная»: изменение реквизита «Склад»

В этот момент в консоли кластера видим, что появилась ещё одна строка с типом блокировки «Объект БД»:

Блокировка типа «Объект БД» в логе кластера

Увы, в данном списке мы видим только факт блокировки, но какой конкретно объект заблокирован не понятно. Однако, для каждого такого заблокированного объекта в этом списке будет отдельная строка.

Также возможно упредим вопрос — управляемые блокировки и тем паче блокировки СУБД в этом списке похоже увидеть нельзя. Мы провели небольшой эксперимент, запустили проведение расходной накладной, установив точку останова сразу после метода «Заблокировать()» одной из управляемых блокировок. В списке блокировок в консоли в разрезе нашего сеанса тонкого клиента ничего нового не появилось:

Отсутствие новых блокировок в консоли кластера для текущего сеанса тонкого клиента

Гипотезы

Теперь, когда мы подкрепились теоретическим основанием, вернемся к нашему случаю. Ясно, что у нас срабатывает пессимистическая блокировка, которая, как видим по описанию на ИТС, может быть тесно связана с системой взаимодействия.

На это также недвусмысленно намекает текст сообщения «Обсуждение уже существует»:

Сообщение «Обсуждение уже существует»

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

Логирование сервера взаимодействия

В первой части цикла статей Иконка «Стрелка» мы описывали порядок запуска сервера взаимодействия и вскользь упоминали его логи. Для начала расскажем про них чуть поподробнее.

Где лежат логи?

Напомним, что основу сервера составляют три компоненты — cs, elasticsearch и hazelcast, для каждой из них мы создавали инстанс и службу:

  1. cs — головная служба сервера взаимодействия.
  2. hazelcast — распределенное хранилище в памяти, в котором обслуживаются сессии/очереди/события.
  3. elasticsearch — служба поиска, обеспечивает полнотекстовый поиск и подбор пользователей.

Инстансы мы инициализировали специальной командой утилиты ring, в этой команде в параметре dir обязательно задается путь к рабочей папке:

ring <Компонент> instance create --name <ИмяСлужбы> --dir <РабочаяПапка> --owner <ПользовательСлужбы>

Пример для нашего демо-стенда из прошлой статьи:

ring cs instance create --name cs-example --dir /var/cs/cs-workdir --owner cs_user

Каждый инстанс ведет свой лог внутри рабочей папки в подпапке «logs»:

<РабочаяПапка>\logs

Чтобы узнать какие инстансы развернуты на сервере и найти путь к рабочей папке, можно воспользоваться командой:

ring <Компонент> instance list

где Компонент принимает значение cs / hazelcast / elasticsearch.

Например:

ring cs instance list

В ответном сообщении будет приведен список экземпляров и в поле «Расположение» указана рабочая папка:

Список экземпляров

Логи инстанса cs

Следующая информация постигалась в основном эмпирическим путём, так как, к сожалению, инструкций по настройке или анализу логов мы не нашли, к тому же сколько-нибудь глубокими знаниями о java-приложениях мы не обладали.

Для начала оценим содержимое папки logs. Для пущего интереса зайдем не в наш демо-стенд, который был развернут в прошлый раз (там скорее всего мало логов), а окинем взглядом логи нашего продуктивного сервера:

Логи инстанса cs

Что ж, количество и разнообразие файлов здесь весомое. Попробуем разобраться что они из себя представляют.

launcher.log

Посмотрим на структуру файла:

Структура файла

Каждое событие лога начинается с даты/времени вплоть до миллисекунд, также можно обратить внимание на поля:

  • level — по нему можно определить критичность события, например есть значения INFO и ERROR.
  • message — собственно текст события.

Т. к. логи имеют текстовый формат, как и логи ТЖ, следовательно можно применить аналогичные методы анализа. Выполним bash-команду, которая отсеет все поля, кроме поля message:

cat launcher.log |sed -r 's/.*message=//g'

Судя по содержимому в этот файл записывается последовательность действий, которая совершается при запуске и завершении службы инстанса:

Лог последовательности действий при старте и остановке службы инстанса

По опыту наших расследований — из полезного здесь в событиях уровня ERROR можно найти причину, по которой служба может не запускаться. Например, в прошлой статье мы упоминали ошибку запуска с переменной JAVA_HOME:

Анализ событий уровня ERROR в логах: выявление причин сбоев запуска службы (пример: некорректная переменная JAVA_HOME)

server.log

По формату похож на launcher.log, здесь также есть дата/время и поля level и message:

Логи инстанса cs

Также по наблюдениям здесь могут попадаться многострочные события, это нужно учитывать при анализе bash-командами:

Особенность логов: многострочные события требуют специальной обработки при анализе bash-скриптами

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

К примеру, в одном из прошлых расследований при попытке подключения к серверу взаимодействия базы с несовместимой версией платформы именно в этом файле может попадаться сообщение вида:

Сообщение в записи лога

Другие файлы

Также в папке логов есть ещё три файла:

  1. websocket,
  2. syserr,
  3. sysout.

Однако, и на продуктивном сервере взаимодействия, и на демо-стенде полезных предупреждений или ошибок мы в них не видели. Файл websocket всегда был пуст, syserr наполнен некими предупреждениями о внутренних операциях, а в sysout единственное мы ловили сообщение о переменной среды JAVA_HOME, но кроме самого сообщения ни даты ни источника ошибки в нём нет:

Результаты мониторинга логов

Конфигурационный файл

Для управления сбором логов можно использовать файл logging.yml в директории config внутри рабочей папки службы сервера:

Конфигурационный файл

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

Конфигурационный файл

В обработчиках handlers указывается ещё ряд настроек, например, имя сохраняемого файла и некие ограничения файлов:

  • limit — задает максимальный размер в байтах, при превышении которого будет создаваться новый файл.
  • count — задает максимальное количество сохраняемых файлов, судя по всему более старые будут удаляться.

На примере файлов server видно, что эти условия соблюдаются — файлов действительно 10 шт., и их размеры 9766КБ, что чуть больше 10 000 000 Байт:

Конфигурационный файл

Правда, для файлов websocket логика с условием по размеру не прослеживается, так что, вероятно, правила чуть сложнее.

По опыту работы с технологическим журналом можно было бы подумать, что процесс сбора логов полностью зависит от настроек файла logging.yml, аналогично тому, как это работает с logcfg.xml. Но, как мы упоминали, в папке логов кроме server и websocket были ещё файлы launcher, sysout, syserr, однако в конфигурационном файле секций с ними нет.

Ради эксперимента, на нашем демо-стенде мы удалили файл logging.yml, удалили все текущие файлы логов и перезапустили сервер. Заглянем в папку логов:

Папка логов

  • Файл websocket не восстановился.
  • Файл server восстановился, но предполагаем, что теперь часть событий в нём не будет логироваться и, вероятно, теперь не будет соблюдаться ограничение на количество и размер.

Судя по всему даже без файла logging.yml некий минимальный уровень логирования предусмотрен в сервере по умолчанию.

Уровни событий

Помимо прочего в файле logging.yml можно заметить настройки уровня событий — level. Мы попробовали разобраться что это и как его использовать.

Сначала предположили, что варианты уровней схожи с аналогичным параметром в технологическом журнале.

Уровень важности события (для всех событий). Может принимать значения:

  • TRACE — самый подробный уровень событий (трассировка).
  • DEBUG — уровень записи событий, содержащих информацию, полезную при расследовании проблем в работе системы.
  • INFO — уровень информационных событий.
  • WARNING — уровень событий, представляющих собой предупреждения о работе системы.
  • ERROR — уровень событий, записываемых при возникновении ошибок.

Источник: its.1c.ru/db/v8325doc#bookmark:adm:TI000000841

Похоже, но не совсем то.

Далее мы проанализировали некоторые конфигурационные файлы сервера взаимодействия и в итоге вышли на файл launcher.yml, который ранее видели в логах. В нём мы подробно не разбирались, но в строке с подключаемыми модулями можно найти строку log4j:

Конфигурационный файл

Согласно страничке ru.wikipedia.org/wiki/Log4j:

Log4j — библиотека журналирования (логирования) Java-программ, часть общего проекта «Apache Logging Project».

Там же можно увидеть перечень уровней логирования:

Перечень уровней логирования

Этот список уже больше похож на правду.

Попробуем поменять уровни в файле logging.yml и посмотрим, какие события мы сможем поймать. Возможно, это пригодится в будущем.

Повышение уровней логирования server.log

Сначала в секции loggers для элемента с именем ROOT повысим уровень логирования с WARN до DEBUG:

Конфигурационный файл

Чтобы настройки вступили в силу перезапустили службу сервера командой:

ring cs --instance cs-example service restart

С уровнем DEBUG в логе начали появляться события с текстом SQL-запросов. Например, вот обращение к таблице context_conversation (связи идентификаторов обсуждений и идентификаторов объектов контекста):

Конфигурационный файл

Однако, значений параметров этого запроса поблизости мы не нашли, так что кажется такой вариант лога малополезен.

Поэтому переключимся в самый подробный режим логирования TRACE:

Режим логирования TRACE

Перезапускаем службу и создадим обсуждение с темой «Тестовое 3!»:

Создание обсуждения с темой «Тестовое 3!»

Теперь в режиме TRACE в логе можно увидеть запросы со всеми параметрами в соседних событиях:

Логирование в режиме TRACE

Это можно использовать в расследованиях, как некий аналог событий DBMSSQL/DBPOSTGRS из лога ТЖ, что нам ещё пригодится в будущем.

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

Логирование в режиме TRACE

Повышение уровней логирования websocket.log

Выше мы писали, что при стандартных настройках в файлах websocket.log обычно ничего нет. А что если в секции loggers для элементов с обработчиком websocket-file установить уровень TRACE:

Настройка конфигурации логирования

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

Создание обсуждения

А вот похоже и причина — у настройки handler обработчика есть своя настройка уровня события = WARN, видимо, она отсекала подробные логи. Поменяем её тоже:

Настройки handler обработчика

Пробуем создать обсуждение и сообщение:

Создание обсуждения и отправка сообщения

И находим в логах websocket.log записи с информацией о методах, по которым в принципе можно понять, что происходит. А также о передаваемых свойствах объектов.

Создание обсуждения:

Создание обсуждения

Создание сообщения:

Создание сообщения

Полагаем, эту информацию уже можно использовать в каких-либо расследованиях.

Логи инстансов elasticsearch/hazelcast

У обеих служб elasticsearch и hazelcast в логах есть файлы launcher.log, в которых мы ранее уже находили полезную информацию в случае если по какой-то причине служба не запускается:

Логи инстансов elasticsearch/hazelcast

Логи инстансов elasticsearch/hazelcast

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

Продолжение расследования — изучение логов сервера взаимодействия

Вернемся к нашему расследованию.

Что именно искать в логах сервера взаимодействия мы на тот момент не знали, поэтому идея была простой — воспроизвести ошибку (благо в этом проблемы не было) и посмотреть последние события в самом свежем измененном файле лога.

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

Повторяем ситуацию с пессимистической блокировкой, ловим заветное сообщение

Для верности повторяем ситуацию несколько раз.

Далее идем в папку логов службы cs и сортируем файлы по дате изменения, самым свежим оказался файл server.log:

Папка логов

Следуем в самый конец файла и находим сообщения следующего содержания «Conversation with key 9A5D8A84-35F4-4319-B45B-284089201110 already exists»:

Обнаружение ошибки в лог-файле

К слову, в нашем случае парсинг не понадобился, но на всякий случай приведем bash-скрипт, которым при необходимости можно проанализировать события уровня ERROR:

cat server.log.* | awk -vORS= '{if(match($0, "^[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]-[0-9][0-9]\:[0-9][0-9]\:[0-9][0-9]\.[0-9]+\-")) print "\n"$0; else print $0"<line>";}' | grep 'level=ERROR' | sed -e "s/\xef\xbb\xbf/ /g" | head -n3| sed -e "s/<line>/\n/g"

Данный скрипт считывает все файлы server.log, приводит события к однострочному виду, отсеивает по строке «level=ERROR» и берет первые 3 события. Результат будет выглядеть подобным образом:

Обнаружение ошибки в лог-файле

Conversation with key already exists

Итак, мы нашли сообщение «Conversation with key 9A5D8A84-35F4-4319-B45B-284089201110 already exists». Строго по тексту оно соответствует нашей ошибке «Обсуждение уже существует», но что за ключ?

Key или Ключ — это строковое поле объекта типа «Обсуждение» в системе взаимодействия. Описание из синтакс-помощника:

Обсуждение системы взаимодействия

Также обратимся к справке its.1c.ru/db/v8325doc#bookmark:dev:TI000001960:

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

Насколько нам было известно, в исследуемой базе на тот момент механизм системы взаимодействия не был задействован в доработках «для коммуникации между сервером и клиентом», следовательно, никаких специальных обсуждений с заданным ключом не должно было быть. Создается впечатление, что это некое служебное обсуждение, которое платформа использует в связке с сообщением о том, что объект уже редактируется.

Странное обсуждение

Далее, чтобы подтвердить или опровергнуть догадку о платформенном обсуждении мы хотели взглянуть на его сообщения.

Для этого воспользовались методами менеджера СистемаВзаимодействия:

  • ПолучитьОбсуждение,
  • ПолучитьСообщения.

Обсуждение = СистемаВзаимодействия.ПолучитьОбсуждение("9A5D8A84-35F4-4319-B45B-284089201110");
Сообщить(Обсуждение = Неопределено);
Отбор = Новый ОтборСообщенийСистемыВзаимодействия;
Отбор.Обсуждение = Обсуждение.Идентификатор;
МассивСообщений = СистемаВзаимодействия.ПолучитьСообщения(Отбор);
МассивТекстСообщений = Новый Массив;
Для Каждого Элемент Из МассивСообщений Цикл
            МассивТекстСообщений.Добавить(Строка(Элемент.Текст) + " - " + Элемент.Дата);
КонецЦикла;
Сообщить(СтрСоединить(МассивТекстСообщений, ";"));

Ответ выглядел так:

Сообщение обсуждения

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

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

Устройство сервера взаимодействия на стороне СУБД

Прежде, чем продолжить расследование, расскажем немного про внутреннее устройство СУБД сервера взаимодействия. Нижеследующая информация касается версии сервера 26.0.54.

Какой-либо методической информации по структуре СУБД мы не нашли, так что содержимое данного раздела основано целиком на наших наблюдениях.

Общее описание

В первой части цикла статей в разделе «настройка JDBC-драйвера» Иконка «Стрелка» мы упоминали, что хранение информации сервера взаимодействия разделено условно на две части, назовем их «Основная база» и «База хранения данных абонентов». Это могут быть разные базы данных, а может быть и одна база для простых случаев. В любом варианте таблицы этих двух частей будут разведены по разным схемам:

Кластер PostgreSQL

Также важно упомянуть, что эти части в сервере взаимодействия настраиваются по-разному.

Основная база

Что хранит:

  • Административные настройки — соответствия абонентов, приложений, пользователей.
  • Конфигурации дополнительных систем — информацию о подключенных медиасерверах, файловых серверах, а также адресацию хранения данных абонентов.

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

ring cs --instance cs-example jdbc pools --name common set-params --url jdbc:postgresql://localhost:5432/cs_db?currentSchema=public

Список таблиц основной базы такой:

Список таблиц основной базы

База хранения данных абонентов

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

Настройка отличается от основной базы, она выполняется не через утилиту ring, а с помощью специального http-запроса к интерфейсу обслуживания, подробнее также читайте в прошлой статье в разделе «Инициализация базы данных». Пример такой команды:

curl -Sf -X POST -H "Content-Type: application/json" -d "{ \"url\" : \"jdbc:postgresql://localhost:5432/cs_db\", \"username\" : \"cs\", \"password\" : \"cs_password\", \"enabled\" : true }" -u admin:admin  http://localhost:8087/admin/bucket_server

Причем схему этой базы мы при настройке сервера нигде не указываем, она имеет наименование вида «vb_subscriber_№», где № определяется по некой внутренней логике сервера и начинается с «13». По наблюдениям для каждой настроенной базы в момент подключения приложения с новым абонентом создается своя схема. В нашем случае было подключено две базы, в одной создалась схема vb_subscriber_13_13, во второй vb_subscriber_14_14.

Список таблиц Базы хранения данных абонентов, следующий:

Список таблиц Базы хранения данных абонентов

Таблиц в сервере множество, цели исследовать их все в данной статье у нас не было. Но мы выделили несколько важных на наш взгляд «контуров» — то есть наборов связанных таблиц. Расскажем о них поподробнее.

Основная база — контур абоненты-приложения-пользователи

Контур представлен тремя таблицами:

  • subscriber — список абонентов, когда-либо подключавшихся к серверу.
  • application — здесь находится список подключений к базам. В рамках одного абонента может быть подключено несколько баз, в том числе с синхронизацией пользователей между ними.
  • user — в этой таблице хранятся записи о пользователях системы взаимодействия. Как правило, запись создается автоматически при первом входе пользователя в 1С, но при желании можно сделать это либо в обработке «Управление системой взаимодействия», либо программно, через объектную модель системы взаимодействия.

Таблица subscriber

Таблица subscriber

Пояснения по некоторым полям:

  • id — внутренний идентификатор, его можно использовать для связи с другими таблицами СУБД с полем subscriber_id.
  • credential — идентификатор абонента, тот самый, что был указан в форме обработки регистрации базы на сервере взаимодействия.
    Со стороны 1С приложения его можно изменить из встроенной обработки «Управление системой взаимодействия».
  • bucket_id — некий индекс «virtual bucket» — используется для адресации базы хранения данных абонента, сама адресация хранится в таблице vb_mapping.
  • created_at/updated_at — даты создания и изменения свойств абонента.
  • adminisration_key_hash — предположительно хранит хэш ключа администратора.
    Подробнее о том, что это такое можно почитать на ИТС в Руководстве разработчика — Работа со списком администраторов абонента (its.1c.ru/db/v8325doc#bookmark:dev:TI000002769).
  • enabled, is_deleted, on_maintenance — некие управляющие флаги, на что конкретно они должны влиять не понятно.
    • Для теста мы выставили для одного абонента все три флага в иное положение, в подключенном приложении базовый функционал (создание обсуждений, сообщений, звонки) остался доступен.
    • Но как бы то ни было, какого-либо управления ими со стороны 1С:Предприятия — нет. Ни из обработки «Управление системой взаимодействия», ни объектными методами.
  • video_enabled — по логике флаг коррелирует с объектным методом ПоддерживаютсяВидеоконференции(), однако, в наших тестах изменение флага ни на этот метод, ни на возможность звонков не повлияло. Истинное назначение пока от нас ускользает, возможно поймем в будущих разборах.

Таблица application

Таблица application

Пояснения по некоторым полям:

  • id — внутренний идентификатор, его можно использовать для связи с другими таблицами СУБД с полем application_id.
  • Следующие поля согласно инструкции на ИТС (its.1c.ru/db/v8325doc#bookmark:dev:TI000001902) называются «данными регистрации приложения»:
    • client_id — идентификатор приложения, генерируется автоматически при регистрации базы на сервере взаимодействия. Можно получить объектным методом ИдентификаторТекущегоПриложения() менеджера системы взаимодействия.
    • title — наименование приложения, что было указано в форме обработки регистрации базы на сервере взаимодействия.
    • ecs_private_key, ecs_public_key, app_public_key — предположительно криптографические ключи, о которых упоминается на ИТС.
  • subscriber_id — внутренний идентификатор абонента, по нему можно связать с таблицей subscriber по полю id.
  • subscriber_bucket_id — индекс «virtual backet», по-видимому дублирует значения поля bucket_id таблицы subscriber.
  • is_deleted — поле используется для некой установки пометки удаления текущей строки приложения:
    • Причем судя по экспериментам оно не взводится в «истина» при отмене регистрации, а только после повторной регистрации того же приложения. В этом случае в таблицу добавится новая строка с теми же полями id/client_id.
    • Если же после отмены регистрации в базе 1С удалить текущие данные регистрации с помощью метода
      УстановитьДанныеРегистрацииИнформационнойБазы(Неопределено) и заново зарегистрировать приложение, то в таблице будет создана строка уже с новыми идентификаторами:

      Создание строки с новым идентификатором

Таблица user

Таблица user

Пояснения по некоторым полям:

  • id — идентификатор пользователя системы взаимодействия, его можно получить объектным методом ПолучитьИдентификаторПользователя() менеджера системы взаимодействия, а также использовать для связи с другими таблицами.
  • application_id — идентификатор приложения, можно использовать для отбора по текущей базе, или для связи с таблицей application по полю id.
  • Следующие поля являются свойствами пользователей, которые соответствуют колонкам в разделе «Пользователи» обработки «Управление системой взаимодействия», многие из них можно тут изменить:

    Раздел «Пользователи»

    • name/full_name — Имя и полное имя пользователя. Похоже эти поля синхронизируются с соответствующими полями пользователя ИБ. Изменить их в обработке нельзя.
    • nickname — псевдоним пользователя, который можно задать в обработке.
      Согласно справке синтакс-помощника:

      Содержит псевдоним пользователя, с которым пользователь отображается во внешних системах (например, в интеграции типа «WebChat»). В интерфейсе клиентских приложений «1С:Предприятия» данное имя не отображается.
    • phone, email, picture — номер телефона, адрес электронной почты и картинка, которые отображаются в свойствах пользователя в интерфейсе системы взаимодействия:

      Интерфейс системы взаимодействия

    • blocked — флаг блокировки пользователя. Заблокировать и разблокировать пользователя можно в обработке. У заблокированного пользователя отключится возможность отправлять сообщения, а при повторном входе в 1С список обсуждений будет пуст, при некоторых интерактивных действиях в окне системы взаимодействия будет выдано предупреждение:

      Предупреждение «Пользователь не авторизован»

Связь таблиц

Связь таблиц

Для примера представим прикладную задачку. Хотим получить список пользователей с информацией по абоненту по текущему приложению.

Для этого сначала получим идентификатор приложения: вызовем в выбранной базе метод ИдентификаторТекущегоПриложения() объекта СистемаВзаимодействия.

Вызов метода ИдентификаторТекущегоПриложения() через объект СистемаВзаимодействия в выбранной базе

Он возвращает значение типа «ИдентификаторПриложенияСистемыВзаимодействия», если привести его к строке, получим идентификатор «665eb34e-1756-4b2a-bb2c-161874c6de25», который соответствует колонке client_id в таблице applications.

Далее, например, в pgadmin можем выполнить запрос, который свяжет данные абонентов, приложений и пользователей и выведет информацию по текущему идентификатору приложения:

SELECT 
subs.credential,
app.title,
users.name,
users.full_name
FROM public.user as users
inner join public.application as app
inner join public.subscriber as subs
on app.subscriber_id = subs.id
and app.client_id = '665eb34e-1756-4b2a-bb2c-161874c6de25'
and not subs.is_deleted and subs.enabled
on users.application_id = app.id
and not app.is_deleted
where not users.is_deleted and not users.blocked
order by users.name
Limit 100;

SQL-запрос

Основная база — контур адресации «Виртуальные корзины»

Как мы уже писали выше, сервер взаимодействия позволяет указать несколько баз для хранения данных абонентов — обсуждений, сообщений и т. д. Параметры доступа к этим базам хранятся в таблице bucket_server основной схемы:

Таблица bucket_server

Эти настройки можно добавить, получить или изменить извне через http запрос, например из консоли с помощью утилиты curl. Подробнее об этом можно почитать в прошлой статье Иконка «Стрелка».

Схема адресации по нашим наблюдениям выглядит примерно так:

  1. При регистрации нового приложения с новым абонентом ему присваивается индекс «virtual bucket» — bucket_id.
  2. По некоторой логике происходит выбор базы из доступных в таблице bucket_server и в ней создается схема с именем vb_subscriber_# (если она ещё не существует).
  3. В итоге в таблицу vb_mapping прописывается соответствие:
    1. key — индекс корзины bucket_id.
    2. db_schema — имя схемы «vb_subscriber_#».
    3. db_connection — параметры подключения к базе.

Таблица vb_mapping

Схема связей с учетом таблиц адресации хранения:

Схема связей с учетом таблиц адресации хранения

База хранения данных абонентов — контур обсуждения-сообщения-контексты

Контур представлен следующими таблицами:

  • conversation — в этой таблице размещена информация обо всех обсуждениях — личных, контекстных и остальных.
  • conversation_message — данные по всем сообщениям.
  • context_in_application — отдельная таблица только для контекстных обсуждений, в которой есть навигационная ссылка на объект базы — тот самый «контекст».
  • context_conversation — таблица, позволяющая связать таблицы conversation и context_in_application.

Таблица conversation

Таблица conversation

Пояснения по некоторым полям:

  • id — внутренний идентификатор обсуждения, его можно использовать для связи с другими таблицами СУБД с полем conversation_id. Также его можно получить объектным методом, из свойства «Идентификатор» объекта «ОбсуждениеСистемыВзаимодействия».
  • application_id/subscriber_id — идентификаторы приложения и абонента соответственно.
  • Ряд полей, соответствующие свойствам объекта обсуждения:
    • title — «Заголовок»;
    • key — «Ключ»;
    • visible — «Отображаемое»;
    • face_to_face — по сути определяет является ли обсуждение «Личным», но такого флага в свойствах нет, так что видимо это инвертированное свойство «Групповое».
  • contextual — флаг, означающий является ли обсуждение контекстным.
  • created_at/updated_at — даты создания и изменения свойств обсуждения.
  • last_message_id/last_message_at — идентификатор и дата последнего сообщения.

Таблица conversation_message

Таблица conversation_message

Пояснения по некоторым полям:

  • id — внутренний идентификатор сообщения. Соответствует свойству «Идентификатор» объекта «СообщениеСистемыВзаимодействия».
  • application_id/subscriber_id — идентификаторы приложения и абонента соответственно.
  • author_id/recipients — идентификаторы автора и получателей соответственно.
  • created_at/updated_at — даты создания и изменения свойств обсуждения.
  • text — текст сообщения.

Связь таблиц

Также обсуждения можно связать через промежуточную таблицу context_conversation с таблицей context_in_application, в которой в поле object_key находится навигационная ссылка на объект 1С:

Связь таблиц

Для примера покажем запрос, которым можно выбрать для конкретного приложения — данные обсуждений, его сообщений и связь с объектом прикладной базы. В условии отбираем по полю application_id таблицы conversation, используем идентификатор приложения из предыдущего примера ‘665eb34e-1756-4b2a-bb2c-161874c6de25’:

SELECT 
cnv.title "Тема обсуждения",
cnv.face_to_face "Личное",
cnv.contextual "Контекстное",
cntx_app.object_key "Навигационная ссылка",
msg.text "Текст сообщения"
FROM vb_subscriber_13_13.conversation cnv
inner join vb_subscriber_13_13.conversation_message msg
on cnv.id = msg.conversation_id
and cnv.application_id = '665eb34e-1756-4b2a-bb2c-161874c6de25'
left join vb_subscriber_13_13.context_conversation cntx_cnv
inner join vb_subscriber_13_13.context_in_application cntx_app
on cntx_cnv.context_id = cntx_app.context_id
on cnv.id = cntx_cnv.conversation_id
order by
cnv.face_to_face,
cnv.contextual,
msg.created_at
Limit 100;

SQL запрос

Расследование — изучаем таблицы сервера

Итак, теперь «мы знаем кунг-фу», как сказал бы Нео. Но нет, мы лишь немного начали ориентироваться в базе данных сервера взаимодействия.

Мы при расследовании пришли к базе данных за ответами. А чтобы задать вопросы мы зашли в pgadmin. На момент расследования мы ещё кунг-фу не владели, поэтому для начала просто хотели найти это злосчастное обсуждение по ключу. Для этого выполнили запрос по всем полям к таблице обсуждений conversation с отбором по полю key равному найденному ключу из лога сервера:

SELECT *
FROM vb_subscriber_13_13.conversation
where key = '9A5D8A84-35F4-4319-B45B-284089201110'
limit 100

Как ни странно — мы нашли и даже не одно:

Результат запроса SQL

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

SELECT 
t2.title,
t1.key,
t1.title,
t1.is_deleted,
t1.visible,
t1.face_to_face,
t1.contextual,
t1.accessible_to_everyone
FROM vb_subscriber_13_13.conversation as t1
left join public.application as t2
on t1.application_id = t2.id    
and not t2.is_deleted
where key = '9A5D8A84-35F4-4319-B45B-284089201110' limit 100

В результате увидели, что именно в базе, в которой случилась проблема, поля title и accessible_to_everyone у обсуждения отличаются от обсуждений остальных баз, в которых подобной ошибки не было:

Результат запроса SQL

И в тот самый момент мы вспомнили, что только эта база из четырех примерно за год до нашего случая, была переведена с подключения к сервису «1С:Диалог» на собственный сервер взаимодействия, остальные базы изначально были подключены к нему.

Про переход с 1С:Диалог на собственный сервер взаимодействия

Текущая база несколько лет жила в сервисе «1С:Диалог». В определенный момент было принято решение по переезду на свой сервер взаимодействия. Можно было просто переключить базу между серверами — это заняло бы пару минут. Но терять накопленные за время эксплуатации обсуждения не хотелось, да и в любом случае пришлось бы заново регистрировать всех пользователей в системе взаимодействия. Поэтому возникла задача по перегрузке данных — информации о пользователях, обсуждениях и сообщений.

На сегодня подобная функциональность уже встроена в платформу и сервер взаимодействия. Об этом можно почитать на ИТС (its.1c.ru/db/cs26doc#bookmark:cs:TI000000068).

Но она появилась в платформе с версии 8.3.22, а в момент переезда наша база была на версии 8.3.17.2306 и пришлось обойтись собственными силами.

Процесс перехода можно описать следующими этапами:

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

Все операции по получению данных и созданию объектов производились с использованием методов менеджера системы взаимодействия «СистемаВзаимодействия».

С помощью метода ПолучитьПользователей() получен массив пользователей Системы взаимодействия и выгружен в файл:

Получение массива пользователей Системы взаимодействия

Используя метод ПолучитьОбсуждения() мы получили массив обсуждений Системы взаимодействия:

Массив обсуждений, полученный через СистемыВзаимодействия

Примечание: для выгрузки контекстных и неконтекстных обсуждений требуется установить свойство КонтекстноеОбсуждение (Истина или Ложь) объекта ОтборОбсужденийСистемыВзаимодействия.
Внимание
ВНИМАНИЕ: Для получения обсуждений, в которых текущий пользователь осуществляющий выгрузку не является участником, необходимо использовать привилегированный режим.

Затем внутри цикла перебора обсуждений для каждого обсуждения получили массив сообщений Системы взаимодействия, используя метод ПолучитьСообщения() и инициализированный ранее объект ОтборСообщенийСистемыВзаимодействия. Полученные сообщения выгрузили в файл:

Экспорт сообщений из обсуждений

Примечание: В объектах ОтборОбсужденийСистемыВзаимодействия и ОтборСообщенийСистемыВзаимодействия имеется возможность ограничения максимального количества запрашиваемых обсуждений/сообщений. Для этого необходимо установить значение свойства Количество у данных объектов.

Но в процессе тестирования данного механизма было выявлено, что если установленное значение превышает значение по умолчанию, то используется значение по умолчанию, а не указанное вручную ограничение.

Управление сообщениями Системы взаимодействия

Для тестирования ограничения максимального количества выборки обсуждений/сообщений мы сгенерировали 300 обсуждений по 10 сообщений в каждом и 3 обсуждения по 300 сообщений. Значение свойства ограничения выборки установили равное 999999. Несмотря на установленное значение, максимальное значение в выборке не превышало 100 обсуждений и 250 сообщений.

В результате работы процедуры выгрузки получаем файл в формате json c данными обсуждений Системы взаимодействия:

Получаем файл в формате json c данными обсуждений Системы взаимодействия

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

Регистрация системы взаимодействия

После этого приступили к загрузке информации из json.

В первую очередь при помощи метода СоздатьПользователя() были добавлены пользователи СистемыВзаимодействия.

ВНИМАНИЕ: Вызов метода необходимо осуществлять при установленном привилегированном режиме. В противном случае будет выдано сообщение об ошибке.

Вызов метода СоздатьПользователя()

Вызов метода СоздатьПользователя()

Далее с помощью метода СоздатьОбсуждение() были загружены обсуждения из файла выгрузки. Для поиска существующих обсуждений использовался метод ПолучитьОбсуждения(), в отборе которого передавался контекст обсуждения или ключ, исключающий дублирование обсуждений при повторной загрузке:

Загрузка обсуждения из файла выгрузки

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

Для каждого обработанного обсуждения создаем сообщения системы взаимодействия с помощью метода СоздатьСообщение()

На этом переход на свой сервер взаимодействия был завершен.

Конечно, процесс перехода не был простым, предполагал многоитерационное тестирование, в процессе которого пришлось решить ряд задач, например, по привязке прежних идентификаторов пользователей, корректной загрузке свойств личных/групповых обсуждений и других. Но об этом может быть расскажем в другой раз.

Главное, что после переезда на свой сервер взаимодействия в течение года не наблюдалось каких-то проблем.

Исправление ключа

На момент расследования мы предположили, что в процессе «переезда» скорее всего не разделяли обсуждения на служебные и обычные. Тем более какого-либо признака, который однозначно говорил бы о том, что это обсуждение служебное, кажется, не существует. И судя по всему, создание нашего проблемного обсуждения через объектные методы по какой-то причине заполнило нужные поля title/accessible_to_everyone не так, как это заложено в платформе.

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

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

Для этого зашли в pgadmin и выполнили команду UPDATE замены ключа:

Далее перезапустили службы сервера взаимодействия, дабы обновились кэши настроек.

После проделанных манипуляций мы повторили ситуацию с пессимистической блокировкой:

Выполнение команды UPDATE замены ключа

И (ура-ура!) в результате ошибки не оказалось!

Было показано обычное сообщение о попытке начала редактирования, кнопка «Начать редактировать» сработала как надо и без проблем. Никакого сообщения про обсуждение не появилось и мы смогли внести изменения в документ.

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

Сообщение «Не удалось начать редактирование»

Истинные причины возникновения ситуации

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

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

Для этого мы хотели «поймать» запрос, который выполняет платформа в момент нажатия на кнопку «Начать редактировать» при пессимистической блокировке.

Чтобы это сделать для начала выставляем самый подробный уровень детализации лога = TRACE в файле logging.yml:

Детализация лога

Чтобы настройки конфигурационного файла «подхватились» перезапускаем основную службу сервера с помощью команды:

ring cs --instance cs-example service restart

Воспроизводим ту же ситуацию с пессимистической блокировкой и нажимаем кнопку «Начать редактировать»:

Воспроизводим ту же ситуацию с пессимистической блокировкой и нажимаем кнопку «Начать редактировать»

Пользователю уходит оповещение:

Оповещения

Теперь идем смотреть логи.

Для начала поисследуем файл websocket.log.

Увы, никаких запросов с SELECT или INSERT мы не нашли, хотя в нём можно увидеть информацию о выполняемых операциях. Например, следующим bash-скриптом мы выбрали события, в тексте которых содержался ключ служебного обсуждения ‘9A5D8A84-35F4-4319-B45B-284089201110′, а также события рядом с ним, содержащие текст «conversation»:

cat websocket.log | awk -vORS= '{if(match($0, "^[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]-[0-9][0-9]\:[0-9][0-9]\:[0-9][0-9]\.[0-9]+\-")) print "\n"$0; else print $0"<line>";}' | grep -C 40 '9A5D8A84-35F4-4319-B45B-284089201110' | grep -P '(conversation|9A5D8A84-35F4-4319-B45B-284089201110)'| sed -e "s/,JAVA,.*level=/,/g; s/,pid=.*message=/, /g; s/\xef\xbb\xbf/ /g" | sed -e "s/<line>/\n/g" > websocket_log_key_query.txt

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

Выполняется попытка получения обсуждения, далее создание обсуждения и сообщения

Но это не совсем то что мы искали, поэтому перешли к файлу server.log

Для его анализа применили похожий подход поиска событий со служебным ключом, а также событий рядом с текстом SELECT/INSERT:

cat server.log | awk -vORS= '{if(match($0, "^[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]-[0-9][0-9]\:[0-9][0-9]\:[0-9][0-9]\.[0-9]+\-")) print "\n"$0; else print $0"<line>";}' | grep -C 40 '9A5D8A84-35F4-4319-B45B-284089201110' | grep -P '(SELECT|INSERT|9A5D8A84-35F4-4319-B45B-284089201110)'| sed -e "s/,JAVA,.*level=/,/g; s/,pid=.*message=/, /g; s/\xef\xbb\xbf/ /g" | sed -e "s/<line>/\n/g" > server_log_key.txt

В результирующем файле, как раз на момент времени нажатия на кнопку, мы нашли два идущих подряд события, в одном из которых содержался текст запроса, а в следующем за ним — все значения параметров этого запроса:

Результирующий файл с текстом запроса

Если убрать всё «лишнее» и упростить этот запрос, то он сводится к следующему:

SQL запрос

Происходит объединение двух выборок из таблицы conversation, условия в них одинаковы кроме последнего:

  1. В первой выборке в условии выполняется коррелированный запрос к таблице conversation_member с отбором по идентификатору пользователя.
  2. Во второй выборке ставится отбор по полю accessible_to_everyone.

Говоря простым языком — происходит попытка найти обсуждения, либо в которых текущий пользователь является участником, либо обсуждения «доступные для всех». Попробуем ответить на вопрос, что это значит.

Если выбрать участников корректных служебных обсуждений, например подобным запросом в pgadmin:

SELECT members.*
FROM vb_subscriber_13_13.conversation as conversations
INNER JOIN vb_subscriber_13_13.conversation_member as members
ON conversations.id = members.conversation_id
AND conversations.key = '9A5D8A84-35F4-4319-B45B-284089201110';

То во всех них увидим ровно одного пользователя с идентификатором «5c9f6038-0026-4439-98d4-1a1a1eb403df»:

SQL запрос

Но пользователя с таким идентификатором в таблице user вы не найдете, потому что это это не обычный пользователь, а некий зарезервированный uuid для обозначения «широковещательного» пользователя «ВсеПользователиПриложения». В этом можно убедиться, выполнив команду на стороне 1С:

Команда 1С

И похоже, что этот идентификатор всегда один и тот же для разных баз, как и ключ служебного обсуждения.

Более того, именно наличие этого пользователя в участниках приводит к установке флага accessible_to_everyone, который никакими свойствами объекта обсуждения со стороны 1С не управляется. Чтобы проверить это утверждение, на демо-стенде создаем обсуждение с одним участником «ВсеПользователиПриложения» и спецключом «Доступное всем обсуждение», чтобы его потом найти:

НовоеОбсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
НовоеОбсуждение.Групповое = Истина;
НовоеОбсуждение.Ключ = "Доступное всем обсуждение";
НовоеОбсуждение.Участники.Добавить(СистемаВзаимодействия.СтандартныеПользователи.ВсеПользователиПриложения);
НовоеОбсуждение.Записать();

Далее делаем выборку по ключу «Доступное всем обсуждение» и, как результат, видим у обсуждения выставился флаг:

SQL запрос

Если же выбрать участников в том самом проблемном обсуждении с изменённым ключом, то в нём увидим совсем иной идентификатор, который соответствует одному из пользователей базы:

SQL запрос

По-видимому, в этом и крылась основная причина.

Но тут вы можете поймать нас на слове «Ведь вы говорили после переноса из 1С:Диалога целый год пользователи работали, неужели никто не жаловался?!».

И ведь правда, проблема как будто возникла только после перехода с версии 8.3.17.2306 на 8.3.21.1895.

В этом месте мы можем только предположить, что на более старых версиях платформа не использовала поле accessible_to_everyone при поиске служебного обсуждения и благополучно его находила. А после обновления появился отбор, из-за которого найти обсуждение не получается, но и создать не удается, т. к. уже существует обсуждение с таким ключом.

Данные сообщения UnblockObjectMsg

А помните те странные сообщения с текстом «UnblockObjectMsg»? Напоследок предлагаем взглянуть на них. При наших изысканиях мы обратили внимание, что у всех сообщений служебного обсуждения заполнено поле «data»:

Данные сообщения UnblockObjectMsg

Анализировать его в «сыром» виде можно, но это дело неблагодарное. Можно пойти другим путём — выловить это сообщение в отладке. В инструменте «Вычислить выражение» конфигуратора видим поле «Данные» с типом «Структура»:

Данные сообщения UnblockObjectMsg

И если раскрыть его детально, становится понятно, что эти сообщения как раз используются для оповещения пользователя, у которого «перехватили» редактирование объектом:

Данные сообщения UnblockObjectMsg

Послесловие

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

Таков путь эксперта: ему неизбежно приходится заглядывать в потаенные комнаты, освещать фонариком темные уголочки. И поскольку нам было это и сложно, и интересно сделать, то мы решили поделиться с вами, уважаемые коллеги!

Вы читаете статью из рубрики:
От экспертов «1С-Рарус»

Есть вопросы по статье? Задайте их нам!

Рассылка «Новости компании»: узнавайте о новых продуктах, услугах и спецпредложениях

Посмотреть все рассылки «1С‑Рарус»

Поле не должно быть пустым
Электронная почта указывается только латиницей, обязательно должен присутствовать знак @, доменное имя не может быть короче двух символов

Посмотреть все рассылки «1С-Рарус»

Иконка «Предупреждение» Отправляя эту форму, Вы соглашаетесь с Политикой конфидециальности и даете согласие на обработку персональных данных компанией «1С-Рарус»

Заинтересованы в сотрудничестве?
Нужна консультация?
Свяжитесь с нами!