Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
01.03.2023
Коли кешування зробити неможливо
Сьогодні коротенькі міркування про той випадок, коли кешування зробити неможливо.
Часто майбутні проблеми зі швидкодією бази даних планують вирішувати впровадженням кешу (принаймні я). Типу, зараз і так все добре, а коли база не буде витягувати — додамо кеш. От натрапив на таку ситуацію, коли кешування, хоч технічно можливо, але нічого абсолютно не дасть.
Уявимо собі що ми робимо інформаційно-погрозливе табло для автостради в стилі “Чорного дзеркала”. Треба розпізнати номер автівки та, якщо по ньому є штрафи, то показати водію попередження. Для цього у нас є централізована база штрафів. Пілотний проєкт працює відмінно. Отримали замовлення від Укравтодору на тисячі таких табло та сотні тисяч автівок. Хочемо уникнути ризику, що база не витягне такого навантаження.
Так от, проблема в тому, що кожна автівка проїжджає повз табло тільки один раз, тож і кожний запит до бази робиться один раз. Хоч ми й можемо закешувати результат, але цей кеш знадобиться тільки в разових крайніх випадках.
Можна зробити інакше та кешувати всю базу заздалегідь, а потім регулярно оновлювати. Таке рішення насправді нерідко зустрічається, наприклад, антивіруси завантажують локальну копію вірусної бази, а не питають кожного разу в сервера.
Якщо ж нас не влаштовує локальна копія — може, ми хочемо оновлювати інформацію якнайшвидше — тоді все ж таки треба робити центральну базу швидкою, без кешу. От нещодавно дізнався, що AWS DynamoDB обіцяє не тільки необмежене масштабування, але й швидкість відповіді в межах 10 мілісекунд.
28.02.2023
Як запустити будь-який скрипт на AWS ECS
Забув вчора розповісти про головне. А саме — бенчмарки треба було запускати на AWS ECS, в умовах, наближених до реальних. От тільки є проблема — на ECS запускаються тільки додатки, які вже запаковані в образи Docker та завантажені у реєстр. Що не сприяє запуску випадкових скриптів та швидким ітераціям розробки.
Але вихід є — при запуску контейнера ECS дозволяє замінити його командний рядок. Тоді скрипт можна розмістити цілком в новій команді, та таким чином запустити. Тільки вручну це робити складно, тому ми в команді зробили утиліту ecs_run, яка власне й робить те, що я написав. Утиліта ecs_run дозволяє нам запускати усілякі rake скрипти, а також просто довільний код на Ruby. (Щоб виконати Ruby-код, вона загортає його у виклик інтерпретатора ruby.)
Тобто, у нас вже є сервіс ECS, в якому міститься додаток, готовий до запуску — код, залежності, необхідні змінні оточення та дозволи. Я командою ecr_run <benchmark.rb запускаю свій бенчмарк безпосередньо в цьому середовищі, тому він має доступ до всього, що є в додатку.
Ось тільки заміна командного рядка в ECS обмежена розміром 8KB. (Як я дізнався вчора; раніше це не заважало.) Що тоді робити? Я придумав розміщати сам скрипт у секретному Gist, а в контейнер передавати обгортку, яка завантажить його та запустить. Якщо цю ідею розвинути, то можна взагалі пакувати всі змінені файли в архів, архів передавати в контейнер, розпаковувати, та мати “неофіційну” версію додатка без зайвого розгортування.
А ще є ідея з іншого боку — що якщо паралельно відкривати локальний тунель, наприклад ngrok, а контейнер нехай підʼєднається до нього та відкриє інтерактивну консоль. В теорії зробити це нескладно, але готових реалізацій я не знаю. Втім, інтерактивна консоль — одна з найцінніших можливостей Heroku, якої часто бракує на ECS.
27.02.2023
Поради по бенчмарках на Ruby
Сьогодні вдалося зробити чудовий бенчмарк для моїх поробок на Redshift. Власне, сам бенчмарк робився на Ruby, та порівнював швидкодію різних запитів в базу. Поки писав, знайшов пару цікавих рішень.
-
Обовʼязково треба знати про бібліотеку benchmark-ips. На відміну від стандартного модуля
Benchmark, ця бібліотека запускає приклади багато разів, та визначає середню швидкодію в ітераціях на секунду (IPS), а також порівнює її проміж прикладами. Я тільки з цією бібліотекою бенчмарки й роблю. -
Бенчмарк бази ускладнюється тим, що треба підготувати репрезентабельний набір даних, а також уникнути кешування результатів — як повного, так і часткового. Тому вирішив вимірювати на реальних даних, а щоб було чесніше — для кожної ітерації підставляв випадкові параметри фільтрів. Для цього, звісно, є вбудований шаблонізатор ERB. До речі, у класу
ERBє непомітний методERB#result_with_hash. Він підставляє у шаблон дані з хешу, а не з об’єкта, що мені майже завжди зручніше. -
Але ж, якщо параметри запитів випадкові, то як можна порівнювати результати проміж варіантами? Можна, якщо генератор випадкових чисел перезаряджати перед прикладом. (Випадкові числа — то є передбачувана послідовність, яка починається з початкового значення — зерна. Знаючи зерно, відтвориш всю послідовність.) Для цього треба створити генератор командою
@rng = Random.new, а перезаряджати, відповідно, командою@rng = Random.new(@rng.seed), щоб зберегти початкове значення. -
До речі, для цього у
benchmark-ipsє можливість вказати власний модуль з хуками, які запускатимуться перед прикладом.
26.02.2023
Де StackOverflow для ремонту?
Вихідні пройшли за ремонтом, а точніше — оновленням проводки та домашньої мережі. Проєкт на 7 розеток, з яких одна - Ethernet, а інша — контрольована, а також два Ethernet кабелі через всю квартиру, та пересування роутера з кімнати на поличку в коридорі (яку теж довелося встановити.)
Найскладніше в ремонті, як на мене — то виправлення помилок. Коли звик до програмування, де будь-які зміни можна відкотити, то посунути на декілька міліметрів отвір в стіні, або зробити довше надто короткий дріт може бути фізично неможливо — втім, якийсь компроміс доведеться робити. Друге найскладніше — то дебаг, тобто зʼясування, що саме не працює. От сьогодні довго порався та декілька разів перетискав мережевий кабель, бо не міг отримати сигналу. А виявилось, що це тому, що я перевіряв Ethernet-адаптером та ноутбуком, а він недостатньо потужний. (Так, якщо кабель задовгий — то вже треба щось с власним джерелом живлення — наприклад, комутатор.)
Мене дуже дратує, що в інтернеті не вистачає інформації по ремонту. Типові тексти — то статті-SEО для різних агенцій. Або погані переклади. Або переклади статей SEO. Все це жахливо читати, але доводиться, бо, скажімо, де ще знайти дані щодо максимальних навантажень на шуруп. Ще зустрічаються стрічки на форумах, де половина постів — то суперечки про те, хто кращий майстер.ʼ
Проблема погіршується тим, що існує купа регіональних особливостей. Всі знають, що розетки в США інші; але також інші стандарти труб та фітингів — навіть кран з польської IKEA не вдасться просто взяти та встановити в Україні. Якщо про програмування можна відразу шукати англійською, то з ремонтом це загрожує неприємними регіональними сюрпризами.
Що з всім цим робити — я не знаю. Може хоч колись поділюся своїми нотатками. Що і вам раджу зробити.
25.02.2023
Інтуїція про швидкодію мов програмування
Яка мова програмування швидка, яка ні? Загальне правило тут старе та просте: інтерпретовані, тобто скриптові, мови — повільні, компільовані — швидкі. Але сучасна арена технологій складніша. Будь-яка успішна інтерпретована мова — будь то Ruby, JavaScript, або Perl - компілюють код під час виконання, щоб його прискорити.
Взагалі програми повільні через невизначеність. Чим більш чітко мова задає програму та її структури даних — тим краще буде працювати. (Компіляція під час виконання саме й аналізує код на наявність “визначеної” частини.) Наочний приклад: Rust проти Go; в Rust треба явно назначати “власність” памʼяті, проте це дозволяє уникнути збірки сміття, яку вимушений робити Go; тож Rust - швидша мова. Керування памʼяттю — це найголовніший фактор невизначеності, що робить програми повільними; системною мовою програмування С вся памʼять виділяється та звільняється вручну — абсолютно визначено та без зайвих витрат часу. Але ручне керування робити складно, та призводить до критичних помилок. Тому більшість сучасних програмістів користуються мовами, що звільняють памʼять самі, неявно. Проте ефективне автоматичне керування памʼяттю — досі не вирішене питання (про що також свідчить існування мови Rust.)
Окрім керування памʼяттю, дорого коштує відсутність типів та усіляка гнучкість. Наприклад, програма на Go під час виконання чітко знає, де в памʼяті знаходиться та чи інша функція; а програма на Ruby має спочатку перевірити, чи не заманкіпатчили її, та взагалі, чи є така функція у даного обʼєкту.
Скриптові мови існують, бо не завжди потрібна максимальна швидкодія. Здатність писати простіші (та менш визначені) програми теж дуже цінна, бо це дозволяє досягнути складнішої логіки меншим кодом. До того ж будь-яка скриптова мова — це неодмінно тільки шар, під яким знаходиться швидкий компільований код. Причому шар порівняно тонкий. Наприклад, може здатись, що в вебі переважають саме скриптові мови. Але ж за додатком на Ruby або PHP або Python стоїть база даних, написана на мові C. А перед ним — балансувальник навантаження, теж на мові C. Тож наш додаток не так вже й багато робить сам по собі.
24.02.2023
Погано чи ненормально?
За результатами вимірювань, інструмент для виявлення SpamAssassin обробляє один лист за 25 секунд. Це добре чи погано?
…Питання з каверзою. Головне, що це ненормально. Пробачте за відсутність кращих термінів, але я маю на увазі, що якщо вимірювання показали такий результат, то щось пішло не так. Звідки це випливає? Наприклад, з того, що SpamAssassin - широко використовувана система, а листи приходять частіше, ніж раз на 25 секунд, тож з такою швидкодією вона просто має не впоратись.
Тож в такому випадку треба зупинитись та зрозуміти, що саме в процесі вимірювань працює не так. Може, клієнт не може під’єднатися до сервісу SpamAssassin, та просто повертає порожній результат після тайм-ауту… Та багато чого може бути, головне, що цей результат надто ненормальний, щоб з ним працювати далі (наприклад, базувати оцінки, або шукати шляхи оптимізації.)
Я вже писав про чуття на проблеми. Це саме той випадок. Звісно, погані показники — це норма, тож зазвичай нічого вирішувати не треба. Звісно, немає ніякого алгоритму, щоб зрозуміти, де саме звернути увагу. Тут як раз потрібна інтуїція. Та, може, ще сміливість побачити, що поточне, зручне розуміння хибне.
23.02.2023
Декілька думок про гарний сон
Сьогодні намагаюсь лягти спати раніше. Як я колись писав, сон — найґрунтовніший фактор якості життя, разом з харчуванням та фізичною активністю. Але ж знати це простіше, ніж влаштувати собі якісний та довгий сон. Кілька думок про це.
В першу чергу все ж таки треба лягати спати до 12; за моїм досвідом такий сон більше відновлює та потрібно його менше. Але ж “просто” лягти раніше не так просто; в мене це значить виправити весь розклад дня, тож ніяк не виходить. Намагаюсь нагадувати себе, що якісний сон покращує все інше, що трапляється в житті, тож “економити” на ньому безглуздо. Ще, хронічна недостача сну занижує когнітивні здатності, тобто реально робить нас дурнішими. Подумай про це наступного разу, як не розвʼязується складна проблема на роботі.
Окрім тривалості, та очевидних рекомендацій типу — купити комфортну постіль, добре зашторювати штори, та заклеїти яскраві індикатори непрозорою плівкою, додам: щоб спалося спокійніше варто перед сном сісти та записати все, що вертиться в голові. Або ж якщо думки ніяк не дають заснути, то встати та записати їх — теж допомагає.
Також, я не доктор, але можу порадити препарати магнія+B6 для покращення якості сну. Причому краще над усе знайти таурат магнію (magnesium taurate). Магній також допомагає концентрації, тож для роботи головою це важлива добавка. (Але магнієм єдиним, на жаль, всього не виправиш.)
22.02.2023
Mermaid - формат побудови схем
Якщо доводиться писати документацію у Markdown, раджу ознайомитись з генератором схем Mermaid. Бо в багатьох редакторах з Markdown підтримка Mermaid вбудована; достатньо помітити блок з вихідним кодом мовою '''mermaid та він буде замінений на відповідну схему. Так роблять GitHub, Obsidian, та навіть попередній перегляд Markdown у VSCode.
Я навіть не писав “якщо доводиться робити схеми”. Бо якщо схеми треба малювати вручну, то не будеш цього робити без великої потреби. В цьому сила Mermaid: якщо вже знаєш синтаксис, то вставити схему в текст майже не складніше, ніж писати сам текст. Тож це ефективний та разючий спосіб покращити наочність повсякденної документації.
Ще, випробував сьогодні Redshift Serverless для запуску тестів на CI. Ой, погана це ідея. Виходить замість $216 на місяць за кластер близько $0.72 за хвилину тестів, причому швидше працювати вони не стали. На мою думку, Serverless підійде тільки для випадків, коли базою користуються в ручному режимі та рідко — може, щоб згенерувати звіт раз на день.
21.02.2023
Виправляв помилки з матеріалізованими розрізами в Redshift
Сьогодні день видався виснажливий. З ранку до ночі шукав, чому мої розрізи, які так чудово працювали в локальному Postgres та в редакторі запитів Redshift, відмовились створюватись на CI. Встиг навіть створити та розвʼязати запитання на StackOverflow. Нарешті, знайшов — методом спроб та помилок. Це те, що мені найбільше не подобається в Redshift - деякі особливості ніде не пояснені, при цьому текст помилок веде на зовсім невірний хід думок. Мабуть, вже треба писати свій гайд.
Що трапилось. Спочатку була помилка, що у користувача не вистачає прав на якусь системну таблицю. Раніше я робив експерименти від суперкористувача, тому вирішив, що у CI користувача немає прав. Як дати права? Команда GRANT працює тільки після факту. Є ALTER DEFAULT PRIVILEGES - не працює. Придумав створити процедуру, яка робить GRANT: якщо у процедури буде атрибут SECURITY DEFINER, то вона надасть CI користувача право на цю команду (щоб дати собі права.) Задоволений таким кмітливим рішенням, запустив тести на CI та… виявилось, що хоч створити розрізи вдалося, але тепер їх неможливо оновити. Чудово.
Виходить, що залежність розрізу від системної таблиці — це в принципі неправильно, та Redshift мав би так і сказати, а не водити за ніс. Тоді чого не вистачає моєму розрізу-основі, щоб цієї залежності не було? Щоб визначити це, зробив копію коду, що створює розрізи, та почав викидати з нього все підряд, запускаючи, щоб перевірити, чи не зникла проблема. Та нарешті виявилось, що треба не викидати, а додати — а саме, всі стовпчики з GROUP BY мали бути присутніми в SELECT. Напевно, інакше Redshift не може відстежувати зміни до розрізу, тому створює системну таблицю. Якби це був тільки один розріз, то я б й не помітив, а з залежностями маємо такі незрозумілі помилки.
Додам, що спочатку витратив багато часу, бо запускав кожне виправлення на CI та чекав 10 хвилин, поки дійде до місця помилки. Справа пішла спритніше, коли почав відтворювати запити з локального psql. А потім, щоб перевірити на CI, скоротив сценарій до єдиної частини, що мене цікавила.
20.02.2023
Навіщо писати тести потім?
Сьогодні практично весь день писав тести для коду, який вже був написаний. Насправді навіть не для коду, а для розрізів в базі. Але тести на RSpec, бо кращого способу не знаю.
Взагалі я не прихильник TDD, та скоріше навіть відхильник, принаймні писати тести спочатку мені ніколи не подобалось. Завжди це здавалось якимсь ритуально-театральним підходом. В мене код зазвичай починається з “вертикальної скибочки”, коли все від початка до кінця працює, але в обмеженій кількості. Коли щось таке працює, починаю розбивати на акуратні модулі, тестувати, доповнювати деталями.
Тести для мене — це спосіб викликати код з різними аргументами, деколи такими, що вручну створити складно. Тобто технічно це завжди спрощення ручної роботи. Тож, як на мене, коли тести перетворюються в складну роботу — треба шукати, що з ними не так. Або тестувати на іншому рівні — юніт-тести не завжди найкраще рішення, інколи можна більше зрозуміти по інтеграційнім. Або замислитись, якого контексту не вистачає — інколи гарно написані фабрики або допоміжний код з жахливих тестів роблять такі, що і писати, і читати приємно.
А ще нещодавно придумав брати скриншоти з інтеграційних тестів як ілюстрацію до PRів. (Для цього в Capybara є функція save_screenshot.) Так можна підготувати ілюстрації до різних контекстів та без проблем оновити їх, якщо в коді будуть зміни.

