Заметки

Позднее Ctrl + ↑

Без комментариев

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

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

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

8 сентября 2021 код с запашком

Медленное оповещение

Твит

Прочитал этот твит и внезапно вспомнил случай из практики. Прилетает запрос от клиента — мол, 1С тормозит, сделайте что-нибудь. Уточняем: тормозит всё-таки не вся 1С, а конкретная операция.

База клиента — переписанная УТ, проблемная операция — из блока доработок. Внутри — множество многоэтажных запросов, возня с таблицами значений, какие-то невнятные вычисления и пачка серверных процедур по семьсот строк каждая. Копию базы снять нельзя, ТЖ недоступен, доступ к СУБД закрыт, доступ к счётчикам — тоже. Отладка на сервере 1С выключена, включить — не вариант.

Бардак, в общем. Как понять, где тут всё вязнет? Ладно, конфигурацию можно менять — уже неплохо. Вешаю на операцию замер: ну да, думает над чем-то пару минут. Отлично! Потираю ручки и сужаю область поиска: докидываю целый ворох замеров на самые подозрительные участки.

Результат любопытный: тормозов нет! Код не то чтобы летает, но о минутах и речи нет — с учетом сложности, расходы в пределах допустимого. Может, какая-то сериализация данных между клиентом и сервером? Тщательно выверяю код серверной процедуры — нет, никакой трансляции. Всё, что было на сервере, осталось на сервере.

Перехожу к клиентской части. Что тут может тормозить-то?.. Выглядел код, упрощая, как-то так:

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

Я знатно офигел, полез проверять и в конце концов наткнулся на форму, которая ловила событие выполнение операции и подрывалась обновлять свои динамические списки. Запрос в одном из них и давал тормоза (там подзапрос к подзапросу к подзапросу и ворох соединений «через точку» — в общем, дальше не интересно).

Такая вот яломиште :-) Оповещайте с осторожностью!

18 августа 2021 работа оптимизация

Умер Павел Чистов

Павел Чистов в пятницу вечером умер из-за сердечной недостаточности. Если вы не знаете, кто это — вот некролог. Впрочем, раз вы читаете этот блог — скорее всего, знаете.

Никита Грызлов, Илья Леонтьев и Сергей Горшенин собирают деньги для помощи семье Павла. Кроме того, номер карты его вдовы есть в конце некролога по ссылке выше.

Берегите себя, пожалуйста.

1 августа 2021

Поиск долгих запросов с помощью Python

Выложил скрипт на Python для поиска длительных запросов в ТЖ 1С. Накатал его в приступе отчаяния: никак не мог понять, почему мой верный баш для одного из запросов выдаёт среднее время выполнения больше максимального.

Как выяснилось, проблема была в gawk. Для некоторых событий ТЖ эта утилита не могла определить длительность: пыталась преобразовать строку в число, фейлилась и… Нет, что вы! Конечно, не кидалась исключением! Просто невозмутимо считала эти строки за ноль и ехала дальше.

Патч, кстати, вышел ещё глупее проблемы: я просто сделал явное преобразование строки в число, и всё заработало как надо. Чем, блин, явное преобразование в мире gawk'а отличается от неявного? И, главное, почему?

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

Это вполне рабочая тактика, пока входящий поток не переваливает за сотни тысяч элементов: где-то тут мы начинаем терять гигабайты ОЗУ на хранении коллекций и прорву времени процессора на поиске в них. Новый скрипт попрямее: коллекция одна, но хранит всe данные по каждому запросу.

18 июля 2021 bash Python

Почему не баш?

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

Чтобы не быть голословным, давайте пример из практики. Есть, скажем, порядка семидесяти гигабайт логов ТЖ 1С, по которым нужно построить топ пояснений к исключениям — от самых частотных к менее частотным.

Плёвое дело, верно? Выгребаем EXCP, извлекаем Descr, считаем повторения. Даже нужный скрипт я как-то уже писал. Запускаю, терпеливо жду…

Знаете, сколько понадобилось времени? Я тоже нет: после того, как скрипт проработал сутки, я его вырубил и полез разбираться, в чём проблема. Затык возник где-то в скрипте gawk'а: именно она активно нагружала процессор (если не считать cat'а, которая время от времени читала очередную порцию данных).

Процессы

Беда, беда, огорчение! В общем, я подумал и переписал этот скрипт на Питоне. Новая версия отработала за 15 минут и дала мне:

  1. Топ пояснений по событиям исключений;
  2. Скрипт, который можно прочитать через полгода без помощи гугла;
  3. Уверенность, что я могу добавить в скрипт два-три условия и не вызвать Сатану случайным сочетанием операторов и ключей.

Конечно, я мог оптимизировать версию на баше. Вероятно, тормозит поиск в массиве — время, необходимое для поиска пояснения в массиве уже зафиксированных пояснений, линейно растет с увеличением размера массива. Можно, например, попробовать изменить подход к сбору данных — gawk'ом только извлекать сами пояснения, а результат сбора передать в тандем sort & uniq.

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

Собственно, к этому я вел. Родовые травмы баша понятны и нередко приемлемы, плюсы — приятны и очевидны, но при работе с ним вопрос иногда встает так: cтоит ли только ради того, чтобы получить решение именно на баше, потратить часа два на возню с утилитами, параметрами, мануалами и постами на Stack Overflow?

Твит

Вот поэтому не баш, да.

3 июля 2021 bash Python

Почему баш?

Периодически вижу вопросы коллег: а что, 1С всё ещё носится с башем, да? А чё не пайтон-то? Или павершелл, на худой конец? Вот чудаки!

Да, баш не родной для Windows (которая, напротив, родная для 1С) и притащить его туда — отдельная история; да, при усложнении задачи читаемость скрипта падает по экспоненте; да, с некоторыми задачами баш просто не справляется.

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

Пример? Допустим, нужно выгрести из ТЖ исключения; простоты ради договоримся, что достаточно первой строки каждого события. Получим примерно такой скрипт на пайтоне.

Скажете, его можно легко сократить вдвое? А то и втрое. В конце концов, мы решаем локальную задачу. Возня с читаемостью и предсказуемостью тут ни к чему.

Согласен! Но давайте сначала решим ту же задачу на баше:

grep -r --include "*.log" ',EXCP,' > result.txt

Вот почему баш.

23 июня 2021 bash Python

Метаданные не найдены

Метаданные не найдены

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

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

В двух словах о том, как вообще работает история данных. По дефолту она выключена, и достаточно привилегированный пользователь может включить её для нужных ему объектов — констант, справочников, документов и так далее. После этого при изменении данных в этих объектах платформа будет фиксировать, что именно меняется. Делает она это в два этапа: сначала — относительно быстрая запись в промежуточный буфер (таблицу _DataHistoryQueue0); потом, при вызове метода ОбновитьИсторию(), информация мигрирует в основное хранилище версий (таблицу _DataHistoryVersions) и становится доступна в интерфейсе приложения.

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

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

Как избежать этой ошибки? Ну, очевидно: метод ОбновитьИсторию() нужно вызывать непосредственно перед тем, как отключать историю для объекта. Лучше всего делать это в период, когда пользователи с системой не работают: есть риск, что между обновлением истории и её выключением вклинятся чьи-то транзакции, которые спровоцируют ту же проблему. Для гарантии можно использовать монопольный режим.

Ещё в голову приходит исключительная блокировка на таблицу объекта, но документация не советует вызывать метод ОбновитьИсторию() внутри транзакций — так что тут я не уверен.

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

8 июня 2021

Скрипт для синхронизации c NAS

Выложил на GitHub скрипт на Python, который я использую для синхронизации файлов между своим компьютером и домашним NAS'ом. У меня стоит Synology DS220j; с ним так-то идет целый вагон софта и в том числе утилита, которая умеет гонять файлы туда-сюда по расписанию. Однако сделана она, похоже, чисто для галочки: программа принялась глючить ещё на этапе настройки, после чего доверие к ней я потерял.

В общем, какое-то время я помучался с решениями конкурентов и в итоге вернулся к привычному rclone, с которым было всего две проблемы. Во-первых, нельзя нормально соединиться с SMB-шарой: да, логин и пароль можно сохранить в Windows и rclone будет их использовать, но они будут слетать при каждом удобном случае. Я вышел из положения, подключив шару как внешний диск.

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

7 июня 2021 готово Python рабочее место

Как готовиться к 1С:Эксперту

В середине мая я сдал экзамен на статус эксперта в 1С по технологическим вопросам крупных внедрений. Подготовка вышла долгая, почти два года — так что я чертовски рад, что наконец-то справился.

В сети есть море материала про этот экзамен и связанные с ним темы — начиная с курса Виктора Богачева и заканчивая, не знаю, релевантным чатом в Телеграме. То есть вопрос из заголовка заметки обычно не стоит. Но я хочу написать о паре психологических заморочек, к которым лучше быть готовым заранее и о которых обычно не вспоминают.

Нехватка дофамина

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

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

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

Например, я для отвлечения катал скрипты для домашней автоматизации — генератор статики для этого сайта, органайзер на текстовых файлах, обвязку для домашнего NAS'а и тому подобные штуки, до которых раньше никак руки не доходили. Подойдут любые задачи — включая те, что с айтишечкой вообще не связаны. Почистить слив в ванной? Отличная идея!

Нехватка памяти

Если вы не работаете с проблемами производительности 1С каждый день, этот экзамен — очень ресурсоемкий контекст. Вы будете держать в голове массу вспомогательных данных, которые обычно берете из документации: флаги трассировки и имена динамических представлений Microsoft SQL Server, настройки PostgreSQL и рекомендации по их заполнению, перечень стандартных индексов платформы и так далее, и тому подобное. Во-о-от такенная куча информации! (показывает руками, какая)

Между тем, наш мозг при работе с большими объёмами данных порой крепко напоминает СУБД, работающую с переполненным буферным кэшем. Другими словами: всё, что не нужно в данный момент, будет выгружено из памяти. Готовьтесь забывать самые неожиданные вещи: номер своей квартиры, кличку соседской собаки, день рождения жены — и это ещё не самые диковатые последствия. Я, пока готовился, чуствовал сильное духовное родство с Джонни Мнемоником — персонажем старого-престарого киберпанка, который загрузил себе в мозги столько данных, что чуть не отбросил коньки :-)

Не надо так

Выхода тут мне видится два, оба довольно очевидные.

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

Во-вторых — старайтесь строить ассоциативные цепочки для наиболее заковыристых штук. Взять, например, баш! Название утилиты sed проще запомнить, если знать, что это сокращение от Stream EDitor. Утилита cat называется так, потому что concatenate, а не потому что кошка, но если вам проще запомнить второй вариант — так и делайте. Почему нет?

Или, скажем, маркер последовательности байтов. \xef\xbb\xbf, ага. Его я намертво запомнил — только не смейтесь — через абсолютно иррелевантную фразу «Elf Friend BB Best Fight». Понятия не имею, почему моя голова работает через такую жо такие заковыристые интерфейсы, но жаловаться я не собираюсь. Главное — отыскать их!

25 мая 2021 готово

Новый скрипт поиска долгих запросов

Обновил скрипт поиска долгих запросов в технологическом журнале платформы. Ранняя версия содержала серьёзный косяк: она старательно группировала DBMSSQL по Sql и Content, но игнорировала случаи, в которых поле Sql отсутствовало (для DBMSSQL это не то чтобы норма, но встречается).

Ну и код слегка причесал, чтобы два раза не вставать: добавил разметку и комментарии, а часть логики gawk'а вынес из основного тела в функции. Да, скрипт стал смотреться длиннее, чем он есть на самом деле, зато читать его стало куда проще.

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

26 апреля 2021 bash готово

Пучок скриптов

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

  • Частотные события. Группирует события по наименованию, считает количество воспроизведений для каждого и выводит в порядке убывания — от наиболее частотного к наименее частотному. Практического применения у этого скрипта, скорее, нет; просто фиксировал для себя, какой процесс кластера какие события пишет.
  • Описания исключений. Группирует EXCP по полю Name; для каждого наименования выводит варианты значений поля Description, которые были у исключений с таким наименованием. С помощью этого скрипта можно составить примерную картину: какие исключения действительно проблема, а какие — просто белый шум, который можно игнорировать и, например, закинуть в фильтры Кибаны.
  • Блокировки по ID соединения и области. Удобен для поиска виновника таймаута на управляемых блокировках: скрипт выгребает из ТЖ все TLOCK'и по конкретной области от конкретного соединения, а потом выстраивает их в хронологическом порядке.

24 апреля 2021 bash готово

SKUUUID в своем глазу

Пару недель назад Александр Кунташов у себя на канале запостил забавный скрин перечисления из Библиотеки Электронных Документов, создателей которого откровенно манили длинные аббревиатуры. Я бодро острил на тему родства ИППДОИПУПДУКД'а и московского ГНУВНИВИПФИТ'а, а потом увидел в собственном коде вот эту красотку:

SKUUUID

Ещё не вполне рука маэстро, конечно, но движение определенно в том же направлении. Вспомнилась притча про сучок в чужом глазу и суковатое бревно — в своём :-)

22 февраля 2021 код с запашком

Проблема с литералом даты при пересчете итогов

Итак, в ходе рутинного пересчета итогов из Конфигуратора мы неожиданно получили ошибку «номер года в литерале типа дата превышает 3999». Это значит, где-то в базе есть (или пытается появиться) дата больше, чем предельно допустимая с точки зрения 1С (31.12.3999 23:59:59).

Исключение

Ладно, что с этим делать?

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

Другой, более методологически правильный подход — настроить сбор ТЖ (SDBL, EXCP и EXCPCNTX) и получить примерно такой лог. Ищем в нём событие EXCP (исключение), а непосредственно перед ним — событие SDBL (SQL-запрос в терминах платформы). Этот запрос — причина сбоя; в его тексте видим имя нужной нам таблицы (AccumRgTn11530). Имя объекта конфигурации для неё можно вытащить любой обработкой, построенной на методе ПолучитьСтруктуруХраненияБазыДанных().

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

Например, у меня был случай, когда после исправления движений записи с некорректными датами сохранялись в таблице оборотов. Средствами платформы их удалить было нельзя, но размер базы позволял играться с реструктуризацией. Я выкинул следующий финт: отключил признак «Использование в итогах» для всех измерений регистра и применил изменения; потом вернул признак на место и пересчитал итоги. В итоге таблица оборотов регистра была физически удалена, а потом создана и наполнена заново — уже без дат на много веков вперёд.

В крайнем случае можно было удалить проблемные записи с помощью прямых SQL-запросов (DELETE или даже TRUNCATE). Но это, во-первых, нарушает лицензионное соглашение с разработчиками платформы, а во вторых — опасно (по неосторожности можно удалить что-то важное и не заметить). Так что не советую, э-э, повторять в домашних условиях :-)

6 февраля 2021

Определение видимости объекта

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

Так вот, помню, лет пять назад я искал способ понять из кода — видит пользователь объект или нет? Чисто с точки зрения функциональных опций. Тогда я почему-то не нашел решения, а ведь оно до смешного простое; вот, например, функция ровно для этого. Логика:

  1. Перебираем опции и ищем объект в составе каждой из них.
  2. Если объект есть в составе опции, проверяем: опция активна? Если да — значит, пользователь видит объект.
  3. Если объект не включен ни в одну опцию — тоже видит.

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

17 января 2021

Рекурсивный поиск по файлам

Время назад я примеривался к поискам уязвимостей в коде скриптами на bash (звучит грозно, но это просто рекурсивный поиск текста с помощью регулярных выражений). Скрипты-то я тогда написал, но, как сегодня понял — несколько… Ректально, кхм. Для решения хватает одного egrep! То есть из связки find, xargs и egrep можно выкинуть два компонента из трех.

Например, сегодня у нас возникла проблема: конфигурация перестала собираться в последнем релизе EDT. Подозрение пало на битые GUID — ссылки на объекты метаданных, удаленные из конфигурации. Платформа не всегда справляется с их вычисткой после того, как удалит сами объекты; я уже пару раз писал про это (например, здесь или вот тут).

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

egrep -rn '.{8}-.{4}-.{4}-.{4}-.{12}' dump

Ключ r включает рекурсивный поиск, ключ n — заставляет утилиту пристегнуть к найденной строке не только имя файла, в котором найдена строка, но и номер самой строки. Последний параметр, dump — имя директории, где нужно искать.

Регулярку можно сделать точнее, но и такой за глаза хватает. Что до ложных срабатываний (то есть GUID, которые не являются битыми ссылками) — их легко отсеять через пайп. Например, скрипт ниже не будет выводить строки с GUID, в которых есть подстрока «uuid»:

egrep -rn '.{8}-.{4}-.{4}-.{4}-.{12}' dump | grep -v 'uuid'

14 января 2021 bash

Старый добрый DATETIME

Порылся в сети по поводу типов дат в MS SQL Server и в целом вопроса «почему 1С до сих пор носится со своим смещением» больше не имею. Люди пишут о целой пачке проблем с DATETIME2:

  1. Недоступна базовая математика. Без дополнительных финтов ушами не выйдет посчитать разницу между двумя датами, прибавить к дате день и так далее.
  2. Стандартные функции по-прежнему возвращают старый добрый DATETIME (например, DATEADD). Если данные хранятся в DATETIME2 — потребуется конвертация.
  3. Поля с этим типом неважно индексируются, так как каждое значение DATETIME2 хранится задом наперед (сначала время, потом дата). В итоге СУБД промахивается с оценкой количества строк, которое может вернуть запрос, и строит для него неэффективный план выполнения.

Подробнее о всем этом можно прочитать на Towards Data Science или, например, на SQL Server Central.

11 ноября 2020 MS SQL

Смещение дат в 1С

В MS SQL Server поля DATETIME не могут хранить даты раньше 1753-го года. Например, если попытаться записать в базу 01.01.0001 — получим ругань на out-of-range value. Я считал это забавным для такой почтенной СУБД рудиментом, пока случайно не наткнулся на причину.

Если вкратце, в 1752-м году Великобритания внедрила у себя Григорианский календарь, и в процессе у них из летосчисления пропало одиннадцать дней. Это породило проблему: вот хочет юзер посчитать разницу в днях между 1653-м и 1753-м годом — что делать будем? Учтем потеряшек? Проигнорируем? Сделаем какие-то хинты или настройки?

Видимо, чтобы не городить неоднозначные механизмы, разработчики СУБД решили вопрос радикально — усечением доступного диапазона дат. А для тех, для кого это проблема, есть DATETIME2, который никаких ограничений не имеет.

Что касается 1С, то изначально платформа использовала DATETIME, а чтобы не иметь головной боли с хранением дат раньше 1753-го года — придумала специальный костыль. В двух словах: когда платформа пишет даты в БД, то тихой сапой прибавляет к каждой две тысячи лет, а когда читает — вычитает обратно. То есть в 1С пользователь видит 01.01.2000, а в БД на самом деле хранится 01.01.4000.

Любопытно, почему 1C до сих пор не выкинула эту штуку? Сейчас платформа использует DATETIME2 и фокус по смещением в общем-то не нужен. Конечно, тут могут быть какие-то подводные камни или просто разумная осторожность, но среди разработчиков самой СУБД сомнений не заметно:

Your great great great great great great great grandfather should upgrade to SQL Server 2008 and use the DateTime2 data type, which supports dates in the range: 0001-01-01 through 9999-12-31.

Joe Stefanelli (SQL Server developer)

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

10 ноября 2020 MS SQL

Халк удалять!

На курсе по PostgreSQL узнал смешную деталь: в 10-й версии СУБД разработчики переименовали папку pg_xlog (журналы предзаписи) в pg_wal, а папку pg_clog (статусы транзакций) — в pg_xact.

Знаете, почему? Из-за не слишком опытных, но уже достаточно смелых администраторов, которые триггерились на слово «log» в названии папки. Мол, мне нужно место на диске освободить, а тут СУБД забила всё своими дурацкими логами. Некогда разбираться, rm -rf их и порядок!

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

9 ноября 2020 PostgreSQL

Время для прогулки

Как понять, что надо сделать перерыв в работе? Скажем так: если ваш скрипт внезапно начал вам подмигивать — точно пора проветриться.

Привет!

2 ноября 2020 bash

Расследование ошибки в Конфигураторе

Итак, Конфигуратор выдает ошибку; нужно её исправить или обойти. Это потенциально неприятный расклад: у нас нет никакого доступа к коду приложения. Тем не менее, чтобы решить проблему — важно понять, что именно делал Конфигуратор до сбоя и почему он не справился.

Что может с этим помочь?

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

Второе — технологический журнал по событиям EXCP, SDBL и DBMSSQL (DBPOSTGRS?) для t:applicationName=Designer. Из него мы получим информацию об исключениях внутри Конфигуратора и данные запросов, которые он выполняет (нередко они и есть причина ошибки).

Кроме того, может пригодиться трассировка запросов к базе данных. Если используется MS SQL, то трассировку можно получить через Extended Events — конкретно, нас интересуют события error_reported, rpc_completed и sql_batch_completed. Общий принцип тот же — ловим ошибки выполнения запросов и сами запросы.

Разберем пример — может, не самый показательный, зато свежий.

Контекст — конфигурация, использующая разделение данных. Её база данных «нарезана» на кусочки (области данных), каждый из которых ничего не знает о своих соседях — сколько их, какого они размера и так далее. При этом в БД есть и общие данные — например, справочники, к которым можно обратиться из любой области. Как правило, это разная техническая информация — например, перечень объектов метаданных.

Задача — исключить справочник FileStorageVolumes из основного разделителя. Сейчас этот справочник — разделенный: то есть, его данные от области к области будут различаться. Нам нужно сделать так, чтобы содержимое справочника стало одинаковым для всех областей.

Задача несложная, так как таблица справочника пуста — ни одна из областей ничего в нем не хранит. Что же, применяем настройку и:

Исключение

В тексте исключения есть сообщения: одно от платформы, второе от СУБД. Первое озадачивает: то есть как это данные не уникальны? Справочник же пуст, никаких данных нет. Возможно, проблема в каких-то вспомогательных структурах, не связанных с содержимым справочника напрямую.

Сообщение СУБД более внятное: MS SQL Server хотела создать уникальный индекс для таблицы _DataHistorySettingsNG, но не смогла, так как сочетания индексируемых полей оказались неуникальны. Приводится даже конкретное значение, из-за которого не получилось создать индекс: это NULL.

Выводы?

  1. Очевидно, что проблема возникла в ходе реструктуризации. Во-первых, именно её мы и делали. Во-вторых, на это указывает и операция CREATE UNIQUE INDEX (создание индексов в таблицах — часть реструктуризации), и название проблемной таблицы: в нём есть постфикс NG (его получают копии таблиц, которые создаются при реструктуризации; если она проходит успешно, то платформа удаляет исходную таблицу и переименовывает копию).
  2. Проблема возникла с настройками механизма истории данных (_DataHistorySettings). Там хранится статус каждого объекта метаданных: нужно или нет вести историю данных для объекта, его полей и полей его табличных частей (если они есть).

Последнее объясняет, почему проблема уникальности возникла на пустом справочнике: настройки истории данных для объекта хранятся независимо от того, есть в объекте какие-то данные или нет. Если посмотреть на таблицу с настройками, там всего три поля: _MetadataId (ID объекта метаданных), _Content (значения настроек) и _Fld626 (разделитель области).

До реструктуризации данные имеют примерно такой вид:

Таблица _DataHistorySettings

Однако потом эта картина изменилась. Когда мы исключили справочник из состава общего реквизита, конфигуратор запустил реструктуризацию: создал таблицу _DataHistorySettingsNG, перенес в неё данные из _DataHistorySettings и установил значение поля _Fld626 в NULL всем записям, которые относятся к справочнику FileStorageVolumes.

К чему это привело? А вот к чему: для справочника FileStorageVolumes появился целый ворох настроек, которые не относятся к какой-либо области. Это само по себе звучит нездорово, но настоящие проблемы начались, когда Конфигуратор попытался создать для таблицы кластерный индекс: он строится по полям _MetadataId и _Fld626, является уникальным и, соответственно, не может быть создан — в таблице множество записей, у которых различается только поле _Content, а _MetadataId и _Fld626 — гарантированно идентичны.

Для очистки совести посмотрим техжурнал (я вычистил оттуда нерелевантные события и другую постороннюю информацию). Наши догадки подтверждается: видим, как Конфигуратор создает и заполняет таблицу _DataHistorySettingsNG, пытается проиндексировать её, но получает ошибку и удаляет. В трассировке СУБД примерно та же картина.

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

В общем, готово — мы великолепны!

31 октября 2020

Ранее Ctrl + ↓