Платформа, яка пережила все
8 / 21

Розділ 7. Сорок сім хвилин

Частина II · Розділ 7

Сорок сім хвилин

1

Юрій сам мені написав. Я опублікував у LinkedIn нотатку про те, що пишу книгу про 1С, і за два дні в особистих повідомленнях впала пропозиція: «Якщо хочете подивитися, як виглядає реальна оптимізація — приходьте у вівторок на Святошин, у мене аудит у продуктового ритейлера, я там до вечора».

У вівторок о десятій ранку я знайшов адресу. Невисока сіра будівля за кільцевою, перший поверх — склад готової продукції, другий — офіс ритейлера, який тримає тридцять магазинів по Києву і Київській області. На третій поверх — сходи з облупленим лаком, на дверях табличка «АХО. ІТ. БУХГАЛТЕРІЯ». Я зайшов.

Усередині — типовий пострадянський опен-офіс, поправлений ремонтом два роки тому: підвісна стеля, ламінат, столи з ЛДСП, на стінах — мотиваційні плакати «Сьогодні — найкращий день для росту» поруч із календарем зі ЗСУ. У дальньому кутку, біля скляної переговорної, сидить чоловік за двома моніторами. На першому — Конфігуратор. На другому — щось чорне з білими рядками тексту, який повільно прокручується.

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

— Денис? Привіт. Сідай отут, мені треба ще десять хвилин дозбирати лог.

2

Я сів поруч. На другому моніторі — той самий чорний екран з білими рядками. Я придивився.

Кожен рядок виглядав приблизно так:

10:23:47.213 EXCP, process=rphost, p:processName=MainDB, ConnectID=4521, Context='Документ.РеализацияТоваров.Модуль.ОбработкаПроведения; Цикл:13', Duration=4287, t:clientID=234

— Це що?
— Технологічний журнал. Налаштував фільтр на запити довше трьох секунд. Працює з ранку, поки я каву пив. За дві з половиною години у логу — сорок дев'ять записів. Тридцять сім із них — з одного контексту. З одного циклу проведення документа. Який рутинно проводиться по сотні разів за день.

— Скільки часу займає один такий цикл?
— Зараз скажу.

Він повернувся до Конфігуратора, відкрив якийсь модуль, натиснув запуск. Внизу екрана з'явився таймер.

— Цей самий звіт у клієнта зараз виконується за сорок сім хвилин. Перевіряли. Закриття місяця — три з половиною години. Він на нього чекає кожне перше число. Я приїхав, бо вже не витримує.

3

— А клієнт думає, що в нього з чим?
— Клієнт думає, що в нього мало серверів. Хоче купити новий. Я приїхав, щоб сказати йому: серверів у тебе достатньо. У тебе запит у циклі. Завжди було.

Юрій сказав це тоном людини, яка повторювала ту саму фразу тридцять разів за рік.

— Запит у циклі — це що?
— Це гріх номер один у нашій професії. Я тобі покажу.

Він відкрив модуль документа «Реалізація товарів». Прокрутив до процедури ОбработкаПроведения. Розгорнув блок коду, в якому обчислювалася ціна за номенклатурою — це і був той самий цикл, на який скаржився технологічний журнал.

На моніторі — приблизно такий код:

Для Каждого Стр Из Документ.Товары Цикл

    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
    |    Цены.Цена
    |ИЗ
    |    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата) КАК Цены
    |ГДЕ
    |    Цены.Номенклатура = &Номенклатура";

    Запрос.УстановитьПараметр("Дата", Дата);
    Запрос.УстановитьПараметр("Номенклатура", Стр.Номенклатура);

    Результат = Запрос.Выполнить().Выбрать();
    Если Результат.Следующий() Тогда
        Стр.Цена = Результат.Цена;
    КонецЕсли;

КонецЦикла;

— Бачиш, що тут не так?
— Ну... запит у циклі, як ти сказав. Запит виконується на кожному рядку табличної частини.

— Точно. У цьому документі — у клієнта — табличні частини бувають по двісті, по триста рядків. Один великий — півтори тисячі. Для кожного рядка — окремий запит: відкриття контексту, передача параметрів, парсинг, виконання, повернення. Одна ціна — одне сходження в БД. Помножай на півтори тисячі. Це і є твої сорок сім хвилин.

4

— А як треба?
— Треба один запит на всі рядки. Збираєш ключі — у цьому випадку, перелік номенклатур з табличної частини — передаєш у запит як параметр-масив, отримуєш одразу всю таблицю цін, складаєш у пам'ять. Потім у циклі тільки забираєш із пам'яті.

Юрій відкрив порожній модуль праворуч, почав набирати. За хвилину на екрані з'явився переписаний код:

МассивНоменклатуры = Документ.Товары.ВыгрузитьКолонку("Номенклатура");

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Цены.Номенклатура,
|    Цены.Цена
|ИЗ
|    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
|        &Дата, Номенклатура В (&МассивНоменклатуры)) КАК Цены";

Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);

ТаблицаЦен = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    ТаблицаЦен.Вставить(Выборка.Номенклатура, Выборка.Цена);
КонецЦикла;

Для Каждого Стр Из Документ.Товары Цикл
    Стр.Цена = ТаблицаЦен.Получить(Стр.Номенклатура);
КонецЦикла;

— Виглядає так само, тільки навиворіт.
— Точно. Перший варіант — півтори тисячі запитів. Другий — один. Перший — сорок сім хвилин. Другий — десь дванадцять секунд. Або п'ятнадцять. Не міряв точно, бо до десяти секунд уже все одно.

Він зберіг зміни, виставив базу в тестовий режим, запустив той самий звіт.

Я чекав. Двадцять секунд — закінчився. Юрій не здивувався. Я — здивувався.

Тричі знайшов цей блок у двох тисячах сорока чотирьох рядках коду. Тричі замінив, — сказав він спокійно, — і клієнт зекономив три з половиною години на тиждень бухгалтерського часу. Це проблема, з якою я живу: ця оптимізація — дванадцять рядків написати і десять рядків викинути. А вона змінює світ клієнта.

5

— А чому так пишуть? — спитав я. — Це ж очевидно — запит у циклі.

Юрій трохи замислився.

— Тому що це не очевидно для того, хто це писав. Той, хто це писав — переважно той самий франчайзі, який сидів у клієнта десять років тому. Він знає 1С на рівні «зробити так, щоб працювало». Він не вчився в університеті бази даних. Він не знає, що таке план виконання запиту. Йому показували на курсах: «ось так пишеться запит». А скільки разів його запускати — це його особиста стилістика. Він обере звичне.

— А коли база була меншою?
— Коли база була меншою — це працювало. Сто рядків у документі — сто запитів за пів секунди. Ніхто не помічає. База росте — час росте. Через п'ять років — годинами. Ось тут приходжу я.

— Так у нього в коді ще скільки таких циклів?
— Я нарахував сімнадцять. У цьому модулі — три. У модулі менеджера документа — чотири. У зовнішній обробці звіту — три. У підписці на подію — два. У регламентному завданні — п'ять. Усе одне і те саме. Усе виправляється однаково. Я зараз закінчу один — клієнту скажу. Він мене найме на наступний тиждень — лагодити решту.

Він зробив паузу.

— Це бізнес-модель, — додав. — Я не приходжу один раз. Я приходжу регулярно. Кожен мій клієнт — це база, яка повільно росте з кожним документом, поки не лопне. Тоді мене кличуть. Я знов лагоджу. Через рік — знов. У клієнта це називається «Юра приїхав».

6

— Розкажи мені інші класичні помилки. Які я мушу написати у книгу.

Юрій налив собі ще кави з термоса.

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

Перше — запит у циклі. Уже бачив. Пів години розмови — на нього.

Друге — звертання по крапці до реквізитів через посилання. Коли ти пишеш Документ.Контрагент.НоменклатураЗаДомовленістю.Наименование, кожна крапка — це окремий запит до БД. На один документ — три запити. Здається, що ти просто навігуєш у пам'яті. Насправді платформа за кожну крапку лізе в базу. Вирішення — або підняти ці поля у первинному запиті, або зберігати у локальну змінну.

Третє — Виборка.Следующий() замість Запрос. Виборка — це послідовний прохід через довідник або документи. Вона не використовує плани, не оптимізується, тягне всі поля. Для двох-трьох записів — нормально. Для тисячі — катастрофа. Замінювати на Запит із вибіркою тільки потрібних полів.

Четверте — звертання до фізичних таблиць регістрів замість віртуальних. Регістр накопичення має дві репрезентації: фізична таблиця рухів (усі записи) і віртуальні таблиці підсумків: Остатки, Обороты, СрезПоследних, ОстаткиИОбороты. Підсумки агреговані заздалегідь, читаються миттєво. Рухи — повільні при великих об'ємах. 1С створювала віртуальні таблиці саме для цього. Новачки про них не знають і пишуть ИЗ РегистрНакопления.Движения — і отримують повільність на рівному місці.

П'яте — відсутність індексів. Платформа підтримує індекси за реквізитами і індекси з додатковим впорядкуванням. Якщо ти часто фільтруєш по реквізиту Контрагент у документі — постав на нього індекс через метадані. Без індексу — повне сканування таблиці на кожен запит.

Шосте — неправильні замки. Документ при проведенні блокує регістри, з якими працює. Якщо ти забув виставити рівень керованих блокувань — отримаєш ескалацію блокувань на цілі таблиці. Два бухгалтери проводять документи паралельно — і блокують одне одного на хвилини. У логах — «таймаут блокування». Лагодиться правильним виставленням явних блокувань.

Сьоме — нестандартні запити замість стандартних об'єктів. Платформа має готовий механізм, наприклад, для отримання заробітної плати: РегистрРасчета.НачислениеЗарплаты.ПолучитьДанные(). Хтось пише замість цього свій запит на двісті рядків. Свій запит працює — але повільніше, ніж стандартний механізм, який платформа оптимізувала десять років.

Він замовк.

— Можеш ще десять написати, але вже з меншою частотою. Сім — це класика.

7

— Філософське питання, — сказав я. — Чому ці помилки роблять? Не тому що люди дурні. Тоді чому?

Юрій довго мовчав. Я звик, що мої співрозмовники довго мовчать на філософські питання.

— Тому що немає вхідного фільтру.

Він пояснив.

— У більшості мов програмування, щоб писати, треба пройти певний поріг. Java — треба знати ООП, JVM, типи. Python — треба знати модулі, генератори, exceptions. PHP — Laravel чи Symfony — треба знати MVC, паттерни. У всіх цих стеках між новачком і продакшеном стоїть якась освіта: університет, буткемп, наставник.

— У 1С — поріг майже нульовий. Курс на місяць, тиждень практики, ти вже сидиш на проекті. Бо платформа все робить за тебе. Метадані — створюються кліком. Форма — генерується. Звіт — компонується. Тобі здається, що ти пишеш сорок рядків коду — а ти за лаштунками рухаєш цілий рантайм рівня операційної системи.

— Це прихована складність. Її ніхто не пояснює. Тому що платформа продавалася як «доступна навіть для бухгалтера». І це правда — поки база маленька. Коли база виростає — прихована складність вилазить назовні. І тут раптом виявляється, що ти ніколи не вчив бази даних. Тебе не вчили. Платформа здавалася такою, що цьому вчитися не треба.

— Це — головна проблема культури 1С. Низький поріг входу — це й сила, і прокляття. Сила, бо економіка ринку — рясна. Прокляття, бо якість коду — у середньому — посередня. Хто помітив це — той росте у ціні. Як я.

8

— Скільки коштує твоя година?

Юрій усміхнувся.

— Менше, ніж DBA в банку. Більше, ніж пересічний 1С-розробник. Десь у три рази вище за середнього 1С-програміста, удвічі нижче за DBA з SQL Server-сертифікацією. Тобто моя ставка — це індикатор того, що я рідкісний у цій ніші. Не тому, що мене краще навчили. А тому, що мало хто з 1С-розробників захотів вчити бази даних окремо. Я захотів. Я лагоджу.

— Як ти бачиш своє майбутнє?
— Постійно зайнятий. Поки в Україні є 1С/BAS — є мережа клієнтів, які мене викликатимуть кожні п'ятнадцять-вісімнадцять місяців. У мене черга бронювань на півроку вперед. Я не займаюсь маркетингом. Я не маю сайту. Усе по сарафану.

— А якщо одного дня BAS зникне?
— Я перейду на PostgreSQL і Odoo. У мене основні навички — реляційні бази даних. 1С — це поверхневий шар. Мені без них теж добре.

Він зачинив ноутбук.

— Слухай, мені треба до клієнта йти. Я зараз пишу йому мейл з результатами першої ітерації. Тобі — успіху з книгою. Якщо хочеш, я тобі ще пришлю кілька прикладів. У мене папка «класика» — там скріншоти найжахливіших речей, які я бачив. Документ, який блокує всю систему на чотири години. Звіт, який падає з помилкою «недостатньо пам'яті» з якогось двадцятого року. Регістр, у якому півмільйона рухів без жодного індексу. Якщо в книзі знадобляться приклади — кажи.

9

Я вийшов з офісу на Святошині близько другої. На вулиці потеплішало, дерева вже розпустили листя. У дворі грали діти — мабуть, з сусіднього дитячого садка.

Я думав про одну фразу, яку Юрій кинув мимохіть: «низький поріг входу — це й сила, і прокляття».

Це, мабуть, найточніше формулювання економіки 1С, яке я чув за весь час роботи над книгою. Платформа, у якій будь-хто, хто пройшов місячний курс, може писати продакшен-код, — це чудо доступності. І це катастрофа якості. Те саме залізо, яке посадило мільйон бухгалтерів за комп'ютери, — посадило і мільйон програмістів, які не знають, що таке план запиту.

Кожна повільна база у країні — це не баг платформи. Це закладена ціна її доступності. Хто платить цю ціну — клієнти, які чекають по сорок сім хвилин. Хто на ній заробляє — Юрії, які приходять і лагодять. Хто її не помічає — більшість самих розробників, які пишуть і йдуть далі.

У наступному розділі я поговорю з людиною, яка пройшла ще один поріг — не поріг входу в платформу, а поріг масштабу. Що відбувається, коли база росте з ста користувачів до тисячі. Коли клієнт у Львові і сервер у Києві. Коли треба раптом мобільний клієнт для двохсот водіїв-кур'єрів. Це наступна історія — про чотири рівні платформи.