Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB

Наскоро ви разказах как, използвайки стандартни рецепти увеличаване на производителността на SQL заявки за четене от база данни PostgreSQL. Днес ще говорим за това как записът може да се извърши по-ефективно в базата данни, без да използвате никакви „обратки“ в конфигурацията - просто чрез правилно организиране на потоците от данни.

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB

#1. Секция

Статия за това как и защо си струва да се организира приложено разделяне „на теория“ вече беше, тук ще говорим за практиката за прилагане на някои подходи в рамките на нашата услуга за наблюдение на стотици PostgreSQL сървъри.

„Неща от отминали дни...“

Първоначално, като всеки MVP, нашият проект стартира при сравнително малко натоварване - мониторингът се извършваше само за десетте най-критични сървъра, всички таблици бяха относително компактни... Но с течение на времето броят на наблюдаваните хостове ставаше все по-голям и отново се опитахме да направим нещо с един от таблици с размер 1.5TB, разбрахме, че въпреки че е възможно да продължим да живеем така, това е много неудобно.

Времената бяха почти като епични времена, различни версии на PostgreSQL 9.x бяха подходящи, така че цялото разделяне трябваше да се направи „ръчно“ - чрез наследяване на таблици и тригери маршрутизиране с динамичен EXECUTE.

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB
Полученото решение се оказа достатъчно универсално, за да може да се преведе на всички таблици:

  • Беше декларирана празна родителска таблица „заглавка“, която описва всичко необходимите индекси и тригери.
  • Записът от гледна точка на клиента е направен в „основната“ таблица и вътрешно използване тригер за маршрутизиране BEFORE INSERT записът беше "физически" вмъкнат в необходимия раздел. Ако все още нямаше такова нещо, хващахме изключение и...
  • … като се използва CREATE TABLE ... (LIKE ... INCLUDING ...) е създаден въз основа на шаблона на родителската таблица секция с ограничение на желаната дататака че когато данните се извличат, четенето се извършва само в тях.

PG10: първи опит

Но разделянето чрез наследяване исторически не е било подходящо за работа с активен поток за запис или голям брой дъщерни дялове. Например, можете да си спомните, че алгоритъмът за избор на необходимия раздел имаше квадратична сложност, че работи със 100+ секции, сами разбирате как...

В PG10 тази ситуация беше значително оптимизирана чрез прилагане на поддръжка естествено разделяне. Затова веднага се опитахме да го приложим веднага след мигрирането на хранилището, но...

Както се оказа след ровене в ръководството, естествено разделената таблица в тази версия е:

  • не поддържа описания на индекси
  • не поддържа тригери върху него
  • не може да бъде нечий "потомък"
  • не поддържат INSERT ... ON CONFLICT
  • не може да генерира раздел автоматично

След като получихме болезнен удар по челото с гребло, осъзнахме, че би било невъзможно да се направи без промяна на приложението и отложихме по-нататъшните изследвания за шест месеца.

PG10: втори шанс

И така, започнахме да решаваме възникналите проблеми един по един:

  1. Тъй като задейства и ON CONFLICT Открихме, че все още имаме нужда от тях тук и там, така че направихме междинен етап, за да ги изработим прокси таблица.
  2. Отървах се от "маршрутизирането" в тригери - тоест от EXECUTE.
  3. Извадиха го отделно шаблонна таблица с всички индекситака че те дори не присъстват в прокси таблицата.

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB
Накрая, след всичко това, ние разделихме главната маса нативно. Създаването на нов раздел остава на съвестта на приложението.

„Рязане“ речници

Както във всяка аналитична система, ние също имахме "факти" и "съкращения" (речници). В нашия случай в това си качество са действали напр. тяло на шаблона подобни бавни заявки или текста на самата заявка.

„Фактите“ бяха разделени по дни от дълго време, така че ние спокойно изтрихме остарелите секции и те не ни притесняваха (дневници!). Но имаше проблем с речниците...

Да не кажа, че бяха много, но приблизително 100TB „факти“ доведоха до 2.5TB речник. Не можете удобно да изтриете нищо от такава таблица, не можете да я компресирате в подходящо време и писането в нея постепенно става по-бавно.

Като речник... в него всеки запис трябва да бъде представен точно веднъж... и това е правилно, но!.. Никой не ни пречи да имаме отделен речник за всеки ден! Да, това носи известен излишък, но позволява:

  • пишете/четете по-бързо поради по-малкия размер на секцията
  • консумират по-малко памет чрез работа с по-компактни индекси
  • съхраняват по-малко данни поради възможността за бързо премахване на остарелите

В резултат на целия комплекс от мерки Натоварването на процесора намалява с ~30%, натоварването на диска с ~50%:

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB
В същото време продължихме да записваме точно същото нещо в базата данни, само с по-малко натоварване.

#2. Еволюция на базата данни и рефакторинг

Така че се спряхме на това, което имаме всеки ден има своя част с данни. Всъщност, CHECK (dt = '2018-10-12'::date) — и има ключ за разделяне и условие записът да попадне в определен раздел.

Тъй като всички отчети в нашата услуга са изградени в контекста на конкретна дата, индексите за тях от „неразделени времена“ са всички видове (Сървър, Дата, Шаблон на план), (Сървър, Дата, план възел), (Дата, клас на грешка, сървър), ...

Но сега живеят на всеки участък вашите копия всеки такъв индекс... И във всеки раздел датата е константа... Оказва се, че сега сме във всеки такъв индекс просто въведете константа като едно от полетата, което увеличава както обема му, така и времето за търсене за него, но не носи резултат. Оставиха си рейка, опа...

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB
Посоката на оптимизация е очевидна - проста премахнете полето за дата от всички индекси на преградени маси. Като се имат предвид нашите обеми, печалбата е около 1TB/седмица!

Сега нека отбележим, че този терабайт все още трябваше да бъде записан по някакъв начин. Тоест ние също дискът вече трябва да зарежда по-малко! Тази снимка ясно показва ефекта от почистването, на което посветихме една седмица:

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB

#3. „Разпределяне“ на върховото натоварване

Един от големите проблеми на натоварените системи е излишна синхронизация някои операции, които не го изискват. Понякога „защото не са забелязали“, понякога „така е било по-лесно“, но рано или късно трябва да се отървете от него.

Нека увеличим предишната снимка и видим, че имаме диск „помпи” под натоварване с двойна амплитуда между съседни проби, което очевидно „статистически“ не би трябвало да се случи с такъв брой операции:

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB

Това се постига доста лесно. Вече започнахме наблюдение почти 1000 сървъра, всеки се обработва от отделна логическа нишка и всяка нишка нулира натрупаната информация, за да бъде изпратена към базата данни с определена честота, нещо подобно:

setInterval(sendToDB, interval)

Проблемът тук се състои именно в това, че всички нишки започват приблизително по едно и също време, така че времето им за изпращане почти винаги съвпада „точно“. Ами сега #2...

За щастие, това е доста лесно за коригиране, добавяне на „случаен“ старт по време:

setInterval(sendToDB, interval * (1 + 0.1 * (Math.random() - 0.5)))

#4. Кешираме каквото ни трябва

Третият традиционен проблем с високото натоварване е няма кеш къде е той бих могъл да бъде.

Например направихме възможно анализирането по отношение на планови възли (всички тези Seq Scan on users), но веднага си мислят, че в по-голямата си част са еднакви - забравили са.

Не, разбира се, нищо не се записва отново в базата данни, това прекъсва тригера с INSERT ... ON CONFLICT DO NOTHING. Но тези данни все пак достигат до базата данни и не са необходими четене за проверка за конфликт трябва да направя. Ами сега #3...

Разликата в броя на записите, изпратени до базата данни преди/след активиране на кеширането, е очевидна:

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB

И това е съпътстващият спад в натоварването на хранилището:

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB

Общо

„Терабайт на ден“ просто звучи страшно. Ако направите всичко правилно, тогава това е справедливо 2^40 байта / 86400 секунди = ~12.5MB/sче дори винтовете за настолен IDE издържаха. 🙂

Но сериозно, дори и с десетократно „изкривяване“ на натоварването през деня, лесно можете да отговорите на възможностите на съвременните SSD.

Пишем в PostgreSQL на sublight: 1 хост, 1 ден, 1TB

Източник: www.habr.com

Добавяне на нов коментар