1. Презентация магистерской диссертации
на тему: «Оптимизация кэширования
сетевой файловой системы»
Музафаров Максим Ринатович
научный руководитель: Игумнов Александр Станиславович
Министерство образования и науки Российской Федерации
Федеральное государственное автономное образовательное учреждение высшего профессионального образования
«Уральский федеральный университет имени первого Президента России Б.Н. Ельцина»
Институт математики и компьютерных наук
Кафедра алгебры и дискретной математики
Екатеринбург 2014
Первоначально компьютеры были большие и обществе царило предубеждение, что чем больше техника, тем она мощнее. И чем она мощнее - тем лучше для производительности. Именно поэтому прогресс в технологиях и уменьшении форм-фактора использовался лишь для возможности количественно усилить мощность одного компьютера - мэйнфрэйма. Со временем компьютеры снова доросли до размеров больших комнат, а необходимые мощности так и не были достигнуты. Тогда возникла резонная мысль:
ОДНОГО МАЛО! Необходимо научиться связывать несколько компьютеров в общую сеть и заставлять их заниматься общей работой. Так возникли вычислительные кластеры. Но оставалась проблема общего доступа к данным, которую нужно было как-то решать. К счастью, возник еще один реонный вопрос: если удается отправлять данные и управлять компьютерами по сети, почему нельзя сделать файловую систему, которая будет работать по сети?
Идея оказалась выигрышной и уже в 1976 году была релизована первая сетевая файловая система FileAccessListener, идеи которой впоследствии были перенесены на протокол IP в реализации NFS, сетевой файловой системы, используемой и по сей день. Вкратце, смысл сетевой файловой системы в предоставлении по сети доступа к физическому носителю, желательно таким образом, что приложениям на клиенте будет совершенно незаметно, работают они с сетевой файловой системой или с локальной.
С течением времени развивались сетевые коммуникации, что привело к неизбежному развитию сетевых файловых систем. Клиентов становилось все больше и первоначальная связь клиент-сервер отошла на второй план, уступив место гибридам, в которых и сервер в некоторых случаях становится клиентом, и в которых клиент становится одновременно и сервером.
Так возникло множество других файловых систем, многие из которых совершенно непохожи по архитектуре на NFS, но тем не менее, обеспечивают сетевой доступ к общим хранилищам данных. Но как бы ни развивались протоколы, дамоклов меч всех компьютерных технологий давал о себе знать.
Это производительность. Рассмотрим на простом примере: у нас есть сервер, предоставляющий какую-то сетевую ФС и клиент, желающий получить к ней доступ.
В общем случае, запрос на чтение файла выглядит как два соединения: собственно запрос и получение данных.
Но что если взглянуть поглубже?
Запрос на чтение файла разбивается на три запроса: поиск файла на сервере, получение его уникального адреса, запрос на открытие и чтение этого уникального адреса.
Можно проводить декомпозицию и дальше, вплоть до того, что каждое TCP соединение разбивать на те же три-четыре пакета.
Понятно, что в развитии сетевых файловых систем, узким местом остается не мощность сервера, а ширина канала и количество одновременных соединений.
Одним, наиболее очевидным решением проблемы сетевого взаимодействия является клиентское кэширование. Кэш в данном случае становится некоторой промежуточной точкой между клиентом и сетью. Но плюс его в том, что доступ к кэшу во много раз быстрее сети.
Таким образом, этап запроса файла дополняется еще одной проверкой - проверкой наличия данных в локальном кэше. Со стороны может показаться, что схема только усложнилась, но во-первых, время доступа к кэшу пренебрежительно мало, по сравнению с сетевыми запросами, что делает изменения схемы незаметными, а во-вторых, при наличии данных в кэше - вообще убирает из схемы сетевые запросы, значительно ускоряя полуение клиентом запрашиваемых данных.
Но не всё так радужно и у кэша есть две проблемы. Первая - его согласованность, то есть актуальность данных, хранящихся в нем.
Необходимы дополнительные механизмы проверки, не обновились ли данные на сервере или клиенте, и обеспечивать их постоянную синхронизацию.
Вторая проблема - кэш ограничен, поэтому нельзя в него поместить все данные сервера и работать уже только локально. Постоянно необходимо решать, какие данные поместить в кэш, а какие из него убрать, чтобы освободить место. Для уменьшения числа проблем есть договоренность, что данные всегда помещаются в кэш, если к ним запрашивается доступ. Таким образом остается одна проблема - какие данные вытеснять.
Чаще всего, кэш это список одинаковых структур данных. Тогда добавление новых данных - это лишь запись в этот список еще одного элемента, с тем лишь ограничением, что количество элементов не превышает некоторого числа - объема кэша.
Когда список заполнен - и возникает описанная проблема: добавлять некуда, следовательно требуется удалить какой-то из элементов. Какой? Для решения этой проблемы придумано несколько алгоритмов. Рассмотрим основные:
Вытеснение случайного элемента. Или алгоритм рэндом. Вытесняемый элемент выбирается совершенно случайно, например с помощью генератора случайных чисел. Несмотря на кажущуюся простоту и бесполезность алгоритма, статистика показывает, что в среднем такой алгоритм работает довольно неплохо, обеспечивая высокую вероятность наличия запрашиваемого элемента в кэше.
Очередь. Второй из простых алгоритмов - представляет кэш в виде очереди элементов, всегда добавляя в конец, а вытесняя с начала. Несложен в исполнении, приемлем для определенных задач.
Следующие три алгоритма сложнее в исполнении, но и производительнее. LRU - Least Recently Used. Алгоритм вытесняет тот элемент, который дольше всего неиспользовался. Требует подсчета времени для каждого элемента и обнуления таймера при обращении к элементу. Доказано, что наиболее производителен в математической модели обновляемой очереди - кэш представляет из себя обычную очередь, но при обращении к элементу он убирается из того места где находился ранее и добавляется снова в конец. Таким образом, в данной модели проблема таймеров заменена на логику очереди.
Противоположность LRU - стратегия Most Recently Used. Вытесняет тот элемент, который только что использовался. Соответствующая математическая модель как у LRU, но с заменой очереди на стек.
Особняком стоит алгоритм Least Frequently Used - алгоритм вытеснения того элемента, который использовался наиболее редко. Требует постоянного подсчета частоты использования элементов. В простейшем случае к каждому элементу просто добавляется поле - счетчик, увеличиваемый при обращении. А при поиске вытесняемого элемента кэш просматривается на поиск минимального значения счетчика. Очевидно, что такая модель неоптимальна, потому что каждое удаление требует просмотра всего кэша - сложность алгоритма О(n). К счастью, существует возможность свести алгоритм поиска к O(1), изменив структуру кэша.
Можно представить кэш как хэш таблицу, в которой стобцами - значением хэша будет значение счетчика, а строками - элементы с этой частотой обращений. Тогда вытеснение элемента сводится лишь к взятию нужного столбца и удалению из него произвольного элемента.
Особняком стоит Оптимальный алгоритм кэширования. Фактически, его описание гласит, что в каждый момент времени система знает, какие данные ей понадобятся в будущем и в каком порядке, и согласно этому может вытеснять и помещать в кэш данные наилучшим образом. В своем идеалистическом определении алгоритм нереален, однако есть множество способов к нему приблизиться. Например, проанализировав поведение кэша при работе с данными, выбирать нужную стратегию из ранее описанных.
Ранее я уже упоминал обилие сетевых файловых систем. Для конкретики анализа алгоритмов кэширования, а также из-за необходимости адаптации под задачи, выполняемые на кластере ИММ УрО РАН, в качестве исследуемой системы была выбрана GlusterFS
GlusterFS не просто сетевая файловая система - это распределенная файловая система, что означает наличие большого числа серверов - узлов кластера и, возможно, большого числа клиентов. При этом серверные узлы не взаимодействуют между собой, а клиенты могут не знать о существовании друг-друга. Кроме этого, архитектура GlusterFS основана на трансляторах - небольших блоках, реализующих определенный функционал.
Таким образом, архитектура GlusterFS - это дерево из взаимосвязанных трансляторов. Например, рассмотрим дерево стандартного кластера GlusterFS из одного клиента и трех серверных узлов. В корне дерева находится транслятор кластеризации dht, распределяющий данные по узлам в зависимости от значения хэша файла. У него три потомка - три транслятора клиентского кэширования, у каждого из которых по одному транслятору типа client - позволяющему соединяться с сервером посредством сети. В данном случае, это TCP/IP, но может быть и FibreChannel, и сокеты, и так далее. Каждый клиент соединяется с одним транслятором типа сервер, предоставляющим сетевой доступ и запущенном каждый на своем узле. В этом простом примере серверная часть состоит всего из двух трансляторов - публикующего server, и транслирующего команды в POSIX-совместимую файловую систему, однако их может быть больше. Особое внимание здесь заслуживает транслятор io-cache, который и обеспечивает необходимый нам функционал и который я и модифицировал.
Обновленная версия транслятора io-cache - это фактически транслятор четыре в одном: с помощью одного параметра в конфигурационном файле можно изменить структуру кэша и алгоритм вытеснения данных в нем. Транслятор поддерживает LRU в неизменном виде. Интересен был вариант с FIFO, для понимания, оправданны ли затраты LRU на постоянное перестроение очереди. А также MRU и LFU как самостоятельные стратегии совершенно иного, нежели LRU подхода к заполнению кэша. После того, как были достигнуты оптимальные значения производительности реализации стратегий я стал проводить замеры времени доступа в одних и тех же сценариях при различных стратегиях кэширования.
На стенде был один сервер и один компьютер клиент, где различные клиенты были разными процессами (не больше числа процессорных ядер в компьютере). Настройки кэша были выбраны такими для того, чтобы уже на небольших размерах файлов увидеть картину, которая должна произойти в реальности. Общий размер кэша 6 мегабайт, а размер каждой страницы 128 килобайт. Каждым клиентом на сервере создавалось 50 файлов одинакового размера и случайного содержания, после чего проводился тест на чтение этих файлов по 1000 раз. Графики времени каждой попытки приведены в приложении к работе, здесь же будем оценивать лишь среднее арифметическое по всем попыткам.
Первый тест на 10-ти Килобайтных файлах. Названия столбцов означают сценарий, при котором проводились замеры: цифра означает количество конкурентных клиентов, s - последовательный доступ, то есть циклически файл за файлом, r - случайный. w - означает тесты, в которых один клиент записывал файл, а другой параллельно его читал. Высота столбцов - среднее время доступа к файлу.
Видно, что на таких малых размерах файлов не особо важен выбор стратегии кэширования, скорее важна производительность самой структуры данных. Так как LRU был нативно разработан и внедрен в эту платформу, а LFU был реализован с помощью быстрой хэш-таблицы - видно их преимущество во времени доступа к файлу.
Так как файлы 100Кб по прежнему меньше размера страницы кэша - картина точно такая же, как и в предыдущем случае.
Рассмотрим выход за границы страницы.
200Килобайт - заметно преобладание LRU.
Более интересен случай файлов 1МБ. При таком размере кэш должен заполняться уже спустя 6 файлов, что покажет бОльшую нагрузку на алгоритм вытеснения и его оптимальность.
И правда, в таком случае картина меняется, в некоторых случаях отодвигая LRU далеко на второй план, отдавая предпочтение MRU при последовательном доступе и LFU при случайном конкурентном. Также интересны столбцы с буквой m - это сценарии работы, в котором каждый файл читался по четыре раза подряд перед тем как перейти к следующему. Стоит отметить, что в таких случаях на первое место выходят LFU и как ни странно, FIFO, видимо за счет того, что в отличие от LRU, нет необходимости перестраивать очередь.
И напоследок, файлы размера больше, чем кэш. Ситуация не видится интересной, так как в любом случае происходит вытеснение, а это - только скорость работы структуры данных - LRU, либо LFU, как было в случае с 10Килобайтными файлами.
Стоит отметить также, что во всех представленных графиках, в сценарии одновременной записи и чтения выигрывает LFU.
В ходе работы был поставлен эксперимент и проанализированы результаты. Можно с достаточно большой уверенностью сказать, что при использовании транслятора io-cache в инфраструктуре сетевой файловой системы GlusterFS, в различных сценариях работы требуются разные алгоритмы кэширования. В расчет не берутся случаи сильно маленьких файлов, в которых кэш не может реализоваться полностью и случаи больших файлов, в которых алгоритм кэширования снова не важен. В среднем, для случая последовательного чтения файлов, вне зависимости от числа клиентов, оптимальной стратегией является вытеснение только что использованного - MRU.
В случае же случайного чтения наилучшие показатели у стратегии кэширования, основанной на частоте использования - LFU
LFU также является абсолютным лидером в сценариях одновременного чтения и записи файлов.
А так популярный в локальных файловых системах LRU и его упрощенная версия FIFO хороши лишь при последовательном чтении с повторами, что не так часто можно встретить в сетевой асинхронной модели файловых систем.
Таким образом, поставленная задача поиска оптимальной стратегии и возможность ее выбора при настройке клиента имеет такой ответ, для инфраструктуры GlusterFS.
Реализация же самого транслятора и параметров тестов доступны в моем репозитории на GitHub.