SPDK: прискорення роботи з NVMe-дисками

SPDK (Storage Performance Developer Kit – це набір інструментів та бібліотек з відкритим вихідним кодом, які покликані сприяти розробці високопродуктивних масштабованих додатків, орієнтованих на взаємодію з дисковими накопичувачами. У цьому матеріалі ми зосередимося на наявному в SPDK NVMe-драйвері, що працює в користувацькому просторі Linux, а також розглянемо реалізацію програми-приклад «Hello World» на платформі Intel.



В наших експериментах задіяний сервер на чіпсеті Intel C610 (степпінг C1, системна шина QPI, 9.6 ГТ/с) з двома сокетами, в якому встановлені 12-ядерні процесори Intel Xeon E5-2697 (тактова частота – 2.7 ГГц, 24 логічних ядра в режимі HT). Конфігурація ОЗП – 8x8 Гб (Samsung M393B1G73BH0 DDR3 1866). У системі є твердотільний накопичувач Intel SSD DC P3700 Series. У якості ОС використана CentOS 7.2.1511 (ядро 3.10.0).

Навіщо потрібен NVMe-драйвер, що працює в користувацькому просторі Linux?
Історично склалося так, що дискові накопичувачі на порядки повільніше інших компонентів комп'ютерних систем, таких, як оперативна пам'ять і процесор. Це означає, що операційна система процесор змушені взаємодіяти з дисками, використовуючи механізм переривань. Наприклад, сеанс такої взаємодії може виглядати так:

  1. Виконується запит до ОС на читання даних з диска.
  2. Драйвер обробляє цей запит і зв'язується з апаратним забезпеченням.
  3. Пластина диска розкручується.
  4. Головка читання-запису переміщається до потрібного ділянки пластини, готуючись почати зчитувати дані.
  5. Дані зчитуються і записуються в буфер.
  6. Генерується переривання, яке повідомляє процесор про те, що дані готові до використання в системі.
  7. І, нарешті, проводиться читання даних з буфера.
Модель переривань створює додаткове навантаження на систему. Проте зазвичай ця навантаження було значно менше, ніж затримки, характерні для звичайних жорстких дисків. В результаті на цю додаткове навантаження не звертали особливої уваги, так як вона не могла помітно знизити ефективність роботи підсистем зберігання даних.

В наші дні SSD-диски і технології наступного покоління, наприклад, сховища 3D XPoint, працюють значно швидше традиційних HDD. В результаті вузьке місце підсистем зберігання даних, яким раніше було апаратне забезпечення, перемістилося у сферу програмних механізмів. Тепер, як можна бачити на наведеному нижче малюнку, затримки, які вносять у процес роботи з накопичувачами переривання і операційна система, в порівнянні зі швидкістю відгуку накопичувачів, виглядають вельми значними.


SSD-накопичувачі і системи зберігання даних на базі технології 3D XPoint працюють значно швидше, ніж традиційні HDD. В результаті тепер вузьким місцем підсистем зберігання даних стало

Драйвер NVMe, який працює в користувацькому просторі Linux, вирішує «проблему переривань». Замість очікування повідомлення про завершення операції, він опитує пристрій зберігання даних у ході читання або запису. Крім того, і це дуже важливо, драйвер NVMe працює всередині користувача простору. Це означає, що програми можуть безпосередньо взаємодіяти з NVMe-пристроєм, минаючи ядро Linux. Одна з переваг такого підходу – позбавлення від системних викликів, що вимагають перемикання контексту. Це призводить до додаткового навантаження на систему. Архітектура NVMe не передбачає блокувань, це спрямовано на те, щоб не використовувати механізми процесора для синхронізації даних між потоками. Той же підхід передбачає і паралельне виконання команд вводу-виводу.

Порівнюючи NVMe-драйвер інтерфейсу простору з SPDK з підходом, який передбачає використання ядра Linux, можна виявити, що при використанні NVMe-драйвера затримки, спричинені додатковим навантаженням на систему, знижуються приблизно в 10 разів.


Затримки, в наносекундах, що викликаються при використанні для роботи з накопичувачами механізмів ядра Linux і SPDK

SPDK може, використовуючи одне ядро процесора, обслуговувати 8 твердотільних накопичувачів NVMe, що дає більш 3.5 мільйона IOPs.


Зміна продуктивності операцій вводу-виводу при роботі з різною кількістю SSD-накопичувачів за допомогою механізмів рівня ядра Linux і SPDK

Попередні вимоги і складання SPDK
SPDK підтримує роботу в таких ОС, як Fedora, CentOS, Ubuntu, Debian, FreeBSD. Повний список пакетів, необхідних для роботи SPDK, можна знайти на тут.

Перш ніж збирати SPDK, необхідно встановити DPDK (Data Plane Development Kit), так як SPDK покладається на можливості по управлінню пам'яттю та по роботі з чергами, які вже є в DPDK. DPDK – зріла бібліотека, яку зазвичай використовують для обробки мережевих пакетів. Вона відмінно оптимізована для управління пам'яттю і швидкої роботи з чергами.

Вихідний код SPDK можна клонувати з GitHub-репозиторію такою командою:

git clone https://github.com/spdk/spdk.git

▍Збірка DPDK (для Linux)
cd /path/to/build/spdk

wget http://fast.dpdk.org/rel/dpdk-16.07.tar.xz

tar xf dpdk-16.07.tar.xz

cd dpdk-16.07 && make install T=x86_64-native-linuxapp-gcc DESTDIR=.

▍Збірка SPDK (для Linux)
Після того, як зібраний DPDK знаходиться в папці SPDK, нам треба повернутися до цієї директорії і зібрати SPDK, передавши make шлях до DPDK.

cd /path/to/build/spdk

make DPDK_DIR=./dpdk-16.07/x86_64-native-linuxapp-gcc

▍Налаштування системи перед запуском SPDK-програми
Нижчеприведена команда дозволяє включити використання великих сторінок пам'яті (hugepages) і відв'язати від драйверів ядра будь NVMe і I/OAT-пристрої.

sudo scripts/setup.sh

Використання великих сторінок важливо для продуктивності, так як вони мають розмір 2 Мб. Це набагато більше, ніж стандартні сторінки по 4 Кб. Завдяки збільшеному розміру сторінок пам'яті зменшується ймовірність промаху в буфері асоціативної трансляції (Translate Lookaside Buffer, TLB). TLB – це компонент усередині процесора, який відповідає за трансляцію віртуальних адрес у фізичні адреси пам'яті. Таким чином, робота зі сторінками великого розміру веде до більш ефективного використання TLB.

Програма-приклад «Hello World»
У SPDK включено безліч прикладів, є тут і якісна документація. Все це дозволяє швидко почати роботу. Ми розглянемо приклад, в якому фразу «Hello World» спочатку зберігають на NVMe-пристрої, а потім прочитують назад в буфер.

Перш ніж зайнятися кодом, варто поговорити про те, як структуровані NVMe-пристрої і привести приклад того, як NVMe-драйвер використовує ці відомості для виявлення пристроїв, запису даних і потім їх читання.

NVMe-пристрій (так зване ж NVMe-контролером) структуровано виходячи з наступних міркувань:

  • В системі може бути одне або кілька NVMe-пристроїв.
  • Кожне NVMe-пристрій складається з деякої кількості просторів імен (воно може бути тільки одне в даному випадку).
  • Кожне простір імен складається з деякої кількості адрес логічних блоків (Logical Block Addresses, LBA).
Тепер приступимо до нашого покрокового наприклад.

▍Налаштування
  1. Ініціалізуємо шар абстракції оточення DPDK (Environment Abstraction Layer, EAL). В коді, наведеному нижче,
    c
    – це бітова маска, яка служить для вибору ядер, на яких буде виконуватися код.
    n
    – це ID ядра, а
    --proc-type
    – це директорія, де буде змонтована файлова система hugetlbfs.

    static char *ealargs[] = {
    "hello_world",
    "-c 0x1",
    "-n 4",
    "--proc-type=auto",
    };
    rte_eal_init(sizeof(ealargs) / sizeof(ealargs[0]), ealargs);

  2. Створимо пул буфера запиту, який використовується всередині SPDK для зберігання даних кожного запиту вводу-виводу.

    request_mempool = rte_mempool_create("nvme_request", 8192,
    spdk_nvme_request_size(), 128, 0,
    NULL, NULL, NULL, NULL,
    SOCKET_ID_ANY, 0);
    

  3. Перевіримо систему на наявність NVMe-пристроїв.

    rc = spdk_nvme_probe NULL, probe_cb, attach_cb, NULL);
    

  4. Перерахуємо NVMe-пристрої, повертаючи SPDK логічне значення, яке вказує на те, чи потрібно приєднати пристрій.

    static bool
    probe_cb(void *cb_ctx, struct spdk_pci_device *dev, struct spdk_nvme_ctrlr_opts *opts)
    {
    printf("Attaching to %04x:%02x:%02x.%02x\n",
    spdk_pci_device_get_domain(dev),
    spdk_pci_device_get_bus(dev),
    spdk_pci_device_get_dev(dev),
    spdk_pci_device_get_func(dev));
    return true;
    }
    

  5. Пристрій приєднано. Тепер можна запросити дані про кількість просторів імен.

    static void
    attach_cb(void *cb_ctx, struct spdk_pci_device *dev, struct spdk_nvme_ctrlr *ctrlr,
    
    const struct spdk_nvme_ctrlr_opts *opts)
    {
    int nsid, num_ns;
    const struct spdk_nvme_ctrlr_data *cdata = spdk_nvme_ctrlr_get_data(ctrlr);
    printf("Attached to %04x:%02x:%02x.%02x\n",
    spdk_pci_device_get_domain(dev),
    spdk_pci_device_get_bus(dev),
    spdk_pci_device_get_dev(dev),
    spdk_pci_device_get_func(dev));
    
    snprintf(entry->name, sizeof(entry->name), "%-20.20 s (%-20.20 s)", cdata->mn, cdata->sn);
    num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr);
    printf("Using controller %s with %d namespaces.\n", entry->name, num_ns);
    for (nsid = 1; nsid <= num_ns; nsid++) {
    register_ns(ctrlr, spdk_nvme_ctrlr_get_ns(ctrlr, nsid));
    }
    }

  6. Перерахуємо простору імен для того, щоб отримати відомості про них, наприклад, такі як розмір.

    static void
    register_ns(struct spdk_nvme_ctrlr *ctrlr, struct spdk_nvme_ns *ns)
    {
    printf(" Namespace ID: %d size: %juGB\n", spdk_nvme_ns_get_id(ns),
    spdk_nvme_ns_get_size(ns) / 1000000000);
    }
    

  7. Створимо пару черг (queue pair) введення виводу для відправки простору імен запиту на читання/запис.

    ns_entry->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_entry->ctrlr, 0);
▍Читання/запис даних
  1. Виділимо буфер для даних, які будуть прочитані/записані.

    sequence.buf = rte_zmalloc NULL, 0x1000, 0x1000);

  2. Скопіюємо рядок «Hello World» в буфер.

    sprintf(sequence.buf, "Hello world!\n");

  3. Відправимо запит на запис заданого простору імен, надавши пару черг, покажчик на буфер, індекс LBA, функцію зворотного виклику, яка спрацює після запису даних, і покажчик на дані, які мають бути передані функції зворотного виклику.

    rc = spdk_nvme_ns_cmd_write(ns_entry->ns, ns_entry->qpair, sequence.buf,
    0, /* Початок LBA */
    1, /* кількість блоків */
    write_complete, &sequence, 0);

  4. Функція зворотного виклику, після завершення процесу запису, буде викликана синхронно.

  5. Відправимо запит на читання заданого простору імен, надавши той же набір службових даних, який використовувався для запиту на запис.

    rc = spdk_nvme_ns_cmd_read(ns_entry->ns, ns_entry->qpair, sequence->buf,
    0, /* LBA start */
    1, /* number of LBAs */
    read_complete, (void *)sequence, 0);

  6. Функція зворотного виклику, після завершення процесу читання, буде викликана синхронно.

  7. Перевіримо прапор, який, який вказує на завершення операцій читання і запису. Якщо запит все ще обробляється, ми можемо перевірити стан заданої пари черг. Хоча реальні операції читання і запису даних виконуються асинхронно, функція
    spdk_nvme_qpair_process_completions
    перевіряє хід роботи і повертає число завершених запитів вводу-виводу, і, крім того, викликає функції зворотного виклику, що сигналізують про завершення процедур читання і запису, описані вище.

    while (!sequence.is_completed) {
    spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
    }

  8. Звільнимо пару черг та інші ресурси перед виходом.

    spdk_nvme_ctrlr_free_io_qpair(ns_entry->qpair);
Ось повний код розібраного тут прикладу, розміщений на GitHub. На сайті spdk.io можна знайти на документацію API SPDK NVMe.

Після запуску нашого «Hello World» повинно бути виведено наступне:


Результати роботи приклад «Hello World»

Інші приклади, включені в SPDK
У SPDK включено безліч прикладів, які покликані допомогти програмістам швидко розібратися з тим, як працює SPDK і почати розробку власних проектів.

Ось, наприклад, результати роботи приклад perf, який тестує продуктивність NVMe-диска.


Приклад perf, тестує продуктивність NVMe-дисків

Розробники, яким потрібен доступ до відомостей про NVMe-дисках, таким, як функціональні можливості, атрибути адміністративного набору команд, атрибути набору команд NVMe, дані про управління живленням, відомості про технічний стан пристрою, можуть скористатися прикладом identify.


Приклад identify, виводить відомості про NVMe-диску

Висновки
Ми розповіли про те, як використовувати SPDK і драйвер, який працює в користувацькому просторі Linux, для роботи з NVMe-дисками. Такий підхід дозволяє звести до мінімуму додаткові затримки, спричинені застосуванням механізмів ядра для доступу до пристроїв зберігання даних, що дозволяє підвищити швидкість передачі даних між накопичувачем і системою.

Якщо вас цікавить розробка високопродуктивних додатків, що працюють з дисковими накопичувачами з використанням SPDK, тут можна підписатися на розсилку SPDK. ось і ось — корисні відеоматеріали.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.