Как устранить скачки задержки записи файлов на mdadm RAID0?

Краткое описание проблемы высокого уровня

Мы работаем над приложением, которое требует высокой пропускной способности RAID0 в течение длительных периодов времени. На выделенные RAID-массивы записывается до 8 независимых потоков данных со скоростью 5 ГБ/с (1 RAID на каждый поток данных). Большую часть времени это работает нормально, однако, по-видимому, возникают непредсказуемые скачки задержки записи файлов, которые приводят к переполнению буферов потока и, следовательно, к потере данных.

Кто-нибудь видел подобные проблемы? Если да, то какие изменения я могу внести в свое программное обеспечение, чтобы этого не произошло?

Нарушающий код

Ниже приведен код, который выполняется в наших потоках файлового ввода-вывода. Обратите внимание, что из-за ограничений платформы, на которой построена остальная часть нашего приложения, мы ограничены передачей в эту функцию одного аргумента, поэтому мы должны распаковать его в верхней части функции. Также обратите внимание, что нас больше всего беспокоит линия.

      void IoThreadFunction(IoThreadArgument *argument)
{
    /////////////// Unpack the argument ///////////////
    rte_ring* io_job_buffer = argument->io_job_buffer;
    rte_ring* job_pool = argument->job_pool;
    bool* stop_signal = argument->stop_signal;
    int fd = argument->fd;
    std::shared_ptr<bool> initialized = argument->initialized;
    ///////////////////////////////////////////////////

    int status;
    Job* job;

    spdlog::trace("io thread servicing fd {0} started on lcore {1}.", fd, rte_lcore_id());

    *initialized = true;

    while(!*stop_signal || rte_ring_count(io_job_buffer))
    {
        /////   This part of the code receives data from
        /////   other parts of the app. And creates an IOVEC
        /////   array that will be used for vectorized
        /////   file IO. It is not suspected to be the
        /////   root of the problem.
        /////   START SECTION
        
        // Poll io job queue
        if(rte_ring_mc_dequeue(io_job_buffer, (void**)&job) == -ENOENT) continue;

        // Populate iovecs
        job->populate_iovecs();
        
        ///// END SECTION

        // Write the data to file
        pwritev2(fd, job->iovecs, job->num_packets, job->file_offset, RWF_HIPRI);

        // Free dpdk packets
        rte_pktmbuf_free_bulk(job->packets, job->num_packets);
        
        // Restore job to pool
        rte_ring_mp_enqueue(job_pool, job);
    }
}

Системное оборудование

  • Компьютер, на котором работает наше приложение, представляет собой сервер с двумяAMD EPYC 764348-ядерные процессоры. Гиперпоточность намеренно отключена.
  • Каждый RAID0 построен с использованием двух NVM, каждый из которых способен поддерживать постоянную скорость записи 3,5 ГБ/с, поэтому теоретически мы должны иметь возможность получить скорость записи до 7 ГБ/с.
  • На все наше оборудование установлена ​​последняя версия прошивки.

Программное обеспечение

  • Мы используемUbuntu 22.04 LTSбег по5.15.0.86-genericЯдро Linux.
  • Следующие аргументы загрузки используются для оптимизации программной среды для нашего приложения:isolcpus=0-39,48-87 rcu_nocbs=0-39,48-87 processor.max_cstate=0 nohz=off rcu_nocb_poll audit=0 nosoftlockup amd_iommu=on iommu=pt mce=ignore_c. Обратите внимание, что некоторые из этих аргументов загрузки необходимы для других частей приложения, не связанных с записью данных на диск.isoclcpusАргумент настроен для изоляции ядер, которые наше приложение использует для потоковой передачи данных на диск, сводя к минимуму системные прерывания, которые могут вызвать большую задержку.

Другие важные детали

  • Наше приложение поддерживает NUMA, поэтому данные, поступающие из данного узла NUMA, всегда будут попадать на RAID, принадлежащий тому же узлу NUMA.
  • Приложение использует до 4 выделенных потоков на каждый поток данных для ввода-вывода файлов. Мы пытались использовать всего два потока на поток, но для надежности пропускной способности ввода-вывода требуется 4.
  • Каждый поток записывается в один файл, размер которого может достигать 5 ТБ.
  • Мы используем синхронные вызовы для записи данных на RAID. Обратите внимание, что мы тщательно экспериментировали с другими подходами, такими какiouring, но из-за характера потока данных синхронные вызовы обеспечивают для нас самую высокую и надежную пропускную способность.
  • Данные поступают пакетами шириной 8192 байта, и мы записываем 1024 пакета за раз с помощью векторизованной записи, чтобы в полной мере использовать преимуществаpwritev().
  • Все данные выравниваются по страницам, и все записи файлов обходят страничный кеш Linux с помощьюO_DIRECTфлаг.
  • Пространство для каждого файла предварительно выделяется с помощьюfallocate()
  • RAID-массивы настроены сmdadmсо следующими опциями:mdadm --create /dev/md0 --chunk=256 --level=0 --raid-devices=2 /dev/nvme[n]n1 /dev/nvme[n+1]n1
  • Все RAID-ы имеютXFSфайловые системы, созданные со следующими параметрами:mkfs.xfs -b size=4096 -d sunit=512,swidth=1024 -f /dev/md[n]. Файловая система настроена так, чтобы наилучшим образом соответствовать геометрии RAID.

0 ответов

Другие вопросы по тегам