MariaDB и jemalloc: 592 MB върната памет на работещ сървър
Смених стандартния glibc алокатор с jemalloc на работещ MariaDB 10.11 и измерих разликата. MariaDB заявява същото количество памет, но отпечатъкът в RAM пада наполовина. Ето методиката, числата и как да го направите сами.
Сървърът има 7,5 GB RAM, четири ядра и десетина сайта отгоре. MariaDB стоеше на 1,1 GB резидентна памет, а суапът беше пълен с около 1,5 GB. Нищо не беше счупено — просто нямаше накъде повече.
Преди да купувам още памет, реших да проверя една хипотеза, която се среща често по форумите, но почти винаги без числа зад нея: че смяната на стандартния алокатор на паметта с jemalloc намалява осезаемо потреблението на MariaDB.
Реших да го измеря вместо да го приема на доверие. Тази статия е резултатът — методиката, реалните числа от работещ сървър и една честна забележка накрая за това какво jemalloc не прави.
Какво всъщност прави алокаторът
Когато MariaDB се нуждае от памет за буфер за сортиране или временна таблица, тя не говори директно с ядрото. Говори с алокатора — библиотеката, която стои между приложението и операционната система. Стандартният на всяка Linux система е този на glibc.
Алокаторът има две задачи: да намери памет, когато я поискат, и да я върне, когато вече не е нужна. Първата я вършат добре всички. Разликата е във втората.
glibc използва арени — отделни зони за всяка нишка, за да не се блъскат нишките една в друга за една и съща заключалка. Това е бързо решение и точно затова е избрано. Но когато нишката освободи паметта си, освободеното остава в нейната арена, готово за следващото заявяване. От гледна точка на ядрото тази памет продължава да е заета.
jemalloc използва същата идея за арените, но добавя нещо, което glibc няма: decay purge. През определен интервал прегледа кои страници не са били пипани и ги връща на ядрото с madvise(MADV_DONTNEED). Паметта остава заделена за процеса, но вече не се брои като резидентна.
free().Ето защо това има значение точно за MariaDB: при 24 едновременни връзки, които правят сортирания и временни таблици, се движат стотици мегабайти в двете посоки всяка секунда. Точно тази обмяна glibc задържа.
Как измерих
Тестът трябваше да отговори на един въпрос — колко памет държи процесът, а не колко е поискал. Затова централната метрика е VmRSS от /proc/<pid>/status, отчитана всяка секунда.
Схемата е еднаква и за двата пробега:
- Рестарт на MariaDB, за да тръгне алокаторът от чисто състояние.
- Загряване на буферния пул и отчитане на началния RSS.
- Четири рунда натоварване: по 25 итерации с 24 едновременни клиента.
- Шестдесет секунди пълен покой след натоварването.
- Отчитане на пика, на края и на разликата.
Стъпка четвърта е най-важната и се пропуска в повечето тестове. Пиковата стойност показва колко е поискано. Стойността след утихването показва колко се връща обратно — а точно там е разликата между двата алокатора.
Натоварването е таблица от 300 000 реда и заявки, подбрани така, че да въртят памет: сортирания, които не се събират в буфера, GROUP BY с GROUP_CONCAT, DISTINCT върху текстово поле, JOIN през буфер. Всяка заявка носи SQL_NO_CACHE, защото query cache-ът на този сървър е включен и без това щеше да връща готови резултати.
За генератора използвах mysqlslap — идва с MariaDB и не изисква нищо допълнително:
mysqlslap --concurrency=24 --iterations=25 \
--create-schema=membench --delimiter=$'\n' \
--query=/tmp/membench.sql --no-drop
А отчитането на паметта е буквално един ред във фонов цикъл:
awk '/^VmRSS:/{print $2}' /proc/$(pgrep -x mariadbd)/status
Държах едновременността на 24 при max_connections = 50, за да остане запас за живите сайтове. Тестът върви върху работещ сървър, а не на празна машина — това добавя малко шум, но пък числата идват от реални условия.
Резултатите
mariadbd, отчитан всяка секунда през целия тест. Всяка линия е един пробег.Формата на двете линии казва повече от всяка таблица.
glibc се качва и остава горе. Линията няма нито едно слизане за 21 минути. Стига 1099 MB и там свършва — включително след шестдесетте секунди покой. Освободената памет никога не напуска процеса.
jemalloc се движи. Трионът между 500 и 630 MB е decay purge в действие: между пиковете на натоварване връща страници на ядрото, при следващия пик ги взема обратно. В момента, в който натоварването спре, пада до 507 MB и остава там.
Числата:
| Показател | glibc | jemalloc | Разлика |
|---|---|---|---|
| RSS при старта | 293,5 MB | 300,4 MB | +2,4% |
| RSS на пика | 1098,7 MB | 712,6 MB | −35% |
| RSS след 60 секунди покой | 1098,7 MB | 506,9 MB | −54% |
| Задържано над началното | 805,3 MB | 206,6 MB | −74% |
| Брой mmap региони | 280 | 251 | −10% |
| Средно време на рунд | 12,068 s | 11,982 s | −0,7% |
Защо това не е буферният пул
Тук идва възражението, което сам си направих, преди да повярвам на резултата. innodb_buffer_pool_size на този сървър е 1 GB. Буферният пул се заделя предварително, но страниците му се пипат постепенно — така че растежът от 293 MB към 1099 MB може просто да е загряващ се пул, а не алокатор.
Две проверки затварят въпроса.
Първата — какво MariaDB смята, че е заела. Статусната променлива Memory_used показва колко памет е поискана през вътрешното ѝ отчитане:
mysql -e "SHOW GLOBAL STATUS LIKE 'Memory_used'"
Memory_used 1280050288 # glibc
Memory_used 1281058720 # jemalloc
Разликата е 0,08 процента. MariaDB е поискала практически едно и също количество памет и в двата пробега — но резидентният отпечатък се различава с 592 MB.
Втората — колко от пула е наистина зает. Ако растежът беше от буферния пул, той щеше да е пълен:
mysql -e "SHOW GLOBAL STATUS WHERE Variable_name LIKE 'Innodb_buffer_pool_pages_%'"
Innodb_buffer_pool_pages_data 12429
Innodb_buffer_pool_pages_free 52467
Innodb_buffer_pool_pages_total 64896
Заети са 12 429 страници от 64 896 — тоест около 194 MB реални данни при свободни над петдесет хиляди страници. Буферният пул няма как да обясни 1099 MB.
Остава едно обяснение и то е алокаторът.
За скоростта — честно
Тук трябва да кажа нещо, което противоречи на голяма част от написаното по темата.
Първата ми справка показа +8,9% повече заявки в секунда за jemalloc. Приятно число, което щеше да стои чудесно в заглавие. Само че то е сметнато от глобалния брояч Questions, а той брои и трафика на живите сайтове върху същия сървър. Тоест мери и неща, които нямат нищо общо с теста.
Правилната стойност е тази, която mysqlslap отчита за собствените си заявки: 12,068 срещу 11,982 секунди средно на рунд. Това е разлика от 0,7 процента — под шума.
jemalloc тук е печалба откъм памет, не откъм скорост. Ако някой Ви обещава ускорение на базата от смяна на алокатора, помолете за метода на измерване.
Това е логично, ако се замислите какво прави decay purge: той връща памет на ядрото, което е допълнителна работа, не спестена. Печалбата е в отпечатъка.
Как да го включите
Три стъпки на AlmaLinux 9 или всяка друга RHEL 9 система. jemalloc е в EPEL:
dnf install -y jemalloc
Следва настройката. Тук е важно да не пипате файла на самата услуга — при следващото обновяване на MariaDB той се презаписва. Правилното място е drop-in файл в /etc:
mkdir -p /etc/systemd/system/mariadb.service.d
cat > /etc/systemd/system/mariadb.service.d/jemalloc.conf <<'EOF'
[Service]
Environment="LD_PRELOAD=/usr/lib64/libjemalloc.so.2"
EOF
systemctl daemon-reload
systemctl restart mariadb
Рестартът е кратък, но е реален прекъсване на услугата — планирайте го извън пиковите часове.
Проверката, която наистина има значение
Ето детайла, който спестява часове: при грешен път до библиотеката LD_PRELOAD се проваля тихо. Динамичният зареждач изписва предупреждение и стартира процеса нормално — със стария алокатор. Тоест съществуването на drop-in файла не доказва нищо.
MariaDB отговаря на въпроса директно:
mysql -e "SHOW GLOBAL VARIABLES LIKE 'version_malloc_library'"
version_malloc_library jemalloc 5.2.1-0-gea6b3e973b477b8061e0076bb257dbd7f3faa756
При стандартния алокатор същата заявка връща system. Ако видите system след рестарта, значи нещо не се е получило.
За допълнителна сигурност проверете и дали библиотеката е наистина заредена в процеса:
grep -c jemalloc /proc/$(pgrep -x mariadbd)/maps
5
Всяко число над нулата означава, че библиотеката е на място.
Кога помага и кога не
Печалбата не е еднаква навсякъде. От измереното и от конфигурацията на този сървър се вижда кога си струва:
Помага осезаемо, когато:
- Имате много едновременни връзки — всяка нишка носи собствена арена.
tmp_table_sizeиmax_heap_table_sizeса едри. На този сървър са по 128 MB приmax_connections = 50, и точно това е паметта, която glibc задържаше.- Заявките въртят сортирания, временни таблици и
GROUP BYвместо прости четения по индекс. - Машината е с ограничена памет и суапът се обажда.
Няма да усетите почти нищо, когато:
- Натоварването е предимно прости заявки по първичен ключ.
- Връзките са малко и дълготрайни.
- Почти цялата памет отива в буферния пул. Той се заделя веднъж и остава зает — там няма какво да се върне.
- Търсите скорост. Вижте предната секция.
Как да върнете обратно
Промяната е обратима за половин минута и без следа:
rm /etc/systemd/system/mariadb.service.d/jemalloc.conf
systemctl daemon-reload
systemctl restart mariadb
Нищо в конфигурацията на MariaDB не е пипано — целият експеримент живее в един файл от три реда.
Накратко
На работещ MariaDB 10.11 с 24 едновременни клиента и натоварване от сортирания и временни таблици, jemalloc задържа 507 MB резидентна памет там, където glibc задържа 1099 MB — при практически еднакво потребление според собственото отчитане на MariaDB.
Разликата не идва от буферния пул, а от това, че glibc не връща освободеното на ядрото, докато jemalloc го връща.
Скоростта остава същата. Който твърди друго, нека покаже как я е мерил.
За сървър с достатъчно памет това е просто спретната оптимизация. За сървър, който вече е в суапа, е разликата между тесен и спокоен.
Една честна забележка за метода: направих по един пробег на алокатор, а не серия. Реших да не товаря работещ сървър още двадесет минути, защото доказателството и без това не е в единичното число — а в това, че Memory_used е еднакво, а формата на двете линии е различна. Ако правите същото на своя сървър, повторете теста няколко пъти и вижте дали получавате същата картина.
Снимка: Macro shot photo of a computer RAM от Liam Briese в Unsplash.
Ако сървърът Ви е в суапа и не Ви се занимава с измервания, свържете се с мен — правя както администриране на сървъри, така и оптимизация на бази данни.