Многие ненавидят Java не потому что язык плохой, а потому что первое знакомство с ним вышло травматичным.
В универе его чаще всего преподавали на примерах из прошлого века, а преподаватели были больше заняты тем, чтобы сто раз написать public static void main, чем объяснить, почему всё устроено именно так. А ещё эта многословность, ошибки компиляции, необходимость сходу понимать классы, объекты, наследование… неудивительно, что после такой встречи у людей остаётся отвращение.
Плюс порог входа у Java выше, чем у более дружелюбных языков вроде Python или JavaScript. И хотя это уже давно не тот тормозной тяжёлый язык из 2000-х, многие до сих пор думают о нем именно так, не замечая, что сегодня он работает почти во всех крупных системах.
И тут самое забавное: пока в интернете его поливают грязью, компании всё так же активно ищут Java-разработчиков и платят им очень хорошие деньги. Потому что в реальном мире остаётся то, что работает и масштабируется.
👉 Java Portal
В универе его чаще всего преподавали на примерах из прошлого века, а преподаватели были больше заняты тем, чтобы сто раз написать public static void main, чем объяснить, почему всё устроено именно так. А ещё эта многословность, ошибки компиляции, необходимость сходу понимать классы, объекты, наследование… неудивительно, что после такой встречи у людей остаётся отвращение.
Плюс порог входа у Java выше, чем у более дружелюбных языков вроде Python или JavaScript. И хотя это уже давно не тот тормозной тяжёлый язык из 2000-х, многие до сих пор думают о нем именно так, не замечая, что сегодня он работает почти во всех крупных системах.
И тут самое забавное: пока в интернете его поливают грязью, компании всё так же активно ищут Java-разработчиков и платят им очень хорошие деньги. Потому что в реальном мире остаётся то, что работает и масштабируется.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤28
Совет по Java: можно использовать
Обычный способ:
Через joining:
👉 Java Portal
Collectors.joining(", ") чтобы собрать Stream в строку с разделителем запятая (или любым другим, если нужно).Обычный способ:
List<String> items = List.of("a", "b", "c");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < items.size(); i++) {
sb.append(items.get(i));
if (i < items.size() - 1) {
sb.append(", ");
}
}
System.out.println(sb.toString());Через joining:
List<String> items = List.of("a", "b", "c");
String result = items.stream().collect(Collectors.joining(", "));
System.out.println(result.toString());Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍3
API VERSIONING позволяет разработчикам поддерживать несколько версий эндпоинтов веб-сервиса (например, /users или /products), не ломая интеграции у клиентов при внесении изменений.
В новом Spring Boot 4 появилась встроенная поддержка версионирования API, и настраивается она очень просто.
→ Добавь строки в конфиг (
1 способ: версионирование через заголовок
(Клиент передает версию в header, например: api-version: 1 — название заголовка можно выбрать любое)
2 способ: версионирование через query-параметр
(Клиент передает версию в запросе, например: ?version=1)
→ Установка версии по умолчанию
(Используется версия 1.0, если клиент ничего не указал)
Примечание: если версия указана и в заголовке, и в query-параметре, то приоритет у более высокой версии. Например, header = 2, query = 1 → в итоге будет 2.
Готово.
a) Без множества контроллеров
b) Без /v1/, /v2/ в URL
c) Код чище и проще поддерживать
👉 Java Portal
В новом Spring Boot 4 появилась встроенная поддержка версионирования API, и настраивается она очень просто.
→ Добавь строки в конфиг (
application.properties) под твой сценарий. Можно использовать оба варианта одновременно — заголовки и query-параметры.1 способ: версионирование через заголовок
(Клиент передает версию в header, например: api-version: 1 — название заголовка можно выбрать любое)
spring.mvc.apiversion.use.header = api-version
2 способ: версионирование через query-параметр
(Клиент передает версию в запросе, например: ?version=1)
spring.mvc.apiversion.use.query-parameter = version
→ Установка версии по умолчанию
(Используется версия 1.0, если клиент ничего не указал)
spring.mvc.apiversion.default = 1.0
Примечание: если версия указана и в заголовке, и в query-параметре, то приоритет у более высокой версии. Например, header = 2, query = 1 → в итоге будет 2.
Готово.
a) Без множества контроллеров
b) Без /v1/, /v2/ в URL
c) Код чище и проще поддерживать
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥7❤2
Spring Boot: на этапе разработки можно включить spring.main.lazy-initialization=true, чтобы ускорить запуск приложения.
По умолчанию Spring Boot инициализирует все бины при старте. В среде разработки это приводит к:
Более долгому старту приложения, особенно в крупных проектах
Приходится ждать загрузки бинов, которые прямо сейчас даже не нужны
Чтобы избежать этого, в
Важно сохранить поведение по умолчанию в проде, потому что:
Ошибки на старте выявляются раньше
Все компоненты готовы принимать запросы сразу
👉 Java Portal
По умолчанию Spring Boot инициализирует все бины при старте. В среде разработки это приводит к:
Более долгому старту приложения, особенно в крупных проектах
Приходится ждать загрузки бинов, которые прямо сейчас даже не нужны
Чтобы избежать этого, в
application.properties укажите:spring.main.lazy-initialization=true
Важно сохранить поведение по умолчанию в проде, потому что:
Ошибки на старте выявляются раньше
Все компоненты готовы принимать запросы сразу
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍5
25 золотых правил системного дизайна
👉 Java Portal
1. Система с упором на чтение
> Используй кэш (Redis/Memcached) для частых запросов вроде профилей пользователей
2. Низкая задержка
> Используй кэш и CDN (Cloudflare), чтобы раздавать статику ближе к пользователю
3. Система с упором на запись
> Используй Message Queue (Kafka) для буферизации большого объема записей (логи, аналитика)
4. ACID-требования
> Используй SQL (PostgreSQL) для строгих транзакций вроде банковских операций
5. Неструктурированные данные
> Используй NoSQL (MongoDB) для гибких схем, например каталогов товаров
6. Сложные медиа-ресурсы
> Используй Blob Storage (AWS S3) для видео, изображений и больших файлов
7. Сложные предварительные расчёты
> Используй Message Queue + Cache для асинхронной генерации контента (например ленты новостей)
8. Поиск при больших объемах данных
> Используй Elasticsearch для полнотекстового поиска и автокомплита
9. Масштабирование SQL
> Используй шардирование, чтобы разделить большие таблицы на несколько инстансов
10. Высокая доступность
> Используй Load Balancer (NGINX) чтобы распределять трафик и избегать перегрузки
11. Глобальная доставка данных
> Используй CDN для стабильного стриминга и раздачи контента по всему миру
12. Графовые данные
> Используй Graph DB (Neo4j) для соцсетей, рекомендаций и связей между сущностями
13. Масштабирование компонентов
> Используй горизонтальное масштабирование, а не просто апгрейд железа
14. Быстрые запросы к базе
> Используй индексы на ключевых колонках вроде email или user_id
15. Пакетные задачи
> Используй Batch Processing для отчётов, расчётов или периодических задач
16. Защита от злоупотреблений
> Используй Rate Limiter, чтобы предотвращать DDoS и спам запросов к API
17. Доступ к микросервисам
> Используй API Gateway для авторизации, маршрутизации и SSL-терминации
18. Единая точка отказа
> Добавляй Redundancy (Active-Passive), чтобы сервис продолжал работать при сбоях
19. Отказоустойчивость данных
> Используй репликацию (Master-Slave), чтобы данные не терялись при падении узлов
20. Реальное время
> Используй WebSockets для чатов, лайв-обновлений, лайв-результатов
21. Обнаружение сбоев
> Используй Heartbeat-пинг, чтобы проверять статус сервисов каждые несколько секунд
22. Целостность данных
> Используй Checksums (MD5/SHA) чтобы проверить, что загруженные файлы не повреждены
23. Децентрализованное состояние
> Используй Gossip Protocol, чтобы ноды обменивались статусами без центрального сервера
24. Эффективное кеширование
> Используй Consistent Hashing, чтобы добавлять или убирать кэш-ноды без полного пересчёта ключей
25. Работа с геоданными
> Используй Quadtree или Geohash для быстрых запросов вроде поиска ближайших водителей
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥2❤1
12 рекомендаций по дизайну API для бэкенд-разработчиков
(Их часто спрашивают на собеседованиях)
1. Понятные имена ресурсов
2. Корректное использование HTTP-методов
-
-
-
-
3. Идемпотентность
Клиент отправляет:
Если запрос повторится, сервер должен вернуть тот же результат, не создавая операцию заново.
4. Версионирование API
Рекомендуемый вариант — в URL:
5. Правильные статус-коды
Если пользователь не найден:
6. Пагинация
Пример:
Ответ должен содержать элементы 51–100.
7. Фильтрация и сортировка
Пример:
8. Безопасность
Использование JWT в заголовках:
9. Rate limiting
Например: 100 запросов в минуту.
После превышения — вернуть:
10. Кэширование
Запрос:
Ответ содержит:
Повторный запрос:
Если не изменилось — 304 Not Modified.
11. Документация
Используй:
- Swagger UI
- OpenAPI
Разработчики должны видеть схемы, параметры и иметь возможность тестировать запросы.
12. Быть прагматичным
Иногда лучше так:
чем строго REST-подход:
Хороший API читается как логичная, предсказуемая система и экономит время всем, кто с ним работает.
👉 Java Portal
(Их часто спрашивают на собеседованиях)
1. Понятные имена ресурсов
❌`GET /get-all-orders`
✅`GET /orders`
2. Корректное использование HTTP-методов
-
POST /users — создать пользователя -
GET /users/123 — получить пользователя -
PUT /users/123 — заменить полностью -
DELETE /users/123 — удалить 3. Идемпотентность
Клиент отправляет:
POST /payments
Idempotency-Key: abc-123
Если запрос повторится, сервер должен вернуть тот же результат, не создавая операцию заново.
4. Версионирование API
Рекомендуемый вариант — в URL:
GET /v1/products/42
GET /v2/products/42
5. Правильные статус-коды
Если пользователь не найден:
❌ `200 OK { "error": "user not found" }`
✅ `404 Not Found`6. Пагинация
Пример:
GET /articles?page=2&limit=50
Ответ должен содержать элементы 51–100.
7. Фильтрация и сортировка
Пример:
GET /orders?status=shipped&sort=-created_at
8. Безопасность
Использование JWT в заголовках:
Authorization: Bearer <token>
9. Rate limiting
Например: 100 запросов в минуту.
После превышения — вернуть:
429 Too Many Requests
10. Кэширование
Запрос:
GET /blog/posts/123
Ответ содержит:
ETag: "abc"
Повторный запрос:
If-None-Match: "abc"
Если не изменилось — 304 Not Modified.
11. Документация
Используй:
- Swagger UI
- OpenAPI
Разработчики должны видеть схемы, параметры и иметь возможность тестировать запросы.
12. Быть прагматичным
Иногда лучше так:
POST /auth/login
чем строго REST-подход:
POST /sessions
Хороший API читается как логичная, предсказуемая система и экономит время всем, кто с ним работает.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤6🔥6
Только что выкатили демо, показывающее, почему Spring Data AOT реально важен для производительности.
Перенос парсинга запросов из рантайма в стадию компиляции дает:
- запуск приложения быстрее на 50–70%
- ошибки в запросах ловятся до деплоя
- SQL генерируется заранее на этапе сборки
- всё готово для GraalVM Native
Репозиторий:
https://github.com/danvega/spring-data-aot
👉 Java Portal
Перенос парсинга запросов из рантайма в стадию компиляции дает:
- запуск приложения быстрее на 50–70%
- ошибки в запросах ловятся до деплоя
- SQL генерируется заранее на этапе сборки
- всё готово для GraalVM Native
Репозиторий:
https://github.com/danvega/spring-data-aot
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - danvega/spring-data-aot
Contribute to danvega/spring-data-aot development by creating an account on GitHub.
👍7❤2😁1
Одна простая привычка, которая сильно упрощает поддержку Dockerfile, это сортировка многострочных списков пакетов по алфавиту.
На первый взгляд мелочь, но влияние на читаемость и будущую поддержку заметное.
Когда ставишь пакеты в многострочном apt-get install или apk add блоке, легко случайно добавить дубликат или пропустить что-то, что затерялось в середине списка.
Алфавитная сортировка полностью снимает этот вопрос. Список можно быстро просканировать, сразу увидеть дубли и держать всё в едином стиле на разных окружениях.
Плюс уменьшаются шумные диффы в код-ревью.
Когда пакеты отсортированы, добавление или удаление одного пункта выглядит как аккуратное и предсказуемое изменение.
Без сортировки маленькое обновление часто превращается в огромный diff, и ревью становится сложнее, чем должно быть.
Этот подход особенно полезен, когда команда растёт.
Несколько человек, трогающих один Dockerfile, не начинают перетасовывать строки под свой вкус или форматировать по-разному.
Структура остаётся стабильной, и поддержка со временем становится проще.
Небольшое улучшение, но на сотнях билдов оно экономит заметно нервов и времени.
👉 Java Portal
На первый взгляд мелочь, но влияние на читаемость и будущую поддержку заметное.
Когда ставишь пакеты в многострочном apt-get install или apk add блоке, легко случайно добавить дубликат или пропустить что-то, что затерялось в середине списка.
Алфавитная сортировка полностью снимает этот вопрос. Список можно быстро просканировать, сразу увидеть дубли и держать всё в едином стиле на разных окружениях.
Плюс уменьшаются шумные диффы в код-ревью.
Когда пакеты отсортированы, добавление или удаление одного пункта выглядит как аккуратное и предсказуемое изменение.
Без сортировки маленькое обновление часто превращается в огромный diff, и ревью становится сложнее, чем должно быть.
Этот подход особенно полезен, когда команда растёт.
Несколько человек, трогающих один Dockerfile, не начинают перетасовывать строки под свой вкус или форматировать по-разному.
Структура остаётся стабильной, и поддержка со временем становится проще.
Небольшое улучшение, но на сотнях билдов оно экономит заметно нервов и времени.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍4❤2
Почему Redis однопоточный (и почему из-за этого он быстрый)
У тебя сервер на 32 ядра, а Redis грузит только одно.
На вид неэффективно. Но при этом Redis остается одной из самых быстрых in-memory баз.
Почему так?
Основной bottleneck обычно не CPU
Для типичных операций Redis вроде GET, SET, INCR CPU почти не тратится.
В реальных условиях упираешься в другое:
- пропускная способность сети (ответы клиентам)
- пропускная способность памяти (RAM <--> CPU cache)
- системные вызовы, TCP, epoll и другая возня ядра
В таких условиях добавление потоков не дает прироста, потому что ограничение в другом.
Почему мультипоточность сделала бы Redis медленнее?
Если бы несколько потоков трогали одну область данных, понадобились бы:
Это создает "cache thrashing" и ест производительность.
Во многих сценариях эти накладные расходы больше, чем сама работа Redis-команды.
Секрет - однопоточный event loop
Redis работает на неблокирующем event loop (epoll/kqueue).
Он умеет держать тысячи соединений и обрабатывать только те сокеты, у которых есть данные.
Плюсы:
- без блокировок
- предсказуемые задержки
- строгий порядок операций
- минимальный overhead
Работая в памяти, один поток способен обработать сотни тысяч или даже миллионы команд в секунду.
Но небольшая поправка = Redis не полностью однопоточный
Redis однопоточен только в части, которая работает с данными.
Остальное вынесено в фоновые потоки.
✔️ Главный поток
Выполняет команды и трогает dataset.
✔️ BIO-потоки
Для долгих задач:
lazy delete (UNLINK)
синхронизация AOF
фоновые задачи модулей
✔️ I/O threads (с версии Redis 6+)
Для чтения запросов и отправки ответов по сети.
При этом сами данные не трогаются, значит = без локов.
Так Redis получает выгоду от распараллеливания, не ломая простоту и предсказуемость.
Redis специально остается однопоточным в части доступа к данным, чтобы избежать локов, гонок и непредсказуемости.
При этом дополнительные потоки используются там, где они реально помогают.
В результате Redis часто обходится быстрее сложных многопоточных систем.
👉 Java Portal
У тебя сервер на 32 ядра, а Redis грузит только одно.
На вид неэффективно. Но при этом Redis остается одной из самых быстрых in-memory баз.
Почему так?
Основной bottleneck обычно не CPU
Для типичных операций Redis вроде GET, SET, INCR CPU почти не тратится.
В реальных условиях упираешься в другое:
- пропускная способность сети (ответы клиентам)
- пропускная способность памяти (RAM <--> CPU cache)
- системные вызовы, TCP, epoll и другая возня ядра
В таких условиях добавление потоков не дает прироста, потому что ограничение в другом.
Почему мультипоточность сделала бы Redis медленнее?
Если бы несколько потоков трогали одну область данных, понадобились бы:
Локи
Чтобы безопасно читать или писать ключи. Ожидание блокировок добавляет задержки.
Context Switching
Переключение между 32 потоками сжигает ресурсы и ломает кэш-локальность.
Cache Coherence
Когда один поток обновляет данные, которые кэшировал другой, аппаратная часть должна инвалидировать кэши по ядрам.
Это создает "cache thrashing" и ест производительность.
Во многих сценариях эти накладные расходы больше, чем сама работа Redis-команды.
Секрет - однопоточный event loop
Redis работает на неблокирующем event loop (epoll/kqueue).
Он умеет держать тысячи соединений и обрабатывать только те сокеты, у которых есть данные.
Плюсы:
- без блокировок
- предсказуемые задержки
- строгий порядок операций
- минимальный overhead
Работая в памяти, один поток способен обработать сотни тысяч или даже миллионы команд в секунду.
Но небольшая поправка = Redis не полностью однопоточный
Redis однопоточен только в части, которая работает с данными.
Остальное вынесено в фоновые потоки.
Выполняет команды и трогает dataset.
Для долгих задач:
lazy delete (UNLINK)
синхронизация AOF
фоновые задачи модулей
Для чтения запросов и отправки ответов по сети.
При этом сами данные не трогаются, значит = без локов.
Так Redis получает выгоду от распараллеливания, не ломая простоту и предсказуемость.
Redis специально остается однопоточным в части доступа к данным, чтобы избежать локов, гонок и непредсказуемости.
При этом дополнительные потоки используются там, где они реально помогают.
В результате Redis часто обходится быстрее сложных многопоточных систем.
Please open Telegram to view this post
VIEW IN TELEGRAM
👀4👍3❤2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Хочешь избежать проблем, когда Cloudflare падает?
Или когда La Liga блокирует твои страницы?
Разработчик сделал open-source CLI, чтобы можно было легко отключать этот сервис в проектах за пару секунд.
👉 Java Portal
Или когда La Liga блокирует твои страницы?
Разработчик сделал open-source CLI, чтобы можно было легко отключать этот сервис в проектах за пару секунд.
$ npx disable-cloudflare@latest
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3
В Java такое видел почти каждый:
var user = new User("Jordy", "Colombia", 28, true, "admin");
Код компилируется, но никто не помнит, что означает каждый параметр (если только IDE не подсказывает).
Порядок важен, значения путаются, а любое изменение превращается в клоунаду.
Классический конструктор начинает убивать читаемость.
От этого и спасает Builder:
Теперь код читается как нормальное предложение.
Не нужно запоминать порядок, угадывать значения и зависеть от IDE. Назначение каждого поля видно сразу.
Причем сам JDK в новых API давно использует этот подход, пример с HttpRequest:
Смысл в том, что когда у объекта много параметров, простой конструктор перестает быть понятным и превращается в препятствие.
Builder это не просто "красивый паттерн".
Это инструмент, который делает код понятным, а создание объектов безопасным и предсказуемым.
Создание объекта не должно выглядеть как угадывание параметров.
👉 Java Portal
var user = new User("Jordy", "Colombia", 28, true, "admin");
Код компилируется, но никто не помнит, что означает каждый параметр (если только IDE не подсказывает).
Порядок важен, значения путаются, а любое изменение превращается в клоунаду.
Классический конструктор начинает убивать читаемость.
От этого и спасает Builder:
var user = User.builder()
.name("Jordy")
.country("Colombia")
.age(28)
.active(true)
.role("admin")
.build();
Теперь код читается как нормальное предложение.
Не нужно запоминать порядок, угадывать значения и зависеть от IDE. Назначение каждого поля видно сразу.
Причем сам JDK в новых API давно использует этот подход, пример с HttpRequest:
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("url"))
.GET()
.build();
Смысл в том, что когда у объекта много параметров, простой конструктор перестает быть понятным и превращается в препятствие.
Builder это не просто "красивый паттерн".
Это инструмент, который делает код понятным, а создание объектов безопасным и предсказуемым.
Создание объекта не должно выглядеть как угадывание параметров.
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍21🔥6❤3💊1
Java хотела добавить лямбды, но при этом не поломать JVM и не переписывать двадцатилетние API.
Решение? Сделать вид, что в Java есть функции первого класса, используя то, что уже было: интерфейсы с единственным абстрактным методом.
Так появились функциональные интерфейсы.
Кажется, что ты передаешь функцию... но нет.
Компилятор создаёт экземпляр Operation.
В Java нет "свободных" функций, как в JavaScript или Kotlin.
Сейчас это используется даже со старыми классами языка.
Например, Runnable существует ещё с Java 1.0 и содержит всего один метод run.
Когда в Java 8 завезли лямбды, стало можно писать так:
И старый конструктор Thread(Runnable r) продолжил работать без изменения ни одной строки оригинального API.
Именно это позволило не сломать экосистему и сохранить стабильность, за которую Java так ценят.
👉 Java Portal
Решение? Сделать вид, что в Java есть функции первого класса, используя то, что уже было: интерфейсы с единственным абстрактным методом.
Так появились функциональные интерфейсы.
@FunctionalInterface
interface Operation {
int apply(int x, int y);
}
Operation add = (a, b) -> a + b;
System.out.println(add.apply(3, 4)); // 7
Кажется, что ты передаешь функцию... но нет.
Компилятор создаёт экземпляр Operation.
В Java нет "свободных" функций, как в JavaScript или Kotlin.
Сейчас это используется даже со старыми классами языка.
Например, Runnable существует ещё с Java 1.0 и содержит всего один метод run.
Когда в Java 8 завезли лямбды, стало можно писать так:
new Thread(() -> System.out.println("Hello from a thread")).start();И старый конструктор Thread(Runnable r) продолжил работать без изменения ни одной строки оригинального API.
Именно это позволило не сломать экосистему и сохранить стабильность, за которую Java так ценят.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍3🔥2
Путаешь Dockerfile и Docker Compose?
Тогда вот короткое пояснение.
Они дополняют друг друга, но выполняют разные задачи в контейнерной инфраструктуре.
Dockerfile используется для создания и сборки Docker-образов.
Docker Compose используется для запуска контейнеров как части многоконтейнерной среды или с заданными параметрами выполнения.
Примечание. Начиная с версии 1.28.6, Docker Compose по умолчанию ищет compose.yaml или compose.yml, но сохраняет обратную совместимость с docker-compose.yaml/yml. Если в проекте есть оба варианта, приоритет у compose.yaml.
Прикрепил простую визуализацию, чтобы было легче разобраться.
👉 Java Portal
Тогда вот короткое пояснение.
Они дополняют друг друга, но выполняют разные задачи в контейнерной инфраструктуре.
Dockerfile используется для создания и сборки Docker-образов.
Docker Compose используется для запуска контейнеров как части многоконтейнерной среды или с заданными параметрами выполнения.
Примечание. Начиная с версии 1.28.6, Docker Compose по умолчанию ищет compose.yaml или compose.yml, но сохраняет обратную совместимость с docker-compose.yaml/yml. Если в проекте есть оба варианта, приоритет у compose.yaml.
Прикрепил простую визуализацию, чтобы было легче разобраться.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥4❤3
В начале карьеры я удалил 5GB лог-файл на продовом сервере, который уже почти забился.
Я запустил df -h, ожидая, что использование диска упадёт. Не упало.
Всё ещё показывало 100% занято.
Ни ошибок, ни предупреждений. Просто те же цифры, будто я вообще ничего не удалял.
И вот тогда я понял, что удаление файла не всегда сразу освобождает место.
В Linux то, что мы называем "файлом", на самом деле состоит из двух частей: имени файла (по сути указателя) и inode (где лежат данные и метаданные). Когда ты удаляешь имя файла, ты просто убираешь указатель. Но inode с данными остаётся на диске, пока какой-то процесс держит файл открытым.
В моём случае веб-сервер всё ещё писал в тот самый лог. И хотя я удалил имя файла, процесс продолжал держать открытый файловый дескриптор. Inode оставался живым — его не видно обычными командами, но место он продолжал занимать.
Место освободилось только после перезапуска веб-сервера, когда все дескрипторы закрылись.
Поэтому нужны разные команды, чтобы увидеть реальную картину:
Проверить использование файловой системы:
Посмотреть реальные размеры директорий:
Найти удалённые файлы, которые всё ещё держатся процессами:
du показывает, что в директориях реально занимает место, а df сколько занято на уровне файловой системы.
Если они не совпадают, почти всегда причина = удалённые файлы, которые всё ещё открыты процессами.
По этой же причине нормальный log rotation не просто удаляет файлы. Такие инструменты, как logrotate, сначала переименовывают файл и отправляют сигнал процессу, чтобы он корректно закрыл старый дескриптор и открыл новый.
Три важных вывода:
Мелочь, но понимание этого может спасти от очень странных и неприятных инцидентов в проде.
👉 Java Portal
Я запустил df -h, ожидая, что использование диска упадёт. Не упало.
Всё ещё показывало 100% занято.
Ни ошибок, ни предупреждений. Просто те же цифры, будто я вообще ничего не удалял.
И вот тогда я понял, что удаление файла не всегда сразу освобождает место.
В Linux то, что мы называем "файлом", на самом деле состоит из двух частей: имени файла (по сути указателя) и inode (где лежат данные и метаданные). Когда ты удаляешь имя файла, ты просто убираешь указатель. Но inode с данными остаётся на диске, пока какой-то процесс держит файл открытым.
В моём случае веб-сервер всё ещё писал в тот самый лог. И хотя я удалил имя файла, процесс продолжал держать открытый файловый дескриптор. Inode оставался живым — его не видно обычными командами, но место он продолжал занимать.
Место освободилось только после перезапуска веб-сервера, когда все дескрипторы закрылись.
Поэтому нужны разные команды, чтобы увидеть реальную картину:
Проверить использование файловой системы:
df -h
Посмотреть реальные размеры директорий:
du -sh /var/log/*
Найти удалённые файлы, которые всё ещё держатся процессами:
lsof +L1
du показывает, что в директориях реально занимает место, а df сколько занято на уровне файловой системы.
Если они не совпадают, почти всегда причина = удалённые файлы, которые всё ещё открыты процессами.
По этой же причине нормальный log rotation не просто удаляет файлы. Такие инструменты, как logrotate, сначала переименовывают файл и отправляют сигнал процессу, чтобы он корректно закрыл старый дескриптор и открыл новый.
Три важных вывода:
Имя файла это всего лишь указатель на inode
Удаление происходит только тогда, когда inode больше никем не используется
При разборе проблем с диском всегда проверяй и df, и du
Мелочь, но понимание этого может спасти от очень странных и неприятных инцидентов в проде.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20❤6🔥4😁1
Java Portal | Программирование
Учись алгоритмам программирования с этим ресурсом - код, пошаговое выполнение и наглядное представление. Более 70 алгоритмов на JavaScript, Java и C++ - идеально для практики и понимания логики. → http://algorithm-visualizer.org 👉 Java Portal
Очень в тему перечитать этот классический материал от Майка Бостока. Отлично ложится как продолжение поста.
https://bost.ocks.org/mike/algorithms/
👉 Java Portal
https://bost.ocks.org/mike/algorithms/
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2
Есть забавная вещь, которая происходит со многими Java-разработчиками. Oни пишут современный код с мышлением старой Java.
И, если честно, их сложно за это винить.
Java менялась куда быстрее, чем комьюнити успевало к этим изменениям адаптироваться.
Годами Java ассоциировалась с огромными классами, кучей геттеров и сеттеров, громоздкими фабриками, бесконечными try/catch блоками. Со стороны казалось, что язык боится любого изменения.
Java была про структуры и правила.
Это был язык принципа = делай явно или не делай вообще.
Но потом подъехали серьезные обновления: лямбды, records, pattern matching, sealed-классы, streams, более выразительный switch, выведение типов, более декларативные API, и стиль, который стал ближе к функциональному.
У всего этого есть плюсы и минусы, понятно.
И внезапно Java перестала ощущаться языком начала двухтысячных и стала языком под 2025 год (ну или под 2020, кому как). Хотя многие до сих пор пишут так, будто на календаре 2010.
Но важно понимать! часто дело не в техническом выборе. Это может быть привычка, вкусы или просто невозможность перейти на новую версию.
И главный момент тут в том, что современная Java не заменяет классическую = они существуют параллельно. Хотя со временем, конечно, и "новая Java" тоже станет предметом споров и хейта, как старая.
Язык изменился, но команды, компании, легаси-фреймворки и технические решения десятилетней давности никуда не делись. Они тянут в обратную сторону и фактически продолжают поддерживать устаревшее представление о Java.
Java сегодня это умение жить сразу в двух эпохах.
Если ты понимаешь эту двойственность, тогда можешь использовать язык по максимуму.
Если нет, то просто будешь сражаться с прошлым, которого уже нет, и игнорировать настоящее, которое уже здесь и точно никуда не денется.
👉 Java Portal
И, если честно, их сложно за это винить.
Java менялась куда быстрее, чем комьюнити успевало к этим изменениям адаптироваться.
Годами Java ассоциировалась с огромными классами, кучей геттеров и сеттеров, громоздкими фабриками, бесконечными try/catch блоками. Со стороны казалось, что язык боится любого изменения.
Java была про структуры и правила.
Это был язык принципа = делай явно или не делай вообще.
Но потом подъехали серьезные обновления: лямбды, records, pattern matching, sealed-классы, streams, более выразительный switch, выведение типов, более декларативные API, и стиль, который стал ближе к функциональному.
У всего этого есть плюсы и минусы, понятно.
И внезапно Java перестала ощущаться языком начала двухтысячных и стала языком под 2025 год (ну или под 2020, кому как). Хотя многие до сих пор пишут так, будто на календаре 2010.
Но важно понимать! часто дело не в техническом выборе. Это может быть привычка, вкусы или просто невозможность перейти на новую версию.
И главный момент тут в том, что современная Java не заменяет классическую = они существуют параллельно. Хотя со временем, конечно, и "новая Java" тоже станет предметом споров и хейта, как старая.
Язык изменился, но команды, компании, легаси-фреймворки и технические решения десятилетней давности никуда не делись. Они тянут в обратную сторону и фактически продолжают поддерживать устаревшее представление о Java.
Java сегодня это умение жить сразу в двух эпохах.
Если ты понимаешь эту двойственность, тогда можешь использовать язык по максимуму.
Если нет, то просто будешь сражаться с прошлым, которого уже нет, и игнорировать настоящее, которое уже здесь и точно никуда не денется.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9
Spring Boot: Spring Data JPA имеет встроенную поддержку пагинации через Pageable.
Лучше использовать пагинацию в репозиториях вместо того, чтобы вытягивать все данные разом.
Вместо такого репозитория:
Лучше так:
И дальше в сервисном слое:
👉 Java Portal
Лучше использовать пагинацию в репозиториях вместо того, чтобы вытягивать все данные разом.
Вместо такого репозитория:
public interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findAll();
}Лучше так:
public interface BookRepository extends JpaRepository<Book, Long> {
Page<Book> findAll(Pageable pageable);
}И дальше в сервисном слое:
public Page<Book> getBooks(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending());
return bookRepository.findAll(pageable);
}Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
Java 21. Виртуальные потоки + структурированная конкуренция в 15 строках:
Запускай 100000+ конкурентных задач почти без оверхеда с виртуальными потоками.
Структурированная конкуренция делает async-код читаемым как синхронный, без утечек и callback-адского.
В реальных проектах даёт 10-кратный рост пропускной способности при гораздо более простом коде.
👉 Java Portal
Запускай 100000+ конкурентных задач почти без оверхеда с виртуальными потоками.
Структурированная конкуренция делает async-код читаемым как синхронный, без утечек и callback-адского.
В реальных проектах даёт 10-кратный рост пропускной способности при гораздо более простом коде.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4😁1
Всегда есть шанс, что один и тот же запрос прилетит в ваш API или сервер несколько раз.
Даже если вы отключаете кнопку после первого клика, вероятность уменьшается, но не исчезает.
Это решается идемпотентностью.
Идемпотентность — это когда одна и та же операция, выполненная несколько раз, даёт строго один и тот же результат.
Не похожий. Не почти такой же. А точно такой же.
Например, в платежных системах это обязательное свойство.
Это разница между стабильной системой и системой, которая может уронить бизнес.
Представим эндпоинт /payment/charge.
Если пользователь дважды жмёт кнопку оплатить, приложение не должно интерпретировать это как две транзакции.
По сути решение простое:
клиент отправляет idempotency-key, а сервер гарантирует, что операция будет выполнена только один раз.
Но правильная реализация важна.
Недостаточно просто игнорировать дубликаты. Нужно сохранять результат первой операции и возвращать его при повторных запросах, даже если второй запрос пришёл раньше, чем завершилась первая обработка.
Это значит, что потребуется хранить состояния, промежуточные результаты и думать об операции как о той, которую можно повторить без нарушения согласованности.
Без идемпотентности API ведет себя непредсказуемо.
С идемпотентностью можно пережить сетевые сбои, таймауты, повторные подключения и слишком быстрых юзеров, не ломая систему.
👉 Java Portal
Даже если вы отключаете кнопку после первого клика, вероятность уменьшается, но не исчезает.
Это решается идемпотентностью.
Идемпотентность — это когда одна и та же операция, выполненная несколько раз, даёт строго один и тот же результат.
Не похожий. Не почти такой же. А точно такой же.
Например, в платежных системах это обязательное свойство.
Это разница между стабильной системой и системой, которая может уронить бизнес.
Представим эндпоинт /payment/charge.
Если пользователь дважды жмёт кнопку оплатить, приложение не должно интерпретировать это как две транзакции.
По сути решение простое:
клиент отправляет idempotency-key, а сервер гарантирует, что операция будет выполнена только один раз.
Но правильная реализация важна.
Недостаточно просто игнорировать дубликаты. Нужно сохранять результат первой операции и возвращать его при повторных запросах, даже если второй запрос пришёл раньше, чем завершилась первая обработка.
Это значит, что потребуется хранить состояния, промежуточные результаты и думать об операции как о той, которую можно повторить без нарушения согласованности.
Без идемпотентности API ведет себя непредсказуемо.
С идемпотентностью можно пережить сетевые сбои, таймауты, повторные подключения и слишком быстрых юзеров, не ломая систему.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4
Совет по Spring Boot
RestTestClient для тестирования API
RestTestClient это единый инструмент для тестирования REST API, который можно использовать вместо WebTestClient, TestRestTemplate (удалён) или MockMVC.
1. Определи объект RestTestClient в тесте
2. Привяжи его к конкретному компоненту. Это может быть controller, MockMVC, server или Spring context
3. Потом используй RestTestClient в тесте
👉 Java Portal
RestTestClient для тестирования API
RestTestClient это единый инструмент для тестирования REST API, который можно использовать вместо WebTestClient, TestRestTemplate (удалён) или MockMVC.
1. Определи объект RestTestClient в тесте
2. Привяжи его к конкретному компоненту. Это может быть controller, MockMVC, server или Spring context
3. Потом используй RestTestClient в тесте
@SpringBootTest
public class PersonControllerWithHeadersTests {
private WebApplicationContext context;
private RestTestClient restTestClient;
@BeforeEach
public void setup(WebApplicationContext context) {
restTestClient = RestTestClient
.bindToApplicationContext(context)
.build();
}
@Test
void addPersonTest() {
restTestClient.post()
.uri("/persons")
.body(Instancio.create(Person.class))
.exchange()
.expectStatus().is2xxSuccessful()
.expectBody(Person.class)
.value(p -> assertNotNull(p.getId()));
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥2