System Design: frontend
На собесе во фронтенд Wildberries на архитектурной секции могут попросить спроектировать аналитику для главной страницы, которая выдерживает 500 тысяч запросов в секунду. Звучит как задача для бэкенда с хранилищами и дашбордами, однако интервьюера в первую очередь интересует браузер. Ему важно, как страница собирает клики, скролл и показы карточек и как доставляет всё это на сервер так, чтобы витрина не начала тормозить от собственной аналитики.
Первая версия, которую предлагает почти каждый, отправляет отдельный запрос на каждое событие. На небольшом сайте это сойдёт, но на главной маркетплейса у каждого посетителя за визит набираются десятки событий, а посетителей одновременно сотни тысяч. Запросы аналитики начинают делить канал с картинками товаров, на слабом мобильном интернете страница заметно дольше грузится, и в итоге падает конверсия, то есть ровно та метрика, ради которой аналитику и строили. Поэтому до того как рисовать схему, стоит выяснить у интервьюера, насколько быстро данные должны оказаться на сервере и нужен ли там каждый клик. От ответа зависит многое, потому что для оплаты рекламных показов терять события нельзя, а для тепловой карты главной страницы спокойно хватит части сессий.
Дальше разговор строится вокруг буфера. События складываются в память вкладки и уходят на сервер одной пачкой, когда их накопилось несколько десятков или прошло несколько секунд. Общие данные вроде id сессии и модели устройства кладутся в пачку один раз, поэтому каждое событие весит совсем немного, а у буфера есть предел, чтобы при недоступном сервере вкладка не съела всю память. Самое интересное начинается, когда пользователь уходит со страницы, потому что последняя пачка содержит как раз то, что аналитикам нужнее всего, момент, в который человек бросил просмотр. Обычный fetch браузер при закрытии вкладки может оборвать, поэтому для последней отправки используют navigator.sendBeacon, который отдаёт запрос браузеру, и тот доставит его уже после закрытия страницы. Сбрасывать буфер правильнее в момент, когда вкладка становится скрытой, по событию visibilitychange, потому что на телефонах страницу чаще сворачивают, чем закрывают, и событие unload там может вообще не прийти.
Параллельно нужно следить, чтобы сбор событий не отнимал время у интерфейса. Показы карточек удобнее считать через IntersectionObserver, чем через обработчик скролла, который срабатывает десятки раз в секунду, а сборку пачки можно отложить в requestIdleCallback, когда у браузера появляется свободное время между кадрами. Когда клиентская часть готова, интервьюер обычно переводит разговор на масштаб. Здесь помогает сэмплирование, при котором продуктовую аналитику шлёт, скажем, каждая десятая сессия, причём решение принимается один раз на всю сессию, иначе воронки перестанут сходиться. Принимает события отдельный лёгкий сервис на своём домене, он складывает пачки в очередь и сразу отвечает, так что основное API этот поток вообще не замечает. Отдельно стоит проговорить, как ведёт себя клиент, когда этот сервис отвечает ошибкой. Если сотни тысяч вкладок одновременно повторят запрос через секунду, они снова положат сервис, который только поднялся, поэтому паузы между повторами растут с каждой попыткой и немного отличаются у разных клиентов.
В итоге задача проверяет, понимаешь ли ты, что аналитика сама по себе нагружает страницу, и умеешь ли устроить её так, чтобы пользователь её никак не почувствовал.
Подписаться: @codeof_art
Вступить в чатик: @code_of_art
На собесе во фронтенд Wildberries на архитектурной секции могут попросить спроектировать аналитику для главной страницы, которая выдерживает 500 тысяч запросов в секунду. Звучит как задача для бэкенда с хранилищами и дашбордами, однако интервьюера в первую очередь интересует браузер. Ему важно, как страница собирает клики, скролл и показы карточек и как доставляет всё это на сервер так, чтобы витрина не начала тормозить от собственной аналитики.
Первая версия, которую предлагает почти каждый, отправляет отдельный запрос на каждое событие. На небольшом сайте это сойдёт, но на главной маркетплейса у каждого посетителя за визит набираются десятки событий, а посетителей одновременно сотни тысяч. Запросы аналитики начинают делить канал с картинками товаров, на слабом мобильном интернете страница заметно дольше грузится, и в итоге падает конверсия, то есть ровно та метрика, ради которой аналитику и строили. Поэтому до того как рисовать схему, стоит выяснить у интервьюера, насколько быстро данные должны оказаться на сервере и нужен ли там каждый клик. От ответа зависит многое, потому что для оплаты рекламных показов терять события нельзя, а для тепловой карты главной страницы спокойно хватит части сессий.
Дальше разговор строится вокруг буфера. События складываются в память вкладки и уходят на сервер одной пачкой, когда их накопилось несколько десятков или прошло несколько секунд. Общие данные вроде id сессии и модели устройства кладутся в пачку один раз, поэтому каждое событие весит совсем немного, а у буфера есть предел, чтобы при недоступном сервере вкладка не съела всю память. Самое интересное начинается, когда пользователь уходит со страницы, потому что последняя пачка содержит как раз то, что аналитикам нужнее всего, момент, в который человек бросил просмотр. Обычный fetch браузер при закрытии вкладки может оборвать, поэтому для последней отправки используют navigator.sendBeacon, который отдаёт запрос браузеру, и тот доставит его уже после закрытия страницы. Сбрасывать буфер правильнее в момент, когда вкладка становится скрытой, по событию visibilitychange, потому что на телефонах страницу чаще сворачивают, чем закрывают, и событие unload там может вообще не прийти.
Параллельно нужно следить, чтобы сбор событий не отнимал время у интерфейса. Показы карточек удобнее считать через IntersectionObserver, чем через обработчик скролла, который срабатывает десятки раз в секунду, а сборку пачки можно отложить в requestIdleCallback, когда у браузера появляется свободное время между кадрами. Когда клиентская часть готова, интервьюер обычно переводит разговор на масштаб. Здесь помогает сэмплирование, при котором продуктовую аналитику шлёт, скажем, каждая десятая сессия, причём решение принимается один раз на всю сессию, иначе воронки перестанут сходиться. Принимает события отдельный лёгкий сервис на своём домене, он складывает пачки в очередь и сразу отвечает, так что основное API этот поток вообще не замечает. Отдельно стоит проговорить, как ведёт себя клиент, когда этот сервис отвечает ошибкой. Если сотни тысяч вкладок одновременно повторят запрос через секунду, они снова положат сервис, который только поднялся, поэтому паузы между повторами растут с каждой попыткой и немного отличаются у разных клиентов.
В итоге задача проверяет, понимаешь ли ты, что аналитика сама по себе нагружает страницу, и умеешь ли устроить её так, чтобы пользователь её никак не почувствовал.
Подписаться: @codeof_art
Вступить в чатик: @code_of_art