Хочешь учить и практиковать SQL без установки ничего на компьютер?
Есть классный ресурс и всё работает прямо в браузере.
Можно создавать базы MySQL и PostgreSQL, писать запросы и сразу видеть результат.
Бесплатно → http://sqlplayground.app
👉 Java Portal
Есть классный ресурс и всё работает прямо в браузере.
Можно создавать базы MySQL и PostgreSQL, писать запросы и сразу видеть результат.
Бесплатно → http://sqlplayground.app
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤2
Почему важно давать хорошие имена в коде?
Потому что большую часть времени мы не пишем код, а читаем его. И одно удачное имя способно объяснить то, что комментарий бы растянул на три строки.
Что решает хорошее имя:
Намерение: expireSession() говорит больше, чем process().
Контекст: calculatePriceWithTax() понятнее, чем просто calculate().
Контракт: isEmpty() (логический результат) и getSize() (число) — разное поведение, и имя это чётко показывает.
Простые, но работающие правила:
Глагол + объект для действий: sendInvoiceEmail().
Ясное существительное для данных: PaymentRequest, CustomerId.
Без тайнописи вроде cfg, mgr, tmp2.
Если логика нетривиальна, пусть имя объясняет “почему”: retryOnRateLimit().
А комментарии не нужны?🤔
Нужны, но точечно. Пиши их там, где код сам не может выразить мысль:
когда решение неочевидное;
когда нужна историческая справка или ссылка на ADR.
Главное не использовать комментарии, чтобы объяснять то, что код и так должен рассказывать сам.
👉 Java Portal
Потому что большую часть времени мы не пишем код, а читаем его. И одно удачное имя способно объяснить то, что комментарий бы растянул на три строки.
Что решает хорошее имя:
Намерение: expireSession() говорит больше, чем process().
Контекст: calculatePriceWithTax() понятнее, чем просто calculate().
Контракт: isEmpty() (логический результат) и getSize() (число) — разное поведение, и имя это чётко показывает.
Простые, но работающие правила:
Глагол + объект для действий: sendInvoiceEmail().
Ясное существительное для данных: PaymentRequest, CustomerId.
Без тайнописи вроде cfg, mgr, tmp2.
Если логика нетривиальна, пусть имя объясняет “почему”: retryOnRateLimit().
А комментарии не нужны?
Нужны, но точечно. Пиши их там, где код сам не может выразить мысль:
когда решение неочевидное;
когда нужна историческая справка или ссылка на ADR.
Главное не использовать комментарии, чтобы объяснять то, что код и так должен рассказывать сам.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤2
Топ-20 техник оптимизации SQL-запросов
1. Индексируй под реальные паттерны запросов (композитные, селективные, covering-индексы), а не просто по числу строк. Следи, чтобы статистика была актуальной.
2. Используй
3. Выбирай конкретные колонки, не пиши SELECT *, чтобы сократить I/O и дать шанс использовать covering-индекс.
4. Пиши sargable-предикаты (которые могут использовать индекс). Медленные коррелированные подзапросы лучше переписать через JOIN или EXISTS.
5. Не лечи ошибки DISTINCT’ом. Исправь логику JOIN и ключи; DISTINCT — только когда реально нужно убрать дубликаты.
6. Фильтруй в
7. Пиши явные
8. Используй keyset pagination вместо OFFSET/LIMIT на больших наборах; для выборки подмножества можно применить TABLESAMPLE (если поддерживается).
9. Предпочитай
10. Заменяй широкие
11. Тяжёлые запросы запускай вне пиковых часов, при возможности — ограничивай ресурсы или ставь в очередь.
12. Избегай
13. Используй
14. Используй временные/derived таблицы, если они реально сокращают работу или добавляют статистику; но помни, что это может заблокировать pushdown-оптимизации.
15. При массовой загрузке данных отключай/удаляй некластерные индексы, вставляй пакетами, потом перестраивай. PK/кластерный индекс можно оставить, если помогает.
16. Используй материализованные представления для редко меняющихся и дорогих агрегатов, продумай их обновление и инвалидацию.
17. Избегай не-sargable сравнений (например, <>) по малоселективным колонкам; лучше перепиши в диапазоны.
18. Минимизируй коррелированные подзапросы на больших выборках, переходи на join’ы или EXISTS.
19. Выбирай
20. Кешируй часто повторяющиеся выборки: временные таблицы (на сессию), result cache или материализованные представления с продуманными правилами обновления.
Что такое Query Optimizer
Query Optimizer — это компонент СУБД, который определяет наиболее эффективный способ выполнения SQL-запроса, подбирая оптимальный execution plan.
Он принимает SQL-запрос, парсит его и строит синтаксическое дерево. Затем анализирует дерево, чтобы понять, какие способы выполнения возможны.
Далее оптимизатор генерирует альтернативные execution plans — разные варианты выполнения одного и того же запроса. В каждом плане задаётся:
- порядок доступа к таблицам
- типы соединений
- способы фильтрации и сортировки
Каждому плану присваивается стоимость — оценка по числу чтений с диска, времени CPU и другим факторам.
После этого оптимизатор выбирает план с минимальной стоимостью и использует его для реального выполнения запроса.
Узнай больше
👉 Java Portal
1. Индексируй под реальные паттерны запросов (композитные, селективные, covering-индексы), а не просто по числу строк. Следи, чтобы статистика была актуальной.
2. Используй
EXISTS
для проверки наличия данных; COUNT(*) — только если реально нужен подсчёт. 3. Выбирай конкретные колонки, не пиши SELECT *, чтобы сократить I/O и дать шанс использовать covering-индекс.
4. Пиши sargable-предикаты (которые могут использовать индекс). Медленные коррелированные подзапросы лучше переписать через JOIN или EXISTS.
5. Не лечи ошибки DISTINCT’ом. Исправь логику JOIN и ключи; DISTINCT — только когда реально нужно убрать дубликаты.
6. Фильтруй в
WHERE
, а HAVING используй только для условий после агрегации. 7. Пиши явные
JOIN ... ON
, не лепи неявные join’ы через WHERE. 8. Используй keyset pagination вместо OFFSET/LIMIT на больших наборах; для выборки подмножества можно применить TABLESAMPLE (если поддерживается).
9. Предпочитай
UNION ALL
вместо UNION
, если дубликаты допустимы. 10. Заменяй широкие
OR
на UNION ALL
, только если каждая ветка может использовать свой индекс. 11. Тяжёлые запросы запускай вне пиковых часов, при возможности — ограничивай ресурсы или ставь в очередь.
12. Избегай
OR
в условиях JOIN
, можно использовать вычисляемые колонки или UNION ALL, если это позволит использовать индекс. 13. Используй
GROUP BY
, когда нужны агрегированные строки, и оконные функции — когда нужно видеть строки с агрегатами рядом. 14. Используй временные/derived таблицы, если они реально сокращают работу или добавляют статистику; но помни, что это может заблокировать pushdown-оптимизации.
15. При массовой загрузке данных отключай/удаляй некластерные индексы, вставляй пакетами, потом перестраивай. PK/кластерный индекс можно оставить, если помогает.
16. Используй материализованные представления для редко меняющихся и дорогих агрегатов, продумай их обновление и инвалидацию.
17. Избегай не-sargable сравнений (например, <>) по малоселективным колонкам; лучше перепиши в диапазоны.
18. Минимизируй коррелированные подзапросы на больших выборках, переходи на join’ы или EXISTS.
19. Выбирай
INNER
или LEFT/RIGHT
по смыслу, но помни: INNER JOIN обычно быстрее, если подходит по логике. 20. Кешируй часто повторяющиеся выборки: временные таблицы (на сессию), result cache или материализованные представления с продуманными правилами обновления.
Что такое Query Optimizer
Query Optimizer — это компонент СУБД, который определяет наиболее эффективный способ выполнения SQL-запроса, подбирая оптимальный execution plan.
Он принимает SQL-запрос, парсит его и строит синтаксическое дерево. Затем анализирует дерево, чтобы понять, какие способы выполнения возможны.
Далее оптимизатор генерирует альтернативные execution plans — разные варианты выполнения одного и того же запроса. В каждом плане задаётся:
- порядок доступа к таблицам
- типы соединений
- способы фильтрации и сортировки
Каждому плану присваивается стоимость — оценка по числу чтений с диска, времени CPU и другим факторам.
После этого оптимизатор выбирает план с минимальной стоимостью и использует его для реального выполнения запроса.
Узнай больше
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍2
Чтобы сделать API быстрым, нужно получать данные из нескольких микросервисов параллельно. Как это требование влияет на выбор между RestTemplate, WebClient и Feign Client?
RestTemplate: Подходит для простых, блокирующих сценариев — например, в старом Spring MVC-приложении, где нужно сделать вызов к другому сервису и дождаться ответа, прежде чем продолжить выполнение. RestTemplate считается устаревшим, вместо него рекомендуется использовать RestClient.
WebClient: Предпочтительный вариант для высокопроизводительных неблокирующих приложений, например, для API-шлюзов, которым нужно одновременно обращаться к нескольким микросервисам и объединять результаты без блокировки потоков.
Feign Client: Лучше всего подходит для взаимодействия между микросервисами, когда хочется писать чистый, читаемый код на основе интерфейсов, абстрагирующий детали HTTP-запросов.
👉 Java Portal
RestTemplate: Подходит для простых, блокирующих сценариев — например, в старом Spring MVC-приложении, где нужно сделать вызов к другому сервису и дождаться ответа, прежде чем продолжить выполнение. RestTemplate считается устаревшим, вместо него рекомендуется использовать RestClient.
WebClient: Предпочтительный вариант для высокопроизводительных неблокирующих приложений, например, для API-шлюзов, которым нужно одновременно обращаться к нескольким микросервисам и объединять результаты без блокировки потоков.
Feign Client: Лучше всего подходит для взаимодействия между микросервисами, когда хочется писать чистый, читаемый код на основе интерфейсов, абстрагирующий детали HTTP-запросов.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Javaeros! Они сделали MCP официального Java SDK — так что нам больше не придётся разбираться с древней документацией 💪
github.com/modelcontextprotocol/java-sdk
Рекомендация перед использованием: сначала разберись, что вообще такое MCP - пойми саму концепцию. После этого реализация, независимо от языка, уже вопрос синтаксиса.
👉 Java Portal
github.com/modelcontextprotocol/java-sdk
Рекомендация перед использованием: сначала разберись, что вообще такое MCP - пойми саму концепцию. После этого реализация, независимо от языка, уже вопрос синтаксиса.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5
Если ты когда-нибудь писал что-то вроде этого в Java:
То то же самое можно записать в одну строку:
А ещё короче так:
Компилятор Java сам поймёт, какой тип ArrayList нужно создать, исходя из типа Map
(в данном случае Map<String, List<String>>).
👉 Java Portal
List<String> stringList = map.get(key);
if (stringList == null) {
stringList = new ArrayList<String>();
map.put(key, stringList);
}
stringList.add(newElement);
То то же самое можно записать в одну строку:
map.computeIfAbsent(key, k -> new ArrayList<String>()).add(newElement);
А ещё короче так:
map.computeIfAbsent(key, k -> new ArrayList<>()).add(newElement);
Компилятор Java сам поймёт, какой тип ArrayList нужно создать, исходя из типа Map
(в данном случае Map<String, List<String>>).
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20❤2
Docker это не мини-виртуальные машины
Многие пользуются Docker каждый день, но не особо понимают, что происходит под капотом. Это становится очевидно, когда начинаются проблемы с производительностью, правами доступа или сетью.
1. Docker — это не виртуалка
Когда ты запускаешь docker run ubuntu, ты не создаешь «мини-компьютер».
Никакого нового ядра нет.
Контейнер использует то же ядро, что и хост — просто в изоляции.
Поэтому он стартует за миллисекунды и ест меньше оперативки, чем виртуальная машина.
2. Изоляция достигается через namespaces
Namespace говорит системе:
«Этот процесс видит только вот этот кусочек мира».
Примеры:
PID namespace — контейнер думает, что его процесс 1 — единственный.
NET namespace — у контейнера своя сеть и интерфейсы.
MNT namespace — своя файловая система с примонтированными ресурсами.
Так несколько контейнеров могут спокойно жить рядом, не мешая друг другу.
3. Cgroups ограничивают ресурсы
Контейнер может считать, что владеет всей машиной…
но control groups (cgroups) ставят реальные лимиты:
- максимум CPU (--cpus=2)
- максимум памяти (--memory=1g)
- лимиты на I/O или количество процессов
Если контейнер пытается выйти за рамки, ядро его просто отрубает😅
4. Файловая система и слои (UnionFS)
Образ это не один файл, а набор слоев.
Каждая инструкция RUN, COPY или ADD в Dockerfile создает новый слой.
Когда контейнер запускается, поверх монтируется временный слой для записи.
Вот почему все изменения исчезают после удаления контейнера —
временный слой просто выкидывается.
5. Как контейнеры общаются между собой
Docker создает виртуальные сети внутри хоста.
Каждый контейнер получает свой внутренний IP.
Когда ты используешь --link или docker-compose,
ты просто задаешь внутренние DNS-имена.
Во внешний мир трафик не выходит,
пока ты сам не пробросишь порт через -p 8080:80.
6. Безопасность -изоляция не идеальна
Поскольку ядро общее, защита не такая жесткая, как у виртуалки.
Для этого и придуманы штуки вроде:
gVisor — песочница для системных вызовов
SELinux или AppArmor
rootless Docker
Если запускаешь сторонние контейнеры — лучше использовать их.
Так что же такое Docker на самом деле?
Это оркестратор изоляции:
- прячет ресурсы через namespaces
- ограничивает их через cgroups
- управляет файловой системой через UnionFS
- эмулирует сеть через network namespaces
Он ничего не эмулирует по-настоящему,
он просто создает очень правдоподобную иллюзию👏
Понимание этого полностью меняет подход к дебагу и оптимизации.
Контейнер жрет всю оперативку? → смотри cgroups.
Не может подключиться к сети? → проверь network namespace.
Билды весят по 5 ГБ? → разбери слои образа.
👉 Java Portal
Многие пользуются Docker каждый день, но не особо понимают, что происходит под капотом. Это становится очевидно, когда начинаются проблемы с производительностью, правами доступа или сетью.
1. Docker — это не виртуалка
Когда ты запускаешь docker run ubuntu, ты не создаешь «мини-компьютер».
Никакого нового ядра нет.
Контейнер использует то же ядро, что и хост — просто в изоляции.
Поэтому он стартует за миллисекунды и ест меньше оперативки, чем виртуальная машина.
2. Изоляция достигается через namespaces
Namespace говорит системе:
«Этот процесс видит только вот этот кусочек мира».
Примеры:
PID namespace — контейнер думает, что его процесс 1 — единственный.
NET namespace — у контейнера своя сеть и интерфейсы.
MNT namespace — своя файловая система с примонтированными ресурсами.
Так несколько контейнеров могут спокойно жить рядом, не мешая друг другу.
3. Cgroups ограничивают ресурсы
Контейнер может считать, что владеет всей машиной…
но control groups (cgroups) ставят реальные лимиты:
- максимум CPU (--cpus=2)
- максимум памяти (--memory=1g)
- лимиты на I/O или количество процессов
Если контейнер пытается выйти за рамки, ядро его просто отрубает
4. Файловая система и слои (UnionFS)
Образ это не один файл, а набор слоев.
Каждая инструкция RUN, COPY или ADD в Dockerfile создает новый слой.
Когда контейнер запускается, поверх монтируется временный слой для записи.
Вот почему все изменения исчезают после удаления контейнера —
временный слой просто выкидывается.
5. Как контейнеры общаются между собой
Docker создает виртуальные сети внутри хоста.
Каждый контейнер получает свой внутренний IP.
Когда ты используешь --link или docker-compose,
ты просто задаешь внутренние DNS-имена.
Во внешний мир трафик не выходит,
пока ты сам не пробросишь порт через -p 8080:80.
6. Безопасность -изоляция не идеальна
Поскольку ядро общее, защита не такая жесткая, как у виртуалки.
Для этого и придуманы штуки вроде:
gVisor — песочница для системных вызовов
SELinux или AppArmor
rootless Docker
Если запускаешь сторонние контейнеры — лучше использовать их.
Так что же такое Docker на самом деле?
Это оркестратор изоляции:
- прячет ресурсы через namespaces
- ограничивает их через cgroups
- управляет файловой системой через UnionFS
- эмулирует сеть через network namespaces
Он ничего не эмулирует по-настоящему,
он просто создает очень правдоподобную иллюзию
Понимание этого полностью меняет подход к дебагу и оптимизации.
Контейнер жрет всю оперативку? → смотри cgroups.
Не может подключиться к сети? → проверь network namespace.
Билды весят по 5 ГБ? → разбери слои образа.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤2
Представим, что тебе нужно посчитать максимальную зарплату сотрудников по отделам на Java. Код мог бы выглядеть так:
Но этот цикл можно записать гораздо короче:
Метод Map.merge() работает так:
Первый параметр — это ключ, по которому ты хочешь добавить или обновить значение в Map.
Второй параметр — новое значение, которым нужно обновить сохранённое.
Если для этого ключа значение отсутствует (или там null), Map просто сохраняет новое значение.
Третий параметр — это функция. Если в Map уже есть значение для этого ключа, функция вызывается с двумя аргументами: старым и новым значением. То, что вернёт эта функция, и будет записано как новое значение для ключа.
👉 Java Portal
List<Employee> employees = new ArrayList<>();
Map<String, Long> maxMap = new HashMap<>();
for (Employee employee : employees) {
Long maxSalaryForDepartment = maxMap.get(employee.department);
if (maxSalaryForDepartment == null || maxSalaryForDepartment < employee.salary) {
maxMap.put(employee.department, employee.salary);
}
}
Но этот цикл можно записать гораздо короче:
for (Employee employee : employees) {
maxMap.merge(employee.department, employee.salary, Math::max);
}
Метод Map.merge() работает так:
Первый параметр — это ключ, по которому ты хочешь добавить или обновить значение в Map.
Второй параметр — новое значение, которым нужно обновить сохранённое.
Если для этого ключа значение отсутствует (или там null), Map просто сохраняет новое значение.
Третий параметр — это функция. Если в Map уже есть значение для этого ключа, функция вызывается с двумя аргументами: старым и новым значением. То, что вернёт эта функция, и будет записано как новое значение для ключа.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9