Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
04.02.2024
Стохастичний тайм-трекінг на SwiftUI
Сьогодні наполовину для розваги, наполовину для діла спробував зробити на SwiftUI реалізацію одного особливого тайм-трекера. Особливий він тим, що, замість ручного ведення журналу чи автоматичного збору даних, трекер питає тебе, чим зараз займаєшся. А щоб це не було передбачуваним — інтервал запитів буде стохастичним, тобто випадковим. Інколи через пʼять хвилин, інколи через дві години. Випадковість моменту робить такий трекер неупередженим (якщо завжди заносити саме те, що робиш зараз.) На довгому проміжку часу статистика дає правдивий розподіл часу.
Це все не я придумав — йдеться про TagTime - зроблений чимало років тому розробниками сервісу Beeminder. TagTime має вигляд купки скриптів на Perl та, відповідно, здатний працювати тільки на десктопі.
А в мене ідея зробити застосунок для Apple Watch та скористатись повсюдністю годинника. У WatchOS є така цікава можливість. як long look для повідомлень — фактично цілий інтерактивний екран, доступний безпосередньо при перегляді сповіщення.
До того ж використання бази даних CloudKit та універсальності SwiftUI дозволяє легко зробити застосунок, в який можна заносити з десктопу, з телефону чи з годинника — де зручніше. А інтеграція Apple Health може автоматично логувати сон та тренування. Причому виглядає так, ніби все це не потребує багато зусиль, динамічного програмування і так далі. Ну, подивимось.
03.02.2024
JIT, чому Ruby повільний, та як він може стати швидшим
Читав нещодавно статтю про те, як реалізацію парсера на Ruby зробили швидше за С. Хотілося прокоментувати, що це в принципі значить. (Все це також стосується майже будь-якої динамічно типізованої мови, будь то JavaScript, Python, і так далі.)
Робота будь-якої програми більш-менш зводиться до операцій над памʼяттю. Саме тут й криється головна різниця між статичними та динамічними мовами: якщо статична мова, як С, під час компіляції знає форму даних в памʼяті, то динамічна, як Ruby, нічого про неї не знає.
Наприклад, в С структурований тип має зафіксований перелік атрибутів. А в Ruby ми можемо додати чи видалити атрибут в будь-який момент. Так само можемо взагалі передавати обʼєкти різних класів у функцію та користуватись duck typing.
А значить, при кожному зверненні до змінної чи атрибута Ruby мусить спочатку знайти його в памʼяті. Це і є повільний етап. (А не інтерпретація коду, бо код давно ніхто не інтерпретує, а компілює наперед в інструкції віртуальної машини.)
Втім, в реальних програмах структури даних не змінюються часто. Тож нормальний код на Ruby теж можна було б привʼязати до фіксованої форми даних та компілювати так само як і C. Саме це й робить JIT - виявляє фрагменти коду, що працюють зі стабільними структурами даних, та компілює їх.
Однак JIT пришвидшує тільки ті програми, де структури даних сталі, що залежить від стилю коду. Якщо динамічно утворювати нові атрибути в різних місцях логіки, то структура класу не буде сталою, а значить, JIT залучений не буде. Більше можна почитати в статті зверху.
02.02.2024
Масові операції в OpenSearch - практика
-
Масове оновлення скриптом — може, не така вже й хороша ідея. Працює це повільно. Що таке повільно — поясню: припустимо при типовій роботі база отримує 1000 нових документів на хвилину. Логічно, що в такому разі ресурси нашої бази розраховані на таку швидкість індексації (плюс стільки, за скільки ми переплачуємо для резерву). З цього випливає, що масове оновлення буде відбуватися десь з такою самою швидкістю — теж 1000 документів на хвилину. На мільйон документів піде 17 годин. І це оптимістична оцінка, бо оновлення старих документів повільніше, ніж утворення або оновлення свіжих. Я б радив придумати, як уникнути оновлення та спиратися на дані, які вже є.
-
Трансформації — теж не дуже швидка операція, бо під час трансформації OpenSearch виконує по одному пошуку на кожну комірку агрегації. На мільйонах документів та сотнях тисяч комірок виходить теж довго. Можливо, дні стовідсоткової завантаженості процесора.
-
З прозорістю в OpenSearch все складно. Зрозуміти, скільки залишилось трансформації, можна хіба за поточними результатами. А ще вона може зупинитись з помилкою. А дізнатись, що то була за помилка, можна тільки з журналу, бо ззовні видно тільки помилку “верхнього рівня”, типу “помилка трансформації” а в неї схована причина - “не вдалося виконати пошук”, а в неї першопричина - “запит має надто багато пунктів”. Все це можна побачити в журналі. А яке це має відношення до моєї трансформації (бо в ній запит не такий вже й складний!), та як виправити — то вже без форумів не розберешся.
-
Одним словом, за зручністю та… “міцністю” до PostgreSQL тут далеко. І це такий момент БД, який я не часто чую в обговореннях — якщо до PostgreSQL майже завжди можна ставитись до бастіону надійності, де нічого непередбачуваного не відбудеться, то OpenSearch все ж тільки ще один сервіс, зі своїми багами та сюрпризами.
01.02.2024
ARF - автовідповідач про спам
Давно не писав нічого про SMTP. Є така прикольна поштова технологія, що отримувач позначає лист як спам, то відправнику надходить про це спеціальний звіт. Називається звіт Abuse Reporting Format (автор його, до речі, родом з України.)
Звіт має дуже просту форму, він складається з оригінального листа, загорнутого в ще один, зі спеціальними заголовками. Взагалі кажучи, стандарт поштового повідомлення може бути вкладеним скільки завгодно раз, наприклад, можна зробити звіт на звіт. Це не так вже й дивно, бо я бачив перенаправлений звіт, який був згенерований системою перевірки на спам, а потім загорнутий головним поштовим сервісом отримувача. Отак.
Звіт надсилається за адресою відправника “з конверта”, яка може відрізнятися від того, що ми бачимо в листі. Зазвичай цією адресою буде технічна скриня сервісу відправника. Відправник має отримати звіт та, певно, подумати над своєю поведінкою.
Є тільки один нюанс — з великих поштових сервісів ARF надсилають тільки Yahoo та Microsoft. А левова частка пошти надходить GMail, який ніяких звітів не надсилає. Ба більше, в GMail взагалі немає способу дізнатись долю листа. Чому так? Як я розумію, бо GMail не хоче, щоб спамери отримували інформацію про успішність своїх розсилок. Отакої — технологія гарна, але толку з неї мало.
31.01.2024
Масове редагування в OpenSearch
Після реляційних баз масові операції в OpenSearch виглядають дуже дивно. Головне, що треба зрозуміти — тут немає ніякої однорідності між документами; якщо в SQL ми знаємо, що операція UPDATE
успішно закінчилась або не була застосована взагалі, то в OpenSearch можна відразу планувати, що успіх буде частковим. А хто звик працювати з розподіленими системами — напевно, не побачить в цьому нічого дивного.
З поганого: масове редагування утворює конфлікти, якщо документ був змінений іншою операцією. Зате якщо скрипт редагування розробити ідемпотентним, то можна запускати команду ще та ще раз, поки не досягнеш повного успіху.
З хорошого: можна написати цілий скрипт (на мові Painless), який буде робити складну логіку. (Але тільки в межах одного документа.) Ще можна обмежити дію операції результатами пошуку. Я раджу визначити маркер успішно проведеної операції та включити в умови пошуку його відсутність. (Наприклад, якщо операція додає атрибут — то відсутність атрибута.)
…А ще корисно мати перед OpenSearch якийсь буфер (Кафку, тобто), щоб можна було призупинити постачання даних та зробити масову операцію без конфліктів.
Тепер, зовсім неприємне: так звана “динамічна типізація” атрибутів (dynamic mapping
). Річ у тім, що в кожного атрибуту є тип, який впливає на індексацію та доступні операції. Цей тип можна вказати заздалегідь, але якщо цього не зробити, OpenSearch призначить тип автоматично. Це ніби добре, але тип атрибута неможливо змінити без повної переіндексації, тобто копіювання документів в новий індекс з правильними типами — а потім, ймовірно, копіювання назад, якщо назва індексу для вас має значення. Тому, якщо у вас документи мають передбачувану структуру, я раджу вимкнути динамічну типізацію (вказати "dynamic":"strict"
) - тоді OpenSearch відмовить в індексації документів з невідомими атрибутами. Що набагато краще, ніж індексація аби як.
30.01.2024
Паралельні обчислення в Ruby
Я вже зачіпляв паралелізацію запитів в Ruby, але тут стало питання про доповнення Ruby бінарним кодом, та я вирішив нагадати собі, як там паралелізація працює.
Взагалі, в одному процесі Ruby може одночасно працювати тільки один потік коду. Це те, що називається GVL - Global VM Lock, раніше відомий як GIL. Віртуальна машина Ruby не розрахована на паралельне виконання.
Міф: раніше в Ruby були “зелені” потоки, а тепер системні, тож сучасний Ruby здатний на паралелізацію. Так, клас Thread створює системні потоки — які можуть бути запущені паралельно на всіх ядрах процесора. Все одно — через GVL всі окрім одного потоку будуть заблокованими.
Правда: щоб досягти справжнього паралельного виконання, потрібно створити декілька процесів (наприклад, через Process.fork). Це працює тому, що кожний процес — це окрема копія віртуальної машини, з власним GVL. Але, звісно, ми втрачаємо спільний простір змінних; окрім того, створення процесів повільне та не підходить для “локальної” паралелізації.
Всі серверні програми на Ruby використовують як процеси — щоб залучити всі ядра процесора, так і потоки — щоб ефективно обробляти ввід/вивід.
Сучасний нюанс: тепер є ще клас Ractor. У кожного Ractor свій GVL. Магії немає — рактори, як і процеси, ізолюють свої змінні. Зате рактори створюються швидше процесів (кожний рактор — окремий потік) та мають зручний механізм обміну даними. Рактори дозволяють виконувати на Ruby справжні паралельні обчислення.
Та, останній момент, про бінарні бібліотеки: звичайний виклик функції з бібліотеки нічого з GVL не робить та на логіку не впливає. Проте тут є спосіб відімкнути GVL, викликом rb_thread_call_without_gvl. Це відкриває шлях для паралельного виконання іншого потоку, якщо такий є. Звісно, без GVL не можна робити нічого з Ruby, зате можна робити внутрішні обчислення чи ввід/вивід.
29.01.2024
Вбудовування бінарної бібліотеки в Ruby - словничок
Викликати бінарний код з Ruby - це настільки нормально, що стандартна бібліотека робить це щосекунди. На те є стандартний механізм інтеграції, який починається з ruby.h. Це заголовок C. На цей час С є спільною фундацією майже для всіх компільованих мов, тож будь то Go, Crystal, чи навіть асемблер, все одно буде інтегруватись через C.
Щоб зінтегруватись, ми повинні збудувати shared library, або ж як їх знають на Windows, DLL. При цьому ми підʼєднаємо до нашої бібліотеки “адаптер” до самого Ruby; на то є модуль mkmf. Сама бібліотека може бути написана будь-якою мовою, яка вміє створювати shared library.
Далі — у бібліотеці ми оголошуємо клас чи модуль: rb_define_module та наповнюємо його. А в Ruby - просто робимо бібліотеці require
, та навіть нічого не треба знати про те, що вона зовнішня. Магія!
Втім, за досвідом, саме код інтеграції, який ховається за цією магією, буде найнеприємнішим. По-перше, він має бути написаний на C (або тонкій абстракції над C - такій, як CGO). По-друге, і це ще не все, бо доведеться конвертувати типи в спеціальні для Ruby та робити інші цікаві речі.
Щоб уникнути такого коду, я знаю два інструменти. SWIG - це генератор коду інтеграції з заголовків C. Тобто достатньо зробити звичайну бібліотеку без інтеграції з Ruby. Або навіть взяти чужу - 14 років тому я так загортав glew - бібліотеку доповнень до OpenGL: SWIG особливо гарний для інтеграції великих чужих бібліотек.
А FFI то вже модуль для Ruby, який здатний зінтегрувати готову динамічну бібліотеку. Робить він це через власний шар C, який ми метапрограмуємо з Ruby. Тому FFI буде повільніше, ніж SWIG чи пряма інтеграція. Хоча це буде помітно тільки на коротких та частих викликах. FFI має сенс брати там, де лізти в вихідний код бібліотеки не можна або не хочеться.
28.01.2024
Як я обрав Go, а не Rust
Go та Rust - багато в чому мови-конкуренти. Я жартую, що існує закон всесвіту, що для кожної програми на Go існує еквівалент на Rust. Наприклад, бачив заміни для ElasticSearch: Zinc та Sonic. Або, як вже писав, Wails та Tauri як альтернативи Electron.
Чисто абстрактно, я мав би бути фанатом Rust: чітка система типів, відсутність GC, дженерики з самого початку. Але ні — я вже восьмий рік працюю з Go, дедалі більше, а до Rust так і не дійшов. На то є історія.
В далекому 2016 році в мене був прототип рішення на Ruby, який ганяв багато даних через Redis та вже використовував вбудовану в Redis мову Lua, щоб робити частину обчислень прямо в базі. Для прототипу це було круто, проте для кінцевого продукту надто ускладнено. Вирішив зробити свою in-memory БД, яка і масиви даних тримає, і обчислення вміє робити.
На той час з системних мов програмування я знав тільки C/C++, та й те без досвіду розробки продуктів. На слуху були… Go та Rust. В цьому плані з 8 років нічого не змінилось (та й добре.) Головною задачею в мене було відтворити логіку, та зробити це за лічені дні, бо строки вже тиснули. Для перевірки обрав реалізацію невеличкого модуля, центрального для бізнес-логіки.
Спочатку я спробував Rust (бо, я ж кажу, він мені більше подобається.) Та швидко зрозумів, що на опанування системи позик (borrow) в Rust піде багато часу. Причому без неї навіть простої програми не побудуєш; займи не можна відкласти на майбутнє, як частини перевірок TypeScript. Це логічно, бо без позик Rust просто не знає, коли звільняти памʼять.
Тому перейшов до Go. В Go є найкращий інтерактивний підручник, який минулого року навіть переклали українською. З підручником я за годину зрозумів достатньо мови, щоб зробити свій тестовий модуль… та переконатись, що я зможу рухатись далі. Безперечно, Go не надає таких гарантій, як Rust, та потребує GC… але швидкість навчання була гідним відшкодуванням за це.
Так, за півдня я обрав Go, а не Rust. Проєкт був успішним. Та, річ у тім. що коли вже програмуєш на Go, то Rust виглядає як крок убік… крок, який досі в мене немає ніяких підстав робити.
27.01.2024
Тобі потрібна історія буфера обміну
У всіх свої інструменти та підходи до роботи за компʼютером. Коли працюєш в парі, можна багато дізнатись нового для себе. Однак є одна техніка, за відсутністю якої мене починає просто сіпати. Це історія буфера обміну.
Історія буфера обміну — це попросту, можливість вставити не останнє скопійоване значення, а одне з попередніх. Існує багато утиліт, які надають історію. Я просто користуюся вбудованою функціональністю Alfred. Вона привʼязана на комбінацію Hyper+V
; тобто використання історії майже не відрізняється від звичайної вставки.
Згадай про це наступного разу, як повертатимешся до місця, яке тільки-но копіював, але вже затер наступним, а тепер воно потрібно знову. Причому так роблять й не по одному колу! Історія робить відтворення попереднього буфера тривіальним.
А ще не раз доводилося в історії знаходити значення, скопійовані набагато раніше; в Alfred можна зберігати значення до трьох місяців. Наприклад, під час дослідження скопіював шлях до файлу чи цікавий ID, а записати забув.
На налаштування історії піде не більше декількох хвилин, а далі вона буде готова тоді, коли знадобиться.
26.01.2024
Функція історії в VS Code
🗑️✨ Пост подяки локальній історії в VS Code, також відомої як Timeline. Шукай її на тій самій вкладці бічної панелі, що й каталог файлів.
Вона існує ще з 2020 та, на перший погляд, зайва, коли є Git. Але ж локальна історія повністю автоматично фіксує кожну збережену версію файлу. Тому вона корисна як резервна копія.
Сьогодні як раз трапилась така халепа, після якої я дійсно поважаю локальну історію. Файл я не просто відредагував, а видалив за помилкою. Причому як раз під час підготовки коміту. До того ж якщо файл видалити не напряму, а “відкинути зміни” в новоствореному файлі, то він не потрапить в смітник, а буде “НАЗАВЖДИ ВТРАЧЕНИЙ”, як попереджає VS Code.
От в такій ситуації я й згадав про локальну історію. Втім, вона працює на рівні файлу, а файл вже втрачений… Здогадався створити файл наново. Та, о диво! Таймлайн цього порожнього файлу містив всі попередні версії. Я вмить повернувся до останньої версії та абсолютно нічого не втратив.
Тепер локальна історія займає місце в моєму арсеналі, поруч з історією буфера обміну (про який мені ще треба написати) та іншими механізованими помічниками.
PS. Знаю ще аналогічну утиліту Dura, яка автоматично зберігає всі зміни в Git, незалежно від редактора.