Хранилище 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. И как раз на этот процесс приходится львиная доля ожидания. Необосновано. В дальнейших тестах видно, что нагрузка приходится на оперативную память. Пока не нашёл эффективного способа решения проблемы. Может, кто подскажет?
Нашёл правильный вариант использования fseek: нужно ввести опцию SEEK_CUR, которая сдвинет курсор на указанное число байт вперёд относительно текущей позиции. Видимо, значение по умолчанию не по нраву потоку FILESTREAM. Сдвиг выполняется всего один раз - от нулевой позиции до «точки входа» запрошенной секции. PHP: $opening = null; // Точка входа секции. $ending = null; // Точка выхода секции. if($range = getenv('HTTP_RANGE')) { list($opening, $ending) = explode('-', str_replace('bytes=', null, $range)); } # // Тут некоторый запрос к базе. # // Получение дескриптора (обёртка функции sqlsrv_get_field). // $stream = $db->get_field(0, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY)); fseek($stream, $opening, SEEK_CUR); // Правильный сдвиг - без возмущений со стороны Пыха. # // Тут следует отправить заголовки. # // Отправка оставшейся части потока клиенту. // fpassthru($stream); fclose($stream); // Закрытие «файла» (пока неизвестно, насколько это актуально). Слегка доработал свой профайлер, чтобы он контрольные замеры скидывал в отдельный лог-файл. Контрольные замеры брались при возобновлении скачивания после останова (где-то в районе 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] Второй проход - для варианта с fseek: (загрузка ОЗУ стабильно держалась в пределах 48-50%): 0.051924 с - инициализация цепи управления 0.001471 с - выполнение запроса к базе данных 0.000286 с - данные получены 5.785100 с - сдвиг fseek с 0 в позицию 366293186, размер файла 369525696 0.002408 с - заголовки отправлены 0.050350 с - секция отдана клиенту Третий проход - для варианта с fseek: (загрузка ОЗУ до старта около 70%) 0.102440 с - инициализация цепи управления 0.001659 с - выполнение запроса к базе данных 0.000357 с - данные получены 1.099184 с - сдвиг fseek с 0 в позицию 65459608, размер файла 369525696 0.002192 с - заголовки отправлены 48.806264 с - секция отдана клиенту Оба варианта реализации (SUBSTRING и fseek) отдавали содержимое файла через fpassthru. По-прежнему существует значительная задержка при запросе дополнительных секций, даже с применением fseek. Очевидно, не учтён какой-то дополнительный фактор.