Prefetch — це та функція, яку помічаєш лише тоді, коли вона підводить. У більшість днів вона тихо перетворює послідовні зчитування на плавні та ефективні операції вводу/виводу. А потім одного дня ваш ARC перетворюється на обертові двері, відсоток хітів падає, затримки стрибають, і хтось вимовляє найнебезпечнішу фразу у сховищах: «Але нічого не змінювалося».
Це польовий посібник про те зраду: що насправді робить ZFS prefetch, як він взаємодіє з ARC і L2ARC, як може створювати треш кешу і як це виправити, не перетворюючи ваше сховище на шкільний проєкт. Я триматиму все прив’язаним до того, що можна виміряти на production-системах, і до того, що можна безпечно змінювати, коли люди чекають.
Prefetch простими словами (і чому воно може шкодити)
ZFS prefetch — це адаптивний механізм «read-ahead». Коли ZFS бачить, що ви читаєте дані файлу послідовно, він починає заздалегідь підвантажувати наступні шматки, перш ніж ви їх запросите. Це головна ідея: приховати затримку диска, тримаючи наступні блоки готовими в пам’яті.
Коли це працює — це магія: стрімінгові зчитування віддаються з ARC на швидкості пам’яті. Коли воно провалюється — це особливо дорога форма оптимізму: воно тягне купу даних в ARC, які ви ніколи не використаєте, витісняючи те, що ви дійсно будете використовувати. Ось і з’являється треш кешу.
Треш кешу через prefetch зазвичай проявляється в трьох загальних ситуаціях:
- Великі «переважно послідовні» сканування, що не повторюються (аналітичні запити, резервні копії, антивірусні сканування, індексація медіа, реплікація логів). Prefetch охоче заповнює ARC одноразовими даними.
- Послідовні зчитування по багатьох файлах паралельно (кілька ВМ, кілька воркерів, паралельні споживачі). Кожен потік виглядає послідовним сам по собі, але в сукупності вони стають гідрантом.
- Навантаження, що виглядають послідовними, але не приносять користі кешу (наприклад, читання гігантських файлів один раз або читання стиснутих/зашифрованих блоків, де вузьким місцем стає CPU, а перебування в ARC просто спалює оперативну пам’ять).
Два жарті, як обіцяно, а потім перейдемо до серйозного:
Жарт №1: Prefetch — як стажер, який починає друкувати листи на завтра «щоб заощадити час». Чудова ініціатива, катастрофічне судження.
Як ZFS prefetch працює під капотом
Накреслимо карту. В OpenZFS звичайне зчитування проходить через DMU (Data Management Unit), яка знаходить блоки (вказівники блоків, індексні блоки, dnode), а потім ініціює ввід/вивід для блоків даних. ARC кешує і метадані, і дані. ZFS prefetch додає додаткову поведінку: коли ZFS виявляє послідовний шаблон доступу, він ініціює додаткові зчитування майбутніх блоків.
ARC, MRU/MFU і чому prefetch змінює політику витіснення
ARC — це не простий LRU. Він адаптується: балансує між «недавно використаним» (MRU) і «часто використовуваним» (MFU). У спрощеній ментальній моделі:
- MRU: «нові штуки», до яких ви торкалися нещодавно (часто одноразові зчитування).
- MFU: «гарячі» дані, до яких ви повертаєтесь часто.
Prefetch має властивість засовувати в ARC блоки, яких ще не вимагало додаток. Залежно від деталей реалізації та навантаження, ці попередньо зчитані блоки можуть роздути бік «недавніх», витісняючи корисні метадані та робочий набір даних. Результат — гірший хіт-рейт, більше реальних зчитувань з диска і іноді неприємний петлеподібний ефект: більше промахів — більше зчитувань — більше prefetch — витіснення кешу — і так далі.
Prefetch — не те саме, що page cache ОС
На Linux ZFS розташований поза традиційним page cache. Це особливість, а не баг: ZFS керує власною логікою кешування. Але це також означає, що не можна припустити, що стандартні налаштування readahead у Linux розкриють всю картину. ZFS має свою поведінку prefetch і параметри налаштування, а наслідки впливають безпосередньо на тиск пам’яті ARC, а не «лише» на VFS cache.
Метадані важливі: prefetch може витіснити нудні, але критичні блоки
Найгірші інциденти, які я бачив, були спричинені не «дані повільні», а «метадані відсутні». Коли ARC втрачає метадані (dnodes, індексні блоки), кожна операція перетворюється на кілька I/O: знайти dnode, прочитати індексні блоки, нарешті прочитати дані. Prefetch, що витісняє метадані, може перетворити «одне зчитування» на «маленький парад зчитувань», який дуже чемний, але дуже повільний.
L2ARC: це не картка «вийти із в’язниці безкоштовно»
L2ARC (SSD-кеш) часто неправильно сприймають як «ARC, але більший». Це не так. L2ARC наповнюється через витіснення з ARC; він має власні витрати на метадані в RAM; і історично він не зберігався після перезавантаження (новіші версії OpenZFS підтримують persistent L2ARC, але це все ще не чарівна кнопка). Якщо prefetch тягне сміття в ARC, L2ARC може стати музеєм речей, які ви одного разу читали, але ніколи не прочитаєте знову.
Ручки, про які ви почуєте
Різні платформи показують трохи різні регулювальні параметри (FreeBSD проти Linux/OpenZFS-версій), але дві назви з’являються найчастіше:
zfs_prefetch_disable: грубий інструмент; відключає prefetch.zfetch_max_distance/zfetch_max_streams: обмеження «наскільки далеко» і «скільки потоків» для prefetch (імена відрізняються в різних версіях).
Не заучуйте імена. Заучіть стратегію: виміряйте, підтвердьте, що prefetch — джерело тиску, потім обмежте його найвужчим способом, який вирішує проблему.
Чому відбувається треш кешу: режими відмов
Режим відмов 1: одноразові послідовні зчитування, що виглядають «корисними»
Класичний приклад — резервні копії. Бекап зазвичай читає весь датасет послідовно, зазвичай раз на добу. Prefetch охоче підвантажує наперед, ARC заповнюється, і ваш робочий набір для production витісняється, щоб звільнити місце під найменш повторювані байти на системі.
Шаблон симптомів: починається бекап, коефіцієнт хітів ARC падає, затримки зростають, і клієнтські запити сповільнюються, хоча диски не насичені по пропускній здатності — вони насичені випадковими IOPS, бо промахи кешу призводять до додаткових перемикань.
Режим відмов 2: паралельні послідовні потоки
Один послідовний потік — це просто конвеєр. Двадцять послідовних потоків по двадцяти ВМ — це круговий рух у шторм. Кожен потік тригерить prefetch. Сумарний prefetch тягне набагато більше даних, ніж ARC може утримати, і витіснення стає постійним.
Режим відмов 3: prefetch конкурує з метаданими і маленькими гарячими блоками
ARC повинен утримувати «фішки індексації» вашої файлової системи: dnodes, індексні блоки, структури директорій. Prefetch має тенденцію вводити великі послідовності даних файлів. Якщо ARC малий відносно робочого набору, prefetch може зламати баланс: ви почнете отримувати промахи по метаданих, які раніше були в пам’яті, і раптом все буде повільно працювати, включно з операціями, що не стосуються сканування.
Режим відмов 4: забруднення L2ARC та витрати RAM
L2ARC звучить як подушка, але він витрачає RAM на індекс і не безкоштовний для заповнення. Якщо prefetch вводить витратні дані в ARC, ви витісняєте їх у L2ARC, витрачаючи I/O і RAM, щоб зберегти те, що ви ніколи не перечитаєте. Це не кешування; це накопичення мотлоху.
Режим відмов 5: стиснення, шифрування і вузьке місце CPU
Якщо зчитування обмежене CPU (декомпресія, дешифрування, перевірка контрольних сум), prefetch все одно може підвантажувати дані в ARC швидше, ніж CPU може їх обробити, або підвантажувати дані, які будуть негайно витіснені іншим робочим набором. Ви побачите завантаження CPU до максимуму, крутіння ARC і диски, що не видно максимально навантаженими — особливо заплутлива трійка під час інцидентних дзвінків.
Факти та історичний контекст (6–10 швидких пунктів)
- ZFS проектувався з акцентом на цілісність даних: контрольні суми, copy-on-write, self-healing — функції продуктивності, як prefetch, завжди накладалися поверх дизайну, орієнтованого на правильність.
- ARC створювали, аби перевершити просте LRU-мислення: він адаптується між недавністю і частотою, тому може витримувати змішані навантаження — поки ви не годуєте його занадто великою кількістю «недавніх» через prefetch.
- Ранні розгортання ZFS часто мали багато RAM: поведінка prefetch, яка була безпечною на 256 ГБ оперативної пам’яті, може стати драматичною на вузлах з 64 ГБ.
- L2ARC історично не був персистентним: холодні перезавантаження означали холодний кеш, що підштовхувало людей перебільшувати налаштування prefetch для «прогріву», іноді створюючи самостійний треш.
- OpenZFS розгалузився і знову конвергувався: коріння Solaris, illumos, FreeBSD, Linux — параметри prefetch і значення за замовчуванням еволюціонували по-різному в різних екосистемах.
- recordsize став своєрідною релігією продуктивності: налаштування recordsize для навантаження часто перевищує ефект prefetch, але prefetch взаємодіє з ним; великі записи підсилюють витрати спекулятивних читань.
- SSD змінили форму болю: prefetch був винайдений у світі, де затримка була дорогою; з NVMe пенальті за промах може бути меншим, але забруднення кешу все ще може вбивати tail latency.
- Віртуалізація примножила послідовні потоки: один пул зараз обслуговує десятки гостьових систем; алгоритми prefetch, що розраховують на «кілька потоків», можуть неправильно працювати в мультиорендній реальності.
- Люди люблять перемикачі: існування
zfs_prefetch_disable— доказ того, що достатня кількість операторів стикалася з реальним болем, щоб виправдати велику червону кнопку.
Три міні-історії з корпоративного світу
Міні-історія №1: інцидент через неправильне припущення
Інцидент почався як «невелике уповільнення». Внутрішній застосунок на базі бази даних — нічого гламурного — раптово почав таймаутитися у пікові години. Графіки сховища спочатку виглядали нормально: пропускна здатність не була максимальна, диски не «кричали», CPU не був завантажений. У чаті з’явився улюблений висновок: «Не може бути проблем з диском».
Але затримка розповіла іншу історію. 99-й процентиль читального latency підскочив, а не середнє. Це характерна ознака того, що кеш бреше: більшість запитів в порядку, невелика частина падає зі скелі кешу і йде повільним шляхом. Неправильне припущення було тонким: команда вірила, що нічний бекап є «послідовним і отже дружнім».
Насправді бекап запускався пізно і перетинався з бізнес-годинами. Він читав величезні датасети у довгих послідовних прогонових читаннях. Prefetch побачив ідеальний шаблон і агресивно підвантажував наперед. ARC заповнився даними бекапу. Метадані та невеликий часто використовуваний робочий набір застосунку були витіснені. Застосунок не потребував високої пропускної здатності; йому потрібні були низькі затримки доступу до невеликого робочого набору. Бекапу потрібна була пропускна здатність, але не кешування — він не збирався перечитувати ті самі блоки найближчим часом.
Виправлення не було героїчним. Команда спочатку підтвердила, що крах хіт-співвідношення ARC корелює з вікном бекапу. Потім вони обмежили швидкість бекапу і відкоригували розклад. Нарешті, вони звузили поведінку prefetch для цього вузла (не глобально для флоту) і змінили розмір ARC, щоб захистити метадані. Урок не в тому, що «prefetch поганий». Урок — «послідовний не завжди означає кешований».
Міні-історія №2: оптимізація, що відвернулася
Платформна команда хотіла прискорити аналітичні задачі на спільному пулі ZFS. Вони помітили великі сканування таблиць і вирішили «допомогти» ZFS, збільшивши паралелізм зчитувань: більше воркерів, більші шматки, агресивніше сканування. На папері це виглядало як виграш по пропускній здатності.
Це спрацювало — на порожньому кластері. У production це зіткнулось з усім іншим. Кожен воркер створив акуратний послідовний потік, тож prefetch спрацював для кожного. Раптом пул мав десяток потоків prefetch, що конкурували за ARC. Частота витіснень ARC зросла, поки не стала схожа на ігровий автомат. Писання в L2ARC теж зросли, перетворюючи SSD-кеші на неохочих журналізаторів для спекулятивних даних.
А потім був фінал: аналітичні запити стали лише трохи швидшими, але решта компанії стала повільнішою. CI-програми таймаутилися. Сторм-ініціації VM запуску зайняли більше часу. Навіть переліки директорій і встановлення пакетів стали «липкими», бо метадані були відсутні в кеші. Оптимізація була реальна, але її вартість зовні лягла на кожного сусіда.
Відкат не був «відключити prefetch скрізь». Команда зменшила конкурентність, ввела ізоляцію ресурсів (окремий клас пулу / окремий рівень зберігання) і додала запобіжники: аналітичні вузли отримали свій профіль налаштування, включно зі суворішими обмеженнями prefetch. Провал стався не тому, що ідея була дурною; проблемою було те, що спільні системи карають егоїстичну пропускну здатність.
Міні-історія №3: нудна, але правильна практика, що врятувала день
Одного разу інцидент зі зберіганням не став катастрофою просто тому, що хтось був нудним. Команда мала рутину: перед будь-яким тюнінгом продуктивності вони збирали невеликий набір доказів — статистику ARC, статистику ZFS I/O, стан пулу та 60-секундний знімок розподілу затримок. Це займало п’ять хвилин і вважалося «дратівливою процедурою».
Одного дня інженер запропонував поставити zfs_prefetch_disable=1, бо «prefetch завжди шкодить базам даних». Така віра поширена, іноді вірна і небезпечно узагальнена. Нудна рутина надала контрприклад: ARC в основному містив метадані, хіт-рейт був високим, а домінуюча проблема була фактично в синхронних записах через неправильно налаштований застосунок. Читання не були вузьким місцем.
Замість перемикання prefetch і введення нової змінної вони виправили шлях запису (поведінка fsync у застосунку, налаштування sync для датасету, узгоджене з бізнес-вимогами). Аварія закінчилася швидко і передбачувано. Тиждень потому вони зробили контрольований експеримент з prefetch і з’ясували, що він не був лиходієм у тій системі.
Операційний мораль: «нудна» звичка збирати однакові базові статистики кожного разу перетворює тюнінг з фольклору в інженерію. І ще — це запобігає ситуації, коли ви виграєте аргумент і втрачаєте систему.
Швидкий план діагностики
Це порядок дій, який я використовую, коли хтось каже «ZFS повільний», а графіки кричать у різнокольорових лініях.
Перше: підтвердіть клас симптомів (латентність проти пропускної здатності проти CPU)
- Це латентність? Дивіться application p95/p99, потім зіставте з latency на рівні сховища (пристрою та ZFS). Треш кешу зазвичай проявляється як сплески tail latency.
- Це насичення пропускної здатності? Якщо ви максимально використовуєте послідовну пропускну здатність і все послідовно, prefetch може бути нормальним.
- Це CPU? Високе навантаження CPU в kernel threads, що роблять checksums/compression, може робити «сховище» повільним на вигляд.
Друге: перевірте поведінку ARC за 60 секунд
- Тренд хіт-співвідношення ARC: чи впав він, коли почалося навантаження?
- ARC розмір проти цілі: чи ARC закрито на максимумі з великою кількістю витіснень?
- Баланс метаданих і даних: чи метадані витісняються?
Третє: скорелюйте з послідовним сканером
- Бекапи, scrub, resilver, реплікаційні зчитування, аналітичні скани, антивірус, індексація, транскодування медіа.
- Шукайте мітку «робота почалася», що збігається з хаотичністю ARC.
Четверте: перевірте, чи справді implicated prefetch
- Шукайте ознаки агресивного read-ahead (висока пропускна здатність читань з низькою повторною використаністю кешу, велике витіснення).
- Спробуйте вузьку зміну: зменшити дистанцію/потоки prefetch (або тимчасово відключити prefetch) на одному хості під контрольованим вікном і виміряти.
П’яте: оберіть найменш небезпечне пом’якшення
- Обмежте швидкість або перенесіть скануюче навантаження.
- Зменшіть конкурентність (особливо паралельні послідовні читачі).
- Обмежте prefetch замість того, щоб його вбити глобально.
- Підкоригуйте розмір ARC, щоб захистити метадані, якщо RAM дозволяє.
Жарт №2: Відключати prefetch у production, бо «можливо допоможе», — це як позбутися стуку, вимкнувши радіо. Шум зникне, звичайно, але ви нічого не дізналися.
Практичні завдання: команди, що вони означають, що робити далі
Ці завдання передбачають Linux з встановленим OpenZFS. Деякі команди вимагають пакетів (наприклад, zfsutils-linux) та прав root. Мета — не запускати все завжди; мета — мати набір інструментів і знати, що кожен інструмент показує.
Завдання 1: ідентифікуйте основи пулу та датасету
cr0x@server:~$ zpool status
pool: tank
state: ONLINE
scan: scrub repaired 0B in 02:11:33 with 0 errors on Sun Dec 22 03:14:10 2025
config:
NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
nvme0n1 ONLINE 0 0 0
nvme1n1 ONLINE 0 0 0
errors: No known data errors
Інтерпретація: Якщо пул деградований/resilvering/scrubbing, висновки про продуктивність під питанням. Налаштування prefetch не вирішить пул, який зайнятий відновленням.
Завдання 2: подивіться властивості датасету, що впливають на шаблони читання
cr0x@server:~$ zfs get -o name,property,value -s local,received recordsize,compression,primarycache,secondarycache,sync tank/data
NAME PROPERTY VALUE
tank/data recordsize 128K
tank/data compression lz4
tank/data primarycache all
tank/data secondarycache all
tank/data sync standard
Інтерпретація: recordsize і політики кешування мають значення. Великий recordsize + агресивний prefetch можуть означати великі спекулятивні читання. Якщо primarycache=metadata, дані не залишаться в ARC незалежно від prefetch.
Завдання 3: перевірте стан ручки prefetch
cr0x@server:~$ cat /sys/module/zfs/parameters/zfs_prefetch_disable
0
Інтерпретація: 0 означає, що prefetch увімкнено. Не змінюйте його ще; спочатку доведіть, що це винуватець.
Завдання 4: швидко захопіть підсумок ARC
cr0x@server:~$ arcstat 1 5
time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c
12:40:01 9234 1840 19 220 12 1620 88 95 5 48G 48G
12:40:02 10110 2912 28 240 8 2672 92 110 4 48G 48G
12:40:03 9988 3220 32 230 7 2990 93 105 3 48G 48G
12:40:04 9520 3011 31 210 7 2801 93 100 3 48G 48G
12:40:05 9655 3155 33 205 6 2950 94 99 3 48G 48G
Інтерпретація: Зростаючий miss% під час сканування — класичний сигнал трешу. Якщо промахи переважно pmis (prefetch misses), це підказка, але не остаточний доказ. Ключ — тренд і кореляція з запуском навантаження.
Завдання 5: перевірте обмеження розміру ARC (Linux)
cr0x@server:~$ cat /sys/module/zfs/parameters/zfs_arc_max
51539607552
cr0x@server:~$ cat /sys/module/zfs/parameters/zfs_arc_min
4294967296
Інтерпретація: Тут ARC max ~48 GiB. Якщо ARC малий відносно навантаження, prefetch може дуже швидко його крутити. Якщо ARC великий і все одно треше — навантаження ймовірно «без повторного використання» або занадто багато потоків.
Завдання 6: стежте за ZFS I/O на рівні пулу
cr0x@server:~$ zpool iostat -v 1 5
capacity operations bandwidth
pool alloc free read write read write
-------------------------- ----- ----- ----- ----- ----- -----
tank 3.2T 10.8T 2400 110 780M 6.2M
mirror-0 3.2T 10.8T 2400 110 780M 6.2M
nvme0n1 - - 1200 60 390M 3.1M
nvme1n1 - - 1200 60 390M 3.1M
-------------------------- ----- ----- ----- ----- ----- -----
Інтерпретація: Якщо під час уповільнення читальна пропускна здатність висока, але застосунок не отримує вигоди, можливо, ви читаєте наперед марно. Перевірте це разом з промахами ARC і затримками застосунку.
Завдання 7: підтвердіть, чи scrub/resilver не конкурують
cr0x@server:~$ zpool status | sed -n '1,25p'
pool: tank
state: ONLINE
scan: scrub in progress since Thu Dec 25 12:10:01 2025
1.20T scanned at 6.9G/s, 210G issued at 1.2G/s, 3.20T total
0B repaired, 6.56% done, 00:38:21 to go
Інтерпретація: Scrub теж є послідовним читачем і може тригерити prefetch та конкурувати за ARC. Якщо ваш треш збігається зі scrub, спочатку розв’язуйте питання планування.
Завдання 8: визначте топ-зчитувачів (рівень процесів)
cr0x@server:~$ sudo iotop -oPa
Total DISK READ: 820.12 M/s | Total DISK WRITE: 6.41 M/s
PID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
18277 be/4 backup 612.55 M/s 0.00 B/s 0.00 % 98.00 % borg create ...
23110 be/4 postgres 84.12 M/s 2.40 M/s 0.00 % 12.00 % postgres: checkpointer
9442 be/4 root 32.90 M/s 0.00 B/s 0.00 % 4.00 % zfs send ...
Інтерпретація: Якщо один процес є послідовним хогом, часто можна вирішити інцидент без втручання в prefetch: обмежте його, перемістіть або перенесіть розклад.
Завдання 9: перевірте, чи тимчасово відключається/вмикається prefetch коректно
cr0x@server:~$ echo 1 | sudo tee /sys/module/zfs/parameters/zfs_prefetch_disable
1
cr0x@server:~$ cat /sys/module/zfs/parameters/zfs_prefetch_disable
1
Інтерпретація: Це зміна під час роботи для швидких експериментів. Трактуйте її як запобіжник: зафіксуйте час, вимірюйте 5–15 хвилин і відкатуйте, якщо це шкодить послідовній пропускній здатності. Робіть постійним тільки після доведення виграшу і розуміння компромісу.
Завдання 10: чисто поверніть зміну
cr0x@server:~$ echo 0 | sudo tee /sys/module/zfs/parameters/zfs_prefetch_disable
0
Інтерпретація: Завжди відкатуйте в тому самому вікні інциденту, якщо дані не підтверджують зміну. Налаштування production без rollback — це азарт з кращою лексикою.
Завдання 11: перевірте тиск пам’яті, що погіршує витіснення ARC
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 128Gi 93Gi 2.1Gi 1.2Gi 33Gi 30Gi
Swap: 0B 0B 0B
Інтерпретація: Якщо «available» низький, ARC може конкурувати з рештою системи. Prefetch під тиском пам’яті частіше швидко витісняє корисні сторінки.
Завдання 12: дослідіть розподіл пам’яті ARC (kstat)
cr0x@server:~$ sudo kstat -p zfs:0:arcstats:size zfs:0:arcstats:target_size zfs:0:arcstats:arc_meta_used zfs:0:arcstats:data_size
zfs:0:arcstats:size 51511234560
zfs:0:arcstats:target_size 51539607552
zfs:0:arcstats:arc_meta_used 8123456789
zfs:0:arcstats:data_size 43200000000
Інтерпретація: Якщо використання метаданих падає під час сканування, ви знайшли ймовірний механізм «все стає повільним». Захист метаданих (через розмір ARC і контроль навантаження) часто відновлює tail latency.
Завдання 13: перевірте поведінку L2ARC (якщо присутній)
cr0x@server:~$ sudo kstat -p zfs:0:arcstats:l2_hits zfs:0:arcstats:l2_misses zfs:0:arcstats:l2_size
zfs:0:arcstats:l2_hits 182334
zfs:0:arcstats:l2_misses 992112
zfs:0:arcstats:l2_size 214748364800
Інтерпретація: Великий L2ARC з низьким хіт-рейтингом під час трешу може бути забруднений одноразовими prefetched даними. Якщо L2ARC відсутній, він не врятує вас; це просто ще один повільний шар за ARC.
Завдання 14: перевірте вирівняність recordsize під навантаження
cr0x@server:~$ zfs get recordsize tank/db tank/backup tank/vmstore
NAME PROPERTY VALUE SOURCE
tank/db recordsize 16K local
tank/backup recordsize 1M local
tank/vmstore recordsize 128K local
Інтерпретація: Якщо ваша база даних на 1M recordsize, prefetch буде спекулятивно тягнути великі блоки, що можуть містити непотрібні сторінки. Якщо бекапи на маленьких записах, ви будете робити більше I/O, ніж потрібно. Виправте основи перед тим, як звинувачувати prefetch.
Завдання 15: виміряйте реальну затримку на блочному пристрої
cr0x@server:~$ iostat -x 1 5
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s w_await aqu-sz %util
nvme0n1 1180 402000 0.0 0.00 4.10 340.7 60 3200 0.95 3.10 74.2
nvme1n1 1195 401000 0.0 0.00 4.05 335.6 62 3400 1.01 3.05 75.0
Інтерпретація: Якщо device r_await стрибає, коли промах% ARC зростає, ви реально йдете на диск. Якщо затримка пристрою стабільна, але застосунок повільний, можливо, вузьким місцем є CPU або блокування/черги вище рівня пристрою.
Завдання 16: зробіть налаштування prefetch персистентним (тільки після доведення)
cr0x@server:~$ echo "options zfs zfs_prefetch_disable=1" | sudo tee /etc/modprobe.d/zfs-prefetch.conf
options zfs zfs_prefetch_disable=1
cr0x@server:~$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.8.0-40-generic
Інтерпретація: Постійні зміни повинні проходити через процес керування змінами. Якщо ви не можете чітко пояснити негативні наслідки (послідовні читання стануть повільнішими) і шлях відкату (видалити файл, перебудувати initramfs, перезавантажитись), ви не готові робити це постійним.
Типові помилки, симптоми та виправлення
Помилка 1: «Хіт-рейт ARC низький — додаємо L2ARC»
Симптом: Ви додаєте великий SSD-кеш, хіт-рейт ледь покращується, і тепер система має вищу write amplification та менше доступної RAM.
Чому так відбувається: Навантаження має низьке повторне використання; prefetch забруднює ARC; L2ARC просто зберігає забруднення з додатковими витратами.
Виправлення: Спочатку зменшіть спекулятивні зчитування (обмеження prefetch або throttling навантаження). Перевірте повторне використання. Лише потім розглядайте L2ARC, розміщений і відстежуваний для реального покращення хітів.
Помилка 2: Відключення prefetch глобально, бо «бази даних ненавидять його»
Симптом: Звітні й пакетні задачі стають повільнішими; вікна бекапу подовжуються; resilver займає більше часу; користувачі скаржаться на «повільні експорти».
Чому так відбувається: Деякі навантаження величезно виграють від prefetch. Глобальне відключення карає всі послідовні читачі, включаючи легітимні.
Виправлення: Віддавайте перевагу обмеженню проблемного навантаження (планування/throttling) або таргетованому налаштуванню на хост/роль пулу замість флотального перемикача. Якщо потрібно відключити, документуйте та вимірюйте вплив на послідовні задачі.
Помилка 3: Плутати «високу пропускну здатність» з «доброю продуктивністю»
Симптом: Сховище читає 800 MB/s, але застосунок повільний.
Чому так відбувається: Ви стрімите дані в ARC і відразу їх витісняєте (або читаєте наперед за межі використання). Це активність, а не прогрес.
Виправлення: Перевірте miss%, витіснення і час виконання задач на рівні застосунку. Якщо пропускна здатність додатка не масштабується разом з пропускною здатністю пулу, ймовірно, ви виконуєте спекулятивні читання або маєте проблеми з метаданими.
Помилка 4: Ігнорування резидентності метаданих
Симптом: «Усе» сповільнюється під час сканування: переліки, дрібні читання, відгук ВМ.
Чому так відбувається: Метадані були витіснені; операції тепер потребують додаткових зчитувань для пошуку даних.
Виправлення: Переконайтеся, що ARC має правильний розмір; уникайте запуску кеш-забруднюючих сканувань у пікові години; розгляньте зміни політики кешування (primarycache) тільки після розуміння наслідків.
Помилка 5: Сліпе налаштування recordsize
Симптом: Навантаження випадкових читань має високу латентність; послідовні сканування дають величезний I/O; prefetch здається «гіршим ніж зазвичай».
Чому так відбувається: Завеликий recordsize збільшує ампліфікацію читання; prefetch тягне великі блоки, що містять мало корисних даних для дрібних читань.
Виправлення: Вирівняйте recordsize під навантаження (наприклад, менший для БД, більший для бекапів/медіа). Повторно протестуйте поведінку prefetch після коректного recordsize.
Помилка 6: Робити багато змін одночасно
Симптом: Після «тюнінгу» продуктивність змінилась, але ніхто не може пояснити чому; подальші регресії неможливо відлагодити.
Виправлення: Міняйте одну змінну, вимірюйте, зберігайте шлях відкату. Налаштування prefetch чутливе до суміші навантажень; вам потрібна атрибуція.
Чеклісти / покроковий план
Покроковий план: доведіть (або спростуйте) треш prefetch
- Позначте вікно інциденту. Запишіть, коли почалося уповільнення і які роботи виконувались.
- Перевірте стан пулу. Якщо scrub/resilver активні, зафіксуйте це.
- Зберіть статистику ARC на 2–5 хвилин. Використайте
arcstat 1і зафіксуйте тренд miss%. - Зберіть статистику I/O пулу. Використайте
zpool iostat -v 1щоб побачити навантаження читань. - Знайдіть топ-зчитувачів. Використайте
iotop(або еквівалент платформи). - Скорелюйте. Чи промахи ARC підвищились, коли почався послідовний читач?
- Оберіть найменш інвазивне пом’якшення. Спочатку обмежте/переплануйте читача.
- Якщо потрібно налаштувати: Тимчасово відключіть prefetch (або зменшіть його діапазон) і виміряйте коротке вікно.
- Прийміть рішення. Якщо латентність матеріально покращилась і послідовні задачі залишаються прийнятними, розгляньте постійну зміну з документацією.
- Після дії: Знову прогайте навантаження у контрольованому вікні і валідуйте виправлення при реалістичній конкурентності.
Чекліст: гігієна безпечного тюнінгу в production
- Майте команду відкату готову перед застосуванням зміни.
- Міняйте одну річ за раз.
- Вимірюйте і системні метрики (ARC/pool), і метрики навантаження (runtime задач, latency запитів).
- Віддавайте перевагу налаштуванню за роллю (вузли бекапів проти вузлів з вимогами низької латентності), а не флотальним перемикачам.
- Запишіть «чому», а не тільки «що». Майбутній ви не згадає контекст паніки.
Часті питання
1) Що таке ZFS prefetch?
Це адаптивний read-ahead ZFS. Коли ZFS виявляє послідовний доступ, він ініціює додаткові зчитування майбутніх блоків, щоб вони були в ARC до того, як застосунок їх запросить.
2) Чи те саме це, що Linux readahead?
Ні. Linux readahead прив’язаний до page cache і блочного шару. ZFS використовує ARC і власний I/O-пайплайн. Ви можете мати «розумний» Linux readahead і одночасно страждати від того, що ZFS prefetch крутить ARC.
3) Коли мені варто відключити prefetch?
Коли маєте сильні докази, що воно забруднює ARC і шкодить навантаженням з вимогами низької латентності — зазвичай під час великих одноразових послідовних сканувань на спільній системі з обмеженим ARC. Відключайте як контрольований експеримент спочатку.
4) Коли відключення prefetch — погана ідея?
Якщо ваше середовище залежить від високої пропускної здатності послідовних читань (бекапи, стрімінг медіа, великі файли, реплікаційні send-потоки) і у вас немає інших способів спланувати/обмежити їх. Відключення може перетворити плавні стріми на рівномірний диск I/O.
5) Як зрозуміти, що це треш prefetch, а не просто «недостатньо RAM»?
Недостатність RAM часто є базовою умовою, але треш prefetch — це тригер. Ви побачите ARC закритим неподалік максимуму, miss% зростає під час послідовної роботи, збільшення дискових зчитувань і погіршення tail latency для несуміжних навантажень. Якщо та сама конфігурація пам’яті працює добре без сканування, спекулятивні зчитування — частина механізму.
6) Чи виправляє L2ARC треш prefetch?
Зазвичай ні. L2ARC наповнюється через витіснення з ARC; якщо ARC повний одноразовими prefetched блоками, L2ARC збере ті ж самі дані з додатковими RAM-витратами. L2ARC допомагає, коли є реальне повторне використання даних, які не вміщаються в ARC.
7) Чи можу я налаштувати prefetch без його відключення?
Часто так, залежно від версії OpenZFS і платформи. Можна обмежити, наскільки далеко ZFS читає наперед або скільки потоків воно відслідковує. Конкретні параметри різняться, тому важливо перевіряти, що ваша система експонує під /sys/module/zfs/parameters (Linux) або через loader/sysctl інтерфейси (FreeBSD).
8) Чому prefetch іноді погіршує продуктивність навіть на швидкому NVMe?
Бо витрати — не лише латентність пристрою. Забруднення кешу витісняє метадані і гарячі блоки, створюючи більше промахів і додаткового I/O. Навіть якщо NVMe швидкий, збільшення tail latency і зайва робота можуть все одно шкодити відгуку застосунку.
9) Який зв’язок між recordsize і prefetch?
Prefetch читає наперед у одиницях, що в кінцевому рахунку відповідають блокам на диску. Більші записи збільшують кількість спекулятивно підвантажених даних. Якщо ваш застосунок читає випадково 8–16K, а recordsize — 1M, prefetch і ампліфікація читань стануть дуже дорогими.
10) Якщо я відключу prefetch, ARC перестане кешувати зчитування?
Ні. ARC все одно буде кешувати demand reads (те, що застосунок реально запросив). Відключення prefetch головним чином зменшує спекулятивні зчитування наперед.
Висновок
ZFS prefetch не є ні героєм, ні лиходієм. Це агресивний помічник, що припускає: завтра буде так само, як сьогодні — послідовні зчитування триватимуть, і кешування наперед окупиться. У стабільних стрімінгових навантаженнях це припущення правильне і продуктивність відмінна. У змішаній, мультиорендній, скан-важкій корпоративній реальності це припущення ламається — і рахунок приходить у вигляді трешу ARC і tail latency.
Перемога — не в «перемкнути магічну бітку». Перемога — у дисциплінованій діагностиці: доведіть кореляцію, ідентифікуйте сканер, захистіть метадані і гарячі дані, і застосуйте найвужчий фікс, що відновлює передбачуваність. Коли ви ставитесь до prefetch як до гіпотези, а не до забобону, ZFS знову стає нудним. А нудке сховище — це найкраще сховище.