Оптимизации и ускорения могут быть шумом нагрузки. Как это проверить

21.08.26

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

Шесть внешних отчётов на боевой базе крупной сети переписали за неделю. По секундомеру ускорились четыре, в акт пошли три: у одного отчёта минус тринадцать процентов оказались выбросом нагрузки прода, а не эффектом кода. Поймал это чередующийся замер до/после в одном окне, обычные три прогона с метрикой "минимум" показывали ускорение уверенно. Дальше в тексте: почему построчная сверка выхода ломается ровно на тех правках, ради которых её заводят; из чего собирается повторяемый слепок результата и почему главную работу в нём делает нормализация, а хеш только сигнализация; где слепок не берётся вовсе и его заменяет по-колоночная сверка; чем проверять пересчёт итогов, чистку данных и операции с кластером. Отдельно - гипотеза, которую замер отклонил, и честный счёт, во что вся эта дисциплина обходится.

Вы переписали запрос, замерили, получили минус тринадцать процентов и отчитались заказчику. Вопрос простой: это результат или совпадение?

У нас был случай, когда ответом оказалось "совпадение". Отчёт переписали, лучший прогон дал 23,0 секунды против 26,5 до правки. Минус тринадцать процентов, скромно, но результат. Потом замер переставили по-другому, и минус тринадцать исчез полностью.

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

 

О чём этот текст

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

Здесь про вторую половину той же дисциплины, которую обычно не делают вовсе.

Сверка доказывает, что вы ничего не сломали. Она ничего не говорит о том, починили ли вы что-нибудь. Это разные вопросы, и второй проваливается чаще.

Материал с проекта оптимизации в крупной сети. Замеры шли на боевой базе в режиме только на чтение, дамп и сборка внешних отчётов на тестовой копии, платформа 8.3.17.1386.

 

Два вопроса приёмки

Первый: результат тот же? Отвечает сверка выхода до и после.

Второй: а эффект вообще был? Отвечает повторный замер, устроенный иначе, чем первый.

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

Шесть внешних отчётов, разобранных за неделю:

Отчёт До После Итог
Сводный складской 30,0 с 23,4 с -22 %
Остатки по группам номенклатуры 20,2 с 14,1 с -30 %
Показатели по неходовым позициям ~38 с ~26 с -30 %
Склады одного бренда ~26,5 с ~23,0 с эффекта нет
Возвраты ~6 с - выигрыша нет, оставлен оригинал
Кассовый X-отчёт 0,11 с 0,11 с уже быстрый

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

Не ускорилось. Первый замер дал -13 %, перепроверка не дала ничего.

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

 

Как минус тринадцать процентов превратились в ноль

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

На боевой базе, где кроме вас работают люди, у минимума есть особенность. Он ловит самый удачный момент. Если удачный момент случайно достался блоку "после", вы получите ускорение, которого нет.

Медиана по тем же прогонам у нас была нейтральной: 26 852 мс до правки и 26 516 после. Чуть больше процента, то есть ничего. Между минимумом (-13 %) и медианой (-1,2 %) выбрали минимум, потому что так считали для всех остальных отчётов и сравнивать надо было одинаково.

Перепроверка была устроена чередованием: до, после, до, после, четыре круга подряд в одном окне. Смысл в том, чтобы дрейф нагрузки попал в обе группы поровну, а не осел в одной.

Вышло так:

  • до правки: 25,1 / 20,8 / 23,5 / 26,4 с;
  • после правки: 22,0 / 22,4 / 25,7 / 24,4 с.

Распределения перекрываются целиком. В первом круге лучший прогон "до" (20,8 с) оказался быстрее лучшего прогона "после" (22,0 с). Устойчивого ускорения нет, разброс в пределах ±5 % шума прода.

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

Разница между этими двумя замерами - вся разница между инженерией и совпадением.

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

 

Почему сверку выхода нельзя делать построчно

Вторая половина приёмки - доказать, что результат не изменился. Первое решение всегда одно: выгрузить до, выгрузить после, сравнить построчно.

На отчётах это ломается, и вот почему.

Порядок строк не гарантирован. Запрос без явной сортировки отдаёт строки в том порядке, в каком его вернула СУБД, система управления базами данных. Изменился план - изменился порядок. Данные те же, файлы разные.

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

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

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

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

 

Слепок и почему он состоит из трёх частей

Мы считаем повторяемый слепок результата и сравниваем его.

Три составляющие:

  • число строк;
  • сумма всех числовых ячеек;
  • SHA-256 по канонически сериализованным строкам, отсортированным как строки.

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

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

Живой пример с того же проекта, отчёт из четвёртой строки таблицы: 75 078 строк, та же сумма числовых ячеек, SHA-256 начинается на 76F50C6E. Все три совпали до и после, хотя запрос переписан целиком. Именно поэтому вариант всё-таки ушёл в деплой: скорости он не дал, но и вреда тоже. Код при этом стал понятнее.

Нормализация - то, на чём всё держится

Слово "канонически" в описании слепка главное, и без него конструкция не работает.

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

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

Без первых двух пунктов слепок закрывает одну проблему из трёх, названных выше. Он снимает порядок строк, а плавающую точку и промежуточные округления не снимает. Это делает не хеш, а нормализация перед ним.

Порядко-независимость и одна мина в ней

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

Второй способ быстрее и имеет неприятное свойство. Если сворачивать через XOR, побитовое исключающее ИЛИ, чётное число одинаковых строк даёт ноль и в слепке не видно. То есть задваивание строк, самая дорогая ошибка оптимизации, для XOR-свёртки невидимо.

Здесь и выясняется, зачем нужна первая часть слепка. Число строк прикрывает эту дыру и держит конструкцию наравне с хешем.

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

Когда слепок не берётся вовсе

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

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

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

Ложное расхождение, которое стоило половины дня

Ещё один встроенный отчёт показал "данные изменились" там, где их не меняли. Причина сидела в замере. Параметр отчёта выбирался вспомогательным запросом с ПЕРВЫЕ 1 без сортировки, платформа отдала разные значения в прогонах до и после, отчёт послушно построился по разным сезонам, слепки разошлись.

Правило после этого простое: параметры обоих прогонов фиксируются явно и одинаково, включая те, которые "выбираются сами". Эквивалентность имеет смысл мерить только на детерминированном входе.

 

Разные объекты - разные приёмы

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

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

Пересчёт итогов слепком не проверить: он как раз меняет то, что мы сравниваем. Здесь берётся регистр, остаток считается суммированием движений от начала и сравнивается с таблицей итогов. На пересчёте, сжавшем итоги с 73,6 до 18,3 миллиона строк за 25 минут, эта сверка и была единственным доказательством корректности.

Веб-выгрузка остатков - отдельный трек того же проекта и самый заметный результат по скорости: на разных объёмах ускорение доходило до 8,5 раза, в замеренном вызове со 192 секунд до 59. Здесь слепок вырождается в приятную частность: выгрузка отдаёт JSON, и он совпал байт в байт. Эндпоинт вызывается по складу, поэтому слепков получается столько же, сколько складов, и каждый сравнивается отдельно. Считать один слепок по объединению всех складов было бы ошибкой: расхождение по одному складу в общей сумме утонет.

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

Что делать, когда слепок красный

Первая реакция всегда одна: "сломали". Часто это не так, и порядок разбора экономит часы.

Смотреть по возрастанию тяжести.

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

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

Строки и сумма совпали, хеш красный. Разошлось что-то нечисловое: текстовое поле, признак, порядок колонок. Часто оказывается, что поехала нормализация, а сама выборка та же. Например, поле стало приходить с пробелом на конце.

Всё совпало, но пользователь говорит, что стало иначе. Слепок снимался не с того, что видит пользователь. Проверить, попадает ли в него итоговая таблица целиком, включая расшифровки и группировки.

 

Гипотеза, которую замер отклонил

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

На отчёте по неходовым позициям первая версия правки отчёт замедлила. Фильтр по виду номенклатуры оказался неселективным, под него попадала почти вся номенклатура, и соединение с огромным списком обошлось дороже дереференса. Помог второй заход, с другой стороны: фильтровать срез цен по номенклатуре, которая реально двигалась за день. Один день движений много меньше всей базы, вот там -30 % и появились.

На чековых отчётах приём не дал ничего. Платформа сама эффективно проталкивает отбор по периоду в условие Ссылка.Дата МЕЖДУ, предварительный отбор документов ей не помогает.

Граница получилась такая: полусоединение выигрывает на селективном фильтре и вредит на широком. Селективность из текста запроса не видна, её показывает только замер на боевых объёмах.

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

 

Чего это стоит

Честная часть.

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

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

Ночные окна. Тяжёлые операции идут монопольно, приёмка следом, в том же окне.

Чередующийся замер. Тот самый, который поймал шум нагрузки. Восемь прогонов вместо трёх, и на тяжёлом отчёте это уже не пять минут.

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

И заказчик платит за оптимизацию, а доказательство её корректности идёт бесплатным приложением.

 

Открытый вопрос

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

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

Одним прогоном тоже не обойтись: у нас один и тот же запрос в разных прогонах гулял от 28 до 65 секунд. "Померить один раз, но внимательно" на живом проде не работает.

Вопрос, на который у меня нет ответа: чем доказывать эффект на отчёте, который физически нельзя прогнать восемь раз? Планом запроса и счётчиками чтений вместо секунд? Тогда вы доказываете, что стало меньше работы, а не что стало быстрее, и это другое утверждение.

 

Другие наши инструменты диагностики 1С:

Вступайте в нашу телеграмм-группу Инфостарт

Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».

  • 0% комиссии — оплата напрямую исполнителю;
  • Исполнители любого масштаба — от отдельных специалистов до команд под проект;
  • Прямой обмен контактами между заказчиком и исполнителем;
  • Безопасная сделка — при необходимости;
  • Рейтинги, кейсы и прозрачная система откликов.

См. также

HighLoad оптимизация Программист 1С 8.3 1С:ERP Управление предприятием 2 Бесплатно (free)

Использование оператора «В» для полей или данных составного типа (например, Регистратор) может приводить к неочевидным проблемам.

10.11.2025    13633    ivanov660    48    

56

Нейросети Рефакторинг и качество кода Тестирование QA Программист 1С:Предприятие 8 Бесплатно (free)

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

11.03.2025    15806    mrXoxot    53    

58

HighLoad оптимизация Программист 1С:Предприятие 8 1C:ERP Бесплатно (free)

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

18.02.2025    14739    ivanov660    39    

62

Тестирование QA Программист 1С:Предприятие 8 Бесплатно (free)

Чтобы обеспечить высокое качество тиражной конфигурации 1С, ручного тестирования недостаточно – нужно учесть множество комбинаций функциональных опций, группы доступа и влияние подсистем друг на друга. Расскажем о промышленном тестировании флагманского продукта 1С:ERP и его дочерних конфигураций.

31.01.2025    18567    Pr-Mex    69    

52

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

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

24.06.2024    17254    ivanov660    13    

64

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

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

13.03.2024    13134    spyke    29    

54

HighLoad оптимизация Инструменты администратора БД Системный администратор Программист 1С 8.3 Абонемент ($m)

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

10 стартмани

15.02.2024    24337    412    ZAOSTG    115    

132
Для отправки сообщения требуется регистрация/авторизация