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

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

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

13.08.2024

Redis, AWS ElastiCache та Terraform

🤯 Мав нагоду попрактикуватись з конфігурацією AWS ElastiCache через Terraform. ElastiCache - то керований AWS сервіс для Memcached та Redis. Напевно, найгірша абстракція, яку я бачив у AWS, та Terraform нічого не спрощує.

Видно, що спочатку в ElastiCache був Memcached, а потім додали Redis. Бо те, що в них називається “кластер”, з Memcached містить декілька вузлів, а з Redis - тільки один. Але потім в Redis теж зʼявилася підтримка кластерів, її додали в ElastiCache та назвали “cluster mode cluster”. Гарно, так?

Окрім того, в Redis ще є реплікація. Кластери — то коли дані розбиті на декілька вузлів, а реплікація — коли кожен вузол містить копію. Звісно реплікацію теж можна увімкнути в ElastiCache, та зовсім все заплутати.

В Terraform є два ресурси для “кластерів” ElastiCache. aws_elasticache_cluster - це один вузол Redis, а aws_elasticache_replication_group - це декілька вузлів… серед яких можуть бути як репліковані вузли, так і ті, що складають кластер. Я вже не кажу, що це ще один шар незрозумілих назв, але також виникає проблема, що коли в Terraform хочемо перейти від одного вузла до декількох, доведеться замінити один ресурс іншим.

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

Як її розвʼязати? Редагувати стан. На превелику радість, за останні роки в Terraform зʼявилися повні можливості редагування стану через конфігурацію. Раніше для того треба було робити консольні команди — що було складно, якщо стан зберігається на CI. А тепер замінити один ресурс на інший можна одним блоком removed (щоб прибрати aws_elasticache_cluster зі стану, але не видалити сам вузол) та import (щоб внести всю групу як aws_elasticache_repication_group).

…Ще з позитивного — те, що міграції та оновлення в ElastiCache відбуваються без відключень та без втрати даних. Коли сервіс вже працює, то працює гарно.


12.08.2024

Двійкові дані в текст: дробові кодування

Нам не обовʼязково обмежуватись кодуваннями, де розмір словника є ступенем двійки. Це, звісно, суттєво спрощує операції та дозволяє робити декодування таким псевдокодом (для Base64):

three_bytes = lookup[c0] + lookup[c1] << 6 + lookup[c2] << 12 + lookup[c3] << 18

Але ж будь-який інший розмір словника також технічно можливий. Фактично ми впроваджуємо систему числення з основою, що дорівнює розміру словника, та переводимо наші двійкові дані в та з неї. Єдине, що відрізняє цей процес від шкільної програми: вхідна послідовність розбивається на фрагменти такого розміру, щоб вони влазили в 64-бітне число. (А ще краще — у 48-бітне, що не втратить точності в типі Double та не створить сюрпризів, наприклад, у JavaScript.)

Як тривіальний приклад, розглянемо Base10. Тут кожний символ має одне з 10 значень. Два символи: 100 значень. Три: 1000 значень. Це більше за 256 - значить, трьома символами Base10 можна закодувати один байт. Надлишок такого кодування аж 300%. Хоча насправді за 3 байти можна впоратись й у Base7 (де 343 можливих значень.) А з Base10 надлишок буде менше, якщо кодувати 2 байти (65536 значень) у 5 символів: тільки 250%.

…Якщо це узагальнити, можна прийти до ілюстрації вище. Вона показує надлишок для всіх основ від 2 до 256, але тільки тих, де він зменшується (що в Base64, що в Base65 3 байти даних кодуються в 4 символи.)

Що ми бачимо? Краще за Base64 буде тільки Base85 - який, як згадали в коментарях, використовує Git, а до того ж ще й PostScript та PDF. Як я розумію, тут вирішили, що розмір файлу важливіший за швидкість обробки. Наприклад, в Git у Base85 кодуються патчі - операції з патчами не такі часті, отже можна й почекати.

Наступне покращення наступає на Base102 - для такого вже доведеться відкусити декілька системних символів. Що робить наше “текстове кодування” не таким вже й текстовим. Гадаю, це пояснює, чому окрім Base64 та Base85 інших кодувань немає. (Ну як, немає… Base16 ми теж скрізь використовуємо!)


11.08.2024

Що може бути краще за Base64?

Прочитав сьогодні в блозі Євгенія Гизили статтю про збірку WASM в складі пакета для браузера. Йшлося проміж іншим про кодування WASM у Base64 для включення у вихідний код JavaScript. (Бо WASM надає нам фактично обʼєктний модуль, який в JavaScript просто так не запакуєш.)

Виникла думка: ну, мабуть, Base64, стандартизований ще у 1996 році, в наш час Unicode та емодзі — не найкомпактніший спосіб кодувати двійкові дані в текст? Думка виявилась хибною: ось чому.

По-перше, стандарт Unicode, та конкретно UTF-8, випереджає Base64, бо зʼявився ще у 1992. Але то таке, історична дрібниця. По-друге, кодування символів у UTF-8 менш ефективне за Base64: символ довжиною у 2 байти кодується у 3, тобто з 50% надлишку замість 33%. (Пояснення тут в тому, що UTF-8 повинен бути сумісним з ASCII, тож всі байти символів Unicode містять виставлений верхній біт, а до того ще й маркери довжини символу).

Отже, як би ми не намагалися використати символи Unicode для кодування, нічого краще за Base64 не отримаємо.

(До речі, рядки в JavaScript мають кодування UTF-16 - єдине з кодувань Unicode, яке не сумісне з ASCII, тобто технічно можна було б досягти кращої ефективності. Але це тільки в памʼяті - а файл, який ми хочемо зменшити, все одно майже напевно буде в UTF-8.)

В ASCII7 128 символів. Не всі можна використовувати: символи 0-31 є системними. До того, важливо, щоб кількість символів в кодуванні була ступенем двійки: це дозволяє кодувати та декодувати з використанням побітових операцій та швидкої таблиці-словника. Виходить, 64 символи — це найкраща кількість, а Base64 - це математично найкраще кодування. Ось так.


10.08.2024

Dark Souls та кати

🦀 Грав сьогодні в Another Crab’s Treasure - до речі, рекомендую як рідкісно життєрадісний, а все-таки дуже складний соулслайк. Та намагався зрозуміти, в чому ж та складність складається.

🚥 Кожний супротивник у Dark Souls має чітко визначений набір ударів та комбінацій - moveset. Критично важливо (для самої гри), що всякий удар починається з підказки (tell), за яким його можна відрізнити. А всяка дія гравця починається із затримки (замаху, наприклад), та не може бути зупинена.

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

⏲️ Тому найпряміший шлях до подолання наступного випробування у Dark Souls (або Another Crab’s Treasure) - це звернути увагу на рухи та підказки противника та шукати, як з ними “танцювати”. А шлях “в лоб” - тобто бити скоріше та захищатися від ударів — значно складніший та не такий приємний (тому багато людей вважають soulslike занадто складними.)

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


09.08.2024

Дженерики та кодогенерація в Golang

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

Дженерики частково прибрали потребу в кодогенерації: а саме, більше не потрібно генерувати код “з підставлянням типів”. Це чудово та, звісно, в нашому коді є місце дженерикам. (Хоча я поки не брався за впровадження суто функціональних підходів, як, наприклад робить модуль github.com/samber/lo.)

Але все ж навіть з дженериками залишається багато шаблонного коду, який неможливо узагальнити. Уявимо, що у нас є структура Database, в якій 10 різних полів Table[Key, Record]. Всі ці поля доведеться оголосити, ініціалізувати, та виконати різне обслуговування. Щоб не копіювати десять разів однакові рядки для кожної таблиці, нам і допоможе кодогенерація.

Раніше я передавав параметри генератора в рядку //go:generate, але нещодавно знайшов інший підхід. Можна почати з конфігурації у JSON чи TOML, а у генераторі зчитувати ту конфігурацію та генерувати весь код. Наприклад, в конфігурації може бути список таблиць, за яким ми генеруємо і структуру Database, і функцію Database.SaveTables(), і все інше.

Можливості практично необмежені! Але все ж я б радив зводити генерований код до мінімуму, бо код в шаблоні точно важче сприймати та редагувати. Наприклад, замість генерації всієї Database можна згенерувати структуру tables та вбудувати її: type Database struct { tables }. Тоді цей тип можна розширювати без вдавання до шаблону.


08.08.2024

Оптимізація структур даних Go в памʼяті

В продовження вчорашньої ситуації: ділюся декількома знахідками.


07.08.2024

Використання памʼяті в Go

Намагаюся знизити розмір величезної структури даних на Golang. (Назвемо її “базою даних в памʼяті.”) Вона більше не влазить в памʼять, відповідно, є сенс знизити витрати, а не просто палити гроші на неоптимально використані ресурси.

Колись я вже дивився на це питання, але тоді питання було більше академічне. На практиці все дуже складно. Звісно, в Go немає способу дізнатися розмір складеної змінної в памʼяті. Майже будь-яка складена змінна буде містити вказівники, тобто складатися з багатьох ділянок памʼяті. Навіть якщо припустити, що всередині немає ані дублікатів, ані циклів вказівників, все одно доведеться рекурсивно збирати все по шматочку.

До того, всі складені типи займають деяку кількість зайвого місця. Структури містять вирівнювальні байти. Масиви мають резервну місткість. З типом map все взагалі погано, бо його нутрощі нам недоступні, та ми практично не можемо дізнатись, яка в словника резервна місткість. Але вона є: дуже грубо кажучи, Go резервує місце під кількість елементів, кратну ступеням двійки.

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

Пишу маленький аналізатор витрат. Поки що мені вдалося “пояснити” десь 60% тих витрат, що повідомляє ReadMemStats. Чи є 40% надлишком на керування памʼяттю? Хочеться, щоб ні.


06.08.2024

Значки-посилання для статичних сторінок

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

Звідки ті значки беруться? Робити їх власноруч — навіть статичні — трохи занадто. Тому що є готові сервіси — вони здатні не тільки згенерувати значок, а й додати до нього динамічний зміст.

Великих сервісів два. Shields.io - популярний та старий. Badgen - зʼявився як заміна для Shields, але так і не витіснив попередника. Shields.io підтримують більше сервісів та мають кращу документацію. Навіть можна взяти дані з власного JSON API, або навіть сторінки HTML!

Ось, я тільки що зробив значок для цього каналу. Хоч Shields.io не підтримує Telegram, але я можу отримати кількість підписників з вебсторінки каналу, передати її в Dynamic XML, витягнути потрібний блок через XPath, та ще й додати логотип Telegram у вигляді власного SVG (в Shields.io є логотип Телеграму, але надто мілкий.) Чудовий результат без коду та без інфраструктури.

Такий значок мені буде корисний на сайті, бо там така сама проблема, як з GitHub: він статичний. Взагалі хто памʼятає, в епоху Web 1 - тобто до соціальних мереж — вже існувала схожа технологія — інформери. Це теж були картинки, які збиралися на сторонньому сервері. Два типових приклади — це лічильних відвідувачів (коли ж я останній раз такий бачив?) та віджети з погодою (на власному сайті погода? просто метеоцифрова магія!) Єдине, що помінялося тепер: значки генеруються у SVG, а значить, не потребують від сервера більше зусиль, ніж звичайна маленька вебсторінка.


05.08.2024

GTD: успіх?

Раптом якось минуло вже два місяці з того як я розпочав нову реалізацію GTD, та це великий успіх, бо вона досі жива та здорова. Зазвичай в мене два місяці це вже вік, коли система забивається сміттям та припиняє існування.

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

Складність канви збігається зі складністю уявлення про проєкт. Та це дуже корисно! Мені раніше цього не вистачало. Відразу зрозуміло, коли пора розбивати проєкт на підпроєкти (бо канва розрослася.) А ще трапляється навпаки: недорозвинена канва вказує на проєкти, яким бракує планування (або краще — ті, що пора видалити, бо вони надто примарні.)

А коли на канві є плани у всіх необхідних подробицях, то список задач вільний містити тільки справжню наступну дію. З цим успіх перемінний, але я докладаю зусиль. Наприклад, читаю дію “зробити фічу X” та ловлю себе на думці “треба буде перевірити як це зробили в Y”. Значить, або відразу перевірити (та ймовірно записати в канву), або записати це як наступну дію. Такий процес для GTD життєво необхідний, та я радий що він не захламлює список задач (бо в ньому записана тільки безпосередньо наступна дія.)

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


04.08.2024

Homebrew Cask на практиці

Цими днями підʼєднав до Homebrew Cask всі встановлені застосунки, які там є. Вийшло аж 55! Навіщо воно потрібно?

(Homebrew Cask - це додаток до Homebrew, який містить звичайні графічні застосунки. Працює саме так, як уявляєш.)

Як я це бачу, найбільша перевага навіть не в тому, що застосунки легко встановити — бо я щоразу налаштовую новий компʼютер переносом системи та такої потреби навіть не стає. А в тому, як їх легко оновлювати. Homebrew Cask містить скрипти для повністю автоматизованого встановлювання навіть там, де воно зазвичай потребує діалогу — наприклад, для Zoom. Навіть MacUpdater так не вміє. (Хоча його теж рекомендую.) Та й звісно всі застосунки можна оновити однією командою — теж гарно.

В HomeBrew Cask не можна додати застосунки з App Store. Але сьогодні дізнався про утиліту mas для роботи з App Store через командний рядок. Так можна зберегти список застосунків та встановлювати автоматично. Хоча мені навіть більше подобається можливість провести аудит.

А з чим гірше, так це Setapp. За все хвалю цей сервіс, проте автоматизації йому бракує. Поки кожний застосунок доведеться встановлювати вручну. Шкода — за вісім років існування могли б вже щось зробити. Навіть стандартної для macOS автоматизації через AppleScript або Shortcuts немає. (Зате AI помічник є. Пріоритети нашого часу!)