IT-задачи о кофе, switch и шумах

Аватарка пользователя Татьяна Бобкова

Собрали самые каверзные задачи на тему программирования по Go, C# и QA, которые удивили инженеров Ozon Tech, и делимся решениями.

В ноябре Ozon Tech и Tproger провели конкурс, в котором участникам не нужно было ничего решать, а наоборот, прислать свои задачи команде инженеров.

Разобрали решения четырёх лучших задач и предлагаем вам тоже подумать над вариантами.

1. Go: нужно ли использовать внешний пул?

Дано:

  • Master-slave postgres стенд (репликация асинхронная посредством patroni);
  • Внешний пул коннекшенов (pgpool/pgbouncer), приконекченный к мастеру.
  • Одно/несколько приложений с внутренним реализованным пулером, работающие с внешним пулом из предыдущего пункта.

Вопросы:

  • Не добавляет ли точек отказа наличие внешнего пулера, ведь он не реплицирован как postgres, и в случае его неработоспособности упадёт всё? Как и где архитектурно предусмотреть fallback-механизм для такого?
  • Есть ли вообще смысл использовать внешний пул? Если да, то какой, при условии что количество коннекшенов с реплик сервисов (даже с учётом разумной работы автоскейлера, если он есть) разумно и не превосходит значение в конфигурации postgres?
  • Предположим, что мы решили отказаться от внешнего пула. На кластере postgres произошёл failover (мастер переключился на реплику). Как быстро приложения переподключатся к новому мастеру? Возможна ли ситуация, что в используемых вами C# и Go пулерах не произойдёт инвалидация коннекшена?

Автор задачи: Иосиф Петрович

Если вы хотите сначала решить задачу самостоятельно, то не открывайте решение инженеров сразу.

Решение руководителя направления базовых сервисов Михаила Кабищева

Сейчас мы используем Patroni два пулера: PgBouncer и Odyssey. Как и многие другие решения у каждого из них есть свои плюсы и минусы, поэтому мы всё ещё находимся в переходном периоде и выборе конечного решения.

Наш подход немного отличается от предложенного в «дано», и я бы советовал запускать пулер не только для мастера, но и для каждой реплики в кластере (мастера, синхронной и асинхронной). Причём пулер физически запущен на том же хосте, что и реплика базы. Он не является дополнительной точкой отказа и при необходимости может быть запущен в нескольких копиях на одном хосте, слушая один порт.

Я бы советовал всегда использовать внешний пулер. Создание нового подключения является дорогой операцией для PostgreSQL, поэтому инициировать их каждый раз при скейле/рестарте приложения — не самая хорошая идея. Также вы всегда можете ошибиться в расчётах или забыть про них, и тогда пулер будет той точкой, которая обезопасит вас от проблем. Не знаю, как у вас, но среди моих набитых шишек точно есть кейс, когда скейл приложения из-за нехватки CPU привёл к проблемам с коннектами в базе, потому что я забыл их пересчитать.

Так как мы используем связку Patroni + PgBouncer/Odyssey + Warden (наша система для Service Discovery), то она позволяет приложением практически мгновенно узнавать о любых изменениях в топологии баз: переключении мастера на другую реплику, запуск новой асинхронной реплики и так далее.

2. C#: сколько кофе пьют коллеги?

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

Автор задачи: Александр Катков

Решение руководителя отдела разработки интеграционных систем склада Дамира Бейльханова

Для расчёта потребления кофе требуется инициализировать:

— календарь (есть возможность учесть необходимые праздники);

— настройки калькулятора.

Полное решение можно посмотреть тут.

Ниже приводим часть решения.

			/// 

public decimal CoffeeMachineCapacity { get; set; }

/// 

///  порог остатка, при котором необходимо наполнять кофе-машину

/// 

public decimal RefillThreshold { get; set; }

/// 

/// текущий остаток в кофемашине 

/// 

public decimal CurrentCoffeeAmount { get; set; }

/// 

/// базовый расход кофе

/// 

public decimal BaseRate { get; set; }

/// 

/// коэффициент расхода кофе в день (может меняться, а по дефолту — 1, то есть без отклонений)

/// 

public decimal CoffeeDayRate { get; set; }

/// вместимость кофе-машины

///
		

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

Пример:

			var calendar = new Calendar(holidays);

var calculator = new Calculator(calendar, calculatorSettings);

var sprintsCalendar = calendar.GenerateSprintsCalendar(startDate, 14, 2); 

// 2 спринта по 14 дней;

var calculationResult = calculator.CalculateRefillSchedule(sprintsCalendar);
		

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

3. Go: когда лучше использовать оператор switch, а когда if / else?

Абстрактный алгоритм в Go можно реализовать оператором switch на четыре варианта событий или четырьмя последовательными if-else. Когда целесообразно сделать через switch и чем тут Go отличается от остальных — С, Java, Kotlin и т. д.?

Автор задачи: Денис Пащенко

По его статистике только 10% кандидатов отвечают на вопрос правильно.
Решение руководителя направления базовых сервисов Михаила Кабищева

Отчасти, вопрос использования switch или if-else является вкусовщиной, ведь любую конструкцию switch можно представить в виде if-else.

Я выделяю следующие преимущества:

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

— Конечно же, в Go не нужно указывать break после каждого случая, а в других языках достаточно часто это становится причиной очень неприятных багов. При необходимости можно использовать fallthrough, но так и не вспомнил, когда последний раз им пользовался. Однако, каждый раз мысленно говорю спасибо команде Go за то, что изменило стандартное поведение.

— Возможность использовать type switch. В Go очень любят интерфейсы и достаточно часто приходится проверять, с каким типом мы работаем, поэтому использование такой конструкции делает код очень простым.

4. QA: откуда шумы в ШУМе?

Когда-то я работал в около-космической сфере. Мы разрабатывали ШУМ — широкополосный усилитель мощности СВЧ. Прибор состоит из трёх модулей:

  •  алюминиевый корпус, покрытый никелем с впаянными в него разъёмами;
  • СВЧ-часть на микрополосках с мощным усилительным транзистором;
  • умная система питания с прошивками ПО.

Я проверял прибор в сборке. Прибор включали на половину мощности, нагревали до +90, охлаждали до -70, трясли сутками, делали вакуум, давление в 10 атмосфер, проверяли на влагостойкость и т. д.

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

Вопрос: как и откуда возникал этот сигнал, и какие действия мы предприняли, чтобы его найти?

Для меня эта задача оказалась очень сложной, так как пришлось применить все свои знания в материаловедении, микроэлектронике и СВЧ.

Автор задачи: Илья Баранов

Решение руководителя группы разработки инструментов тестирования и мониторинга Александра Свеженцева

Инженерам QA в Ozon Tech в большей степени приходится сталкиваться с задачами обеспечения качества ПО — чтобы код в продакшене был работоспособен, поэтому задачи, связанные с железными устройствами, довольно от нас далеки, но при этом не менее интересны.

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

1. Шум в приборе мог возникнуть по следующим причинам:

— Исходя из подсказки про материаловедение, основной версией звучит то, что на максимальной мощности никель сработал как дополнительный источник теплового шума в приборе. Это могло случиться из-за попадания никеля в электромагнитное поле, генерируемое блоком питания (система питания — «умная», это могло внести свой вклад).

— Так как корпус сделан из сплава алюминия и никеля, при нагретом никеле мог возникнуть эффект Зеебека (возникает ЭДС), который тоже мог привести к паразитному шуму.

— Свой вклад также могла внести неправильная геометрия проводников при экранировании, которая могла привести к магнитным помехам (паразитный контур заземления).

2. Основные действия здесь:

— Для диагностики — снятие и анализ спектрограммы.

— Возможно, проблему можно было обнаружить при проведении тестов датчика в корпусе и без корпуса.

— Возможно, если источник паразитного шума — в питании, замена источника питания помогла бы локализовать проблему.

***

На время конкурса участники попали в зазеркалье королевства Nozo, где всё происходит наоборот: код пишется слева направо, а тестирование начинается до разработки. Чтобы выбраться из зазеркалья, участники бросили вызов инженерам Ozon Tech и устроили им своё техсобеседование.

На конкурс прислали больше 90 задач по Go, C# и QA. Мы поделились четырьмя самыми каверзными. Если вы хотите решить задачи остальных участников, переходите на сайт конкурса.

Хотя все задачи финалистов были увлекательными, победить и действительно выпить кофе с лидом команды инженеров удалось автору задачи по QA про шумы. Остальные финалисты получили мерч от Ozon Tech. А сколько задач решили вы?

Задачи умеренной сложности
C#
Golang
QA
626