Для дотошных В последнее время среди разработчиков серверных приложений часто возникают споры о том, как лучше управлять файлами и какая технология обеспечивает более быстрое чтение/запись файлов.
В Интернете стали появляться статьи и статьи о сравнительной производительности локальной файловой системы и GridFS. Или о хранении файлов в реляционной базе данных в виде BLOB вместо хранения их на жестком диске в файловой системе.
Поэтому я решил вмешаться в это противостояние.
Сегодня мы будем сравнивать производительность и накладные расходы MongoDB 2.6.7 x64 GridFS, MS SQL Server Express 2012 v11.0.5058.0 x64 и NTFS. Для эксперимента мы использовали платформу Windows 7 x64 SP1 на процессоре AMD Athlon(tm) II X2 250 3,00 ГГц с 4 ГБ оперативной памяти 1033 МГц и жестким диском 600 ГБ SATA 6 ГБ/с Western Digital VelociRaptor 10000 об/мин 32 МБ.
После каждого теста компьютер перезагружался и базы данных сбрасывались.
Производительность будем рассматривать на примере файлового сервера на C# под .
NET 4.5, код которого прикрепил к ст.
Перейдем к делу
В ходе теста мы попытаемся сохранить 1000 файлов по 10 000 000 байт каждый.Каждый загруженный файл будет зарегистрирован в таблице «Файлы»: «Хеш» используется для проверки того, был ли уже загружен такой файл, «НовоеИмя» связывает информацию о файле с его растровым изображением на сервере в таблице «FileMapping».
Схема базы данных файлового сервера:
При этом мы попробуем хранить файлы в GridFS с помощью официального драйвера MongoDB и просто на жестком диске с помощью класса FileInfo из библиотеки классов .
NET. Чтобы иметь возможность удобно строить «читабельные» запросы к SQL Server с помощью технологии LINQ и лямбда-выражений, мы будем использовать Entity Framework 6.0:
Однопотоковая запись
Для начала протестируем сохранение просто на диск, загружая файлы в однопоточном режиме.По результатам пяти запусков потребовалось на операции: 164751, 165095, 164611, 165937 и 166296 миллисекунд. Максимальная разница между результатами составила около 1%.
Это означает, что в среднем процесс работал 165338 миллисекунд, из них 52966 — время регистрации файла, 12685 — время записи файла:
Запуск программы с измерением времени работы ее частей и без нее не привел к разным результатам.
Запуск программы с браузером потоков показал, что максимальный размер занимаемой памяти составил 59796 КБ.
Дисковое пространство: примерно 9,3 ГБ.
Для простоты ниже я сразу приведу средний результат по времени выполнения операций и в секундах.
Теперь давайте проверим запись файлов в базу данных SQL Server. Размер полученной базы данных на диске составил примерно 9,8 ГБ.
На пике программа занимала 233432 КБ, а СУБД — 1627048 КБ.
Программа работала в среднем 998 секунд. Из них 34 секунды приходится на регистрацию файлов и 823 секунды на запись:
Следующим был GridFS. Используемое дисковое пространство составляет примерно 12 ГБ.
MongoDB израсходовала всю доступную оперативную память.
При этом потребление памяти SQL Server и нашего сервера осталось в пределах нормальных значений, которыми в данной ситуации можно пренебречь.
Все было завершено примерно за 921 секунду.
Регистрация – 60 секунд, запись – 766 секунд:
Многопоточная запись
Здесь мы столкнулись с первыми трудностями: при попытке нескольких потоков одновременно установить соединение с СУБД возникают ошибки.И если работа с MongoDB идет «без подтверждения» — драйвер не выдает ошибку и продолжает работать и все идет гладко, то EF при вызове «CreateIfNotExists» при этом показывает кучу отладочной информации, выдает ошибку его невозможно перехватить с помощью try-catch, и процесс завершается с ошибкой.
При этом ошибка не появляется, если скомпилировать и запустить его вместе с отладчиком из среды разработки.
Проблема решилась синхронизацией потоков и поочередным установлением соединений с СУБД.
Проведем операции записи, используя для записи 20 потоков по 50 файлов в каждом.
Тестирование GridFS показало широкий разброс: 716259, 623205, 675829, 583331 и 739815 миллисекунд, в среднем 668 секунд. Как видите, при тех же накладных расходах на выполнение ушло меньше времени.
Это связано с меньшим временем простоя, как показывает Concurrency Visualizer на графиках использования ядер ЦП (выполнение с использованием Concurrency Visualizer заняло значительно больше времени):
При записи файлов непосредственно в файловую систему разброс полученных тестовых значений был невелик и среднее значение составило 170 секунд. Как видите, разница между однопоточной и многопоточной записью на диск составила менее 3%.
Поэтому считаю целесообразным опустить графики использования процессора для этого случая.
Многопоточная запись файлов в базу данных SQL Server приводила к дополнительным проблемам: под нагрузкой некоторые потоки завершались сбоем из-за таймаута.
В результате соединение было потеряно, процесс завершился с ошибкой, а база данных была приведена в несогласованное состояние.
На каждую тысячу запросов первая попытка провалилась примерно в 40 случаях.
Из них около двух закончились неудачей даже со второй попытки.
Проблема решилась увеличением таймаута до 30 секунд и повторной попыткой подключения в случае неудачи.
При сохранении 1000 файлов в многопоточном режиме с использованием SQL Server разброс по времени составил менее 5%, а результат составил в среднем 840 секунд. Это почти на 16% быстрее, чем в однопоточном режиме:
Чтение файлов
Теперь протестируем чтение файлов из разных хранилищ.В однопоточном режиме мы считаем все файлы подряд, а в многопоточном — 20 потоков по 50 случайных файлов в каждом.
Традиционно по 5 попыток каждого типа:
Чтение файлов из базы данных SQL Server с первого раза не прошло гладко.
Entity Framework кэширует результат на стороне клиентского приложения для каждого запроса к базе данных для получения данных.
И, хотя CLR предполагает автоматическую «сборку мусора», память быстро расходуется, что приводит к ошибке «OutOfMemory» и сбою процесса.
Это связано с тем, что EF не обрабатывает этот тип исключения самостоятельно и не удаляет данные из «кэша», даже когда вся память израсходована.
А при выполнении запросов к таблицам, хранящим файлы, это становится критически важным.
Проблема решена отключением кэширования в коллекциях сущностей FileMapping с помощью настройки AsNoTracking.
Общий
Имеем изнасилованный жесткий диск и следующую сводную таблицу:Бонус
Стараясь предвидеть возможную критику и заранее ответить на еще не заданные вопросы, чуть позже дополню статью некоторыми интересными (надеюсь) и, возможно, полезными комментариями.Первое: если перед каждым новым тестом не сбрасывать базу данных, то производительность существенно не изменится.
По крайней мере, ни при втором, ни при третьем, ни при повторных многократных (в разумных пределах) запусках получить разницу в результатах не удалось.
Поэтому мы опустим подробное описание этой части работы, как самой скучной.
Второе: многие наверняка скажут, что при работе с СУБД через сокеты данным для передачи придется пройти через весь стек сетевых протоколов связи туда и обратно.
Поэтому мы рассмотрим другой вариант. По сути, тот же вариант и с классом FileInfo, только обращаться к нему мы теперь будем с помощью .
NET Remouting, если вы понимаете, о чем я:
Теги: #local #file #download #download #upload #save #get #put #server #thread #Express #AsNoTracking #outofmemory #CLR #mapping #FileInfo #entity #CreateIfNotExists #try #catch #framework #C++ #.
NET # удаленное взаимодействие #sql #grid #fs #mongo #mongo #db #database #fail #storage #loading #saving #upload #server #multithreading #.
NET #C++ #Тестирование веб-сервисов
-
Обзор Компьютера Toshiba Qosmio G35-Av650
19 Oct, 24 -
Число
19 Oct, 24 -
Ноутбуки Apple Из Сша Под Заказ.
19 Oct, 24 -
Проверка Загруженного Аватара
19 Oct, 24 -
Мы Преодолеем
19 Oct, 24