Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

16.02.2023

Чому в Go є тип string та byte[]

Сьогоднішній баг стосувався того, що в Go у змінну з типом string передавали ззовні значення nil. (Там вхід з кодеком, що побудований на рефлексії, тож таке технічно можливо.) Виправилось це заміною типу на *string, який може мати або значення nil, або вказувати на рядок. Наскільки я знаю, доволі ідіоматичне рішення. Але воно наштовхнуло на роздуми — якщо в Go рядок це технічно слайс байтів, а слайси можуть бути nil, то чому рядки не можуть? Моя відповідь — напевно, щоб абстракція рядків була красивішою, тобто щоб рядки виглядали більше як “примітивний” тип.

Ось інші нюанси роботи з типами string та byte[]. Конверсія з рядка в слайс не безплатна, на відміну від інших. Операції string(someBytes) та []byte(someString) створюють копію даних. Це потрібно тому, що рядок — тип незмінний, тому не може розділяти памʼять зі змінним типом — слайсом. При цьому є багато API, які приймають тільки слайси або тільки рядки, тож деколи конверсії не уникнути. В мене був навіть випадок, коли рядкову функцію довелось переписати для слайсів, щоб прискорити вузьке місце. Ще, наприклад, функції форматування приймають обидва: можна написати fmt.Printf("contents of file are: %s", byteSlice).

Можна подумати, що рядки мають якусь семантику символів, тобто кодування. Але ні — в Go рядки складаються з байтів. Довжина вимірюється в байтах, позиції теж. Є один нюанс — цикли з range перебирають символи UTF-8. Це єдине місце, де я знаю, що рядки набувають кодування. Бо якщо рядок має некоректні символи, то такий цикл їх зіпсує. Або я майже впевнений, що деякі комбінації кодів він буде нормалізувати. Одним словом, краще бути обережним.


15.02.2023

Історія сьогоднішнього багу

Тільки закінчив випускати хотфікс. Випускати щось в продакшн вночі — завжди цікаво, та завжди з історією. Що ж трапилось на цей раз?

Баг зʼявився у надсиланні пошти. Його помітили ще на стейджингу — чомусь листи не відправлялись з помилкою SMTP 503 5.5.1 Error: authentication not enabled. Але на стейджингу принаймні я багато уваги цьому не приділив, бо було більше схоже на проблеми з налаштуваннями середовища, ніж на реальну проблему. Але ж на продакшні баг відтворився, та почав перешкоджати надсиланню чесних користувацьких листів. Довелося терміново розвʼязувати.

Перше питання — що взагалі значить ця помилка? З мого досвіду, аутентифікація SMTP потребує шифрування, тобто STARTTLS. Бо як я вже писав, інакше пароль зможуть підгледіти по дорозі. Тож інтуїція каже, що насправді проблема у тому, що клієнт не надсилає команду STARTTLS. Шукаю параметри підключення та знаходжу: enable_starttls_auto: false. Так, це дуже схоже на причину. Але цей рядок зʼявився в проєкті роки тому. Як трапилось, що поведінка змінилась зараз?

З нещодавніх змін до проєкту підозрою є оновлення Ruby on Rails. Оновлення мінімальне та стосується тільки патч-версії, але все ж таки це глобальна зміна, яка теоретично може вплинути на будь-що. А конкретно, при перегляді різниці файлу Gemfile.lock бачу, що помінялася також версія гему mail. Його має сенс перевірити в першу чергу.

І нарешті, в переліку змін гему mail бачу цікавий рядок “Bug Fixes: Regression: accept enable_starttls_auto: false”. Знайшов! (Окреме питання, що написано, що вона unreleased, хоча вже 2 тижні як released. Але буває.)

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

Що робити, щоб такого не було? Напевно, головне, це підтримувати стейджинг в повністю робочому стані, як пріоритет №1. Щоб впевнено шукати помилки, навіть якщо вони не повʼязані очевидно з поточними змінами.


14.02.2023

Інкрементальні розрізи подій у Redshift

Нарешті сьогодні розкусив дві проблеми, що мене турбували по Redshift.

Перша — як я вчора писав, події краще комбінувати… логічною вершиною такого підходу буде взагалі позбавитись від різних потоків для типів подій та звалювати все в одну велику таблицю. Далі це дозволить нам згрупувати таблицю по спільному ключу, та агрегувати результат. Головне, щоб агрегація підпадала під інкрементальне оновлення. Бо тоді ми звільняємося від необхідності сканувати всю таблицю, а будемо завжди працювати тільки з новими подіями — це виводить здатність масштабування на новий рівень. Обмеження для інкрементального оновлення досить суворі, але коли всі події в одній таблиці, то можна робити цікаві речі, наприклад: COUNT(NULLIF(event_type = 'purchase', false)) AS purchase_count.

Друга — була така задача, щоб в межах GROUP BY взяти деяке поле з найсвіжішої події. Визначити час цієї події легко - MAX(created_at)… але ж агрегаційні функції працюють в межах стовпчика, тому інші стовпчики за таким запитом не знайдеш. Винайшов трохи хитрий підхід. Якщо інше поле — то рядок, то можна додати до нього час події як префікс. Час має бути в форматі з лексикографічним порядком — наприклад, ISO8601. Тепер можемо знайти останнє за часом значення будь-якого стовпчика - SUBSTRING(MAX(prefixed_item_id), 24). Як бачите, до результатів агрегаційних функцій можна застосовувати інші функції, та це не зашкодить інкрементальному оновленню.


13.02.2023

Трохи уроків з Event Sourcing на Redshift

Трохи уроків з так званого “event sourcing”, що для мене значить, архітектури, де ми, замість того, щоб редагувати записи в базі, зберігаємо кожну зміну стану як маленький, незмінний запис - “подію”. А потім з сукупності подій генеруємо поточний стан даних. (Наочний приклад — це реєстр операцій в магазині, підсумувавши які, можна отримати кількість грошей в касі.)

Мене на event sourcing підштовхнув сервіс AWS Kinesis Firehose. Бо він пропонує ну дуже спокусливу властивість — практично необмежений вхідний обсяг. Скільки не пиши, все візьме, складе в пачки, та відправить далі в базу, наприклад, в Redshift. А вже у Redshift доведеться виконати перетворення, щоб зібрати події в загальну картину. Можливо, це будуть матеріалізовані розрізи.

Так от, нарешті, до уроку. Події краще комбінувати в найбільші можливі. Тобто якщо за одну операцію генерується послідовно три події, що стосуються одна одної — краще зробити одну велику подію. Причина в тому, що кожна операція зʼєднання вам коштуватимете. Якщо зʼєднань в запиті багато, наприклад, десять — планувальник Redshift починає заплутуватись. Особливо, якщо ти знаєш, що треба поєднати події, які мають відповідність “одна до одної”. Ми-то знаємо про це, а Redshift - ні. Тому, якщо є можливість, краще відразу єднати в одну подію, а відповідно — й таблицю.

До речі, автоматичне оновлення — потужна функція Redshift - неможлива для розрізів, що залежать від інших розрізів. Тобто каскаду розрізів не вийде, принаймні, без побудови додаткового механізму оновлення.

Та ще до речі — якщо вже треба єднати в одну десять таблиць, може статись, що UNION ALL ... GROUP BY працюватиме значно ефективніше, ніж купа JOIN. Варто погратись.


12.02.2023

Коли розмір даних має значення?

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

Але також компактність даних має значення при побудові жетонів. Коротший жетон — завжди краще. Інколи ми робимо з них QR-код; тут чим більше зміст — тим більше деталей матимете QR-код, та тим складніше буде його прочитати. Тому дуже важливо тримати пакет даних для коду якнайкоротшим; брати не JSON, а компактне двійкове подання. Так само важливо тримати короткими жетони для URL-адрес - бо довгі адреси мають ризик бути зіпсованими браузером або мережевою інфраструктурою.

Ну та якщо мова зайшла про подання двійкових даних, то треба памʼятати, що найбільш компактний текстове подання — це Base64; він у півтора раза краще, ніж формат Hex. Для URL-адрес, до речі, є своя варіація - base64url - щоб не кодувати додатково символи + та / як три символи відсоткового кодування. Та, в решті решт, якщо дані не треба передавати текстом — краще над усе залишити їх у двійковому вигляді — це економія у 33%, порівняно з Base64, та у 50%, порівняно з Hex.


11.02.2023

Пост про компактне подання даних

Пост про компактне подання даних. Варто уявляти, яка оптимальна форма для кожного типу даних, та скільки місця він займає. Це я кажу як людина, яка в школі зберігала піксельну графіку в текстових файлах: 0 1 15 1 0. Якщо зберігати числа у тестовому файлі здається безглуздим, нагадаю, що формат JSON був би ще менш ефективним. Це свідчить про те, що сучасні програмісти не звикли думати, скільки займають їх дані. Та це чудово — але деколи доводиться. Наприклад, на масштабі — якщо умовний запис буде повторюватись мільярд разів, то марна трата одного байту вже виллється в втрачений гігабайт.

Перший урок з JSON - не треба зберігати дані в рядках тексту. Очевидно, для чисел є числові типи. Але є чисельні типи даних, які теж влазять в число. Наприклад, дати. Або IP адреси. Або кольори. Також часто можна обрати не найбільший числовий тип - тобто менш ніж 64 біти. Для булевих значень достатньо одного байту, для IP-адрес - чотири. Також ми звикли відображати двійкові рядки як текст, у вигляді Hex або Base64 - можна заощадити, якщо цього не робити. Наприклад, UUID замість 36 байтів тексту влізе в 16 байтів двійкових даних.

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

Окремо про дати, я колись непогано виграв на використанні памʼяті у додатку на JavaScript, коли почав зберігати дати як числа, та створювати обʼєкти тільки за потребою. Це тому, що число — набагато компактніша форма.

А ще число — не завжди найкомпактніша форма подання. Мій улюблений приклад — зберігання множини чисел у вигляді бітової мапи. У такій формі біт в розряді N позначає, чи не міститься N в множині. Так можна досягнути економії місця в 32 рази. Не кажучи про те, що операції над бітовими мапами набагато швидше… але про це розкажу іншим разом.


10.02.2023

Розклеювач проєктів

Сьогодні півдня не було світла та звʼязку. Займався розгрібанням старих нотаток у Drafts - з 2021 року їх накопичилось більш ніж 200. Ось чому так важливо вичищати вхідні нотатки щодня! Важко згадати контекст думки, що трапилася рік тому. В одному випадку думав, що записана назва лікарської рослини, а насправді виявилось, що це прізвище рекомендованого стоматолога (спасибі Гуглу та базам лікарів!)

Проміж нотаток знайшов ідею для минулої системи самоменеджменту. Кодова назва “розклеювач проєктів”. Зміст: це як бізнес-канва, але для особистих проєктів, які ніяк не хочуть зсунутись з місця. Це для таких діл, на які дивишся тижнями чи навіть місяцями, але не наважуєшся робити. Розклеювач складається з девʼяти питань в трьох групах — можна розкреслити аркуш як в хрестики-нулики, а можна просто записати в стовпчик. Отже, ось питання:

Що саме потрібно зробити? Як зрозуміти, що мета досягнена? Що зміниться? Який конкретний вигляд результату?

Чому це потрібно робити? Яка мотивація? Можу порадити підхід “пʼяти чому”, тобто заглиблюватись в причини, щоб привʼязати проєкт до чогось важливого.

Чому це варто НЕ робити? Контрінтуїтивно, але факт — багато проєктів ми ніколи не зробимо. Може, є привід не робити цей?

Складнощі. Які аспекти відразу викликають стрес? Може, ти відштовхуєш цей проєкт, бо він неприємний? Чому саме?

Ідеї. Власне, сюди записати цікаві ідеї, які хотілося б випробувати. Може, варто собі нагадати, що саме запалило інтерес, коли проєкт був поставлений.

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

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

Ресурси. Які фізичні предмети будуть залучені? Що потрібно купити? Чи не заблокований проєкт нестачею інструментів, чи інгредієнтів?

Знання. Які знання та навички потрібні для розвʼязання задачі? Чому потрібно навчитися? Яку книжку прочитати?

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


09.02.2023

Як приховати ключі AWS та SSH у 1Password

Сьогодні пост про безпеку. Напевно, всі знають, що паролі треба мати довгі, випадкові, та зберігати їх у менеджері паролів. В мене вже 12 років всі паролі лежать у 1Password. Правда, тоді це був приємний додаток для Mac та iOS, а зараз він перетворився в всеплатформенний гігант з фокусом на підприємства. Додаток трохи зіпсувався, чесно, але натомість 1Password набув більше функцій, а в авторів є достатньо інвестицій, щоб підтримувати безпеку. Та це головне - 1Password найбільш надійний менеджер паролів (кажу як людина, яка вивчала їх модель безпеки).

Як одна з функцій — можливість інтеграції в термінал. Утиліта op вміє витягувати зі сховища паролі та підставляти куди треба. А деякі системи авторизації підтримує напряму. Отже, сьогодні нарешті переніс свої ключі AWS теж у 1Password. Для AWS є плагін, який замінює команду aws на таку, що може забрати ключ з 1Password. На жаль, це працює тільки з командою aws - тобто, наприклад, Terraform ці ключі не знайде. Але в цілому треба розуміти, що як Terraform може знайти твій пароль від AWS, так його може знайти будь-який скрипт — наприклад, скомпрометований пакет NPM.

Також 1Password може виступати як агент авторизації для SSH. Це працює з будь-яким клієнтом SSH, тобто з командою git теж. Замість вводу фрази-пароля доведеться відімкнути сховище та підтвердити використання ключа. До речі, 1Password чомусь не дозволяє зберігати недостатньо надійні ключі. Трошки дивний крок, але немає нічого поганого в тому, щоб перейти на свіжіші стандарти безпеки — наприклад, Ed25519, який я вже бачив у WireGuard.


08.02.2023

Документація по проєкту

Де тримати документацію по проєкту? В мене похмуре передбачення, що як і з системами для керування задачами, тут не знайдеш “приємного” варіанта, бо що не бери — все одно доведеться ту документацію писати. Але все ж дам кілька порад.

Перше та найголовніше — я особисто не довіряю документам, які написані не просто в тексті. Напевно, то інженерна профдеформація. Я розумію, що всі на світі користуються Microsoft Word та всіма його нащадками. Але якщо справа стосується живої проєктної документації, то її я хочу бачити тільки в Markdown. Тільки так можу буту переконаним, що документи можна буде без проблем переносити між інструментами та середовищами, особливо коли вони застаріють та зʼявляться нові. Також, як для мене, просто текст легше редагувати та форматувати. Але одним текстом не обійтися, та треба мати можливість додавати ілюстрації.

Документацію має бути легко писати, бо написання — це вже робота, та до того ж побічна. Тож додати нотатку має бути можливо з мінімумом зусиль. Цю цінність я зрозумів на важкому досвіді, що щоб створювати на кожну зміну Pull Request - ні в кого немає часу. Взагалі Git в прямому вигляді незручно використати — бо треба робити commit, а потім push, а потім може ще й конфлікти вирішувати.

Документацію має бути легко шукати, це не менш важливо. Зміст та навігація — це добре, але на практиці чим більше напишуть (що добре!), тим складніше підтримувати штучну систему організації. Краще, коли можна шукати нотатки за текстом, а також за тегами.

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

Документація має бути доступною не тільки інженерам — інакше буде “сіра зона” спільних документів, які житимуть в іншому місці. Наприклад, інструкції для підтримки. Та й взагалі краще не відокремлювати “інженерну” документацію, а тримати все в одному місці.

Ще дуже корисно мати живе редагування документації, щоб додавати нотатки під час нарад та дзвінків.

Який інструмент все це підтримує? Я теж хотів би знати. :)


07.02.2023

Як оптимізація по стеку гуляла

Розповідь про те, як оптимізація по стеку гуляла. Ця історія почалась три тижні тому. Задача стояла покращити швидкодію API (макро)сервісу, тож в таких межах вона й була вирішена. API дійсно став швидше. Про це в минулому пості.

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

Тому пішов далі. Хотів перенести оптимізацію до сервера, що споживає цей API. Це було б досить очевидно. Але якщо відстежити шлях виклику ще далі, то первинне джерело виклику — фронтенд. Тож логічно, що треба припинити запити ще на фронтенді. Ну добре, тут теж можна передати прапорець наявності даних, а потім пропускати виклик, якщо прапорець відсутній. На жаль, просто це зробити не вийшло, оскільки пропущений виклик залишався навічно в стані “Завантаження”. Одне з рішень — можна було б не пропускати, а імітувати виклик.

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

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