Історія про Singleton

Введення

Якось місяць тому я обіцяв моїм шановучам написати історію про один Singleton, який завалив завод.

Завод виробляв одну одиницю продукцію кожні 90 секунд. Вартість товару коливалась між 10 000 та 45 000 доларів.

Тобто, півтори хвилин простою коштували мінімум 10к доларів.

Початок кінця

Справа була дуже давно, ще коли на ДОУмийці були відкриті коментарі і було свавільна воля. Один з моїх колег наярював на джаві, з себе корчив мега архітектора (це вроджений недолік всіх джавішнутих).

Якось я помітив у нього книгу “101 патерн для Java”, чи щось в такому дусі. Всі 800 сторінок були лістінги якихось патернів на 5 скролів. В джаві ж щоб відкрити файл треба заюзати 5 патернів, 7 класів, 2 інтерсептора і все одно всраться, бо без спрінга уже жоден джавіст не здатен вичитати перший рядок файлу. Я у нього спитав, навіщо в 20ХХ році купляти паперові книги, на що отримав відповідь “я не понимаю, когда читаю с екрана” (піздец уже сам по собі).

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

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

Що мені запам’яталось, в момент, коли він закрив тікет, на його столі лежала ця книга з купою поміток на абзаці про Singleton. Ну думаю, молодець, що учиться. Я тоді наярював на Clojure, тому ці всі патерни були до сраки. Як і зараз :)

Проходить пів року

Я і інші колеги висаджуємося десантом до замовника в США. Будемо робити go-live продукту. Наш продукт являється ключовим: весь конвеєр залежить від працездатності нашої системи контролю виробництва. Тобто, якщо наша частина не дає добро, то весь конвеєр стоїть. На тому заводі було 140 робочих місць, багато з них спарені, тобто десь 200 загалом.

Go Live

Перша ластівка

Десь на третій день після тестових ранів, почали звітувати про помилки нам. Так як працювали в великій корпорації, то задача кожного розробника була звалити тікет або багу на інший підрозділ, а самим завжди залишатися сухим. Як завжди, в таких ситуаціях, лякали, що запуск GoLive іде на особистому рахунку нашого CEO в Америці і особисто CFO замовника.

Це тоді ще мене лякало, а зараз не дуже, якщо чесно. Стільки разів це все було “борд особисто контролює”, потім виявляється, що всім насрать десь на рівні середнього менеджменту як у нас, так і у кастомера.

Помилка

Помилка була в тому, що на станції, де закручують болти, інколи наша система видавали дивні значення, які не відповідали дійсності. Мало закрити на 4.5 оберти, а показує 3. І тому не дає далі рухатися конвеєру. Звісно почали валити все на чуваків, які робили ПЗ для станків.

Там був повний колхоз на .NET, вони все слали завжди як GET, навіть якщо якісь дані треба було відіслати…ще і стрінгою “4.5”…Ще і без будь-який логів, чи варіанту як глянути, що ж там станок послав насправді.

Але головна проблема була в тому, що це траплялось раз в день. Інколи не траплялося взагалі.

Іду в засаду

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

Стояв я так три години, паралельно дивлячись логи на всяк пж. Тут хопа, робочий клас почав махати руками і кликати мене.

Забігаючи наперед скажу, що діти Леніна ненавиділи нас всім серцем. Деякі у відкриту сказали, що нас відпиздять на заводі прямо, якщо ще раз зірветься їх зміна. Бо якщо простій більше 45 хв, то оголошується про закриття зміни і всіх розпускають додому, а зп платять там по факту роботи в приміщенні...Також врахуйте, що робочі в США це як в Ковбої Марльборо типажі - кожен другий качок з ножем або стволом. Кому цікаво, то напишу історію про цікавого амера, як він проніс ствол на завод в столовку.

Я підхожу - хопа! Болт недокручено, хоча робочий божиться, що все ок було, болтокрут показав 4.5 у себе на датчику, а у нас показує 3.

І ТУТ МЕНІ ЯК ПО ГОЛОВІ ДАЛО

БОЛТОКРУТ БУВ ТАНДЕМНИЙ

Тобто, одна гашетка закручувала одразу два спарених болта (мабуть, щоб не перекосило деталь і ідентична синхрона сила була прикладена).

Мене почав бити холодний піт, я став відходити назад до свого лептопа.

Два болта синхроно.

Синхроно.

Виклик нашого REST API синхроно…

Сервак процесить все за 2мс…в найгіршому випадку (це було до ваших сраних мікросервісів, де щоб щось зробити треба 7 інших рестів послати з сервака на сервак, замість одного запиту в бд).

І тут як флешбек з В’єтнаму перед очима сторінка “101 Патерн Джави” з відкритою сторінкою Singleton.

Я зібрав лептоп і чкурнув до Бойової кімнати. Це було кодло всіх інженерів, штук 10-15 завжди сиділо. Чомусь, коли треба зробити реальну роботу, завжди забивають на всякі скрами, отжайли, вигоняють всіх скам мастеров і не заважають інженерам працювати, тому я любив бути у відділі кастомізацій для замовників).

Швидко поділився побаченим з колегою. Вона швидко сіла дебажить і дивиться, що може статися з синхроним викликом сервіса з двох місць.

Сам на SOAPUI швидко варганив скрипт для відсилки 100 результатів болтокрутів на 20 різних операцій.

Через 10 хвилин почув вердикт: “Єто Піздец”.

В чому сіль

Автор функціоналу заюзав Singleton для входу в обробку результату болтокрута. Дані сетав в проперті, щоб зручніше було. Якщо одночасно заходило декілька запитів, то хто куди що запише ставало не ясно, адже попередній запит ще міг не відпрацювати, а наступний уже загадив проперті, тому комплітер першого реквесту починав срати дані не для потрібної операції.

Тобто, Болтокрут #1 має закручувати на 4.5 оберти. А болтокрут #2 - на 3. Тому, коли декілька таких тандемних болтокрути крутили гайки, то вони збільшували вирогідність, що якийсь із запитів прийде одночасно з іншим запитом з іншої станції, де працював інший болтокрут. І що виходило, на специфічних мотоциклах, де треба були оці тандемні закрутки, дуже сильно збільшувався шанс, що робочі пошлють дані в один момент часу. Тому бага і стріляла раз в пару днів…

Ми швидко це перевірили на моїх SOAPUI скриптах (до речі, кльова тула). Ішуя репродюсалась навіть на 5 синхроних запитах. Результати болтокрута залітали куди попало, рандомом взагалі по цих 5 операціях.

Полегшення

Швиденько підлатали процесор запиту, я перевірив на дев серваку (тоді не було клаудів, все можна було запускати локально за 5 хв). Ішуя зникла! SOAPUI тести всі проходять!

Попросили вікно для деплоя, нам його дали.

Після цього ішуя зникла назавжди. Через 3 дні нарешті цю вері хай імпакт ішую закрили.

Вердикт

А от юзав би Functional Programming, все було б добре! Ніяких там засиронів даних чужими викликами функцій. Прийшли дані - засунув їх як є, immutable гарантує, що ніхто не загадить твої дані з інших процесорів на сервері.