Стендап Сьогодні
📢
Канал в Telegram @stendap_sogodni
🦣
@stendap_sogodni@shevtsov.me в Федиверсі
Пости з тегом #OpenSearch
13.05.2024
OpenSearch в тестовому оточенні
Як забезпечити кожному тесту чистий аркуш в OpenSearch? В теорії — просто. Проте щоб це ще й не гальмувало збірку, довелося попрацювати.
-
Зазвичай з PostgreSQL ми вживаємо транзакції. В OpenSearch транзакцій немає. Значить, просто скасувати зміни після тесту не вийде. Другий підхід — це команда
TRUNCATE. Такої в OpenSearch теж немає; щоправда, є API “delete by query”, яким можна видалити з індексу всі документи, але вона не ефективна. -
Отже, залишається тільки видаляти цілі індекси та створювати наново. На жаль, створення індексів не така швидка операція, щоб робити її без потреби. Тому я порефакторив код запитів, щоб він допускав відсутність індексів (тобто інтерпретував як порожній результат.) Таким чином, індекси ми створюємо тільки тоді, коли в них пишуться дані. (А значить, і видаляти потрібно не так багато, а коли тест не звертається до OpenSearch - то взагалі нічого.)
-
До речі, на відміну від PostgreSQL, схема даних зберігається в шаблонах, а не в самих індексах. Тому створювати індекси можна навіть не окремою операцією, а неявно, під час додавання до індексу першого документу.
-
Нарешті, спочатку я видаляв індекси так, як чищу таблиці в PostgreSQL, тобто кожний індекс окремим викликом. Виявилося, що це повільно (і навіть паралелізація викликів не допомагає.) Тому перейшов до очищення всіх індексів за префіксом
test. -
Ідея, якою я ще не скористався: для кожного тесту генерувати унікальний префікс для ізоляції даних. Тоді можна видаляти всі тестові дані наприкінці.
17.05.2024
Перевірка унікальності в OpenSearch
…А немає її! В ті чи інші причини. Певно, головним чином тому, що індексація відбувається асинхронно, а до індексації перевірки не буде.
Проте є один метод, перевірений часом. Це зробити унікальний ключ ідентифікатором. Таке працює в багатьох NoSQL базах, наприклад, в DynamoDB практично лягає в основу. Тільки з міркувань рівномірного розподілення значень в OpenSearch краще брати не прямо ключ (скажімо, URL сторінки), а хеш від нього.
(Взагалі підхід з використанням справжніх значень як ідентифікаторів називається “натуральні ключі” та був популярним в часи “голих” SQL баз. А з приходом ORM як головного методу доступу — занепав.)
Ідентифікатори в OpenSearch, звісно, унікальні. Але й тут криється нюанс - звичайна операція індексації не заборонить повторюваний ідентифікатор, а просто перезапише документ (тобто зробить “upsert”). А нам потрібна операція create.
Я вже натрапляв на проблему в тестах, що згенеровані фабрикою документи час від часу збігалися ідентифікаторами, вставлялися без create, та призводили до випадкових помилок.
01.06.2024
OpenSearch: база, де все відбувається не відразу
Нарешті зміг для себе зрозуміти, що мене понад усе дратує в роботі з OpenSearch: майже нічого в цій базі не відбувається синхронно. Я звик, що база даних є синхронною; звісно, реплікація займає час, але той вузол, з яким працюєш, все робить коли йому скажеш. В OpenSearch все не так.
Навіть з базовими діями — наприклад, індексацією одного документа — є спеціальний параметр refresh=wait_for, який змусить виклик чекати, поки документ не стане проіндексований. За замовчуванням індексація буде відкладена. Це відразу помічаєш у тестах, бо без цього параметра вони ніколи не будуть стабільними.
А коли маєш справу зі складнішими операціями, взагалі краще думати про них як про процеси; в деяких випадках — буквально. Наприклад, в PostgreSQL масове оновлення (UPDATE ... WHERE) теж може тривати навіть години, але таблиця може бути тільки у двох станах: не оновлена та оновлена. З OpenSearch масове оновлення опрацює скільки встигне та повернеться — далі запускай наново.
Так само і перетворення хоч виглядають, як матеріалізовані розрізи, але працюють сторінка за сторінкою, тобто результат завжди буде неповним: причому не в тому сенсі, що відповідати деякому старому стану вхідних даних — а бути десь посередині, причому ми ніколи не впевнені, де саме.
Все це добре, коли вас влаштовує кінцева узгодженість, а чи буде вона досягнена зараз, чи через хвилину — байдуже. Та це, певно, найбільша перешкода у використанні OpenSearch як традиційної бази даних.
12.02.2025
Оптимізація: треба йти глибше!
Далі, коли шукаєш причини уповільнення, мусиш ставити під сумнів кожний свій висновок та знаходити для нього обʼєктивні обґрунтування.
Вчора хотів написати пост “чому OpenSearch повільний, коли у вас надто багато шардів?” (Шард — це неподільний блок даних, як партиція; індекс — тобто колекція — складається з фіксованого набору шардів.) Всі радять робити шарди розміром у 10-50 Гб… але чи значить це, що робити їх меншими обовʼязково погано?
Виявилося, не дуже; дійсно, кожен шард коштує трохи памʼяті, але поки памʼять не вичерпана, додаткові шарди не суттєво впливають на швидкість операцій. Негативно — ні, але позитивно — теж ні, навіть попри те, що шард також є одиницею паралелізації. Бо щоб пришвидшити щось паралелізацією, потрібно більше ядер процесора, а не потоків.
Але це я знаю після того, як перевірив. А міг би залишитись із такою гадкою, витратити час на переформування індексів, та… нічого не отримати на заміну. Та це не перша та не остання ланка в розслідуванні! Перевіряти потрібно кожну.
Легко вказати на неприємну частину проєкту та сказати: проблема тут! (Мені, наприклад, доводилося витрачати час на алгоритмічну оптимізацію “некрасивого коду” там, де можна було обійтися як максимум кешем.) Але взагалі шукати причини — це ціле мистецтво. Треба знати, що важливо, що не важливо, та параноїдально собі не довіряти (що, до речі, називається науковим методом.)
Ото було колись — шукав, чому API повільний. Локально наче все було чудово, але більшість часу API проводив у серіалізації, тому я довго обирав оптимальний формат. А потім виявилося, що в продакшні є ще рукостискання TLS, та вся проблема зовсім не в серіалізації, а у встановленні підключення. Так що завжди перевіряти!
02.03.2025
Затримка оновлення в OpenSearch
В OpenSearch/ElasticSearch є така цікава особливість, що між внесенням змін та їхнім ефектом минає кілька секунд. Тобто буквально доведеться почекати, щоб побачити, що щось змінилось.
Причиною є те, що OpenSearch видає результати тільки через словники (“індекси” мовою інших баз), а оновлення словника відбувається періодично — як мінімум, щосекундно. Бо оновлення є дорогою операцією та краще, коли воно працює з цілою пачкою змін.
Зауважу, що затримка ніяк не є наслідком того, що OpenSearch є розподіленою базою. Навіть один індекс на одній машині буде оновлюватися так саме повільно. Взагалі OpenSearch оптимізований для великого масштабу, а на малому працює не набагато швидше. Також затримка не впливає на надійність — зміни нікуди не дінуться.
З досвідом традиційних СУБД це є повною нісенітницею. Проте типові використання OpenSearch - наприклад, аналіз журналів — не потребують моментального застосування змін. Будь-яка архітектура з OpenSearch всередині повинна розділяти запис та читання та передбачати ці затримки. Тому на роль головної бази - OLTP - вона фізично не підходить.
31.10.2025
Наскільки важливо розуміти спеціалізацію бази даних
Трохи допомагав хлопцям із проблемним навантаженням на ElasticSearch. Виявилося, проблема з запитами, де фігурував пошук зі скриптом. Скрипт виконував операцію на кшталт foo - bar > THRESHOLD.
Як побачив, то відразу все стало зрозуміло. OpenSearch будує з документів словники — де кожному значенню поля відповідає множина документів. OpenSearch швидкий, поки ти робиш пошук за словниками. Такий пошук виконується як операції над множинами, та тому є дійсно надзвичайно швидким, навіть коли в запиті згадується багато полів. Це — головна сила OpenSearch.
Але як тільки для пошуку доводиться читати самі документи — швидкість провалюється на порядок. Бо з операцій над множинами ми скочуємося до операцій над документами.
Тобто можливість пошуку за скриптом існує, щоб закрити рідку потребу, зробити щось неможливе можливим. Але вона ніяк не годиться для базового функціоналу. Це може приголомшити, якщо ти приходиш з бази загального призначення — як Postgres - де пошук по foo - bar не сильно відрізняється за пошуком по foo чи bar.
В цьому сенсі я завжди любив документацію з Redis - в ній для кожної команди виписана обчислювана складність. Колись я робив складний рушій на основі Redis, та це було критично важливо для планування.
А у випадку з OpenSearch відповідь така: хочеш шукати за різницею полів — зберігай цю різницю в ще одно поле - fooMinusBar. Та левову частку інтеграції OpenSearch складає саме планування наперед потрібних операцій та полів, які їх уможливлюють.
14.11.2025
Трошки з адміністрування OpenSearch
…Передісторія: так склалося що в одного з індексів OpenSearch цілих 100 шардів на приблизно 5 Гб даних. Стандартні рекомендації — це шарди розміром у 10-50 Гб, тобто десь у 1000 разів більші. Ну буває.
Десь місяців із 9 тому я писав, що це ні на що не впливає, але виявляється, все залежить від навантаження. Отже, стала задача звести кількість шардів до розумної… ну, наприклад, 2.
Робити це “наживу” OpenSearch не вміє. Тобто нам доведеться замінити індекс. (До речі, саме тому в OpenSearch також є псевдоніми індексів (alias) - щоб легко підміняти індекси або цілі набори.)
Є операція reindex. Вона бере кожний документ та копіює його в новий індекс. Триває все це кілька годин. Я вже писав, що все в OpenSearch, що працює на рівні документів — дуже повільне. Під час цього, якщо старий індекс відкритий до запису, накопичуються ще зміни, які наздоганяти мені не захотілося.
Тому є інша операція: shrink. Оце вже краще: вона на рівні структур бази обʼєднує шарди. Працює замість годин — лічені хвилини. Але ж не все так просто.
Перепоною стало те, що для виконання shrink всі шарди індексу повинні бути на одному вузлі. Для того є інструкція прямо в документації вище. Але на практиці в нас OpenSearch починав цей переніс та не закінчував. Виявилося, що є обмеження на кількість шардів на вузол.
Причому (!) в AWS OpenSearch обмеження це — як ми визначили — залежить не від розміру вузла. Та точно не від налаштувань — такі речі AWS не дає змінити, щоб ми не “наробили дурниць”. Дякуємо!
Ні — насправді AWS OpenSearch прагне зберегти баланс в кластері, а саме рівний розподіл шардів між вузлами. Ну й добре… створили ще один, порожній, індекс на 100 шардів. Після чого перенесення справжніх шардів на один вузол завершилося успіхом. Ну а далі й shrink відбувся без подальших сюрпризів. Роботу зроблено.
Це я ще не торкнувся того, наскільки безпорадним є UI OpenSearch Dashboards, в якому одне світле місце - Dev Console, тобто можливість надсилати до кластера команди HTTP API.

