Многопоточная закачка файла в S3

Эта статья иллюстрирует реальное применение и получение выигрыша в производительности на примере закачки файлов в хранилище Amazon S3 с использованием многопоточности на языке Ruby с использованием gem aws-sdk.

Начнем с простого

Реализовать закачку файла при помощи официального (т.е. разрабатываемого Amazon) gem aws-sdk достаточно просто. Если опустить подготовительную часть формирования параметров авторизации Amazon Web Services (AWS), то код занимает три строки:

Метод работает и показывает среднюю скорость закачки до 5Мб/c на файлах, больших 5Мб (на меньших файлах средняя скорость падает).
Однако, из опытов несложно заметить, что суммарная скорость одновременной закачки нескольких файлов выше, чем скорость закачки одного файла за раз. Получается, что есть некое ограничение пропускной способности на один поток? Попробуем распараллелить закачку, используя многопоточность и метод multipart_upload, и посмотреть, что получится.

Закачка файла частями

Для закачки файла частями в aws-sdk существует метод multipart_upload. Посмотрим на его типовое применение:

На вид получается посложнее, чем простая закачка. Однако, у нового метода есть существенные преимущества:

  • части могут закачиваться в произвольном порядке
  • нет необходимости ждать завершения закачки одной части, чтобы начать закачку другой

Эти два свойства позволяют нам использовать многопоточность при выполнении закачки.

Многопоточная закачка файла по частям

Есть несколько подходов для решения этой задачи:

  • фиксированное число потоков: файл делится на N частей (по количеству потоков N), каждая часть закачивается параллельно;
  • фиксированный размер части: файл делится на части размера, не превышающего значение S, каждая часть закачивается параллельно;
  • смешанный: файл делится на части размера, не превышающего значение S, полученные части закачиваются паралельно, но не более чем N потоками одновременно.

С практической точки зрения наиболее удобен именно смешанный подход, т.к.:

  • в API S3 есть ограничение на минимальный размер части (5Мб);
  • использование большого числа потоков снижает эффективность.

Приведу код многопоточной закачки файла:

Для создания потоков мы используем стандартный класс Thread (основы работы с многопоточностью в Ruby описаны в статье http://habrahabr.ru/post/94574). Для реализации взаимных исключений мы используем простейшие двоичные семафоры (мьютексы), реализованные стандартным классом Mutex.

Для чего требуются семафоры? С их помощью мы отмечаем участок кода (называемый критическим), который может выполнять только один поток в один момент времени. Остальные потоки должны будут ждать, пока включивший семафор поток не покинет критический участок. Обычно семафоры используются для обеспечения правильного доступа к общим ресурсам. В данном случае, общими ресурсами являются: объект ввода src_io и переменные read_size и parts. Переменные buff и part_number объявлены как локальные внутри потока (т.е. блока Thread.new do … end), и поэтому не являются общими.

Более подробно про семафоры и многопоточность в Ruby можно прочитать в статье http://www.tutorialspoint.com/ruby/ruby_multithreading.htm (англ.)

Результаты сравнения

Замеряем скорость закачки разными методами на нескольких тестовых файлах (размером 1Мб, 10Мб, 50Мб, 150Мб) и сводим в таблицу:

Файл Части Потоки upload, Mb/s multipart_upload, Mb/s threaded_upload, Mb/s
1 64k 1 1 0.78 0.29 (37%) 0.29 (37%)
2 512k 1 1 2.88 1.84 (64%) 1.65 (57%)
3 1Mb 1 1 3.39 2.38 (70%) 2.58 (76%)
4 10Mb 2 2 5.06 4.50 (89%) 7.69 (152%)
5 50Mb 10 5 4.48 4.41 (98%) 9.02 (201%)
6 50Mb 10 10 4.33 4.44 (103%) 8.49 (196%)
7 150Mb 30 5 4.34 4.43 (102%) 9.22 (212%)
8 150Mb 30 10 4.48 4.52(101%) 8.90 (199%)

Тестирование проводилось закачкой файлов с машины в регионе EU West (Ireland) на хранилище S3 в этом же регионе. Проводилась серия из 10 последовательных испытаний для каждого файла.
Если оценить скорость закачки простым методом для испытаний 4-8, то погрешность измерения составляет около 8%, что вполне приемлемо.
Закачка по частям (multipart_upload) на небольших файлах показала худший результат по сравнению с простой и такой же — на больших файлах.
Многопоточная закачка (threaded_upload) показала ту же эффективность на файлах из одной части, что и закачка по частям (что достаточно очевидно). А вот на больших по объему файлах мы имеем значительное преимущество — до двух раз (по сравнению с обычной закачкой).
Задачи выяснить оптимальные размер части и количества потоков не ставилось, но увеличение потоков с 5 до 10 на больших файлах не дало значимого эффекта.

Заключение

Многопоточная закачка файла показала себя эффективнее обычной на файлах, которые состоят более чем из одной части, прирост скорости — до двух раз.
Кстати, удобно будет создать метод, который сам выбирает наиболее подходящий способ закачки в зависимости от размера файла.
Исходный код, приведенный в примерах, выложен на Github: https://github.com/whisk/s3up

У нас есть похожие новости по этим темам:
Наверх