Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
06.11.2023
Як не дати AWS Cloudwatch себе пограбувати
Давно вже не писав про Cloudwatch, хоча продовжую ним користуватись принаймні щотижня. Попри наявність “брендових” альтернатив, мені до вподоби те, що пропонує AWS у своєму рішенні для збору журналів та метрик. Є тільки один нюанс: можна швидко влетіти на багато грошей. Як і з усім в AWS.
Найризиковішим, як на мене, є утворення власних метрик. У метрик є “виміри” (dimension) - щоб під одним імʼям збирати показники по різних ресурсах. Кожне значення виміру власної метрики сплачується як окрема метрика. А це вже відчутні $0.30 на місяць. Якщо обрати такий вимір, у якого багато значень — наприклад, ID акаунта в нашій системі — то недовго почати витрачати сотні доларів на таку нібито тривіальну потребу.
Окремо підступний вимір - ID задачі ECS, або її ж IP адреса: вони не є сталими, тож за місяць перезапусків та деплоїв можна теж назбирати вагомий рахунок. Якщо вже збирати статистику за задачами, потрібно спочатку винайти сталий ідентифікатор.
Зазначу, що вбудовані метрики AWS нічого не коштують. Це ті, які неможливо відключити. Бо трапляються ще “розширені” метрики — вони рахуються як власні.
А ще дорого може вийти надсилання метрик. Тут $0.01 на тисячу запитів, але знов-таки, якщо наївно відправляти кожне значення окремо, то вони швидко накопичуються. Тому краще буферизувати на своєму боці та викликати PutMetricData
рідше. До речі, оскільки для кожного значення в цьому запиті можна окремо вказати час, то нічого нам не заважає, наприклад, відправляти похвилинні метрики раз на пʼять хвилин.
Зате потім зайшов на сторінку цін DataDog та зрозумів, що в AWS не найбільш заплутана цінова політика.
05.11.2023
Amnesia: The Bunker та жанр "дослідницький хорор"
Закінчив на вихідних гру Amnesia: The Bunker, що вийшла цього року. Вона нагадала мені одну з моїх улюблених ігор - Subnautica. Я б сказав, що ці гри належать до піджанру жахів, який я називаю дослідницький хорор.
Ключовим аспектом дослідницького хорору є опанування оточення. Якщо в класичному хорорі метою є обхід рівня, пошук розвʼязка та просування далі, то в дослідницькому хорорі світ гри не змінюється від початку до кінця. Що змінюється, то це уявлення гравця про оточення, ризики та власні можливості.
Мета гри ставиться максимально широко — наприклад, “втекти”. Ігровий цикл являє собою експедицію - 1) спланувати вилазку з безпечного місця, 2) виконати вилазку, 3) повернутись в безпечне місце з результатами (ресурсами та знанням) та планувати знову. Замість звичайних для хорора “установок” (set piece), тут джерелом страху є постійно присутня загроза та особливо загроза втратити прогрес, бо збереження доступне тільки в безпечному місці.
🤿 Subnautica - це гра, що прикидається класичною пісочницею, але насправді є справжнім хорором, де головний противник — удушення. Бо весь процес гри відбувається під водою, де невпинно вибігає лічильник кисню. А ще є глибина та тиск та неприязна фауна… Якщо такий жанр цікавить, радив би зануритись у Subnautica без спойлерів та подарувати собі просто унікальний досвід дослідження морського світу від сонячного шельфу до тьмяних глибин.
Є ще продовження, Subnautica: Below Zero, але краще почати з першої, бо тут очевидно є припущення, що гравець вже знає що до чого та тому пропущений етап поступового знайомства з механізмами.
🪖 Amnesia: The Bunker так само складається з дослідження лабіринту темних підземних коридорів. Загроза тут банальніше — це бабай, який тебе зʼїсть… але реагує той бабай на галас, тобто ми вимушені бути якомога тихіше. Що не так просто, якщо навіть ліхтарик не світить без брязкучої динамо-машинки. Отже, доведеться балансувати між світлом, темрявою, тишею, гуркотом, та купою перешкод. Окремо мені подобається, що розробники рандомізують зміст, та навіть включили режим де ключові предмети розташовані випадково. Бо типова гра на дослідження — одноразова… далі хіба спідрани.
Які ще ігри з дослідницького хорору я знаю? В Minecraft можна так грати. В режимі виживання точно є ігровий цикл з експедиції за ресурсами вдень та відбудови бази вночі. Ще Dark Souls (перша частина) нагадує, оскільки там світ щільно повʼязаний, без телепортації по ньому доведеться багато разів побігати, а ціль ставиться туманно та її потрібно ще відшукати. (Та й перше проходження DS - неабиякий хорор.)
04.11.2023
Аналіз поля Сапера за допомогою множин
…Вчорашнє рішення з обчисленням ймовірностей мін розвʼязує багато ситуацій, але не всі. Проте щоб досягти чогось більшого, недостатньо дивитись на комірки поодинці: ми витягли з них всю можливу інформацію. Подальший аналіз має дивитись на групи комірок з відомою кількістю мін та через операції над множинами досягати висновку про розташування мін.
????
2334
1*11
Наведу приклад: в цьому фрагменті поля ми не можемо визначити розташування мін за жодною з цифр. Але ми точно можемо сказати, що для виконання 2
перші дві комірки повинні містити одну та тільки одну міну. А для виконання 3
перші три невідомі комірки повинні містити дві міни. Можна зробити висновок, що в третій комірці міна є гарантовано. Або навпаки: якби замість 3
стояла б 2
, то в третій комірці гарантовано не було б міни.
Щоб алгоритмізувати такий підхід, потрібно ввести поняття множини комірок, а далі перетинати ці множини. Кожний перетин утворює три нові множини: множину спільних комірок та множину рештки з кожного боку. Залишається обчислити кількість мін в кожній з підмножин та маємо успіх. Під успіхом маю на увазі, що в деяких випадках отримуватимемо або множину зовсім без мін, або таку де кількість мін дорівнює кількості комірок.
Є тільки одне ускладнення: не завжди ми будемо отримувати точну кількість мін. Бо, якщо подивитись на приклад де дві цифри 3
поруч, то про перетин їх множин можемо сказати тільки те, що в ньому не більше двох мін (що не дуже корисно). Та, не менше однієї — бо решткою від перетину з кожної сторони буде тільки одна комірка, значить, принаймні одна з мін припадає на спільну множину.
Такі нерівності теж можуть бути корисними, оскільки через додаткові перетини можна дійти до точних значень. Взагалі, почати можна й з одного раунду перетинів, але в ідеалі треба обробляти й новоотримані множини, поки отримуємо нову інформацію. Благо масштаб задачі дозволяє робити всілякі перебори. Особливо якщо зберігати стан між кроками, бо поки що я обчислюю все наново кожен раз.
03.11.2023
Сапер: відображення ймовірності
Перше, що я хотів додати у звичайного “Сапера” - це обчислення та відображення ймовірності мін. Бо просто цікаво роздивитись, а ще з цього можна зробити режим підказки або авторозвʼязувач.
Ймовірність — не така проста річ, як здається, та алгоритм її обчислення, власне, і являє собою розвʼязок гри. Тому треба було почати з основ.
-
Ймовірність кожної комірки на полі можна обчислити як частку від ділення кількості мін на кількість ще не відкритих комірок. Ну, це не тільки просто, але й не цікаво — хоч можна поспостерігати, як вона змінюється за ходом розкриття поля.
-
Далі можна зробити теж саме, але біля кожної цифри на краю: кількість невідмічених мін поділити на кількість невідкритих комірок. Різні цифри можуть давати різну ймовірність: беремо максимальну з тих, що знаємо.
-
Але є особливий випадок: якщо навколо деякої цифри вже не залишилось мін, то ймовірність ще не відкритих комірок стає 0. Такий нуль має пріоритет над іншими значеннями.
-
На цей час це вся логіка, хоча можна йти й далі. Власне, вже з таким набором евристик можна розвʼязувати нескладні ігри. (До речі, ще одна ідея на майбутнє: роздивитись ймовірність виграшу в залежності від кількості мін.)
(Ще настав час додати вибір складності. На жаль, на маленьких екранах поки немає щастя, бо flex
, який я використовую для центрування, псує прокрутку, коли зміст не влазить в екран.)
02.11.2023
Робота з вводом/виводом в Golang
В продовження вчорашньої теми…
Головна різниця в роботі вводу/виводу в Golang в тому, що тут код, що блокує, нічим не відрізняється від синхронного. Ніякої спеціальної відмітки для очікування не потрібно. Це, звісно, спрощує код. Бо ввід/вивід — це не тільки читання файлів, але й спілкування з клієнтами та з базою даних, та й взагалі з будь-яким зовнішнім ресурсом.
Можна подумати, що це властивість компільованої чи системної мови програмування, але ні: наприклад, в Ruby IO теж виглядає синхронно, а у Swift - потребує асинхронного коду або колбеків. Ось такий аспект дизайну мови.
Але то не єдина різниця між Go та JavaScript. Менш помітно те, що в JavaScript ввід/вивід працює за принципом push, а в Go - pull. В JavaScript ми можемо зробити запит на читання та займатись чимось іншим — допоки не буде викликаний наш обробник. В Go виклик Read
зупиняє виконання програми. З читанням файлів це менш помітно, але з сокетами ми можемо чекати досить довго.
Тому в Go все ж використовують асинхронний код, але не конкретно для вводу/виводу, а для одночасного виконання. Наприклад, типовий сервер буде запускати горутіну для кожного підключеного клієнта: комунікація через кожне підключення відбуватиметься послідовно, та блокування очікуване.
Для читання та запису в Go є інтерфейси io.Reader
та io.Writer
. Так саме як і в JavaScript, вони можуть приховувати будь-яку логіку. На відміну від JavaScript, немає “дуплексного” інтерфейсу для перетворень. Щоб додати до даних перетворення, зазвичай загортають читач та повертають новий читач: наприклад, bufio.NewReader додасть до будь-якого читача буферизацію.
01.11.2023
Порівняння API вводу/виводу JavaScript та Go
Так вже вийшло, що останнім часом я спочатку працював з потоками в JavaScript (модуль stream), а потім — з модулем io в Golang. Обидва модулі реалізують ввід/вивід в загальному сенсі цього слова, але підхід зовсім різний та його цікаво порівняти.
Модуль stream
побудований навколо асинхронних примітивів JavaScript - від подій до генераторів та async/await
. Важливо, що ввід/вивід завжди вимагають блокування, тобто пасивного очікування. А в JavaScript немає можливості блокувати синхронний код. Тому при використанні модуля stream
нам доведеться ускладнювати код — хоч сучасний підхід з async/await
мені подобається та майже не напружує розуміння.
(Примітка: в JavaScript є декілька функцій, які здійснюють синхронний ввід/вивід, наприклад, fs.readFileSync. Вони реалізовані через вихід в C, тобто фактично “ламають” модель виконання. Ось детальне пояснення.)
Важлива функція абстракції вводу-виводу — це додавання логіки зі збереженням інтерфейсу. В JavaScript для цього є функція pipeline. Наприклад, нею можна додати до потоку запису в файл стискання. Або до читання з сокета — буферизацію.
Результат виконання pipeline
- теж потік, тобто споживачам коду не потрібно знати, що в нього всередині. Також приємно, що pipeline
приховує асинхронність всередині та залишає тільки зовнішню.
Про Go - завтра.
31.10.2023
Оптимізація Kafka для локального запуску
Звісно, зазвичай Kafka як і інші технології оптимізують під велике навантаження. Але, щоб інтеграційні тести швидко працювали локально або на CI, потрібні інші параметри оптимізації, та деколи варто витратити час, щоб їх знайти.
Як я до того дійшов? Вирішив зрозуміти, чому пакет тестів довго працює. На Go якщо запустити тести з ключем -cpuprofile
, то буде записаний профіль виконання. Але профіль тільки показав, що більшість часу тести проводять в очікуванні даних, тобто в IO. В таких випадках профілювання по CPU дає зовсім некорисні результати, бо місце очікування не повʼязане з конкретним рядком програми, отже, незрозуміло, де саме вона гальмує.
Тоді є інший інструмент - -trace
. Трасування запише деревоподібний журнал виконання програми, в якому в тому числі видні й очікування IO, проміж іншими видами блокування. Оскільки я вже знаю, що проблема в IO, то конвертую трасувальний журнал у профіль (go tool trace -pprof=net
) та вивчаю його. Виявилось, що тести багато чекають на Кафку. Вдалося трохи це виправити налаштуваннями.
Чого не треба робити: зменшувати параметр fetch.max.wait
. При мінімальних його значеннях цикл обробки буде безперестанно звертатися до Кафки. Це спричиняє зайві витрати CPU. Якщо ми очікуємо від Кафки хоч якісь дані, то fetch.max.wait
спокійно можна ставити вище.
Натомість треба переконатись, що fetch.min.bytes
встановлений в 1 (мінімальне значення, яке стоїть за замовчуванням.) Тоді ми дійсно отримуватимемо записи як тільки вони зʼявляться
Є ще linger.ms
- це час буферизації продюсера. Його краще виставити більше ніж 0
, що за замовчуванням, бо тоді відбудеться менше запитів до Кафки. Якщо середній тест триває 100 мс — то можна робити linger.ms=100
.
Нарешті, за замовчуванням Кафка створює топіки на декілька розділів — а локально та з єдиним споживачем це тільки сповільнює отримання даних. Раджу перевірити, що топіки створюються лише з одним розділом.
Завтра ще є ідеї паралелізувати деякі кроки підготовки та попрацювати з викликами внутрішніх API.
30.10.2023
Спідран по саперу
Останнім часом сімʼя захоплюється “Сапером” в версії від Google (це одна їх з прихованих цяцьок). Мені відразу стає більш цікаво алгоритмізувати розвʼязок — цікава вправа. Щоб мати можливість робити інші експерименти, вирішив написати свою версію.
Сапер одна з найпростіших компʼютерних ігор, як для розуміння, так і у своїй моделі. Тому й добре підходить як вправа.
В стані маємо поле та міни. Міни розташовуються абсолютно випадково. Клітини навколо них оздоблюються лічильниками мін. Окремо відстежуємо стан відкриття клітин: закрито, відкрито, відмічено.
Гра закінчується тоді, коли відкриті всі клітини (а не за кількістю прапорців — бо ми маємо переконатися, що мін дійсно ніде більше немає.) Тож для стану гри обчислюємо: чи відкрили всі клітини (виграш), та чи відкрили міну (програш.) Поки гра не закінчилась, дозволяємо відкривати та відмічати клітини. От і весь ігролад.
На першу реалізацію в мене пішло година 20 хвилин, з яких останні 20 - на реалізацію двоетапного діалогу відкриття, бо плейтестінг показав, що на пристроях без миші він практично необхідний.
Це мій перший проєкт на SvelteKit, який тут, до речі, зовсім не потрібний, бо немає серверної складової. Чисто браузерний додаток можна було робити просто зі Svelte. Проте мені було ліньки налаштовувати проєкт з нуля, а SvelteKit все робить за мене. Далі проєкт можна безплатно розмістити на Vercel.
Ось: Сапер. А тут код. Далі можна робити над ним щось цікавеньке.
29.10.2023
Система коментарів для блогу
Коротенький огляд того, як в мене на блозі працюють коментарі. Сам по собі блог є статичним сайтом, тож коментарі є єдиним динамічним компонентом. До того ж реалізація такої системи була більше вправою для розваги, бо реально коментарів майже ніколи ніхто не залишає.
А раз вправа, то робив я її сам, замість того, щоб брати щось з систем, які вже існують. Зазначу, вони в більшості працюють не так, як я хотів: а саме, потребують як мінімум локального файлу бази даних, а зазвичай — просто сервер СУБД. (Є ще рішення-сервіси, наприклад, Disqus, але вони начиняють сторінку пачкою рекламних скриптів, та мені це дуже не подобається.)
Я хотів рішення з мінімальним використанням ресурсів. Тому обрав архітектуру яка витрачає гроші тільки на додавання коментарів, а не на відображення. Кеш коментарів до кожної статті зберігається як файл JSON на S3. Відображення здійснюється скриптом на Svelte. Спочатку я хотів зберігати на S3 готовий HTML, але JSON+додаток не тільки простіше в розробці, а й займають менше місця, тобто завантажуються швидше.
А при додаванні підключається додаток на Golang, який міг би бути розташований на безплатній AWS Lambda, але поки залишився на безплатному fly.io. Додаток працює з базою коментарів, яка сидить в безплатному DynamoDB. Від DynamoDB мені були потрібні два запити: пошук коментарів для сторінки та пошук конкретного коментаря за ідентифікатором. До речі, для ідентифікаторів обрав ksuid, бо вони є впорядкованими за часом.
В системі є неявна авторизація — а саме, після створення коментаря в сесії автора залишається ключ, який дозволить відредагувати коментар. По закінченню сесії можливість редагування втрачається. Для перевірки на спам використовую Akismet - з цим сервісом я вже багато років.
28.10.2023
Два тижні з робочим компʼютером — враження
Минуло два робочих тижні з того часу, як я почав працювати за окремим компʼютером. Ділюся враженням від цього незнайомого (та дивацького, на 2023 рік, хех) образу життя.
Щодо технічної реалізації. Я працюю за робочою станцією з монітором, клавіатурою та мишею. Після того, як я переніс периферію на розʼєми USB монітора, перемикання між робочою та неробочою машиною виконується одним кабелем. Проте насправді така зручність не дуже потрібна. Не тільки тому, що в ноутбук можна зазирнути й без монітора — а тому, що одна з моїх цілей це фізично розділити середовища — в тому числі й в часі. Швидке перемикання тут тільки шкодить.
Щодо режиму використання. Я намагаюсь емулювати фактично роботу в офісі, тобто перемикаю на робочу машину після сніданку та повертаюсь “додому” перед вечерею. Такий режим виявився моєю улюбленою стороною експерименту. Мені подобається, що є час, коли я фізично сфокусований на роботі. Більшість відволікань зникла сама собою. Так само подобається, що наприкінці дня можна її відкласти. Сам момент відключення робочої машини наповнює день сенсом та завершеністю, яку було важко досягти раніше.
Щодо програмної частини. Я вже писав, як налаштовував середовище. З витрат на програми — довелося сплатити додаткову підписку Setapp. Зʼявилася проблема обміну інформацією між робочим та домашнім компʼютером; для цього скористався месенджером, який вже є - Apple Messages - щоб листуватися зі своїм альтер его. Це трохи простіше, ніж класичний спосіб — електронна пошта. До речі, так, для робочої системи я створив новий обліковий запис Apple, щоб не мати доступ до документів та світлин.
В цілому, залишаюсь задоволеним цим підходом — навіть набагато більше, ніж уявляв. Всім раджу, за можливістю.