Привет, Хабр! Сегодня System Design интервью стало неотъемлемой и, пожалуй, самой трудной частью найма для разработчиков. От кандидатов требуют за короткое время спроектировать условный YouTube, Google Drive или Telegram, способный выдерживать высокую нагрузку, не падать при отказе дата-центров и отвечать пользователю за считанные миллисекунды. И здесь большинство разработчиков сталкивается с суровой реальностью. Сложность в том, что на собеседованиях дают задачи на проектирование масштабных распределенных систем, но реальным опытом их создания обладают немногие разработчики. Задача проектирования часто может быть решена несколькими способами и не имеет единственного правильного ответа. Одно и то же требование можно реализовать разными способами, и каждый будет иметь плюсы и минусы. Нужно уметь проектировать высоко нагруженные системы, учитывая проблемы сети: задержки, сбои серверов, обеспечение согласованности данных и балансировку нагрузки. Также требуется разбираться во множестве технологий и понимать, когда и как их применять.
Поэтому на интервью кандидат часто совершает критические ошибки: не умеет определить требования к системе, путает функциональные требования с нефункциональными, не видит нюансов и узких мест, из-за чего система перестанет быть устойчивой при первой же пиковой нагрузке. На мой взгляд, лучший способ разобраться в тонкостях проектирования распределенных систем и увереннее чувствовать себя на архитектурных секциях — это создать прототип такой системы с нуля в виде пет — проекта. Этой публикацией я начинаю серию статей, целью которой является желание поделиться опытом создания такой системы. Начнем с проектирования архитектуры, далее шаг за шагом реализуем ее на языке Go, развернем в облаке и оценим производительность. Будем проектировать систему сокращения ссылок из классической книги по системному дизайну. На Хабре есть несколько статей (раз, два, три, четыре) по данной теме, но я бы хотел сделать акцент на разработке системы с использованием микросервисной архитектуры. И да, я понимаю, что применение микросервисной архитектуры для решения такой задачи - вопрос дискуссионный.
Начнем с краткого описания задачи. Пусть задан URL адрес, например, https://www.boost.org/doc/libs/1_88_0/libs/mysql/doc/html/mysql/examples/coroutines\_cpp11.html. Необходимо разработать систему, получающую на вход этот адрес, и выдающую короткий, похожий на тот, который можно получить в таких сервисах, как tinyurl. Например, https://tinyurl.com/5zae7c7v.
Ввод короткого адреса в строке поисковика должен перенаправлять пользователя к исходному URL адресу. Длина сокращенного адреса должна быть как можно короче. Сокращенный URL адрес может содержать цифры (0–9) и буквы (a–z, A–Z).
Максимальное количество генерируемых адресов — 100 миллионов в день. Отсюда максимальное количество генерируемых сокращенных URL в секунду равно 100 миллионов / 24 / 3600 = 1160. Будем называть операцию получения сокращенных URL записью.
Исходя из того, что операции чтения (получения исходного URL по короткому) и записи имеют соотношение 10 к 1 (на 10 операций чтения приходится одна запись), вычислим, что за одну секунду будет выполняться 1160 * 10 = 11 600 операций чтения.
Пусть сервис для сокращения URL-адресов должен работать 8 лет. Тогда необходимо хранить на сервере 100 миллионов 365 дней 8 лет = 292 миллиарда записей. Максимальная длина исходного URL адреса — 100 символов. В результате необходимо хранить на диске 292 миллиарда записей * 100 байт запись / 1024^4, что примерно равно 26,5 ТБ. Будем предполагать, что сокращенный URL адрес не подлежит обновлению, но в будущем возможно удаление старых адресов, с момента создания которых прошло N лет. Система должна иметь высокую доступность, масштабируемость и устойчивость к сбоям.
Возникает вопрос: «Зачем городить огород и пилить микросервисы для задачи, которую можно решить при помощи монолита?». Для пет — проекта я выбрал микросервисную архитектуру по нескольким ключевым причинам:
Система будет обрабатывать переходы по ссылкам в 10 раз чаще, чем создание новых. В монолите нам пришлось бы масштабировать всё приложение целиком. Микросервисы позволяют нам, при необходимости, развернуть десятки экземпляров сервиса чтения и всего два — три экземпляра сервиса записи, экономя ресурсы.
Если по какой — то причине перестанет работать генерация коротких ссылок, это не должно вывести всю систему из строя. Микросервисный подход гарантирует: пользователи продолжат переходить по уже созданным ссылкам, даже если функция создания новых временно недоступна.
Оптимизация работы с данными. Процесс чтения — это I/O-bound задача. Процесс создания — это CPU-bound задача. Разделение этих процессов на уровне микросервисов позволяет эффективно масштабировать и выполнять их на разных серверах.
Как известно, микросервисная архитектура делит систему на, по возможности, небольшие, слабо связанные и легко изменяемые модули — микросервисы, выполняющие относительно элементарные функции, и взаимодействующих с использованием сетевых протоколов, таких как HTTP и gRPC. Архитектура нацелена на уменьшение связности, что позволяет проще добавлять и изменять функции в системе. Это облегчает модификацию микросервисов и их независимое развертывание. Микросервис должен выполнять только одну достаточно элементарную функцию. Для реализации архитектуры не обязательно использовать один язык программирования.
Теперь рассмотрим вопрос о том, из каких частей будет состоять наша система. Поскольку микросервисы должны выполнять достаточно элементарные функции, то логично разделить проектируемую систему на следующие части:
Link Analysis. Проверяет не ведет ли исходный URL к ресурсу, содержащему запрещенный контент.
ID Generator. Создает уникальные идентификаторы для коротких URL.
Stat Collector. Сбор статистики по географическому положению пользователей, устройствам, создаваемым ссылкам, переходам по ним и т.д.
URL Redirector. Принимает короткий URL и выполняет переход по соответствующему длинному URL.
URL Shortener. Принимает исходный URL, возвращает короткий.
API Gateway. Занимается маршрутизацией запросов, авторизацией пользователей.
Service Discover. Используется для обнаружения микросервисов и получения их адресов.
Рассмотрим функционал микросервисов подробнее.
Link Analysis. Мы не хотим чтобы пользователи хранили в нашей системе ссылки на мошеннические, фишинговые или вредоносные ресурсы. Поэтому перед созданием короткого URL нужно проверить исходный при помощи специализированных внешних сервисов. Например, этот полностью бесплатен для разработчиков приложений, если они используют его в некоммерческих целях, а этот можно использовать для коммерческих целей. Если URL не прошел проверку, то возвращаем ответ с кодом 422 (Unprocessable Entity) или 400 (Bad Request).
ID Generator. После того как исходный URL успешно прошел проверку, можно переходить к созданию сокращенной ссылки. Для ее генерации нам нужен сервис, генерирующий уникальные числа (ID), которые переведем в систему счисления с основанием 62(допустимые символы [a—z, A—Z, 0—9]). Алгоритм перевода подробно описан в книге Алекса Сюя. Однако, существует проблема. Микросервисов генерации ID может быть несколько. Как обеспечить уникальность генерируемых ими значений? Для её решения используем алгоритм Snowflake для генерации 64 разрядных ID, в которых часть битов представляют значение текущего времени, другая хранит номер сервера в дата — центре, а третья — номер дата центра. Подробнее опишем этот алгоритм в следующей статье.
Stat Collector. Собирает данные о пользователях для последующего анализа. Например, аналитикам могут быть интересны следующие данные:
IP-адрес, с которого пришел запрос, чтобы определить страну.
Название браузера, его версию и движок, тип устройства (смартфон, планшет, ПК, Smart TV), его операционную систему.
С какого сайта, поисковика или по какой ссылке перешел пользователь.
Для хранения и последующего анализа этих данных используем классический архитектурный принцип — каждый микросервис имеет отдельную базу данных. Такой подход имеет два основных преимущества.
Первое — мы не хотим хранить всю информацию в одной базе. Нужно разделить две принципиально различных активности. Первая связана с сохранением и последующим получением пар "длинный URL : короткий URL" в базе, что составляет основу работы всей системы и выполняется постоянно.
Вторая — получение аналитических отчетов. Например, анализ того, из каких стран или регионов чаще всего обращаются к нашей системе, какие самые популярные ссылки. Эта информация может быть очень ценна, например, для маркетологов. Но формирование аналитических отчетов происходит гораздо реже, чем сохранение ссылок. Разделяя эти процессы, мы не только позволяем аналитикам работать параллельно не нагружая основную базу запросами, но и избавляемся от необходимости хранить всю информацию в одной таблице, что со временем неизбежно приведет к замедлению работы системы.
В качестве базы для хранения аналитической информации можно использовать ClickHouse, которая специально создана для таких задач и имеет встроенную поддержку sql подобного синтаксиса. Благодаря чему запросы выполняются гораздо быстрее, а аналитикам не нужно изучать новый язык запросов.
URL Redirector. Отвечает за получение исходного URL на основе короткого. Так как количество операций чтения в 10 раз превосходит количество операций записи, то логично выделить чтение в отдельный микросервис. При этом чтение должно происходить максимально быстро. Для этого необходимо хранить пары "исходный URL : ID" и "ID : исходный URL" в БД Redis. При этом в оперативной памяти будем хранить только те пары, к которым недавно происходило обращение. Для этого каждой паре необходимо указать время жизни (TTL), например, одни сутки. После истечения этого срока Redis автоматически удалит пару из оперативной памяти.
Алгоритм перенаправления по короткой ссылке:
1.Пользователь вводит короткую ссылку.
2. Система направляет запрос к URL Redirector, который получает ID из короткого URL.
3. Если ID уже есть в кэше, то сразу возвращается исходный URL.
4. Если ID нет в кэше, то направляем запрос к URL Shortener и извлекаем длинный URL из базы данных. Если ID нет в БД, то возвращаем ошибку.
5. Возвращаем исходный URL, используя перенаправление с кодом 301 или 302.
Код состояния 301 означает, что запрошенный URL адрес «навсегда» перемещен по длинному URL адресу. Так как перенаправление постоянное, браузер кэширует ответ и последующие запросы по тому же адресу не будут направляться к нашему сервису. Вместо этого браузер сразу откроет сокращенный URL адрес.
Код состояния 302 говорит о том, что URL адрес «временно» перемещен по длинному URL адресу. То есть последующие запросы того же URL адреса будут сначала отправляться нашему сервису, а затем перенаправляться к серверу исходного URL адреса. Нам нужна аналитическая информация, поэтому используем код состояния 302, так как он упрощает отслеживание частоты и источника переходов по ссылке.
URL Shortener. Отвечает за создание короткого URL, а также получения исходного URL по короткому. Для этого вначале проверяем с помощью вызова Link Analysis, что исходный URL не ведет на ресурсы с запрещенным контентом. Если проверка провалена, то возвращаем ответ с кодом 422 или 400, как было описано выше. Если проверка завершилась успешно, то далее необходимо проверить, не создавалась ли короткая ссылка на основе исходного URL ранее.
Критики необходимости такой проверки могут возразить что современные системы сокращения ссылок позволяют создавать дубликаты длинных URL. Мне кажется, что бесконтрольная возможность создавать дубликаты URL является очевидной уязвимостью системы. Злоумышленник, нашедший такую уязвимость, может забить базу данных мусором или полностью вывести систему из строя, выбрав два направления для атаки:
Атака на переполнение памяти. Можно генерировать огромное количество запросов на сокращение одной и той же ссылки. Если система не проверяет уникальность, она будет каждый раз создавать новую запись, генерировать новый короткий код и сохранять пару в базе данных, что быстро приведет к ее неконтролируемому росту.
Атака на исчерпание пространства уникальных идентификаторов. Система имеет ограниченное количество уникальных ID для создания коротких URL. Если на одну и ту же ссылку выдать миллион разных кодов, система впустую израсходует ID, что приведет к сбою в её работе.
Для защиты от атак подобного рода система должна иметь ограничение частоты запросов (rate limiting). Ограничиваем количество создаваемых ссылок с одного IP-адреса или от одного пользователя (например, не более 5 ссылок в минуту).
Будем считать, что создание дубликатов URL запрещено. В последующих версиях можно подумать о разрешении дубликатов в рамках одного пользователя.
Структура таблицы для хранения URL в базе выглядит следующим образом:

short_url хранит идентификатор (ID) короткой ссылки. Вместо строки здесь используется 64 битное целое число. Числа занимают гораздо меньше места в памяти и на диске, чем строки, а индексы по ним работают быстрее.
long_url содержит исходный URL, на который будет перенаправлен пользователь. Длина ограничена 100 символами.
created_at хранит дату и время создания записи с учетом часового пояса (TIMESTAMPTZ). Помогает собирать статистику, а также, при необходимости, удалять устаревшие ссылки, если у них есть “срок годности”.
Индекс по short_url гарантирует, что каждое число в столбце уникально, и не позволяет записывать туда NULL. Использование структуры B-Tree позволяет базе данных быстро находить нужный long_url по коду.
Индекс по long_url позволяет защитить систему от хранения дублей. Он запрещает добавлять одну и ту же длинную ссылку дважды. Позволяет быстро проверять наличие длинной ссылки в базе.
Алгоритм создания короткой ссылки:
1. Исходный URL подается на вход.
2. Система проверяет, есть ли такой URL в базе данных.
3. Если да, это означает, что исходный URL уже был преобразован в короткий. В этом случае мы извлекаем соответствующий ID из базы данных, преобразуем в короткий URL и возвращаем его клиенту.
4. Если URL отсутствует в БД, то для него генерируется новый уникальный ID.
5. Создаем в БД новую запись с ID и исходным URL, преобразуем ID в короткий URL и возвращаем его.
Идею использовать SQL БД можно покритиковать. Для сокращателя ссылок NoSQL база данных является альтернативным выбором. Наша структура данных примитивна, используется всего одна таблица, а горизонтальное масштабирование критически важно для объема в 26.5 ТБ.
Если мы выбираем NoSQL, то получаем более легкое горизонтальное масштабирование "из коробки". Модель данных идеально ложится в концепцию ключ — значение, где short_url — это ключ, а long_url — значение. Но мы сталкиваемся с проблемой обеспечения уникальности значений в NoSQL.
Тема выбора NoSQL/SQL обширна и требует отдельной статьи. Пока остановимся на SQL БД, так как, на мой взгляд, для демонстрации работы Go — микросервисов реляционная модель нагляднее.
API Gateway (шлюз) позволяет решать следующие задачи:
Избавляет микросервисы от необходимости обрабатывать внешний HTTP трафик напрямую.
Маршрутизация. Запросы POST /api/v1/shorten идут на создание, а GET /{short_url} — на перенаправление.
Авторизация пользователей.
Защита с целью предотвращение DDoS-атак.
Преобразование протоколов обмена данными. На API Gateway подаются HTTP запросы, но внутри системы обмен данными лучше производить при помощи gRPC.
Клиент взаимодействует с нашей системой по протоколу HTTP. Сервису сокращения URL-адресов нужны две основные конечные точки:
Сокращение URL адреса. Чтобы создать новый сокращенный URL адрес, клиент отправляет POST — запрос с одним параметром: исходным длинным URL адресом. Конечная точка выглядит так: POST /api/v1/data/shorten параметр запроса: {longUrl: longURLString}; возвращается shortURL.
Пере направление URL адреса. Чтобы перенаправить сокращенную ссылку к соответствующему длинному URL адресу, клиент отправляет GET — запрос. Конечная точка: GET /api/v1/short/{shortUrl}. Возвращается исходный URL для HTTP перенаправления.
Разделение системы на микросервисы ставит перед нами важный вопрос: как компоненты будут общаться друг с другом? Будем использовать разделение системы на два контура: внешний (клиентский) и внутренний (меж сервисный).
Внешний контур (HTTP версии 1.1 или 2) будет использовать классический REST для взаимодействия клиентов (браузеры, мобильные приложения, сторонние API) с нашей системой. Браузеры «из коробки» не умеют делать произвольные gRPC-запросы без дополнительных прокси — слоев (вроде gRPC-Web). Кроме того, механика редиректов завязана на стандартные HTTP — заголовки и статусы ответов (301 или 302). Применение REST здесь упрощает интеграцию и делает сервис универсальным.
Внутренний контур. gRPC (Protocol Buffers над HTTP/2) для общения микросервисов между собой. gRPC работает поверх HTTP/2. Это позволяет отправлять множество запросов и ответов параллельно по одному TCP-соединению. Также к преимуществам gRPC можно отнести упорядочивание взаимодействия между клиентом и сервером. Сначала мы описываем контракт взаимодействия в .proto-файле. На основе него компилятор protoc автоматически генерирует строго типизированный код клиента и сервера на одном из поддерживаемых языков программирования (в нашем случае Golang). Это исключает ошибки несоответствия типов данных. Кроме того, gRPC работает быстрее за счет бинарного формата передачи данных и отсутствия необходимости парсинга JSON.
Из POST запроса, пришедшего на /api/v1/data/shorten, извлекается исходный URL и перенаправляется в сервис URL Shortener. Из GET запроса извлекаем короткий URL и направляем в URLRedirector.
Service Discover. Ведение списка работоспособных микросервисов. Так как система должна обеспечивать масштабируемость и высокую доступность, то необходимо запускать несколько копий(реплик) одного и того же микросервиса. Желательно чтобы они размещались в разных дата — центрах, географически удаленных друг от друга. При этом количество микросервисов одного типа может варьироваться в зависимости от количества запросов клиентов в единицу времени (нагрузки). Также некоторые экземпляры сервисов могут перезапускаться из-за сбоев. Как в таких условиях определить работоспособные микросервисы каждого типа, а также их адреса?
Для решения этой проблемы можно, например, прописывать IP адреса вручную или хранить их в конфигурации. Однако эти подходы плохо масштабируются. Обнаружение сервисов решает проблему автоматически: микросервисы передают информацию о себе в специальный сервис —реестр, а клиенты запрашивают актуальный список живых экземпляров каждого сервиса.
Основные функции Service Discovery могут реализовываться через следующие механизмы:
Реестр сервисов (Service Registry). База данных, хранящая актуальные адреса и данные всех активных микросервисов.
Автоматическая регистрация. Процесс, при котором микросервис при запуске добавляет свой IP-адрес в реестр, а при отключении — удаляет его.
Обнаружение (Discovery). Когда одному микросервису нужно обратиться к другому, он запрашивает у реестра актуальный адрес целевого сервиса.
Мониторинг состояния (Health Checking). Регулярная проверка работоспособности сервисов, исключающая неработающие узлы из списка доступных для маршрутизации трафика. Благодаря этому процессу, при масштабировании, обновлении или сбое сервисов (когда их IP-адреса меняются), система маршрутизации не ломается, а запросы автоматически перенаправляются на работающие экземпляры.
Технология gRPC имеет поддержку Service Discovery. Подробно реализацию обнаружения сервисов в следующих статьях.
Еще одной задаче в рамках микросервисной архитектуры является балансировка нагрузки для распределения сетевого трафика между несколькими экземплярами одного микросервиса. Без балансировки все запросы могут быть направлены на один сервер, который будет перегружен, пока остальные простаивают.
Главные задачи балансировки:
Отказоустойчивость (High Availability). Если один экземпляр сервиса ломается, балансировщик исключает его из цепочки и перенаправляет запросы на работающие серверы.
Масштабируемость (Scalability). Позволяет легко добавлять новые копии сервиса при росте нагрузки. Трафик начнет поступать на них автоматически.
Равномерное распределение запросов. Это защищает отдельные серверы от перегрузки.
Обновления без простоя. Позволяет отключать экземпляры сервиса по очереди для обновления кода, сохраняя систему доступной для пользователей.
Виды балансировки в микросервисной архитектуре:
На стороне сервера. Запросы идут на единую точку входа (например, Nginx, Envoy, AWS ALB), которая сама решает, какому экземпляру передать запрос.
На стороне клиента. Микросервис — отправитель (например, gRPC-клиент) сам знает адреса всех доступных экземпляров получателя благодаря Service Discovery и выбирает нужный сервис напрямую.
Будем использовать балансировку на стороне клиента для уменьшения количества сервисов в системе и снижения времени ответа пользователю.
После описания отдельных микросервисов рассмотрим общую структуру системы, не привязываясь к решениям какого-либо облачного провайдера.

Внешний балансировщик нагрузки (Load Balancer) принимает входящие запросы от пользователей и распределяет их между несколькими экземплярами API Gateway. Это обеспечивает высокую доступность и масштабируемость шлюзов. Балансировка нагрузки может выполняться на уровнях 4 или 7 модели OSI.
Экземпляры API Gateway принимают и обрабатывают трафик, осуществляя авторизацию, ограничение скорости, маршрутизацию на основе путей или версий API, а также преобразование запросов в gRPC и передачу их микросервисам. При передаче запросов необходимо выполнить балансировку нагрузки. Как говорилось ранее, эта задача решается на стороне клиента (CLB). Адреса микросервисов балансировщик получает из Service Discovery. CLB распределяет запрос между несколькими экземплярами конкретного микросервиса (URL Shortener либо URL Redirector). Такая конфигурация обеспечивает высокую доступность и масштабируемость отдельных микросервисов.
Если пришел запрос на сокращение URL адреса, то URL Shortener проверяет на допустимость создания короткого URL из исходного, посылая запрос на один из экземпляров Link Analysis. Параллельно данные о сокращаемом URL поступают в один из экземпляров сервиса статистики Stat Collector, который записывает их в кластер базы данных аналитики Analytics DB Cluster. Если исходный URL является допустимым, то URL Shortener делает запрос на получение уникального ID к одному из экземпляров микросервиса ID Generator. После получения ID URL Shortener обращается к URL Redirector чтобы тот записал пару "исходный URL : ID" в БД Redis (In Memory Cache Cluster) и отдает сформированный короткий URL в API Gateway.
Если запрос на перенаправление, то URL Redirector вначале проверяет есть ли поданный на вход короткий URL в его кэше. Если есть, то сразу происходит редирект по найденному длинному URL, иначе идет запрос на один из экземпляров URL Shortener, чтобы попытаться извлечь длинный URL из БД (URL DB Cluster). После этого осуществляется перенаправление по длинному URL с кодом 302.
Проектирование любой распределенной системы — это всегда поиск компромиссов. Пройдя путь от расчетов нагрузки до декомпозиции сервисов, мы заложили фундамент системы, которая:
Горизонтально масштабируется. Мы можем независимо увеличивать количество экземпляров URL Redirector, не трогая микросервис записи.
Устойчива к отказам. Благодаря избыточности и кэшированию, отказ базы данных или ID Generator не приведет к полной деградации сервиса. Горячие ссылки продолжат жить в Redis, а API Gateway отсечет вредоносный трафик.
Однако за эти преимущества мы платим высокую цену — сложность. Наша система стала значительно сложнее классического монолита. Вместо одного приложения нам теперь нужно настраивать Service Discovery, распределенный генератор ID, синхронизировать кэш, агрегировать логи из разных микросервисов.
Стоит ли оно того? Всё зависит от требований. При условиях неравномерной нагрузки, требованиям к отказоустойчивости и масштабируемости — считаю что да.
В следующей статье перейдем от теории к коду: напишем ID Generator на Golang, реализовав алгоритмы Snowflake и Base62. Также посмотрим как правильно организовать архитектуру этого микросервиса. Продолжение следует...
Комментарии (7)

zartdinov
22.07.2026 18:22Уже были похожие статьи, архитектура лишь малая часть, в первый же день такой сервис окажется во всех черных списках, ну и остальное.

LaRN
22.07.2026 18:22Вы уверены что у вас реально будет 292G урлов? Сколько всего доступных сайтов в инете есть?
По идее чтобы не хранить лишнее можно было бы периодически в фоне чекать по экспоненте в течении какого-то перода доступность урлов из базы ссылок и если ссылка не отвечает, то удалять ее. Так появляется еще одна реальная причина делить систему на МСА.
Анализ незаконных ссылок легко обходится. Просто сам же владелец запрещенного сайта или первые из его посетителей сначала идут на ваш сокращатель ссылок и получает короткую версию до того, как оно вообще попадет в списки опасных, а далее тк короткая ссылка уже у вас в базе, то ничего более не проверяется и просто идет переход на опасный сайт.

farengeyt451
22.07.2026 18:22Если по какой — то причине перестанет работать генерация коротких ссылок, это не должно вывести всю систему из строя. Микросервисный подход гарантирует: пользователи продолжат переходить по уже созданным ссылкам, даже если функция создания новых временно недоступна.
Если запрос на перенаправление, то URL Redirector вначале проверяет есть ли поданный на вход короткий URL в его кэше. Если есть, то сразу происходит редирект по найденному длинному URL, иначе идет запрос на один из экземпляров URL Shortener, чтобы попытаться извлечь длинный URL из БД (URL DB Cluster). После этого осуществляется перенаправление по длинному URL с кодом 302.
Немного не понял, а если ни один инстанис URL Shortener не будет доступен и URL нет в кеше? Получается система упадет
Void-Cowboy
из опыта - чем заморочененее собеседование тем меньше итоговая работа имет отношение к "пройденым конкурсам" )))
по задаче вы как-то не туда преусложнили. Все абсолютно верно хоть и со странными точками фокуса (про тот же алгоритм генерации uid можно было бы сразу расписать, вы и так словами все уже сказали, а вот балансировки и тд как раз вытянуть в отдельную полноценную статью с пояснениями почему так а не иначе, почему тут мы закладывваем возможность роста, а тут нет и тд).
вообще изначально кандидат должен задать вопрос "это теоретическая задача или практическая?", потому как кроме крисивой алгоритмической теории, есть скучная практика которая обычно строится на базе готовых узлов по очень многим причинам...
У вас как раз теоретическая реализация с редкими вкраплениями "на чем делать", что только сбивает общий тон.
AndrewDeveloper Автор
Про алгоритм генерации uid и про балансировку нагрузки будут отдельные статьи. Материала довольно много, поэтому решил не делать эту статью ещё длиннее.
AndrewDeveloper Автор
Согласен)))