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#: сколько кофе пьют коллеги?
Разработайте систему для учёта потребления кофе в офисе и автоматического пополнения запаса кофейных зёрен. Система должна учитывать изменение потребительской активности, включая возрастающее потребление при завершении спринтов и снижающееся потребление во время праздничных дней.
Автор задачи: Александр Катков
Решение руководителя отдела разработки интеграционных систем склада Дамира Бейльханова:
Полное решение можно посмотреть тут.
Ниже приводим часть решения.
Для расчёта потребления кофе требуется инициализировать:
— календарь (есть возможность учесть необходимые праздники);
— настройки калькулятора.
/// <summary>
/// вместимость кофе-машины
/// <summary>
public decimal CoffeeMachineCapacity { get; set; }
/// <summary>
/// порог остатка, при котором необходимо наполнять кофе-машину
/// <summary>
public decimal RefillThreshold { get; set; }
/// <summary>
/// текущий остаток в кофемашине
/// <summary>
public decimal CurrentCoffeeAmount { get; set; }
/// <summary>
/// базовый расход кофе
/// <summary>public decimal BaseRate { get; set; }
/// <summary>/// коэффициент расхода кофе в день (может меняться, а по дефолту — 1, то есть без отклонений)
/// <summary>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 можно также передать номер дня, который будет влиять на увеличенное потребление кофе (к примеру, с 8-го дня в спринте кофе начинают пить больше, чем обычно).
3. Go: когда лучше использовать оператор switch, а когда if / else?
Абстрактный алгоритм в Go можно реализовать оператором switch на 4 варианта событий или 4 последовательными if-else. Когда целесообразно сделать через switch и чем тут Go отличается от остальных — С, Java, Kotlin и т. д.?
Автор задачи: Денис Пащенко.
По его статистике только 10% кандидатов отвечают на вопрос правильно.
Решение руководителя направления базовых сервисов Михаила Кабищева:
Отчасти, вопрос использования switch или if-else является вкусовщиной, ведь любую конструкцию switch можно представить в виде if-else. Я выделяю следующие преимущества:
— Использование switch’a позволяет уменьшить вложенность и код выглядит «чище». С ним легче проследить «поток выполнения», что хорошо сказывается на ревью, и при необходимости внести изменения в код.
— Конечно же, в Go не нужно указывать break после каждого случая, а в других языках достаточно часто это становится причиной очень неприятных багов. При необходимости можно использовать fallthrough, но так и не вспомнил, когда последний раз им пользовался. Однако, каждый раз мысленно говорю спасибо команде Go за то, что изменило стандартное поведение.
— Возможность использовать type switch. В Go очень любят интерфейсы и достаточно часто приходится проверять, с каким типом мы работаем, поэтому использование такой конструкции делает код простым.
4. QA: откуда шумы в ШУМе?
Когда-то я работал в около-космической сфере. Мы разрабатывали ШУМ — широкополосный усилитель мощности СВЧ. Прибор состоит из трёх модулей:
1 — алюминиевый корпус, покрытый никелем с впаянными в него разъёмами;
2 — СВЧ-часть на микрополосках с мощным усилительным транзистором;
3 — умная система питания с прошивками ПО.
Я проверял прибор в сборке. Прибор включали на половину мощности, нагревали до +90, охлаждали до -70, трясли сутками, делали вакуум, давление в 10 атмосфер, проверяли на влагостойкость и т. д.
По итогам проверки приборы показали себя отлично. Пришло время контрольной проверки на максимальную мощность, и тут в сигнале появился шум (паразитный сигнал). Непонятно, откуда он возникал, но этим была поражена вся партия приборов последней сборки.
Вопрос: как и откуда возникал этот сигнал, и какие действия мы предприняли, чтобы его найти?
Автор задачи: Илья Баранов
Решение руководителя группы разработки инструментов тестирования и мониторинга Александра Свеженцева:
Инженерам QA в Ozon Tech в большей степени приходится сталкиваться с задачами обеспечения качества ПО — чтобы код в продакшене был работоспособен, поэтому задачи, связанные с железными устройствами, довольно от нас далеки, но при этом не менее интересны.
Многие усилители показывают пиковые характеристики только под нагрузкой. В задаче очень мало входящих данных, что даёт немалое пространство для размышлений.
1. Шум в приборе мог возникнуть по следующим причинам:
— Исходя из подсказки про материаловедение, основной версией звучит то, что на максимальной мощности никель сработал как дополнительный источник теплового шума в приборе. Это могло случиться из-за попадания никеля в электромагнитное поле, генерируемое блоком питания (система питания — «умная», это могло внести свой вклад).
— Так как корпус сделан из сплава алюминия и никеля, при нагретом никеле мог возникнуть эффект Зеебека (возникает ЭДС) , который тоже мог привести к паразитному шуму.
— Свой вклад также могла внести неправильная геометрия проводников при экранировании, которая могла привести к магнитным помехам (паразитный контур заземления).
2. Основные действия здесь:
— Для диагностики — снятие и анализ спектрограммы.
— Возможно, проблему можно было обнаружить при проведении тестов датчика в корпусе и без корпуса.
— Возможно, если источник паразитного шума — в питании, замена источника питания помогла бы локализовать проблему.
На время конкурса участники попали в зазеркалье королевства Nozo, где всё происходит наоборот: код пишется слева направо, а тестирование начинается до разработки. Чтобы выбраться из зазеркалья, участники бросили вызов инженерам Ozon Tech и устроили им своё техсобеседование.
На конкурс прислали больше 90 задач по Go, C# и QA. Мы поделились 4-мя самыми каверзными. Если вы хотите решить задачи остальных участников, переходите на сайт конкурса.
Хотя все задачи финалистов были увлекательными, победить и действительно выпить кофе с лидом команды инженеров удалось автору задачи по QA про шумы. Остальные финалисты получили мерч от Ozon Tech. А сколько задач решили вы?