Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
27.03.2023
Паралелізація запитів в Ruby
Хочу висловити похвалу моделі одночасного виконання в Ruby, а саме, класу Thread. Ruby так зневажливо відносять до повільних мов, що яка вже там одночасність! Тут же ж навіть багатопотоковості немає. (До речі, справжня багатоядерна паралелізація в Ruby 3 теж вже є, з класом Ractor).
Насправді є сценарій, в якому одночасне виконання дуже допомагає, та він всім нам добре відомий — це звертання до мережевих ресурсів. Будь то запити до API, чи завантаження сторінок, чи надсилання листів по SMTP - всілякі операції через Інтернет можна робити паралельно в декількох потоках, бо під час запиту програма все одно стоїть та чекає відповіді. (Єдине, що треба мати на увазі — не робіть з потоків звертань до бази даних, бо це вичерпає пул підключень. Або пул треба збільшити, або роботу з базою винести з потоку.)
# Паралельне завантаження сторінок
urls.map { |url| Thread.new { Net::HTTP.get(url) } }.map(&:join)
Так на минулому тижні мені вдалося замінити процес, що займав дві години, та опитував кожний ресурс послідовно, на паралелізоване рішення, якому потрібно двадцять хвилин. І це мені поки боязно робити надто багато запитів — можливо, в майбутньому зробимо ще швидше.
Для більш поглибленого використання можливостей одночасності можу порадити гем concurrent-ruby. В ньому реалізовані класи для всього, що можна собі уявити. Я, наприклад. завжди маю на увазі пул тредів для обмеженої паралелізації.
26.03.2023
Structured Data та OpenGraph
Сьогодні черговий раз намагався щось зробити з SEO на сайті. Дізнався, що на цей час Google хоче бачити розмітку Structured Data, як головне джерело метаінформації про сторінки. Ніяк не допомагає те, що стандарт Structured Data визначає безліч атрибутів, та до того ж декілька різних типів сторінок.
До цього мені був відомий стандарт OpenGraph. Втім, ці два стандарти розв’язують різні задачі. OpenGraph призначений для створення деталізованих карточок для посилань в соціальних мережах т.і.; Structured Data збагачує зміст сторінки метаінформацією для спрощення машинної обробки. В цьому випадку, Structured Data допомагає Google будувати базу даних сторінок.
Одним словом, з минулого вересня рейтинги в Google так і не повернулись. Так, в січні 2020 на мій сайт було 2.5K переходів з Google, а цього місяця - 24; буквально в 100 разів менше.
PS: розпочав акаунт Unsplash зі шпалерами на iPhone. Має піти добре, бо відбір матеріалу відбувається напівавтоматично. Шпалери ці беруться з нової функції iOS 16 - Photo Shuffle. Вона сама обирає з моєї фотобібліотеки красиві світлини. Далі залишається тільки відбирати найкращі та поширювати через Unsplash.
25.03.2023
Cюжетні пазли - мало відомий піджанр детективних ігор
🕵️🧩🎮 Є такий мало відомий піджанр детективних ігор, я б назвав його “сюжетний пазл”. В таких іграх всі події сюжету вже відбулися в минулому, а нам залишається тільки розслідувати їх. Метою є зібрати з розрізнених шматочків певну картину подій. Це нагадує такі фільми, як Памʼятай або Підозрілі особи. Або процес пошуку помилки в логах. :)
Ось декілька “сюжетних пазлів”, які мені дуже сподобались. Одне в них погано — по другому разу проходити вже не цікаво.
Her Story - вся гра — це архів інтервʼю з дівчиною, який можна шукати за ключовими словами. Треба зрозуміти, що з нею сталося. Деякі ключові слова можна просто вгадати, а деякі доведеться виділяти з вже доступних оповідей. Найприємніше, коли помічаєш особливе слово, за яким розкривається відразу цілий шар небачених досі відео.
Return of the Obra Dinn - розгадай, як помер кожний член команди корабля-привида, та як їх звали. Причому вся інформація, яка є — це момент смерті для кожного знайденого (по черзі) тіла. Причину смерті здогадатись нескладно, а от де хто, доведеться розбиратись за неявними ознаками. Гра має унікальний графічний стиль та чудову озвучку.
Eternal Threads - треба побудувати (а точніше, відновити) дерево рішень, яке призведе до правильного результату. Причому це не так просто, як вибрати “правильне” рішення кожного разу! Бо треба ще обрати правильний момент, а також інколи “помилятись”, щоб вчитись на цих помилках. Та, звичайно, з кожним рішенням відкриваються або закриваються гілки дерева…
Ще у мене в списку бажань The Case of the Golden Idol та Telling Lies.
24.03.2023
Коли закладати локалізацію додатка?
Обговорення вчорашньої статті на DOU порушило цікаве питання. А саме, чи має локалізація додатка бути закладена на початку розробки? Я маю на увазі — чи треба від початку роботи складати рядки у словник та використати в коді ключі перекладу замість рядків без перекладу?
На моєму досвіді, щоб це мало сенс, треба дві умови. Перше — щоб інтерфейс додатка був чітко заданий заздалегідь. Якщо розробка має, так би мовити, характер Agile, та екрани змінюються з новими вимогами та випробуваннями, то ви змарнуєте купу часу на підтримку словника для фраз, які не попадуть до остаточної версії.
Друге — що багатомовність має бути обґрунтована потребами бізнесу. Та головне, що на перекладі додатка інтернаціоналізація тільки починається. Треба ще продавати його на різних ринках, робити розкрутку, рекламу, підтримку користувачів. Перекладати інформаційні матеріали, нарешті. Якщо бізнес не планує це робити, то можливість зміни мови в додатку має суто символічний характер.
Я вважаю, що такий збіг обставин трапляється рідко. Так, якщо робити сервіс мікрокредитів для європейського банку, то в них і дизайн готовий є, і перелік країн, де він буде представлений. В інших випадках раннє впровадження локалізації тільки вадить розробці. Як я показав, додати i18n пізніше — цілком реально, а якщо це так, то не бачу сенсу думати про інтернаціоналізацію раніше, ніж це буде потрібно.
Навіть при розробці нового функціоналу в додатку, що вже має i18n, я схиляюсь до того, щоб нові рядки локалізувати вже наприкінці іншої розробки. До того ж як я писав в статті, в деяких місцях i18n здатний суттєво ускладнити архітектуру. Доведеться думати не тільки про розвʼязання базових задач бізнесу, а ще й про те, як розвʼязок локалізувати. Як на мене, то простіше ці дві справи робити окремо.
23.03.2023
Поштовий заголовок Received
Продовжуючи серію про приколи електронної пошти, розповім про заголовок Received
.
Це один з тих заголовків, що виставляються поштовою інфраструктурою, а не клієнтом, тому звичайні відправники про нього знати не будуть. Хоча, якщо відправляєш напряму до отримувачів — листи без заголовка Received
мають високий ризик попасти в спам. На перший погляд, це дивно — хіба заголовок з назвою “Отримав” виставляє не отримувач? Чому відправник має про нього думати?
А от виходить, що Received
- це як штемпель поштового відділення (застаріла аналогія у вік Нової Пошти.) Кожний сервер, через який проходить лист, має додавати свій Received
, в якому відстежується шлях листа. Зазвичай листи від відправника не йдуть прямо до отримувача, а проходять через так званий message submission agent. Це як ваше поштове відділення. Він й має виставити перший заголовок Received
.
Для звичайних, людських листів MSA - це той поштовий сервіс, яким ти користуєшся - GMail, наприклад. А якщо вже відправляти пошту з додатка самотужки, без поштового сервісу, то треба не забути самому додати Received
, в якому, як мінімум, буде зазначена дата посилання:
Received: from myapp.io by mx.myapp.io; Thu, Mar 23 2023 23 11:27:16 GMT
Все це описано у RFC 822.Через такі особливості SMTP і варто доручити доставлення пошти спеціалізованому сервісу, такому як Mailtrap - нехай вони (тобто ми :) самі читають RFC.
Також сьогодні на DOU вийшла моя стаття про українізацію Сінтри. Частково я ділився деталями тут на каналі, ще минулого літа, але тепер можна прочитати все разом.
22.03.2023
Як довести думку через ланцюг міркувань
Як довести думку до аудиторії? Це може бути потрібно як коли даєш презентацію, так і повсякденно — коли треба пояснити колегам рішення чи ситуацію, яка склалась. Тобто навичка ця корисна до кожного, хто працює в команді.
Я використовую таку модель. Треба визначитись з кінцевою думкою. Потім взяти, явно чи не явно, базовий рівень знань аудиторії. А потім — побудувати ланцюжок думок, який починається з чогось загально доступного для аудиторії, та закінчується висновком.
Найскладніше в цьому процесі — це уникнути стрибків в міркуваннях. Це коли наступна думка не випливає з попередньої. Звичайна причина стрибків — наші переконання, часто приховані. Через ці переконання для нас все послідовно — переконання заповнюють розрив в логіці. Але аудиторію такий стрибок примушує самим шукати пояснення, що або відвертає увагу, або взагалі викликає недовіру до презентації.
Тому я багато разів проходжу по тексту та перевіряю — чи не пропустив я крок. Якщо знаходжу — намагаюсь розкрити свої переконання. Насправді може це і є найцікавіше в презентаціях — досвід. з якого автор має ту чи іншу думку.
Як приклад, моя презентація про підхід React виросла навіть не з думки, а з почуття, яке раніше було описане в статті. Але щоб це почуття викласти, довелося довго копатися у своїх попередніх досвідах розробки, та в першу чергу для себе його зрозуміти. (Ось слайди, до речі.)
Буває й так, що ланцюжок ніяк не будується. Тоді доводиться починати спочатку. Або навіть передивитись свою кінцеву думку, бо вона виявилась хибною чи необґрунтованою — таке теж трапляється.
21.03.2023
Чи боятись Vendor Lock
Мене сьогодні спитали, чи не страшно спиратись на AWS Redshift, дивлячись на Vendor Lock, тобто привʼязку до постачальника. Моя коротка відповідь — привʼязки до постачальника тут немає. Чому, та коли це є проблемою — нижче.
Для мене привʼязка до постачальника — в контексті програмної архітектури — значить, що продукт буде неможливо в майбутньому перенести на іншого постачальника без повного його перетворення. Як приклад — якщо у вас є додаток на iOS, то ви привʼязані до iOS; без iOS додаток не може існувати.
Трохи слабший приклад — Сінтра побудована на Google Firebase; це настільки “не просто” база даних, що якщо ми вирішимо переїхати ще кудись, доведеться заново зробити всю архітектуру додатка, окрім його верхнього шару з бізнес-логікою. Знайти, як робити живе оновлення, авторизацію, і так далі. Проте, Сінтра це переживе. Головне, що ми вільні забрати свої дані — неможливість експорту даних це як не найгірший випадок vendor lock. (А ще, користувач Сінтри теж вільний забрати свої дані, та своєю чергою не є привʼязаним до нас!) До речі, існують відкриті сервіси-замінники Firebase такі, як Supabase, за якими я стежу на всяк випадок. Але поки ми зовсім не збираємось кидати Firebase та ради користуватися її дивовижними функціями та не витрачати час на їх розробку та підтримку.
Якщо брати технології AWS, то серед них є такі, що схожі по незамінності на Firebase. Це, на мою думку, база AWS DynamoDB, або процесор подій Kinesis Firehose. Вони настільки специфічні, що для переїзду на інший сервіс доведеться переосмислити вашу архітектуру (хоча, он, у DynamoDB є замінник в Azure - не знаю, наскільки хороший.)
А інші сервіси AWS, хоч і є специфічними, але суттєвої різниці не пропонують. Так, хостинг контейнерів ECS не дуже складно замінити на будь-яких хостинг на основі Docker. До цієї категорії я відношу і Redshift - він є типовою базою категорії Data Warehouse, а в теорії, з Redshift можна переїхати хоч на звичайний PostgreSQL.
Але взагалі, перше, що мене турбує — це які задачі вирішує сервіс, та наскільки він спростить розробку для нашої бізнес-задачі. А потім вже про ризик vendor lock - чи надійний постачальник, які витрати обіцяє в майбутньому, наскільки сильна привʼязка. Та може виявитись — як у нас з Firebase - що навіть сильний vendor lock буде цілком виправданим.
20.03.2023
Складнощі з міграціями в Redshift
Найбільш за все у схемі перетворення даних розрізами в Redshift мене дратує, що зміни структури бази ускладнюються суттєво.
В Redshift обмежені можливі зміни стовпчиків. Навіть така незавадна зміна, як з NOT NULL на NULL, не дозволена. Можна тільки додати новий стовпчик з потрібним типом та перенести туди дані. На щастя, стовпчики можна перейменувати, тому принаймні можна замінити старий стовпчик на новий. Поки в нас не було розрізів, все було більш-менш прийнятне.
Але якщо від стовпчика залежить розріз, то такий трюк не вийде. Тоді залишається тільки видалити розріз, змігрувати таблицю, та створити розріз наново. (А якщо розрізів багато… Доведеться перестворювати всі.)
Але так теж не вийде, якщо розріз вже активно використовується. Хоча б тому, що перше створення розрізу може зайняти багато часу — часу простою. Тоді доведеться створити новий розріз, може, з версією - my_stats_v2_mv
, наповнити його даними, та потім плавно перевести код зі старого на новий розріз. Перейменувати розріз не дозволено, тож він назавжди залишиться з версією в назві. Тобто з часом буде і v3_mv
, і так далі, на кожну необхідну зміну.
Добре, що всі ці складнощі можна частково приховати за красивим кодом з боку додатка. Є навіть гем Scenic, який вміє версіонувати розрізи. Тільки Redshift він не підтримує.
19.03.2023
Форми в React
Сьогодні, нарешті, повернувся до Сінтри, на яку останніми місяцями ніяк не вистачає часу. Працюю над декількома складними формами.
Для створення форм у React я беру react-final-form. (Є ще formik, який дуже схожий за використанням.) Головне що така бібліотека заміняє купу копіпасти, яка потрібна для керування станом полів окремо. Але взагалі, абстракція форми в React та в HTML трохи різна, та якщо це розуміти, форми покриють ще більше потреб.
Чого точно не треба робити, це зберігати стан форм в Redux. Будь-яка зміна стану Redux призводить до виклику кожного селектора в додатку. Тільки так Redux може визначити, які компоненти потребують перемальовування. Відповідно, якщо на кожний символ, введений в форму, міняти стан Redux, то втрата швидкодії забезпечена та залежить тільки від розміру додатка. Причому це фундаментальна проблема Redux; ніколи не кладіть в Redux те, що змінюється щосекунди або частіше. Прощавайте, фантазії про “чисту архітектуру”. (До речі, дуже ціню, що React Final Form дозволяє звузити підписку на зміни, та уникнути зайвої перемальовки. Так мені вдалося зробити навіть маленький табличний редактор, цілком на можливостях бібліотеки.)
Тоді виходить, що бібліотека react-final-form - це, в першу чергу, механізм збереження стану. Стан цей тимчасовий, та існує від початку змін до збереження їх в стале сховище. Це не завжди короткий проміжок; наприклад, в Сінтрі весь майстер створення бюджету з чотирма сторінками — єдина величезна форма. Так ми легко маємо доступ до будь-якого поля з будь-якого місця форми, а також можемо гортати майстер вперед та назад. Тільки коли ти натискаєш “Створити бюджет”, в базі даних створюються необхідні записи. До речі, щоб не втрати стан форми при перезавантаженні сторінки, дублюємо його в LocalStorage.
Одним слово, що я хочу сказати, що в React форма зовсім не має виглядати як форма, а натомість є абстракцією, яка спрощує керування тимчасовим станом деякого процесу.
18.03.2023
Відновлення флешки з APFS, яку не відкривав macOS
Сьогодні займався, напевно, найстрашнішою побутовою компʼютерною задачею — відновленням даних з жорсткого диска. 💣
Як це трапилось. В мене був зовнішній диск, відформатований в APFS - файлову систему Apple. Диск був зашифрований - APFS це вміє на рівні файлової системи, дуже зручно.
Все було добре. Аж допоки мені не знадобилося перенести macOS на новий ноут. З наявних способів перенесення найшвидшим було через резервну копію - Ethernet під рукою не було, а USB-C виявився дуже повільним. Але для цього як раз потрібний вільний диск або хоча б розділ. Тут стала до нагоди ще одна можливість APFS - вона також вміє створити два розділи, що будуть розташовані в єдиному просторі на диску. Тобто, я зміг створити додатковий незашифрований розділ на тій самій флешці без фізичного виділення під нього місця. (До речі, не знаю, може Linux теж так вміє.)
Перенесення системи пройшло без проблем. Проблеми виникли трохи пізніше. Після чергового оновлення macOS моя флешка припинила відкриватись. Причому найстрашнішим чином — навіть Disk Utility не міг нічого про неї сказати, а зависав. Виправлення диска відмовлялося казати хоч щось, окрім загальної помилки. Я вже майже вирішив, що флешка (а точніше, зовнішній SSD з терабайтом цінних даних) незворотно поламана. Це було дивно ще й тому, що останнім часом я нічого не писав на ту флешку — окрім згаданої резервної копії, звісно.
Промінь надії майнув від утиліти Disk Drill, яка змогла показати каталог диска (хоча файли відновити не змогла). Це, а також дивна поведінка Disk Utility, дало мені ідею, що справа не в диску, а все ж таки в багах macOS. Довго чекав, що macOS виправиться, але з минулого літа нічого не змінилось.
Тому далі я знайшов оцю статтю, а через неї — драйвер apfs-fuse. Але на моєму Маці все до купи не зібралось, через те, що флешка була постійно зайнята Disk Utility, та драйвер FUSE не міг отримати до неї низькорівневий доступ. (FUSE це цікава технологія, відкриває можливість відобразити у вигляді директорії практично будь-що, до чого є драйвер. Наприклад, S3.)
Тому наступне, що довелось робити, це встановити Ubuntu на мій ігровий компʼютер (бо на M2 Macbook Air навіть не хочу пробувати.) Про це треба окремо розказувати, але головне, що на Ubuntu цей apfs-fuse з першої спроби скомпілювався, примонтував мою флешку, та без жодних заперечень скопіював з неї дані.(Пароль від диска він питає під час монтування.) Якось так:
sudo apfs-fuse -o allow_other /dev/sdc2 ~/external
Такі справи. Де macOS зависає, аматорський драйвер для Linux працює.