Наверх ▲

Ajax Layout

Олег Илларионов Олег Илларионов Разработчик социальной сети ВКонтакте.

Олег Илларионов: Меня зовут Олег Илларионов, я представляю ВКонтакте. Сегодня хотелось бы рассказать о том, каким веб стал сейчас, как он изменился и к чему он пришел. Мы называем это Ajax Layout. Я не знаю, может быть, кто-то использовал это словосочетание до нас. Может быть, нет. Но мы не нашли другого термина, которым можно было бы назвать эту сущность. Разные люди называют это по-разному. Сейчас расскажу, о чем речь.

Начать хотелось бы с переходов.

 

Что такое переходы? Это процесс, когда пользователь переходит с одной страницы на другую. Раньше всю ответственность за переходы брал на себя браузер. Браузер сам "ловил" событие клика по ссылке. Браузер сам показывал индикатор загрузки на странице. Он получал контент. Потом он "смотрел", какие стили и какие скрипты требует эта страница. Получал стили и скрипты. 

По мере того, как он получал контент, он последовательно отрисовывал его на определенной странице. Потом он отображал его и менял адрес в заголовке. Большинство браузеров так и работает. Большинство сайтов тоже так выглядит. Вы прекрасно знаете об этом.

Но в определенный момент все поняли, что такая концепция недостаточно хорошо работает. Это не дает возможности оптимизировать очень многие вещи. Это не дает возможности контролировать многие процессы, потому что у нас полностью перезагружается страница, мы все теряем и все получаем заново.

Мы должны полностью формировать страницу заново. Мы не можем хранить события, мы не можем показывать что-то. Нам тяжело работать с длинными опросами (англ. Long Poll). Веб очень простой, не в режиме реального времени. Нужно это изменить. Начали экспериментировать. Многие сайты реализовали эту технологию в разной степени прогресса.

 

Началось это с простой схемы, когда пользователь делает щелчок, клик. JavaScript отправляет Ajax-запрос на какой-то контент. После этого контент вставляется в страницу, и устанавливается хеш в браузере. После этого очищается память, убираются какие-то локальные переменные, которые были установлены.

Эта схема работала недостаточно хорошо. Во-первых, адреса были некрасивыми. Были хеши, которые передавались на серверную сторону. Это была основная их причина. Если мы имеем страницу с хешом, то хеш не отправится к серверу, и нам нужно отловить его на JS и отправить самим. Это значит, что когда пользователь вставляет к себе в браузер ссылку типа "vkontakte.ru/hash что-то там", мы должны сначала загрузить какую-то страницу, потом сделать Ajax-запрос на сервер, "поймав" то, что у нас в адресе. После этого мы можем ее как-то отображать и работать дальше. Это плохо. 

Потом был следующий этап реализации того, как это все может работать. Это момент, когда мы делаем клик, очень хорошо оптимизируем скорость нагрузки за счет того, что заранее знаем, для каких страниц какие стили и скрипты нужны. Мы просто храним в файле конфигурации определенную схему сайта, где указано, что у нас есть такие-то страницы, которые требуют такие-то скрипты по «регуляркам». У нас есть «регулярки», которые описывают страницы. Мы находим нужную «регулярку» и решаем, какие скрипты нужно подключить. То есть мы начинаем загружать статику не в тот момент, когда мы уже получили контент, а одновременно. Тем самым, если статика не закеширована, мы ускоряем загрузку страницы. После чего мы отображаем контент и меняем строку адреса. Но тут есть проблемы с адресом, поэтому это плохо.

Еще один момент – то, что нам приходится изображать загрузку страницы. Когда мы делаем Ajax-запрос, в большинстве браузеров ничего не "крутится". Пользователь думает, что запроса нет, и это очень плохо. Поэтому вначале мы использовали анимированный значок (англ. favicon). Это простой способ изобразить что-то. 

Но потом мы пришли к History API. Он позволил избавиться от хешей в тех браузерах, которые это поддерживают. Естественно, в браузерах, которые этого не поддерживают, мы не можем работать с History API. Когда была выкачана первая версия (это было, наверное, где-то в ноябре прошлого года, даже чуть попозже), это работало только в Chrome. На тот момент был всего 1 браузер, который поддерживал History API. Все остальные работали с хешами.

History API стоит выделить, потому что он, на самом деле, дал такой шаг. Если бы его не было, мы не стали бы ничего делать. Мы бы оставили старую схему и пытались бы искать какие-то другие способы – каждый раз перезагружать всякие уведомления, чаты и так далее.

Что у нас осталось? Мы отображаем контент. Используем нормальный адрес там, где можно. Где нет, используем решетку (#) и используем разные хаки. Например, известно, что для того, чтобы старые версии IE "отловили" смену события, поняли, что у нас был переход на другую страницу, нам нужно использовать iframe. Нужно менять там страницу. 

В противном случае просто не будет работать навигация. Пользователю не нравится, когда он не может нажать на кнопку «назад» и вернуться на предыдущую страницу. Это работало достаточно правдоподобно. Это уже увидели пользователи. 

Следующий вопрос был в том, что это работало медленно. Казалось бы, время генерации на сервере только ускорилось, потому что нам не нужно получать левое меню или еще что-то. Понятно, что это хорошо кешируется, но это трафик. Нам не нужно его получать. Это проблема, потому что страница вроде бы генерируется быстрее, а визуально это медленнее.

Но очень быстро стало понятно, в чем дело. Все дело в том, что страница отображается только в тот момент, когда мы получили весь контент. Мы получили гигантскую страницу и только потом стали ее отрисовывать, в то время как при классической загрузке браузер получил первую часть. Если у него есть нужные скрипты и стили (если он их уже получил), он отрисует это и по мере получения информации будет отрисовывать остальное.

Пришлось осознать эту проблему. Понять, что вся проблема в контенте и вернуться к каким-то истокам.

Начали все заново осмыслять. Стало ясно, что Ajax не подходит для решения этой задачи. 

Поэтому пришлось использовать iframe. У нас есть iframe, и когда мы хотим перейти на другую страницу, мы открываем скрытый iframe, в котором есть части. Части – это разные части страницы. Вместо того, чтобы брать и загружать сразу всю страницу посредством Ajax, мы загружаем ее по частям. 

Сначала идет заголовок… В этом iframe есть функция, которая "перекидывает" код в заголовок. Потом еще что-то, еще что-то, еще что-то. Все это через iframe. Все это кажется нереальным. Сначала мы думали, что это не будет работать. Это не может быть быстрее.

Но потом мы увидели, что это быстрее в разы. Пользователь плавно получает контент, и у него остается ощущение скорости. Он думает, что страница быстрее. Это как раз то, на чем многие сайты остановились. Они не пошли по этому пути и, мне кажется, сделали большую ошибку.

 

Таким образом, все стало работать хорошо.

Но мы столкнулись со следующей проблемой. Естественно, все это затевалось ради того, чтобы мы могли получать какие-то события. Но только представьте, какие нагрузки будут, если мы просто берем и начинаем держать длинные запросы, Long Poll. У сайта немаленькая посещаемость, и вдруг нам придется держать по соединению на каждого пользователя постоянно. Но это еще полпроблемы. Это проблема решаемая.

Но у каждого пользователя по 20 открытых вкладок. Это уже, действительно, проблема. Это даже целых две проблемы.

Это целых две проблемы. Первая проблема в том, что мы не можем держать много соединений на один домен. Мы не можем делать это для каждой вкладки… Браузеры это ограничивают. Но эта проблема легко решается.

Мы просто берем и делаем поддомены.

Так делал Facebook. Не смотрел, что они сейчас делают. Скорее всего, они тоже все переосмыслили, но я не уверен. Может быть, они до сих пор это используют.

Много поддоменов. Для каждой вкладки свой поддомен – все окей, это работает. 

Но есть огромная нагрузка. Для каждой вкладки свой длинный запрос, long poll – это неприемлемо.

Появилась такая мечта - общаться между вкладками. У нас много вкладок, и хочется общаться между ними. Хочется иметь возможность из одной вкладки сказать: «Я буду держать длинный запрос, long poll и передавать вам всем, что там происходит, а вы ничего не делайте».

Сначала казалось, что с этим справится только flash.

Локальное подключение, Flash, и нет другого выхода. Это очень печально, потому что не хочется завязывать сайт на flash, от которого очень хочется отказаться, потому что он где-то вылетает… Все знают проблемы с разными версиями flash-плеера. 

Но потом был открыт html5. Многие, наверное, в курсе, что есть такая вещь, как веб-хранилище (англ. web storage).

Веб-хранилище – это стандартный html5, который позволяет нам хранить какую-то информацию прямо в браузере. Это никак не помогло бы нам, если бы не было события "window on storage". Мы можем в момент "складывания" каких-то данных в локальное хранилище "отловить" это событие, посмотреть, что туда положили и как на это отреагировали. То есть мы можем "общаться" между вкладками, причем без использования flash и в достаточно большом количестве браузеров. Можно сказать, что во всех современных браузерах, более или менее используемых, даже в мобильных. Правда, не знаю, насколько Opera Mini это поддерживает. Но для нее у нас отключены Ajax-переходы, поэтому "живем".

Таким образом, у нас появился сервер длинных запросов, который держит одно соединение, вкладки "общаются", "решают", какая будет держать соединение. Если сервер очень долго не отвечает (то есть вкладка была закрыта), кто-то другой берет эту задачу и решает ее.

Хотелось бы рассказать, как устроен сам сервер длинных запросов. Мне известно, что многие тоже решают эту проблему, многие используют технологию Real Time в своих проектах и пытаются организовать что-то подобное. 

Понятно, что с нашими нагрузками это возможно только на "Си" и на низком уровне (очередь и никакой работы с диском). Только какие-то локальные вещи, только оперативная память.

Для других проектов это можно сделать совершенно по-другому. Но в нашем случае только так. Самое главное – это то, что ни в коем случае на сторону длинных запросов не нужно "навешивать" логики. Возможно, изначально может показаться, что сервер должен быть большим сервером, которым узнает о каком-то событии, генерирует html, получает пакет данных длинного запроса, проверяет приватность/неприватность и только потом что-то отдает пользователю. 

Это неправильно, потому что в условиях, когда нужно держать "висящие" соединения в оперативке, это будет медленно работать. Это очень сложно реализовать.

Поэтому такой сервер должен быть очень тонким. Он не должен ничего делать, только передавать события и хранить, пока есть память, некую очередь. Мы называем это Q-Engine.

Нет шаблонизации. Нет бизнес-логики.

 

Нет ничего. Только передача событий. 

Это работает быстро.

Последняя, очень важная тема, о которой хотелось бы рассказать, это преимущества, которые от всего этого можно получить. Что вообще это дает в плане производительности, в плане борьбы с нагрузкой?

Первое – это подключение статики и контента, о чем я уже сказал.

Вторая вещь – это синхронизация вкладок и кеширование динамических данных. Мы можем в локальное хранилище что-то класть, что мы хотим получить на каждой вкладке. Например, онлайн-чат, который можно включить, друзей. В общем, многие вещи можно положить в локальное хранилище и там с ними работать.

Еще один момент – это кеширование страниц. Когда мы все делаем на клиентской стороне, мы можем не делать некоторых запросов, а пользователь будет думать, что они происходят, что он перешел на другую страницу. На самом деле, ничего не происходит. Мы просто, имея информацию, которая у нас уже есть, отобразили ему другую страницу и решили таким образом проблему, не делая запроса к серверу и снизив нагрузки.

В первую очередь, это касается кнопок «Назад». Допустим, мы перешли с одной страницы на другую, а потом нажали «Назад» в браузере. Что сделает большинство браузеров? Они сделают запрос к серверу и загрузят страницу заново. Но зачем это делать, если пользователь уже был на этой странице, и мы можем просто показать эту страницу?

Возникает вопрос: «Как это реализовать?» Как сделать так, чтобы мы могли показать какую-то страницу, на которой он был, у нее была верстка, там выполнялись какие-то скрипты. Очень много вещей, которые в окружении. Как их все заставить работать?

Было найдено такое решение, которое сначала показалось каким-то странным, но оно хорошо работает. Мы берем все, что относится к текущему контенту вкладки, мы изымаем это из DOM. Мы получаем, например, по id этот элемент (какой-то контейнер), сохраняем в какую-то переменную, в некую очередь и "выкидываем" из DOM. Вместо этого вставляем какой-то новый элемент. Ссылка на старый у нас хранится. Она, естественно, не вычищается из памяти, потому что мы в любой момент можем ее использовать. Более того, это достаточно быстро работает. Мы можем достаточно быстро вставить его в DOM (то, что мы уже взяли), и оно вставится в том виде, в котором оно было.

Еще один момент – это переменные. Что делать с переменными? Большинство людей, когда пишут JavaScript, какие-то классы, функции и так далее, используют глобальные переменные либо закрывают их в какой-то области. Но у них есть какие-то переменные, которые очень сложно "выцепить". Мы решили, что все переменные, которые относятся к какой-то странице, мы будем называть "cur.что-то". У нас есть объект "cur", в котором мы храним все подэлементы (все переменные, с которыми мы что-то делаем на этой странице). Как только пользователь переходит на другую страницу, мы все эти переменные собираем, складываем куда-то в очередь и потом, когда он жмет кнопку «Назад», мы их оттуда достаем, чтобы он мог вернуться вперед.

Таким образом, мы полностью сохраняем переменные, сохраняем DOM. Единственный момент – это всякие события, которые хотелось бы сохранить и которые есть снаружи и не "навешены" на внутренние элементы. Очень часто на окно нужно что-то повесить. Поэтому, естественно, нужны функции обратного вызова, нужно что-то обрабатывать.

Еще один хороший момент – это поиск в разных разделах. Например, первым это было сделано в разделе «Друзья». Была задача сделать так, чтобы поиск по «моим друзьям» был очень быстрым. Стало понятно, что на серверной стороне мы не сможем этого сделать. Мы не можем сделать достаточно быстрый поиск на серверной стороне так, чтобы пользователь пользовался этим, как фильтром. Чтобы он зашел в раздел «Мои друзья», начал что-то набирать и тут же увидел список. Чтобы он не чувствовал какой-то задержки.

Нужно выгружать всех друзей на клиент. Но это достаточно проблематично, когда друзей много (например, когда их 10 тысяч). Как это делается? Мы рисуем какую-то страницу. Естественно, мы должны в JSON выгрузить этих друзей. Чтобы выполнять поиск по ним на стороне клиента, мы должны проиндексировать их на клиенте, составить дерево. Понятно, что для этого нам нужно получить их в JSON-формате. Но если мы получаем всех друзей в JSON, и их, например, 10 тысяч, то мы сталкиваемся с двумя проблемами.

 

Первая проблема – это то, что этот JSON очень большой. 10 тысяч друзей это около двух мегабайт. Два мегабайта, которые нужно вылить на клиент при заходе в «Друзья», и только после этого показать ему страницу. Мы можем показывать страницу по частям, но все равно это будет плохо работать.

Поэтому было решено делать несколько запросов. Мы делаем один запрос и показываем только страницу с n-ным количеством друзей (15 друзей). После этого мы делаем еще один запрос, чем слегка напрягаем Apache, зато мы кешируем в memcached те данные, которые нам нужны посередине. То есть те данные, которые мы вытащили уже при первом запросе и используем при втором, мы кладем в memcached. Мы не достаем их второй раз, по крайней мере, из труднодоступных мест, только из memcached. 

После чего мы получаем этот JSON, формируем его и отдаем его уже в фоновом режиме. Если пользователь куда-то перешел, если пользователь что-то нажал в этом интерфейсе, мы просто показываем загрузку до тех пор, пока не появятся данные, чтобы использовать быстрый фильтр.

Таким образом, удалось сделать достаточно быстрый интерфейс, который незаметно подгружает достаточно много данных. Если друзей мало, то это не используется, и все друзья отображаются разом.

Это последний слайд. Наверное, надо рассказать что-то еще, так как времени осталось много.

Есть ли какие-нибудь вопросы? Я бы оттолкнулся от какого-нибудь вопроса и продолжил рассказ.

Вопросы и ответы

Вопрос из зала: По поводу серверов длинных запросов. Что они вообще делают? Это прокси к чему-то или что-то ещё? Например, откуда они берут данные?

Олег Илларионов: Да, кстати, я не рассказал об этом достаточно подробно. Как это происходит? Например, вы отправили кому-то сообщение. В момент отправки ваших сообщений мы формируем те данные, которые получит пользователь, которому вы их отправили. В этот момент мы смотрим, какой у него пакет длинного запроса (англ. long pack), получаем этот пакет, берем шаблоны, составляем все, что нужно и отправляем на сервер сервер длинных запросов. Причем независимо от того, есть этот пользователь или нет. Мы все равно отправляем в QE, чтобы потом это использовать.

Вопрос из зала: В смысле, это все клиент создает?

Олег Илларионов: Нет-нет-нет. Это, естественно, на сервере.

Вопрос из зала: То есть запрос сначала идет отдельно, не на сервер сервер длинных запросов?

Олег Илларионов: Нет-нет. Сами события на сервер длинных запросов попадают из PHP. Мы в PHP получаем какое-то событие, формируем данные и отправляем их. 

Вопрос из зала: Вы отправили данные на PHP, грубо говоря. Дальше, в соответствии с какой-то логикой, вы кинули все это в соответствующую очередь, которая потом ушла через длинный запрос клиенту. Правильно?

Олег Илларионов: Да.

Вопрос из зала: Есть ли у вас ситуации, когда нужно подсоединять не через PHP, а через какой-то отдельно стоящий сервис? Не знаю, мультичат какой-нибудь. Или все идет только через PHP front-end?

Олег Илларионов: Не совсем. На самом деле, есть два типа QE-серверов (серверов, которые управляют длинными запросами). Один из них является частью движка сообщений и отправляет события в изначально заданном формате сообщений, и это касается только сообщений. 

Второй движок занимается этими же сообщениями для уведомлений. Но при этом он формирует и отправляет все остальные события, так как событий на сайте, на самом деле, очень много. Кто-то что-то залайкал, кто-то добавил что-то в ленту новостей. Все это обновляется в реальном времени, поэтому требует событий, которые нужно обрабатывать на клиентской стороне.

Вопрос из зала: Получается, как-то симулируется ответ сервера в некоторые моменты? То же сообщение, например. При отправке ведь мы же должны показать список сообщений, а не ждать, пока это уйдет на сервер, придет в очередь, очередь "рассосется" длинным запросом и вернется обратно. 

Олег Илларионов: Мы отдаем уже готовую информацию, но не какой-то раздел, а только то, что нужно поменять на странице. Для каждого случая это решается отдельно.

Вопрос из зала: Нет, я про то, что когда мы отправили сообщение, у нас должен появиться список сообщений, но он теоретически может долго идти до нас обратно. Есть какая-то эмуляция ответов?

Олег Илларионов: В плане по порядку?

Вопрос из зала: Просто пока придет очередь, что происходит? Мы же должны что-то показать. Мы показываем список сообщений уже с новым отправленным сообщением. А там входящие появляются…

Олег Илларионов: Нет, естественно, мы берем и… 

Вопрос из зала: Тогда понятно.

Олег Илларионов: Мы просто на лету формируем верстку относительно того, что нам нужно.

Вопрос из зала: Можно поподробнее про использование iframe для подгрузки вместо Ajax? Очень быстро было.

Олег Илларионов: Хорошо. Сейчас расскажу. Идея в том, что мы не можем в достаточно большом количестве браузеров получать информацию, которая приходит через Ajax в течение ее загрузки. Мы получаем этот контент только после того, как сервер все отдал и прервал соединение. Это очень плохо. 

Если у пользователя медленное соединение, это будет долго. Не обязательно иметь супермедленное соединение. Просто нужно, чтобы что-то было немного забито в сети. Это будет достаточно долго. Это будет заметное глазу время. Поэтому нужно отдавать контент частями. В противном случае пользователь будет замечать, что что-то работает не очень быстро. 

Вопрос из зала: Вы при первой загрузке страницы загружаете части, а потом перемещаете в DOM? Или какая-то подгрузка идет? Сначала создается DOM-элемент, где есть iframe. Так?

Олег Илларионов: Когда просто страница открывается (не Ajax-переход, а просто она открывается), она открывается просто, как она должна быть. Когда пользователь сделал какой-то переход, перешел на какую-то ссылку, мы не перезагружаем страницу, мы остаемся на той странице, мы просто подгружаем элементы. 

В этот момент мы, вместо того чтобы делать Ajax-запрос, делаем скрытый iframe, который имеет все параметры, аналогичные тому запросу, который бы мы сделали, если бы использовали Ajax для этого. Данные, которые приходят, выглядят примерно следующим образом: "top.какая-то функция callback" плюс набор данных, которые нужно использовать для отображения в каком-то блоке.

Вопрос из зала: Спасибо.

Олег Илларионов: Кстати, еще хотелось бы добавить такой момент. В вебе, на самом деле, рендеринг браузера не такой быстрый, как кажется. Мы привыкли считать, что браузеры отрисовывают все очень быстро, и это время можно не считать. На самом деле это не так. Они отрисовывают все за заметное глазу время. В принципе, это очень сильно ускоряется визуально, если мы рисуем по частям. 

Если ответ у нас пришел не полностью, мы можем нарисовать верхнюю часть. Когда придет нижняя часть, мы гораздо быстрее отрисуем нижнюю часть, чем если бы рисовали и верхнюю, и нижнюю. Скорость рендеринга очень зависит от количества блоков, от того, что там внутри.

Вопрос из зала: Почему для этого надо использовать iframe, почему не использовать 3 разных Ajax-запроса?

Олег Илларионов: Потому что для этого придется очень "мучить" серверы front-end и back-end.

Вопрос из зала: 3 iframe точно так же дадут 3 запроса.

Олег Илларионов: Нет-нет-нет. Идея в том, что когда мы загружаем iframe, он загружается браузером по частям. JS, который там есть, отрабатывает, не только когда он весь загружен. Если это разные блок и скрипт, они отрабатываются по мере загрузки. Закончился тег скрипта – он выкидывается, несмотря на то, что последующие еще не догрузились.

Вопрос из зала: Сейчас производительность JavaScript постоянно улучшается. Когда вы планируете перейти на чисто клиентский рендеринг шаблонов и вообще всего?

Олег Илларионов: Мне кажется, это будет очень плавный переход. Точно так же, как с Ajax Layout. Это было очень плавно. Не было такого, что раз, вчера сайт был обычный, а сегодня он весь на Ajax. Он переписывался по разделам. Специально для этого была придумана система, которая позволяла не конфликтовать обычному и на Ajax. Переходы между разделами были реализованы через Ajax, но как только пользователь выходил за этот круг, как только он выходил на какую-то старую страницу, там все происходило обычным образом. Для этого мы просто вручную на ссылки "вешали" функции обратного вызова. У нас такое правило: все новые переписанные разделы мы использовали без .php, делали rewrite. Чуть позже мы заменили этот обратный вызов на простую проверку. Если у нас .php, мы делаем обычный запрос, если у нас без .php (какая-то внутренняя страница, но без расширения), значит, мы делаем Ajax-запрос. 

Вопрос из зала: Здравствуйте. Спасибо за хороший доклад. Вы при реализации используете немного нестандартные методы (iframe и так далее, что специфично для разных браузеров). Каким образом вы все это тестируете, используете ли вы какие-то специализированные средства?

Олег Илларионов: Мы используем «виртуалки». Естественно, сложно поставить на одну машину все браузеры. Мне не удавалось.

Вопрос из зала: Это понятно. То есть у вас используется ручное тестирование?

Олег Илларионов: Да. Довольно проблематично, например, в IE 6 автоматизировать тестирование верстки. Я не знаю таких примеров. Хотя, наверное, при должном желании это можно сделать.

Еще хотелось бы рассказать такую вещь. Это решение одной проблемы, с которой все сталкиваются, и мало кто знает, как ее решить. Как сделать так, чтобы не было окна перехода при отсутствии History API? Пользователь открывает ссылку без Ajax. Хеш мы можем получить только на клиентской стороне, поэтому большинство загружает некую страницу, в которой он обрабатывает этот хеш, и после этого загружает в нормальную страницу. 

Это выражается таким белым всплеском. Загружается, как правило, белая страница либо страница с какой-то заглушкой, после чего появляется нормальная страница. Для нас это было совершенно неприемлемо, потому что в тот момент, когда существовали и старые, и новые разделы, сложно представить, как бы отреагировали пользователи, если бы они переходили из одного раздела в другой, и посередине у них была бы какая-то заглушка.

Как мы решили эту задачу? Оказалось, что есть очень простой способ, который работает везде. Мы в заголовке страницы (англ. header) до содержимого, тела страницы (англ. body) делаем JavaScript код, который вызывает “location=что-нибудь”, то есть который меняет адрес. В этот момент браузер ничего не отрисовывает и по поведению ведет себя примерно так же, как серверный редирект. Это очень хороший хак, который спас всю затею.

Вопрос из зала: У меня целая серия вопросов. Для начала я хотел бы уточнить. Работа кнопки «Назад» – это часть History API? Когда я кликаю, происходит перехват метода, и браузер просто не переходит. Вы это как-то через History API сделали?

Олег Илларионов: Да. Да. В старых браузерах это тоже работает за счет iframe. Где нет History API, есть iframe, которые меняют свои правила. Страница не менялась, но менялись сами iframe и их контент. Браузер запоминает это и при нажатии кнопки «Назад» перезагружает не страницу, а iframe внутри страницы. Это можно "отловить".

Вопрос из зала: Во время загрузки страниц таким хитрым способом у вас как-то производится загрузка счетчиков. Эта загрузка страницы считается загрузкой страницы? Баннеры обновляются…

Олег Илларионов: Баннеры, естественно, обновляются, счетчики – тоже. Счетчики обновляются двумя способами. При любом обычном переходе получаются счетчики, но не все левое меню, а только цифры. Минимум трафика.

Второй способ – это QE. На большинство важных счетчиков стоит очередь. Как только они изменяются, приходит событие, и они обновляются.

Вопрос из зала: Вы манипулируете с DOM, переносите страницу в какое-то хранилище и загружаете на ее место новую. Все события, которые были "навешены" на клики, на всплывающие окна, все это потом нормально возвращается назад в браузерах? С этим проблем нет?

Олег Илларионов: Если событие было на какой-то внутренний элемент, то оно возвращается. За исключением того, если это событие как-то "баблится" и так далее. С этим могут быть проблемы. Но нет таких случаев, когда это вело бы себя совершенно неадекватно.

Вопрос из зала: Проблем со снятием "listener" и "навешиванием" его обратно для очистки памяти не возникает?

Олег Илларионов: Возникают они, конечно. Но это, в основном, верхние события. То есть события, которые навешиваются на окно, на BodyNote. Еще такой интересный момент с BodyNote. Дело в том, что мы не используем document.body, потому что он не везде используется. Есть браузеры (например, IE), в которых вы заходите и смотрите на страницу и видите dif, у которого переполнение (англ. overflow), у которого полоса прокрутки. Вы видите не страницу, у которой полоса прокрутки, а dif, у которого полоса прокрутки. 

Это сделано для того, чтобы спасти слои, которые открываются, когда вы, например, смотрите фотографию, и у которых своя верхняя прокрутка. Это можно очень элегантно сделать в Chrome. Можно достаточно элегантно, но не так, как в Chrome, сделать в Firefox, но без каких-то извращений. Например, в IE этого не сделать, а в Opera мы вообще не нашли способа сделать это достаточно элегантно, чтобы не было каких-то неприятных случаев с этим (например, чтобы при прокрутке слоя до конца не было прокрутки внутренней страницы).

Вопрос из зала: Большое спасибо.

Вопрос из зала: Я хотел бы задать вопрос по использованию вебсокетов. Будет ли эта технология где-то использоваться или, может быть, уже где-то используется? Ваше отношение.

Олег Илларионов: Отношение хорошее. Очень хотелось бы иметь рабочие веб-сокеты в браузерах, но, к сожалению, сейчас с этим не все хорошо. Вы в курсе, что, например, Firefox от них отказался. Будем надеяться, что они передумают. Мы, например, считаем эту угрозу не такой страшной. С этим можно жить. 

На мой взгляд, сокеты – это не обреченная технология. Она может жить. Когда-нибудь мы, возможно, будем это использовать. Но сейчас единственный способ сделать все достаточно эффективно – это длинные запросы. Если заглядывать в будущее, скорее всего, это будет управляться событиями. Всякие события, наверное, даже обгонят по развитию сокеты, потому что сокеты как-то застряли и непонятно, что с ними будет.

Реплика из зала: Я хотел бы уточнить. Веб-сокеты обратно ввели в Firefox 6 со всеми починенными проблемами безопасности. Также в Chrome их уже ввели или введут в 16-й версии. И с Opera пока что-то непонятное.

Олег Илларионов: Я могу рассказать про NoJS. На самом деле, в последнее время мы не очень активно его используем, но используем. В последнее время просто не было возможности его применить, за исключением отправки почты, push-нотификации и вещей, связанных с Twitter, и так далее. 

Например, отправка писем великолепно работает просто потому, что там нет ничего сложного пока. Пока мы не сделали e-mail @vk.com, пока у нас письма только с хешами, то есть вы можете отправить письмо прямо из личных сообщений. Многие не замечали, потому что надо еще догадаться вместо имени человека начать набирать адрес электронной почты. Но такая возможность есть. Ответ от этого пользователя будет получен и придет вам в личное сообщение (правда, без вложений). 

Мы, на самом деле, уже давно мечтаем сделать @vk.com. Но системные администраторы не могут сделать это быстро, потому что там есть некоторые загвоздки с портами и так далее. Не могу сказать, когда появится эта функция. Наверное, довольно скоро. Мы, по крайней мере, давно движемся к тому, чтобы организовать на уровне дата-центров все необходимое, чтобы можно было это реализовать. 

Мне кажется, самый лучший пример того, как можно использовать NoJS в таких проектах, – это push-нотификация. Все знают, что iPhone поддерживает push-нотификацию. Мы можем отправлять события на iPhone. Я могу рассказать, как это работает, потому что не все, наверное, знают. У Apple есть свой бинарный протокол. Это не так, как в Android, не нужно "дергать" какие-то скрипты. Вы подключаетесь к TCP-потоку и отправляете туда события в бинарном виде внутри бинарника. Правда, вы вкладываете еще JSON. Такая веселая смесь, но это очень хорошо. Мне это очень нравится.

Базовые вещи вы отдаете в бинарном виде, все остальное – в JSON. Очень удобно, достаточно экономично и за счет того, что это всего лишь одно соединение, это работает достаточно быстро. Очень долго этим занимался один сервер. Потом, просто из соображений того, что он может вдруг "отвалиться", добавили еще, но это совершенно не требует каких-то больших нагрузок, несмотря на то что пользователей iPhone великое множество, и всем нужно отправлять уведомления. 

У нас были проблемы с доставкой уведомлений. Они не всегда приходили. Сейчас эти проблемы решены. Насколько я знаю, сейчас у всех работает push-нотификация на iPhone.

Вопрос из зала: Не у всех.

Олег Илларионов: Не у всех? Они вообще не приходят или что? Вообще не приходят. Странно. Мне тоже не приходят, но у меня девелоперская сборка, у меня они не должны приходить. Сколько я ни тестировал, ни проверял… Вы можете оставить мне свой id, я посмотрю в базе. Если они вообще не приходят, скорее всего, проблема на уровне того, что у вас в базе прописано, что вам не нужно отправлять push-нотификацию. У нас были такие моменты, когда не совсем то писалось в базу. Были пользователи, которые попали в такое несчастливое число. Но я был уверен, что я им все обновил.

Вопрос из зала: Раньше приходило, теперь перестало.

Олег Илларионов: Это очень печально. Финальный момент. Пришлось все переписать и заставить это работать хорошо. Я немного сменил протокол, и они стали сбрасывать соединение. Собственно, из-за этого были большие проблемы с доходом push-уведомлений. Они стали сбрасывать соединение, если c одного IP-адреса я подключаюсь с двумя экземплярами, с двумя разными сертификатами. Я делал это, чтобы подсылать себе еще push-уведомления, потому что у меня девелоперская сборка, у которой другой сертификат. Они просто стали сбрасывать. Сервер все время выполнял повторные подключения, отправлял несколько сообщений. Потом кому-нибудь из разработчиков отправлял эти сообщения, и все вырубалось. Это было очень печально, но сейчас такого не должно быть, я отключил всем разработчикам push-уведомления. Теперь все хорошо.

Вопрос из зала: Он отрубал вообще весь Вконтакте?

Олег Илларионов: Были дисконнекты. Apple очень весело себя ведет. Чуть что дисконнект. Причем в feedback ничего не приходит. У них два TCP-канала – feedback и основной stream. Но, в принципе, достаточно легко догадаться, почему он все отключает. По крайней мере, если все делать правильно, они этого делать не будут. Но так как все работало, и изменение было с их стороны, пришлось немного пройтись отладчиком и поэкспериментировать, чтобы дойти до этого.

Вопрос из зала: При отправке сообщения на адрес электронки, куда оно уходит? Можно указать полный адрес с @ или что?

Олег Илларионов: Идея в том, что когда вы набираете письмо и отправляете сообщение, пользователю приходит письмо со специального адреса. В адресе есть хеш. Мы проверяем, чтобы отсекать спам и все подобное. Нет спама и нет нагрузки.

Вопрос из зала: Я слышал, что у вас используется своя СУБД. Это правда?

Олег Илларионов: Это правда.

Вопрос из зала: Вы можете рассказать, что лучше и какие сравнительные характеристики с чем-нибудь, хотя бы с MySQL?

Олег Илларионов: Давайте я расскажу, это очень интересно. У нас используется и MySQL для каких-то задач. Изначально MySQL использовался для всего. Но в определенный момент стало понятно, что MySQL справляется не со всем. Первая вещь, которая была переписана, это личные сообщения. С ними было больше всего проблем. 

Основная причина – поиск. Хотелось искать по сообщениям. В MySQL начать искать по сообщениям каждого пользователя оказалось нереальной задачей. Был написан специальный движок. Он называется "text engine". Он написан специально для личных сообщений. В нем много чего не предусмотрено. Редактирование было не предусмотрено до последнего момента. Но так как "text engine" – это не только личные сообщения, но и стена пользователя, недавно появилось редактирование и на стенах. Это было очень серьезное изменение в движке. 

Это специальный движок, который позволяет получать выборки по определенному ранжированию очень быстро и имеет такую структуру, которая оптимизирована для этих вещей. Когда есть какой-то хозяин (англ. owner) и есть какие-то его записи.

Вопрос из зала: Не могли бы вы рассказать, как у вас технически устроена отправка почты пользователям. В момент ее генерации, как почта путешествует внутри ваших площадок, на которых стоят серверы? Это не вопрос, как попасть во "Входящие" или в "Спам". Как вы вообще все это мониторите и  как понимаете, что у вас все хорошо?

Олег Илларионов: Отправка, естественно, не совсем прямая. Мы не можем просто взять и отправить с того же сервера почту по понятным причинам. Есть серверы, которые выполняют разные задачи. Если один сервер будет выполнять все задачи, то нашим сисадминам станет дурно. 

Когда отправляется почта, просто кладется запись в очередь. Для очереди, кстати, сделан свой движок, который оптимизирован под очереди. Дело в том, что все отправляемые письма почему-то хочется хранить, и поэтому MySQL тоже плохо справлялся с тем, чтобы хранить все.

Вопрос из зала: Вы храните прямо исходник письма, который вы сгенерили, с html, заголовками – все вместе?

Олег Илларионов: Да. Насколько я понимаю, это было по той причине, что периодически "отваливалась" отправка почты когда-то давно и хотелось потом отправить все, что не удалось отправить. Это "кладется" в специальный движок, который это хранит до того момента, пока другой сервер не обратится за этим. Но это, как правило, происходит очень быстро. Он обращается за этим письмом, чтобы отправить его. Это уже специальный сервер, настроенный исключительно для отправки почты.

Вопрос из зала: Правильно ли я понял, что у вас есть некоторые машины, которые сами генерируют html- и txt-версии писем с нужными заголовками? После этого "кладут" их в систему, которая база/небаза, в ней лежат полные исходники писем. Есть совершенно отдельные выделенные машины, которые из той очереди забирают эти сгенерированные письма и по факту делают только команду mail, отправляя в ваши NTA.

Олег Илларионов: Нет, все с другой стороны. При отправке сообщения все необходимые данные кладутся в эту очередь. Потом сервер, который отправляет, выполняет команду mail, составляет шаблоны, конфигурирует из тех данных, которые он вытащил, и отправляет. Правда, это касается только уведомлений. 

Та почта, которая отправляется на какой-то определенный адрес из личных сообщений, на самом деле, работает совершенно по-другому. Ее отправка написана на JS. Это отдельный сервер в другом месте с другими IP-адресами, который через наш API получает эти письма и отправляет их. Это совершенно внешняя система. Она сделана для того, чтобы в случае, если все отправляемые письма будут характеризоваться как спам (чего мы очень боимся), это не стало критичным для отправки уведомлений. Чтобы были совсем разные IP-адреса и все было совсем по-разному.

Вопрос из зала: У вас куча всяких IP-адресов из разных подсетей…

Олег Илларионов: Нет, их всего несколько. Есть такая маленькая подсеть, а есть все остальное.

Вопрос из зала: Сколько машин, если не секрет, занимается отправкой почты, на которых NTA "крутится"?

Олег Илларионов: Отправкой уведомлений занимается достаточно большое количество машин, но я совершенно не в курсе сколько.

Вопрос из зала: Это десятки, сотни?

Олег Илларионов: Это десятки, но не сотни. Отправкой почты занимается один сервер. Надо его продублировать, а то вдруг он "упадет".

Вопрос из зала: Какие объемы писем, если не секрет, вы отправляете каждый день? Просто я читал доклад про 100 миллионов писем каждый день. Мне интересно, кто окажется круче по цифрам.

Олег Илларионов: Невероятное количество. Я заикаюсь, когда такие цифры вижу. Очень много уведомлений за счет того, что долгое время регистрация на сайте выполнялась посредством электронной почты, и всем этим людям надо было отправлять уведомления. Это дикое количество. 

Вопрос из зала: То есть у вас все еще круче, чем у нас?

Олег Илларионов: Я, если честно, не помню этого числа, потому что я не сосчитал количество нулей. Я его как-то видел.

Вопрос из зала: Еще вопрос про почту. Каким образом вы мониторите? Есть какой-нибудь отдельный человек или группа сотрудников, которые думают, как эти письма правильно отправить, доставить, как их правильно генерировать?

Олег Илларионов: Нет, конечно. У нас маленькая команда. Есть просто статистика по количеству отправляемых писем. Начальство периодически смотрит на статистику. Если там что-то не так, то всем плохо.

Вопрос из зала: Что можно узнать из статистики, которая представляет собой прямую линию, на ней уведомления о новых сообщениях – 5 миллионов. Вот она, прямая за последние два месяца. Что там можно увидеть?

Олег Илларионов: Там не прямая. Сайт постоянно растет, притом достаточно большими темпами, и это число увеличивается.

Вопрос из зала: То есть я правильно понимаю, что это единственный способ мониторить ситуацию? Скорее, вы меряете генерацию писем.

Олег Илларионов: Идея в том, что если что-то случилось с сервером, то сисадминам придет sms. Это, конечно, не самый лучший способ решить проблему, потому что дело может быть совсем не в сервере. Но пока это достаточно хорошо работает. Как правило, если не отправляются письма, то сисадмин уже в курсе.

Вопрос из зала: Понятно. Но вы хотя бы измеряете эту очередь? Грубо говоря, у вас стоит очередь заявок на генерацию писем про новые комментарии, еще про что-то новое. Вы меряете, какой у нее объем? Вдруг там стоит 100 миллионов, вы их накопили за какое-то время.

Олег Илларионов: Я точно не знаю, стоит ли в этом случае очередь. Но  у нас есть такой специальный скрипт "watch doc", который проверяет разные числа, "смотрит", чтобы они были достаточно высокими, и отправляет sms, если что-то идет не так.

Вопрос из зала: Хорошо. Вы вроде очень подробно рассказали. Спасибо.

Вопрос из зала: Расскажите, пожалуйста, какие серверы очередей используются и в каких случаях?

Олег Илларионов: Вы имеете в виду – для внутренней передачи данных?

Вопрос из зала: Да.

Олег Илларионов: Мы не используем Open Source, как делают многие. Мы решаем задачи не самыми оптимальными способами, зато самыми быстрыми, потому что, как правило, это вещи, которые не касаются высоких нагрузок. За исключением почты, где используется свой движок. Там понятно – там много писем, и это, действительно, высоконагруженная штука. 

Во всех остальных случаях мы применяем всякие интеграции, парсинг RSS, еще что-то. Это, как правило, обычный MySQL. Даже импорт в Twitter – это обычный MySQL. Там специальная табличка, которая "заточена" под конкретные задачи. Она выступает в роли очереди.

Вопрос из зала: Давно хотел спросить... Зачем вы ломаете в History API логику поведения браузера? Если в ленте новостей открыть фотографию, она откроется в LightBox, потом ее закрываем и нажимаем «назад», мы попадаем не на предыдущую страницу, а в LightBox.

Олег Илларионов: Да. Действительно, ломаем. Мы считаем, что так лучше.

Вопрос из зала: То есть вы считаете, что ломать стандартную логику браузера лучше? Вы знаете лучше...

Олег Илларионов: Это такой спорный момент. Не все с этим согласны, естественно. Я считаю, что это момент, который нужно немного дорабатывать, находить "золотую середину". С одной стороны, не очень хорошо, когда пользователь просмотрел целый альбом, а потом, например, хочет вернуться на человека. Нажимает «Назад», приходится перемещаться по всему альбому. С другой стороны, может быть, пользователь хотел как раз назад перейти. 

Это разные психологии, разные вещи. Был очень забавный момент, мы по этому поводу периодически шутили. В Facebook был забавный глюк: если из ВКонтакте перейти на Facebook, открыть там фотографию, пощелкать и нажать «Назад», то пользователь попадал на ВКонтакте.

Комментарии

Нет ни одного комментария

Только пользователи могут оставлять комментарии

Возможно, вам будет интересно:

Михаил Волович

Михаил Волович

Признанный эксперт в лингвистике, лексикограф, специалист по интеллектуальным технологиям и юзабилити сайтов.

Подробный рассказ о веб-фильтре Remparo, который позволяет ограничить доступ к нежелательному сетевому контенту.

Эсен Сагынов (Esen Sagynov)

Эсен Сагынов (Esen Sagynov)

Разработчик в NHN - крупнейшей IT-компании Южной Кореи.

Мы будем говорить о CUBRID и расскажем, как мы разработали эту платформу, зачем мы ее разработали, почему мы не пользовались уже существующими решениям.

Михаил Кумсков

Михаил Кумсков

Эксперт Luxoft Training по методологиям управления требованиями, использования методологии RUP и инструментария IBM Rational, доктор физ.-мат. наук., профессор кафедры вычислительной математики механико-математического факультета МГУ им.М.В.Ломоносова.

Я хотел вас познакомить с подходом и посмотреть, насколько то, что было сформулировано в 1950-х годах, перекликается и актуально для интернет-компаний, для менеджмента в этих интернет-компаниях, для организации работ.