Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!

Пости з тегом #OpenSearch

12.08.2022

Перші враження від ElasticSearch

⏱🗂🔍 Сьогодні розбираюсь з ElasticSearch. Відкриття дня: це не двигун повнотекстового пошуку, як я завжди думав.

ElasticSearch це NoSQL база даних. Вона не тільки шукає, а й зберігає документи, тобто в деяких випадках може бути єдиною базою в проєкті. Як виявляється, вона заточена під роботу з часовими потоками даних. Приклад з підручника - структуровані журнальні записи. Дані можно і шукати, і агрегувати. До того ж, є можливість згортати (rollup) старі записи, коли від них залишаються тільки результати агрегацій за деякими атрибутами.

Ще одна вигідна якість - ElasticSearch індексує нові документи миттєво. Ще її можна використати з AWS Kinesis Firehose - звідти я на неЇ і потрапив. (До речі, з минулого року ElasticSearch звузили ліцензію і тепер у AWS та навіть у Homebrew доступний тільки відкритий форк - OpenSearch.)

Поки ще ретельно не тестував, але обіцяє буди гарною заміною доморощеним засобам на базі Redshift.


16.08.2022

Що вміє ElasticSearch

🗄🗄🗄 Сьогодні продовжую розбиратися з ElasticSearch, тобто насправді з OpenSearch.

Завантажив датасет на 5 ГБ. Для цього є операція _bulk, в яку влазить невідомо скільки записів за раз. Сто тисяч точно влазить, а більше соромно було спробувати. Звісно, записи відправляються в форматі рядків JSON, тобто якщо ви відвантажили JSON з іншої бази, то його можна без перетворень залити в OpenSearch. Тільки й різниці, що перед кожним рядком треба додати рядок з командою _create. П'ять гігабайтів база забрала десь за 8 хвилин - це з негайною індексацією. Відзначу, що непогано працює система типів - mappings (а саме, значення типу "дата" або "IP адреса" проходять перевірку).

Після цього до бази можна ставити запити - або за допомогою езотеричних JSON-структур, або SQL з купою обмежень.І це, мабуть, найслабкіше місце OpenSearch. З прикладів видно, що база вміє дуже багато - наприклад, миттєво проводить пошук та агрегації. Але це якщо навчитись писати до неї запити.

Але ж так само з Redis, Mongo, CouchDB або будь-якою новітньою базою даних. У кожної є деякі переваги над SQL базами. Як правило це набагато краще продуктивність у деяких специфічних сценаріях. І кожну з них треба знати, коли і як використати. Натомість SQL бази можуть "все", але ж в продуктивності програють.


09.06.2023

PostgreSQL vs Elasticsearch

Поступив запит: чим Elasticsearch краще за PostgreSQL. Спробую сформулювати. Наперед скажу, що питання скоріше не чим, а де або для чого.

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

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

Як я вже писав, для вирішення сучасних потреб в масштабуванні зʼявилися бази даних NoSQL. В їхньому числі й Elasticsearch (так, як я вже писав, Elasticsearch - справжня база даних.) В серці кожної NoSQL бази сидить компроміс: так, вона масштабується, але характер використання буде обмежений. Тому, при виборі NoSQL бази, треба дуже уважно дивитись, що вона вміє, та чи не буде вам потрібно щось інше.

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

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

На останнє, не раджу читати абстрактні порівняння вигляду “знайди 10 відмінностей”.  “PostgreSQL - реляційна база даних. Elasticsearch - документна” і так далі.  З таким підходом в сучасних базах (та й взагалі технологіях) швидко загубишся. Щоб обрати базу, треба розуміти, що вона робить краще інших, та може ще важливіше — чого ніяк не робить. А якщо є сумніви — беріть PostgreSQL. :)


10.07.2023

ElasticSearch - база даних як злив

На мою думку, що треба зрозуміти для ефективного використання ElasticSearch/OpenSearch - ця база у вашій архітектурі має бути стоком. Дані мають стікатись в OpenSearch в готовому, остаточному вигляді. Все, що залишається робити OpenSearch - це а) пошук та б) агрегація.

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

На практиці це значить, що дані доведеться “підсипати” до документів, що вже існують. Для цього є операція update - це зрозуміло. Що менш очевидно: ця команда здатна приймати скрипт, який виконає оновлення, замість простої заміни значень полів. Інтуїтивно мені здавалось, що цей підхід надо повільний для постійного використання. Але ж ні — оновлення скриптом працює дуже швидко, на рівні з запитами SQL. Які, власне, теж є скриптами, які треба розпарсити та виконати. Єдина різниця, що в базу SQL ми не передаємо їх в середині JSON. А так — навіть параметри до скрипту передаються окремо, так само як до підготованих запитів SQL.

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


03.08.2023

Міграції в OpenSearch

Хоч в OpenSearch й немає жорстко заданої структури даних, потреба в однорідності даних залишається. Ну, може, не у всіх застосунках, може десь є проєкт, який просто індексує аби який JSON - але зазвичай все ж таки ми очікуємо бачити в документах певні атрибути.

Та, як всі знають, сьогодні атрибути одні — завтра можуть бути інші. Що робити з тими документами, які вже існують? На відміну від бази SQL, де можна легко додати новий стовпчик зі значенням за замовчуванням, в OpenSearch готового рішення немає.

Найпростіший технічно варіант — це нічого не робити, а при читанні чи пошуку враховувати, що поле може бути порожнім (тобто null). Не знаю, як ви, а я дуже не люблю перевірки на null “про всяк випадок”. Особливо коли воно скероване не бізнес-логікою, а невпевненістю бази даних.

Можна ввести версії схеми документів. Тобто в кожному документі є атрибут версії. При читанні документа робиться міграція до поточної версії (та, можливо, запис її в базу.) Так я робив, тільки не в OpenSearch, а в CouchDB - моделі документів в них дуже схожі. Впровадження явної версії спрощує обробку порожніх полів. Вона нагадує міграції бази, як в Rails, тільки на рівні кожного документа. (Особливо цікаво воно в розподіленій базі CouchDB, бо старі документи можуть бути приховані на вузлі, який давно не синхронізувався.)

Нарешті, є механізм Update by query, який, здається, і призначений робити міграції даних. Тут можна запустити масову операцію оновлення — хоч для всіх документів. Є тільки одна проблема — на відміну від SQL бази, ця операція не є атомарною або транзакційною. Вона обробляє документи поступово, та ті документи, що встигли помінятись іншим шляхом, не будуть оброблені взагалі.


24.08.2023

Ролапи в OpenSearch

Є така функція в OpenSearch - ролап (rollup). Це така собі передагрегація. Вона дозволяє робити обмежений набір агрегацій, маючи тільки стисле відображення оригінальних даних — тобто заощадити на місці, коли крім статистики нічого не потрібно.

Я коли її побачив, то дуже зрадів, бо підхід мені добре знайомий. В Redshift (або PostgreSQL) таке доводилось робити вручну. Як я писав, з PostgreSQL можна взагалі зробити майже будь-що — тільки на особливу оптимізацію не треба розраховувати.

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

CREATE TABLE stats AS
SELECT DATE(created_at) as date, user_id, COUNT(*) as cnt
FROM events
GROUP BY date, user_id

Тепер таблицю можна використати для агрегації за більшими комірками:

-- статистика по даті
SELECT date, SUM(cnt) FROM stats WHERE user_id=123 GROUP BY date
-- статистика по користувачам
SELECT user_id, SUM(cnt) FROM stats WHERE date>'2023-01-01' AND date<'2023-08-24'  GROUP BY user_id

Залишається питання — як цю таблицю оновляти? Добре, якщо агрегація відбувається за поточною датою, тобто дані за вчора вже не поміняються. (Це й з боку розуміння користувачами краще.) Проте цей підхід до передагрегації не підведе навіть коли в майбутньому можуть зʼявиться нові дані для комірки, що вже існує: два чи більше рядків з однаковим ключем будуть просто підсумовані.

Приблизно так само воно й працює в OpenSearch, тільки в напівавтоматичному режимі. Зокрема, можна робити агрегацію разом по ролапах та по звичайних індексах — зручна абстракція.


31.08.2023

Основа архітектури ElasticSearch, або: чому він такий спритний?

Вирішив для себе зрозуміти, в чому ж така перевага ElasticSearch (та OpenSearch, який є його відгалуженням) по швидкості індексування та пошуку. Розповім про головне.

ElasticSearch побудований на рушії пошуку Lucene. Дуже цікаво, що базова можливість в Lucene вузька: це словник документів за ключовими словами. Як пишуть в цій статті, інші типи пошуку реалізовані через той самий словник — через пошук ключових слів або їх префіксів. (Тут хотілося б ще дорозслідувати.)

Ну, добре, пошук в словнику — операція добре вивчена, двійковий пошук в університеті проходять. Але типовий запит потребує пошуку не за одним, а за декількома словами. Тобто множини документів від кожного слова ще потрібно буде поєднати. Щоб робити це ефективно, Lucene, як пишуть тут використовує для множин… добре мені знайомі бітові мапи. Ось він, секрет швидкості!

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


01.09.2023

Як OpenSearch індексує числа

Вчора я написав трохи застарілу інформацію: насправді десь з 2016 року Lucene вже не зберігає числа у вигляді рядків. Тобто, така можливість залишилась, але тепер є інший спосіб індексування: k-d дерево. Я не пишу “кращий”, бо все залежить від того, що з ним робити.

K-d дерево це дерево множин точок у K-вимірному просторі. Кожен рівень дерева ділить множину навпіл за одним з вимірів. Таким чином, дерево є збалансованим, тобто має рівну кількість точок в кожній гілці — а значить, мінімальну кількість розгалужень.

Але ми хотіли індексувати числа. До чого тут якісь простори? Так, технічно число задає точку в 1-вимірному просторі, а узагальнення на багато вимірів дозволяє використати ту саму структуру даних також для географічних координат, діапазонів та інших застосувань, де важливо порівнювати одночасно декілька показників. У випадку чисел, дерево просто ділить діапазон навпіл, поки не досягне зазначеного розміру множини, що залишається.

В Lucene K-d дерева зберігаються окремо від інвертованого індексу (словника, про який я писав вчора.) Тобто заради ефективності пошуку архітектори впровадили цілу нову структуру даних та формат файлу. З цього боку зрозуміло, чому обрали саме k-d дерева, які покривають багато потреб, а не тільки пошук чисел.

Пошук в K-d дереві відбувається завжди за діапазоном. Кожний вузол дерева визначає діапазон значень всередині. Він може бути або цілком всередині, або цілком ззовні, або перетинати діапазон пошуку. Ті вузли, що всередині, додаються до результату, ззовні — відкидаються, та тільки в ті, що перетинають, доведеться спускатись на наступний рівень та дивитись на менші підмножини. Таким чином ми маємо справу з найбільшими можливими множинами, та відповідно — з найменшою їх кількістю.

Тепер, про доцільність такого індексу в контексті чисел. Якщо вам потрібно шукати числа за діапазоном, то він ефективний, питань нема. А якщо числа — це ідентифікатори, а шукати потрібно тільки по рівності, то алгоритм залишиться такий самий — хоч для діапазону з одного значення. Ба більше, оскільки множини в k-d дереві не доходять до такої деталізації, то наприкінці пошуку відбувається перебір — не дуже великий, але все ж зайвий.

Тому для індексації чисельних ідентифікаторів краще брати тип keyword. Тоді для них буде використаний звичайний інвертований індекс, оптимізований саме для пошуку конкретного значення.


02.09.2023

Чим пошук в ElasticSearch відрізняється від PostgreSQL

Наступне для мене питання — в PostgreSQL теж є індекси. Чим тоді ElasticSearch краще?

Взагалі, якщо почитати документацію про PostgreSQL, або подивитись це гарне відео, то можна помітити, що аналоги індексам ElasticSearch тут теж є. Індекси GIN то є інвертований словник, який теж зберігає групу документів, в яких є те чи інше слово. Індекси GiST то більш-менш те саме дерево пошуку, як ElasticSearch використовує для чисел та геолокацій.

Щоправда, я не бачив, щоб GIN/GiST використовували для простих значень — рядків або чисел — так, як це робить ElasticSearch. Ну, може я чогось не знаю. Але річ не в тім. Як я розумію, справжня перевага ElasticSearch у швидкому поєднанні багатьох індексів.

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

Бітові мапи настільки ефективні, що PostgreSQL їх теж використовує. Мабуть, доводилось в EXPLAIN бачити назву bitmap scan? Щоб поєднати два індекси (а точніше, дві умови, що їх використають), PostgreSQL будує бітові мапи для рядків, які відповідають кожній умові та поєднує їх бітовими операціями.

Тільки різниця в тому, що в ElasticSearch бітові мапи завжди готові до використання, а PostgreSQL ще має їх побудувати з індексу. Та також, в PostgreSQL дані нормалізовані, тому їх доведеться зводити до купи з різних таблиць, де будуть використані різні індекси, які вже так просто не поєднаєш.

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

Отже, зорієнтованість на швидкий пошук за багатьма умовами, денормалізація та відсутність транзакцій робить ElasticSearch швидше за PostgreSQL.


03.09.2023

Для чого гарний ElasticSearch?

Нарешті, з цими новими знаннями, можу відповісти на питання — що (окрім повнотекстового пошуку) доцільно робити на ElasticSearch.


11.09.2023

Інтуїція про бази даних. Чому OpenSearch агрегує дані швидше, ніж Redshift?

Отримав сьогодні таке питання — напівжартома — бо дійсно, те, що в Redshift займає десять секунд, в OpenSearch може бути виконано миттєво. Йдеться про агрегаційний запит на кшталт “підрахувати статистику суми поля по часовій шкалі.” Думаю, дуже важливо інтуїтивно розуміти, в чому саме різниця. Я вже писав про архітектуру OpenSearch/ElasticSearch, але що по цьому конкретному випадку?

Моя перша здогадка була — що OpenSearch тримає ці дані в памʼяті, а Redshift ні. Проте при перевірці виявилось, що це не зовсім так. Насправді OpenSearch не завантажує всі індекси в памʼять. Зате індекси на диску зберігаються в такому форматі, який готовий для прямого читання в структури памʼяті. Для того є механізм mmap, який уможливлює швидкий доступ до файлу в памʼяті без зайвого копіювання даних. Про це можна почитати в документації до модуля Store.

Проте mmap використовується, напевно, для читання індексів в усіх базах даних. Справжня причина швидкості OpenSearch не в тому, що індекси розташовані в памʼяті.

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

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


20.11.2023

Ролапи в ElasticSearch тепер застарілі?

Не встигли ми інтегрувати ролапи (ні, серйозно - не встигли), аж раптом остання версія ElasticSearch - 8.11 - оголошує, що вони deprecated. Хоч в нас не ElasticSearch, а OpenSearch, я б все одно не покладав великих надій на цю функцію, бо на практиці OpenSearch слідує за ElasticSearch по своїм планам. (OpenSearch це гілка ElasticSearch з відкритою ліцензією.)

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

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

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

А ще перетворення здатні на повноцінний заскриптований map-reduce, тобто можна реалізувати майже будь-яку логіку.


21.11.2023

Нюанси перетворення індексів в OpenSearch

Перетворення індексів в OpenSearch (або ElasticSearch) кажуть, що вони схожі на матеріалізовані розрізи в реляційних базах таких, як PostgreSQL.

Схожість в тому, що перетворення утворюють новий індекс, який побудований на даних з індексу-джерела. (Якщо що, то “індексом” в OpenSearch називається колекція документів, а ніяк не механізм пошуку. Але, якщо нахилити голову, то це і є “індекс для пошуку”, а таблиці до нього просто немає.)

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

По-друге, цікаво те, як саме тут реалізована інкрементальність. OpenSearch відстежує перелік документів, які змінилися від останнього виконання. Далі — обчислюються всі комірки агрегації, які містять ці документи. І, нарешті, кожна з комірок обчислюється повторно. (В порівняння з розрізами в Redshift, це досить просто для розуміння.)

Складно стає, коли стає потрібно видалити вихідні дані та зберегти при цьому агрегації. З видаленими документами самими по собі OpenSearch нічого не робить. Але якщо відповідні комірки будуть обчислені наново — що відбудеться, якщо нові чи оновлені документи потраплять до тих самих комірок — то вже без врахування видалених документів.

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


11.01.2024

Map/Reduce в OpenSearch

Сьогодні мав досвід з так званою Scripted metric в OpenSearch. (Така сама є й в ElasticSearch).

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

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

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

Якщо просто, то агрегація потребує два головних скрипти, та обидва з них мають форму reduce. Один збирає дані з кожного документа в сегменті до структури-акумулятора, другий — поєднує акумулятори з усіх сегментів. Обмежень на акумулятор немає.

Мені це знадобилося, щоб знайти документи, в яких поле-масив містить дублікати. Не проміж документів дублікати, а всередині одного документа. Такої агрегації я не знайшов, надто специфічна задача. Зате скриптом вийшло майже тривіально.


23.01.2024

Opensearch DSL в Ruby, та чому він мені не подобається

У Opensearch для Ruby є DSL, який, звісно, запозичений в ElasticSearch DSL. Чим довше ним користуюсь, тим менше він мені подобається.

Може я не правий, але цей DSL для мене це приклад “DSL задля DSL”. По суті, він повторює те ж саме, що можна зробити побудовою JSON-у, тільки замість масивів та хешів отримуємо блоки. Зате, бачите, DSL придумали такий, що блоки не передають контекстну змінну всередину — все “красиво”, команди пишуться “чистими”:

definition = search do
  query do
    match title: 'test'
  end
end

Тільки магії не існує; відсутність явного контексту значить, що він передається прихованим. По-перше, код реалізації від того дуже складний. По-друге, через набуття контексту з DSL код всередині блоку втрачає батьківський контекст та вже не може викликати методи з власного класу. Від того рефакторити код з цим DSL дуже боляче.

(До речі, щоб отримати доступ до self всередині блоку, можна призначити його звичайній локальній змінній: me = self. Локальні змінні мають локальну область застосування, їх в нас не відібрати!)

Якщо порівнювати з найуспішнішим DSL для Ruby - Arel - то в ньому завжди контекст (тобто запит в процесі побудови) є явним; його можна зберігати в змінну, передавати аргументом, використовувати багато разів. А не оце все.


31.01.2024

Масове редагування в OpenSearch

Після реляційних баз масові операції в OpenSearch виглядають дуже дивно. Головне, що треба зрозуміти — тут немає ніякої однорідності між документами; якщо в SQL ми знаємо, що операція UPDATE успішно закінчилась або не була застосована взагалі, то в OpenSearch можна відразу планувати, що успіх буде частковим. А хто звик працювати з розподіленими системами — напевно, не побачить в цьому нічого дивного.

З поганого: масове редагування утворює конфлікти, якщо документ був змінений іншою операцією. Зате якщо скрипт редагування розробити ідемпотентним, то можна запускати команду ще та ще раз, поки не досягнеш повного успіху.

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

…А ще корисно мати перед OpenSearch якийсь буфер (Кафку, тобто), щоб можна було призупинити постачання даних та зробити масову операцію без конфліктів.

Тепер, зовсім неприємне: так звана “динамічна типізація” атрибутів (dynamic mapping). Річ у тім, що в кожного атрибуту є тип, який впливає на індексацію та доступні операції. Цей тип можна вказати заздалегідь, але якщо цього не зробити, OpenSearch призначить тип автоматично. Це ніби добре, але тип атрибута неможливо змінити без повної переіндексації, тобто копіювання документів в новий індекс з правильними типами — а потім, ймовірно, копіювання назад, якщо назва індексу для вас має значення. Тому, якщо у вас документи мають передбачувану структуру, я раджу вимкнути динамічну типізацію (вказати "dynamic":"strict") - тоді OpenSearch відмовить в індексації документів з невідомими атрибутами. Що набагато краще, ніж індексація аби як.


02.02.2024

Масові операції в OpenSearch - практика


20.02.2024

Операції з часом в OpenSearch

Поступово дізнаюся нові можливості OpenSearch (та ElasticSearch), які, хоч і базові, але чомусь на поверхні не лежать. Сьогодні — про те, що дати набагато гнучкіше, ніж я собі думав.

Взагалі OpenSearch зберігає дати як числа. Проте, як я розумію з коду, різниця в індексації є: як я писав, числа OpenSearch індексує деревом множин, щоб швидше шукати за діапазоном. Для дат ці множини будуються не аби як, а за одиницями часу: хвилинами, годинами й так далі: ось код. Звісно, це саме те, чого ми хочемо від пошуку за часом.

Але це таке, знати не потрібно. А що варто знати, так це те, що час можна вказати зі скругленням до потрібної одиниці. Наприклад, потрібно мені знайти записи за останній тиждень. Як вказати верхню межу? Відома проблема, що просто датою не можна — відбитки часу за останню дату будуть технічно більші за дату без часу. Тому можна вдаватись до хаків: написати 23:59:59 (і втратити відбитки за останню хвилину!), чи зробити строго менше наступної дати. Проте в OpenSearch можна просто зробити запит зі скругленням до дня:

{
  "range": {
    "timestamp": {
      "gte": "2024-02-12||/d",
      "lte": "2024-02-18||/d"
    }
  }
}

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

Та й повертати значення дати можна відразу в такому форматі, як нам потрібно: для документів з пошуку є опція docvalue_fields (дивна назва), а для агрегацій за датою просто format. Трохи незвично таке робити в базі, бо я звик, що бібліотека-клієнт віддає обʼєкти дати, з якими вже робиш що хочеш — проте за відсутністю такої бібліотеки дуже зручно.


26.03.2024

OpenSearch як рушій потоку подій

OpenSearch насправді вміє робити цікаві речі як сховище подій та рушій для архітектури Event sourcing.

Як я, певно, вже писав, OpenSearch дуже погано робить JOIN. Зате досить добре вміє GROUP BY в межах Transform job (раніше). Це вже основа того, щоб збирати повʼязані події та утворювати з них нову сутність.

Тепер, серед агрегацій, доступних для Transform job, доступна так звана Scripted metric aggregation (раніше). Це цілий map/reduce: за допомогою Scripted metric можна комбінувати дані у складні структури та повертати такі документи, як нам потрібно. Причому працює десь так само швидко, як і інші агрегації. (До речі, це не очевидно, але на вхід метрики потрапляють документи кожної групи окремо.)

І нарешті, Transform job можна робити й над індексами, отриманими з іншої Transform job. В мене був випадок, що одні й ті самі дані потрібно було групувати двома різними способами, тож однією Transform job такого не зробиш; але можна зробити дві, а потім — третю, що їх поєднає.

З недоліків — трансформації мають помітну затримку (мінімум хвилину), не всі потреби таке влаштує. Зате все це чудово масштабується: бо відбувається інкрементально та розподілено. Я поки в захваті.


11.04.2024

Експорт даних з OpenSearch

В OpenSearch немає очевидного способу відвантажити зміст бази — такого, як pg_dump, mysqldump або команди \copy. А потреба в такій операції, звісно, є — утворення резервної копії, синхронізація. Нещодавно просто потрібний був експорт даних, щоб проаналізувати їх разом.

Якщо поритися в документації, знайдемо механізм scroll. Він саме і надає можливість забрати з бази не одну сторінку пошуку, а абсолютно всі результати.

Scroll - це особливий режим пошуку; він вмикається опцією scroll в пошуковому запиті. Пошук все одно поверне одну сторінку результатів, але також ми отримаємо вказівник scroll ID, та за наступними сторінками підемо вже в окремий Scroll API.

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

Так зі Scroll можливо відвантажити не тільки весь індекс, але й результати будь-якого пошуку в OpenSearch. Ось такий цікавий механізм. Порівняно з PostgreSQL, то це навіть зручніше, бо працює по HTTP та не потребує особливих команд або доступу до локального диска.