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

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

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

25.05.2023

Engineering vs Marketing

Хочу розібратися з уявленням інженерів про маркетинг, яке часто є зневажливим. Типу, маркетологи тільки й роблять, що брешуть, а інженери такі хороші та чесні.

Робота інженера складається з того, щоб побудувати найякісніший продукт (та не тільки інженера — а всієї продуктової трійки.) Тому інженери багато піклуються та розмовляють про недоліки. Бачити та виправляти недоліки — повсякденні будні.

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

Якщо ти не займався маркетингом, може здатись, що це зайве, що продукт сам за себе говорить, та якщо він гарний, то ніякого маркетингу не треба. Це абсолютно не так. Без маркетингу продукт не будуть “сприймати таким, як він є”. Про нього просто ніхто не дізнається. Можу проілюструвати розповіддю про сервіс аналітики Plausible - сервіс чудовий, але поки не прийшов маркетолог, користувачів у нього не було.

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

Інженер та маркетолог дійсно мають різний погляд на продукт та на його майбутнє, але їх цілі синергічні. Щоб маркетинг менше брехав, інженер має, як завжди, комунікувати, а саме — знаходити в продукті реальні, важливі обмеження та наголошувати на них. А не валити на маркетолога всю вагу своїх тривог та турбот. Тобто теж стати трохи маркетологом.


24.05.2023

Мета одночасного програмування — застосувати всі ядра процесора

…Коли я писав свій перший сервіс на Go, мене захопила легкість, з якою там створюються паралельні процеси — горутіни. Мені як раз треба було обчислювати структуру, що складалась з більш ніж 1000 значень. Чудове застосування для горутін, думав я, та створював в циклі всі 1000 штук — по одній на значення.

Через декілька днів після запуску сервіс почав падати через нестачу памʼяті. Виявилось, що хоч горутіни є легковажними, та їх можна створити багато, але тисяча горутін, помножена на сотні паралельних запитів, споживала всю наявну памʼять. Та коли колега поділився статтею про створення мільйона горутін, вона підняла ці спогади.

Моє бачення в тому, що абстракції одночасного програмування існують не для того, щоб “розділити незалежні обчислення” чи “щось швидше обчислити”. Одночасне програмування розв’язує задачу ефективного використання процесора. Та, хоч технічно горутін можна створити навіть мільйон, це буде така сама помилка програміста, як і алгоритми експоненційної складності й інше. Суто теоретично воно має сенс, але на реальному залізі так робити ніколи не треба.

Реальний процесор може виконувати паралельно тільки обмежену кількість операцій. Якщо мовити про сервери, то типова реальна машина — не більше 4, а в абсолютному максимумі - 64. Ніяк не тисячі. Створення тисяч одночасних процесів не прискорить їх виконання, а тільки впровадить невизначеність, оскільки ти більше не контролюєш порядок виконання. (Не кажучи про проблеми з памʼяттю.)

Якщо треба виконати багато обчислень в одночасному режимі, створюється [пул]`(https://github.com/panjf2000/ants) за кількістю ядер процесора. Пул саме й дозволяє використати всі ядра, та нічого більше. Саме до такого рішення я й перейшов після того, як мій сервіс звалювався під вагою тисяч горутін.

Якщо наш додаток делегує роботу базі даних, то в пулі може бути більше процесів, ніж ядер, бо процеси будуть чекати (це якщо база на іншій машині.) Більше настільки, наскільки багато часу процес проводить в базі; якщо третину часу, то на третину більше. Задача та сама — пустити в дію всі ядра більшість часу.

Окремо стоїть славнозвісна мірка мови — підтримка тисяч підключень. Що точно не потребує окремого процесу, це мережеве підключення. Бо багато підключень — це абстракція. Насправді у компʼютера, навіть у сервера, тільки одне, послідовне підключення до зовнішнього світу. Скільки б не було клієнтів, процес на них потрібний тільки один. Буферів в памʼяті треба багато, це так. Та типовий сервер обробляє їх послідовно, завдяки виклику select(). Для паралелізації буде 2, 4, 8 послідовних обробників select(). Мати по процесу на підключення — ще одна хибна абстракція.


23.05.2023

Смуга прокрутки для анімації Greensock

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

Власне, викинути прокрутку просто — прибрав конфігурацію та прибрав CSS, який створював довжелезний контейнер - “предмет” прокрутки. Ну та й додав саму смугу (до речі, чомусь Unicode media control symbols на айфоні перетворюються в емоджі — поки не знайшов, як це виправити.)

Щодо реалізації смуги. Її стан оновлюється в тій самій функції render(), що малює сцену. Для цього у Greensock є функція timeline.progress(), яка повертає значення від 0 до 1 - дуже зручно.

Щоб керувати анімацією, треба лише виставити той самий прогрес, що я роблю на події mousedown, mousemove, touchmove - обчислити прогрес як дріб нескладно. Для паузи теж є функції timeline.pause() та timeline.play().

З паузою й почалися складнощі. На відміну від анімації прокруткою, нову анімацію потрібно створювати в стані паузи та починати, коли карта зʼявляється на екрані. Для цього підійшов код з IntersectionObserver, який я вчора написав для автопрокрутки. Але це не все.

Анімація, що створюється в паузі, при першому старті починалася з незрозумілого сміття. Почитав документацію, дійшов висновку, що це через те, що я всюди вживаю інтерполяцію fromTo(): а саме, всі значення from застосовуються миттєво та псують початковий стан анімації. Щоб виправити це, переписав все на команду to(); вона інтерполює з поточного стану до заданого. Там де потрібні були стрибки значення — впровадив їх командою set().


22.05.2023

Прогрес-бар для сторінки зі світлинами. Автопрокрутка

Сьогодні доробляв вчорашній пост. За першими відкликами побачив дві проблеми.

Одним словом, скоріше за все, взагалі приберу анімацію на прокрутку та зроблю більш очевидний елемент прокрутки на кшталт відеопрогравачів.


21.05.2023

Пост про похід

🥳 Нарешті пост про похід закінчений та його можна подивитись: Похід на Клаферкесель. Ви перші глядачі, тож якщо щось поламане, прошу поділитися.

Пару зауважень: треба почекати, поки завантажиться — бо світлин близько 26 Мб. На десктопі чи планшеті має кращий вигляд, ніж на телефоні. Бо я хотів красивий великоекранний досвід, а про телефон думав в другу чергу. В майбутньому, сподіваюсь, придумаю щось гарне для телефону, бо оригінальний задум на маленький екран не перекладається.

Ще, анімацію карти треба прокруткою вниз докрутити до кінця; після цього відімкнеться прокрутка нижче. Я вже бачу, що треба це або пояснювати, або зробити інакше (може, автоскрол.)

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

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


20.05.2023

Приховування обʼєктів в Three.js за допомогою Raycaster

Продовжую роботу над своєю картою - з MVP майже закінчив.

Додав до треку мітки зі світлинами. Як я писав раніше, самі мітки позиціюються завдяки CSS2DRenderer та працює це чудово Але, окрім того, треба приховувати мітки, закриті іншими предметами, інакше втрачається просторове уявлення.

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

Перевірка робиться на render. До речі, ще я прибрав render з requestAnimationFrame та викликаю його в scene.onUpdate. Так суттєво знизилося завантаження процесора — а перемальовувати без оновлення сцени мені все одно не потрібно.

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


19.05.2023

Blurhash для статичного сайту (та чому це погана ідея)

Blurhash - то класний механізм швидкого відображення попереднього перегляду для світлин. Він кодує світлину у стислий (декілька десятків символів) рядок, з якого потім малює розмите наближення. Ось, як приклад, мій аватар: UrLq3Nxv?w%N?JogIoWBRkWCRjjboMayoMoM.

Мені захотілося впровадити його на своєму сайті. Але є нюанс: для відображення такого хешу потрібний JavaScript та елемент <canvas> - або ж взагалі повноцінний додаток. Бо Blurhash не є загально прийнятним форматом файлів — це просто вхідний рядок для алгоритму

А я хотів повністю статичне рішення. Якщо розібратись, що може з себе ставити таке рішення, то це має бути або інше зображення, або деякий CSS, який можна створити з даного blurhash. Навіть знайшов бібліотеку blurhash-to-css, яка нібито саме те й робить.

Навіть написав код, який генерує blurhash зі світлини, а потім перетворює його на CSS. До речі, знайшов непогану бібліотеку Sharp для перетворення зображень на Node.js. Мені вона знадобилась, щоб дістати з файлу JPEG “сирі пікселі” - а також додати до них альфа-канал. Сайт бібліотеки навіть обіцяє, що вона швидша за ImageMagick, тож варто мати на увазі.

От тільки все це марнування часу. Виявилось, що все, що фактично робить blurhash-to-css - це генерує піксельну версію зображення з низькою роздільною здатністю, а потім її розмиває. Причому ця піксельна версія збирається з CSS linear-gradient, тому, на перший погляд, може здатись, що робиться щось розумніше. Але ні. До того ж згенерований CSS займає цілих 2.5 КБ! В такий розмір тривіально влізе проста маленька версія зображення в data URL. Якщо її ще й розмити, то, мабуть, краще вийде.

Це, нарешті, підводить мене до висновку, що Blurhash в HTML/CSS не потрібен. Головна перевага Blurhash - що це ASCII рядок, який легко включити в документ JSON. Але натомість потрібний особливий рушій, щоб його показати. На вебі так само можна WEBP закодувати в data URL, буде компактний ASCII рядок. Проте, як я розумію, Blurhash в першу чергу потрібний саме поза межами HTML/CSS, в додатках — де, може, не так легко показати зображення з рядка?

Одним словом, наступним разом поекспериментую з data URL.

PS: підхід попереднього завантаження компактної версії світлин називається LQIP.


18.05.2023

Реальна ймовірність збігу UUID

Вчора я трохи завтикав зі ймовірністю. А саме: вирішив, що ймовірність збігу за годину у 3600 разів більша за ймовірність збігу за секунду. Зрозуміти, що це неправильно можна, якщо спростити приклад до підкидання монети. Ймовірність “орла” - 1/2. Якщо підкинути монету двічі, то ймовірність аж ніяк не зросте удвічі та не становитимете 1!

Тож з’ясуймо, яка все ж таки ймовірність того збігу UUID.

…Теорія ймовірності — найпарадоксальніша з галузей математики. Вона, безумовно, має найбільший вплив на наше життя — оцінки ймовірності визначають наші найважливіші життєві рішення. Тільки при цьому немає науки, де легше зробити помилку та бути 100% впевненим у своїй правоті. Дозвольте познайомити вас з парадоксом Монті Голла.


17.05.2023

Залежність UUID від MAC-адреси

Виявив істерично смішну дивину з UUID на AWS ECS. Дивився на UUID в базі та помітив, що всі вони мають однакове закінчення. Що трапилось? Розібрався.

Наскільки це погано? Таймстемп в UUID має роздільну здатність 100 наносекунд. За формулою збігу величин, для 1% ймовірності збігу потрібно 450 паралельних генераторів. Але це 1% шанс щосекунди! Для 1% шансу збігу щогодини достатньо 8 машин. З двома машинами можна очікувати збігу кожні 14 годин. Так що, достатньо погано, щоб негайно припинити вживати “стандартні” UUID v1.


16.05.2023

Текст у Three.js

Як додати до сцени текстові мітки? Я очікував, що є якась складна специфічна технологія. Але виявилось все набагато простіше. В Three.js є спеціальний рендерер CSS2DRenderer, який замість малювання 3D обʼєктів позиціює елементи HTML. Тобто до сцени додаються спеціальні “маркери” - CSS2DObject - до кожного з яких приєднаний елемент DOM.

Це працює в поєднанні зі базовим рендерером; в документі <canvas> для обʼєктів та <div> для міток є сусідами та ділять один та той самий простір (що, звісно, необхідно, щоб мітки поєднувались з іншими 3D обʼєктами.)

Так до сцени легко додати не тільки текст, а й будь-який зміст HTML - наприклад, світлину. Зручно. Єдине, чого не вистачає — це відтинання міток, які закриті за іншими предметами. Для того треба вручну робити raycasting, тобто перевіряти, чи не перетинається відрізок від камери до мітки з чимось іншим. Ну, принаймні, напіввручну, бо клас Raycaster бере на себе складну частину.

Поки шукав, як ховати текст “під обʼєкти”, знайшов таку скажену річ, як React для three.js @react-three/fiber. Сенс в тому, що не треба вручну та процедурно оновлювати зміст сцени — можна задати її декларативно. Тобто так само як і для HTML. А ще інтеграція зі звичайним HTML там вбудована, що уможливлює круті штуки. Проте мені зараз React не потрібний, бо для анімації за сценарієм від нього більше перешкод, ніж користі.