Кратко

Технологии

Ключевые технологии, которые нужно знать для интервью по System Design

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

Это подводит нас к разделу технологий:

Общая структура

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

Помните, что глубина, которую ожидает интервьюер, пропорциональна уровню, на который вы проходите интервью. Middle кандидату достаточно примерно описать ElasticSearch как поисковый индекс, но если Senior кандидат не может объяснить обратный индекс или рассуждать о масштабировании, это уже "желтый флаг". В любом случае сначала фокусируйтесь на широте, а не на глубине!

Основная база данных

Почти во всех задачах по System Design вам нужно хранить данные, и чаще всего вы будете хранить их в базе данных (или в Blob хранилище). Хотя существует много типов баз данных, самые распространенные - это реляционные базы данных (например, Postgres) и NoSQL базы данных (например, DynamoDB). Мы рекомендуем выбрать одну из них для вашего интервью. Если вы проходите преимущественно продуктовые интервью, выбирайте реляционную базу данных. Если вы проходите преимущественно инфраструктурные интервью, выбирайте NoSQL базу данных.

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

Правда такова: в большинстве случаев интервьюерам не нужно явное сравнение SQL и NoSQL баз данных в вашем ответе, и это ловушка, которой стоит избегать. Вместо этого расскажите о том, что вы знаете о базе данных, которую используете, и как она помогает решить задачу. Если вас просят сравнить - сфокусируйтесь на различиях в знакомых вам базах и на том, как они повлияют на ваш дизайн. Например, "я использую Postgres, потому что его ACID‑свойства помогают сохранять целостность данных".

Реляционные базы данных

Реляционные базы данных (их также называют RDBMS - Relational Database Management Systems) - самый распространенный тип баз данных. Их часто используют для транзакционных данных (например, пользовательских записей, заказов и т.п.) и, как правило, это выбор по умолчанию для продуктовых интервью. Реляционные базы данных хранят данные в таблицах, которые состоят из строк и столбцов. Каждая строка представляет одну запись, а каждый столбец - одно поле записи. Например, таблица users может иметь столбцы name и email. Данные в реляционных базах обычно запрашиваются с помощью SQL - декларативного языка для запросов к данным.

Разбор того, как реляционные базы устроены внутри, выходит за рамки этого руководства, но если вы хотите глубже разобраться, мы рекомендуем статью How Does a Database Work. Понимание того, как данные обрабатываются базой внутри, - это модель тех приемов, которые можно использовать в System Design интервью, чтобы оптимизировать производительность и масштабирование.

Помимо хранения данных, реляционные базы обладают несколькими возможностями, полезными для интервью по System Design. Самые важные из них:

  1. SQL JOIN-ы: это способ объединять данные из нескольких таблиц. Например, если у вас есть таблицы users и posts, вы можете запросить все посты конкретного пользователя. Это важно для запросов, и SQL‑базы поддерживают произвольные JOIN-ы между таблицами. Помните, что JOIN-ы могут стать узким местом по производительности, поэтому минимизируйте их, где это возможно.
  2. Индексы: это способ хранить данные так, чтобы запросы выполнялись быстрее. Например, если у вас есть таблица users с колонкой name, вы можете создать индекс по name. Это позволит находить пользователей по имени гораздо быстрее. Индексы часто реализуются на основе B‑Tree или Hash Table. Большой плюс реляционных баз данных в том, что (a) они поддерживают произвольное количество индексов, что позволяет оптимизировать разные запросы, и (b) поддерживают составные и специализированные индексы (например, геопространственные и полнотекстовые).
  3. Транзакции: позволяют объединять несколько операций в одну атомарную операцию. Например, если у вас есть таблицы users и posts, вы можете создать нового пользователя и новый пост для него одновременно. В транзакции либо обе операции успешны, либо обе откатываются. Это гарантирует, что не появятся несогласованные данные, такие как пост пользователя, которого не существует!

Самые распространенные реляционные базы данных - это Postgres и MySQL. Если у вас нет любимой базы, мы рекомендуем выбрать Postgres и у нас есть хороший подробный разбор, который поможет разобраться в деталях.

NoSQL базы данных

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

NoSQL базы данных

NoSQL базы данных - хороший выбор в ситуациях, когда:

Гибкие модели данных: ваша модель данных меняется или вам нужно хранить разные типы структур без фиксированной схемы.

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

Большие данные и real-time приложения: у вас есть приложения с большими объемами данных, особенно неструктурированных, или приложения, которым требуется обработка данных в реальном времени и аналитика.

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

О NoSQL базах данных нужно знать следующее:

  1. Модели данных: NoSQL базы данных бывают разных типов, и у каждого - своя модель данных. Наиболее распространенные типы: ключ-значение, документоориентированные, колоночные и графовые базы данных.
  2. Модели согласованности: NoSQL базы данных предлагают разные модели согласованности - от строгой до согласованности в конечном счете. Строгая согласованность (strong consistency) означает, что все узлы системы имеют одинаковые данные в один и тот же момент, а согласованность в конечном счете (eventual consistency) означает, что узлы будут иметь одинаковые данные со временем.
  3. Индексация: Как и в реляционных баз данных, NoSQL базы поддерживают индексацию для ускорения запросов. Самые распространенные типы индексов основаны на B‑деревьях и хеш-таблицах.
  4. Масштабирование: NoSQL базы данных масштабируются горизонтально с помощью согласованного хеширования и/или шардирования для распределения данных по множеству серверов.

Самые распространенные NoSQL базы данных - DynamoDB, Cassandra и MongoDB. DynamoDB это один из лучших вариантов из‑за широты возможностей и популярности. Подробности можно почитать в статье DynamoDB. Cassandra это хороший выбор для нагрузок с интенсивной записью благодаря append‑only модели хранения, но имеет ряд компромиссов по функциональности. Подробности можно почитать в статье Cassandra.

Blob хранилище

Иногда вам нужно хранить большие, неструктурированные бинарные данные - изображения, видео или другие файлы. Хранить такие большие файлы в традиционной базе данных дорого и неэффективно, и этого следует избегать, когда возможно. Вместо этого используйте blob хранилища, такие как Amazon S3 или Google Cloud Storage. Эти сервисы специально созданы для хранения больших файлов и гораздо более эффективны по стоимости, чем традиционная база.

Blob хранилища просты. Вы загружаете бинарный файл, сервис сохраняет его и возвращает URL. Затем вы используете этот URL, чтобы скачать данные. Часто blob хранилище работает вместе с CDN, так что вы можете получать быструю загрузку из любой точки мира. Загружайте большой файл в blob хранилище, который будет вашим источником, а затем используйте CDN, чтобы кэшировать файл в edge‑локациях по всему миру.

Не используйте blob хранилища вроде S3 в качестве основной базы данных, если на то нет очень веской причины. В типичной схеме у вас есть основная база, такая как Postgres или DynamoDB, где хранится указатель (просто URL) на blob в S3. Это позволяет использовать базу для запросов и индексации с высокой производительностью, при этом сохраняя преимущества дешевого blob хранилища.

Вот несколько распространенных примеров, где используют blob хранилища:

  • Design Youtube -> хранить видео в blob хранилище, а метаданные - в базе данных.
  • Design Dropbox -> хранить файлы в blob хранилище, а метаданные - в базе данных.

Очень распространенная схема при работе с большими бинарными файлами выглядит так:

Пример базовой схемы c Blob хранилищем

Для загрузки:

  • Когда клиент хочет загрузить файл, он запрашивает у сервера presigned URL.
  • Сервер возвращает presigned URL клиенту и сохраняет его в базе данных.
  • Клиент загружает файл по presigned URL.
  • Blob хранилище отправляет уведомление серверу о завершении загрузки, и статус обновляется.

Для скачивания:

  • Клиент запрашивает конкретный файл у сервера и получает presigned URL.
  • Клиент использует presigned URL, чтобы скачать файл через CDN, который проксирует запрос к blob хранилищу.

О blob хранилище нужно знать следующее:

  1. Надежность (Durability): blob хранилище рассчитано на чрезвычайно высокую надежность. Оно использует репликацию и корректирующие коды, чтобы ваши данные оставались в безопасности даже при отказе диска или сервера.
  2. Масштабируемость: managed blob хранилища вроде AWS S3 можно считать бесконечно масштабируемыми. Они способны хранить неограниченные объемы данных и обрабатывать неограниченное число запросов (в пределах лимитов вашего аккаунта). Поэтому на интервью вам не нужно отдельно обсуждать масштабирование blob хранилища - можно считать это данностью.
  3. Стоимость: blob хранилище разработано так, чтобы быть экономичным. Оно гораздо дешевле, чем хранить большие файлы в традиционной базе данных. Например, AWS S3 берет $0.023 за GB в месяц за первые 50 TB хранения - это намного дешевле, чем хранить те же данные в DynamoDB, где стоимость $1.25 за GB в месяц за первые 10 TB.
  4. Безопасность: blob хранилище имеет встроенные механизмы безопасности вроде шифрования "на диске" и "при передаче". Также есть контроль доступа, позволяющий управлять тем, кто может читать ваши данные.
  5. Загрузка и скачивание напрямую с клиента: blob хранилище позволяет загружать и скачивать файлы напрямую с клиента. Это полезно для приложений, которые должны хранить и получать большие файлы данных, например изображения или видео. Разберитесь с presigned URL и с тем, как они дают временный доступ к файлам - как для загрузки, так и для скачивания.
  6. Разбиение на куски (Chunking): при загрузке больших файлов часто используют разбиение на куски - загрузку файла небольшими частями. Это позволяет продолжить загрузку после ошибки и распараллелить ее. Это особенно полезно для больших файлов, где загрузка целиком может занять много времени. Современные blob хранилища вроде S3 поддерживают разбиение на куски из коробки через multipart upload API.

Самые популярные сервисы blob хранилищ - Amazon S3, Google Cloud Storage и Azure Blob. Все они быстрые, надежные и экономичные. Они также имеют ряд дополнительных функций, таких как управление версиями, политики жизненного цикла и контроль доступа. Их используют такие компании как Netflix, Airbnb и Spotify для хранения больших файлов данных.

Поисковая база данных

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

SELECT * FROM documents WHERE document_text LIKE '%search_term%'

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

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

  • структуру данных, которая сопоставляет слова документам, где эти слова встречаются. Простой пример инвертированного индекса выглядит так:
{
"no": [doc1, doc2, doc3],
"winter": [doc2, doc3, doc4],
"view": [doc1, doc3]
}

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

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

О базах данных оптимизированных для поиска нужно знать следующее:

  1. Инвертированные индексы: как мы уже говорили, поисковые базы данных используют инвертированные индексы, чтобы сделать поиск быстрым. Инвертированный индекс - это структура данных, которая сопоставляет слова документам, в которых они встречаются. Это позволяет быстро находить документы, содержащие конкретное слово.
  2. Токенизация (Tokenization): разбиение текста на отдельные слова. Это позволяет сопоставлять слова с документами в инвертированном индексе.
  3. Стемминг (Stemming): приведение слов к базовой форме. Это позволяет сопоставлять разные формы одного слова. Например, "бегущий" и "бежит" будут приведены к "бег".
  4. Нечеткий поиск (Fuzzy Search): способность находить результаты, похожие на поисковый запрос. Большинство поисковых баз данных поддерживают эту функциональность из коробки как опцию конфигурации. Если коротко, нечеткий поиск использует алгоритмы, которые терпимы к небольшим опечаткам или вариациям запроса. Обычно такие алгоритмы реализуются через вычисление "расстояния редактирования" (edit distance) - количества букв, которые нужно изменить, добавить или удалить, чтобы превратить одно слово в другое.
  5. Масштабирование: как и традиционные базы данных, поисковые базы данных масштабируются добавлением новых узлов в кластер и шардированием данных между ними.

Среди примеров поисковых баз явным лидером является Elasticsearch. Вы можете узнать больше в нашем разборе Elasticsearch, но, если коротко Elasticsearch - это распределенный RESTful движок поиска и аналитики, построенный поверх Apache Lucene. Он быстрый, масштабируемый и удобный, что делает его самой популярной поисковой базой данных, которую используют Netflix, Uber и Yelp.

Другие варианты поисковых баз данных включают использование полнотекстовых возможностей вашей основной базы данных. В Postgres есть GIN‑индексы, поддерживающие полнотекстовый поиск, а в Redis есть (возможно, пока довольно сырая и слабая) возможность полнотекстового поиска.

API-шлюз (API Gateway)

API-шлюз часто используется в микросервисной архитектуре и отвечает за маршрутизацию входящих запросов к нужному бэкенд‑сервису. Например, если система получает запрос GET /users/123, API-шлюз направит этот запрос в сервис пользователей и вернет ответ клиенту. API-шлюз также часто отвечает за сквозные (cross‑cutting) обязанности, такие как аутентификация, ограничение скорости запросов и логирование.

Почти во всех продуктовых интервью по System Design имеет смысл добавлять API-шлюз в дизайн как входящую точку системы, которая будет обрабатывать и маршрутизировать запросы клиентов.

API Gateway

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

Самые распространенные решения для API-шлюзов - AWS API Gateway, Kong и Apigee. Также не редкость использовать NGINX или Apache Web Server в качестве API-шлюза (в ранние дни Amazon огромный парк Apache Web Server'ов выполнял эту роль).

Балансировщик нагрузки (Load Balancer)

В большинстве задач по System Design нужно спроектировать систему, которая может справляться с большим трафиком. Когда трафика много, вам нужно распределять его по нескольким машинам (горизонтальное масштабирование), чтобы не перегружать одну машину и не создавать горячие точки (hotspot). Здесь и нужен балансировщик нагрузки. В рамках интервью часто можно считать, что балансировщик - это "черный ящик", который распределяет нагрузку по системе.

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

Пример масштабируемого сервиса с аутентификацией

Учтите, что иногда вам нужны специфичные возможности балансировщика, например sticky sessions или persistent connections. Чаще всего нужно решить, использовать L4 (Layer 4) или L7 (Layer 7) балансировщик.

Вы можете упростить выбор простым правилом: если у вас есть persistent connections вроде websockets, скорее всего вам нужен L4 балансировщик. Иначе выбирайте L7 балансировщик, который дает гибкость в маршрутизации трафика к разным сервисам. Подробнее о websocket‑соединениях - в нашем разборе паттерна Обновления в реальном времени.

Самые распространенные решения для балансировщиков нагрузки - AWS Elastic Load Balancer (управляемый сервис от AWS), NGINX (open‑source веб‑сервер, который часто используют как балансировщик) и HAProxy (популярный open‑source балансировщик). Учтите, что для систем с экстремально высоким трафиком специализированные hardware‑балансировщики будут производительнее программных, которые вы развернете сами - и вы быстро окажетесь в сумасшедшем мире сетевой инженерии.

Очередь сообщений (Message Queue)

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

Функция очереди - сглаживать нагрузку на систему. Если у нас случился всплеск в 1,000 запросов, а обрабатывать мы можем только 200 запросов в секунду, 800 запросов подождут в очереди, но не будут потеряны! Очереди также разрывают жесткую связь между производителем (producer) и потребителем (consumer), позволяя масштабировать их независимо. Сервисы, которые используют очередь, можно выключать и включать с минимальным влиянием на систему.

Осторожно добавляйте очереди в синхронные сценарии обработки. Если у вас строгие требования по задержке (например, < 500мс), добавив очередь, вы почти гарантированно нарушите этот SLA.

Рассмотрим пару распространенных сценариев использования очередей:

  • Буфер для всплесков трафика: в сервисе такси, таком как Uber, очереди помогают управлять резкими всплесками запросов на поездки. В пиковые часы или во время особых событий запросы могут резко вырасти. Очередь буферизует эти запросы и позволяет системе обрабатывать их в управляемом темпе без перегрузки серверов и ухудшения пользовательского опыта.
  • Распределение работы в системе: в облачном сервисе обработки фотографий очереди помогают распределять дорогие задачи обработки изображений. Когда пользователь загружает фото для редактирования, задачи попадают в очередь. Разные воркеры извлекают задачи из очереди, обеспечивая равномерное распределение нагрузки и эффективное использование ресурсов.
Очередь сообщений

Для интервью по System Design об очередях сообщений нужно знать следующее:

  1. Порядок сообщений (Message Ordering): большинство очередей - FIFO (first in, first out), то есть сообщения обрабатываются в порядке поступления. Однако некоторые очереди поддерживают более сложные гарантии порядка, например по приоритету (например, RabbitMQ) или по времени (например, SQS).
  2. Механизмы повторов (Retry Mechanisms): многие очереди имеют встроенные механизмы повторной доставки сообщений - они пытаются доставить сообщение несколько раз прежде, чем считать обработку неудачной. Вы можете настраивать задержки между попытками и максимальное число попыток.
  3. Dead Letter Queue (DLQ): DLQ хранит сообщения, которые не удалось обработать. Это полезно для отладки и аудита, так как позволяет посмотреть необработанные сообщения и понять причину неудачи.
  4. Масштабирование с помощью партиционирования: очереди можно партиционировать по нескольким серверам, чтобы масштабироваться и обрабатывать больше сообщений. Каждый раздел (partition) будет обслуживаться отдельным набором воркеров. Как и в базе данных, вам нужно выбрать ключ партиционирования (partition key), чтобы связанные сообщения попадали в один и тот же раздел.
  5. Обратное давление (Backpressure): самая большая проблема очередей - они позволяют легко перегрузить систему. Если система поддерживает 200 запросов в секунду, а мы получаем 300, мы никогда не обработаем все! Очередь просто скрывает проблему нехватки емкости. Решение - обратное давление (backpressure). Это способ замедления приема сообщений, когда очередь переполнена. Например, если очередь заполнена, вы можете отклонять новые сообщения или снижать скорость приема, возвращая ошибку пользователю или производителю.

Самые распространенные технологии очередей - Kafka и SQS. Kafka - распределенная streaming‑платформа, которую можно использовать как очередь (у нас есть разбор, где подробно описано, как ее применять), а SQS - полностью управляемый сервис очередей от AWS.

Потоки (Streams) / Event Sourcing

Иногда вы проектируете систему, в которой нужно либо обрабатывать огромные объемы данных в реальном времени, либо поддерживать сложные сценарии обработки, например event sourcing.

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

В обоих случаях вам, скорее всего, нужен поток (stream). В отличие от очередей, потоки могут хранить данные заданное время, позволяя потребителям читать и перечитывать сообщения с одной и той же позиции или с определенного момента в прошлом. Потоки - хороший выбор:

  • Когда нужно обрабатывать большие объемы данных в реальном времени. Представьте систему для соцсети, в которой нужно в реальном времени показывать аналитику по вовлечению пользователей (лайки, комментарии, репосты) по постам. Вы можете использовать поток, чтобы принимать огромные объемы событий (лайков) от пользователей по всему миру. Stream‑processing система (например, Apache Flink или Spark Streaming) может обрабатывать эти события в реальном времени и обновлять дашборд.
  • Когда нужно поддерживать сложные сценарии обработки, такие как event sourcing. Представьте банковскую систему, где каждая транзакция (депозит, снятие, перевод) записывается как событие и может затрагивать несколько счетов. При использовании event sourcing с потоком (например, Kafka), каждая транзакция становится событием, которое можно хранить, обрабатывать и воспроизводить. Это позволяет с одной стороны обрабатывать транзакции в реальном времени, с другой стороны проводить аудит, откатывать изменения или восстанавливать состояние любого счета в любой момент времени, воспроизводя события.
  • Когда нужно поддерживать нескольких потребителей, читающих один и тот же поток. В чат‑приложении реального времени, когда пользователь отправляет сообщение, оно публикуется в поток, связанный с чат‑комнатой. Этот поток служит централизованным каналом, где все участники чата являются подписчиками. Когда сообщение проходит через поток, каждый участник (потребитель) получает его одновременно, что обеспечивает коммуникацию в реальном времени. Это отличный пример паттерна publish‑subscribe, который является типичным сценарием использования потоков.

Для интервью по System Design о потоках нужно знать следующее:

  1. Масштабирование через разделы (partition): чтобы масштабировать потоки, их партиционируют по нескольким серверам. Каждый раздел может обрабатывать отдельный потребитель, что позволяет масштабироваться горизонтально. Как и в базе данных, нужно выбрать partition key, чтобы связанные события попадали в одну партицию.
  2. Несколько групп потребителей (consumer group): потоки поддерживают несколько групп потребителей, позволяя разным потребителям независимо читать один и тот же поток. Это полезно, когда одни и те же данные нужно обрабатывать по‑разному. Например, в системе реального времени аналитики одна группа потребителей обновляет дашборд, а другая сохраняет те же события в базу данных для аудита.
  3. Репликация: чтобы обеспечить отказоустойчивость, как и в базах данных, потоки реплицируют данные на несколько серверов. Это гарантирует, что при отказе сервера данные будут доступны с другого.
  4. Окна (Windowing): потоки поддерживают windowing - группировку событий по времени или количеству. Это полезно, когда нужно обрабатывать события пакетами, например считать часовые или дневные агрегированные метрики. Представьте аналитический дашборд реального времени, который показывает среднее время доставки по регионам за последние 24 часа.

Самые распространенные потоковые технологии - Kafka, Flink и Kinesis. Наши разборы по Kafka и Flink подробно рассказывают о том, как использовать их в задачах по System Design.

Распределенные блокировки

Если вы работаете над онлайн‑системой бронирования, такой как Ticketmaster, вам может понадобиться временно заблокировать ресурс, например билет на концерт, на короткое время (~10 минут). Это нужно, чтобы пока один пользователь покупает билет, никто другой не мог его перехватить. Традиционные базы данных с ACID‑свойствами используют транзакционные блокировки для поддержания целостности данных, что отлично подходит, когда один пользователь обновляет запись, а другие не должны ее изменять, но такие блокировки не предназначены для долгого удержания. Здесь и помогают распределенные блокировки.

Распределенные блокировки идеальны, когда вам нужно блокировать ресурс на достаточно длительное время, в распределенной среде между разными сервисами или процессами. Их часто реализуют через распределенное хранилище ключ-значение вроде Redis или ZooKeeper. Идея проста: вы храните блокировку в хранилище ключ-значение и используете атомарность, чтобы гарантировать, что только один процесс может ее захватить. Например, если в вашем экземпляре Redis есть ключ ticket-456 и вы хотите его заблокировать, вы устанавливаете значение ticket-456 в locked. Если другой процесс пытается установить то же значение, он получит отказ, потому что оно уже установлено. Когда первый процесс завершает работу, он устанавливает значение ticket-456 в unlocked, и другой процесс может захватить блокировку.

Еще одна полезная возможность распределенных блокировок - ограничение времени жизни (time to live, TTL). TTL используется чтобы гарантировать, что блокировка не оказывается в "застрявшем" состоянии, если процесс упал или был завершен. Например, если вы установили ticket-456 в locked, а процесс упал, блокировка истечет через определенное время (например, 10 минут), и другой процесс сможет ее захватить.

Вот несколько распространенных примеров, когда использовать распределенную блокировку в System Design интервью:

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

О распределенных блокировках для интервью нужно знать следующее:

  1. Механизмы блокировок: существуют разные способы реализации распределенных блокировок. Один из распространенных вариантов использует Redis и называется Redlock. Redlock использует несколько экземпляров Redis, чтобы обеспечить безопасное и согласованное получение и освобождение блокировки.
  2. Срок действия блокировки: распределенные блокировки могут истекать через заданное время. Это важно, чтобы блокировки не оставались в "застрявшем" состоянии, если процесс упал или был завершен.
  3. Гранулярность блокировок: блокировки можно применять к одному ресурсу или группе ресурсов. Например, можно блокировать один билет или группу билетов в секторе стадиона.
  4. Взаимные блокировки (deadlocks): возникают, когда два или больше процессов ждут, пока остальные освободят блокировку. Представьте, что один процесс захватил блокировку A и пытается захватить блокировку B, а другой захватил блокировку B и пытается захватить блокировку A. В итоге оба ждут друг друга. Будьте готовы обсудить, как предотвращать взаимные блокировки - распространенная ошибка в том, что блокировки берутся из разрозненных частей инфраструктуры или кода, из‑за чего их сложно распознать и предотвратить.

Распределенный кэш

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

Используйте кэш, чтобы:

  • Хранить агрегированные метрики: например, аналитическая платформа агрегирует данные из множества источников, чтобы показать дашборд метрик. Эти метрики дорого вычислять, поэтому платформа считает их асинхронно (например, раз в час в фоновом процессе) и сохраняет результат в распределенный кэш. Когда пользователь запрашивает дашборд, платформа извлекает данные из кэша вместо пересчета, снижая время ответа.
  • Сократить число запросов к базе данных: в веб‑приложении сессии пользователей часто хранятся в распределенном кэше, чтобы снизить нагрузку на базу данных. Это особенно важно для систем с большим числом одновременных пользователей. Когда пользователь логинится, система сохраняет его сессию в кэше, и последующие запросы читают данные оттуда.
  • Ускорить дорогие запросы: некоторые сложные запросы выполняются очень долго на традиционной дисковой базе данных. Например, в соцсети вроде Twitter вы хотите показывать пользователю ленту постов людей, на которых он подписан. Это сложный запрос, требующий JOIN-ов и фильтраций по нескольким колонкам. Исполнение такого запроса в Postgres может занимать секунды или даже минуты. Вместо этого можно выполнить запрос один раз в фоновом процессе, сохранить результат в распределенный кэш и отдавать результат оттуда.

О распределенных кэшах для интервью нужно знать следующее:

  1. Политика вытеснения (Eviction Policy): разные кэши используют разные политики вытеснения, определяющие, какие элементы удаляются, когда кэш заполнен. Например:
    • Least Recently Used (LRU): удаляет элементы, у которых наибольшее время последнего обращения.
    • First In, First Out (FIFO): удаляет элементы в порядке добавления.
    • Least Frequently Used (LFU): удаляет элементы, к которым обращались реже всего.
  2. Стратегия инвалидации кэша (Cache Invalidation Strategy): гарантирует актуальность данных в кэше. Например, если вы проектируете Ticketmaster и кэшируете популярные события, то при обновлении события в базе данных (например, смене площадки) нужно инвалидировать это событие в кэше.
  3. Стратегия записи в кэш (Cache Write Strategy): обеспечивает согласованность данных которые записываются в кэш. Например:
    • Write‑Through Cache: запись одновременно в кэш и основное хранилище данных. Это обеспечивает согласованность, но может замедлять запись.
    • Write‑Around Cache: запись напрямую в основное хранилище данных, минуя кэш. Это снижает "загрязнение" кэша редко используемыми данными, но может увеличить задержку при последующих чтениях.
    • Write‑Back Cache: запись в кэш с последующей асинхронной записью в основное хранилище данных. Это ускоряет записи, но может привести к потере данных, если произойдет сбой кэша до того, как запись попадет в основное хранилище данных.

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

Два самых распространенных in‑memory кэша - Redis и Memcached. Redis - хранилище ключ-значение, которое поддерживает множество структур данных: строки, хеши, списки, множества, отсортированные множества, битовые карты и hyperloglog-и. Memcached - простое хранилище ключ-значение, которое поддерживает строки и бинарные объекты.

CDN

Современные системы часто обслуживают пользователей по всему миру, из‑за чего сложно быстро доставлять контент всем пользователям. Пользователи (и интервьюеры) ожидают быструю загрузку страниц, а задержки приводят к плохому пользовательскому опыту и потере трафика. Content Delivery Network (CDN) - это тип кэша, который использует географически распределенные серверы, чтобы доставлять контент пользователям в зависимости от их местоположения. CDN часто используют для статического контента вроде изображений, видео и HTML, но также можно использовать для динамического контента, например API-ответов.

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

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

О CDN необходимо знать следующее:

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

Самые популярные CDN - Cloudflare, Akamai и Amazon CloudFront. Эти CDN предоставляют разные возможности, включая кэширование, DDoS‑защиту и файрволы веб-приложений. У них есть глобальная сеть edge‑серверов, которая позволяет доставлять контент пользователям по всему миру с низкой задержкой.

Войдите чтобы отмечать прогресс