Техническое собеседование: проектирование систем

0
917
views

Перевод статьи «Get Hired: The System Design Interview, Explained».

Проектирование систем

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

Также следует сказать, что собеседования, связанные с проектированием систем, позволяют отделять разработчиков-джуниоров от сеньоров. Задачи на кодинг призваны проверить, умеете ли вы писать код. А задания, связанные с проектированием систем, позволяют проверить, умеете ли вы создавать программы.

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

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

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

О каких собеседованиях идет речь?

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

Что подразумевается под словами «спроектировать от начала до конца»? Это именно то, что отличает джуниоров от сеньоров. Когда вы новичок в разработке программ, перспектива у вас не слишком широка. Естественно, что вы фокусируетесь на деталях реализации. Большая часть времени у вас может уйти на обсуждение структуры классов в приложении и модели данных. Сеньор эти вопросы тоже затронет, но он будет говорить и о таких вещах как схемы репликации базы данных и балансировщики нагрузки. Разница в масштабе.

Как отвечать на вопросы по проектированию систем

Вот мой алгоритм решения задач по проектированию систем:

  1. Прояснить вопрос и узнать масштаб продукта.
  2. Нарисовать самую базовую инфраструктуру.
  3. Определить API между сервером и клиентом.
  4. Определить схему базы данных.
  5. Оптимизировать производительность и доступность.

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

Пример задания: «Спроектируйте мессенджер Facebook»

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

1. Прояснить вопрос и узнать масштаб продукта

Первым вашим вопросом должно быть «На каких функциях мне сфокусироваться?». Затем следует спросить, на какое число пользователей следует рассчитывать. Ответы на эти вопросы определят ваш проект. От функционала зависит дизайн базы данных и API, а масштаб определяет лучший способ для оптимизации.

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

2. Нарисовать самую базовую инфраструктуру

Базовая инфраструктура

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

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

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

3. Определить API между сервером и клиентом

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

Движение данных на схеме системы

Пользователь А отсылает сообщение на сервер. Сервер перенаправляет сообщение пользователю В. Пользователь В получает сообщение, которое отображается в его клиенте.

Мы можем взять это словесное описание и преобразовать его в псевдокод.

//client pseudocode

function sendMessage(message) {
  //REST call to post message to our backend server
}

function receiveMessage(message) {
  //we can receive the message via long poll, web socket, or server-side event
  //once message is received, render it to the screen
}
//server pseudocode

public void passMessage(message, userA, userB) {
  //receive the message from User A via a REST post 
  //store the message in the database
  //post the message to User B
}

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

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

4. Определить схему базы данных

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

Вот как может выглядеть простая схема базы данных для мессенджера:

Простая схема базы данных

Здесь есть сущность User, содержащая ID пользователя и его имя, и есть сущность Message, содержащая ID сообщения, ID пользователя-отправителя, ID пользователя-получателя, метку времени сообщения и тело сообщения.

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

5. Оптимизировать производительность и доступность

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

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

1. Отправка и получение сообщений

Как мы будем обрабатывать миллион сообщений в секунду? Как правило, один сервер может обрабатывать 50 тысяч соединений одновременно. Это означает, что для обработки миллиона сообщений нам понадобится 20 серверов. Чтобы распределить запросы поровну, мы добавим балансировщик нагрузки, использующий алгоритм round-robin для определения, какой клиент к какому серверу направляется.

Схема системы с добавленным балансировщиком нагрузки

2. Хранение и извлечение сообщений

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

Имея это в виду, предположим, что у нас ежесекундно будет читаться 10 тысяч сообщений — при загрузке страницы пользователем и при прокрутке. Это дает нам соотношение чтение/запись, равное 1/100, т. е., в нашей системе превалируют операции записи (write-heavy system). Значит, нам нужно оптимизировать число конкурентных операций записи.

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

Дальнейшие шаги

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

Источники

Вот некоторые ресурсы по теме проектирования систем:

Проектирование API

SQL и NoSQL

Базы данных SQL

Базы данных NoSQL

Шардинг баз данных

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

Другие руководства по собеседованиям, касающимся проектирования систем

Совет на прощание

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

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

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here