600 likes | 755 Views
«Вечная статика» оптимизация отдачи контента. Сергей Скворцов. 2011-0 4 -26. I. Теория. Стороны и связь. Браузер отображение/рендеринг страниц/данных Протокол передача данных: запрос-ответ или stream Сервер приём запросов, формирование и передача ответов. Скорость показа контента.
E N D
«Вечная статика»оптимизация отдачи контента Сергей Скворцов 2011-04-26
Стороны и связь • Браузер • отображение/рендеринг страниц/данных • Протокол • передача данных: запрос-ответ или stream • Сервер • приём запросов, формирование и передача ответов
Скорость показа контента • Скорость отображения в браузере • Скорость передачи по сети • Скорость ответа от сервера
1)Скорость отображения в браузере • Рендеринг / вёрстка • Встраивание ресурсов/данных • Data URI, CSS sprites • увеличивает размер ответа • AJAX, WebSockets • Отложенная загрузка;Prefetching • JavaScript: async/defer; post-load
1.1)Рендеринг/вёрстка • Корректный порядок указания ресурсов (JS/CSS) • Yahoo «Best Practices for Speeding Up Your Web Site» • Самоограничения: CSSexpressions • Прогрессивная вёрстка • см. StoyanStefanov (Yahoo) • Верстка независимыми блоками • см. Виталий Харисов (Яндекс) • Шаблоны на client-side (XSLT)
2)Скорость передачи по сети • Каналы… • … связность, распределение по странам, ДЦ • DNS-resolving • Уменьшение числа lookup’ов (число хостов, TTL) • Geo-balancing, IP anycast • Постоянные соединения • HTTP 1.0 keep-alive == HTTP 1.1 persistent
2)Скорость передачи по сети • Раздача с нескольких хостов • CDN; DNS/geo-balancing, TCP anycast • Параллелизация загрузки: default лимит у браузеров – не более 6 запросов на хост • JS-хостинги (для jQuery, YUI, …) • 13% страниц используют Google Libraries API • Хорошо кэшируютсямежду сайтами • Privacy: источник Referer’ов для поисковиков
2)Скорость передачи по сети • HTTP pipelining - не популярен • Дружим с proxy-servers, и ненавидим их • Кэширование! • Их 10% запросов (оценка по Openstat.Trends) • «Над»-протоколы (HTTPS, SPDY) • HTTPS – безопасность, но overhead • SPDY – только Google, нет поддержки в nginx :)
3)Скорость ответа от сервера • Скорость генерации ответа • Уменьшение размера ответа • Уменьшение размера запроса • Уменьшение числа запросов
3.1) Скорость генерации ответа • Хорошие frontend’ы • Быстрые серверы приложений (backend’ы) • Прогрессивная отдача контента • flush() • Кэширование: • данных (memcached) • ответа (mod_cache*)
3.2)Уменьшение размера ответа • Сжатие ответа • ngx_http_gzip, ngx_http_gzip_static • mod_gzip, mod_deflate • Отложенная «подгрузка» JS, контента • Post-loading
3.2)Уменьшение размера ответа • Оптимизация содержимого (minification) • Оптимизация JS/CSS • Closurecompiler, YUI Compressor • Оптимизация графики • Оптимизация HTML • Пробелы, комментарии, default атрибуты, … • htmlcompressor,или при компиляции шаблонов
3.3)Уменьшение размера запроса • Отдача статики в другом домене • Чтобы не слать лишние заголовки: Cookie • Уменьшаем размер Cookie • FYI: максимальный суммарный размер на домен: • Firefox, Chrome - no limit • Opera, IE6 - 4096байт • IE8 - 10234байт
3.4)Уменьшение числазапросов • Кэширование контента на стороне браузера • Объединение стилей и скриптов (JS, CSS) • Объединение и внедрение картинок(CSS sprites, Data URI, MHTML) • ценой большего размера ответа • Избегать редиректов
Итого • Всё вышеописанное можно использовать как checklist, варианты ответов: • Уже так делаем • Не делаем, потому что (программисты|админы) сказали что… • Не знали / совсем забыли – надо подумать
Как работает кэширование в HTTP • При первом запросе к ресурсу браузер: • Запоминает заголовки ответаLast-Modified и/ли ETag • Определяет сколько времени можно не обращаться к серверу, а использовать локальный кэш - на основе данных из заголовков: • Expires • Cache-Control http://www.w3.org/Protocols/HTTP/1.1/rfc2616bis/
Как работает кэширование в HTTP • При втором запросе к серверу: • Если ресурс в локальном кэше – браузер делает валидацию – шлёт условный запрос: • If-Unmodified-Sinceи/ли If-None-Match • Если ресурс не менялся (304 Not modified):ждём опять expiration
Нюансы де-факто • По факту ETagиспользуетсядля статики так: • nginx: не вычисляет вообще • Apache:inode-size-timestamp • IIS:Filetimestamp:ChangeNumber • Это не работает на кластере машин • inodeи ChangeNumberбудут разные
Проблемы этого дня • Заголовки ExpiresиCache-Control: max-age тупо не выставляют • 56% запросов вообще не указывают expiration • 24% запросов может кэшироваться http://www.stevesouders.com/blog/2011/04/18/http-archive-max-ag/ • Нопри этом есть статика – которая меняется редко: • CSS, JavaScript, картинки, Flash, иконки (!)
Проблемы этого дня • Поскольку нет expiration – каждый раз шлётся запрос (в надежде на 304 Not modified) • Но такой запрос это: • Минимум 2 IP пакета • Время на ожидание ответа • Особенно печально ждать когда блокируемся на рендеринге
Решение: выставлять expiration • Выставлять expiration для динамики несложно (семантику знает backend) • Но любой константный expiration для статики приводит к проблемам при обновлении сайта (новый дизайн, шаблоны, …) • Единый глобальный Expire - выставить нельзя • Выход: версионированиена уровне URI
Версионирование • Наивный подход – добавлять «?» + timestamp: /favicon.ico?ver=3 • Многие прокси серверы считают URL с «?» некэшируемым в принципе • Не учитываются зависимости внутри ресурса: • /style.css?ver=3ссылается внутри на /logo.png?ver=5
Версионирование: верный подход • Добавляем версию внутрь пути: /favicon_1303591683.ico • Версию можно брать из VCS, но не каждый ресурс лежит в репозитории, он может создаваться при выкатке • Очевидный вариант – timestamp • Для файлов с зависимостями «наружу» – считаем версию с учётом зависимостей, пример: md5( timestamp + @versioned_dependencies_urls) • Экономим байты – используя Base64 URLSafe
Обработка внешних зависимостей • Делаем общий префикс /s/в URLах статики • Для всех шаблонов, которые ссылаются на версионированную статику, обновляем ссылки • Всё! Статика стала вечной, поэтому: location /s/ { expires max; gzip_static on; }
Применимость • Стоит применять для: • Файлов стилей и скриптов (CSS, JS) • Картинок, являющихся частью дизайна/шаблона • Flash-объектов • Не надо применять для: • Скачиваемых файлов (и подлежащих индексации) • Особо актуально: • Мобильные сайты
Немного статистики:число запросов
HTTP Archive:медиана размера ответа http://httparchive.org/interesting.php#responsesizes
Htdocs layout • На файловой системе: htdocs/ frontend/ eternal/ backend/ • frontend - статика, отдаётся nginx • eternal– «вечная» статика, отдаётся nginx • backend - шаблоны, используются в upstream
VCS layout • В репозитории: trunk/ htdocs-frontend/ a/ css/ img/ js/ htdocs-backend/
Сборка пакетов: шаги • Checkout • Processing • Стадия «generate» • Стадия «preprocess» • Стадия «optimize» • Стадия «produce» • Генерация «вечной статики» (кладём в /s/) • Packing distfiles
Сборка пакетов – Processing • Стадия «generate» • Генерация новых файлов, на основе checkout’а • img2css– набор иконок в один CSS файл • Стадия «preprocess» • Изменение содержимого файла, и/ли метаданных о нём • inline-includes– объединение файлов • CSS: раскрытие @import • JavaScript: раскрытие _include_js()
Сборка пакетов – Processing • Стадия «optimize» • Оптимизация формата файла, с сохранением инварианта его семантики • yuicompressor– JS/CSS minification • Стадия «produce» • Генерация других файлов на основе итоговых • gzip_static– создание сжатой версии статики
Сборка пакетов – «вечная» статика • Конфигурация: <handler name="eternal-static" process_path="/a/" target_path="/s/" match_ext="gif icojpg pngswfjscss" expand_ext="jscss" />
Сборка пакетов – «вечная» статика • Сканируем директорию process_pathна предмет статических файлов с нужными расширениями • Обрабатываем раскрываемые файлы (*.js *.css) на предмет ссылок на другие найденные ранее статические файлы • Строим граф зависимостей
Сборка пакетов – «вечная» статика • В директории target_pathсоздаём соотв. версии статических файлов (и их gzipped-версий, если у файлов был handlergzip_static) • Итоговый файл будет называтьсяfilename_revision.ext, где revision – ревизия файла /a/file.txt /s/file_REVISION.txt
Сборка пакетов – «вечная» статика • Для статического файла, который не имеет зависимостей (в т.ч. тех, которые не раскрываются вообще, типа картинок), ревизия равна mtime • VCS ревизия может быть недоступна, если файл генерировался. • При генерации файлов mtimeфинального файла всегда равен максимальному mtimeего исходных файлов
Сборка пакетов – «вечная» статика • Для статического файла, который имеет зависимости, ревизия вычисляется как md5( join('\0', mtime, sort@eternal_static_deps) ) - т.е. создаётся семантически уникальная ревизия файла. • Жёсткое создание ревизии (md5 от тела файла) не используется, чтобы при изменениях контента, которые семантически инвариантны (например, применён новый алгоритм оптимизации/obfuscation файлов) не создавалась новая ревизия.
Сборка пакетов – «вечная» статика • Создаём файл /s/.mapping, который содержит строки с парами "статический файл", "вечно-статический файл": /a/js/base.js /s/js/base_iv6uTQ.js /s/css/main.css /s/css/main_msy5mT8H-Ak6hBAsce-30w.css
Пример: www.openstat.ru <!DOCTYPE HTML><html lang="ru"><head> <title>Интернет-статистика и веб-аналитика | Openstat: Независимая аналитика</title> <link rel="stylesheet" type="text/css" href="/s/css/main_msy5mT8H-Ak6hBAsce-30w.css" /> <script type="text/javascript" src="/s/js/base_iv6uTQ.js"></script>
Установка пакетов • Файлы ставятся в share/htdocs/frontend • На этапе POST-INSTALL запускаем: rsync -rlptgoD--checksum--delete-after share/htdocs/frontend htdocs/frontend • Это делается для того, чтобы при удалении пакета в htdocs/frontendчто-то оставалось
Установка пакетов • Также копируем вечную статику: rsync -rlptgoD--checksum htdocs/frontend/s/ htdocs/eternal • В htdocs/eternalфайлы никогда не удаляются!
Установка пакетов • Раскрываем(и атомарно замещаем) файлы шаблонов по списку из /s/.mapping:htdocs/backend • Поскольку директория /a/также ставится, то задержка в раскрытии файлов, равно как их нераскрытиене влияет фатально на работу сайта - просто запросы пойдут к "менее вечной" статике.
Конфигурация frontend’а • Пример для обычного веб-сервера: location ^~ /s/ { alias htdocs/eternal; gzip_static on; expires max; }
Соглашения • Соглашения для раскрываемых файлов: • Полное указание пути на ссылаемые файлы • В JS не использовать составление URLиз частей , прописывать в коде полностью(как hash)