Наша компания — Netflix — способна организовывать бесперебойную, высококачественную потоковую передачу видео миллионам пользователей благодаря своей надёжной глобальной серверной инфраструктуре. В самом центре этой инфраструктуры лежит множество онлайновых распределённых баз данных. Среди них — Apache Cassandra — NoSQL-СУБД, известная высокой доступностью и хорошей масштабируемостью. Cassandra играет роль опорной технологии для множества самых разных возможностей Netflix: от механизма входа пользователя в систему — до хранения истории просмотренных материалов и до поддержки аналитики реального времени и прямых трансляций.

Со временем появлялись новые базы данных типа «ключ-значение» (Key-Value, KV), владельцы сервисов вводили в строй новый функционал. В результате мы столкнулись с массой сложностей, связанных с неправильным использованием хранилищ данных. Во-первых — разработчикам сложно оперировать такими понятиями, как производительность хранилищ данных, согласованность и устойчивость данных. Ведь речь идёт о взаимодействии со сложной системой глобальных масштабов, представленной множеством хранилищ. Во-вторых — разработчикам приходилось постоянно переучиваться, осваивая новые подходы к моделированию данных и распространённые, но очень важные паттерны доступа к данным. В перечень сложностей, встающих перед разработчиками, входят высокие задержки, которым подвержен небольшой процент запросов, находящихся в «хвосте» распределения задержек (tail latency) и идемпотентность операций. Тут же можно упомянуть и поддержку работы «широких» разделов хранилищ с множеством строк, и работу в условиях, когда для хранения данных применяется единственный «толстый» столбец, и медленную пагинацию ответов. Кроме того — наши системы были связаны с множеством собственных API разных баз данных — с API, которые постоянно развивались, и в которых иногда появлялись изменения, нарушающие обратную совместимость. Всё это привело к тому, что инженеры, в масштабах всей организации, тратили много времени на поддержку и оптимизацию механизмов доступа к данным наших микросервисов.

Мы, чтобы решить эти проблемы, разработали единый подход, основанный на нашей платформе, реализующей возможности шлюза данных — Data Gateway Platform. Применение этого подхода привело к созданию нескольких базовых слоёв абстракции, представленных различными сервисами. Самым зрелым из них является слой абстракции хранилища данных типа «ключ-значение» — Key-Value (KV) Data Abstraction Layer (DAL). Эта абстракция упрощает доступ к данным и увеличивает надёжность инфраструктуры. Она позволяет нам поддерживать более широкий спектр сценариев работы, необходимых в Netflix, не требуя от разработчиков почти никаких усилий.

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

Сервис хранения данных типа «ключ-значение»

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

Модель данных

По сути, наша KV-абстракция основана на архитектуре двухуровневой хеш-карты (HashMap). Первый уровень — это хешированный строковой идентификатор (ID), являющийся первичным ключом. Второй уровень — это отсортированная карта (SortedMap) пар байтов, представляющих собой ключи и значения. Этот подход к организации хранилища поддерживает реализацию и простых, и сложных моделей данных, обеспечивая баланс гибкости и эффективности хранилища.

HashMap<String, SortedMap<Bytes, Bytes>>

Такой вот двухуровневый подход позволяет эффективно реализовывать поддержку иерархических структур, давая возможность одновременного извлечения из хранилища связанных данных. Это требуется при реализации сложных моделей данных — таких, которые нужны для хранения структурированных записей (Record), или событий, упорядоченные по времени (Event). В более простых случаях с помощью этого подхода можно представлять плоские карты (Map), хранящие данные типа «ключ-значение» (например — вида id → {"" → value}), или именованные множества (Set) (например — вида id → {key → ""}). Такой уровень адаптируемости позволяет пользоваться KV-абстракцией в сотнях самых разных сценариев. Это делает её универсальным решением для поддержки и простых, и сложных моделей данных в крупномасштабной инфраструктуре, подобной той, которая имеется у Netflix.

KV-данные можно упрощённо изобразить так, как показано на следующем рисунке, где представлены три записи.

https://miro.medium.com/v2/resize:fit:700/0*9Ny8Uc-diSDnVGnk
Визуализация трёх записей

Вот как устроены эти записи:

message Item (   
  Bytes    key,
  Bytes    value,
  Metadata metadata,
  Integer  chunk
)

Абстракция, не зависящая от базы данных

Слой KV-абстракции спроектирован так, чтобы он мог бы скрывать детали реализации базовых хранилищ данных. Он предлагает разработчикам приложений один и тот же интерфейс вне зависимости от того, какая именно система хранения данных выбрана для оптимального решения конкретной задачи. Один из примеров такой системы хранения данных — СУБД Cassandra, но наш слой абстракции работает с множеством хранилищ. Среди них — EVCache, DynamoDB и RocksDB.

Например, когда базовым хранилищем слоя абстракции является Cassandra, он использует специфические возможности этой СУБД — такие, как поддержка ключей раздела (partition key) и ключей кластеризации (clustering key). ID записи играет роль ключа раздела, а ключ элемента — роль столбца кластеризации.

https://miro.medium.com/v2/resize:fit:700/1*tMhXVTWqtHt24l1oflpAJQ.png
Данные в СУБД Cassandra

На языке описания данных (Data Definition Language, DDL) Cassandra это будет выглядеть так:

CREATE TABLE IF NOT EXISTS <ns>.<table> (
  id             text,
  key            blob,
  value          blob,
  value_metadata blob,

PRIMARY KEY (id, key))
WITH CLUSTERING ORDER BY (key <ASC|DESC>)

Пространство имён: логическая и физическая конфигурация хранилища

Пространство имён (namespace) определяет то, где и как хранятся данные, организуя логическое и физическое разделение хранилищ и скрывая от пользователя применяемую систему хранения данных. Пространства имён, кроме того, служат в роли основного средства настройки механизмов работы с данными — таких, как целевые показатели согласованности данных и задержек доступа к данным. Каждое пространство имён может использовать различные базовые хранилища. Например — это могут быть Cassandra, EVCache, или комбинация нескольких хранилищ. Такая гибкость позволяет нашей платформе, реализующей возможности шлюза данных, предоставлять разработчикам наиболее подходящие в различных ситуациях хранилища, основываясь на требованиях к производительности хранилища, к согласованности и устойчивости данных. При таком подходе разработчикам нужно лишь описывать задачи, связанные с хранением данных, а не создавать собственные решения, основанные на неких СУБД!

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

"persistence_configuration":[                                                   
  {                                                                           
    "id":"PRIMARY_STORAGE",                                                 
    "physical_storage": {                                                    
      "type":"CASSANDRA",                                                 
      "cluster":"cassandra_kv_ngsegment",                                
      "dataset":"ngsegment",                                             
      "table":"ngsegment",                                               
      "regions": ["us-east-1"],
      "config": {
        "consistency_scope": "LOCAL",
        "consistency_target": "READ_YOUR_WRITES"
      }                                            
    }                                                                       
  },                                                                          
  {                                                                           
    "id":"CACHE",                                                           
    "physical_storage": {                                                    
      "type":"CACHE",                                                     
      "cluster":"evcache_kv_ngsegment"                                   
     },                                                                      
     "config": {                                                              
       "default_cache_ttl": 180s                                             
     }                                                                       
  }                                                                           
]

Основные API слоя абстракции хранилища данных типа «ключ-значение»

Для того чтобы обеспечить поддержку различных сценариев использования хранилища, наш слой абстракции даёт пользователям четыре базовых API, реализующих операции создания, чтения, модификации и удаления записей (Create, Read, Update, Delete — CRUD).

PutItems — создание одного или нескольких элементов записи

API PutItems реализует операцию обновления или вставки данных (upsert). С её помощью можно вставлять в двухуровневую структуру карты новые данные или обновлять существующие данные.

message PutItemRequest (
  IdempotencyToken idempotency_token,
  string           namespace, 
  string           id, 
  List<Item>       items
)

Как видите — запрос включает в себя пространство имён (namespace), ID записи (id), сведения об одном или большем количестве элементов (items) и ключ идемпотентности (idempotency_token). Этот ключ обеспечивает то, что несколько вызовов одного и того же запроса на запись изменяют состояние данных лишь один раз. Данные, разбитые на фрагменты, могут сначала готовиться к записи, а потом записываться с добавлением к ним соответствующих метаданных (например — сведений о количестве фрагментов).

GetItems — чтение одного или нескольких элементов из записи

API GetItems даёт разработчику структурированный и адаптивный инструмент для чтения данных с использованием ID, предикатов и механизмов формирования выборок данных. Такой подход позволяет найти баланс между необходимостью получения больших объёмов данных и соблюдением строгих требований к целям уровня обслуживания (Service Level Objective, SLO), касающихся производительности и надёжности системы.

message GetItemsRequest (
  String              namespace,
  String              id,
  Predicate           predicate,
  Selection           selection,
  Map<String, Struct> signals
)

Запрос GetItemsRequest включает в себя несколько важных параметров:

  • namespace — определяет логический набор данных или таблицу.

  • id — идентифицирует элемент в хеш-карте верхнего уровня.

  • predicate — фильтр, позволяющий отбирать определённые элементы. Может извлекать все элементы (match_all), конкретные элементы (match_keys), диапазоны элементов (match_range).

  • selection — сужает диапазон возвращаемых элементов. Например — page_size_bytes используется для пагинации ответа, item_limit — для ограничения общего числа элементов на страницах, include/exclude позволяет включать большие значения в ответы или исключать из ответов такие значения.

  • signals — реализует внутреннюю систему сигналов, позволяющих довести до сервера сведения о возможностях клиента, среди которых — поддержка сжатия данных или разбиения данных на фрагменты.

Сообщение GetItemResponse содержит возвращаемые данные:

message GetItemResponse (
  List<Item>       items,
  Optional<String> next_page_token
)
  • items — список полученных элементов, собранный с учётом полей Predicate и Selection, определённых в запросе.

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

DeleteItems — удаление одного или нескольких элементов из записи

API DeleteItems предоставляет нам гибкие возможности по удалению данных, включая работу на уровнях записи и элемента, а так же — позволяет удалять диапазоны значений. И всё это — с поддержкой идемпотентности данных.

message DeleteItemsRequest (
  IdempotencyToken idempotency_token,
  String           namespace,
  String           id,
  Predicate        predicate
)

Так же, как и в случае с API GetItems, параметр predicate позволяет одновременно обращаться к одному или нескольким элементам. А именно, речь идёт о следующих возможностях:

  • Операции удаления данных, работающие на уровне записи (match_all): удаление всей записи за постоянное время, не зависящее от количества элементов в записи.

  • Операции удаления данных, работающие с диапазоном элементов (match_range): удаление диапазона элементов внутри записи. Это полезно для сохранения в записи «n самых новых элементов» или для удаления префикса пути.

  • Операции удаления данных, работающие на уровне элемента (match_keys): удаление одного или нескольких отдельных элементов.

Некоторые подсистемы хранения данных (любые хранилища, которые откладывают истинное удаление данных), вроде Cassandra, испытывают сложности с выполнением больших объёмов операций удаления данных. Это так из-за дополнительной нагрузки на систему, вызываемой пометкой объектов, предназначенных для удаления (tombstone) и выполнением операций объединения таблиц и фактического удаления объектов (compaction). Наш слой абстракции хранилища данных типа «ключ-значение» оптимизирует удаление записей и диапазонов значений так, что при выполнении одной операции генерируется всего один объект, предназначенный для удаления. Подробности об этом можно почитать здесь.

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

Сложные операции изменения и сканирования данных

Помимо простых CRUD-операций, выполняемых над отдельной записью, KV поддерживает и сложные операции изменения (API MutateItems) и сканирования (API ScanItems) данных, затрагивающие несколько элементов и несколько записей. Операция PutItems, кроме того, поддерживает атомарную запись больших двоичных объектов в пределах одного объекта Item с использованием протокола, основанного на фрагментировании данных. Применение этих сложных API требует внимательного проектирования запросов. Это нужно для обеспечения предсказуемого уровня задержек при работе с данными. Задержки при этом должны быть невысокими и отличаться линейными временными характеристиками. Мы планируем рассказать об этом в будущих публикациях.

Принципы проектирования, позволяющие достичь надёжных и предсказуемых показателей производительности системы

Идемпотентность и борьба с замедлением небольшого процента запросов из «хвоста» распределения задержек

Для того чтобы обеспечить целостность данных, API PutItems и DeleteItems используют ключи идемпотентности, которые дают уникальный идентификатор каждой операции изменения данных. Они гарантируют то, что операции выполняются в определённом логическом порядке, даже в том случае, если, из-за высокого уровня задержек, выполняются хедж-запросы или повторяются исходные запросы. Это особенно важно в базах данных, вроде Cassandra, работающих по принципу «выигрывает последняя операции записи» (last-write-wins). В таких системах жизненно важен правильный порядок выполнения запросов и их дедупликация.

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

message IdempotencyToken (
  Timestamp generation_time,
  String    token
)

В Netflix предпочтение отдаётся монотонно изменяющимся ключам, генерируемым клиентами. Это так из-за надёжности таких ключей, особенно — в средах, где генерирование ключей на серверах может попасть под воздействие сетевых задержек. Речь идёт о комбинации монотонно изменяющейся метки времени, generation_time, предоставляемой клиентом, и 128-битного случайного UUID-ключа token. Генерирование ключей, основанных на времени, может подвергаться негативному влиянию рассинхронизации часов. Но наши эксперименты, проведённые на инстансе EC2 Nitro, показали, что смещение времени крайне мало (менее 1 миллисекунды). В некоторых случаях, где нужен более строгий порядок запросов, могут применяться ключи, уникальные для некоего региона. Генерировать их можно с помощью инструментов наподобие Zookeeper. Могут использоваться и ключи, уникальные в глобальном масштабе, представленные, например идентификаторами транзакций.

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

https://miro.medium.com/v2/resize:fit:700/0*gTmQpPIyZcKDb4Fb
Рассинхронизация часов в парке серверов Cassandra

Обработка блоков данных большого размера путём разбиения их на фрагменты

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

Данные объектов, размер которых не превышает 1 МиБ, сохраняются непосредственно в базовом хранилище (например — в Cassandra). Это обеспечивает быстрый и эффективный доступ к ним. Но при работе с более крупными блоками данных в основном хранилище размещают только идентификатор, ключ и метаданные. А сами данные разделяют на более мелкие фрагменты и хранят отдельно, в специальном хранилище. Это хранилище тоже может быть представлено СУБД Cassandra, но в нём может применяться другая схема разделов, оптимизированная для обработки больших фрагментов данных. Ключ идемпотентности связывает все эти операции записи воедино, в одну атомарную операцию.

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

Сжатие данных на стороне клиента

KV-абстракция применяет сжатие передаваемых данных на стороне клиента для оптимизации производительности. Особенно это актуально при передаче больших объёмов информации. В то время как многие СУБД предлагают сжатие данных на стороне сервера, выполнение сжатия на клиенте снижает уровень использования дорогих серверных процессорных ресурсов, снижает уровень нагрузки на сеть и на дисковую систему ввода/вывода. В одном из наших проектов, входящих в подсистему поиска Netflix, включение сжатия данных на стороне клиента уменьшило размер передаваемых данных на 75%, что значительно повысило экономическую эффективность проекта.

Более эффективная пагинация данных

Мы, для ограничения количества данных, передаваемых в ответ на запрос, используем лимит, задаваемый в байтах, а не в количестве элементов. Это позволяет нам обеспечивать предсказуемый уровень SLO операций. Например, при чтении страницы размером 2 МиБ, мы можем обеспечить показатель SLO, не превышающий 10 миллисекунд. А если ограничивать размеры страниц количеством элементов — это приводит к задержкам непредсказуемой длительности. Так происходит из-за серьёзной изменчивости размеров элементов. Запросы, в параметрах которых указано получение 10 элементов на страницу, могут выполняться с очень разным уровнем задержек, который зависит от размеров элементов. Элементы размером 10 МиБ будут обрабатываться медленнее, чем элементы размером 1 КиБ.

Установка лимитов в байтах — это не так уж и просто. Лишь некоторые хранилища поддерживают пагинацию, основанную на физическом размере данных. Большинство хранилищ используют ограничения на количество результатов. Например — в DynamoDB и в Cassandra используются ограничения на количество элементов или строк. Для решения этой проблемы мы задаём статический лимит для исходных запросов к базовому хранилищу. Мы делаем запрос с этим лимитом и обрабатываем результаты. Если для достижения лимита, устанавливаемого в байтах, нужно больше данных — выполняются дополнительные запросы. Так делается до тех пор, пока лимит не будет достигнут. Результаты, которые в него не укладываются, отбрасываются. После этого генерируется ключ страницы.

Применение статического лимита может вести к неэффективному использованию системных ресурсов. Появление одного большого элемента может привести к тому, что нам придётся отбросить множество результатов. А при обработке маленьких элементов для заполнения страницы может понадобиться сделать много запросов, что приводит к так называемому «усилению чтения данных». Для того чтобы смягчить эти проблемы, мы реализовали адаптивную пагинацию. Её суть в динамической настройке лимитов с учётом сведений об обрабатываемых данных.

Адаптивная пагинация

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

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

https://miro.medium.com/v2/resize:fit:700/0*yg8xyQEoEmvKYoOV
Адаптивная пагинация

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

Например, представим, что клиент отправил запрос GetItems с лимитом на размер страницы в 2 МиБ и максимальным временем задержки между конечными пунктами в 500 мс. Сервер, обрабатывая этот запрос, загружая данные из базового хранилища. В записи, с которой он работает, имеются тысячи маленьких элементов, поэтому для того, чтобы заполнить страницу данными, в обычных условиях понадобится больше 500 мс, что не соответствует SLO. Если сервер будет обрабатывать запрос слишком долго — клиент получит сообщение об ошибке нарушения SLO, что приведёт к отмене запроса даже в том случае, если при его обработке ничего экстраординарного не произошло. Для того чтобы этого избежать, сервер, загружая данные из хранилища, наблюдает за временем. Если сервер определит, что продолжение загрузки дополнительных данных может нарушить SLO, он прекращает обрабатывать новые результаты и отправляет ответ с ключом пагинации.

https://miro.medium.com/v2/resize:fit:700/0*hEkIfkUJ4KDnbbGx
Заблаговременная отправка ответа на запрос

Такой подход обеспечивает обработку запросов в пределах SLO даже в том случае, если не достигнуто полное заполнение страницы. Это позволяет клиенту получать предсказуемые результаты. Более того — если клиент представляет собой gRPC-сервер, у которого заданы адекватные крайние сроки выполнения операций, то он представляет собой систему достаточно интеллектуальную. Систему, способную в определённых условиях прекратить отправку дальнейших запросов, что сокращает объём ненужной работы.

Если вам интересна эта и другие подобные темы — взгляните на эту статью.

Обмен сигналами

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

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

https://miro.medium.com/v2/resize:fit:700/0*sVOLoSeIKpzDMQ5N
Обмен сигналами

Использование KV в Netflix

Слой абстракции хранилища данных типа «ключ-значение» обеспечивает функционирование нескольких важнейших механизмов Netflix. В их число входят следующие:

  • Работа с метаданными потоковой передачи видео: обеспечение доступа к стриминговым метаданным с высокой пропускной способностью и низкими задержками. Это обеспечивает доставку пользователям персонализированного контента в режиме реального времени.

  • Работа с профилями пользователей: эффективное хранение и получение сведений об истории просмотров и предпочтениях пользователей позволяют организовать органичный переход пользователей от устройства к устройству и персонализацию материалов.

  • Работа с сообщениями, передаваемыми между устройствами: хранение и загрузка данных Push-реестра для нужд работы с сообщениями, что выглядит как обработка миллионов запросов.

  • Организация работы системы аналитики в режиме реального времени: в этой системе хранятся огромные объёмы данных, описывающих действия пользователя, которые он совершает в то время, когда не смотрит видео (impression events). Она даёт представление о поведении пользователей и о производительности системы. Её работа связана с перемещением данных между онлайновыми и оффлайновыми хранилищами.

О будущих улучшениях

Глядя в будущее, мы планируем расширить слой абстракции хранилища данных типа «ключ-значение», реализовав следующие возможности:

  • Управление жизненным циклом данных: тонкая настройка параметров хранения и удаления данных.

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

  • Поддержка новых систем хранения данных: интеграция с дополнительными системами хранения данных, которая позволит реализовывать новые сценарии использования хранилища.

  • Применение алгоритмов словарного сжатия: дальнейшее уменьшение объёма данных при поддержании высокого уровня производительности.

Итоги

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

О, а приходите к нам работать? ? ?

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

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

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Присоединяйтесь к нашей команде

Комментарии (3)


  1. iRumba
    09.12.2024 13:54

    А как поддерживается атомарность?


  1. Master255
    09.12.2024 13:54

    Как выяснилось на практике - торренты превосходят все эти технологии Нетфликс. Принципиально)))


  1. jingvar
    09.12.2024 13:54

    Оу ребята переоткрыли s3gateway?