Русский
Русский
English
Статистика
Реклама

Compression

Envoy как универсальный сетевой примитив

05.02.2021 00:16:03 | Автор: admin

В октябре прошлого года мои коллеги представили на EnvoyCon доклад "Построение гибкой подсистемы компрессии в Envoy". Вот он ниже



Судя по статистике сегодняшней статьи от SergeAx, тема компрессии сетевого трафика оказалась интересной многим. В связи с чем я немедленно возжелал вселенской славы и решил кратко пересказать содержание доклада. Тем более, что он не только о компрессии, но и том, как можно упростить сопровождение сетевой подсистемы как backend'а, так и мобильного frontend'а.


Я не стал полностью "новелизировать" видео доклада, а только ту часть, которую озвучил Хосе Ниньо. Она заинтересует больше людей.


Для начала о том, что такое Envoy.


В описании на официальном сайте значится следующее. Envoy это высокопроизводительный распределённый прокси-сервер, спроектированный как для отдельных сервисов и приложений, так и для работы в качестве коммуникационной шины в микросервисной архитектуре, то есть в сервис-мэшах, с учётом уроков вынесенных из эксплуатации NGINX, HAProxy И так далее.



Принцип работы прокси-сервера очень прост: клиент устанавливает сетевое соединение не напрямую с сервисом, а через прокси-сервер, который в случае необходимости, например, сжимает проходящий через него сетевой трафик. Или оконечивает TLS. Или, не разрывая соединения с клиентом, пытается повторно соединиться с временно недоступным сервисом. Или, если совсем всё плохо, соединиться с резервным сервисом. И так далее.
Главное в том, что подобная схема заметно снижает сложность кода и клиента, и сервиса. Особенно сервиса.



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



Таким образом общая архитектура в очень многих компаниях всё чаще начинает выглядеть примерно вот так.



Мобильный клиент общается с граничным прокси (Edge), который решает, куда отправлять клиентские запросы дальше, попутно балансируя нагрузку на сервера. Сервисы получают запросы от Edge не напрямую, а через вспомогательные прокси (Sidecar). Далее, сервисы формируют ответы, опционально пообщавшись друг с другом, и отсылают их к Edge.


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



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



Так появился проект Envoy Mobile, который представляет собой байндинги на Java, Kotlin, Swift, Objective-C к Envoy. А тот уже линкуется к мобильному приложению как нативная библиотека.


Тогда задача уменьшения объёма трафика описанная в статье от FunCorp, могла бы быть решена примерно как на картинке ниже (если поменять местами компрессор и декомпрессор, и заменить response на request). То есть даже без необходимости установки обновлений на телефонах.



Можно пойти дальше, и ввести двустороннюю компрессию



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

Подробнее..

Перевод Сжатие ответов в GRPC для ASP.NET CORE 3.0

27.08.2020 12:18:32 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса C# ASP.NET Core разработчик.



В этом эпизоде моей серии статей о gRPC и ASP.NET Core мы рассмотрим подключение функции сжатия ответов (response compression) служб gRPC.

ПРИМЕЧАНИЕ: В этой статье я рассказываю о некоторых деталях касательно сжатия, которые я узнал, изучая параметры и методы настройки вызовов. Скорее всего есть более точные и более эффективные подходы для достижения тех же результатов.

Эта статья является частью серии о gRPC и ASP.NET Core.

КОГДА СЛЕДУЕТ ВКЛЮЧАТЬ СЖАТИЕ В GRPC?


Короткий ответ: это зависит от ваших полезных нагрузок (payloads).
Длинный ответ:
gRPC использует protocol buffer в качестве инструмента сериализации сообщений запросов и ответов, отправляемых по сети. Protocol buffer создает двоичный формат сериализации, который по умолчанию предназначен для небольших эффективных полезных нагрузок. По сравнению с обычными полезными нагрузками в формате JSON, protobuf дает более скромный размер сообщений. JSON довольно подробный и удобочитаемый. В результате он включает имена свойств в данные, передаваемые по сети, что увеличивает количество байтов, которые должны быть переданы.

В качестве идентификаторов данных, передаваемых по сети, protocol buffer использует целые числа. Он использует концепцию base 128 variants, которая позволяет полям со значениями от 0 до 127 требовать только один байт для транспортировки. Во многих случаях существует возможность ограничить ваши сообщения полями в этом диапазоне. Для больших целых чисел требуется более одного байта.

Итак, напомним, полезная нагрузка protobuf уже достаточно мала, поскольку формат нацелен на сокращение байтов, отправляемых по сети, до минимально возможного размера. Тем не менее, все еще остается потенциал для дальнейшего сжатия без потерь с помощью такого формата, как GZip. Этот потенциал необходимо тестировать на ваших полезных нагрузках, поскольку вы будете наблюдать сокращение размера только в том случае, если ваша полезная нагрузка имеет достаточное количество повторяющихся текстовых данных, чтобы извлечь из сжатия хоть какую-нибудь выгоду. Возможно, для небольших сообщений ответов попытка их сжатия может привести к получению большего количества байтов, нежели использование несжатого сообщения; что явно никуда не годится.

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

По умолчанию серверная интеграция ASP.NET Core не использует сжатие, но мы можем включить его для всего сервера или определенных служб. Это кажется разумным вариантом по умолчанию, поскольку вы можете отслеживать свои ответы для различных методов с течением времени и оценивать выгоду от их сжатия.

КАК ВКЛЮЧИТЬ СЖАТИЕ ОТВЕТОВ В GRPC?


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

НАСТРОЙКА НА УРОВНЕ СЕРВЕРА


services.AddGrpc(o =>{   o.ResponseCompressionLevel = CompressionLevel.Optimal;   o.ResponseCompressionAlgorithm = "gzip";});

Startup.cs на GitHub

При регистрации сервиса gRPC в контейнере инъекции зависимостей с помощью метода AddGrpc внутри ConfigureServices, у нас есть возможность произвести настройку в GrpcServiceOptions. На этом уровне параметры влияют на все службы gRPC, которые реализует сервер.

Используя перегрузку расширяющего метода AddGrpc, мы можем предоставить Action<GrpcServiceOptions>. В приведенном выше фрагменте кода мы выбрали алгоритм сжатия gzip. Мы также можем установить CompressionLevel, манипулируя временем, которое мы жертвуем на сжатие данных для получения меньшего размера. Если параметр не уазан, текущая реализация по умолчанию использует CompressionLevel.Fastest. В предыдущем фрагменте мы предоставили для сжатия больше времени, чтобы уменьшить количество байт до минимально возможного размера.

НАСТРОЙКА НА УРОВНЕ СЕРВИСА


services.AddGrpc()   .AddServiceOptions<WeatherService>(o =>       {           o.ResponseCompressionLevel = CompressionLevel.Optimal;           o.ResponseCompressionAlgorithm = "gzip";       });

Startup.cs на GitHub

В результате вызова AddGrpc возвращается IGrpcServerBuilder. Мы можем вызвать для билдера расширяющий метод под названием AddServiceOptions, чтобы предоставить параметры для каждой службы отдельно. Этот метод является универсальным и принимает тип службы gRPC, к которой должны применяться параметры.

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

ЗАПРОС ОТ КЛИЕНТА GRPC


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

var channel = GrpcChannel.ForAddress("https://localhost:5005");

Program.cs на GitHub

Созданные таким образом каналы уже отправляют заголовок grpc-accept-encoding, который включает тип сжатия gzip. Сервер считывает этот заголовок и определяет, что клиент разрешает возвращать сжатые ответы.

Один из способов визуализировать эффект сжатия включить логирование для нашего приложения во время разработки. Этого можно сделать, изменив файл appsettings.Development.json следующим образом:

{ "Logging": {   "LogLevel": {       "Default": "Debug",       "System": "Information",       "Grpc": "Trace",       "Microsoft": "Trace"   } }}

appsettings.Development.json на GitHub

При запуске нашего сервера мы получаем гораздо более подробные консольные логи.

info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]     Executing endpoint 'gRPC - /WeatherForecast.WeatherForecasts/GetWeather'dbug: Grpc.AspNetCore.Server.ServerCallHandler[1]     Reading message.dbug: Microsoft.AspNetCore.Server.Kestrel[25]     Connection id "0HLQB6EMBPUIA", Request id "0HLQB6EMBPUIA:00000001": started reading request body.dbug: Microsoft.AspNetCore.Server.Kestrel[26]     Connection id "0HLQB6EMBPUIA", Request id "0HLQB6EMBPUIA:00000001": done reading request body.trce: Grpc.AspNetCore.Server.ServerCallHandler[3]     Deserializing 0 byte message to 'Google.Protobuf.WellKnownTypes.Empty'.trce: Grpc.AspNetCore.Server.ServerCallHandler[4]     Received message.dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]     Sending message.trce: Grpc.AspNetCore.Server.ServerCallHandler[9]     Serialized 'WeatherForecast.WeatherReply' to 2851 byte message.trce: Microsoft.AspNetCore.Server.Kestrel[37]     Connection id "0HLQB6EMBPUIA" sending HEADERS frame for stream ID 1 with length 104 and flags END_HEADERStrce: Grpc.AspNetCore.Server.ServerCallHandler[10]     Compressing message with 'gzip' encoding.trce: Grpc.AspNetCore.Server.ServerCallHandler[7]     Message sent.info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]     Executed endpoint 'gRPC - /WeatherForecast.WeatherForecasts/GetWeather'trce: Microsoft.AspNetCore.Server.Kestrel[37]     Connection id "0HLQB6EMBPUIA" sending DATA frame for stream ID 1 with length 978 and flags NONEtrce: Microsoft.AspNetCore.Server.Kestrel[37]     Connection id "0HLQB6EMBPUIA" sending HEADERS frame for stream ID 1 with length 15 and flags END_STREAM, END_HEADERSinfo: Microsoft.AspNetCore.Hosting.Diagnostics[2]     Request finished in 2158.9035ms 200 application/grpc

Log.txt на GitHub

В 16-й строке этого лога мы видим, что WeatherReply (по сути, массив из 100 элементов WeatherData в данном примере) был сериализован в protobuf и имеет размер 2851 байт.

Позже, в 20-й строке мы видим, что сообщение было сжато с помощью кодировки gzip, а в 26-й строке мы можем увидеть размер фрейма данных для этого вызова, который составляет 978 байт. В данном случае данные были хорошо сжаты (на 66%), поскольку повторяющиеся элементы WeatherData содержат текст, и многие значения в сообщении повторяются.

В этом примере сжатие gzip хорошо повлияло на размер данных.

ОТКЛЮЧЕНИЕ СЖАТИЯ ОТВЕТА В РЕАЛИЗАЦИИ МЕТОДА СЛУЖБ


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

Давайте посмотрим на серверный лог при вызове метода службы, который передает сообщения WeatherData с сервера. Если вы хотите узнать больше о потоковой передаче на сервере, вы можете почитать мою предыдущую статью Потоковая передача данных на сервере с gRPC и .NET Core.

info: WeatherForecast.Grpc.Server.Services.WeatherService[0]     Sending WeatherData responsedbug: Grpc.AspNetCore.Server.ServerCallHandler[6]     Sending message.trce: Grpc.AspNetCore.Server.ServerCallHandler[9]     Serialized 'WeatherForecast.WeatherData' to 30 byte message.trce: Grpc.AspNetCore.Server.ServerCallHandler[10]     Compressing message with 'gzip' encoding.trce: Microsoft.AspNetCore.Server.Kestrel[37]     Connection id "0HLQBMRRH10JQ" sending DATA frame for stream ID 1 with length 50 and flags NONEtrce: Grpc.AspNetCore.Server.ServerCallHandler[7]     Message sent.

Log.txt на GitHub

В 6-й строке мы видим, что отдельное сообщение WeatherData имеет размер 30 байт. В 8-й строке происходит сжатие, а в 10-й мы видим, что длина данных теперь составляет 50 байтов больше, чем исходное сообщение. В этом случае для нас нет никакой выгоды от gzip сжатия, мы видим увеличение общего размера сообщения, отправляемого по сети.

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

public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context){   context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);   // реализация метода, который записывает в поток}

WeatherService.cs на GitHub

Мы можем установить WriteOptions в ServerCallContext в верхней части нашего метода службы. Мы передаем новый экземпляр WriteOptions, для которого значение WriteFlags установлено в NoCompress. Эти параметры используются для следующей записи.

При потоковой передаче ответов это значение также можно установить в IServerStreamWriter.

public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context){      responseStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);   // реализация метода записи в поток}

WeatherService.cs на GitHub

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

info: WeatherForecast.Grpc.Server.Services.WeatherService[0]     Sending WeatherData responsedbug: Grpc.AspNetCore.Server.ServerCallHandler[6]     Sending message.trce: Grpc.AspNetCore.Server.ServerCallHandler[9]     Serialized 'WeatherForecast.WeatherData' to 30 byte message.trce: Microsoft.AspNetCore.Server.Kestrel[37]     Connection id "0HLQBMTL1HLM8" sending DATA frame for stream ID 1 with length 35 and flags NONEtrce: Grpc.AspNetCore.Server.ServerCallHandler[7]     Message sent.

Log.txt на GitHub

Теперь 30-байтное сообщение имеет длину 35 байтов в DATA фрейме. Есть небольшие накладные расходы, которые составляют дополнительные 5 байтов, о которых нам здесь не нужно беспокоиться.

ОТКЛЮЧЕНИЕ СЖАТИЯ ОТВЕТА ИЗ КЛИЕНТА GRPC


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

Единственный способ, который я нашел в своем исследовании API на сегодняшний день, это настроить канал, передав экземпляр GrpcChannelOptions. Одно из свойств этого класса предназначено для CompressionProviders IList<ICompressionProvider>. По умолчанию, когда это значение равно null, реализация клиента автоматически добавляет поставщика сжатия Gzip. Это означает, что сервер может использовать gzip для сжатия сообщений ответов, как мы уже видели.

private static async Task Main(){   using var channel = GrpcChannel.ForAddress("https://localhost:5005", new GrpcChannelOptions { CompressionProviders = new List<ICompressionProvider>() });   var client = new WeatherForecastsClient(channel);   var reply = await client.GetWeatherAsync(new Empty());   foreach (var forecast in reply.WeatherData)  {       Console.WriteLine($"{forecast.DateTimeStamp.ToDateTime():s} | {forecast.Summary} | {forecast.TemperatureC} C");   }   Console.WriteLine("Press a key to exit");   Console.ReadKey();}

Program.cs на GitHub
В этом примере клиентского кода мы устанавливаем GrpcChannel и передаем новый экземпляр GrpcChannelOptions. Мы присваиваем свойству CompressionProviders пустой список. Поскольку теперь мы не указываем поставщиков в нашем канале, когда вызовы создаются и отправляются через этот канал, они не будут включать какие-либо кодировки сжатия в заголовок grpc-accept-encoding. Сервер видит это и не применяет gzip сжатие к ответу.

РЕЗЮМЕ


В этом посте мы исследовали возможность сжатия сообщений ответов от сервера gRPC. Мы обнаружили, что в некоторых случаях (но не во всех) это может привести к уменьшению размера полезной нагрузки. Мы видели, что по умолчанию вызовы клиентов включают в заголовки значение gzip grpc-accept-encoding. Если сервер настроен на применение сжатия, он будет делать это только в том случае, если поддерживаемый тип сжатия совпадает с заголовком запроса.

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

Чтобы узнать больше о gRPC, вы можете прочитать все статьи, которые являются частью моей серии о gRPC и ASP.NET Core.



ВСЁ О КУРСЕ




Читать ещё:

Подробнее..

Как мы просто сократили объем входящего в дата-центр трафика на 70

03.02.2021 22:16:57 | Автор: admin

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

Единственное, о чем мы пожалели что не применили это решение раньше.

Наша команда придерживается принципов Data-Driven, то есть решения о развитии продуктов принимаются на основе метрик, а внедрение любых новых фич начинается с проверки гипотез. Для этого мы собираем и агрегируем колоссальное количество данных о том, что происходит внутри.

Два года назад, когда мы переходили с RedShift на ClickHouse, количество собираемых аналитических событий (приложение открылось, приложение запросило ленту контента, пользователь просмотрел контент, пользователь поставил смайл (лайк) и так далее) составляло около 5 млрд в сутки. Сегодня это число приближается к 14 млрд.

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

Но перед тем, как агрегировать, сохранить и обработать столько данных, их надо сначала принять и с этим есть свои проблемы. Часть описана в статье о переходе на ClickHouse (ссылка на неё была выше), но есть и другие.

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

Во время брейншторма по поводу одного из таких инцидентов прозвучала идея: раз мы создаём этими событиями такой колоссальный объём трафика, может быть нам начать его сжимать на клиентских устройствах? Отличная идея, но она была отвергнута как неконструктивная. Ведь это не избавит нас от катастрофы, а лишь оттянет её на какое-то время. Поэтому идея отправилась в специальный ящичек для неплохих идей.

Но ближе к лету непростого 2020 года ей нашлось применение.

Протокол HTTP, помимо сжатия ответов (о котором знают все, кто когда-либо оптимизировал скорость работы сайтов), позволяет использовать аналогичный механизм для сжатия тела POST/PUT-запросов, объявив об этом в заголовке Content-Encoding. В качестве входящего обратного прокси и балансировщика нагрузки мы используем nginx, проверенное и надёжное решение. Мы настолько были уверены, что он сумеет ко всему прочему ещё и на лету распаковать тело POST-запроса, что поначалу даже не поверили, что из коробки он этого не умеет. И нет, готовых модулей для этого тоже нет, надо было как-то решать проблему самостоятельно или использовать скрипт на Lua. Идея с Lua нам особенно не понравилась, зато это знание развязало руки в части выбора алгоритма компрессии.

Дело в том, что давно стандартизированные алгоритмы сжатия типа gzip, deflate или LZW были изобретены в 70-х годах XX века, когда каналы связи и носители были узким горлышком, и коэффициент сжатия был важнее, чем потраченное на сжатие время. Сегодня же в кармане каждого из нас лежит универсальный микрокомпьютер первой четверти XXI века, оборудованный подчас четырёх- и более ядерным процессором, способный на куда большее, а значит алгоритм можно выбрать более современный.

Выбор алгоритма

Требования к алгоритму были простыми:

  1. Высокая скорость сжатия. Мы не хотим, чтобы приложения тормозили из-за второстепенной функции.

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

  3. Хорошая поддержка, доступность для основных языков программирования.

  4. Permissive лицензия.

Сразу отбросили Brotli, предназначенный для одноразового сжатия статического контента на стороне сервера. По схожей причине решили отказаться от bzip, также ориентированного на статику и большие архивы.

В итоге остановились на алгоритме Zstandard, по следующим причинам:

  • Высокая скорость сжатия (на порядок больше, чем у zlib), заточенность на небольшие объёмы данных.

  • Хороший коэффициент сжатия при щадящем уровне потребления CPU.

  • За алгоритмом стоит Facebook, разрабатывавший его для себя.

  • Открытый исходный код, двойная лицензия GPLv2/BSD.

Когда мы увидели первым же в списке поддерживаемых языков JNI, интерфейс вызова нативного кода для JVM, доступный из Kotlin мы поняли, что это судьба. Ведь Kotlin является у нас основным языком разработки как на Android, так и бэкенде. Обёртка для Swift (наш основной язык разработки на iOS) завершила процесс выбора.

Решение на бэкенде

На стороне бэкенда задача была тривиальная: увидев заголовок Content-encoding: zstd, сервис должен получить поток, содержащий сжатое тело запроса, отправить его в декомпрессор zstd, и получить в ответ поток с распакованными данными. То есть буквально (используя JAX-RS container):

// Обёртка над Zstd JNIimport org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;// ...if (  containerRequestContext    .getHeaders()    .getFirst("Content-Encoding")    .equals("zstd")) {  containerRequestContext    .setEntityStream(ZstdCompressorInputStream(      containerRequestContext.getEntityStream()    ))}

Решение на iOS

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

import Foundationimport ZSTDfinal class ZSTDRequestSerializer {    private let compressionLevel: Int32    init(compressionLevel: Int32) {        self.compressionLevel = compressionLevel    }    func requestBySerializing(request: URLRequest, parameters: [String: Any]?) throws -> URLRequest? {        guard let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {            return nil        }        // ...        mutableRequest.addValue("zstd", forHTTPHeaderField: "Content-Encoding")        if let parameters = parameters {            let jsonData = try JSONSerialization.data(withJSONObject: parameters, options: [])            let processor = ZSTDProcessor(useContext: true)            let compressedData = try processor.compressBuffer(jsonData, compressionLevel: compressionLevel)            mutableRequest.httpBody = compressedData        }        return mutableRequest as URLRequest    }}

Осторожно включив фичу на 10% клиентов прямо в процессе раскатки очередной версии, затаив дыхание, стали смотреть в логи, метрики, индикатор крашей и прочие инструменты. Проблем не обнаружилось, полёт был нормальный.

Впрочем, и снижение объёма трафика было не сильно заметно. Дождавшись, пока новая версия клиента раскатится пошире, мы врубили сжатие на 100% аудитории.

Результат нас, мягко говоря, удовлетворил:

График падения трафика на iOSГрафик падения трафика на iOS

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

То есть мы на четверть сократили весь объём.

Решение на Android

Воодушевлённые, мы запилили сжатие для второй платформы.

// Тут перехватываем отправку события через interceptor и подменяем оригинальный body на сжатый если это запрос к eventsoverride fun intercept(chain: Interceptor.Chain): Response {   val originalRequest = chain.request()   return if (originalRequest.url.toString()               .endsWith("/events")) {      val compressed = originalRequest.newBuilder()            .header("Content-Encoding", "zstd")            .method(originalRequest.method, zstd(originalRequest.body))            .build()      chain.proceed(compressed)   } else {      chain.proceed(chain.request())   }}// Метод сжатия, берет requestBody и возвращает сжатыйprivate fun zstd(requestBody: RequestBody?): RequestBody {   return object : RequestBody() {      override fun contentType(): MediaType? = requestBody?.contentType()      override fun contentLength(): Long = -1 //We don't know the compressed length in advance!      override fun writeTo(sink: BufferedSink) {         val buffer = Buffer()         requestBody?.writeTo(buffer)         sink.write(Zstd.compress(buffer.readByteArray(), compressLevel))      }   }}

И тут нас ждал шок:

График падения на AndroidГрафик падения на Android

Так как доля Android среди нашей аудитории больше, чем iOS, падение составило ещё 45%. Итого, если считать от исходного уровня, мы выиграли суммарно 70% от, напомню, всего входящего трафика в ДЦ.

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

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

Также стало видно, что наши опасения относительно батарейки не оправдались. Наоборот, потратив немного процессорной мощности телефона на сжатие данных, мы экономим намного больше электричества на передаче этих данных в эфир, как на Wi-Fi, так и по сотовой сети.

Два слова, что ещё можно улучшить

У Zstandard есть очень интересная возможность использования предобученного словаря. Если взять достаточно большой объём типичных данных и прогнать по нему сжатие, то можно получить на выходе словарь наиболее высокочастотных вхождений, который затем использовать и при сжатии, и при распаковке.

При этом коэффициент сжатия увеличивается от 10-15% на текстах до 50% на однообразных наборах строк, как у нас. А скорость сжатия даже несколько увеличивается при размере словаря порядка 16 килобайт. Это, конечно, уже не приведёт к такому впечатляющему результату, но всё равно будет приятно и полезно.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru