Java Backend / Tech Lead Interview Cheatsheet
2026
Темы: Collections, Concurrency, GC, SQL/PostgreSQL, Hibernate/JPA, Spring/Spring Boot, Kafka, Kubernetes
Формат: основной вопрос → ответ. Где уместно — блок ↺ Уточняющий вопрос с возможным продолжением интервьюера и ответом.
1. Collections
HashMap internals
Массив бакетов. Индекс бакета = hash(key) & (n-1). При коллизии — связный список, который при длине ≥8 и размере таблицы ≥64 превращается в красно-чёрное дерево (O(log n) вместо O(n)). При заполнении выше load factor (0.75) — resize: удвоение массива и рехеширование.
↺ Зачем дерево с Java 8?
Длинная цепочка вырождала поиск в O(n), что эксплуатировалось в hash-collision DoS-атаках. Дерево ограничивает худший случай до O(log n).
↺ Почему именно степень двойки как размер?
Чтобы вычислять бакет дешёвой битовой операциейhash & (n-1)вместо деления по модулю. Дополнительно Java перемешивает старшие биты хэша (h ^ (h >>> 16)), чтобы они влияли на индекс.
HashMap vs LinkedHashMap vs TreeMap
HashMap— без порядка, O(1) средн.LinkedHashMap— порядок вставки (или доступа — для LRU-кэша), за счёт двусвязного списка поверх.TreeMap— отсортирован по ключу (Comparable/Comparator), O(log n), красно-чёрное дерево.
equals и hashCode
Нарушение контракта (равные объекты — разные хэши) → объект «теряется»: положили в один бакет, ищем в другом. Правило: переопределяешь equals — обязан переопределить hashCode.
↺ Что будет, если hashCode всегда возвращает константу?
Всё попадёт в один бакет → HashMap выродится в список (или дерево) → O(n)/O(log n) вместо O(1). Работать будет, но медленно.
ArrayList vs LinkedList
ArrayList — массив, O(1) доступ по индексу, вставка/удаление в середину O(n). LinkedList — O(1) вставка по известному узлу, O(n) доступ по индексу. На практике ArrayList почти всегда быстрее (локальность кэша); LinkedList редко оправдан.
ConcurrentHashMap vs synchronizedMap vs Hashtable
synchronizedMap/Hashtable блокируют всю структуру на каждую операцию. ConcurrentHashMap блокирует только нужный бакет (Java 8+: CAS + synchronized на узле), чтение почти без блокировок. Не допускает null ключей/значений.
↺ Почему ConcurrentHashMap запрещает null?
В конкурентной средеget()==nullнеоднозначно: «нет ключа» или «значение null»? Без блокировки это не различить черезcontainsKey. Запрет убирает неоднозначность.
fail-fast vs fail-safe итераторы
Fail-fast (ArrayList, HashMap) — бросают ConcurrentModificationException при изменении во время итерации (через modCount). Fail-safe (CopyOnWriteArrayList, ConcurrentHashMap) — итерируют по снимку / без исключения.
CopyOnWriteArrayList
Много чтений, мало записей (список слушателей). Каждая запись копирует весь массив — дорого, зато чтение без блокировок.
2. Concurrency
happens-before
Гарантия JMM: если A happens-before B, результаты A видны B. Устанавливается через: synchronized (выход из монитора → вход), volatile (запись → чтение), Thread.start(), Thread.join(), операции с Lock, инициализация final-полей в конструкторе.
volatile vs synchronized
volatile — только видимость одиночного поля, без атомарности составных операций (i++ не потокобезопасен). synchronized — и видимость, и взаимное исключение блока. Для счётчика — AtomicInteger или synchronized, не volatile.
↺ Почему i++ не атомарен даже с volatile?
i++— три операции: read, increment, write. volatile гарантирует видимость каждой, но не атомарность их связки. Между read и write другой поток может вклиниться.
ReentrantLock vs synchronized
ReentrantLock гибче: tryLock() (с таймаутом), lockInterruptibly(), честная очередь, несколько Condition. Требует ручного unlock() в finally. synchronized проще и достаточен в большинстве случаев.
CAS и ABA-проблема
Compare-And-Swap — атомарно «если значение == ожидаемому, замени». Основа атомиков, без блокировок. ABA: значение менялось A→B→A, CAS не заметит подмену. Решение — AtomicStampedReference (счётчик версий).
ExecutorService и типы пулов
fixedThreadPool— фиксированное число потоков.cachedThreadPool— создаёт по надобности (опасно: неограниченный рост).singleThreadExecutor— один поток, последовательность.scheduledThreadPool— отложенные/периодические задачи.
В проде — явный ThreadPoolExecutor с ограниченной очередью и понятным RejectedExecutionHandler.
↺ Что произойдёт при переполнении очереди пула?
СработаетRejectedExecutionHandler. Политики:AbortPolicy(default, бросает исключение),CallerRunsPolicy(задачу выполнит вызывающий поток — естественный backpressure),DiscardPolicy,DiscardOldestPolicy.
Future vs CompletableFuture
Future — блокирующий get(), без колбэков и композиции. CompletableFuture — цепочки (thenApply, thenCompose), комбинирование (allOf/anyOf), обработка ошибок (exceptionally), свой executor.
Virtual Threads (Java 21)
Лёгкие потоки под управлением JVM, не привязаны 1:1 к OS-потокам. Можно миллионы. При блокирующем I/O не держат OS-поток. Идеальны для I/O-bound сервисов. Не дают выигрыша для CPU-bound.
↺ Что такое pinning?
Если virtual thread входит вsynchronized-блок или нативный вызов, он «пиннится» к carrier-потоку и не может отцепиться при блокировке — теряется преимущество. Рекомендация: заменятьsynchronizedнаReentrantLockв горячем коде (в новых версиях JDK pinning на synchronized частично устранён).
deadlock / livelock / starvation
- Deadlock — взаимное ожидание ресурсов. Профилактика: единый порядок захвата локов,
tryLockс таймаутом. - Livelock — потоки активны, но прогресса нет (уступают друг другу).
- Starvation — поток не получает ресурс из-за приоритетов. Решение: fair lock.
ThreadLocal и риск утечки
Хранит данные per-thread (контекст транзакции, SecurityContext). В пуле потоков поток не умирает → значение остаётся → утечка памяти и протечка данных между запросами. Всегда remove() в finally.
CountDownLatch vs CyclicBarrier vs Semaphore
CountDownLatch— ждать завершения N событий (одноразовый).CyclicBarrier— N потоков ждут друг друга в точке (переиспользуемый).Semaphore— ограничение числа одновременных доступов (пул из N разрешений).
3. Garbage Collection
Memory layout
Heap (Young = Eden + 2 Survivor, Old Gen), Metaspace (метаданные классов, нативная память), Stack (фреймы, per-thread), Code Cache (JIT).
Generational GC
Гипотеза: большинство объектов умирают молодыми. Новые — в Eden. Minor GC чистит Young (быстро), выжившие мигрируют между Survivor и в Old Gen. Major/Full GC чистит Old (дорого, длинные паузы).
Stop-the-World
Пауза, когда все потоки приложения остановлены для работы GC. Длительность пауз — главная метрика выбора сборщика.
G1 vs ZGC vs Shenandoah
- G1 (default с Java 9) — регионы, паузы ~10–200 мс, предсказуем. Общий случай.
- ZGC — паузы <1 мс, конкурентный, для больших heap (десятки/сотни ГБ), low-latency.
- Shenandoah — сверхнизкие паузы, конкурентная компакция. Близок к ZGC.
Для low-latency сервиса под нагрузкой — аргумент за ZGC/Shenandoah; для типового сервиса хватает G1.
GC roots и определение мусора
Достижимость от GC roots (стек потоков, статические поля, JNI-ссылки). Недостижимый объект — мусор. Циклические ссылки между мусором собираются (это не reference counting).
OutOfMemoryError
Java heap space (нехватка/утечка), Metaspace (слишком много классов), GC overhead limit exceeded (GC >98% времени). Диагностика: -XX:+HeapDumpOnOutOfMemoryError, анализ дампа (MAT/VisualVM), GC-логи, поиск растущих коллекций / незакрытых ресурсов / ThreadLocal.
Reference types
- Strong — обычная, не собирается пока достижима.
- Soft — собирается при нехватке памяти (кэши).
- Weak — собирается при ближайшем GC (
WeakHashMap). - Phantom — пост-финализационная очистка ресурсов.
JVM flags for highload
-Xms = -Xmx (без ресайза heap), выбор сборщика (-XX:+UseZGC), -XX:MaxGCPauseMillis (цель для G1), heap dump on OOM.
↺ Особенность JVM в контейнере (Kubernetes)?
Старые JVM не видели cgroup-лимиты контейнера и брали память хоста → OOMKill. Решение:-XX:MaxRAMPercentageвместо фиксированного-Xmx(современные JDK по умолчанию учитывают лимиты контейнера). limit памяти пода должен быть выше heap (есть ещё Metaspace, стеки, off-heap).
4. SQL / PostgreSQL
Index types
- B-tree (default) —
=,<,>,BETWEEN,LIKE 'prefix%', сортировка. - Hash — только
=. - GIN — массивы, JSONB, полнотекст.
- GiST — геоданные, диапазоны.
- BRIN — огромные таблицы с естественным порядком (по дате).
Index not used?
Функция над колонкой (LOWER(name)=... — нужен функциональный индекс), LIKE '%suffix', неявное приведение типов, низкая селективность (Seq Scan дешевле), устаревшая статистика (нужен ANALYZE).
EXPLAIN ANALYZE
Seq Scan где ждёшь Index Scan; расхождение rows (оценка) и actual rows (факт) → плохая статистика; actual time; loops (узел в цикле — признак N+1 на уровне БД); тяжёлые Sort/Hash Join с уходом на диск.
Isolation levels
| Level | Dirty | Non-repeatable | Phantom |
|---|---|---|---|
| READ UNCOMMITTED | yes* | yes | yes |
| READ COMMITTED | no | yes | yes |
| REPEATABLE READ | no | no | yes* |
| SERIALIZABLE | no | no | no |
PostgreSQL default — READ COMMITTED.
↺ Особенность PostgreSQL?
READ UNCOMMITTED работает как READ COMMITTED (грязного чтения нет вообще). REPEATABLE READ через snapshot isolation блокирует и phantom. SERIALIZABLE использует SSI (Serializable Snapshot Isolation) и может откатывать транзакции сserialization failure— приложение должно уметь ретраить.
MVCC
Multi-Version Concurrency Control: каждая транзакция видит снимок данных на её начало. Читатели не блокируют писателей и наоборот. Старые версии (dead tuples) удаляет VACUUM/autovacuum.
↺ Чем грозит отсутствие/отставание VACUUM?
Раздувание таблиц (table bloat), деградация производительности, в пределе — wraparound transaction ID (катастрофа). Autovacuum должен успевать; под высокой нагрузкой его тюнят.
Optimistic vs Pessimistic locking
Пессимистичная — SELECT ... FOR UPDATE, лочим строку сразу (высокая вероятность конфликта). Оптимистичная — version-колонка, проверка при сохранении, конфликт → ретрай (низкая вероятность, лучше масштабируется).
Window functions
Агрегаты без схлопывания строк: ROW_NUMBER(), RANK(), LAG/LEAD, SUM() OVER (PARTITION BY ...). Пример — нумерация заявок в рамках клиента, скользящие суммы.
Read scaling
Read-replica (стриминговая репликация), отчёты/аналитика на реплику. Помнить про репликационный лаг (eventual consistency).
5. Hibernate / JPA
N+1 problem
Загрузили N сущностей → на каждую связь отдельный запрос → 1 + N запросов. Решения: JOIN FETCH в JPQL, @EntityGraph, @BatchSize (пачками через IN), DTO-проекция. Обнаружение — лог SQL / Hibernate statistics / p6spy.
LAZY vs EAGER
LAZY — связь грузится при первом обращении (default для коллекций). EAGER — сразу (default для @ManyToOne/@OneToOne). Правило: всё LAZY, грузить осознанно через fetch. EAGER порождает N+1 и тянет лишнее.
LazyInitializationException
Обращение к LAZY-связи после закрытия сессии/транзакции (в контроллере). Лечение: грузить нужное в транзакции (JOIN FETCH/@EntityGraph), маппить в DTO внутри транзакции.
↺ Почему OpenSessionInView — антипаттерн?
Держит сессию открытой на весь HTTP-запрос (включая рендеринг) → маскирует N+1, удерживает соединение из пула дольше нужного, ленивые запросы летят неконтролируемо. Лучше явно грузить данные в сервисном слое.
Entity lifecycle
Transient (новый, не в контексте) → Managed/Persistent (в persistence context, изменения трекаются) → Detached (контекст закрыт) → Removed. merge возвращает detached в managed (создаёт копию!), persist сохраняет transient.
Persistence Context и dirty checking
Кэш первого уровня в рамках транзакции. Hibernate трекает managed-сущности и при flush сам генерирует UPDATE для изменённых полей — без явного save(). First-level cache: повторный find по тому же ID не идёт в БД.
flush vs commit
flush — синхронизация изменений в БД (SQL отправлен), транзакция ещё открыта (можно откатить). commit — фиксация (внутри сначала flush).
Batch save
Накопление сущностей в context → рост памяти, медленный flush. Решение: периодический flush() + clear(), hibernate.jdbc.batch_size, упорядочивание insert/update.
Optimistic locking in JPA
@Version-поле. Hibernate добавляет WHERE version = ? в UPDATE; 0 обновлённых строк → OptimisticLockException. Для конкурентного редактирования — естественный выбор.
Hibernate vs native SQL / Spring Data JDBC
Hibernate хорош для CRUD по графу объектов. Для сложной аналитики, оконных функций, тяжёлых отчётов — нативный запрос или отдельный read-слой (CQRS). Trade-off JPA vs Spring Data JDBC: последний проще и предсказуемее по SQL, без ленивой загрузки и магии прокси.
6. Spring / Spring Boot
IoC и DI
IoC — управление созданием/жизненным циклом объектов передано контейнеру. DI — способ реализации: зависимости передаются извне, а не создаются объектом.
Injection types
- Constructor (recommended) —
final, валидное состояние, тестируемость, видны циклы. - Field — антипаттерн: нет
final, скрыты зависимости, сложно тестировать. - Setter — для опциональных.
При одном конструкторе @Autowired не нужен.
↺ Как Spring разрешает несколько кандидатов одного типа?
@Primary(по умолчанию),@Qualifier("name")(явно), или по имени параметра. ИначеNoUniqueBeanDefinitionException.
Bean lifecycle
1. Инстанцирование → 2. DI → 3. *Aware → 4. postProcessBeforeInitialization → 5. @PostConstruct/afterPropertiesSet → 6. postProcessAfterInitialization (здесь оборачиваются прокси — AOP, @Transactional) → 7. готов → 8. @PreDestroy/destroy.
Bean scopes
singleton (default), prototype, request/session (web).
↺ Что будет, если инжектить prototype в singleton?
Prototype создастся один раз (при создании singleton) и переиспользуется — обычно не то, что хотели. Решения:ObjectProvider,@Lookup,Provider<T>.
Spring Boot autoconfiguration
@SpringBootApplication → @EnableAutoConfiguration. Сканирует classpath, через ...AutoConfiguration.imports подключает конфиги по условиям @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty. Convention over configuration: объявил свой бин → автоконфиг отступает.
↺ Как переопределить/отладить?
Свой бин побеждает авто; можно исключить черезexclude. Запуск с--debugдаёт отчёт о сработавших/несработавших условиях.
Configuration hierarchy
Аргументы CLI → env-переменные → application-{profile}.yml → application.yml → дефолты. Relaxed binding: SPRING_DATASOURCE_PASSWORD (env) → spring.datasource.password — именно так инжектятся секреты из k8s Secret.
Spring AOP и прокси
Spring оборачивает бин прокси, перехватывающим вызовы (транзакции, кэш, лог). JDK Dynamic Proxy (есть интерфейс) или CGLIB (наследование, default в Boot).
↺ Почему @Transactional не работает при self-invocation?
Вызовthis.method()изнутри того же бина идёт напрямую, минуя прокси → аспект не срабатывает. То же для@Cacheable,@Async. Также не работает наprivate/final-методах. Решения: вынести в отдельный бин, self-ссылка,AopContext.currentProxy().
@Transactional internals
Прокси открывает транзакцию, коммитит при успехе, откатывает при unchecked-исключении.
↺ Что откатывает транзакцию по умолчанию?
ТолькоRuntimeExceptionиError. Checked-исключения НЕ откатывают — частый баг. Переопределяется:@Transactional(rollbackFor = Exception.class).
Propagation: REQUIRED (default), REQUIRES_NEW (новая, текущая приостановлена — аудит/лог, переживающий откат), NESTED (savepoint), MANDATORY/NEVER/SUPPORTS/NOT_SUPPORTED.
Web layer
DispatcherServlet — front controller: HandlerMapping → контроллер → интерсепторы → HttpMessageConverter (JSON через Jackson). @RestController = @Controller + @ResponseBody. Централизованная обработка ошибок — @RestControllerAdvice + @ExceptionHandler. Валидация — @Valid + Bean Validation.
↺ Фильтр vs интерсептор?
Filter — уровень сервлет-контейнера, до Spring MVC (аутентификация, CORS). Interceptor — внутри MVC, видит handler. Spring Security работает через цепочку фильтров.
Testing
@SpringBootTest — весь контекст (интеграционные, медленно). Срезы быстрее: @WebMvcTest (web + MockMvc), @DataJpaTest (JPA + тестовая БД), @JsonTest. @MockBean/@MockitoBean — подмена бина моком. Testcontainers — реальная PostgreSQL/Kafka в Docker для интеграционных (ближе к проду, чем H2).
Spring MVC vs WebFlux
MVC — thread-per-request, блокирующий, прост, хорош с JPA. WebFlux — реактивный event-loop, неблокирующий (Mono/Flux), меньше потоков при массе I/O.
↺ Когда WebFlux оправдан и при чём тут virtual threads?
WebFlux — много медленных I/O-вызовов, высокая конкурентность при ограниченных потоках; минус — сложность, нужен R2DBC (JPA блокирует). С Java 21 virtual threads закрывают исходную мотивацию WebFlux: блокирующий простой код без платы за поток на запрос. Senior-поинт: для нового сервиса с блокирующими зависимостями — MVC на virtual threads вместо реактивщины.
Actuator
Эндпоинты эксплуатации: /health (liveness/readiness → k8s-пробы), /metrics (Micrometer → Prometheus → Grafana), /loggers (смена уровня на лету). Безопасность: закрывать /env, /heapdump от внешнего доступа.
7. Apache Kafka
Topic, Partition, Broker, Offset
Топик — категория сообщений, разбит на партиции. Партиция — упорядоченный неизменяемый лог на одном брокере (+реплики), единица параллелизма. Брокер — сервер кластера. Offset — порядковый номер сообщения в партиции; consumer хранит свой offset в __consumer_offsets.
Consumer groups
Партиции распределяются между consumer'ами группы: каждую партицию читает ровно один consumer в группе. Consumer'ов больше партиций → лишние простаивают. Механизм горизонтального масштабирования.
Message ordering
Гарантирован только внутри партиции. Связанные сообщения → одинаковый partition key → одна партиция. Глобального порядка по топику нет.
↺ Что сломается при увеличении числа партиций?
hash(key) % partitionsизменится → сообщения с одним ключом могут попасть в разные партиции → порядок нарушится. Партиции закладывают с запасом и не меняют на лету.
Delivery guarantees
- At-most-once — commit до обработки, можно потерять, без дублей.
- At-least-once — commit после обработки (default), не теряется, возможны дубли.
- Exactly-once (EOS) —
enable.idempotence=true+ транзакции + идемпотентный consumer. «Из коробки» только внутри Kafka (read-process-write).
↺ Как бороться с дублями на стороне consumer?
Идемпотентная обработка: дедуп по идемпотентному ключу (ID сообщения) в БД, проверка перед выполнением. Настоящий exactly-once при записи во внешние системы недостижим без идемпотентности приёмника.
Replication и ISR
У партиции лидер + followers. Чтение/запись через лидера, followers догоняют. ISR (In-Sync Replicas) — реплики, не отстающие от лидера. При падении лидера новый из ISR.
acks: 0 (не ждём, быстро/рискованно), 1 (ждём лидера, баланс), all (ждём все ISR, надёжно). В связке с min.insync.replicas гарантирует запись на N реплик.
↺ Как не потерять сообщения?
Producer:acks=all,retries>0,enable.idempotence=true. Брокер:min.insync.replicas=2при RF=3. Consumer: commit только после успешной обработки.
Consumer lag
Отставание consumer'а от последнего сообщения. Решения: добавить consumer'ов (≤ числа партиций), ускорить обработку (батчинг/параллелизм), увеличить max.poll.records, вынести тяжёлую логику.
Retention vs compaction
Retention — удаление по времени/размеру (топик как поток событий). Log compaction — хранит последнее значение по ключу (топик как «текущее состояние», напр. актуальный профиль).
Rebalancing
Перераспределение партиций при изменении состава группы. Во время — обработка останавливается (stop-the-world). Смягчают: session.timeout.ms, heartbeat.interval.ms, cooperative rebalancing.
DLQ (Dead Letter Queue)
Сообщения, не обработанные после N ретраев, — в отдельный топик, чтобы не блокировать остальные и разобрать позже.
Kafka in Spring Boot
application.yml
spring:
kafka:
bootstrap-servers: localhost:9092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
acks: all
properties:
enable.idempotence: true
consumer:
group-id: subsidy-service
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
enable-auto-commit: false
properties:
spring.json.trusted.packages: "*"
listener:
ack-mode: manual
Зависимость spring-kafka. Автоконфигурация поднимает KafkaTemplate и фабрики.
Producer
@Service
public class SubsidyEventProducer {
private final KafkaTemplate<String, SubsidyEvent> kafkaTemplate;
public SubsidyEventProducer(KafkaTemplate<String, SubsidyEvent> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void send(SubsidyEvent event) {
// key = applicationId — all events for one application go to same partition (ordering)
kafkaTemplate.send("subsidy-events", event.applicationId(), event);
}
}
Consumer
@Component
public class SubsidyEventConsumer {
@KafkaListener(topics = "subsidy-events", groupId = "subsidy-service")
public void listen(SubsidyEvent event, Acknowledgment ack) {
try {
process(event); // should be idempotent
ack.acknowledge(); // commit offset ONLY after successful processing
} catch (Exception e) {
throw e; // offset not committed — will be re-read
}
}
}
Key points:
enable-auto-commit: false+ ручнойacknowledge()= at-least-once.@KafkaListener(concurrency = "3")— 3 потока-consumer'а, но не больше числа партиций.- Дубли при at-least-once неизбежны → идемпотентная обработка (дедуп по ключу в БД).
JsonSerializer/JsonDeserializerдля типизированных DTO;spring.json.trusted.packages— доверие пакетам.
8. Kubernetes
Pod, Deployment, Service, Ingress
- Pod — минимальная единица, контейнер(ы) с общей сетью/хранилищем. Эфемерен.
- Deployment — управляет репликами через ReplicaSet: число, обновление, откат.
- Service — стабильная точка доступа (IP/DNS) к меняющимся подам, балансировка.
- Ingress — маршрутизация внешнего HTTP(S) по хостам/путям, TLS-терминация.
↺ Deployment vs StatefulSet?
Deployment — stateless, поды взаимозаменяемы. StatefulSet — stateful (БД, Kafka): стабильные имена, упорядоченный старт/стоп, привязка к своему хранилищу.
Probes: liveness vs readiness vs startup
- liveness — жив ли контейнер. Провал → перезапуск пода.
- readiness — готов ли принимать трафик. Провал → убирается из балансировки, не перезапускается.
- startup — для медленного старта; блокирует liveness/readiness до успеха.
↺ Чем грозит неправильная настройка?
Агрессивный liveness → бесконечные перезапуски под нагрузкой. Нет readiness → трафик на непрогретый под → ошибки при деплое. liveness вместо readiness для зависимостей (БД недоступна) → каскадные перезапуски вместо вывода из балансировки.
Requests vs Limits
Request — гарантированный минимум, для планирования. Limit — потолок: CPU → throttling, память → OOMKill. Рекомендация: requests = типичное потребление, limits — с запасом, без перерасхода памяти.
↺ QoS-классы?
Guaranteed (requests=limits), Burstable (requests<limits), BestEffort (ничего). При нехватке на ноде первыми вытесняются BestEffort, затем Burstable.
HPA
Horizontal Pod Autoscaler меняет число реплик по метрикам (CPU/память/кастомные — RPS, Kafka lag). Для кастомных нужен metrics adapter (Prometheus Adapter). VPA — вертикально; с HPA по CPU/памяти не сочетают (конфликт).
Rolling Update vs Recreate
Rolling (default) — постепенно, без простоя (maxSurge, maxUnavailable). Recreate — гасим все, потом поднимаем (простой, но нет двух версий — важно при несовместимой схеме БД).
↺ Blue-green vs canary?
Blue-green — две полные среды, переключение разом (быстрый откат, двойные ресурсы). Canary — новая версия на малую долю трафика, постепенно (ловит проблемы на части пользователей).
Zero-downtime deploy: readiness + graceful shutdown — SIGTERM → вывод из Service → дочитать запросы (preStop, terminationGracePeriodSeconds) → завершение.
ConfigMap vs Secret
ConfigMap — несекретные конфиги. Secret — чувствительные данные, в base64 (НЕ шифрование). Реальная защита: encryption at rest для etcd + внешнее хранилище (Vault).
PersistentVolume vs PersistentVolumeClaim
PV — ресурс хранилища в кластере. PVC — запрос пода на хранилище нужного размера/класса. Связывание часто динамическое через StorageClass.
Service discovery
Внутренний DNS (CoreDNS): service.namespace.svc.cluster.local. Типы: ClusterIP (внутри кластера, default), NodePort, LoadBalancer (внешний балансировщик облака), Headless (без балансировки, для StatefulSet).
PodDisruptionBudget
Минимум доступных подов при добровольных нарушениях (обновление/drain нод). Защита от случайного падения всех реплик сразу.
ArgoCD — GitOps CD
Инструмент GitOps continuous delivery. Git — единственный источник истины о желаемом состоянии кластера. Манифесты/Helm/Kustomize лежат в Git, ArgoCD непрерывно сравнивает их с live-состоянием.
- Видит drift (desired vs live) → применяет (auto-sync) или по кнопке.
- Ручное изменение в кластере → обнаружит и (при self-heal) откатит к Git.
Что даёт: декларативность и аудит (история в Git), откат = git revert, нет ручного kubectl apply в прод, визуализация дерева ресурсов (Synced/OutOfSync, Healthy/Degraded).
↺ ArgoCD vs Jenkins?
Jenkins — CI, push-модель (пайплайн пушит в кластер). ArgoCD — CD, pull-модель (кластер подтягивает из Git). Связка: CI собирает образ и обновляет тег в Git-манифесте → ArgoCD деплоит.
Vault через External Secrets Operator (ESO)
Проблема: секреты нельзя в Git открыто, k8s Secret — лишь base64. Решение: секреты в Vault, в кластер подтягиваются автоматически. ESO синхронизирует секреты из Vault в нативные k8s Secret; в Git — только ссылка (путь в Vault), не значение.
SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: subsidy
spec:
provider:
vault:
server: "https://vault.internal:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "subsidy-role"
serviceAccountRef:
name: "subsidy-sa"
ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: subsidy-db-secret
namespace: subsidy
spec:
refreshInterval: "1h"
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: subsidy-db-credentials
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD
remoteRef:
key: subsidy/database
property: password
- secretKey: DB_USERNAME
remoteRef:
key: subsidy/database
property: username
ESO периодически читает Vault → создаёт/обновляет k8s Secret. Изменился секрет в Vault → обновится k8s Secret.
Inject into pod env
spec:
template:
spec:
serviceAccountName: subsidy-sa
containers:
- name: app
image: registry.internal/subsidy:1.2.0
env:
- name: SPRING_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: subsidy-db-credentials
key: DB_USERNAME
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: subsidy-db-credentials
key: DB_PASSWORD
Spring Boot подхватит SPRING_DATASOURCE_* через relaxed binding.
Full flow: Vault → ESO authenticates via ServiceAccount → reads path → creates k8s Secret → pod mounts as env → Spring Boot reads env. In Git — only SecretStore/ExternalSecret/Deployment, no passwords → GitOps/ArgoCD compatible.
↺ ESO vs Vault Agent Injector?
ESO материализует секрет в k8s Secret (прозрачно для приложения — обычный env; но секрет существует в etcd). Vault Agent Injector (sidecar) монтирует секрет прямо в файл пода, минуя k8s Secret (не материализуется в etcd; приложение читает из файла). Выбор по требованиям ИБ.
Secrets in env — security considerations
Env — наименее безопасный из распространённых способов:
- Видны любому процессу в контейнере:
env,cat /proc/<pid>/environ— без root, достаточноkubectl exec. - Наследуются всеми дочерними процессами целиком.
- Утекают в креш-дампы, стектрейсы, логи, APM-агенты.
kubectl describe podпоказывает env (literal value светит насквозь;secretKeyRefмаскирует значение).- Статичны: ротация требует рестарта пода.
More secure (ascending):
- Files instead of env (volume mount / Vault Agent Injector в
tmpfs) — не наследуются дочерними, не светят в env/дампах, права файла, ротация без рестарта. - Runtime fetch from Vault — секрет только в памяти приложения, нигде не материализуется.
- Dynamic secrets — Vault выдаёт короткоживущие креды к БД с TTL; утекли → протухли за минуты.
Bottom line: env + k8s Secret — приемлемый baseline, но для финтеха правильнее двигаться к файловому монтированию и/или динамическим секретам, плюс обязательно encryption at rest для etcd и жёсткий RBAC на kubectl exec.
9. Liquibase
What is Liquibase?
Инструмент версионирования схемы БД: изменения описываются как упорядоченные changeset'ы в changelog'е и применяются автоматически при деплое. Решает проблему «у всех разные версии схемы»: схема меняется только через миграции, история — в служебной таблице DATABASECHANGELOG.
Changelog structure
databaseChangeLog— корневой (мастер) файл, обычно подключает остальные черезinclude/includeAll.changeSet— атомарная миграция. Идентифицируется связкой id + author + путь к файлу.- Форматы: XML, YAML, JSON и formatted SQL.
databaseChangeLog:
- changeSet:
id: 001-create-application
author: dev
changes:
- createTable:
tableName: application
columns:
- column: { name: id, type: bigint, autoIncrement: true,
constraints: { primaryKey: true } }
- column: { name: status, type: varchar(32),
constraints: { nullable: false } }
rollback:
- dropTable: { tableName: application }
Tracking applied changesets
Таблица DATABASECHANGELOG: id, author, путь, checksum, дата применения. При старте сравнивает changelog с таблицей: новые changeset'ы применяет, применённые проверяет по checksum.
↺ Что будет, если изменить уже применённый changeset?
Ошибка checksum mismatch — changeset'ы неизменяемы. Правильно: новый changeset с исправлением. Обходные пути (осознанно):validCheckSumв changeset илиclearCheckSums— но это исключение, не практика.
Multiple nodes (Kubernetes)
Таблица-лок DATABASECHANGELOGLOCK: первая нода захватывает лок и применяет миграции, остальные ждут.
↺ Под упал во время миграции — что с локом?
Лок может остаться висеть → следующие старты ждут бесконечно. Лечение:liquibase release-locksили ручнойUPDATE DATABASECHANGELOGLOCK SET LOCKED=false. Best practice для k8s — выносить миграции из старта приложения в init-контейнер или отдельный Job, чтобы реплики приложения не гонялись за локом и rolling update не зависал.
Rollback
Откат changeset'а. Для простых изменений (createTable) генерируется автоматически, для сложных (изменение данных, dropColumn) — пишется вручную в блоке rollback. Команды: rollback-count N, rollback <tag>, rollback-to-date. Главное преимущество над бесплатным Flyway, где undo-миграции платные.
Preconditions и contexts
- preConditions — проверки перед применением (например,
tableExists,sqlCheck). Поведение при провале:HALT,WARN,MARK_RAN,CONTINUE. - contexts / labels — фильтрация changeset'ов по окружению: тестовые данные с
context="test"не поедут в прод (liquibase --contexts=prod).
Liquibase vs Flyway
| Flyway | Liquibase | |
|---|---|---|
| Format | SQL files V1__name.sql | XML/YAML/JSON/SQL changelog |
| Identification | Version in filename | id + author + checksum |
| Rollback | Paid | Built-in |
| Conditional logic | No (Java migrations as workaround) | Preconditions, contexts, labels |
| Diff/generate | No | diff, generate-changelog |
| Learning curve | Lower (just SQL) | Higher, but more flexible |
Spring Boot integration: зависимость liquibase-core + spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.yaml. Миграции применяются при старте контекста — так же, как Flyway.
10. SOAP
SOAP message structure
Протокол обмена структурированными XML-сообщениями. Сообщение — Envelope, внутри:
- Header (опционально) — метаданные: WS-Security-токены, маршрутизация, transaction id.
- Body — полезная нагрузка (запрос/ответ операции).
- Fault (внутри Body при ошибке) — стандартизированная ошибка: faultcode, faultstring, detail.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>...</soap:Header>
<soap:Body>
<getSubsidyStatus xmlns="http://example.com/subsidy">
<applicationId>12345</applicationId>
</getSubsidyStatus>
</soap:Body>
</soap:Envelope>
WSDL
Web Services Description Language — XML-контракт сервиса: какие операции есть, какие сообщения они принимают/возвращают, типы данных (XSD-схема), binding (как кодировать, обычно SOAP over HTTP) и endpoint (адрес). По WSDL генерируется клиентский код.
↺ Contract-first vs code-first?
Contract-first: сначала WSDL/XSD, из него генерируются классы (wsimport, jaxb-плагины). Code-first: пишем Java, WSDL генерируется из аннотаций. Для интеграций с внешними системами (госорганы, банки) — всегда contract-first: контракт диктует владелец сервиса.
Calling SOAP from Java/Spring
Два основных пути:
- JAX-WS — стандарт Java:
wsimportгенерирует прокси-классы из WSDL, вызов как обычного Java-метода. - Spring WS —
WebServiceTemplate(аналог RestTemplate): JAXB-маршалинг объектов в XML, перехватчики для WS-Security и логирования.
// Spring WS client
public class SubsidyClient extends WebServiceGatewaySupport {
public GetStatusResponse getStatus(long id) {
GetStatusRequest request = new GetStatusRequest();
request.setApplicationId(id);
return (GetStatusResponse) getWebServiceTemplate()
.marshalSendAndReceive("https://gov.example.com/subsidy", request);
}
}
SOAP vs REST
| SOAP | REST | |
|---|---|---|
| Style | Operations (RPC) | Resources (HTTP semantics) |
| Format | XML only | Usually JSON |
| Contract | WSDL, mandatory, strict | OpenAPI, optional |
| Security | WS-Security (message-level sign/encrypt) | TLS + OAuth2/JWT (transport-level) |
| Transport | Any (HTTP, JMS) | HTTP |
| Where alive | Gov systems, banks, EDI, legacy | Modern APIs |
↺ Что такое WS-Security? Чем отличается от TLS?
TLS защищает канал (точка-точка, расшифровывается на каждом промежуточном узле). WS-Security защищает само сообщение: XML-подпись и шифрование частей Envelope, токены в Header. Сообщение остаётся защищённым при передаче через посредников (брокеры, шины) — end-to-end.
11. gRPC
What is gRPC?
RPC-фреймворк от Google: вызовы удалённых методов поверх HTTP/2 с бинарной сериализацией Protocol Buffers. Contract-first: API описывается в .proto, из него генерируется код клиента и сервера для любого языка.
Отличия от REST: бинарный компактный формат (быстрее JSON), HTTP/2 (мультиплексирование запросов в одном соединении, server push), строгая типизация контракта, стриминг из коробки, deadlines/cancellation как часть протокола.
syntax = "proto3";
service SubsidyService {
rpc GetStatus (StatusRequest) returns (StatusResponse);
rpc WatchStatus (StatusRequest) returns (stream StatusResponse); // server streaming
}
message StatusRequest {
int64 application_id = 1;
}
message StatusResponse {
string status = 1;
string updated_at = 2;
}
Four call types
- Unary — запрос → ответ (аналог REST).
- Server streaming — запрос → поток ответов (подписка на изменение статуса).
- Client streaming — поток запросов → один ответ (загрузка чанков файла).
- Bidirectional streaming — двусторонний поток (чат, realtime-синхронизация).
Protocol Buffers
Бинарный формат сериализации со схемой. Поля кодируются по номерам (= 1, = 2), а не по именам → компактность; нет парсинга текста → скорость. Схема обеспечивает типобезопасность и кодогенерацию.
↺ Как обеспечивается обратная совместимость в proto?
Правила эволюции: не менять номера существующих полей, не переиспользовать номера удалённых (помечатьreserved), новые поля — только с новыми номерами и опциональной семантикой. Старый клиент просто игнорирует незнакомые поля.
gRPC drawbacks
- Не human-readable: отладка через
grpcurl/рефлексию, не curl. - Браузеры не поддерживают нативно (нужен grpc-web + прокси) → наружу для фронта обычно REST.
- Балансировка сложнее: HTTP/2 держит одно долгое соединение → L4-балансировщик отправит все вызовы в один под. Нужна L7-балансировка (Envoy, Istio, headless service + client-side LB).
↺ Как балансировать gRPC в Kubernetes?
Обычный ClusterIP Service балансирует соединения, а не вызовы — gRPC-клиент откроет одно HTTP/2-соединение и все запросы уйдут в один под. Решения: headless service + client-side load balancing (round_robinв gRPC-клиенте), либо L7-прокси / service mesh (Envoy, Istio, Linkerd), который балансирует на уровне HTTP/2-стримов.
gRPC in architecture
Синхронное межсервисное взаимодействие внутри периметра, где важна низкая задержка и строгий контракт: расчёт графика платежей, проверка лимитов, валидация заявки. Наружу (фронт, партнёры) — REST через API Gateway.
↺ gRPC vs Kafka — когда что?
Это не конкуренты, а разные паттерны. gRPC — синхронный запрос-ответ, когда вызывающему нужен результат сейчас (проверить лимит до подтверждения заявки). Kafka — асинхронные события: развязка сервисов, гарантии доставки, переживание недоступности потребителя.
Deadlines и errors
- Deadline — клиент задаёт максимальное время на вызов, передаётся по всей цепочке вызовов (deadline propagation) → каскадные таймауты согласованы из коробки.
- Ошибки — стандартные status codes:
NOT_FOUND,DEADLINE_EXCEEDED,UNAVAILABLE,RESOURCE_EXHAUSTED+ метаданные.UNAVAILABLE— типовой кандидат на ретрай. - Interceptors — аналог фильтров: аутентификация, логирование, метрики, трейсинг.
Quick reminders
- Достижения: ситуация → проблема → действие → результат.
- Не знаешь — «не сталкивался, но предположу логику...» и рассуждай вслух.
- Архитектурные вопросы — сначала уточни контекст и ограничения, потом отвечай.
- Свои вопросы интервьюеру — минимум 3.