Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni
🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!Пости з тегом #AmazonRedshift
26.09.2022
Ключові моменти розробки схеми для AWS Redshift
❄️📦🪣 Сьогодні вдало попрацював з AWS Redshift.
Redshift - це аналітична (OLAP) база даних, що виросла з PostgreSQL, виглядає як PostgreSQL, але працює фундаментально по-іншому. Вона призначена для обробки великого обсягу даних, про що свідчить і цінник, що починається з $0.25 на годину.
Але просто завантажити дані у Redshift та отримати швидкий результат не вийде. (Перевірено.) Треба знати деякі ключові моменти.
- Спочатку треба зрозуміти, що це не база для транзакційного використання — робота з індивідуальними записами набагато повільніша, ніж у Postgres.
- Redshift, як стовпчикова база даних, добре робить внутрішню “нормалізацію” даних, тому від явної нормалізації не так багато вигоди.
- Індексів у Redshift взагалі немає. Замість індексів є ключ сортування та ключ розподілення. Від правильно заданих ключів продуктивність запитів може різнитися на порядки.
- Ключ розподілення (distribution key) - це колонка, яка визначає, на який вузол потрапить рядок. (Бо на відміну від Postgres, Redshift відразу розподіляє рядки таблиці по всіх вузлах у кластері.) Такій колонці варто мати рівномірно поділені значення, для досягнення найбільшої вигоди від паралелізації.
- Ключ сортування (sort key) - це колонка або декілька, що задають фізичний порядок зберігання даних. Це значно впливає на швидкість фільтрації по ключу, а також операції JOIN. Якщо, наприклад, дані поділені по клієнтах, то сортування по коду клієнта дозволить Redshift просто відкидати при обробці всі дані, окрім даних вказаного клієнта.
- Матеріалізовані розрізи (materialized view) у Redshift категорично крутіші, ніж у Postgres, і їх опанування є критично важливим для розкриття можливостей цієї бази.
- Перше - materialized view можна оновлювати не тільки цілком, але й інкрементально — що набагато швидше, особливо зі складними запитами. Є певні інтуїтивні обмеження на запити, які це підтримують. Наприклад, максимум можна порахувати інкрементально, а кількість унікальних значень — ні.
- Друге - materialized view можна оновлювати автоматично — тобто Redshift сам відстежує зміни у вхідних таблицях, та оновлює розріз (навіть інкрементально). На жаль, це не працює з каскадами розрізів. Емпірично, оновлення відбуваються протягом хвилин.
- Для розрізів так само задаються ключі сортування і розподілення, тобто це повноцінні та повно-швидкісні таблиці.
- А ще Redshift вміє і забирати дані з AWS S3, і отримувати їх з Kinesis Firehose.
31.10.2022
Перші враження від Obsidian. Тести з Redshift.
🪨📓💫 Що ж, переніс нотатки в Obsidian. Перші враження дуже приємні, як вже писав, працює швидко, інтеграція з Git вбудована. Знайшов ще важливу властивість — при перейменуванні чи пересуванні нотатки всі посилання на неї будуть виправлені автоматично. Що стане до нагоди, коли я буду впорядковувати базу, для чого і шукав більш зручну програму.
Дуже зручно мати глобальну комбінацію клавіш для пошуку та додавання нотаток. Таку комбінацію можна створити програмою BetterTouchTool - для цього дія на комбінацію має бути “відправити ⌘+O програмі Obsidian, перед чим вивести її на передній план”.
По роботі писав та тестував SQL-скрипти, що мають працювати на AWS Redshift. Великий недолік Redshift - у неї немає локального емулятора. При цьому мова помітно відрізняється від Postgres - настільки, що тести для Postgres недостатні. Багато разів було таке, що навіть після ретельного тестування код не працював, і дізнавались про це тільки на стейджингу. Тому у нас на CI тести (RSpec) запускаються також і з Redshift. Це надзвичайно повільно — ця база не розрахована на транзакційні операції. Маю ідеї переробити тести на щось більш сумісне — наприклад, замість створення тестових даних на кожний тест окремо, підготувати базу для всіх тестів один раз.
09.11.2022
Як поєднати Ruby on Rails та AWS Redshift
☁️🗄️☑️ Коротенький чекліст, як зробити, щоб Rails і Redshift запрацювали гармонічно:
-
Не забути про гем activerecord*-redshift-adapter (для вашої версії Rails). Без нього не вийде встановити підключення, бо Redshift не підтримує деякі системні команди.
-
Напевно, налаштувати додаток на роботу з двома базами — щоб не зберігати у Redshift транзакційні дані. Добре, що сучасна екосистема це цілком підтримує.
-
Вирішити, що робити локально. Якщо ви плануєте заміняти Redshift на Postgres, то напевно доведеться впровадити роздільні запити в деяких випадках — в першу чергу, для міграцій.
-
Вирішити, що робити з CI. Наполегливо раджу запускати тести з базою Redshift, бо інакше помилки через несумісність будуть випливати регулярно. Тож треба зробити схему, щоб запускати паралельно декілька збірок. Добре, що на одному кластері Redshift можна зробити багато баз.
-
Нарешті, навчитися використати потужні можливості Redshift з Rails - робити моделі з
VIEW
таMATERIALIZED VIEW
, завантажувати дані через S3, відправляти до Redshift через Kinesis, та інше.
… Тож насправді чекліст виходить не такий і короткий.
12.12.2022
Висновки від розгортування величезної переробки для нашого Redshift
🗄️🛫🌧️ Побачила світло продакшна велике перероблення схеми Redshift, яка тривала понад два місяці. Кілька думок:
-
Дуже допомогло те, що ми реалізували нову схему паралельно, та використали Feature Flag на рівні облікового запису, щоб викликати нову чи стару. З тим, скільки ця робота зайняла часу, можу тільки уявити, скільки проблем у клієнтів довелося б виправляти, якби замість того переробляли “наживу” та для всіх разом.
-
Дотична до цього думка — найдешевше в Redshift - це місце. Якщо вибір стоїть, ускладнити код або дублювати дані — краще дублювати.
-
Поки схоже на те, що auto-refreshing incremental materialized views (фух, довго як!) дійсно добре та надійно працюють — та навіть не додають помітно навантаження на кластер, на якому вже виконується наша самописна задача ETL (а ось вона добряче навантажує.)
-
Найкраще результат змін покаже статистична метрика, а не синтетичний стрес-тест. Радий, що у нас така метрика є (а саме, до Cloudwatch записуємо, як швидко дані проходять обробку.)
30.12.2022
AWS Redshift, його видатне кешування, та як їм користуватись
Нарешті перед новорічними святами величезне перероблення архітектури Redshift доставлено повністю, нічого не поламалось, можна спокійно піти у відпустку.
Я нещодавно писав, що у кожної NoSQL бази даних є “золотий сценарій використання”, який треба розуміти, щоб ефективно побудувати свій додаток на її основі. Поступово починаю розуміти, який “золотий сценарій” у Redshift.
Раніше я думав, що головна перевага Redshift - це можливість швидко обробляти великі обсяги даних. Але ні, це лише визначальна властивість OLAP баз.
Зараз мені здається, що у Redshift найкрутіше — це кешування. Навіть складні запити (основну роботу OLAP бази) можна зробити швидкими, якщо розробити таку структуру запитів, яка буде здатна до часткового кешування. Ось знайшов сьогодні гарну статтю від AWS.
Один з видів кешу — це materialized views, про які я вже писав. Redshift здатний робити їх автоматично, якщо можна у запиті виділити фрагмент, що підходить. Так, якщо деякий складний запит можна частково підготувати для всіх клієнтів разом, а потім фільтрувати під клієнта, то він буде відпрацьовувати швидко, попри видиму складність.
Не все так просто — якщо у запиті прихована функція, яку неможливо кешувати — наприклад, CURRENT_DATE
- тоді ніякого кешування не вийде, запит буде виконуватись кожного разу в повному обсязі. Повернувшись з відпустки, планую подивитись на статистику запитів, та підлагодити з цими новими знаннями.
13.02.2023
Трохи уроків з Event Sourcing на Redshift
Трохи уроків з так званого “event sourcing”, що для мене значить, архітектури, де ми, замість того, щоб редагувати записи в базі, зберігаємо кожну зміну стану як маленький, незмінний запис - “подію”. А потім з сукупності подій генеруємо поточний стан даних. (Наочний приклад — це реєстр операцій в магазині, підсумувавши які, можна отримати кількість грошей в касі.)
Мене на event sourcing підштовхнув сервіс AWS Kinesis Firehose. Бо він пропонує ну дуже спокусливу властивість — практично необмежений вхідний обсяг. Скільки не пиши, все візьме, складе в пачки, та відправить далі в базу, наприклад, в Redshift. А вже у Redshift доведеться виконати перетворення, щоб зібрати події в загальну картину. Можливо, це будуть матеріалізовані розрізи.
Так от, нарешті, до уроку. Події краще комбінувати в найбільші можливі. Тобто якщо за одну операцію генерується послідовно три події, що стосуються одна одної — краще зробити одну велику подію. Причина в тому, що кожна операція зʼєднання вам коштуватимете. Якщо зʼєднань в запиті багато, наприклад, десять — планувальник Redshift починає заплутуватись. Особливо, якщо ти знаєш, що треба поєднати події, які мають відповідність “одна до одної”. Ми-то знаємо про це, а Redshift - ні. Тому, якщо є можливість, краще відразу єднати в одну подію, а відповідно — й таблицю.
До речі, автоматичне оновлення — потужна функція Redshift - неможлива для розрізів, що залежать від інших розрізів. Тобто каскаду розрізів не вийде, принаймні, без побудови додаткового механізму оновлення.
Та ще до речі — якщо вже треба єднати в одну десять таблиць, може статись, що UNION ALL ... GROUP BY
працюватиме значно ефективніше, ніж купа JOIN
. Варто погратись.
14.02.2023
Інкрементальні розрізи подій у Redshift
Нарешті сьогодні розкусив дві проблеми, що мене турбували по Redshift.
Перша — як я вчора писав, події краще комбінувати… логічною вершиною такого підходу буде взагалі позбавитись від різних потоків для типів подій та звалювати все в одну велику таблицю. Далі це дозволить нам згрупувати таблицю по спільному ключу, та агрегувати результат. Головне, щоб агрегація підпадала під інкрементальне оновлення. Бо тоді ми звільняємося від необхідності сканувати всю таблицю, а будемо завжди працювати тільки з новими подіями — це виводить здатність масштабування на новий рівень. Обмеження для інкрементального оновлення досить суворі, але коли всі події в одній таблиці, то можна робити цікаві речі, наприклад: COUNT(NULLIF(event_type = 'purchase', false)) AS purchase_count
.
Друга — була така задача, щоб в межах GROUP BY
взяти деяке поле з найсвіжішої події. Визначити час цієї події легко - MAX(created_at)
… але ж агрегаційні функції працюють в межах стовпчика, тому інші стовпчики за таким запитом не знайдеш. Винайшов трохи хитрий підхід. Якщо інше поле — то рядок, то можна додати до нього час події як префікс. Час має бути в форматі з лексикографічним порядком — наприклад, ISO8601. Тепер можемо знайти останнє за часом значення будь-якого стовпчика - SUBSTRING(MAX(prefixed_item_id), 24)
. Як бачите, до результатів агрегаційних функцій можна застосовувати інші функції, та це не зашкодить інкрементальному оновленню.
17.02.2023
Розрізи з інкрементальним оновленням в Redshift
Після нещодавніх відкриттів та досліджувань по Redshift перейшов до політики “інкрементальне оновлення, або геть!” З одного боку, набув впевненості, що це можливо, а з іншого — що без інкрементального оновлення та повністю матеріалізованих розрізів нічого хорошого не буде. Автоматичне оновлення менш важливо; запустити його вручну нескладно. Але критично важливо, щоб оновлення відбувалось швидко та без повної перебудови.
На жаль, здогадатись, що розріз не здатний до інкрементального оновлення, важко. Тож додав автоматичний тест, який перевіряє це для всіх розрізів, якось так: SELECT name, state=1 AS is_incremental FROM stv_mv_info
. Без цього в майбутньому ми приречені наробити собі проблем.
Про механізм інкрементального оновлення написано дуже мало. Як я кажу, хотілося б щоб можна було глянути на розріз та відразу сказати, чи він інкрементальний, але ж ні. Особливо коли заходить за розрізи, залежні від інших розрізів. Декілька спостережень:
-
Виявив, що деколи розріз з вкладеним запитом не буде інкрементальним, проте якщо винести вкладений запит в окремий розріз, то обидва стануть інкрементальними (хоча тоді зовнішній розріз втрачає автоматичне оновлення — бо воно можливе тільки для розрізів, що залежать від таблиць.)
-
На агрегаційні функції можна накладати інші функції, або навпаки, вкладати, та це не порушить інкрементальність. Наприклад, можна зробити
date(min(created_at))
. -
АЛЕ! Коли один розріз залежить від одного, то інкрементальним він стане тільки якщо не залежить від стовпчиків з “загорнутою” агрегаційною функцією. (Це, напевно, псує відстеження змін.) Тож, щоб виправити, можна внести функцію під агрегацію:
min(date(created_at))
. (Якщо це дозволяє логіка.) -
Ще можу рекомендувати редактор запитів Query Editor v2, що вбудований в AWS Console. В ньому є дуже корисна функція “записничок” (notebook) - мені було зручно готувати експерименти з матеріалізованими розрізами. Бо експериментувати доведеться багато, це вже зрозуміло. Перед тим, як впроваджувати розріз в додаток, раджу випробувати його вручну та наживу, та перевірити, чи є він інкрементальним.
21.02.2023
Виправляв помилки з матеріалізованими розрізами в Redshift
Сьогодні день видався виснажливий. З ранку до ночі шукав, чому мої розрізи, які так чудово працювали в локальному Postgres та в редакторі запитів Redshift, відмовились створюватись на CI. Встиг навіть створити та розвʼязати запитання на StackOverflow. Нарешті, знайшов — методом спроб та помилок. Це те, що мені найбільше не подобається в Redshift - деякі особливості ніде не пояснені, при цьому текст помилок веде на зовсім невірний хід думок. Мабуть, вже треба писати свій гайд.
Що трапилось. Спочатку була помилка, що у користувача не вистачає прав на якусь системну таблицю. Раніше я робив експерименти від суперкористувача, тому вирішив, що у CI користувача немає прав. Як дати права? Команда GRANT
працює тільки після факту. Є ALTER DEFAULT PRIVILEGES - не працює. Придумав створити процедуру, яка робить GRANT
: якщо у процедури буде атрибут SECURITY DEFINER
, то вона надасть CI користувача право на цю команду (щоб дати собі права.) Задоволений таким кмітливим рішенням, запустив тести на CI та… виявилось, що хоч створити розрізи вдалося, але тепер їх неможливо оновити. Чудово.
Виходить, що залежність розрізу від системної таблиці — це в принципі неправильно, та Redshift мав би так і сказати, а не водити за ніс. Тоді чого не вистачає моєму розрізу-основі, щоб цієї залежності не було? Щоб визначити це, зробив копію коду, що створює розрізи, та почав викидати з нього все підряд, запускаючи, щоб перевірити, чи не зникла проблема. Та нарешті виявилось, що треба не викидати, а додати — а саме, всі стовпчики з GROUP BY мали бути присутніми в SELECT. Напевно, інакше Redshift не може відстежувати зміни до розрізу, тому створює системну таблицю. Якби це був тільки один розріз, то я б й не помітив, а з залежностями маємо такі незрозумілі помилки.
Додам, що спочатку витратив багато часу, бо запускав кожне виправлення на CI та чекав 10 хвилин, поки дійде до місця помилки. Справа пішла спритніше, коли почав відтворювати запити з локального psql
. А потім, щоб перевірити на CI, скоротив сценарій до єдиної частини, що мене цікавила.
20.03.2023
Складнощі з міграціями в Redshift
Найбільш за все у схемі перетворення даних розрізами в Redshift мене дратує, що зміни структури бази ускладнюються суттєво.
В Redshift обмежені можливі зміни стовпчиків. Навіть така незавадна зміна, як з NOT NULL на NULL, не дозволена. Можна тільки додати новий стовпчик з потрібним типом та перенести туди дані. На щастя, стовпчики можна перейменувати, тому принаймні можна замінити старий стовпчик на новий. Поки в нас не було розрізів, все було більш-менш прийнятне.
Але якщо від стовпчика залежить розріз, то такий трюк не вийде. Тоді залишається тільки видалити розріз, змігрувати таблицю, та створити розріз наново. (А якщо розрізів багато… Доведеться перестворювати всі.)
Але так теж не вийде, якщо розріз вже активно використовується. Хоча б тому, що перше створення розрізу може зайняти багато часу — часу простою. Тоді доведеться створити новий розріз, може, з версією - my_stats_v2_mv
, наповнити його даними, та потім плавно перевести код зі старого на новий розріз. Перейменувати розріз не дозволено, тож він назавжди залишиться з версією в назві. Тобто з часом буде і v3_mv
, і так далі, на кожну необхідну зміну.
Добре, що всі ці складнощі можна частково приховати за красивим кодом з боку додатка. Є навіть гем Scenic, який вміє версіонувати розрізи. Тільки Redshift він не підтримує.
11.09.2023
Інтуїція про бази даних. Чому OpenSearch агрегує дані швидше, ніж Redshift?
Отримав сьогодні таке питання — напівжартома — бо дійсно, те, що в Redshift займає десять секунд, в OpenSearch може бути виконано миттєво. Йдеться про агрегаційний запит на кшталт “підрахувати статистику суми поля по часовій шкалі.” Думаю, дуже важливо інтуїтивно розуміти, в чому саме різниця. Я вже писав про архітектуру OpenSearch/ElasticSearch, але що по цьому конкретному випадку?
Моя перша здогадка була — що OpenSearch тримає ці дані в памʼяті, а Redshift ні. Проте при перевірці виявилось, що це не зовсім так. Насправді OpenSearch не завантажує всі індекси в памʼять. Зате індекси на диску зберігаються в такому форматі, який готовий для прямого читання в структури памʼяті. Для того є механізм mmap, який уможливлює швидкий доступ до файлу в памʼяті без зайвого копіювання даних. Про це можна почитати в документації до модуля Store.
Проте mmap
використовується, напевно, для читання індексів в усіх базах даних. Справжня причина швидкості OpenSearch не в тому, що індекси розташовані в памʼяті.
Причина, на мою думку, в тому, що Redshift, як реляційна база даних, підтримує незрівнянно ширший спектр операцій. Тому вона має складний конвеєр даних, який не потрібний для простих агрегацій. Почнемо з того, що Redshift компілює кожний запит — тільки це може зайняти від 7 секунд. Це та наступні етапи виконання розраховані на маленьку кількість складних аналітичних запитів, які будуть обʼєднувати великі масиви даних реляційними операціями.
В той час як OpenSearch, хоч теж розрахований на великі обсяги даних та на масивну паралелізацію, але обмежений простими запитами, які працюють на рівні індивідуальних документів. Тож моя остаточна відповідь - OpenSearch швидше, бо простіше ніж Redshift. Такий тут компроміс.