en
Feedback
Максим Добрынин | Как стать востребованным Java разработчиком | ex. Jetbulb

Максим Добрынин | Как стать востребованным Java разработчиком | ex. Jetbulb

Open in Telegram

IT community с полезными материалами о программировании, технических собеседованих и рабочих буднях в IT. Авторский контент от @maksymdobrynin

Show more
4 937
Subscribers
+224 hours
-47 days
+1530 days
Posts Archive
📌 Takeaway Rate limiting отлично интегрируется в API Gateway (например, на уровне ingress), Reverse Proxy, Redis для горизонтального масштабирования либо внутри приложения, если логика связана с данными, которые удерживает сам сервис. Стоит помнить, что распределенных системах предпочтение необходимо отдавать глобальными лимитам, а не локальным. Не стоит забывать и о компромиссах (trade-off) между: уровень защиты и удобство пользователей, устойчивость и производительность системы, проста и распределенность. Макс Добрынин Software Engineer ⬇️ Поделишься своим списком задач по проектированию и разработке? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой. Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

📚Шаблон проектирования Rate limiting для защита API от перегрузки Привет, всем! Макс вещает 😼 Мне часто приходится говорить о шаблонах проектирования применяемых в микросервисной архитектуре. И я обратил внимание, что все знают Saga, Outbox, Idempotency, но все игнорируют Rate limiting, хотя он применяется очень часто. Сегодня говорим о том, что такое Rate limiting и когда его надо применять. Rate limiting – это базовый, но критически важный механизм защиты API. Его задача заключается в ограничении количество запросов от клиента за определенный период времени. Без него даже хорошо спроектированная система может прийти в негодонсти под влиянием высокой нагрузки – как от реальных пользователей, так и от ботов, которые регулярно спамят публично доступные URL. 🔹Зачем нужен Rate limiting? Во-первых, это защита от перегрузки. Если один клиент начинает слать тысячи запросов в секунду, то это может привести к потреблению всех ресурсов системы (ЦПУ, память, соединения с БД и др.). Во-вторых, это контроль справедливости между параллельными пользователями. Rate limiting гарантирует, что чрезмерная активность одного пользователя не ухудшает опыт других. В-третьих, это защита от злоумышленников. Например, Brute Force Attack, DDoS, Scraping, и др. виды. 🔹Как это работает на практике? Идея простая: во время обработки каждого запроса, система проверяет не превысил ли клиент отведенный ему лимит. Такая проверка основана на определенных алгоритмах (об этом позже). Так например, клиент может определяться на основе API Key, UserID, IP Address, и др. характеристики. В итоге, если лимит превышен, то API отвечает на запрос с ошибкой, как правило – 429 Too Many Requests. 🔹А какие есть алгоритмы реализации Rate limiting? Первым будет Fixed Window, где клиенту отводится строго зафиксированный лимит активности. Например, 100 запросов в минуту. Но, есть проблема, на границе окна возможна вспышка активности – 100 запросов в конце минуты и еще 100 в начале следующей. Это будет слабой точкой такой системы, где может привести к ее падению. Следующий – Sliding Window. Такая реализация Rate limiting использует знаменитый алгоритм Sliding Window (почему? см. тут), который позволяет смещать окно динамически, одновременно удерживая баланс между лимитом и временем в рамках котором он существует. Это позволяет сгладить проблему Fixed Window, учитывая предыдущие запросы, но реализация намного сложнее. Тройку завершает Token Bucket. Такой алгоритм предоставляет клиенту количество токенов, где один расходует на один запрос. Эти токены восполняются с течением времени. Тут все еще остается окно для резких вспышек количества запросов, но при этом они будут кратковременны, а средняя нагрузка станет контролируемой. 🔹Простой пример на основе Spring Boot и Resilience4j Здесь мы ограничиваем вызов метода и при превышении лимита возвращаем понятный ответ клиенту:

@RestController
@RequestMapping("/api/orders")
public class OrderController {
   @GetMapping("/{id}")
   @RateLimiter(name = "ordersApi", fallbackMethod = "rateLimitFallback")
   public ResponseEntity<String> getOrder(@PathVariable String id) {
       return ResponseEntity.ok("Order: " + id);
   }

   public ResponseEntity<String> rateLimitFallback(String id, RequestNotPermitted ex) {
       return ResponseEntity.status(429).body("Too many requests. Please try again later.");
   }
}
Сейчас установлено ограничение – каждую секунду не более 5 запросов. Если приходит шестой, Resilience4j не пропускает его дальше и выбрасывает RequestNotPermitted, после чего срабатывает метод fallback. В результате чего клиент получает ожидаемый HTTP Status 429. Но здесь есть важный нюанс, такой Rate limiting работает внутри конкретного экземпляра приложения. Если у вас сервис масштабирован горизонтально, то фактический лимит уже будет не 5 запросов в секунду, а примерно 25. Поэтому для распределенной системы локальный Rate limiting не всегда решает задачу полностью. В таких случаях обычно используют Redis, API Gateway или ingress с общим состоянием лимитов.

💡 Главный навык, который отличает дорогих разработчиков Можно посмотреть очень просто. На то, как человек работает с проблемой⬇️ Один разработчик: 🔹 получил задачу 🔹 написал код 🔹 закрыл тикет Вроде всё ок. Другой: 🔹 задал вопросы 🔹 понял, где эта задача в системе 🔹 предложил решение 🔹 подумал о последствиях 🔹 только потом начал писать И вот это уже другой уровень. По сути оба могут: — знать Spring — работать с БД — писать норм код Разница в том, что один: 👉 выполняет А второй: 👉 разбирается Давайте на реальном примере⬇️ Ситуация: нужно добавить новую фичу. Разработчик уровня “обычный”: 🔹 “ок, сделаю endpoint” 🔹 “добавлю таблицу” 🔹 “готово” Разработчик уровнем выше: 🔹 “а это точно отдельный сервис?” 🔹 “а что будет при росте нагрузки?” 🔹 “как это повлияет на другие части системы?” 🔹 “где тут потенциальные проблемы?” Он думает не про код, а про реальные последствия. Сейчас ценятся те, кто: — заранее видит проблемы — снижает риски — не создаёт технический долг ⚡️ Большинство разработчиков привыкают работать так: — дали задачу → сделал — дали задачу → сделал И это становится автоматизмом. Но на самом деле это тупик. 📌 Простая проверка Если тебе дать задачу, ты: сразу идёшь писать код или сначала пытаешься понять систему? Вот здесь и проходит граница Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

🤚 Преподаватель курса по Kubernetes - Максим Добрынин И это главное, что нужно понимать😉 Если вы уже смотрели его разборы: — по архитектуре — по системному дизайну — по техническим интервью то уже прекрасно знаете, какой там уровень. Это не формат:
“прочитал документацию → пересказал”
Это человек, который: — работает с реальными системами — сталкивается с продакшеном — понимает, как всё ломается в жизни, а не в теории — и умеет объяснить это нормально Одна из самых частых обратных связей:
“Наконец-то стало понятно, как это работает”
Потому что объяснение идёт не через термины, определения и сухую теорию. А через логику, систему и реальные кейсы 🎉 И мы рады объявить, что ближайший поток будет вести сам Максим. Курс по Kubernetes не про:
“вот pod, вот deployment”
А про: 🔹как работает система внутри 🔹как сервисы живут в Kubernetes 🔹что происходит при деплое 🔹где искать проблемы 🔹как мыслить, когда что-то падает Это ровно тот уровень, который отличает: “я запускал Kubernetes” от “я понимаю, что происходит” 🗓 Старт — 27 апреля По опыту такие группы закрываются быстрее всего. 👉 Подробности и запись: https://iprody.com/microservices-java 💬 Задать вопрос / забронировать место: https://t.me/iprody_online Если давно хотел разобраться в Kubernetes — лучше заходить именно в такие форматы.

📚Юнит тесты vs. Интеграционные тесты — где на самом деле проходит граница? На самом деле это один из самых «скользких» вопро
📚Юнит тесты vs. Интеграционные тесты — где на самом деле проходит граница? На самом деле это один из самых «скользких» вопросов. Почти все могут дать определение юнит тестами или интеграционным тестами. Проблемы начинаются, когда просят объяснить разницу между ними на практике И это именно то место, где начинается путаница. Суть в том, что граница между юнит и интеграционными тестами не является столько технической, сколько контекстуальной в рамках покрываемого кода. ➖ Для того, чтобы разобраться начнем с простого – классическое определение. Юнит тесты призваны тестировать одну изолированную единицу кода (метод, класс). В данном случае, все зависимости заменены на моки и/или стабы. Интеграционные тесты напротив, проверяют взаимодействие компонентов между собой. Например, как Payment Service взаимодействует с Payment Repository, где вероятно может быть реальное соединение с тестовой базой данных. Другими словами, главное — изолирован ли код от внешнего мира. Звучит просто. Но давайте попробуем найти больше различий⬇️ ➖ Основные различия Юнит тесты обладают следующими характеристиками, которые позволяют их отделить от интеграционных тестов и как следствие, выделить их скорость выполнения, легко реализации и как результат дешевизну их поддержки. 🔹Изоляция кода от других компонент 🔹Скорость выполнения (обычно миллисекунды) 🔹Стабильность и надежность 🔹Просто реализации и эксплуатации 🔹Проверяют бизнес-логику отдельно взятой компоненты Интеграционные тесты в свою очередь призваны проверять взаимодействие отдельно взятых компонент между собой, что позволяет выявить отклонения, которые невозможно обнаружить в изолированной среде. 🔹Проверка взаимодействия 🔹Средняя и высокая скорость выполнения 🔹Хрупки и нестабильные (сетевые проблемы, несогласованность версий между зависимостями) 🔹Отлавливают реальные проблемы конфигурации ➖ Что выбрать юнит тесты или интеграционное тесты? Тут не может быть однозначного выбора в пользу юнит тестов или интеграционных. Каждый из этих двух уровней тестирования отвечает за качество кода в строго отведенном контексте – они дополняют один другого. Так например, юнит тесты не гарантируют, что система реально работает, поскольку они не могут проверить: синтаксис SQL-запроса, верность конфигурации приложения написанного на Spring Boot, совместимости двух сервисов использующих один API, и многие другие проблемы. Все это покрывается на уровне интеграционных тестов. И наоборот — только интеграционные тоже плохо. Поскольку они часто медленно работают, имеют сложные зависимости, минимизируют шанс на локализацию (изоляцию) проблемы, более сложны в реализации и поддержке. 📌 Вывод Граница между юнит тестами и интеграционными тестами проходит по уровню изоляции. Первые полностью изолируют код через моки (или стабы), тогда как вторые проверяют взаимодействие с реальными зависимостями. Очень важно помнить, что тесты различных уровней должны дополнять друг друга, а не дублировать. И это может быть хорошим индикатором для ревизии уровня изоляции. Потому, важно не противопоставлять их друг другу, а правильно комбинировать для достижения максимально эффективного покрытия кода и высокого качества в продакшене. ⬇️Как ты измеряешь уровень покрытия тестами реализованного тобой решения? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой. Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

🔥 Как готовится к прохождению собеседования по системному дизайну? Вы знаете технологии. Понимаете архитектуру. Работаете с
🔥 Как готовится к прохождению собеседования по системному дизайну? Вы знаете технологии. Понимаете архитектуру. Работаете с продакшеном. Но на System Design — отказ. И причина почти никогда не в знаниях. System Design — это не про Kafka, Redis и микросервисы. Это про: — умение задавать вопросы — понимание бизнеса — работу с требованиями — аргументацию решений — компромиссы В новом видео разобрал: • главные ошибки разработчиков • как правильно проходить System Design интервью • как мыслить, чтобы получить оффер • почему даже сильные кандидаты проваливаются Если вы готовитесь к собеседованиям — обязательно посмотрите: https://youtu.be/FwznJBVRypc

❓Почему Two-phase commit (2PC) редко используют в реальных системах? На интервью по системному дизайну Two-phase commit (дале
Почему Two-phase commit (2PC) редко используют в реальных системах? На интервью по системному дизайну Two-phase commit (далее 2PC) часто звучит как очевидное решение для случаев, когда существует несколько сервисов или баз данных и необходимо сделать распределенную транзакцию с обеспечением согласованности данных. Но на практике 2PC почти не используют. Давайте разберемся почему⬇️ ➖ Как работает 2PC? Всегда есть координатор и участники распределенной транзакции. Сам процесс разделен на несколько этапов: 🔹Стадия подготовки – координатор спрашивает: «все готовы закоммитить?» 🔹Стадия согласования – участники блокируют ресурсы и отвечают «да» или «нет» 🔹Стадия выполнения – если все согласны, координатор отправляет сигнал на выполнение Такой алгоритм звучит надежно. Но, он начинает рассыпаться, когда начинаются проблемы связанные с распределенными системами. Давайте разберемся с чем именно приходится иметь дело. ➖ Блокировки и деградация производительности Во время подготовительной стадии участники блокируют данные и ждут решения координатора. Если координатор испытывает нагрузку или происходит сетевая задержка, то это приводит к следующим негативным факторам: 🔹увеличивает скорость выполнения транзакция на местах (latency) 🔹ресурсы блокируются на длительное время 🔹падает уровень пропускной способности (throughput) Такой негативный фактор может быть критическим моментом для высоконагруженных систем с малым временем отклика. Single Point of Failure (SPOF) Координатор – центральный элемент. Если он выходит из строя после подготовительной стадии, то участники не знают об этом и не могут принять решение: отклонить транзакцию или закоммитить. Хуже, участники могут остаться в состоянии ожидания на длительное время. Эту проблему принято называть “blocking problem” и она является ключевым недостатком 2PC. ➖ Плохая масштабируемость 2PC требует синхронной координации между всеми участниками. Большое количество сервисов-участников будет неизбежно увеличивать время ожидания (latency). В итоге, каждый участник влияет на общую транзакцию, что делает всю систему уязвимой к сбоям. Такой подход сам по себе противоречит принципам распределенных систем, где в основу ложится независимость отдельно взятых компонент между собой. CAP-теорема на практике Целью 2PC является обеспечение полной согласованности данных (strong consistency). Это приводит к необходимости принятия компромисса, что в итоге выражается в понижении уровня доступности (availability) и слабой устойчивости к сетевым сбоям. В случае возникновения проблемы система предпочитает ожидать, а не работать. Этот фактор может быть критическим моментом для определенных систем, где доступность и устойчивость является более предпочтительными характеристиками. 📌 Что же тогда использовать вместо 2PC? В реальных системах выбор чаще всего падает на: Saga pattern (оркестрация или хореография), событийно-ориентированную архитектуру, механизм повторов и идемпотентности, а также Outbox pattern. Иначе говоря, согласованность данных со временем (eventual consistency) вместо глобальной согласованности (strong consistency). ⬇️А ты понимаешь когда необходим eventual consistency, а когда strong consistency? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой. Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

🍃Пропагация транзакции между вызовами в Spring Framework На собеседованиях по Java и Spring очень любят спрашивать про @Transactional. Почти все знают про уровни изоляции, но когда разговор заходит о передачи контекста транзакции между вызовами методов (речи идет о параметре Propagation), то ответы резко становятся поверхностными. Хотя именно propagation определяет, что произойдет с транзакцией при вложенных вызовах. Для того, чтобы понять проблему, представим простой сценарий: ctional. Почти все знают про уровни изоляции, но когда разговор заходит о передачи контекста транзакции между Ответ: зависит от параметрау вызовами в Sp примененного к аннотацииежду вызовами в SpТак что же необходимо знать о параметреpring Framework 🔹REQUIRED (по умолчанию) Самый распространённый режим. Если транзакция уже есть — метод просто присоединяется к ней. В противном случае, создается новая транзакция. Это удобно, но здесь есть важный нюанс: если внутри вложенного метода происходит исключение и транзакция помечается как rollback-only, внешний код может этого не ожидать. В итоге можно получить неожиданный rollback в конце выполнения. 🔹REQUIRES_NEW Всегда создает новую транзакцию, даже если уже есть активная. Текущая транзакция при этом приостанавливается. Это полезно, например, для логирования или аудита: даже если основная операция завершается ошибкой, то запись в лог сохранится. 🔹NESTED Создает вложенную транзакцию черезбходимо знать(если поддерживается СУБД). Если выполнение вложенного метода завершается ошибкой, то откатывается только он, а не вся транзакция. Важно, это не полноценная независимая транзакция, как в REQUIRES_NEW. 🔹SUPPORTS / NOT_SUPPORTED / NEVER Менее популярные, но иногда встречаются: SUPPORTS — работает в транзакции, если она есть, NOT_SUPPORTED — выполняется без транзакции, даже если она была, NEVER — выбрасывает исключение, если транзакция есть. ➖Самое важное, что часто упускают Аннотация @Transactional работает через прокси. Все вызовы внутри одного класса не учитывают propagation, аПропагация тпо умолчанию происходит только для исключений типаamework На собеседо Также стоит учитывать, чтоду вызовами в Sвлияет не только на БД, но и на границы согласованности всей операции целиком. ⬇️А тебе уже приходилось разбирать на собеседовании аннотацию @Transactional? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой.

🔥 Последние 2 места на Курс по проектированию и разработке микросервисной архитектуры 2.0 Старт — 6 апреля. Подробнее про курс в посте или на сайте ⬇️Новый курс про то, как: — проектировать систему вокруг бизнес-домена (DDD) — строить Event-Driven архитектуру — управлять распределёнными транзакциями (Saga / Outbox) — обеспечивать гарантии доставки сообщений — проектировать отказоустойчивость (Circuit Breaker, Retry, Bulkhead) — строить масштабируемую архитектуру, готовую к росту нагрузки 🎯 Кому особенно актуально Если вы: 🔹 действующий разработчик 🔹 работаете с монолитом или простыми сервисами 🔹 чувствуете пробелы в архитектуре 🔹 хотите перейти на уровень Middle+ / Senior 🔹 готовитесь к сильным компаниям — этот курс даст системность. ⬇️ Что будет на выходе — понимание распределённой архитектуры — практическая реализация паттернов — опыт проектирования, а не просто кодинга ⌛️ Осталось 2 места Группа изначально формируется небольшой, чтобы сохранить качество архитектурных разборов и обратной связи. Старт — уже 6 апреля. 👉 Подробности и запись: https://iprody.com/microservices-java 💬 Задать вопрос / забронировать место: https://t.me/iprody_online Если давно хотели закрыть пробел в микросервисной архитектуре — сейчас правильный момент.

🔥 ТОП 5 вещей, которые реально усилят твоё резюме Не “ещё один фреймворк” и даже не “курс по аннотациям”. А то, что реально влияет на офферы. Давайте смотреть👇 1️⃣ Опыт разработки системы, а не разные фичи На самом деле разница огромная. ❌ “делал сервис” ✔️ “участвовал в проектировании системы из N микросервисов” Если ты не можешь описать систему — для рынка ты просто исполнитель. 2️⃣ Архитектурные решения Не: “использовал Kafka”, а: 🔹 зачем выбрали Kafka 🔹 какие были альтернативы 🔹 какие trade-offs Это сразу поднимает уровень. 3️⃣ Командная разработка Рынку важно: 🔹 работал ли ты в команде 🔹 проходил ли Code Review 🔹 участвовал ли в обсуждениях Одиночные проекты почти не ценятся. 4️⃣ Работа с продакшеном Любой опыт, где ты: 🔹 деплоил 🔹 разбирал падения 🔹 работал с логами 🔹 чинил проблемы сильно выделяет тебя среди кандидатов. 5️⃣ Понимание системы Самый недооценённый навык среди разработчиков. Можешь ли ты объяснить: 🔹 как работает система 🔹 где узкие места 🔹 что сломается при нагрузке Если да — ты уже выше 80% кандидатов. 📌 В чём суть? Резюме усиливают не технологии. Резюме усиливает опыт, который показывает: — ты думаешь — ты понимаешь — ты принимаешь решения Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

❓Почему сообщения дублируются при доставке At-least-once Гарантия at-least-once delivery (доставка «как минимум один раз») ча
Почему сообщения дублируются при доставке At-least-once Гарантия at-least-once delivery (доставка «как минимум один раз») часто встречается в в качестве решения. На первый взгляд звучит надежно и создается впечатление, что это сообщение точно не потеряется. Но у этой гарантии есть важное следствие — дубликаты неизбежны. Разберёмся, почему это происходит. В основе лежит простая идея: брокер сообщений считает сообщение доставленным только после получения подтверждения (ack) от потребителя. Пока ack не получен, сообщение считается «необработанным» и может быть отправлено повторно. Типичный сценарий дублирования выглядит так: 🔹Потребитель получает сообщение 🔹Успешно обрабатывает его (например, записывает в базу) 🔹Но не успевает отправить ack (или ack теряется) 🔹Брокер сообщений считает, что сообщение не обработано 🔹И совершает повторную попытку отправки В результате одно и то же сообщение обрабатывается дважды. Важно понимать: с точки зрения брокера всё корректно. Он действует по контракту — «лучше отправить ещё раз, чем потерять». Какие существуют причины неполучения ack? 🔹сбой сети между консьюмером и брокером сообщений 🔹ошибка в консьюмере после обработки, но до отправки ack 🔹таймаут обработки (visibility timeout в SQS, session timeout в Kafka) 🔹ребалансировка Вот отсюда и происходит природа дупликаторов при различного рода коммуникациях, включая асинхронную. Что с этим делать? Главный подход — идемпотентность. Обработчик должен уметь безопасно обрабатывать одно и то же сообщение несколько раз. На практике это реализуется так: 🔹использование уникального ключа для контроля уникальности 🔹хранение обработанных запросов в БД 🔹обновление вместо вставки в БД Например, если сообщение создает заказ, перед его созданием будет выполнена проверка на возможность его существования в системе. В случае детекции дубликата, такого сообщение будет игнорироваться и пользователю вернется ответ связанный уже с ранее обработанным сообщением. 📌 Вывод При доставке At-least-once сообщения могут дублироваться, потому что брокер сообщений повторно отправляет их, если не получил подтверждение обработки. Это может происходить из-за сбоев сети или недоступности консьюмера. Поэтому обработчики входящих сообщений должны быть идемпотентными. Это фундаментальный компромисс между надежностью и уникальностью доставки. ⬇️ А тебе уже приходилось реализовывать системы и предусматривать гарантии доставки? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой. Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

🤚 Что на самом деле даёт курс по Kubernetes Есть одна проблема с Kubernetes. Его “все используют”. Но почти никто нормально не понимает. Мы собрали обратную связь от ребят после курса. И она довольно показательная⬇️ В основном приходят с запросом не “стать DevOps”. А гораздо проще: — “хочу понять, как это работает” — “не хочу бояться kubernetes” — “хочу разбираться в конфиге, а не копировать” И это честно. Потому что большинство сейчас работает с K8S абсолютно не понимая его. 💡 Что оказывается самым ценным — разбор архитектуры от простого к сложному — настройка своей конфигурации руками — практика после каждого занятия — объяснение через аналогии
“стал понимать рабочие конфиги и могу развернуть приложение”
— Святослав ⚡️ Что меняется после курса Самый частый инсайт:
“перестал бояться Kubernetes”
И это ключевое. Потому что до этого: — всё выглядит сложным — непонятно, куда смотреть — страшно что-то трогать После: — понимаешь, как устроена система — понимаешь, где искать проблему — понимаешь, что происходит при деплое
“стал лучше понимать system design”
— Данил 👱‍♂️ Кому реально подходит 🔹 тем, кто уже вырос из junior 🔹 тем, кто работает с микросервисами 🔹 тем, кто “использует, но не понимает” 🔹начинающим DevOps 🗓Старт новой группы - 2 апреля Если ты: — используешь Kubernetes — но не понимаешь, что происходит — и хочешь перестать “тыкать наугад” возможно, это тот самый навык, который давно откладывался. Подробная информация на сайте - iprody.com/courses-kubernetes Задать вопрос/забронировать место - написать менеджеру

📚Почему пагинация решение, которое плохо масштабируется “Offset пагинация” — это самый простой и интуитивный способ разбивать данные на страницы. Он часто используется «по умолчанию», потому что легко реализуется и хорошо ложится в модель page/size. Однако на реальных нагрузках и больших объемах данных этот подход начинает деградировать. Вот пример:

SELECT * 
FROM orders 
ORDER BY created_at DESC 
LIMIT 10 OFFSET 10000;
Проблема производительности С точки зрения разработчика всё выглядит логично: «пропусти 10 000 записей и верни следующие 10». Но проблема в том, как это операция будет выполнена внутри СУБД. База данных не умеет «перепрыгивать» строки, поскольку не удерживает курсов из предыдущего запроса. Напротив, необходимо пройти через эти 10 000 записей, чтобы определить, какие именно нужно отбросить. Даже при наличии индекса это часто означает сканирование по индексу или таблице с последующим отбрасываем ненужных записей. В результате сложность запроса растет линейно относительно OFFSET. Если первая страница выполняется за миллисекунды, то страница с OFFSET 100000 может уже занимать десятки или сотни миллисекунд. При OFFSET в миллионы — это становится серьезной нагрузкой на ЦПУ и диск. Именно поэтому говорят, что “Offset пагинация” имеет сложность O(n) и плохо масштабируется. ➖ Проблема согласованности Есть и вторая проблема — согласованность данных. “Offset пагинация” не гарантирует стабильный результат, если данные в таблице меняются между запросами. Например: пользователь открывает первую страницу, затем в систему добавляются новые записи, и при переходе на вторую страницу часть данных может продублироваться или, наоборот, пропасть. Это особенно критично для систем с высокой частотой записи (логирование, события, заказы). Отдельно стоит отметить, что ORDER BY в таких запросах обязателен. Без него СУБД не гарантирует порядок строк, и пагинация становится недетерминированной (одна и та же страница может возвращать разные данные при каждом вызове). 📌 Решение Альтернативой является “Keyset пагинация” (или seek method). Вместо того чтобы при каждом запросе пропускать N строк, запрос продолжает выборку от последнего полученного значения. Таком подход основан на курсоре. Вот пример:

SELECT * 
FROM orders 
WHERE created_at < :lastSeen 
ORDER BY created_at DESC 
LIMIT 10;
Здесь СУБД использует индекс для быстрого перехода к нужной позиции. Время выполнения такого запроса остается стабильным независимо от «глубины» пагинации. Фактически сложность такого запроса будет O(1) относительно размера уже просмотренных данных. Кроме того, этот подход устойчив к изменениям данных и не дает дубликатов. Тем не менее, “Keyset пагинация” обладает ограничениями. Нельзя просто перейти на произвольную страницу (например, «страница 50»), так как у нас нет фиксированного смещения. Также требуется стабильный и уникальный порядок сортировки. Обычно это комбинация created_at и id. “Offset пагинация” допустима для небольших таблиц или административных интерфейсов, но для высоконагруженных систем почти всегда выбирают подход основанный на курсоре, где “Keyset пагинация” один из них. ⬇️ Как часто тебе приходится реализовывать пагинацию и каким алгоритмом? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой. Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

⚠️Что делать разработчику в текущих реалиях рынка. Все мы видим, что происходит последнее время на рынке. На 1 вакансию разработчика - тысячи отликов за несколько дней. Наступило время, когда рынок стал рынком работодателя. А жто значит, что старые правила больше не работают. Раньше было достаточно: 🔹 знать стек 🔹 писать код 🔹 иметь 2–3 года опыта И можно было спокойно жить и : 🔹 менять работу 🔹 расти стабильно по зарплате 🔹 получать офферы Сейчас этого мало. И многие это уже почувствовали: — отклики есть → офферов нет — интервью проходят → финал не дают — опыт есть → роста нет Рынок стал значительно жёстче. Компании перестали нанимать “на вырост”. Им нужны люди, которые: — сразу приносят пользу — понимают систему — могут принимать решения ⚡️Многие продолжают делать то же самое. Учат новые технологии не покладая рук, проходят курсы “по фреймворкам”, решают задачки чтобы блеснуть умом на собесдеовании. Но это почти не влияет на результат. Потому что проблема не в знаниях. Проблема в уровне мышления. Сейчас работаделям нужен не “ещё один разработчик”, а инженер, который: 🔹 понимает архитектуру 🔹 умеет декомпозировать систему 🔹 видит последствия решений 🔹 может объяснить, почему сделал именно так ❓Что, собственно, предлагаете делать в такой ситуации? 1️⃣ Перестать думать “что изучить” И начать думать:
“какие задачи я умею решать?”
Это ключевой сдвиг. 2️⃣ Выйти из уровня задач → в уровень системы Не:
— “как написать этот сервис”
А: — зачем он нужен — как он взаимодействует с другими — где он сломается — как он масштабируется 3️⃣ Начать активно лезть в архитектуру Даже если страшно и “пока не мой уровень”. Именно там рост. 4️⃣ Искать сложность, а не комфорт Если ты постоянно делаешь знакомые задачи и не сталкиваешься с неопределённостью - это стагнация. 5️⃣ Прокачивать объяснение На интервью решает не только то, что ты знаешь. А то, как ты это доносишь: — структура — логика — аргументация 6️⃣ Получать реальный опыт Это самый важный пункт. Пока у тебя нет: — командной разработки — архитектурных решений — сложных систем — ты будешь уступать тем, у кого это есть. 📌 По итогу Можно ещё год: — “готовиться” — “подучивать” — “разбираться” И остаться там же. А можно за 3–6 месяцев: — поменять уровень задач — прокачать мышление — получить опыт И выйти на другой уровень рынка. Поймите, рынок не стал хуже. Он стал честнее. Теперь недостаточно “просто писать код”. Нужно: понимать, объяснять и принимать решения. И именно это сейчас определяет: — офферы — зарплату — скорость роста Если вы чувствуете, что упёрлись — это не потолок. Это просто сигнал, что нужно менять уровень игры. Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

Почему знание AtomicInteger не делает тебя сильнее Ты можешь знать: • как работает CAS • что такое атомарность • как устроен multithreading Но на интервью спросят:
“Как ты обеспечишь консистентность данных между сервисами?”
И это уже не про Java, а про архитектуру. Именно здесь большинство “сильных разработчиков” начинают плавать. 🗓 На курсе по микросервисной архитектуре мы как раз разбираем: 🔹 как работает консистентность в распределённых системах 🔹 какие паттерны реально применяются 🔹 как принимать архитектурные решения Старт: 6 апреля Осталось 4 места 👉 Подробности и запись: https://iprody.com/microservices-java 💬 Задать вопрос / забронировать место: https://t.me/iprody_online

☕️ AtomicInteger — как работает атомарность без блокировок? На собеседованиях по Java этот вопрос встречается регулярно. И не зря — он отлично проверяет понимание многопоточности. AtomicInteger обеспечивает атомарные операции с помощью CAS (Compare-And-Swap) без использования synchronized и явных блокировок. ➖ Как это работает внутри? В основе лежит низкоуровневая инструкция процессора CAS (Compare-And-Swap). Она делает три шага за одну атомарную операцию: 🔹Сравнивает текущее значение с ожидаемым 🔹Если совпадает, заменяет на новое 🔹Если нет, ничего не делает

int prev, next;
do {
    prev = atomicInt.get();
    next = prev + 1;
} while (!atomicInt.compareAndSet(prev, next));
return next;
Если другой поток изменил значение между get() и CAS — операция просто повторится. ➖ Почему это без блокировок? В таком алгоритме отсутствует Mutex. Потоки не блокируются, а вместо этого они повторяют попытку. Это называется lock-free подход. В итоге, достигается высокая производительность при низкой/средней конкуренции, меньше контекстных переключений, хорошо масштабируется. Однако, при высокой конкуренции возможны частые повторы и есть вероятность возникновения проблемы ABA. 📌 Вывод AtomicInteger использует CAS — атомарную инструкцию процессора, которая позволяет обновлять значение без блокировок. Если операция не удалась из-за гонки (Race Condition), она повторяется в цикле. ⬇️ А тебе часто задают вопросы о многопоточности на собеседованиях? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой. Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

🔥 Осталось 5 из 15 мест на Курс по проектированию и разработке микросервисной архитектуры 2.0 Старт — 6 апреля. Места разбирают очень быстро. Подробнее про курс в посте Новый курс про то, как: — проектировать систему вокруг бизнес-домена (DDD) — строить Event-Driven архитектуру — управлять распределёнными транзакциями (Saga / Outbox) — обеспечивать гарантии доставки сообщений — проектировать отказоустойчивость (Circuit Breaker, Retry, Bulkhead) — строить масштабируемую архитектуру, готовую к росту нагрузки 🎯 Кому особенно актуально Если вы: 🔹 действующий разработчик 🔹 работаете с монолитом или простыми сервисами 🔹 чувствуете пробелы в архитектуре 🔹 хотите перейти на уровень Middle+ / Senior 🔹 готовитесь к сильным компаниям — этот курс даст системность. ⬇️ Что будет на выходе — понимание распределённой архитектуры — практическая реализация паттернов — опыт проектирования, а не просто кодинга ⌛️ Осталось 5 мест из 15 Группа изначально формируется небольшой, чтобы сохранить качество архитектурных разборов и обратной связи. Старт — уже 6 апреля. 👉 Подробности и запись: https://iprody.com/microservices-java 💬 Задать вопрос / забронировать место: https://t.me/iprody_online

📚Testcontainers или как правильно тестировать интеграцию с базой данных Привет, всем! Макс вещает 😼 Одна из частых проблем на любых проектах это тестирование интеграции с базой данных. Обычно встречаются два подхода: использование мокирования (Mock) или In-memory БД (H2 и др.). Оба варианта имеют серьезные ограничения и недостатки. При мокировании, тест не проверяет реальный SQL-запрос, не выявляет ошибки в запросах, не отражает поведение БД, и др. важные детали. При использовании In-memory БД, элементарно выбранная вами СУБД может отличаться от той, которую вы реально будет использовать в продакшене. Например, для тестов вы используете H2, для среды развертывания PostgreSQL. Это может оказать влияние на работу с индексами, может проявиться различие в SQL-диалектах, и само собой – неожиданные ошибки в проде. Это все можно описать одним выражением: Вы эмулируете инфраструктуру, а не запускаете ее. 🔹Решением может быть Testcontainers Testcontainers позволяет запускать реальные сервисы в Docker прямо во время тестов. Вместо H2 вы используете настоящую базу данных, которая полностью соответствует вашей инфраструктуре. Пример с PostgreSQL:

@Testcontainers
@SpringBootTest
class UserRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:18");

    @Autowired
    UserRepository userRepository;

    @Test
    void shouldSaveUser() {
        User user = new User("test@example.com");
        userRepository.save(user);

        assertTrue(userRepository.findByEmail("test@example.com")
.isPresent());
    }
}
🔹Что происходит под капотом? Перед выполнением тестовых сценариев будет запущен Docker, затем PostgreSQL container и только потом Spring-приложение подключается к нему. После выполнения теста контейнер удаляется. В данном случае, каждый запуск будет выполняться с чистой базой, что обеспечивает контролируемое управление данными и доступ к ним. 🔹Почему это правильно? Testcontainers дает реальное поведение базы, выполняется настоящий SQL-запрос, индексы и ограничения соответствуют ожиданиям согласно спецификации СУБД, вы с легкостью можете воспроизвести состояние происходящее на продакшене. Сами тесты по прежнему находятся в изоляции, запускаются в чистом окружении, не зависят от других тестов. CI/CD становится намного проще. Вам не нужно заниматься инфраструктурой и поднимать БД вручную. Все поднимается автоматически, в изолированной среде. Тесты выполняются одинаково локально и во время CI (сборка, контроль качества и др.) Testcontainers не зависит от миграций, они будут автоматически учтены, если вы используете Liquibase или Flyway. 📌 Takeaway Testcontainers позволяет организовать тестирование так, будто система реально работает с базой данных. Это позволяет приблизить выполняемые тестовые сценарии к таким условиям, которые бы были применимы к реальной среде развертывания – прод. Особенно полезным Testcontainers становится в комбинации с @SpringBootTest, WireMock (или MockServer). В таком случае, интеграционное тесты удается сдвинуть максимально близко к реальным условиям, что позволяет доставлять софт с минимальным количеством дефектов. Именно поэтому Testcontainers стал стандартом для production-ready тестирования. Макс Добрынин Software Engineer ⬇️ Поделишься своим опытом тестирования ПО? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой. Курс по Kubernetes | Курс по разработке и проектированию микросервисной архитектуры | Групповая практика по разработке микросервисной архитектуры | Java Roadmap | Задать вопрос менеджеру | Отзывы

☕️ Почему ForkJoinPool быстрее обычных потоков и когда его применять Когда речь заходит о многопоточности в Java, многие дума
☕️ Почему ForkJoinPool быстрее обычных потоков и когда его применять Когда речь заходит о многопоточности в Java, многие думают о классических потоках (Thread, ExecutorService). Но в задачах с большим количеством мелких операций гораздо эффективнее работает ForkJoinPool. Для того, чтобы разобраться почему это так, сперва необходимо понять в чем проблема обычных потоков. Типичный пример можно представить следующим образом:

ExecutorService pool = Executors.newFixedThreadPool(4);
Проблемы выражаются в том, что: задачи распределяются статически, один поток может быть перегружен, а другой — простаивать, отсутствует балансировка нагрузки. В результате возможности ЦПУ используется неэффективно. ➖ Как работает ForkJoinPool? ForkJoinPool реализует модель Divide → Execute → Merge. Другими словами: задача разбивается на подзадачи (fork), подзадачи выполняются параллельно, затем результаты объединяются (join). Вычисление суммы всех значений очень длинного массива:

class SumTask extends RecursiveTask<Integer> {

    private final int[] arr;
    private final int start, end;

    protected Integer compute() {
        if (end - start <= 10) {
            int sum = 0;
            for (int i = start; i < end; i++) {
                sum += arr[i];
            }
            return sum;
        }

        int mid = (start + end) / 2;

        SumTask left = new SumTask(arr, start, mid);
        SumTask right = new SumTask(arr, mid, end);

        left.fork();
        int rightResult = right.compute();
        int leftResult = left.join();

        return leftResult + rightResult;
    }
}
Главная причина скорости — алгоритм Work Stealing, который ForkJoinPool использует. Так, у каждого потока есть своя очередь задач, поток берет задачи из своей очереди, а если очередь пуста — "ворует" задачи у других потоков. В отличие от обычного пула, такой алгоритм быстрее, поскольку: нет простаивающих потоков, нагрузка автоматически балансируется, уменьшается конкуренция за ресурсы (contention). В итоге, ЦПУ используется максимально эффективно. ➖ Когда стоит использовать, а когда нет ForkJoinPool отлично подходит для CPU-bound задач, где требуется разбиение комплексной задачи на множество легковесных подзадач, которые впоследствии должны соединиться в конечный вычисляемый результат. Например: обработка больших массивов данных или рекурсивные алгоритмы. Однако, ForkJoinPool плохо подходит для I/O операций (HTTP, DB), блокирующих вызовов или долго выполняющихся задач. Это связано с тем, что поток, который блокируется, не может украсть задачи, и в итоге эффективность падает. 📌 Вывод ForkJoinPool быстрее обычных потоков за счет возможности распределения задач и минимизации простоя потоков задачи у которых закончились. Это позволяет эффективно использовать выделенное ЦПУ. Важно помнить, что не потоки делают код быстрым — а то, как задачи распределяются между ними. Именно это ForkJoinPool делает лучше всего. ⬇️ Какие вопросы по многопоточности встречаются тебе на собеседованиях? Пиши о своем опыте в комментариях. 👍 Понравился этот пост? Подписывайся, ставь лайк и поделись постом с другом или коллегой.

🤚 Освойте Kubernetes за 1,5 месяца Объявляем старт набора нового курса по K8S С 2024 года Kubernetes стал де-факто стандарто
🤚 Освойте Kubernetes за 1,5 месяца Объявляем старт набора нового курса по K8S С 2024 года Kubernetes стал де-факто стандартом оркестрации контейнеров. А зарплаты специалистов со знанием Kubernetes в среднем на 18% выше по рынку. Мы сделали курс, который проведёт тебя от Docker до продакшн-кластера: — Мини-группы до 20 человек — Живые занятия 2 раза в неделю с обратной связью от преподавателя — Практика на каждом уроке — Наставник уровня Senior — Полный доступ к записям 12 занятий для полного освоения Kubernetes 1. Введение в контейнеризацию 2. Основы Kubernetes 3. Рабочие нагрузки (Pod, ReplicaSet, Deployment) 4. Сетевые ресурсы (Service, ClusterIP/NodePort/LoadBalancer, DNS) 5. Балансировка HTTP-запросов при помощи Ingress Смотреть полную программу По результатам курса ты: - освоишь Kubernetes - разработаешь проект для портфолио - получишь опыт CI/CD, мониторинга и логирования - получишь сертификат о прохождении курса и рекомендации по дальнейшему развитию _______________ 👉🏻 Старт группы - 20 апреля Место разлетаются очень быстро, успей занять свое 😉 Подробная информация на сайте - iprody.com/courses-kubernetes Задать вопрос/забронировать место - написать менеджеру