За последние 24 часа нас посетили 20579 программистов и 1130 роботов. Сейчас ищут 706 программистов ...

Задержка при отдаче файлов из базы через FILESTREAM

Тема в разделе "MSSQL", создана пользователем Pran, 6 фев 2011.

  1. Pran

    Pran Активный пользователь

    С нами с:
    15 янв 2011
    Сообщения:
    39
    Симпатии:
    0
    Хранилище FILESTREAM объединяет компонент SQL Server Database Engine с файловой системой NTFS, размещая данные больших двоичных объектов (BLOB) типа varbinary(max) в файловой системе в виде файлов.

    Условия для PHP - работа через драйвер php_sqlsrv.dll (SQL Server Driver for PHP 2.0).

    В ответе от сервера приходит открытый дескриптор потока, который можно скормить функции fpassthru.
    Основная проблема - поток не поддерживает смещение курсора (seek) в любом его проявлении.

    Решил сделать в своей системе поддержку докачки файлов и работу в несколько потоков, используя нарезку данных средствами SQL: SUBSTRING(content, opening, ending). Всё работает на «Ура», однако тормоза начинаются тогда, когда клиент запускает дополнительные секции на больших файлах.

    Заголовок HTTP/1.1 206 Partital Content отдаётся клиенту спустя две минуты после отправки запроса (для файла размером в 180 МБ), в то время как основная секция (без деления, полный файл) отдаётся моментально.

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

    Пока не нашёл эффективного способа решения проблемы. Может, кто подскажет?
     
  2. Pran

    Pran Активный пользователь

    С нами с:
    15 янв 2011
    Сообщения:
    39
    Симпатии:
    0
    Нашёл правильный вариант использования fseek: нужно ввести опцию SEEK_CUR, которая сдвинет курсор на указанное число байт вперёд относительно текущей позиции. Видимо, значение по умолчанию не по нраву потоку FILESTREAM.

    Сдвиг выполняется всего один раз - от нулевой позиции до «точки входа» запрошенной секции.

    PHP:
    1.  
    2.  
    3. $opening = null; // Точка входа секции.
    4. $ending = null; // Точка выхода секции.
    5.  
    6. if($range = getenv('HTTP_RANGE')) {
    7.  
    8.     list($opening, $ending) = explode('-', str_replace('bytes=', null, $range));
    9. }
    10.  
    11. #
    12. // Тут некоторый запрос к базе.
    13. #
    14.  
    15. // Получение дескриптора (обёртка функции sqlsrv_get_field).
    16. //
    17. $stream = $db->get_field(0, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
    18. fseek($stream, $opening, SEEK_CUR); // Правильный сдвиг - без возмущений со стороны Пыха.
    19.  
    20. #
    21. // Тут следует отправить заголовки.
    22. #
    23.  
    24. // Отправка оставшейся части потока клиенту.
    25. //
    26. fpassthru($stream);
    27. fclose($stream); // Закрытие «файла» (пока неизвестно, насколько это актуально).
    28.  
    Слегка доработал свой профайлер, чтобы он контрольные замеры скидывал в отдельный лог-файл.

    Контрольные замеры брались при возобновлении скачивания после останова (где-то в районе 60 МБ).
    После каждого прохода производился перезапуск sqlservr.exe

    Конфигурация:
    Intel Core Duo T2350 @ 1.86GHz, 1.00 ГБ ОЗУ.

    Программное обеспечение (всё на одной машине - локальная разработка):
    Windows 7 Professional, SQL Server 2008, IIS7.5, PHP 5.3.2, SQL Server Driver for PHP 2.0,
    ReGet Deluxe 5.2.0.330 Personal

    Размер скачиваемого файла - 352 МБ.

    Первый проход - для варианта с SUBSTRING
    (постепенно с 48% sqlservr.exe нагрузил ОЗУ на 80%+ и до перезапуска не хотел отдавать половину объёма):

    0.110200 с - инициализация цепи управления
    48.956076 с - выполнение запроса к базе данных
    0.052391 с - данные получены
    0.007702 с - заголовки отправлены
    51.720121 с - содержимое отдано клиенту [страрт в 70728076, длина 298797620, размер 369525696]

    [​IMG]

    Второй проход - для варианта с fseek:
    (загрузка ОЗУ стабильно держалась в пределах 48-50%):

    0.051924 с - инициализация цепи управления
    0.001471 с - выполнение запроса к базе данных
    0.000286 с - данные получены
    5.785100 с - сдвиг fseek с 0 в позицию 366293186, размер файла 369525696
    0.002408 с - заголовки отправлены
    0.050350 с - секция отдана клиенту

    [​IMG]

    Третий проход - для варианта с fseek:
    (загрузка ОЗУ до старта около 70%)

    0.102440 с - инициализация цепи управления
    0.001659 с - выполнение запроса к базе данных
    0.000357 с - данные получены
    1.099184 с - сдвиг fseek с 0 в позицию 65459608, размер файла 369525696
    0.002192 с - заголовки отправлены
    48.806264 с - секция отдана клиенту

    Оба варианта реализации (SUBSTRING и fseek) отдавали содержимое файла через fpassthru.
    По-прежнему существует значительная задержка при запросе дополнительных секций, даже с применением fseek. Очевидно, не учтён какой-то дополнительный фактор.