Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
19.07.2023
Приховані проблеми з 64-бітними ідентифікаторами
Коли я рекомендував використати 64-бітні числа як ідентифікатори, я упустив важливий нюанс, а саме те, що не всі мови та середовища їх підтримують. Навіть в наш час, коли майже кожний компʼютер має 64-бітний процесор — від смартфона до сервера.
Ви маєте ретельно перевірити, що ID не проходять в числовій формі там, де мова не підтримує 64-бітні цілі числа. Я впевнений, що такі місця у вас є, принаймні тому, що JavaScript їх не підтримує. Але перевіряти треба все, бо як я писав, наші проблеми почалися не з JavaScript, а з закритої цільової мови. Щоб не створювати додаткових ризиків, краще не тільки перетворювати ці ID на рядки, а й робити їх очевидно не чисельного вигляду — хоча б додати префікс.
При чому, в типовій ситуації надто велике значення ID не призведе до винятку. ID залишиться числом — цілим числом — він тільки округлиться до іншого значення. Абсолютно непомітно.
Поламані ID - це не так погано на фронтенді, бо там вони призведуть хіба що до тимчасової втрати доступу користувача до своїх ресурсів. Але всередині системи це вкрай ризикова помилка, бо ID зазвичай нема чим перевірити, та якщо вони потрапляють в базу, відновити дійсну картину буде складно.
Та ще загальна рекомендація — якщо аналітики знайшли вам баг в даних за допомогою деякого SQL запиту, треба перетворити цей запит на постійний моніторинг. Добре мати набір SQL-інваріантів, які помітять розбіжності до того, як це зроблять аналітики (або гірше, користувачі.)
18.07.2023
Сучасна сцена модів на Doom та GZdoom
Якщо хочеться класної ретро-стрілялки (жанр, також відомий як boomer shooter), варто знати, що моди для Doom роблять й по цей час та вони під час вражають якістю, яку не очікуєш від безплатного проєкту. Треба зазначити, що сучасні рушії для Doom - з яких перший GZDoom - вийшли далеко за межі оригінального рушія — але все ж таки загальне відчуття залишається.
Що відрізняє покоління Doom від інших ігор? Рівні Doom були побудовані з кімнат, кожна з яких є призмою — в основі її лежить довільний багатокутник, а підлога та стеля мають фіксовану висоту. У 1993 році це уможливлювало алгоритм, що сканував екран за стовпчиками та визначав, на яку кімнату та стінку припадає кожний стовпчик. Оскільки вся підлога є пласкою, а стіни — вертикальними, то це потребувало тільки обчислень в площині, бо для тривимірних обчислень потрібна матрична математика, а тогочасні компʼютери були не настільки розумні. (Чи знаєте ви, що Doom вийшов навіть на приставку Super Nintendo?)
А у 2023 цей архаїчний формат рівнів створює цікаве творче обмеження. Та й до того ж моделювати рівні у 2D простіше. Окрім звичайних кімнат, Doom дозволяє створювати всілякі обʼєкти за допомогою відʼємного простору. Наприклад, “кімнатою” може стати ліжко. Або подушка на ліжку. Або навіть телефон на подушці. Головне, щоб це був багатокутник, та над ним нічого, крім стелі, не було. Тож сучасні рівні для Doom це особливий жанр 3D мистецтва.
Для ознайомлення можу запропонувати два різних проєкти. Мод Ashes 2063 являє собою цілий епізод захопливих пригод в постапокаліптичному місті. Деталізація рівнів неймовірна, та особливо цікаво, що вони відтворюють справжні локації — вулицю, заправку, ресторан. Мод MyHouse - це експеримент, який робить з рушія неможливе… сказати більше буде спойлером.
До речі, GZDoom чудово почуває себе на macOS. Втім, багато модів розповсюджуються як “standalone executable” - звісно, тільки для Windows. Тут можна не зволікати, знайти в архіві мода найбільший файл .pk3 та запускати його в власному GZDoom.
17.07.2023
Контексти в Golang - простота
Минулого раз я писав, що контексти в Go існують для зупинки процесу ззовні. С тих пір я намагався вписати їх у свій код, та набув додаткового розуміння.
Розуміння таке: так само як і винятки, контексти не потребують багато уваги, насправді. Не потрібно їх постійно створювати або перевіряти.
Новий контекст потрібний тільки там, де може виникнути потреба скасувати операцію окремо від решти програми. Наприклад, на початку обробки запита HTTP можна створити контекст з обмеженням по часу. Якщо час сплинув, то запит пора зупиняти, а решту сервера — звісно, ні. Окремо цікаво, що контекст запита може успадкувати контекст сервера, та тоді запит буде зупинений не тільки за власною тривалістю, але й при зупинці сервера — якщо така поведінка потрібна.
В інших випадках контекст просто проходить по коду зверху вниз. Що, може, не так й зручно, але тут ще нюанс: контексти потрібні здебільшого там, де відбувається зовнішня комунікація — оскільки саме вона створює неочікувані затримки. В простій бізнес-логіці контексти не потрібні. Хіба що можу уявити, якщо є код, що робить довге обчислення — наприклад, відеокодек — то в ньому має сенс перевіряти стан контексту. (Що, до речі, робиться через конструкцію if c.Err() != nil { return c.Err() }.)
Нарешті, чи потрібно перевіряти, чи повернула функція помилку context.Canceled? Насправді ні — таку помилку можна передавати нагору та само, як і будь-яку іншу. Це виконує задачу контексту: код буде зупинений. Тільки на найвищому рівні — там, де контекст був створений — треба перевірити, що саме трапилось — скасування контексту або інша помилка.
]
16.07.2023
Ruby у VSCode - станом на 2023
Сьогодні приділив уваги своєму оточенню розробки на Ruby в VS Code. Сприяло цьому те, що після увімкнення доповнення Error Lens помилки Rubocop на одному з маленьких проєктів стали більш очевидними та навіть нестерпними. (Rubocop в мене був увімкнений для всіх проєктів, але налаштований — не для всіх.) Окрім банального створення .rubocop.yml та вимикання деяких непотрібних правил, захотілося також, щоб автоматичне виправлення відбувалось після зберігання файлу.
Дізнався, що канонічне доповнення vscode-ruby офіційно оголошене застарілим. Вони рекомендують перейти на доповнення Ruby LSP. Мотивація в тому, що Ruby LSP являє собою мовний сервер, написаний на самому Ruby, що є більш перспективною архітектурою. Та дійсно, хоч обидва доповнення вміють запускати Rubocop на збереження файлу, Ruby LSP робить це помітно швидше — практично миттєво. От тільки дратує те, що обидва доповнення не дозволяють обрати рівень виправлень, хоча це робиться простою зміною аргументу для Rubocop. Тут обговорення.
Поки з всім цим розбирався, зрозумів, що насправді я хочу Prettier. Тим паче що з Ruby LSP можна виправляти складніші помилки Rubocop через вікно “Quick Fix” (cmd+.) А поки намагався налаштувати prettier-ruby, зрозумів, що це просто обгортка над гемом syntax_tree - причому Ruby LSP вже вміє форматувати саме цим гемом. Так що Ruby LSP вже пропонує найкращий автоформатувальник коду для Ruby. Чи добре він працює, зможу поділитися пізніше.
…Але є нюанс. Та дуже важливий. На поточний момент, Ruby LSP не показує в коді синтаксичні помилки. Звісно, це вкрай незручно. Тому після деяких вагань я повернув також доповнення vscode-ruby, тільки увімкнув в ньому єдину функцію Lint.
15.07.2023
Rails на AWS Lambda - чому ні? або чому так?
Поставили питання — чи має сенс розгортати типовий вебдодаток на манеру CRUD на AWS Lambda? Моя перша реакція — а навіщо? Втім, особистого досвіду на це немає, тому спробую розібратись.
З технічної сторони різниці небагато. Як Lambda, так і Fargate, а також Fly.io та інші контейнеризовані хостинги використовують технологію віртуалізації Firecracker. Тому Lambda здатна запустити те ж саме, що й звичайний Docker-хостинг. Відмінність цих сервісів не в реалізації, а в рівні абстракції, яку вони пропонують.
Типовий вебдодаток вже є колекцією функцій, які ми викликаємо за протоколом HTTP. Є тільки один нюанс — в додатку не повинно бути власного стану. Проте сучасна парадигма 12-Factor App пропонує це для всіх додатків. Звісно, як я писав, не всі додатки підходять під цю модель — але типовий CRUD-додаток - цілком.
Деякий “стан” все одно буде в додатку. Це завантажений в оперативну памʼять код, підключення до бази даних, кеш. Все це ми втрачаємо з Lambda - наприклад, код доведеться щоразу завантажувати наново. Звісно, якщо запити робляться часто, то Lambda зі своїм станом буде збережена в памʼяті. Але тоді вона мало чим відрізняється від звичайного сервісу — окрім ціни, звісно.
Оскільки вартість Lambda вимірюється в запитах та їх тривалості, а вартість Fargate - в обсягах, можна обчислити, скільки разів можна запустити Lambda, щоб це було вигідно. Година Fargate з 1 CPU + 2 GB = $0.04947. Мільйон запусків Lambda з 2 GB, кожний з яких триває 100 мс (типовий час для вебдодатка) = $3.53. Виходить, 14000 разів на годину. Не так вже й мало! Виходить, на AWS Lambda можна економічно розгортати сервіси з невеликим навантаженням.
З боку розробки, в деяких моментах Lambda додають складнощів (наприклад, їх не так прямо можна запустити локально), але суттєвої різниці немає. Такі бібліотеки, як Lamby дозволяють прямо використати Ruby on Rails на Lambda, або Jets - пропонує схожу на Rails абстракцію контролерів.
Lambda мають версії, та їх підтримує CodeDeploy. Тому обережне розгортування Lambda можливе, як і екстрене повернення на попередню версію — якщо перед Lambda стоїть обгортка, яка обирає активну версію. Підхід ECS мені більше подобається, бо там сервіс знає, яку версію запускати.
Lambda можна відвантажувати у формі архіву, а не тільки образу Docker. Це суттєво спрощує підготовку додатка, якщо середовище дозволяє. А з іншого боку, збірка Docker універсальна, а з архівом треба розбиратись.
Як з Fargate, так і з Lambda, доведеться створити багато різних ресурсів, щоб сервіс почав працювати — ролі, ALB або Gateway, і таке інше. Простіше буде на Fly.io.
До речі, на Fly.io хостинг в 4 рази дешевше, ніж Fargate, тож математика зсувається. Як я писав, Fly.io варто мати у своєму інструментарії для простого запуску сервісів. Але як я також писав, сервіс не завжди потрібний, а невеликий парк функцій буде більш економічним в підтримці та розміщенні.
14.07.2023
Як перетворити прототип на пул-реквести
Мабуть, всім знайомий підхід до розробки Red-Green-Refactor, де функціонал зʼявляється в коді поступово, та після кожної зміни код завжди працює — єдине питання, що саме він вміє зараз. Одна з переваг такого підходу — це зручні, послідовні пачки змін, які легко зрозуміти вашим оглядачам.
Проте в моїй практиці постійно трапляються задачі, які починаються з прототипа. Прототип зазвичай робиться без чіткого плану змін, бо замість плану відбувається ітеративний процес дослідження та доробки коду, без уявлення, що саме за код доведеться писати та де. В результаті, коли все запрацює, виходить добрячий шмат коду, який в один Pull Request вже не влізе. Головним чином, тому, що зрозуміти його за один підхід неможливо. Що я роблю в таких ситуаціях?
В теорії, можна прототип викинути та зробити наново за планом. На практиці, таке трапляється тільки коли прототип за природою відрізняється від остаточного коду (може, написаний іншою мовою.) А в типовому випадку я доробляю та рефакторю прототип, поки він не досягне стандартів якості стабільного коду. Також на цьому етапі пишуться тести. Все це добре, тільки від того прототип стає ще більше та ще неосяжніше.
Тоді я починаю відокремлювати найпростіші та найізольованіші модулі та робити з них пул-реквести. Недолік: без контексту вони можуть потребувати додаткових пояснень. Перевага: кожен модуль легше зрозуміти сам по собі. До того ж такі модулі не створюватимуть ризиків, що в продукті є напівробоча частина. Останнім пул-реквестом впроваджується найвищий рівень коду, який повʼязує всі інші.
Такий підхід уникає створення тимчасових затичок замість ще не реалізованого функціоналу — бо фактично він вже весь реалізований, та затички будуть зайвою витратою. Як приклад: якщо прототип складається з читання даних та їх обробки, я спершу зроблю ПР для модуля, що обробляє дані, а вже потім для верхньорівневої інтеграційної частини.
13.07.2023
Батчинг в Кафці та інші подробиці
Сьогодні працював над споживачем даних з Kafka та особливої уваги потребував батчинг — тобто збір вхідних повідомлень в пачки такого розміру, який є економічним для подальшої обробки. Батчинг — взагалі складна справа, бо треба не тільки набирати пачку, але й стежити за таймером. А ще думати про безпечну зупинку всього цього сервісу.
Тому, я був трохи здивований побачити, що в Кафці батчинг вже вбудований. А саме, Кафка віддаватиме дані споживачу пачками заданого розміру та з заданою максимальною затримкою. На першому ознайомленні я ці налаштування проігнорував, та розглядав споживача як джерело поодиноких пакетів. Взагалі це ще одна дуже корисна функція для брокера повідомлень.
До речі, щодо клієнтів для Golang. Їх є немало, але знайти хороший не так легко. Баланс проходить як раз за віссю “проста абстракція — відповідність архітектурі Kafka”
-
Sarama - мега складний. Goka - простіша абстракція для Sarama, але я впевнений, що без розуміння Sarama туди краще не лізти,
-
Confluent-Kafka-Go - обгортка над CGO, тому туди я навіть не дивлюся.
-
Kafka-Go - це був мій перший вибір. В цілому непогана бібліотека, але на мою думку надто абстрагована. До того ж в ній не виявилось методу
Flush(), тому я вирішив шукати щось інше. А ще в них надто дивно влаштований інтерфейс підключень та клієнтів. -
Franz-Go - це те що в мене зараз, та баланс абстракції тут кращий. Наприклад, треба знати, що насправді кожний топік в Кафці поділений на розділи для паралелізації, та кожний споживач є підписаним на частину розділів. Тому коли отримуєш повідомлення, то в Franz-Go вони приходять поділені на розділи (хоча можна взяти й масивом.) З боку постачальника повідомлень мені теж більше подобається, бо є окремі функції для синхронного та асинхронного надсилання, між іншими.
12.07.2023
Спортивні додатки
Напевно, багато хто за роки карантину та війни залишився без нормального рішення для спорту. Хочу поділитися двома додатками, які для мене працюють роками.
Down Dog - неперевершений тренер для йоги. З одного боку, він генерує повноцінний відеоряд та детальні пояснення, як по позах, так і по диханню. З іншого боку, це не просто відеозапис — програма на кожний раз будується нова. Причому гарна програма виходить як на 15 хвилин, так і на годину. До того ж Down Dog має безліч налаштувань — загальний рівень підготовки, характер тренування (від “не встаючи з підлоги” до гарячої силової йоги), вибір фокуса (чудово для відновлювальної розтяжки), кількість інструкцій. Окремої згадки варте музикальне супроводження — воно складається зі цілої радіостанції, де темп треків підібраний до відповідного етапу практики. Але можна обрати й більш традиційну музику, або навіть звуки природи.
Down Dog мандрував зі мною по світу та не раз рятував від затягнутих мʼязів та інших нездужань.
Окрім йоги, Down Dog також пропонують HIIT тренування. Цими я теж колись займався. По HIIT теж купа налаштувань — наприклад, можна зазначити, що в тебе немає гантелей або болять коліна стрибати.Є навіть режим HIIT-йога фʼюжн — непогано скомбіновано. Йога виходить на заминку.
Але коли йога — то надто нудно або надто складно, є ще Seven. Вони нібито позиціюють себе як HIIT додаток, але на високоінтенсивне тренування, як на мене, їх програми не тягнуть. Та це добре: натомість Seven пропонує технічно прості, короткі (сім хвилин! насправді, з перервами — вісім) програми на кожен день. Нехитра така, але ефективна зарядка. Можливість налаштувань тут менше, ніж в Down Dog. Проте можна обрати з декількох різних курсів тренувань за своїм смаком. Гарно, що Seven можна запускати саме з Apple Watch. Погано, що тільки так він вміє відстежувати пульс та рахувати калорії.
11.07.2023
Terraform Cloud проти Scalr
Я писав про Scalr майже рік тому. Тоді ми тільки перенесли туди наше оточення Terraform. В цілому, працювало воно нормально, за єдиним нюансом. Який вони приховують з таблиці цін. А саме, кожний запуск Terraform на Scalr коштує 1 долар. Причому plan та apply - рахується вже як два запуски. Так ми не помітили, як влетіли в рахунки в пару тисяч на місяць. Як на мене, це грабіжницька політика формування цін, бо ніхто просто так запуски не рахує. (А розділення плану та застосування на два запуски то окрема образа.)
Ми не хотіли в майбутньому бюджетувати те, скільки розробки нам робити на Тераформі, тому колега Володимир знайшов та переніс нас на Terraform Cloud. Користуємося цим сервісом вже декілька місяців, та, в цілому, для наших потреб, великої різниці не бачимо.
Зокрема, функція планування витрат на Terraform, яка є у Scalr, це просто запуск Infracost, та його легко інтегрувати й в Terraform Cloud. До того ж Infracost виявився менш корисним, ніж я очікував.
Треба зазначити, що Terraform Cloud та Scalr - це єдині сервіси, які зберігають в собі стан Terraform, та тому реалізують повну систему CI - з всім іншим доведеться розміщати стан в іншому місці, а значить, також розв’язувати питання авторизації та паралельного доступу до нього. Не кажучи вже про те, що UI для перегляду стану теж не буде.
Цінова політика Terraform Cloud, на поточний момент, побудована на ресурсах. Перші 500 - безплатно, далі — кожні 10 ресурсів коштують $1/місяць. Теж може вийти дорого, але принаймні ціна зростатиме з розміром бізнесу, а не з тим, скільки планів треба запустити, щоб остаточно налагодити код.
10.07.2023
ElasticSearch - база даних як злив
На мою думку, що треба зрозуміти для ефективного використання ElasticSearch/OpenSearch - ця база у вашій архітектурі має бути стоком. Дані мають стікатись в OpenSearch в готовому, остаточному вигляді. Все, що залишається робити OpenSearch - це а) пошук та б) агрегація.
В протилежність цьому можна уявити систему, де ми складаємо дані в деякій нормалізованій структурі, як ми б робили це в PostgreSQL. А потім, за допомогою засобів бази даних формували б результат. Так OpenSearch не працює, та зробити з нього реляційну базу не вийде. Тому, в цілому, OpenSearch може існувати поруч з базою даних — де в PostgreSQL сидять нормалізовані дані, а в OpenSearch - підготовлені для пошуку.
На практиці це значить, що дані доведеться “підсипати” до документів, що вже існують. Для цього є операція update - це зрозуміло. Що менш очевидно: ця команда здатна приймати скрипт, який виконає оновлення, замість простої заміни значень полів. Інтуїтивно мені здавалось, що цей підхід надо повільний для постійного використання. Але ж ні — оновлення скриптом працює дуже швидко, на рівні з запитами SQL. Які, власне, теж є скриптами, які треба розпарсити та виконати. Єдина різниця, що в базу SQL ми не передаємо їх в середині JSON. А так — навіть параметри до скрипту передаються окремо, так само як до підготованих запитів SQL.
Я робив маленьке дослідження та виявилось, що OpenSearch кешує вже розібрані скрипти. Тобто поки ці скрипти не є надто довгими, та ми їх не генеруємо динамічно, швидкодія сягає мільйонів оновлень на хвилину.

