Ссылка на видео:
Список вопросов
- Java vs Kotlin
- Stream API в Java
- Опыт работы с инфраструктурой
- Процесс разработки на проекте (описание опыта)
- Что такое CI/CD?
- Организация нагрузочного тестирования
- Потокобезопасные вычисления
- Обработка данных под нагрузкой
- Подсчёт уникальных пользователей
- Консенсус в распределённой системе (RAFT)
- Gossip-протокол (Discovery)
- Как UUID обеспечивает почти уникальные значения
- Strong consistency vs eventual consistency
- Использование Keycloak в корпоративных системах
Ответы на вопросы
1. Java vs Kotlin
Java – это классический, строго типизированный объектно-ориентированный язык программирования, являющийся стандартом в индустрии более двух десятилетий. Он используется в миллионах корпоративных приложений по всему миру и обладает мощной экосистемой: Spring, Jakarta EE, Quarkus, Kafka, Hadoop, Flink, и др.
Kotlin – язык, разработанный JetBrains в 2011 году, официально поддерживаемый Google для Android-разработки с 2017 года. Совмещает строгую типизацию с лаконичностью и функциональностью, имеет полную бинарную совместимость с Java и компилируется в байт-код JVM.
Подробное сравнение
| Категория | Java | Kotlin |
|---|---|---|
| Синтаксис | Многословный, verbose | Компактный, минимизирует boilerplate |
| Null-безопасность | Нет встроенной защиты от NPE | Статическая система типов учитывает nullable |
| Функциональный стиль | Поддержка через Stream API, но ограниченная | Lambda, let, run, apply, with, also — повсеместно |
| DSL | Ограничена | Позволяет строить внутренние DSL (например, HTML builders) |
| Data-классы | Требуется вручную писать equals, hashCode, toString | data class с автогенерацией всех этих методов |
| Корутины | Только с появлением виртуальных потоков (Java 21+) | Встроенная поддержка корутин и асинхронного кода |
| Extension-функции | Нет | Есть, позволяют расширять классы, не модифицируя их |
| Преобразования типов | Неявные касты невозможны | Есть smart-casts и безопасные приведения типа |
| Компиляция в JS/Native | Требует сторонних решений | Поддерживается Kotlin Multiplatform |
Поддержка в инфраструктуре
| Поддержка | Java | Kotlin |
|---|---|---|
| Spring Boot | Изначально для Java | Полностью совместим, есть Kotlin DSL |
| Android | Поддержка, но не приоритетная | Является основным языком Android-разработки |
| Gradle | Изначально с Groovy/Java DSL | Поддерживает Kotlin DSL как альтернативу |
| Инструменты анализа кода | SpotBugs, Checkstyle, PMD | Detekt, ktlint |
Особенности Kotlin, которых нет в Java
- Smart-casts – компилятор сам определяет тип переменной после проверки:
if (x is String) {
println(x.length) // x - уже String
}
- Default/Named параметры:
fun greet(name: String = "World") = println("Hello, $name")
greet() // Hello, World
greet(name = "Alice")
- Sealed классы и when-выражения:
Позволяют элегантно реализовывать паттерн “сумма типов” (Algebraic Data Types). - Inline классы, value-классы (Kotlin 1.5+) – для zero-cost обёрток.
- Reified generics: работают только в inline-функциях и позволяют использовать
T::classбез хаков.
Почему компании выбирают Kotlin
- Быстрое прототипирование
- Снижение количества багов (null safety + immutability)
- Отличная интеграция с Java-кодом (можно мигрировать поэтапно)
- Более высокий уровень выразительности, что важно при разработке SDK или API
Подводные камни Kotlin
- Больше “магии”, сложнее отлаживать для новичков
- Компиляция медленнее (особенно с kapt)
- Сложнее подобрать команды с опытом Kotlin (в сравнении с Java)
- Потенциальная несовместимость на границах ABI между версиями Kotlin
Когда использовать Java
- Корпоративные проекты с чётким контролем качества и строгими SLA
- Проекты с высоким уровнем легаси
- Требования на совместимость с существующими Java-only библиотеками
- Команды, где большинство разработчиков – Java-инженеры
Когда использовать Kotlin
- Android (официальный язык)
- Создание внутренних DSL (например, для тестов, конфигурации)
- Проекты с уклоном в функциональный стиль
- Команды, нацеленные на высокую скорость разработки и modern stack
Резюме для собеседования
Kotlin предлагает более выразительный и безопасный синтаксис, особенно для современных, реактивных или Android-проектов. Java остаётся стандартом для крупных, надёжных систем, благодаря своей зрелости и огромной экосистеме. В продакшене часто комбинируют оба языка, используя Kotlin для новых модулей, сохраняя Java в ядре.
2. Stream API в Java
Stream API – это часть функционального программирования в Java, впервые представленная в Java 8. Она позволяет работать с коллекциями данных декларативно, с возможностью ленивой, параллельной и конвейерной обработки.
Основная идея
Stream – это не структура данных, а последовательность элементов, над которой можно выполнять ленивые операции трансформации и терминальные операции.
Он не изменяет исходную коллекцию и обеспечивает неразрушаемый и иммутабельный подход к работе с данными.
Жизненный цикл Stream
1. Источник данных – коллекция, массив, файл, генератор:
Stream<String> s = list.stream();
2. Промежуточные операции (lazy):
map(),filter(),sorted(),limit(),distinct()
3. Терминальные операции (eager):
collect(),forEach(),reduce(),count()
Пример на практике
List<User> users = ...
List<String> activeUsernames = users.stream()
.filter(User::isActive) // оставить только активных
.map(User::getUsername) // извлечь имя
.distinct() // убрать дубликаты
.sorted() // отсортировать
.collect(Collectors.toList()); // собрать в List
Параллельные стримы
users.parallelStream()
.map(this::enrichUser)
.forEach(this::saveToDb);
Используются ForkJoinPool.commonPool() – один на все потоки JVM. Подход работает хорошо на CPU-bound задачах, но может перегрузить пул и привести к локам, если внутри используются блокирующие вызовы (например, I/O, JDBC).
Важно: никогда не использовать .parallelStream() внутри веб-контроллеров без настройки кастомного пула.
Комбинирование с Collectors
toList(),toSet()toMap()joining(", ")groupingBy()partitioningBy()
Map<String, List<User>> usersByCity = users.stream()
.collect(Collectors.groupingBy(User::getCity));
Reduce – агрегирование
int totalAge = users.stream()
.map(User::getAge)
.reduce(0, Integer::sum);
Optional<User> oldest = users.stream()
.max(Comparator.comparing(User::getAge));
Lazy evaluation
Операции выполняются только при вызове терминальной операции:
Stream<String> names = users.stream()
.map(u -> {
System.out.println("Mapping " + u);
return u.getName();
}); // <-- ничего не происходит
names.collect(Collectors.toList()); // вот здесь запускается
Производительность
- Streams быстрее
for-циклов на больших коллекциях и при pipeline-обработке - Медленнее на маленьких коллекциях из-за накладных расходов
- Параллельные потоки работают эффективно, если операции CPU-bound и не зависят друг от друга
Ограничения и подводные камни
- Streams нельзя переиспользовать (однократные):
Stream<String> s = list.stream();
s.forEach(System.out::println); // ok
s.forEach(System.out::println); // IllegalStateException
- Побочные эффекты в
map/filterвредны:
.map(user -> { log.info(user); return user; }) // плохо
- Итерация с индексом – неудобна:
- Используйте IntStream:
IntStream.range(0, list.size()) .mapToObj(i -> list.get(i))
Где уместен Stream API
- Чтение, фильтрация и агрегация данных
- Очистка и нормализация коллекций
- Преобразование данных в DTO
- Аналитика (подсчёт, группировка)
Где неуместен
- Сложные алгоритмы (DFS, BFS, динамическое программирование)
- Императивный код с шагами отладки
- Код с тяжёлой логикой внутри map/filter (нарушение SRP)
Краткое резюме для собеседования
Stream API – это декларативный подход к обработке данных, основанный на ленивых операциях и pipeline-архитектуре. Он обеспечивает чистый, иммутабельный и функциональный стиль, особенно удобный для фильтрации, агрегации и трансформации коллекций. Однако требует аккуратности при работе с побочными эффектами, параллельностью и повторным использованием стримов.
3. Опыт работы с инфраструктурой
Современный backend-инженер не ограничивается написанием бизнес-логики. Он должен понимать, как код живёт в продакшене, как разворачивается, масштабируется, мониторится и отлаживается. Это требует владения стеком DevOps-инструментов и хорошего понимания production-инфраструктуры.
Контейнеризация: Docker
- Создание и оптимизация Dockerfile:
- Использование
multi-stage builds - Снижение размера образов (например, переход на
distroless,alpine) - Управление кэшированием слоёв (
COPY+.dockerignore) - Практика:
- Образы Spring Boot с
RUN ./gradlew build -x test && COPY build/libs/app.jar - Уменьшение attack surface: non-root user, ограниченные permissions
Оркестрация: Kubernetes / OpenShift
- Деплой:
- Написание манифестов
Deployment,Service,Ingress,ConfigMap,Secret - Helm charts и Kustomize для переиспользуемости
- Продвинутое:
- Horizontal Pod Autoscaler (HPA)
- Liveness / Readiness probes
- StatefulSets, Init Containers, VolumeClaimTemplates
- В OpenShift: Route, SCC, service account bindings, templates
CI/CD
- Сценарии:
- Промежуточные этапы (
build,lint,unit,integration,deploy) - Шаги публикации в Docker Registry (Harbor, GitHub Container Registry)
- Инструменты:
- GitLab CI:
.gitlab-ci.yml, шаблоны, stages - Jenkins: pipelines с declarative DSL + shared libraries
- GitHub Actions: matrix job builds, environment protection rules
Пример .gitlab-ci.yml:
build:
script:
- ./gradlew clean build -x test
- docker build -t registry/app:$CI_COMMIT_SHA .
test:
script:
- ./gradlew test
Мониторинг: Prometheus + Grafana
- Метрики приложений:
- Micrometer (
/actuator/prometheus) - JVM: GC, heap, threads, HTTP status codes, DB pool stats
- Настройка Alertmanager:
- Rules на P99 latency, high CPU, Kafka lag
- Дашборды:
- Создание и экспорт собственных шаблонов
- Использование Grafana variables и templating
- Опыт:
- Интеграция c Kafka-exporter, PostgreSQL-exporter, Node Exporter
Логирование: ELK, Loki, structured logs
- Формат логов:
- JSON (через Logback encoder или LogstashLayout)
- Использование MDC для
request_id,user_id,trace_id - Сбор:
- Fluent Bit / Fluentd -> Elasticsearch
- Promtail -> Loki
- Опыт:
- Объединение логов из Spring Boot, Nginx и Kafka consumer в одну панель
Kafka
- Архитектура:
- Topics, partitions, consumer groups
- Exactly-once semantics (EOS), transactional producer
- Практика:
- Kafka Streams joins, state stores
- DLT (Dead Letter Topics), retry механизмы
- Использование Schema Registry и Avro/JSON Schema
- Мониторинг:
- Kafka-exporter, Burrow, Cruise Control
Базы данных
PostgreSQL:
- Индексация: B-tree, GIN/GIN, partial indexes
- Анализ планов:
EXPLAIN (ANALYZE, BUFFERS) - Partitioning (range, hash), оптимизация таблиц на 1+ млрд записей
- Работа с
pg_stat_activity,pg_stat_statements,auto_explain - Валидация JSONB,
jsonpath,->>vs#>>, индексы на jsonb-поля - Использование materialized views, логической репликации
Redis:
- Использование TTL, eviction policies
- Redis Streams, HyperLogLog, Lua-скрипты
- RedisJSON + RediSearch (в Redis Stack)
- Обнаружение проблем: slowlog, latency monitor, memory usage
Cassandra:
- Вставка через
QUORUM,LOCAL_ONE - Проектирование схем под query-first подход
- Оптимизация по read path и write amplification
- Понимание LSM tree, compaction, tombstones
Secrets & Configurations
- Kubernetes
Secret,ConfigMap, Volume mounts - HashiCorp Vault:
- KV Store, AppRole authentication
- Dynamic secrets для DB/Cloud
- Spring Cloud Config / Consul / Etcd
- Практика: автоматическая ротация сертификатов, защита от утечек
Примеры проблем и решений из реального опыта:
- Проблема: Kafka lag рос на одной партиции -> пересоздание topic с балансировкой по userId
- Проблема: Memory leak в проде -> добавление экспорта heap dump +
-XX:+HeapDumpOnOutOfMemoryError - Проблема: Невалидируемые конфиги в Helm -> внедрение CI-проверок через
helm lintиkubeval - Проблема: Спайки RPS -> внедрение Circuit Breaker + горизонтального autoscaler
Резюме для собеседования
У меня hands-on опыт с построением CI/CD пайплайнов, деплоем в Kubernetes, мониторингом с Prometheus и логированием в Loki. Я умею выявлять узкие места по Kafka lag, CPU, GC или PostgreSQL-индексам, и устранять их через профилирование и конфигурационные оптимизации. Активно использую DevOps-инструменты для обеспечения производительности, безопасности и прозрачности production-среды.
5. Что такое CI/CD?
CI – Continuous Integration
Автоматизация сборки, тестирования и проверки качества кода при каждом коммите.
CD – Continuous Delivery/Deployment
Автоматическая доставка изменений в staging или production.
Этапы CI/CD:
- Checkout репозитория
- Сборка проекта
- Unit + интеграционные тесты
- Анализ покрытия и линтинг
- Сборка Docker-образа
- Push в registry
- Деплой с Helm/Kustomize
6. Организация нагрузочного тестирования
Цели нагрузочного тестирования
- Определение пропускной способности: сколько RPS/объёма данных система выдерживает до деградации.
- Поиск bottlenecks: база данных, блокировки, очереди, GC, HTTP-таймауты.
- Проверка отказоустойчивости: поведение при падении компонентов, нехватке ресурсов.
- Определение нужных ресурсов: сколько CPU/RAM потребуется в пиковый момент.
- Валидация SLAs/SLOs: например, “95% запросов за < 500ms”.
Виды нагрузочного тестирования
| Тип | Описание |
|---|---|
| Load | Постепенное увеличение до ожидаемой рабочей нагрузки |
| Stress | Увеличение до предела, чтобы понять, где система выходит из строя |
| Spike | Резкое увеличение (например, от 50 до 1000 RPS за секунду) |
| Soak | Постоянная нагрузка в течение длительного времени (например, 24 ч) |
| Breakpoint | Поиск точки, после которой latencies резко растут |
Инструменты
k6 (по умолчанию для CI)
- Скрипты на JS
- Интеграция с Grafana Cloud, Prometheus, InfluxDB
- CI-friendly, лёгкий, понятный синтаксис
- Отлично для проверки HTTP API
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
vus: 100,
duration: '30s',
thresholds: {
http_req_duration: ['p(95)<500'],
},
};
export default function () {
let res = http.get('https://myapi.com/resource');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}
Apache JMeter
- Legacy-инструмент
- GUI + CLI
- Подходит для SOAP/REST, JDBC, JMS
- Поддерживает сценарии с cookies, sessions, CSV datasets
Locust
- Python-based
- Хорошо масштабируется в Kubernetes
- Подходит для stateful-сценариев и сложной логики
Vegeta / wrk / hey
- CLI-инструменты для простых benchmark’ов
- Мгновенный запуск, хороши для spike/load-тестов
Важные метрики
| Метрика | Описание |
|---|---|
| RPS (req/sec) | Сколько запросов в секунду обрабатывает API |
| Latency (P50/P95/P99) | Медиана, 95-й и 99-й процентиль задержек |
| Error rate | Доля HTTP-ошибок (4xx, 5xx, timeout’ы) |
| GC pauses | Длительность и частота GC |
| CPU/Memory usage | Использование ресурсов на уровне Pod/Node |
| Throughput | Количество обрабатываемых байт/объектов в сек |
| Connection errors | Проблемы с DNS, TLS, socket’ами |
Практика организации
Подготовка окружения:
- Тестировать в staging с production-данными
- Выделенные метрики/дашборды (Grafana, Prometheus, Jaeger)
- Изоляция нагрузочного стенда (чтобы не затронуть клиентов)
Построение сценариев:
- CRUD для API
- Профиль пользователя (10% admin, 90% client)
- Распределение RPS по endpoint’ам
Тестирование на основе профиля трафика:
- Пиковое время суток
- Чередование PUT/GET/DELETE
- OAuth2 или JWT-аутентификация
CI/CD интеграция:
- Пороговые условия (
p95 < 500ms,error rate < 1%) - Публикация отчётов в Allure, GitLab Pages или Slack
Визуализация результатов
- Grafana с Prometheus:
- RPS по endpoint’ам
- Временные ряды latency
- Количество 5xx ошибок
- k6 Cloud: интерактивные графики
- JMeter + InfluxDB + Grafana
Типичные узкие места
| Компонент | Проблема | Решение |
|---|---|---|
| PostgreSQL | Blocked queries, SeqScan, locks | Индексы, partitioning, analyze, vacuum |
| Kafka | Lag, rebalance, slow consumer | Увеличить max.poll.records, автоскейлинг |
| JVM | Long GC, thread starvation | Tuning heap, G1/Parallel GC, virtual threads |
| Redis | Evictions, memory overflow | TTL, eviction policy, key redesign |
| Network/Ingress | TLS latency, slow TLS handshake | ALB tuning, keep-alive headers |
Частые ошибки
- Нагрузка на тестовом сервере != на боевом (разное железо, масштаб)
- Отсутствие warm-up-фазы
- Сравнение P95 без учёта GC
- Нет анализа ошибок (например, все 200 OK, но payload пустой)
- Использование
parallelStream()внутри Web API под нагрузкой
Резюме для собеседования
Я организую нагрузочное тестирование с помощью k6 и Grafana. Проектирую сценарии на основе боевого трафика, замеряю latency по P95 и P99, отслеживаю ошибки и показатели GC, анализирую поведение PostgreSQL и Kafka под давлением. Интегрирую performance-тесты в CI с автоматическим фейлом пайплайна при нарушении SLO.
7. Потокобезопасные вычисления
Проблема:
Race condition при параллельной работе потоков.
Решения:
AtomicInteger,LongAddersynchronized,ReentrantLockStampedLock– для тонкой настройки чтения/записи- ThreadLocal – для контекста
- ExecutorService – управление пулом
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
8. Обработка данных под нагрузкой
Обработка данных под высокой нагрузкой – критически важная часть backend-архитектуры. Система должна обеспечивать стабильную производительность, отказоустойчивость и предсказуемую деградацию, а не падение под нагрузкой. Это требует осознанных решений на всех слоях: от базы данных до шины сообщений.
Типовые проблемы
| Проблема | Примеры |
|---|---|
| Медленная БД | Долгие INSERT, SELECT, блокировки |
| Ограничение по IOPS/CPU | Поток данных превышает возможности сервиса |
| Рост latency под пиковыми RPS | Очереди растут, GC работает чаще, время отклика — выше |
| Неравномерный трафик | Вечерние пики, флешмобы, периодические шторма |
| Out-of-memory | Накопление данных в памяти без обработки |
Архитектурные подходы
1. Batch Insert / Update
- Множественные записи в одном запросе:
INSERT INTO users (id, name) VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie');
- Эффективнее, чем по одной операции (
nсетевых roundtrip > 1) - Используется с
JDBC batch,R2DBC batch,Jooq.batchStore()
Снижение нагрузки на БД в 5-10 раз
2. Асинхронная обработка
- Разделение
ingestиprocess - Использование очередей: Kafka, RabbitMQ, Redis Streams
- Выделенный worker pool, rate-limited
ExecutorService pool = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> process(msg), pool);
Гибкость, масштабирование, изоляция
3. Кэширование
- Использование Redis или локального кэша (Caffeine, Guava)
- Пример: список справочников, частые
SELECTпо user_id - Техника: Read-through cache, Cache-aside, Write-through
Снижение RPS на БД, миллисекундный доступ
4. Backpressure (давление назад)
- Reactive Streams:
onBackpressureBuffer,onBackpressureDrop - Kafka consumer:
pause()/resume()на partition level - WebFlux и
Project Reactorимеют встроенные операторы для контроля
someFlux
.onBackpressureBuffer(1000)
.flatMap(this::handle, 8); // max concurrency
Контроль напора данных, избежание OOM
5. Load shedding (аварийный сброс нагрузки)
- Падение запросов с приоритетом “низкий”:
- HTTP 429 Too Many Requests
- Circuit Breaker fallback
- Реализация через Bucket4J, Resilience4j, Istio
if (currentLoad > threshold) {
throw new TooManyRequestsException();
}
Грациозная деградация вместо падения
6. Распараллеливание
- Использование виртуальных потоков (Java 21):
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
list.forEach(item -> executor.submit(() -> process(item)));
}
- Деление задач по partition/key для одновременной обработки
- Разбиение batch-задач на
chunk‘и
Утилизация CPU, скорость обработки
7. Pre-aggregation и денормализация
- Вынос
count,sum,avgв отдельные таблицы/кэши - Использование materialized views
- Redis counters:
INCR,PFADD,ZINCRBY
Снижение нагрузки на аналитику и отчёты
8. Query оптимизация
- Индексы (partial, composite, covering)
- Разбиение таблиц (partitioning по tenant_id, time range)
- Избегать
SELECT *, неиндексированных фильтров
Стабильная производительность под большими объёмами
Практические техники
| Сценарий | Решение |
|---|---|
| 10k событий в Kafka каждую секунду | Batch-потребление, window-агрегация, Kafka Streams |
| Загрузка CSV в 1 млн строк | Чтение с BufferedReader, batch insert по 10k, progress-индикатор |
Частые дубли INSERT по ключу | UPSERT, Redis SETNX, PostgreSQL ON CONFLICT DO NOTHING |
| Микросервис не справляется с RPS >500 | Ограничение input rate, кэширование, вынесение части логики в Redis |
Метрики для мониторинга
- Lag очереди: Kafka consumer lag
- Обработка сообщений в сек.: throughput
- Time-to-process: latency обработки одной единицы
- Heap usage, GC pauses
- PostgreSQL:
pg_stat_activity,locks,slow queries
Резюме для собеседования
В системах под высокой нагрузкой я применяю асинхронную обработку, backpressure, кэширование и batch-операции. Использую Kafka + PostgreSQL + Redis как надёжную связку. Подхожу к обработке событий с учётом профиля нагрузки, проектируя отказоустойчивость и graceful degradation. Активно профилирую throughput и latency с помощью Prometheus, Grafana и логов.
9. Подсчёт уникальных пользователей
Подсчёт уникальных пользователей (UU, Unique Users) – важная задача для аналитики, A/B-тестов, расчёта MAU/DAU и построения метрик вовлечённости. Решение этой задачи должно быть масштабируемым, быстрым и ресурсно эффективным, особенно в распределённых и высоконагруженных системах.
Цели подсчёта UU:
- Получить точное или приближённое количество уникальных пользователей за период
- Не хранить всю “сырую” активность
- Уметь агрегировать по дням/неделям/часам
- Возможность дешёвого хранения и запроса
Подходы
1. Set в памяти (наивный способ)
Set<String> users = ConcurrentHashMap.newKeySet();
users.add(userId);
int count = users.size();
- Подходит только для single-node и малых объёмов данных
- Не масштабируется в распределённой среде
- Требует много RAM: 1 млн UUID ~ 100–200 МБ
Подходит для unit-тестов, эмуляции, демо-сценариев
2. Redis SET (точный учёт)
SADD active_users:2025-05-31 user123
SCARD active_users:2025-05-31
SADD– добавление уникального элементаSCARD– подсчёт кардинальности множества- Redis в этом случае работает как in-memory hash set
Особенности:
- Высокая точность
- Быстрые операции
O(1) - Потребляет много памяти при миллионах записей
Подходит для подсчёта в реальном времени по дням, с TTL
3. Redis HyperLogLog (приближённый учёт)
PFADD hll:users user123
PFCOUNT hll:users
- Структура Redis с фиксированным объёмом (~12 КБ)
- Погрешность около 0.81%
- Отлично работает на объёмах 10^5–10^8 уникальных ID
Отличие от SET:
SET– точный, дорогой по памятиHLL– приближённый, но эффективный
Идеален для дашбордов, realtime UI, дешёвого хранения
4. ClickHouse (точный/неточный подсчёт)
-- Точный:
SELECT uniqExact(user_id) FROM events WHERE event_date = today();
-- Приближённый, но быстрый:
SELECT uniq(user_id) FROM events WHERE event_date = today();
Методы:
| Функция | Описание | Погрешность |
|---|---|---|
uniqExact() | Точный, медленнее | 0% |
uniq() | Быстрый, основан на HyperLogLog | ~1–2% |
Подходит для аналитических задач, агрегированных отчётов
5. Kafka + Redis + PostgreSQL (event-based pipeline)
- Kafka topic
user-events - Kafka Streams агрегирует
user_idв Redis или PostgreSQL - Redis -> краткосрочный учёт
- PostgreSQL -> долговременное хранилище (с
ON CONFLICT DO NOTHINGдля uniqueness)
Подходит для high-volume потоков событий
Агрегация по временным окнам
- Redis HLL с ключами по дню:
PFADD hll:uu:2025-05-30 userId - ClickHouse с
toStartOfHour(toDateTime(ts)) - PostgreSQL с
date_trunc('hour', event_time)
Можно строить:
- DAU (Daily Active Users)
- WAU, MAU
- UU per feature, per segment
Частые ошибки
| Ошибка | Почему плохо | Как исправить |
|---|---|---|
| Сохраняются все user_id в список | OOM при росте объёма | Используйте Redis HLL |
Используется точный uniqExact при миллиарде | Замедление, рост CPU | Используйте uniq() или Pre-agg |
| Повторно считаются одни и те же ID | Нет TTL, нет windowing | Используйте Redis с TTL или ClickHouse |
| Нет deduplication на ingestion | Утечка уникальности | Фильтруйте на ingestion или stream level |
Пример реального применения
- В Kafka приходят события
UserAction(userId, timestamp) - Kafka Streams агрегирует по ключу
date + userIdи пишет: - В Redis SET для дня (для точного подсчёта по дню)
- В Redis HLL – для дешёвых UI-графиков
- Один раз в день записываем итог в PostgreSQL/ClickHouse
Резюме для собеседования
Я использую Redis HyperLogLog для недорогого подсчёта уникальных пользователей с допустимой погрешностью и Redis SET – если важна точность. В аналитике применяю ClickHouse с
uniq()илиuniqExact, в зависимости от требований. Архитектурно выстраиваю event-based pipeline: Kafka Streams -> Redis -> аналитическая БД.
10. Консенсус в распределённой системе (RAFT)
Что такое консенсус?
В распределённых системах узлы могут быть:
- недоступны (сбой, сеть),
- не синхронизированы по времени,
- в разных состояниях.
Консенсус – это достижение соглашения о значении данных между несколькими узлами, даже в случае сбоев. Это основа для:
- управления конфигурацией,
- распределённых логов,
- лидерства (leader election),
- обеспечения согласованности (consistency) при репликации.
RAFT: Replicated And Fault Tolerant
RAFT – популярный алгоритм консенсуса, предложенный в 2013 году как альтернатива более сложному Paxos. Он легче для понимания и реализации.
Ключевые принципы RAFT
| Компонент | Описание |
|---|---|
| Лидер (Leader) | Один узел принимает команды от клиентов и реплицирует их другим узлам |
| Последователи (Followers) | Узлы, получающие команды от лидера и подтверждающие их |
| Кандидаты (Candidates) | Узлы, претендующие на лидерство в случае таймаута |
| Термы (Terms) | Логические интервалы, каждый выбор лидера начинается с новой term |
| Log Replication | Все команды записываются в журнал и реплицируются |
| Commit Index | Команды считаются зафиксированными при подтверждении большинством узлов |
Жизненный цикл RAFT-узла
- Start as Follower
- Если нет сигналов от лидера – переходит в Candidate, запускает выборы
- Узлы голосуют, кто получит большинство (quorum -> N/2+1)
- Становится Leader
- Начинает репликацию команд (log entries) другим узлам
- Когда большинство подтвердили – команда считается зафиксированной
Log Replication в RAFT
Каждая команда (например, put(key, value)) записывается в log:
- Лидер добавляет запись в свой лог
- Рассылает AppendEntries всем последователям
- Если большинство подтвердили – фиксирует команду (
commitIndex) - После этого узлы применяют команду к своему состоянию (state machine)
Это гарантирует linearizability – поведение как у одного узла
Применение RAFT
| Система | Где используется RAFT |
|---|---|
| etcd | Управление конфигурацией и секретами в Kubernetes |
| Consul | Service discovery, KV store |
| Temporal | Workflow history и состояния |
| CockroachDB | Distributed SQL-репликация |
| dgraph | GraphDB с distributed consensus |
Отличие от Paxos
| Критерий | RAFT | Paxos |
|---|---|---|
| Понимание | Простой, разбит на этапы | Сложный, требует глубоких знаний |
| Поддержка лидерства | Встроено | Требует дополнительных расширений |
| Распространение | etcd, Consul, Temporal | ZooKeeper, Cassandra (частично) |
Подводные камни
| Ошибка | Почему критично |
|---|---|
| Смена лидера при задержке | Может привести к временной недоступности |
| Split-brain при потере quorum | Возможна потеря данных или двойная запись |
| Большой log, неактуальный follower | Репликация может быть медленной, блокирующей лидер |
| Без снапшотов log растёт бесконечно | Нужна периодическая фиксация состояния |
Метрики и наблюдаемость RAFT
- Current Term – номер текущего лидерского периода
- Leader ID – ID текущего лидера
- Commit Index – последний зафиксированный индекс
- Raft Log Size – объём журнала
- Followers Sync % – насколько последователи отстают
- Election Count – сколько раз происходила переизбрание лидера
Наблюдаются через Prometheus + Grafana или /_metrics эндпоинты (в etcd, Consul)
Резюме для собеседования
Я хорошо понимаю алгоритм RAFT: его механизмы выбора лидера, репликации и подтверждения через кворум. Использовал etcd и Consul как KV-хранилища и системы Discovery. Понимаю риски split-brain и обеспечиваю observability через метрики лидерства, задержек и размера RAFT-журнала. Знаю, как RAFT отличается от Paxos и когда его стоит применять.
11. Gossip-протокол (Discovery)
Gossip Protocol – это метод распространения информации в распределённой системе, вдохновлённый биологическим механизмом заражения: каждый узел “перешёптывается” с другими узлами, передавая им актуальное состояние.
Используется для:
- Распространения конфигурации
- Обнаружения доступных узлов (service discovery)
- Поддержания кластерной топологии
- Обновления статуса health (жив/не жив)
Основная идея
Каждый узел периодически выбирает случайного соседа и отправляет ему свой snapshot состояния (например, таблицу живых узлов, хэши состояний, временные метки).
Сосед объединяет информацию с текущим состоянием, и у обоих появляется чуть более “актуальная” картина мира. Через несколько раундов эта информация распространяется по всей системе.
Принцип работы
- Инициализация – у каждого узла есть свой
state map, например:{hostA: alive, hostB: suspect} - Раунд (gossip interval):
- Узел выбирает случайного соседа
- Отправляет ему свою карту состояния (digest)
- Получает в ответ более новую информацию
- Объединение состояния (merge):
- Выбирается самая “свежая” информация по версии/таймстемпу
Такой обмен продолжается в фоне. Он не требует согласования и центра.
Форматы сообщений (условно)
{
"node": "hostA",
"generation": 172934,
"heartbeat": 247,
"status": "ALIVE"
}
- Generation – уникальное значение при перезапуске узла
- Heartbeat – счётчик, монотонно растущий
- Status – ALIVE / SUSPECT / DEAD
Свойства Gossip-протокола
| Свойство | Описание |
|---|---|
| Децентрализация | Нет главного сервера — нет single point of failure |
| Сходимость | Через O(logN) шагов состояние сойдётся у всех |
| Фоновый и надёжный | Переносит потери пакетов, сбои узлов |
| Встраиваемость | Может быть реализован на уровне библиотеки или встроен в платформу |
Реальные реализации
| Система | Как используется Gossip |
|---|---|
| Cassandra | Узлы обмениваются live-status, токенами партиций и кластерным конфигом |
| Consul | Использует Gossip для обнаружения агентов, планирования конфигурации |
| Serf | Минималистичный инструмент на Go, используется в Nomad, Vault |
| ScyllaDB | Реализация Gossip в C++ с low-latency и частичной совместимостью с Cassandra |
Безопасность
- Gossip не имеет встроенной аутентификации
- Обычно защищается через IP allowlist, ACL или TLS-настройки на уровне транспорта
Возможные проблемы
| Проблема | Причина | Решение |
|---|---|---|
| Split-brain | Некоторые узлы считают других “мертвыми” | Настройка таймаутов, quorum-based ops |
| Травянистое сходжение (slow) | При сетевых задержках | Увеличение числа параллельных узлов обмена |
| Старое состояние “затирает” новое | Нет сравнения generation/heartbeat | Введение timestamp-based merge |
Метрики и наблюдаемость
gossip_received_messages_totalgossip_heartbeat_intervalsuspect_node_countgossip_convergence_time_secondscluster_view_size
В Grafana можно отследить задержки, количество SUSPECT-узлов и длительность сходимости.
Сравнение с RAFT
| Критерий | Gossip | RAFT |
|---|---|---|
| Тип | Эпидемический, soft-state | Жёсткий консенсус с логом |
| Сходимость | Постепенная, вероятностная | Детерминированная, quorum-based |
| Consistency | Eventual (нет консенсуса) | Strong (после commit) |
| Область применения | Обнаружение, статус, конфиги | Репликация данных, согласование команд |
Резюме для собеседования
Я хорошо знаком с принципами Gossip-протокола: использовал его косвенно через Consul и Cassandra. Понимаю принципы асимптотической сходимости, механизм heartbeat, роль generation при перезапуске узлов. Знаю, как настроить параметры convergence и troubleshoot-ить split-brain. Отличаю его от RAFT по степени согласованности и области применения.
12. Как UUID обеспечивает почти уникальные значения
UUID (Universally Unique Identifier) – это 128-битный идентификатор, широко используемый в распределённых системах, базах данных, брокерах сообщений и API. Основная задача UUID – быть уникальным без необходимости обращения к центральному генератору.
Формат UUID
UUID состоит из 128 бит (16 байт), чаще всего записывается в виде:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
Где:
M– версия UUID (v1, v4, v7 и т.д.)N– вариант (например, RFC4122)
Пример UUIDv4:
f47ac10b-58cc-4372-a567-0e02b2c3d479
Почему UUID почти уникален?
UUIDv4 (random-based)
- Содержит 122 случайных битов
- Возможное количество уникальных значений:
2^122 -> 5.3 -> 10^36
- Вероятность коллизии крайне мала – сравнима с шансом, что два человека случайно придумают одинаковый 36-значный пароль
Закон больших чисел и парадокс дней рождения:
- Даже при генерации 1 млрд UUIDv4 в секунду, вероятность коллизии за 100 лет практически нулевая
- Источник расчёта: RFC 4122, §6
UUID и коллизии на практике
| Сценарий | Риск коллизии | Рекомендация |
|---|---|---|
| Локальный UUIDv4 на одном сервере | Крайне мал | Можно использовать |
| UUIDv4 на миллионах машин | Очень мал | Подходит для распределённых систем |
| UUIDv1 на одинаковом MAC-адресе | Есть риск | Следить за clock drift, использовать mutex |
| UUIDv4 в базе с индексами | Возможна деградация | Использовать UUIDv7 / ULID / KSUID |
Виды UUID и особенности
| Версия | Описание | Уникальность | Сортируемость | Особенности |
|---|---|---|---|---|
| v1 | Время + MAC-адрес | Высокая | Да | Утечки о времени и хосте |
| v4 | Случайные биты | Почти 100% | Нет | Наиболее часто используемый |
| v5 | Хеш на основе имени и namespace | Детерминирован | Нет | SHA-1 |
| v7 | Timestamp + randomness (proposed) | Высокая | Да | Идеален для баз с индексами по времени |
| ULID | Crockford Base32 + время | Высокая | Да | Читаемый, компактный |
| KSUID | Similar to ULID with longer timestamp window | Высокая | Да | Поддерживает миллиарды лет |
Когда UUID может не подойти?
- В PostgreSQL
uuidиндекс медленнееbigint - UUIDv4 плохо сортируется -> низкая эффективность B-Tree индексов
- Большой размер (
uuid= 16 байт,bigint= 8 байт)
Для insert-heavy систем:
- Используйте UUIDv7 / ULID / KSUID
- Или
bigintс генератором (Snowflake, Twitter ID, sequence)
Пример генерации UUID в Java
import java.util.UUID;
UUID uuid = UUID.randomUUID();
System.out.println(uuid.toString());
Для высоконагруженных систем
- Генерация UUIDv4 достаточно быстрая, не требует синхронизации
- Для хранения большого объёма данных лучше использовать сортируемые UUID (v7, ULID)
- В Kafka или event sourcing – часто предпочтительнее
ULID/KSUIDкакmessageId
13. Strong Consistency vs Eventual Consistency
В распределённых системах согласованность (consistency) – один из ключевых аспектов CAP-теоремы. Она определяет, насколько быстро и надёжно данные распространяются между узлами после обновления.
Определения
| Тип согласованности | Описание |
|---|---|
| Strong consistency | После подтверждения записи — все узлы сразу видят новое значение |
| Eventual consistency | Запись гарантированно распространится на все узлы, но с задержкой |
Strong Consistency (жёсткая согласованность)
Признаки:
- Все клиенты всегда видят одно и то же значение
- Операции чтения и записи проходят через один согласованный источник (quorum, master)
- Реализуется с помощью:
- Quorum write + read
- Two-Phase Commit
- RAFT/Paxos
- Лидерской репликации
Примеры:
| Система | Как достигается strong consistency |
|---|---|
| PostgreSQL | Один мастер (write), остальные читают реплику |
| etcd | Использует RAFT: только лидер пишет, кворум подтверждает |
| Zookeeper | Согласование через ZAB (аналог RAFT) |
| Spanner | TrueTime для глобального согласования |
Плюсы:
Простота reasoning (как будто работаем с одной машиной)
Идеально для банков, транзакций, идентичности, ACL
Минусы:
Более высокая латентность
Сложнее масштабировать горизонтально
Требуется quorum для операций
Eventual Consistency (постепенная согласованность)
Принцип:
- После записи данные рано или поздно распространятся на все узлы
- Не гарантируется моментальная видимость новых данных
- Узлы могут расходиться во мнениях временно
Реализация:
- Hinted handoff (отложенная доставка)
- Read repair (починка данных во время чтения)
- Anti-entropy (регулярная синхронизация)
- Gossip (эпидемическое распространение)
Примеры:
| Система | Поведение |
|---|---|
| Cassandra | Поддержка Tunable Consistency: можно выбрать ONE, QUORUM, ALL |
| DynamoDB | Пишет в несколько узлов, без блокировки |
| S3 / Blob | После PUT объект может быть не сразу виден во всех зонах |
| Couchbase | Eventually replicated to all nodes |
Плюсы:
Отличная масштабируемость
Высокая доступность
Быстрая запись и чтение
Минусы:
Возможны stale reads
Нужно проектировать систему с учётом конфликтов
Отсутствие гарантии linearizability
Сравнение
| Характеристика | Strong Consistency | Eventual Consistency |
|---|---|---|
| Видимость данных | Мгновенно во всех узлах | Может быть с задержкой |
| Устойчивость к сбоям | Ниже | Выше (при снижении согласованности) |
| Производительность | Ниже | Выше |
| Коммуникации между узлами | Кворум | Опционально |
| Типичные задачи | Банкинг, ACL, ID генерация | IoT, аналитика, кэш, рекомендации |
Конфликты и разрешение
- Eventual consistency требует решения конфликтов:
- Last Write Wins (LWW)
- Vector Clocks
- CRDT (Conflict-free Replicated Data Types)
- Application-level merge
Пример (Cassandra):
-- запись с уровнем ONE
INSERT INTO users (id, name) VALUES ('123', 'Alice') USING CONSISTENCY ONE;
-- чтение с уровнем QUORUM
SELECT * FROM users WHERE id = '123' USING CONSISTENCY QUORUM;
Поддержка в базах данных
| СУБД | Consistency model |
|---|---|
| PostgreSQL | Strong (ACID) |
| MongoDB (реплика) | Tunable (до strong) |
| Cassandra | Eventual / Tunable |
| Redis Cluster | Eventual (по умолчанию) |
| Spanner | Strong (через TrueTime) |
14. Использование Keycloak в корпоративных системах
Keycloak – это open-source Identity and Access Management (IAM) решение от Red Hat, широко применяемое в корпоративной среде для централизации управления доступом и идентификацией пользователей.
Ключевые возможности
| Возможность | Описание |
|---|---|
| SSO (Single Sign-On) | Один логин — доступ к нескольким приложениям |
| MFA (Multi-Factor Auth) | Поддержка TOTP, WebAuthn, SMS |
| RBAC / ABAC | Ролевой и атрибутный контроль доступа |
| OAuth 2.0 / OpenID Connect | Аутентификация и авторизация по стандартам |
| SAML 2.0 | Поддержка старых протоколов (например, для SAP) |
| LDAP/AD интеграция | Прямая федерация пользователей без миграции |
| User Self-Service | UI и REST API для управления профилем |
| Custom Flows / Scripts | Расширяемость через SPI, JavaScript, Webhooks |
Протоколы и Flows
| Поток | Применение |
|---|---|
| Authorization Code Flow | UI-приложения (web, mobile) |
| Client Credentials | Machine-to-machine |
| Resource Owner Password | Legacy, устаревший |
| Implicit Flow | Устаревший (для SPA) |
| Device Flow | ТВ, CLI-интерфейсы |
| Token Exchange | Делегирование прав |
| Token Introspection | Валидация opaque токенов |
| Userinfo Endpoint | Получение информации о пользователе |
Архитектура Keycloak
- Realm – изолированная область пользователей и клиентов
- Client – приложение, которому требуется доступ
- Role – роль, назначаемая пользователям
- Group – логическое объединение пользователей
- Mapper – преобразование полей в токене (например,
LDAP -> claim)
Интеграция со Spring Boot
Пример конфигурации ресурсов сервера:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.mycorp.ru/realms/main
Конфигурация клиента (например, UI-приложения):
spring:
security:
oauth2:
client:
registration:
myapp:
client-id: my-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/myapp"
provider:
keycloak:
issuer-uri: https://auth.mycorp.ru/realms/main
Примеры использования в продакшене
- Внутренние дашборды -> SSO через Keycloak
- Сервисы интеграции -> авторизация через Client Credentials + RBAC
- Публичные API -> токены OIDC + scopes (permissions)
- Сценарии “от имени пользователя” -> токен обмен (Token Exchange)
Настройка федерации с LDAP/AD
# UI -> User Federation -> Add Provider -> LDAP
# Указываем:
- bindDN: cn=reader,ou=users,dc=corp,dc=ru
- userSearchBase: ou=users,dc=corp,dc=ru
- syncPeriod: 1h
Пользователи не копируются, а подтягиваются в реальном времени. Можно маппить cn в preferred_username и memberOf в роли.
Расширяемость
- Custom SPI – собственная логика (аутентификация, email, логирование событий)
- JS-скрипты – написание условий в auth flow
- Webhooks – интеграция с внешними аудит-системами
- Themes – брендинг логина и портала
Безопасность
- Token lifetime:
- Access Token: 5–15 минут
- Refresh Token: 30–60 минут
- Scopes: ограничение прав клиента
- Audience claim: защита от misuse токенов
- PKCE: обязательный для мобильных и публичных клиентов
- Key Rotation: поддержка JWKS endpoint
Мониторинг и логи
- Метрики через Prometheus Exporter (community)
- Аудит через Event Listener SPI
- Логи: JSON в stdout, интеграция с Loki, ELK
- Валидация токенов:
/.well-known/openid-configuration+ JWKS endpoint
Ограничения и подводные камни
| Проблема | Обходной путь |
|---|---|
| Отсутствие HA-репликации базы | Использовать Keycloak.X + DB failover |
| Ошибки refresh token в нагрузке | Настроить reuseRefreshToken = false |
| Много клиентов -> медленный UI | REST API / CLI tools |
| Нестабильность на старых версиях | Использовать версию > 20.0.0 |
Рекомендуемая литература и ресурсы
Java и Kotlin
- Effective Java – Joshua Bloch
Классика по стилю, API-дизайну и best practices Java. - Java Concurrency in Practice – Brian Goetz
Подробно про многопоточность, volatile, synchronized, memory model. - Kotlin in Action – Dmitry Jemerov, Svetlana Isakova
Глубокое введение в Kotlin от команды JetBrains.
Stream API, функциональное программирование
- Modern Java in Action – Raoul-Gabriel Urma
Лямбды, стримы, Collectors, параллельная обработка. - Functional Programming in Java – Venkat Subramaniam
Понимание декларативного стиля.
CI/CD, DevOps, инфраструктура
- The DevOps Handbook – Gene Kim
Описывает культуру DevOps и практики доставки. - Kubernetes: Up and Running – Kelsey Hightower
Быстрый старт и понимание K8s. - Terraform: Up and Running – Yevgeniy Brikman
Инфраструктура как код, IaaC.
Мониторинг, нагрузка, observability
- Observability Engineering – Charity Majors
Современный подход к мониторингу, логам, трассировкам. - Distributed Systems Observability – Cindy Sridharan
Краткое и точное руководство от практикующего инженера. - Art of Scalability – Martin L. Abbott
Про производительность и масштабирование.
Kafka и распределённые системы
- Designing Data-Intensive Applications – Martin Kleppmann
Основной учебник по Kafka, consistency, CAP, RAFT, event sourcing. - Kafka: The Definitive Guide – Neha Narkhede
Глубокое понимание Kafka и паттернов использования..
Алгоритмы и архитектура
- Clean Architecture – Robert C. Martin
Про слои, границы, инверсию зависимостей. - Software Architecture: The Hard Parts – Neal Ford
Практические подходы к архитектуре в условиях распределённости. - System Design Interview – Alex Xu
Подготовка к high-level архитектурным вопросам.
Безопасность и Keycloak
- OAuth 2 in Action – Justin Richer
Глубокое понимание всех потоков авторизации. - Identity and Data Security for Web Development – Jonathan LeBlanc
Актуально для интеграций OIDC, JWT, Keycloak. - Keycloak Documentation
https://www.keycloak.org/documentation – официальные гайды
![]()