Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
11.08.2023
Розширення модуля в TypeScript
Я люблю TypeScript за те, як вона вдало розвʼязала питання інтеграції з кодом, що вже існує — а саме, з JavaScript. Завдяки можливості впровадити типи для будь-якої бібліотеки, TypeScript чудово інтегрувався з екосистемою та майже не обмежує вибір залежностей. Ну, багато ідіом JavaScript просто погано типізуються, але про це розмова окрема.
Напевно, кожний знає про проєкт DefinitelyTyped, тобто про пакети @types/*, які й додають типи до бібліотек на JavaScript. Але чи задавалися ви тим, як воно працює? Видається, дуже просто — вони містять так звані шаблони модулів. Власне, щоб TypeScript “побачив” модуль, він має або бути написаний на TypeScript (логічно), або мати шаблон.
Менш відома можливість — розширення модулів. Насправді не обовʼязково мати весь шаблон в одному місці. Цілком можливо розбити оголошення на дві частини. Більш за те, додаткові оголошення можуть бути зовсім в іншому файлі та навіть пакеті. Якщо TypeScript підвантажує в проєкт файл, в якому містяться оголошення, то він їх використає.
Причому розширяти можна й оголошення чужих модулів. Це як раз покриває ту незручну ідіому JavaScript, коли додатковий функціонал надається у вигляді плагіну. Як приклад: типи до Day.js. Або, ви можете виправити незручні типи залежностей прямо в додатку.
А якщо плагін — це окремий пакет, то треба памʼятати одну незручну річ: вкладені залежності. Бо той пакет, типи якого ви розширюєте, має не бути такою. Взагалі, при розробці пакетів-плагінів краще не створювати явних залежностей, а використовувати peerDependencies, або навіть devDependencies, та нехай споживачі самі знають, який головний пакет їм ставити.
10.08.2023
Логічна реплікація в PostgreSQL
Логічна реплікація (на відміну від фізичної) - це відносно новий (з 2018) та цікавий спосіб перенесення даних з однієї бази PostgreSQL в іншу. Оскільки зазвичай люди звикли до реплікації як відтворення копії цілої бази, зазначу, що логічна реплікація — це зовсім інша технологія. Працює інакше, вирішує інші завдання.
А саме, логічна реплікація створює копію не всієї бази, а однієї або декількох таблиць. Це корисно там, де треба мати одні й ті самі дані в декількох базах. Наприклад: замість центрального сервісу авторизації можна реплікувати користувачів до баз окремих підсистем, або навіть регіонів. Або: логічною реплікацією можна збирати частину даних в головну базу для генерації звітів та аналітики. Тобто в цілому, корисна технологія, яка працює майже без зусиль.
Все це працює за допомогою журналу. Після створення логічної реплікації база-джерело зберігає журнал змін до таблиць, що реплікуються — аж допоки база-отримувач не прочитає цей журнал та не відтворить зміни на своєму боці. До речі, отримувачів на один журнал може бути навіть декілька. Швидкість реплікації хороша, в нормальних обставинах вона займає секунду чи три. Втім, якщо робити масові оновлення чи імпорти, то реплікація триватиме довше, бо зміна до кожного рядка переноситься окремо.
Є декілька нюансів, про які треба памʼятати. Головне, таблиця-отримувач має бути сумісною з таблицею-джерелом за структурою. Найчастіше натрапляємо на те, що коли додаєш стовпчик в джерело, то перед цим треба було вже додати в отримувача. Але також пильнувати треба за індексами та іншим.
Бо якщо PostgreSQL не може застосувати чергову зміну у таблицю, то він просто призупиняє реплікацію. Хороші новини — вона відновиться автоматично, як тільки зміни стануть можливими. Погані — при паузі реплікації на стороні джерела накопичується журнал, причому стрімко. Якщо цього не помітити, можна заповнити журналом все місце на диску, від чого база лягає ще серйозніше.
Та другий недолік логічної реплікації — вона нічого не знає про зміст таблиць. Вона просто намагається переносити зміни з одної бази в іншу. Якщо таблиці розбіглися, то реплікація або буде продовжувати, як може, або поламається, якщо зміни стануть неможливими. При цьому можливість локального редагування передбачена, я тільки не впевнений, що вона доцільна.
Але важливіше те, що логічна реплікація не гарантує рівності таблиць. Тобто, гарантує, але якщо нічого не трапилось, та поки реплікація ніколи не зупинялась повністю. Хоч завжди можна скопіювати всю таблицю наново…
Одним словом, проблеми в логічної реплікації є, але якщо порівнювати з іншими рішеннями, то вона вельми надійна та легка в налаштуванні.
09.08.2023
Машини AWS Burstable (T-class)
Сьогодні моє уявлення про мінімальний масштаб сервісів було доповнене ще одним критерієм. Йдеться про так звані burstable (тобто “імпульсні”) машини AWS.
Коли бачиш в категорії машини літеру T - то це ті самі імпульсні машини. Їх можна помітити не тільки в EC2, але й в RDS, ElastiCache, та будь-якому іншому сервісі. Коштують вони дешевше, тож виглядають як гарний вибір за відсутністю конкретних вимог. Проте зі швидкодією T-класу є нюанс: вони не здатні працювати на зазначених показниках постійно. Натомість машини класу T накопичують кредити, поки не мають навантаження, а під час навантаження навпаки, ці кредити витрачають.
Що буде, коли кредити закінчаться? Машина буде обмежена до так званої базової швидкодії, що може значить від 5% до 40% від зазначених показників — в залежності від розмірів машини. Причому буде обмежений як CPU, так і дискова активність. Та до того ж щоб помітити цю ситуацію, треба стежити за балансом кредитів, бо за графіком CPU її не помітиш. Наприклад, графік буде показувати, що машина витрачає безпечні 20% CPU, в той час, як вона задихається від навантаження.
Одним словом, в продакшн машини T-класу брати не можна. Бо опинишся в неприємній ситуації, та, скоріше, посеред важкої задачі, яка через обмеження триватиме ще довше. Хоча ще такий Unlimited mode, коли робота поза вичерпанням кредитів можлива, тільки за додаткові гроші.
08.08.2023
Пригоди з оновленням Ruby
Сьогодні вирішив оновити на проєкті Ruby з 3.1 на 3.2. Якщо не знаєте, то нова мінорна версія Ruby виходить щороку на Різдво — тобто давно пора оновлюватись.
Але вийшло, що Rails 6.1 - які, до речі, вийшли в грудні 2020 - припинили отримувати оновлення ще минулого вересня. Та з Ruby 3.2 не працюють. Проблеми зʼявляються навколо використання названих аргументів — а саме, якщо в Ruby 3.1 функцію з названими аргументами можна було викликати з хешем аргументів, то в 3.2 це вже дві різні конструкції та такий виклик призводить до помилки.
Значить, потрібно також оновитись до Rails 7 (які вийшли в грудні 2021 та являють собою останню мінорну версію на цей день.) Змін не так вже й багато. Цікаво, що Rubocop також включає декілька правил для Rails 7, наприклад, Rails/UniqueValidationWithoutIndex.
Але не все так просто — з Rails 7 припинив працювати Webpacker - бо він взагалі більше не підтримується. Є декілька альтернатив - jsbundling-rails та інші — проте найпростіша в переході бібліотека shakapacker. Назва дивакувата, зате Shakapacker є прямим нащадком Webpacker та без великих зусиль підтримує ту саму конфігурацію, що й Webpacker 6.0rc6, що я перевірив порівнянням результатам збірки. Конфігурація була дуже складна, тому й не став все переробляти на кардинально новий конвеєр — бо все, що я хотів, це оновити Ruby.
07.08.2023
Як кодувати міста в програмній системі?
Вправа: користувач має змогу обрати своє місто та країну, з автодоповненням. Далі адміністратор може побачити користувачів за обраним містом та країною. Як би я реалізував таку функціональність?
-
Варіант наївний. Робимо два текстових поля, автодоповнення зі списку країн та великих міст — або свій варіант. Відповідно, адміністратор робить запити за текстовим збігом. Плюси: мінімум зусиль; покриває буквально всі міста у світі — поточні, минулі та майбутні. Мінуси: у даних будуть розбіжності — помилки, альтернативні назви, назви різними мовами, та інше. Такий варіант підходить для невибагливих систем — наприклад, мапа мешкання спільноти не потребує прямо стовідсоткової повноти.
-
Варіант специфічний. Часто список міст цілком конкретний. Наприклад, в поштовій системі це можуть бути міста з відділеннями. Тут можна зробити табличку в базі та дати обирати міста зі списку. Плюси: максимальна чіткість, простота для користувача. Мінуси: вимагає наявності готового переліку. Тут треба запитати: чи дійсно потрібно дозволити користувачу обрати будь-яке місто? Чи є в цьому користь для продукту?
-
Варіант утопічний. Моделюємо всі міста у світі, скориставшись, напевно, деякою готовою базою даних. Тут, на мою думку, баланс “витрати — користь” непрактичний. В реальних географічних даних купа ускладнень. Єдине місто може називатись різними іменами: Krakow, Cracow чи Krakau? Навпаки — є сила-силенна міст-тезок: у США 5 Лондонів, а у Херсонській області — три Новодмитрівки. Фактично, така проста ззовні функція вибору міста перетворюється на повноцінний продукт. Тому…
-
Варіант аутсорсингу. Варто звернутись до готових рішень: API. Наприклад, API Google Maps. Він повідомить дві назви:
main_text- назву міста, таsecondary_text- уточнюючу локацію — наприклад, “Новодмитрівка” та “Білозерський район, Херсонська область”. Цей API вже знає й різні мови, й альтернативні назви. Окрім того, він повертаєplace_id- унікальний ідентифікатор, який можна зберігати у свою базу та легко за ним фільтрувати.
До речі: цікава гра “Скільки міст ти знаєш?”. Мені подобається, що вона повідомляє, який відсоток населення живе у вказаних тобою містах.
06.08.2023
Мова емоцій
Прочитав (або точніше, прослухав) книжку The Language of Emotions, дізнався про яку, як це не дивно, зі статті Кента Бека — творця Agile Programming. На мою думку, ця книга добре підходить для айтівців, оскільки надає аналіз та інтелектуальну інтерпретацію емоцій — тобто як їх розуміти “головою”. Таким чином, книга вчить інтелектуальну та емоціональну частину особистості співпрацювати, причому вчить досить прямим та дієвим підходом. Це дуже важливо, бо наша субкультура вимагає тверезого та аналітичного мислення та спілкування, де емоції ніби зайві — при тому, що, на мою думку, вони потрібні не тільки для чемного спілкування, а й для ефективної роботи (бо код, проєкти та сервіси теж викликають емоції, які теж варто розуміти.) На думку автора книги, емоції cлід не оцінювати як хороші чи погані, а слухати як сповіщення від несвідомої частини нашого розуму.
Напевно понад усе мені запамʼяталася глава про страх. Вона каже, що страх — це відчуття підвищеної уваги до нашого оточення. Я раніше думав, що страх — це коли відбувається щось загрозливе, а виходить, що страх починається раніше. В мене така аналогія: тривога — це коли ти збираєшся йти до лісу, де живуть ведмеді; страх — коли ти в лісі; а коли вже тікаєш від ведмедя — то паніка. З цього ракурсу я зміг зрозуміти, що значить, що українці живуть зі страхом; бо хоч в містах нашому життю рідко загрожує безпосередня небезпека, але ти завжди прислуховуєшся до звуків на вулиці, причому абсолютно автоматично. Це і є страх.
Ще приклад страху — коли дивишся код та отримуєш неприємне відчуття, що це місце спричинить проблеми в майбутньому. А тривога — коли пишеш код, але остаточного розуміння задачі немає. Моя історія про відволікання - це теж тривога та дизасоціація, яку вона спричиняє. Тож якщо навчитись краще помічати за собою такі емоції, то можна стати кращим розробником… ну як, продав вам “мʼякі навички”?
05.08.2023
CouchDB - база даних з крутою реплікацією
Є така база даних - CouchDB - про яку майже ніхто не знає. Вона була помірно популярна десь біля 2010-го, але й досі отримує регулярні оновлення, причому досить вагомі. Я використовував її в маленькому сайд-проєкті, де робив з нею багато, проте у світ проєкт не вийшов, тож як воно в справжньому продакшні, сказати не можу.
З першого погляду, CouchDB - документна СУБД з максимально простою структурою, яка, до речі, схожа з OpenSearch. Документи складаються в бази даних, бази даних живуть на серверах. Цікавий момент — баз даних може бути скільки завгодно, наприклад, по базі на користувача.
Але що насправді цікаво, це як вона працює з реплікацією. Модель реплікації CouchDB підтримує скільки завгодно вузлів, причому не обовʼязково постійно підключених. Тобто це ідеальна база для роботи офлайн. Наприклад, мобільний пристрій може працювати зі своєю локальною базою, попри стан підключення, а оновлювати коли це стає можливо.
Що робити з конфліктами? Власне, вирішати нам — в CloudFlare потужний механізм розвʼязання конфліктів. Можна робити це автоматично, за правилами переваги, а можна й написати код зі специфічною логікою.
А ще, реплікувати можна не всю базу, а частину документів за правилом. Наприклад, може ми хочемо завантажувати на сервер тільки опубліковані документи. А ще можна обмежити реплікацію тільки одним з напрямків. А ще реплікувати багато баз в одну або навпаки — одну в декілька…
Окрім самої CouchDB, є ще повноцінна реалізація на JavaScript - PouchDB. Її можна запускати в браузері або в React Native, та синхронізувати з сервером CouchDB.
04.08.2023
Бітові поля в базах даних та чому це погана ідея
Розкажу про одну зі своїх поганих ідей. Треба було зберігати в базі для кожного рядка набір булевих відміток. А потім — шукати за комбінацією цих відміток. Все це являло собою такий собі скінченний автомат.
Мені здалося дуже розумно не робити по стовпчику на кожну відмітку, а зробити бітове поле. Тоді можна додавати біти операцією OR, а шукати на кшталт bits AND 0b101 != 0. Бітові поля — крута технологія, та дозволяє розвʼязувати задачі, які здаються неможливими. Тільки історія не про це, а про те, як любов до крутої технології мене підвела.
В чому ж проблема? В пошуці. Як думаєш, яким чином можна здійснити пошук по такому стовпчику? Якщо відкрити перелік індексів в PostgreSQL, то знайдеш багато корисного. От тільки індексів по бітовому полю немає. На практиці це значить, що доведеться завантажити кожне значення та перевірити його на відповідність умові. Іншого шляху фізично немає.
Далі все залежить від того, скільки рядків треба перебирати. Якщо є додаткові (та більш ефективні) умови — добре. Якщо ж цей скінченний автомат лежить в серці логіки та фільтрувати доведеться мільйони записів — погано.
А в OpenSearch - базі, що побудована навколо ефективного пошуку — взагалі немає можливості шукати за бітовими операціями. Щоб не кортіло.
У цьому стародавньому треді знайшов гарну альтернативу — зробити замість бітового поля масив цілих чисел — множину. Та побудувати для нього індекс GIN.
03.08.2023
Міграції в OpenSearch
Хоч в OpenSearch й немає жорстко заданої структури даних, потреба в однорідності даних залишається. Ну, може, не у всіх застосунках, може десь є проєкт, який просто індексує аби який JSON - але зазвичай все ж таки ми очікуємо бачити в документах певні атрибути.
Та, як всі знають, сьогодні атрибути одні — завтра можуть бути інші. Що робити з тими документами, які вже існують? На відміну від бази SQL, де можна легко додати новий стовпчик зі значенням за замовчуванням, в OpenSearch готового рішення немає.
Найпростіший технічно варіант — це нічого не робити, а при читанні чи пошуку враховувати, що поле може бути порожнім (тобто null). Не знаю, як ви, а я дуже не люблю перевірки на null “про всяк випадок”. Особливо коли воно скероване не бізнес-логікою, а невпевненістю бази даних.
Можна ввести версії схеми документів. Тобто в кожному документі є атрибут версії. При читанні документа робиться міграція до поточної версії (та, можливо, запис її в базу.) Так я робив, тільки не в OpenSearch, а в CouchDB - моделі документів в них дуже схожі. Впровадження явної версії спрощує обробку порожніх полів. Вона нагадує міграції бази, як в Rails, тільки на рівні кожного документа. (Особливо цікаво воно в розподіленій базі CouchDB, бо старі документи можуть бути приховані на вузлі, який давно не синхронізувався.)
Нарешті, є механізм Update by query, який, здається, і призначений робити міграції даних. Тут можна запустити масову операцію оновлення — хоч для всіх документів. Є тільки одна проблема — на відміну від SQL бази, ця операція не є атомарною або транзакційною. Вона обробляє документи поступово, та ті документи, що встигли помінятись іншим шляхом, не будуть оброблені взагалі.
02.08.2023
Як Kafka гарантує послідовність повідомлень
Ну, годі про формальну верифікацію. Я нещодавно був вражений простим, але дієвим підходом Kafka до послідовності повідомлень.
Для побудови простої в розумінні системи бажано, щоб послідовність подій на виході була така сама, як на вході. Втім, зазвичай зі збільшенням масштабу гарантія послідовності втрачається. Це відбувається тому, що повідомлення можуть бути отримані різними вузлами в кластері. А при читанні послідовність обробки вузлів також не визначена, тому може перемішатись ще й там.
Розглянемо приклад. де нам треба обробляти журнал у форматі “запит”-“відповідь”. Через втрату гарантій послідовності ми не можемо очікувати, що побачимо повідомлення “запит” раніше, ніж “відповідь”, та нам доведеться додатково продумати, що робити з відповіддю, яка прийшла раніше свого запита.
Звісно, рішення існують — наприклад, в AWS SQS є режим FIFO - хоч і суворо обмежений - 300 повідомлень в секунду проти мільйонів у Кафки.
Що робить Кафка? У Кафки одиниця масштабування — це розділ. Кожна “черга” (topic) ділиться на розділи (partition). На рівні розділу послідовність повідомлень гарантована. Кожний розділ призначений до конкретного вузла, який приймає всі записи в нього. Зі збільшенням масштабу кількість розділів та вузлів може зростати стільки, скільки треба, зі збереженням гарантій.
Ключовим є те, що виробники можуть самі вирішувати, в який розділ писати кожне повідомлення. Наприклад, якщо ми пишемо журнал подій, та їх треба групувати за субʼєктом, можна зробити ID субʼєкта ключем розподілу. А в системі “запит”-“відповідь” - просто стежити, щоб відповідь була записана в той самий розділ. (Єдине розумне обмеження — щоб розподіл був рівномірним та не створював “гарячих розділів”.)
А з боку споживача існує механізм групи споживачів. Група споживачів ділить між собою всі розділі топіка — кожний розділ дістається тільки одному клієнту з групи. Таким чином, кожний споживач отримує “скибочку” повідомлень. Послідовність в скибочці гарантована. як і відсутність конфліктів. Коли клієнти підключаються чи відключаються, призначення у групі балансуються наново.
Така от неочевидна архітектурна перевага (а я спочатку думав — навіщо ті розділи?)

