Критикую JDK: метод hashCode
Сегодня на простом примере из JDK покажу, как НЕ нужно проектировать апи. Обычно всю теорию по этой теме можно свести к “делайте хорошо, а плохо не делайте”. Разбор ошибок - очень наглядный способ посмотреть все на практике.
Поэтому углубимся в два метода класса Objects:
Человек, который видит их в первый раз, обязательно спросит
🤔 Чем отличается hash и hashCode?
🤔 Почему они выдают разные результаты?
(ответ на вопрос перед постом: hash3 отличается от остальных хэшей)
В хорошем апи у пользователя минимум вопросов, как им пользоваться. В идеале даже смотреть документацию на нужно. Это, наверное, самый главный принцип проектирования.
В нашем случае приходится лезть в доку и исходный код👎
Что увидим внутри:
▫️ hashCode принимает 1 аргумент - Object, и возвращает его хэш
▫️ hash принимает массив объектов переменной длины, итоговый хэш считается на основе переданных полей. Чтобы учесть порядок, hash производит дополнительные вычисления, даже для одного поля. Поэтому итоговые хэши отличаются.
Супер, я разобралась, но зачем мне в это погружаться? Я хочу просто посчитать хэш объекта, а не вникать в разницу 2 методов. Тратить даже 5 минут на такую простейшую задачу отвратительно. Bad user experience во всей красе.
В случае хэшей исправить ситуацию элементарно, просто дать методам одинаковые имена:
Если передать один обьект, java не запутается и вызовет нужный метод. Плюсы очевидны:
✅ Не надо думать, какой метод вызвать
✅ Хэшкод обьекта будет одинаков в любом месте кода
Почему так не сделано изначально?
Я пыталась найти ответ, смотрела старые исходники и доки. Не нашла ни одной причины, почему имена разные и результаты не согласованы.
Видимо автор решил, что и так сойдёт.
Оставим это на совести разработчиков JDK, а себе заберём следующие выводы:
▫️ В хорошем апи пользователю не нужно смотреть документацию, чтобы сделать базовые вещи
▫️ Если в апи есть неочевидные моменты, их надо поправить, а не писать в доке warning
▫️ Если пользователь пошел смотреть исходники, чтобы разобраться - это полный провал
Чем сложнее система, тем важнее вкладываться в простоту. Простоту понимания, чтения, поддержки. Даже собственный код через пару месяцев выглядит как чужой. Понятные названия переменных, классов и методов, непротиворечивость - база для комфортной работы❤️
Это уже не первый пост, где я разбираю неудачные методы в JDK. Если вам хочется больше, предлагаю почитать
🤦 Критикую метод HashMap из java 20
🤦 Критикую методы StreamAPI в java 22
🤦 Критикую методы BigDecimal
Сегодня на простом примере из JDK покажу, как НЕ нужно проектировать апи. Обычно всю теорию по этой теме можно свести к “делайте хорошо, а плохо не делайте”. Разбор ошибок - очень наглядный способ посмотреть все на практике.
Поэтому углубимся в два метода класса Objects:
Objects.hashCode(Object)
Objects.hash(Object…)
Человек, который видит их в первый раз, обязательно спросит
🤔 Чем отличается hash и hashCode?
🤔 Почему они выдают разные результаты?
В хорошем апи у пользователя минимум вопросов, как им пользоваться. В идеале даже смотреть документацию на нужно. Это, наверное, самый главный принцип проектирования.
В нашем случае приходится лезть в доку и исходный код👎
Что увидим внутри:
▫️ hashCode принимает 1 аргумент - Object, и возвращает его хэш
▫️ hash принимает массив объектов переменной длины, итоговый хэш считается на основе переданных полей. Чтобы учесть порядок, hash производит дополнительные вычисления, даже для одного поля. Поэтому итоговые хэши отличаются.
Супер, я разобралась, но зачем мне в это погружаться? Я хочу просто посчитать хэш объекта, а не вникать в разницу 2 методов. Тратить даже 5 минут на такую простейшую задачу отвратительно. Bad user experience во всей красе.
В случае хэшей исправить ситуацию элементарно, просто дать методам одинаковые имена:
Objects.hashCode(Object)
Objects.hashCode(Object…)
Если передать один обьект, java не запутается и вызовет нужный метод. Плюсы очевидны:
✅ Не надо думать, какой метод вызвать
✅ Хэшкод обьекта будет одинаков в любом месте кода
Почему так не сделано изначально?
Я пыталась найти ответ, смотрела старые исходники и доки. Не нашла ни одной причины, почему имена разные и результаты не согласованы.
Видимо автор решил, что и так сойдёт.
Оставим это на совести разработчиков JDK, а себе заберём следующие выводы:
▫️ В хорошем апи пользователю не нужно смотреть документацию, чтобы сделать базовые вещи
▫️ Если в апи есть неочевидные моменты, их надо поправить, а не писать в доке warning
▫️ Если пользователь пошел смотреть исходники, чтобы разобраться - это полный провал
Чем сложнее система, тем важнее вкладываться в простоту. Простоту понимания, чтения, поддержки. Даже собственный код через пару месяцев выглядит как чужой. Понятные названия переменных, классов и методов, непротиворечивость - база для комфортной работы❤️
Это уже не первый пост, где я разбираю неудачные методы в JDK. Если вам хочется больше, предлагаю почитать
🤦 Критикую метод HashMap из java 20
🤦 Критикую методы StreamAPI в java 22
🤦 Критикую методы BigDecimal
Spring Security: основная архитектура
Разгребала на выходных опрос по непоняткам в спринге. Один из самых частых вопросов касается Spring Security.
Для многих это самый непонятный модуль. Он есть на каждом проекте, понимать его надо, но до чего же тяжело разбираться! Сама была в такой ситуации не раз, ловила огромное сопротивление, когда приходили задачи на секьюрити.
К счастью, с годами ситуация прояснилась. Секрет успеха по работе с Security лежит в понимании двух вещей:
🔶 Используемый механизм безопасности
LDAP, JWT, разные схемы oAuth и SSO. Роли, белые/черные списки, секреты, токены и тд. В чистом виде большинство механизмов несложные, на ютубе отлично объясняются за 10 минут.
🔶 Базовая архитектура Spring Security
Об этом и будет пост. Опишу простыми словами, что происходит в классической (не реактивной) архитектуре, и как решать задачки с секьюрити на практике.
Spring Security участвует в двух процессах:
1️⃣ Путь запроса до контроллера и обратно
Схема такая:
▪️ Сервер получает запрос
▪️ Проводит его через цепочку фильтров, у каждого из которых своя задача:
▫️ CsrfFilter проверяет CSRF токен
▫️ SessionManagementFilter разбирается с сессией
И так далее, обычно 10-15 фильтров в итоге
▪️ Запрос попадает в контроллер
▪️ Выполняется бизнес-логика проекта
▪️ Контроллер возвращает ответ
▪️ Ответ проходит по той же цепочке фильтров, но в обратном порядке. Фильтры выставят у ответа нужные заголовки, обнулят/сохранят сессию и тд
▪️ Ответ возвращается пользователю
Что тут важно:
▫️ Проверка прав, заголовков и многие секьюрити штуки часто обрабатываются в фильтрах ДО вызова контроллера
▫️ Можно поменять содержимое, заголовки и другие части ответа ПОСЛЕ выхода из контроллера
▫️ Каждый фильтр может менять запрос/ответ или контекст, поэтому порядок фильтров важен
▫️За конфигурацию фильтров отвечает метод конфига
Чтобы увидеть процесс наглядно, поставьте брейкпойнт в методе
2️⃣ Авторизация/аутентификация
скрывается за фасадом — бином AuthenticationManager.
Чтобы авторизовать пользователя, в коде проекта нужно вызвать метод
Может в фильтре, может в контроллере, зависит от механизма безопасности.
Метод authenticate пройдётся по всем заданным источникам авторизации. Результат можно положить в SecurityContextHolder, тогда он будет доступен из любого места в коде. Также бросится событие AuthenticationSuccessEvent, но обычно его игнорируют:)
Источники авторизации называются *Provider (например, DaoAuthenticationProvider) и определяются в конфиге. В итоге всё, что нужно от разработчика это:
☝️определить провайдеры
✌️ вызвать authenticate в нужном месте
Посмотреть список текущих провайдеров: брейкпойнт в методе
Задать список провайдеров: метод
🦄Что с этим знанием делать на практике
Если вам дали задачу, связанную с Spring Security, не бросайтесь сразу гуглить
spring security jwt/oauth/ldap example
Шанс запутаться и сделать не то стремится к 100%. Если у вас крупный и сложный проект, идеально подходящего туториала точно не будет.
Лучше проработать каждую часть по порядку:
🛡️ Разобраться в механизме безопасности, который нужно реализовать. Что, откуда и как передаётся, где, когда и как валидируется и сохраняется.
Это самый важный шаг! Обязательно подключите тестировщика, ему тоже надо в это погрузиться.
🛡️ Прикинуть, как полученная схема ложится на архитектуру секьюрити. Что сделать в фильтрах до обработки запроса, а что после. Возможно, что-то надо сделать внутри слоя сервисов
🛡️ Желательно обсудить полученное решение со старшим товарищем
Только после этого смотрите документацию, туториалы и собирайте из них решение.
Действуя по плану выше, вы потратите меньше времени и сохраните высокую самооценку🙂
Разгребала на выходных опрос по непоняткам в спринге. Один из самых частых вопросов касается Spring Security.
Для многих это самый непонятный модуль. Он есть на каждом проекте, понимать его надо, но до чего же тяжело разбираться! Сама была в такой ситуации не раз, ловила огромное сопротивление, когда приходили задачи на секьюрити.
К счастью, с годами ситуация прояснилась. Секрет успеха по работе с Security лежит в понимании двух вещей:
🔶 Используемый механизм безопасности
LDAP, JWT, разные схемы oAuth и SSO. Роли, белые/черные списки, секреты, токены и тд. В чистом виде большинство механизмов несложные, на ютубе отлично объясняются за 10 минут.
🔶 Базовая архитектура Spring Security
Об этом и будет пост. Опишу простыми словами, что происходит в классической (не реактивной) архитектуре, и как решать задачки с секьюрити на практике.
Spring Security участвует в двух процессах:
1️⃣ Путь запроса до контроллера и обратно
Схема такая:
▪️ Сервер получает запрос
▪️ Проводит его через цепочку фильтров, у каждого из которых своя задача:
▫️ CsrfFilter проверяет CSRF токен
▫️ SessionManagementFilter разбирается с сессией
И так далее, обычно 10-15 фильтров в итоге
▪️ Запрос попадает в контроллер
▪️ Выполняется бизнес-логика проекта
▪️ Контроллер возвращает ответ
▪️ Ответ проходит по той же цепочке фильтров, но в обратном порядке. Фильтры выставят у ответа нужные заголовки, обнулят/сохранят сессию и тд
▪️ Ответ возвращается пользователю
Что тут важно:
▫️ Проверка прав, заголовков и многие секьюрити штуки часто обрабатываются в фильтрах ДО вызова контроллера
▫️ Можно поменять содержимое, заголовки и другие части ответа ПОСЛЕ выхода из контроллера
▫️ Каждый фильтр может менять запрос/ответ или контекст, поэтому порядок фильтров важен
▫️За конфигурацию фильтров отвечает метод конфига
WebSecurityConfigurerAdapter#configure(HttpSecurity http)
Чтобы увидеть процесс наглядно, поставьте брейкпойнт в методе
FilterChainProxy#doFilterInternal
. Там увидите все фильтры, можно погулять по ним и отследить, что происходит с запросом2️⃣ Авторизация/аутентификация
скрывается за фасадом — бином AuthenticationManager.
Чтобы авторизовать пользователя, в коде проекта нужно вызвать метод
authenticationManager.authenticate
Может в фильтре, может в контроллере, зависит от механизма безопасности.
Метод authenticate пройдётся по всем заданным источникам авторизации. Результат можно положить в SecurityContextHolder, тогда он будет доступен из любого места в коде. Также бросится событие AuthenticationSuccessEvent, но обычно его игнорируют:)
Источники авторизации называются *Provider (например, DaoAuthenticationProvider) и определяются в конфиге. В итоге всё, что нужно от разработчика это:
☝️определить провайдеры
✌️ вызвать authenticate в нужном месте
Посмотреть список текущих провайдеров: брейкпойнт в методе
ProviderManager#authenticate
.Задать список провайдеров: метод
configure(AuthenticationManagerBuilder auth)
в классе WebSecurityConfigurerAdapter
🦄Что с этим знанием делать на практике
Если вам дали задачу, связанную с Spring Security, не бросайтесь сразу гуглить
spring security jwt/oauth/ldap example
Шанс запутаться и сделать не то стремится к 100%. Если у вас крупный и сложный проект, идеально подходящего туториала точно не будет.
Лучше проработать каждую часть по порядку:
🛡️ Разобраться в механизме безопасности, который нужно реализовать. Что, откуда и как передаётся, где, когда и как валидируется и сохраняется.
Это самый важный шаг! Обязательно подключите тестировщика, ему тоже надо в это погрузиться.
🛡️ Прикинуть, как полученная схема ложится на архитектуру секьюрити. Что сделать в фильтрах до обработки запроса, а что после. Возможно, что-то надо сделать внутри слоя сервисов
🛡️ Желательно обсудить полученное решение со старшим товарищем
Только после этого смотрите документацию, туториалы и собирайте из них решение.
Действуя по плану выше, вы потратите меньше времени и сохраните высокую самооценку🙂
Вопрос на знание Stream API. Что делает метод flatMap(Optional::stream)?
Anonymous Poll
30%
Превращает стрим из List<Optional> в стрим Optional
22%
Превращает стрим Optional<Optional> в стрим Optional
13%
Оборачивает значения в Optional
24%
Извлекает непустые значения из набора Optional
10%
Ничего, это рандомный набор java слов
Зачем нужен и чем плох
Когда ты в разработке 10+ лет, сложно чему-то удивиться. Кажется, что JDK изучен вдоль и поперек, и этот мир абсолютно понятен.
Но недавно мне встретился странныйзверёк метод, и это вылилось в очередной пост серии “критикую JDK”.
Допустим, вы увидели в коде
Может быть, метод нужен, чтобы сделать из Optional стрим и использовать методы Stream API?
Хотя интернет предлагает нам статьи с подобными примерами, включим здравый смысл.
Зачем так усложнять код? Зачем из одного значения делать стрим?
Оставим авторов статей молча краснеть и продолжим исследование.
📚 В документации находим больше интересных деталей:
▫️ Если в Optional есть значение, метод вернёт стрим с этим значением.
▫️ Если Optional пустой, вернётся пустой стрим.
Также узнаем, что метод предназначен для работы с flatMap.
Например, некоторые пользователи указывают емейл, а некоторые нет. Если getEmail возвращает Optional, список емейлов получаем так:
А теперь критика. Ваша любимая часть:) Что с этим методом не так, и как можно сделать лучше?
Метод нужен, чтобы вытащить данные из набора Optional. В одну строчку это не сделать, минимум в две:
Новый метод делает все в одну строку, но
Почему. Такой. Ужасный. Дизайн.
👎 Метод находится в классе Optional, но бесполезен для Optional обьекта
👎 Метод заточен только под flatMap
👎 Выглядит непонятно. flatMap(Optional::stream) похож на набор слов, приходится напрячься, чтобы его понять. Сравните с очаровательными filter, map, count, findFirst🥰
👎 Сомнительная реализация. Сначала плодим маленькие стримы из каждого элемента, потом их соединяем. Столько лишних действий!
Как можно по-другому?
Я бы добавила в Stream API отдельный метод, “очищающий” текущий стрим от лишнего. Пусть он будет универсальным:
▫️ Для стрима из Optional возвращает стрим значений, empty пропускаются
▫️ Для прочих стримов убирает null элементы
Название под вопросом. Мне нравится getValues(), но можно и получше придумать.
И всё, незачем усложнять класс Optional и плодить сложные конструкции!
Простота и ясность - наше всё. Не знаю, чем думал автор метода и куда смотрели ревьюеры. Мы с вами точно бы такое не пропустили🔥
Optional.stream()
?Когда ты в разработке 10+ лет, сложно чему-то удивиться. Кажется, что JDK изучен вдоль и поперек, и этот мир абсолютно понятен.
Но недавно мне встретился странный
Допустим, вы увидели в коде
Optional.stream().
Попробуем угадать, что он делает.Может быть, метод нужен, чтобы сделать из Optional стрим и использовать методы Stream API?
Хотя интернет предлагает нам статьи с подобными примерами, включим здравый смысл.
Зачем так усложнять код? Зачем из одного значения делать стрим?
Оставим авторов статей молча краснеть и продолжим исследование.
📚 В документации находим больше интересных деталей:
▫️ Если в Optional есть значение, метод вернёт стрим с этим значением.
▫️ Если Optional пустой, вернётся пустой стрим.
Также узнаем, что метод предназначен для работы с flatMap.
Например, некоторые пользователи указывают емейл, а некоторые нет. Если getEmail возвращает Optional, список емейлов получаем так:
List emails = users.stream().map(User::getEmail).flatMap(Optional::stream).toList();
А теперь критика. Ваша любимая часть:) Что с этим методом не так, и как можно сделать лучше?
Метод нужен, чтобы вытащить данные из набора Optional. В одну строчку это не сделать, минимум в две:
.filter(Optional::isPresent)
.map(Optional::get)
Новый метод делает все в одну строку, но
Почему. Такой. Ужасный. Дизайн.
👎 Метод находится в классе Optional, но бесполезен для Optional обьекта
👎 Метод заточен только под flatMap
👎 Выглядит непонятно. flatMap(Optional::stream) похож на набор слов, приходится напрячься, чтобы его понять. Сравните с очаровательными filter, map, count, findFirst🥰
👎 Сомнительная реализация. Сначала плодим маленькие стримы из каждого элемента, потом их соединяем. Столько лишних действий!
Как можно по-другому?
Я бы добавила в Stream API отдельный метод, “очищающий” текущий стрим от лишнего. Пусть он будет универсальным:
▫️ Для стрима из Optional возвращает стрим значений, empty пропускаются
▫️ Для прочих стримов убирает null элементы
Название под вопросом. Мне нравится getValues(), но можно и получше придумать.
И всё, незачем усложнять класс Optional и плодить сложные конструкции!
Простота и ясность - наше всё. Не знаю, чем думал автор метода и куда смотрели ревьюеры. Мы с вами точно бы такое не пропустили🔥
Любимые подписчики!
Конец года - прекрасное время для благодарностей и подведения итогов. От всей души, спасибо, что читаете❤️ Спасибо за ваш интерес к разработке и повышению знаний. На таких людях наша сфера держится и развивается!
Лучшие посты этого года:
⭐ Паттерн Сага
⭐ Как быстро отдавать данные по частям
⭐ Полезное в PostgreSQL
⭐ Новая фича Java 23: Derived Record
⭐ Как считается хэшкод по умолчанию?
⭐ Нужно ли высшее образование для разработчика?
⭐ Как вредит ChatGPT
⭐ От JDBC до Spring Data
Почитайте, если что то пропустили. Или не читайте:)
Желаю вам хорошо отдохнуть на каникулах и выспаться, а в следующем году покорить новые профессиональные и финансовые высоты!
С наступающим новым годом, друзья🎄
Конец года - прекрасное время для благодарностей и подведения итогов. От всей души, спасибо, что читаете❤️ Спасибо за ваш интерес к разработке и повышению знаний. На таких людях наша сфера держится и развивается!
Лучшие посты этого года:
⭐ Паттерн Сага
⭐ Как быстро отдавать данные по частям
⭐ Полезное в PostgreSQL
⭐ Новая фича Java 23: Derived Record
⭐ Как считается хэшкод по умолчанию?
⭐ Нужно ли высшее образование для разработчика?
⭐ Как вредит ChatGPT
⭐ От JDBC до Spring Data
Почитайте, если что то пропустили. Или не читайте:)
Желаю вам хорошо отдохнуть на каникулах и выспаться, а в следующем году покорить новые профессиональные и финансовые высоты!
С наступающим новым годом, друзья🎄
Как избежать выгорания, часть 1
В конце года люди делятся на две группы. Одна бодро подводит итоги года и строит планы на следующий. Другая устало лежит на диване и смотрит в стену.
Сегодня хочу поговорить про выгорание.
Среди разработчиков выгорание случается сплошь и рядом. А многие живут в таком состоянии несколько лет!
Обычно под выгоранием понимают апатию и потерю интереса к работе. Но это только одна из граней. Выгорание делится на 4 стадии, каждая отличается своим “настроением”, симптомами и лечением. В этом посте обсудим первую стадию:
Накопление усталости
Обычно начинается, когда работа занимает слишком много места в жизни:
🧑💻 Нет четкого разделения работа/отдых, работа размазывается на весь день
🧑💻 В выходные часто думаете над рабочими задачами
🧑💻 Постоянно проходите курсы, смотрите конференции и учитесь
🧑💻 Нацелились на 5+ на перформанс ревью и очень стараетесь
🧑💻 Пришли на новый проект и хотите быстро показать отличные результаты
Ничего удивительного, если много работать, то устанешь:) Но есть и менее очевидные ситуации.
Представьте, работа вам очень нравится. Задачи интересные, коллеги вдохновляют. С работы не хочется уходить. Казалось бы, мечта.
Но в перспективе все не так радужно. И переработки, и горящие глаза со временем приводят к накоплению стресса и усталости.
Как понять, что я на первой стадии выгорания?
Вы можете чувствовать себя нормально. Большинство людей не чувствуют усталости, пока она не станет 10/10.
На первой стадии остальные сферы жизни проседают, а сама жизнь концентрируется вокруг работы. Человек становится менее радостным, скучным, не любопытным.
Все в целом нормально, но довольно серенько.
Психологи рекомендуют периодически оценивать свое состояние и учиться замечать усталость на ранней стадии. Не когда она 10/10, а хотя бы 5/10.
Если это звучит слишком абстрактно, вот пара приемов, как косвенно оценить свое состояние:
🤔 Посмотреть, как с активностями после работы и с остальными сферами жизни. Если на них все чаще не остаётся ни времени, ни сил, если по вечерам и на выходных хочется полежать, это плохой знак
🤔 Оценить работоспособность в привычные “пики продуктивности”. У кого-то работа идёт бодрее с утра, у других - после обеда. Кто-то в понедельник делает большую часть дел, кто-то окрыляется в четверг. Но если запланированной продуктивности не случается, значит в организме копится усталость
Что делать?
Из первой стадии выбраться довольно просто:
✅ Временно снизить рабочие активности
✅ Четко определить рабочее время
✅ Спланировать вечера и выходные заранее: спорт, прогулки, общение, хобби. Что угодно, не связанное с работой
Если в первые рабочие дни после нового года вы чувствовали себя бодро, запомните это состояние. Это состояние отдохнувшего человека, оно нам нужно почаще:)
В следующем посте обсудим более тяжёлые стадии выгорания. Но давайте не попадать даже в первую. Пусть горят только огоньки под постами, а вас выгорание обойдет стороной🔥
В конце года люди делятся на две группы. Одна бодро подводит итоги года и строит планы на следующий. Другая устало лежит на диване и смотрит в стену.
Сегодня хочу поговорить про выгорание.
Среди разработчиков выгорание случается сплошь и рядом. А многие живут в таком состоянии несколько лет!
Обычно под выгоранием понимают апатию и потерю интереса к работе. Но это только одна из граней. Выгорание делится на 4 стадии, каждая отличается своим “настроением”, симптомами и лечением. В этом посте обсудим первую стадию:
Накопление усталости
Обычно начинается, когда работа занимает слишком много места в жизни:
🧑💻 Нет четкого разделения работа/отдых, работа размазывается на весь день
🧑💻 В выходные часто думаете над рабочими задачами
🧑💻 Постоянно проходите курсы, смотрите конференции и учитесь
🧑💻 Нацелились на 5+ на перформанс ревью и очень стараетесь
🧑💻 Пришли на новый проект и хотите быстро показать отличные результаты
Ничего удивительного, если много работать, то устанешь:) Но есть и менее очевидные ситуации.
Представьте, работа вам очень нравится. Задачи интересные, коллеги вдохновляют. С работы не хочется уходить. Казалось бы, мечта.
Но в перспективе все не так радужно. И переработки, и горящие глаза со временем приводят к накоплению стресса и усталости.
Как понять, что я на первой стадии выгорания?
Вы можете чувствовать себя нормально. Большинство людей не чувствуют усталости, пока она не станет 10/10.
На первой стадии остальные сферы жизни проседают, а сама жизнь концентрируется вокруг работы. Человек становится менее радостным, скучным, не любопытным.
Все в целом нормально, но довольно серенько.
Психологи рекомендуют периодически оценивать свое состояние и учиться замечать усталость на ранней стадии. Не когда она 10/10, а хотя бы 5/10.
Если это звучит слишком абстрактно, вот пара приемов, как косвенно оценить свое состояние:
🤔 Посмотреть, как с активностями после работы и с остальными сферами жизни. Если на них все чаще не остаётся ни времени, ни сил, если по вечерам и на выходных хочется полежать, это плохой знак
🤔 Оценить работоспособность в привычные “пики продуктивности”. У кого-то работа идёт бодрее с утра, у других - после обеда. Кто-то в понедельник делает большую часть дел, кто-то окрыляется в четверг. Но если запланированной продуктивности не случается, значит в организме копится усталость
Что делать?
Из первой стадии выбраться довольно просто:
✅ Временно снизить рабочие активности
✅ Четко определить рабочее время
✅ Спланировать вечера и выходные заранее: спорт, прогулки, общение, хобби. Что угодно, не связанное с работой
Если в первые рабочие дни после нового года вы чувствовали себя бодро, запомните это состояние. Это состояние отдохнувшего человека, оно нам нужно почаще:)
В следующем посте обсудим более тяжёлые стадии выгорания. Но давайте не попадать даже в первую. Пусть горят только огоньки под постами, а вас выгорание обойдет стороной🔥
Что делать при выгорании?
В прошлом посте рассмотрели первую стадию выгорания, сегодня обсудим остальные.
Вторая стадия: усталость
Состояние, которое обычно и понимают под выгоранием. Лень, прокрастинация, работа не радует, коллеги бесят. Сон ухудшается, стресс заедается. Сил нет, выходные проходят на диване. Здоровье начинает подводить.
Здесь человек сам чувствует, что сильно устал. Решение очевидно - пора в отпуск! Но сознание загоняет в ловушку: чем ниже продуктивность, тем выше чувство вины, и тем сложнее разрешить себе уехать на отдых.
Но это нужно сделать. Само такое состояние не пройдет. Чем быстрее примете меры, тем быстрее вернётесь к нормальной жизни.
Как отдохнуть?
Отличная схема описана в old but gold статье Разъ*б, безделье, чилл. Хороший отдых состоит из 3 частей:
🤪 Перезагрузка. Активный отдых, путешествия, новые опыт и знакомства. Чем непривычнее, тем лучше получится отвлечься
🤗 Гедонизм. Заняться любимыми делами. Хобби, еда, сериалы, игры, что угодно. Никакого чувства вины, все для себя любимого!
🥱 Скука. Дать мозгу отдохнуть от потоков информации и впечатлений: много спать, гулять на природе, смотреть в окно, наблюдать за волнами.
Не всегда для полноценного отдыха хватает двух недель отпуска. Я обычно увольняюсь и отдыхаю 2-3 месяца. Если отдых прошел успешно, вы почувствуете, что соскучились по работе. Очень приятное чувство🥰
Но если не отдыхать и продолжать копить усталость, рано или поздно наступит
Третья стадия: обратимое истощение
Апатия трансформируется в злость. Человека все раздражает, токсичность зашкаливает, идет огромное сопротивление к работе и саботаж. Может появиться депрессия и тревожность, организм начинает разваливаться то тут, то там.
Здесь отпуск уже не поможет. Нужен длительный перерыв от полугода.
Четвертая стадия: необратимое выгорание
Ненависть к работе, деформация личности, рушатся все сферы жизни. Вернуться к нормальной жизни очень сложно, поможет только смена деятельности и помощь психиатра.
3 и 4 стадии встречаются редко, поэтому описала их кратко. В айти в основном все сидят в первой-второй.
Физическое и психическое здоровье - это база. Без него не получится достигнуть целей, реализовать свои амбиции и таланты. Так что заботимся о себе, отдыхаем почаще. Тогда найдутся силы на рабочие подвиги и профессиональный рост в новом году💪
В прошлом посте рассмотрели первую стадию выгорания, сегодня обсудим остальные.
Вторая стадия: усталость
Состояние, которое обычно и понимают под выгоранием. Лень, прокрастинация, работа не радует, коллеги бесят. Сон ухудшается, стресс заедается. Сил нет, выходные проходят на диване. Здоровье начинает подводить.
Здесь человек сам чувствует, что сильно устал. Решение очевидно - пора в отпуск! Но сознание загоняет в ловушку: чем ниже продуктивность, тем выше чувство вины, и тем сложнее разрешить себе уехать на отдых.
Но это нужно сделать. Само такое состояние не пройдет. Чем быстрее примете меры, тем быстрее вернётесь к нормальной жизни.
Как отдохнуть?
Отличная схема описана в old but gold статье Разъ*б, безделье, чилл. Хороший отдых состоит из 3 частей:
🤪 Перезагрузка. Активный отдых, путешествия, новые опыт и знакомства. Чем непривычнее, тем лучше получится отвлечься
🤗 Гедонизм. Заняться любимыми делами. Хобби, еда, сериалы, игры, что угодно. Никакого чувства вины, все для себя любимого!
🥱 Скука. Дать мозгу отдохнуть от потоков информации и впечатлений: много спать, гулять на природе, смотреть в окно, наблюдать за волнами.
Не всегда для полноценного отдыха хватает двух недель отпуска. Я обычно увольняюсь и отдыхаю 2-3 месяца. Если отдых прошел успешно, вы почувствуете, что соскучились по работе. Очень приятное чувство🥰
Но если не отдыхать и продолжать копить усталость, рано или поздно наступит
Третья стадия: обратимое истощение
Апатия трансформируется в злость. Человека все раздражает, токсичность зашкаливает, идет огромное сопротивление к работе и саботаж. Может появиться депрессия и тревожность, организм начинает разваливаться то тут, то там.
Здесь отпуск уже не поможет. Нужен длительный перерыв от полугода.
Четвертая стадия: необратимое выгорание
Ненависть к работе, деформация личности, рушатся все сферы жизни. Вернуться к нормальной жизни очень сложно, поможет только смена деятельности и помощь психиатра.
3 и 4 стадии встречаются редко, поэтому описала их кратко. В айти в основном все сидят в первой-второй.
Физическое и психическое здоровье - это база. Без него не получится достигнуть целей, реализовать свои амбиции и таланты. Так что заботимся о себе, отдыхаем почаще. Тогда найдутся силы на рабочие подвиги и профессиональный рост в новом году💪
Паттерн Bulkhead
- паттерн для повышения надёжности. Сегодня расскажу несколько способов, как его реализовать и подсвечу неочевидный момент при работе с паттернами в целом.
В чем суть?
Название Bulkhead пришло из кораблестроения. Корабль делится на независимые отсеки. Если в одном отсеке появится дыра, водой заполнится только он. Остальные части будут в порядке, и корабль хоть как-то продолжит плыть.
Главная идея паттерна - разграничить ресурсы, чтобы ошибка в одном компоненте не повлияла на работу других.
Реализовать паттерн можно на трёх уровнях:
▫️ Docker контейнер
▫️ Приложение (jar-файл)
▫️ Внутри сервиса
На каждом уровне свои ресурсы и возможности. Пойдем по порядку:
✨Docker контейнер✨
и остальные инструменты виртуализации дают контроль над главными ресурсами - памятью и потреблением CPU:
Если в одном сервисе произойдет беда, остальные будут работать как ни в чем ни бывало.
✨Приложение✨
Здесь возможности поменьше. JVM не контролирует загруженность процессора, этим занимается ОС. Зато тщательно считает свои обьекты и занятую память, мы можем задать рамки флажками типа -Xmx.
✨Внутри сервиса✨
В java коде нельзя явно ограничить количество памяти и нагрузку на цпу. Но в нашей власти повлиять на количество потоков, как следствие - ограничить число запросов для разных эндпойнтов.
Для этого Spring предлагает аннотацию @Bulkhead:
В проперти maxThreadPoolSize настраиваем максимальное число потоков.
Ограничение запросов иногда полезно, но это ненадежная реализация паттерна Bulkhead. Нет нужного уровня изоляции, у приложения все ещё общая память. Тяжёлый SQL запрос из одного потока может выгрузить половину БД и запросто положит сервис с OutOfMemoryError.
И ещё, обратите внимание на важный момент!
Допустим, мы почитали паттерны отказоустойчивости микросервисов, нашли среди них Bulkhead и решили, что нам надо. Гуглим bulkhead example, и вся поисковая выдача будет забита спринговыми аннотациями.
Как мы уже обсудили, это не самый надёжный способ. Самое верное - ограничить память и CPU в настройках Docker/виртуалки/Kubernetes/etc. Беда в том, что слово Bulkhead в этих инструментах не используется, там limits и constraints. Поэтому такое решение может легко пройти мимо.
При работе с паттернами нужно четко понимать, какая проблема решается и за счёт чего. Не хватать первое же решение с нужным словом, смотреть не только на форму, но и на содержание🤌
- паттерн для повышения надёжности. Сегодня расскажу несколько способов, как его реализовать и подсвечу неочевидный момент при работе с паттернами в целом.
В чем суть?
Название Bulkhead пришло из кораблестроения. Корабль делится на независимые отсеки. Если в одном отсеке появится дыра, водой заполнится только он. Остальные части будут в порядке, и корабль хоть как-то продолжит плыть.
Главная идея паттерна - разграничить ресурсы, чтобы ошибка в одном компоненте не повлияла на работу других.
Реализовать паттерн можно на трёх уровнях:
▫️ Docker контейнер
▫️ Приложение (jar-файл)
▫️ Внутри сервиса
На каждом уровне свои ресурсы и возможности. Пойдем по порядку:
✨Docker контейнер✨
и остальные инструменты виртуализации дают контроль над главными ресурсами - памятью и потреблением CPU:
docker run -dit --cpus="2" --memory="512m" imagename
Если в одном сервисе произойдет беда, остальные будут работать как ни в чем ни бывало.
✨Приложение✨
Здесь возможности поменьше. JVM не контролирует загруженность процессора, этим занимается ОС. Зато тщательно считает свои обьекты и занятую память, мы можем задать рамки флажками типа -Xmx.
✨Внутри сервиса✨
В java коде нельзя явно ограничить количество памяти и нагрузку на цпу. Но в нашей власти повлиять на количество потоков, как следствие - ограничить число запросов для разных эндпойнтов.
Для этого Spring предлагает аннотацию @Bulkhead:
@GetMapping(value = "/post/{id}")
@Bulkhead(name = "getPost", fallbackMethod = "postFallback")
public Post getPost(@PathVariable int id) {…}
В проперти maxThreadPoolSize настраиваем максимальное число потоков.
Ограничение запросов иногда полезно, но это ненадежная реализация паттерна Bulkhead. Нет нужного уровня изоляции, у приложения все ещё общая память. Тяжёлый SQL запрос из одного потока может выгрузить половину БД и запросто положит сервис с OutOfMemoryError.
И ещё, обратите внимание на важный момент!
Допустим, мы почитали паттерны отказоустойчивости микросервисов, нашли среди них Bulkhead и решили, что нам надо. Гуглим bulkhead example, и вся поисковая выдача будет забита спринговыми аннотациями.
Как мы уже обсудили, это не самый надёжный способ. Самое верное - ограничить память и CPU в настройках Docker/виртуалки/Kubernetes/etc. Беда в том, что слово Bulkhead в этих инструментах не используется, там limits и constraints. Поэтому такое решение может легко пройти мимо.
При работе с паттернами нужно четко понимать, какая проблема решается и за счёт чего. Не хватать первое же решение с нужным словом, смотреть не только на форму, но и на содержание🤌
Какое хэширование используется в HashMap в терминах computer science?
Anonymous Poll
49%
Открытое
15%
Закрытое
7%
Кукушечное
29%
Связанное (coalesced)
HashMap: альтернативная реализация
Если хэши, бакеты, контракт equals и hashcode для вас давно пройденный этап, то вот вопрос со звездочкой:
🤔 Можно ли сделать что-то быстрее, чем HashMap? Хотя бы для частных случаев? И как это сделать в java?
Об этом и будет сегодняшний пост. Несложный компутер саенс для расширения кругозора.
HashMap - реализация структуры данных хэш-таблица. Принцип прост: вычисляем хэш ключа, находим нужный бакет, кладём пару ключ-значение.
Нюансы начинаются, если в бакете уже что-то есть. Тогда есть два основных пути:
1️⃣ Метод цепочек (separate chaining hash tables)
Его использует HashMap. Допускаем, что в одном бакете могут быть несколько элементов, организуем их в список или дерево.
Альтернативное название - open hashing. “Открытость” означает, что данные лежат не в самом массиве, а где-то в куче.
2️⃣ Метод открытой адресации (open addressing hash tables)
Если бакет занят, вычисляем следующий, пока не найдем свободный. Самое простое - проверить соседний бакет, но есть и другие стратегии.
Как только нашли свободный, кладём туда пару ключ-значение.
Поиск в такой структуре прост:
▫️ Считаем хэш ключа
▫️ Вычисляем бакет
▫️ Смотрим, какой ключ там лежит. Если нужный - возвращаем значение
▫️ Если ключ не совпал, вычисляем новый бакет и проверяем там. Повторяем, пока не дойдем до нужного ключа или пустой ячейки
В каждом бакете максимум одно значение, которое записывается в сам бакет. Все лежит в памяти рядышком, вставка и поиск работают космически быстро🚀
Из-за того, что данные лежат в самом массиве, полученную структуру так же называют close hashing.
🤔 Получается, текущий HashMap - не самый быстрый вариант?
Верно, тк в бакете хранится ссылка на элемент или цепочку элементов, приходится прыгать по ссылкам в куче на несколько гигов.
Но благодаря ссылкам можно работать с объектами произвольных размеров, легко заменять и удалять элементы. Метод цепочек - более простой и универсальный вариант, поэтому именно он используется в готовых хэш-таблицах в java, go и c++.
Метод открытой адресации побеждает лишь в определенных кейсах и сложнее в реализации, поэтому не входит в стандартные библиотеки.
🤔 Можно ли реализовать хэш-таблицу с методом открытой адресации в java?
Можно, но только для примитивов. Ссылочные типы здесь не подойдут. Нужно, чтобы данные лежали в самой структуре.
Но в будущем ситуация изменится! В java вовсю идёт разработка value types — объектов с полями и методами, работа с которыми идёт как с примитивом.
Это позволит хранить данные рядом, пользоваться локальностью, чаще задействовать кэши процессора. Даст зелёный свет многим алгоритмам и структурам данных, в том числе хэш-таблице с открытой адресацией.
Ответ на вопрос перед постом: HashMap использует открытое хэширование. Ставь ❤️, если выбирал ответ сердцем, и 👍 если выбирал умом
Если хэши, бакеты, контракт equals и hashcode для вас давно пройденный этап, то вот вопрос со звездочкой:
🤔 Можно ли сделать что-то быстрее, чем HashMap? Хотя бы для частных случаев? И как это сделать в java?
Об этом и будет сегодняшний пост. Несложный компутер саенс для расширения кругозора.
HashMap - реализация структуры данных хэш-таблица. Принцип прост: вычисляем хэш ключа, находим нужный бакет, кладём пару ключ-значение.
Нюансы начинаются, если в бакете уже что-то есть. Тогда есть два основных пути:
1️⃣ Метод цепочек (separate chaining hash tables)
Его использует HashMap. Допускаем, что в одном бакете могут быть несколько элементов, организуем их в список или дерево.
Альтернативное название - open hashing. “Открытость” означает, что данные лежат не в самом массиве, а где-то в куче.
2️⃣ Метод открытой адресации (open addressing hash tables)
Если бакет занят, вычисляем следующий, пока не найдем свободный. Самое простое - проверить соседний бакет, но есть и другие стратегии.
Как только нашли свободный, кладём туда пару ключ-значение.
Поиск в такой структуре прост:
▫️ Считаем хэш ключа
▫️ Вычисляем бакет
▫️ Смотрим, какой ключ там лежит. Если нужный - возвращаем значение
▫️ Если ключ не совпал, вычисляем новый бакет и проверяем там. Повторяем, пока не дойдем до нужного ключа или пустой ячейки
В каждом бакете максимум одно значение, которое записывается в сам бакет. Все лежит в памяти рядышком, вставка и поиск работают космически быстро🚀
Из-за того, что данные лежат в самом массиве, полученную структуру так же называют close hashing.
🤔 Получается, текущий HashMap - не самый быстрый вариант?
Верно, тк в бакете хранится ссылка на элемент или цепочку элементов, приходится прыгать по ссылкам в куче на несколько гигов.
Но благодаря ссылкам можно работать с объектами произвольных размеров, легко заменять и удалять элементы. Метод цепочек - более простой и универсальный вариант, поэтому именно он используется в готовых хэш-таблицах в java, go и c++.
Метод открытой адресации побеждает лишь в определенных кейсах и сложнее в реализации, поэтому не входит в стандартные библиотеки.
🤔 Можно ли реализовать хэш-таблицу с методом открытой адресации в java?
Можно, но только для примитивов. Ссылочные типы здесь не подойдут. Нужно, чтобы данные лежали в самой структуре.
Но в будущем ситуация изменится! В java вовсю идёт разработка value types — объектов с полями и методами, работа с которыми идёт как с примитивом.
Это позволит хранить данные рядом, пользоваться локальностью, чаще задействовать кэши процессора. Даст зелёный свет многим алгоритмам и структурам данных, в том числе хэш-таблице с открытой адресацией.
Ответ на вопрос перед постом: HashMap использует открытое хэширование. Ставь ❤️, если выбирал ответ сердцем, и 👍 если выбирал умом
Как найти и починить in-memory пагинацию в Spring Data JPA
N+1 - самая известная проблема в Spring Data JPA/Hibernate, но не единственная. В этом посте расскажу об in-memory пагинации.
В чем суть.
Пагинацию логично делать на уровне БД с помощью limit и offset. Но иногда Hibernate делает пагинацию на стороне клиента. Выгружает все записи из память, выбирает из них заданное количество и возвращает.
Чем плохо:
❌ Запрос выполняется долго, тк по сети передается куча данных
❌ Забивается оперативная память. В худшем случае получаем OutOfMemoryError
Проблема возникает при сочетании предварительной загрузки связных сущностей и Pageable.
Простой пример. У постов есть комментарии со связью OneToMany:
Мы хотим получить 5 постов с комментариями. Чтобы избежать N+1, загружаем комментарии через EntityGraph:
Метод findAllPosts вернёт 5 постов, как мы и просили. Но в логах увидим SQL запрос без лимитов, в память загрузятся все посты.
Как найти места со скрытой пагинацией?
Hibernate пишет в консоль ворнинг, который легко пропустить. Лучше сделать так:
🌷 Ставим свойство
🌷 Запускаем интеграционные тесты
🌷 Смотрим, где падают исключения
Как починить скрытую пагинацию?
Способов много, самое простое - добавить над списком @BatchSize. Либо поставить свойство
Тогда BatchSize будет по умолчанию применяться для всех списков.
Про другие решения и перфоманс читайте в этой статье на Хабре
Зачем Hibernate использует in-memory пагинацию?
При запросе с джойном из нескольких таблиц получается декартово произведение, для которого некорректно применить limit. Но контракт соблюдать надо хоть как-то, поэтому была выбрана пагинация на стороне клиента.
Сразу возникает вопрос. Почему в таких случаях не сделать 2 запроса?
▫️ select * from posts limit 5;
▫️ Извлечь айдишники постов
▫️ select * from comments where post_id in (?,?,?,?,?);
▫️ Связать сущности
Решение выглядит просто, и вариант с BatchSize примерно так и работает. Почему оно не используется по умолчанию - непонятно.
Hibernate - яркий пример “протекающих абстракций”. Куча нюансов и проблем, о которых нужно знать. В документации по поводу in-memory панинации есть такая фраза:
Possibility of terrible performance is left as a problem for the client to avoid.
Проблема ужасного перформанса - это проблема клиента.
Вся суть хибернейта🤌
По возможности не тащите его в проект. Есть более удобные и прозрачные альтернативы. Например, Spring Data JDBC💚
N+1 - самая известная проблема в Spring Data JPA/Hibernate, но не единственная. В этом посте расскажу об in-memory пагинации.
В чем суть.
Пагинацию логично делать на уровне БД с помощью limit и offset. Но иногда Hibernate делает пагинацию на стороне клиента. Выгружает все записи из память, выбирает из них заданное количество и возвращает.
Чем плохо:
❌ Запрос выполняется долго, тк по сети передается куча данных
❌ Забивается оперативная память. В худшем случае получаем OutOfMemoryError
Проблема возникает при сочетании предварительной загрузки связных сущностей и Pageable.
Простой пример. У постов есть комментарии со связью OneToMany:
@Entity
public class Post {
@OneToMany
private List<Comment> comments;
}
Мы хотим получить 5 постов с комментариями. Чтобы избежать N+1, загружаем комментарии через EntityGraph:
@EntityGraph
List<Post> findAllPosts(PageRequest.of(0, 5));
Метод findAllPosts вернёт 5 постов, как мы и просили. Но в логах увидим SQL запрос без лимитов, в память загрузятся все посты.
Как найти места со скрытой пагинацией?
Hibernate пишет в консоль ворнинг, который легко пропустить. Лучше сделать так:
🌷 Ставим свойство
spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true
🌷 Запускаем интеграционные тесты
🌷 Смотрим, где падают исключения
Как починить скрытую пагинацию?
Способов много, самое простое - добавить над списком @BatchSize. Либо поставить свойство
spring.jpa.properties.hibernate.default_batch_fetch_size=50
Тогда BatchSize будет по умолчанию применяться для всех списков.
Про другие решения и перфоманс читайте в этой статье на Хабре
Зачем Hibernate использует in-memory пагинацию?
При запросе с джойном из нескольких таблиц получается декартово произведение, для которого некорректно применить limit. Но контракт соблюдать надо хоть как-то, поэтому была выбрана пагинация на стороне клиента.
Сразу возникает вопрос. Почему в таких случаях не сделать 2 запроса?
▫️ select * from posts limit 5;
▫️ Извлечь айдишники постов
▫️ select * from comments where post_id in (?,?,?,?,?);
▫️ Связать сущности
Решение выглядит просто, и вариант с BatchSize примерно так и работает. Почему оно не используется по умолчанию - непонятно.
Hibernate - яркий пример “протекающих абстракций”. Куча нюансов и проблем, о которых нужно знать. В документации по поводу in-memory панинации есть такая фраза:
Possibility of terrible performance is left as a problem for the client to avoid.
Проблема ужасного перформанса - это проблема клиента.
Вся суть хибернейта🤌
По возможности не тащите его в проект. Есть более удобные и прозрачные альтернативы. Например, Spring Data JDBC💚
Обычно при работе с Postgres используется пул соединений. Объясняется это тем, что установка соединения с БД - это долго и дорого. А почему?
Anonymous Poll
15%
Сервису и Postgres нужно договориться о протоколах, форматах, алгоритмах и тд
6%
Каждое соединение создает свою копию индексов для ускорения поиска
16%
Внутри сервиса выделяется область памяти для приема данных от БД
63%
Для каждого соединения стартует отдельный процесс в ОС
База по Postgres
Сегодня расскажу про 3 важных сущности Postgres: соединения, буфер и WAL. Это та база, которая точно пригодится на практике.
💫Соединение (connection)💫
Чтобы пообщаться с БД, сервис устанавливает коннекшн с БД. Но что такое “соединение”, и зачем оно нужно?
База данных - обычное приложение, которое работает с внешними клиентами (нашими сервисами). Чтобы клиенты не мешали друг другу, нужно обеспечить должную изоляцию и безопасность данных.
В Postgres проблема решается просто - работа с каждым клиентом происходит в отдельном процессе. Переносим вопрос изоляции на операционную систему💅
Так делает не только постгрес. Если откроете 10 вкладок в Google Chrome, в диспетчере задач появятся 10 новых процессов.
Решение простое и надёжное, но минусы тоже есть:
🌚 Новый процесс долго запускается. В ОС происходит куча всего, чтобы обеспечить изоляцию памяти
🌚 Большой расход оперативки
Пул соединений нужен, чтобы не создавать процессы заново каждый раз и ограничить их количество.
💫Буфер💫
Также в оперативке создаётся область общей памяти, которая называется буфер. Там обитают данные, которые только что прочитаны с диска, и которые скоро отправятся на запись.
Почему апдейты не сразу пишутся на диск?
Запись на диск - медленная операция. Поэтому выполняется асинхронно, чтобы не заставлять пользователя ждать.
С буфером вы встретитесь при выполнении EXPLAIN ANALYZE. Строка
означает, что в буфер с диска прочитано 5000 страниц/блоков. Каждый блок 8 КБ, итого с диска считано 40 Мб данных.
Эти цифры часто нужны для анализа нагрузки.
💫WAL💫
Свежие обновления не сразу пишутся на диск, а некоторое время лежат только в оперативной памяти.
В случае сбоя данные потеряются. Чтобы этого не произошло, суть изменений записывается в конец специального файлика, который называется WAL (write ahead log).
Что, запись на диск? Это же медленно!!!1
Все верно, но сравните масштаб. В случае апдейта изменения встраиваются в огромную структуру, надо обновить индексы, плотнее укомплектовать данные, обновить статистику и ещё миллион вещей.
В случае WAL мы пишем минимум информации в конец файла. Это гораздо быстрее💫
Помимо повышения надёжности, WAL используется для некоторых видов репликации.
Вот и всё, ничего сложного:) Ставьте огонечки постам, это меня очень радует😊
Сегодня расскажу про 3 важных сущности Postgres: соединения, буфер и WAL. Это та база, которая точно пригодится на практике.
💫Соединение (connection)💫
Чтобы пообщаться с БД, сервис устанавливает коннекшн с БД. Но что такое “соединение”, и зачем оно нужно?
База данных - обычное приложение, которое работает с внешними клиентами (нашими сервисами). Чтобы клиенты не мешали друг другу, нужно обеспечить должную изоляцию и безопасность данных.
В Postgres проблема решается просто - работа с каждым клиентом происходит в отдельном процессе. Переносим вопрос изоляции на операционную систему💅
Так делает не только постгрес. Если откроете 10 вкладок в Google Chrome, в диспетчере задач появятся 10 новых процессов.
Решение простое и надёжное, но минусы тоже есть:
🌚 Новый процесс долго запускается. В ОС происходит куча всего, чтобы обеспечить изоляцию памяти
🌚 Большой расход оперативки
Пул соединений нужен, чтобы не создавать процессы заново каждый раз и ограничить их количество.
💫Буфер💫
Также в оперативке создаётся область общей памяти, которая называется буфер. Там обитают данные, которые только что прочитаны с диска, и которые скоро отправятся на запись.
Почему апдейты не сразу пишутся на диск?
Запись на диск - медленная операция. Поэтому выполняется асинхронно, чтобы не заставлять пользователя ждать.
С буфером вы встретитесь при выполнении EXPLAIN ANALYZE. Строка
Buffers: shared hit=32 read=5000
означает, что в буфер с диска прочитано 5000 страниц/блоков. Каждый блок 8 КБ, итого с диска считано 40 Мб данных.
Эти цифры часто нужны для анализа нагрузки.
💫WAL💫
Свежие обновления не сразу пишутся на диск, а некоторое время лежат только в оперативной памяти.
В случае сбоя данные потеряются. Чтобы этого не произошло, суть изменений записывается в конец специального файлика, который называется WAL (write ahead log).
Что, запись на диск? Это же медленно!!!1
Все верно, но сравните масштаб. В случае апдейта изменения встраиваются в огромную структуру, надо обновить индексы, плотнее укомплектовать данные, обновить статистику и ещё миллион вещей.
В случае WAL мы пишем минимум информации в конец файла. Это гораздо быстрее💫
Помимо повышения надёжности, WAL используется для некоторых видов репликации.
Вот и всё, ничего сложного:) Ставьте огонечки постам, это меня очень радует😊
Брейкпойнты в IDEA
Иногда смотрю, как люди дебажат, и сердце кровью обливается💔
Большинство разработчиков знают только, как добавить и удалить брейкпойнт. Но у IDEA много фич для дебага, перечислю самые полезные:
1️⃣ Условия остановки
Если метод часто вызывается, или брейкпойнт стоит в цикле, не тратьте время на ожидание нужных значений:
⭐ Правый щелчок по брейкпойнту
⭐ Добавить в Condition условие остановки. Можно использовать все доступные переменные, объекты и методы
2️⃣ Посмотреть значения параметров в динамике
Вариант для котят: добавить в код System.out.println с нужным полем/выражением.
Вариант для тигров:
🐯 Зажать Shift и добавить брейкпойнт
🐯 Щёлкнуть галочку Evaluate and log
🐯 Вписать нужное выражение
Отладчик не будет останавливать выполнение, а запишет в консоль значение выражения. Незаменимая фича для отладки многопоточных приложений, кода сторонних библиотек и удалённого дебага.
3️⃣ Отключение брейкпойнта
Ненужный брейкпойнт можно не удалять, а отключить:
▪️Щёлкнуть колёсиком по брейкпойнту
ИЛИ
▪️Правый щёлчок по брейкпойнту → снять галочку с Enabled
4️⃣ Массовые сокращения
Когда в проекте много брейкпойнтов, IDE при дебаге немного тормозит. Чтобы удалить ненужные, открываем полный список:
▫️Правый клик по любому брейкпойнту
▫️Ссылка More
▫️Слева видим список брейкпойнтов
▫️Удаляем ненужные
Обязательно попробуйте! Пусть дебаг проходит легко и приятно😊
Иногда смотрю, как люди дебажат, и сердце кровью обливается💔
Большинство разработчиков знают только, как добавить и удалить брейкпойнт. Но у IDEA много фич для дебага, перечислю самые полезные:
1️⃣ Условия остановки
Если метод часто вызывается, или брейкпойнт стоит в цикле, не тратьте время на ожидание нужных значений:
⭐ Правый щелчок по брейкпойнту
⭐ Добавить в Condition условие остановки. Можно использовать все доступные переменные, объекты и методы
2️⃣ Посмотреть значения параметров в динамике
Вариант для котят: добавить в код System.out.println с нужным полем/выражением.
Вариант для тигров:
🐯 Зажать Shift и добавить брейкпойнт
🐯 Щёлкнуть галочку Evaluate and log
🐯 Вписать нужное выражение
Отладчик не будет останавливать выполнение, а запишет в консоль значение выражения. Незаменимая фича для отладки многопоточных приложений, кода сторонних библиотек и удалённого дебага.
3️⃣ Отключение брейкпойнта
Ненужный брейкпойнт можно не удалять, а отключить:
▪️Щёлкнуть колёсиком по брейкпойнту
ИЛИ
▪️Правый щёлчок по брейкпойнту → снять галочку с Enabled
4️⃣ Массовые сокращения
Когда в проекте много брейкпойнтов, IDE при дебаге немного тормозит. Чтобы удалить ненужные, открываем полный список:
▫️Правый клик по любому брейкпойнту
▫️Ссылка More
▫️Слева видим список брейкпойнтов
▫️Удаляем ненужные
Обязательно попробуйте! Пусть дебаг проходит легко и приятно😊
CRaC (Coordinated Restore at Checkpoint)
- свежая фича с большим потенциалом. Свежая настолько, что ее нет в базовой OpenJDK, только в отдельной сборке.
В основе CRaC лежит простая и интересная идея. Расскажу, в чем суть, и как это применимо к практике.
Как стартует типичный сервис в java вселенной:
✨ Запускается JVM: считывает файлики с классами, загружает их в оперативку и тд
✨ Запускается контекст спринга. Конфиги, бины, постпроцессоры, прокси классы
Общий запуск занимает несколько минут. В основном благодаря спрингу.
Какую схему предлагает CRaC:
▫️ Берём работающее приложение
▫️ Делаем снимок оперативки
▫️ Разворачиваем этот снимок на другой машине
▫️ Приложение стартует за пару секунд🥹
Но это в теории. А что на практике?
На деле пока не всё гладко. Если приложение сложнее HelloWorld, нас ждут сложности с БД соединениями, задачами по расписанию, безопасностью параметров, развертыванием и тд. Вот тут человек запускал нормальное приложение, и это стоило немалых трудов. Но в итоге оно стартовало на 90% быстрее🚀
Проблемы в целом не критичные. Большую часть может взять на себя Spring, если решит всерьез поддержать CRaC. Сейчас поддержка минимальная - можно сделать снимок сразу после создания бинов. Надо, конечно, что-то посерьезнее.
Работает CRaC только в Linux, в других ОС нет поддержки снэпшотов памяти. Но можно запустить сервис в докере на основе Linux. И здесь тоже хочется автоматизации. Сейчас собрать и запустить образ с CRaC - сложная консольная многоходовочка.
Несмотря на незрелость, я вижу у CRaC большие перспективы. При хорошей поддержке он серьезно повлияет на расстановку сил в джава мире.
Мнение субъективное, но опыт позволяет делать такие прогнозы. Ставьте огонечки, если надо рассказать подробнее 🔥
- свежая фича с большим потенциалом. Свежая настолько, что ее нет в базовой OpenJDK, только в отдельной сборке.
В основе CRaC лежит простая и интересная идея. Расскажу, в чем суть, и как это применимо к практике.
Как стартует типичный сервис в java вселенной:
✨ Запускается JVM: считывает файлики с классами, загружает их в оперативку и тд
✨ Запускается контекст спринга. Конфиги, бины, постпроцессоры, прокси классы
Общий запуск занимает несколько минут. В основном благодаря спрингу.
Какую схему предлагает CRaC:
▫️ Берём работающее приложение
▫️ Делаем снимок оперативки
▫️ Разворачиваем этот снимок на другой машине
▫️ Приложение стартует за пару секунд🥹
Но это в теории. А что на практике?
На деле пока не всё гладко. Если приложение сложнее HelloWorld, нас ждут сложности с БД соединениями, задачами по расписанию, безопасностью параметров, развертыванием и тд. Вот тут человек запускал нормальное приложение, и это стоило немалых трудов. Но в итоге оно стартовало на 90% быстрее🚀
Проблемы в целом не критичные. Большую часть может взять на себя Spring, если решит всерьез поддержать CRaC. Сейчас поддержка минимальная - можно сделать снимок сразу после создания бинов. Надо, конечно, что-то посерьезнее.
Работает CRaC только в Linux, в других ОС нет поддержки снэпшотов памяти. Но можно запустить сервис в докере на основе Linux. И здесь тоже хочется автоматизации. Сейчас собрать и запустить образ с CRaC - сложная консольная многоходовочка.
Несмотря на незрелость, я вижу у CRaC большие перспективы. При хорошей поддержке он серьезно повлияет на расстановку сил в джава мире.
Мнение субъективное, но опыт позволяет делать такие прогнозы. Ставьте огонечки, если надо рассказать подробнее 🔥
Прогноз на 2030 год
Предсказывать будущее - вещь неблагодарная (особенно в России), но иногда сложно устоять. Прошлый пост искрится от огоньков (спасибо❤️), поэтому поделюсь своим видением.
У каждой технологии в сердечке есть какая-то идея. Свой оригинальный способ решить задачу или проблему. Если появится решение кардинально проще, быстрее или дешевле, со временем технология отпадет за ненадобностью.
Поэтому делаю прогноз - если Spring сделает удобную поддержку CRaC (создание и загрузка снэпшотов оперативки), фреймворки типа Micronaut и Quarkus станут не нужны.
Объясню, почему.
Spring - прекрасный фреймворк, но его приложения стартуют чудовищно долго. Основная магия работает через рефлекшн в момент запуска.
Это архитектурная особенность, от нее никуда не уйти.
Несколько лет назад появилась альтернатива - проворачивать ту же логику в момент компиляции. Такой подход используют фреймворки Quarkus и Micronaut.
Да, не такие мощные и удобные как Spring, зато приложение стартует в разы быстрее. Многим это важно, поэтому популярность Micronaut и Quarkus с каждым годом растет. Сервисы пишутся, люди работают.
Представим, что Spring и Docker серьезно вложились в поддержку CRaC. Снэпшоты создаются и развертываются одной кнопкой.
Посмотрите на картинку внизу👇Это время запуска приложений с CRaC для Spring Boot, Micronaut и Quarkus. Время везде одинаковое.
Micronaut и Quarkus теряют свое конкурентное преимущество, проигрывают спрингу и забрасываются. Недавно написанные сервисы становятся легаси. Spring матереет, и ещё больше укрепляет монополию.
Ситуация не уникальна. Сейчас в джава вселенной происходит очень похожий процесс с виртуальными потоками.
В чем суть:
Традиционный сервер обрабатывает каждый запрос в отдельном потоке. Этим ограничивается количество запросов в работе. Даже если сервер не загружен на 100%, он физически неспособен взять больше работы.
Реактивный подход предлагает альтернативу. Да, код писать не так удобно. Да, приходиться решать кучу проблем. Зато пропускная способность улетает в космос🚀
Сервисы пишутся, люди работают.
А потом появились виртуальные потоки и решили исходную проблему традиционного сервера. Реактивный подход теряет смысл, традиционная многопоточность укрепляет позиции.
Прекрасный момент, чтобы напомнить про
⭐ Лучший бесплатный курс про основы многопоточки⭐
🔥 Самый полный и практический курс по конкарренси🔥
Какой вывод можно сделать из ситуации со спрингом и виртуальными потоками? Все ли проблемы со временем сами решатся?
Увы, но это лотерея. Новые решения могут появиться, а могут и нет. Хорошая идея может выстрелить, а может споткнуться о плохую реализацию.
В любом случае, наблюдать за этим очень интересно😊 И вернёмся к посту через 5 лет, посмотрим, сбудется ли прогноз!
Предсказывать будущее - вещь неблагодарная (особенно в России), но иногда сложно устоять. Прошлый пост искрится от огоньков (спасибо❤️), поэтому поделюсь своим видением.
У каждой технологии в сердечке есть какая-то идея. Свой оригинальный способ решить задачу или проблему. Если появится решение кардинально проще, быстрее или дешевле, со временем технология отпадет за ненадобностью.
Поэтому делаю прогноз - если Spring сделает удобную поддержку CRaC (создание и загрузка снэпшотов оперативки), фреймворки типа Micronaut и Quarkus станут не нужны.
Объясню, почему.
Spring - прекрасный фреймворк, но его приложения стартуют чудовищно долго. Основная магия работает через рефлекшн в момент запуска.
Это архитектурная особенность, от нее никуда не уйти.
Несколько лет назад появилась альтернатива - проворачивать ту же логику в момент компиляции. Такой подход используют фреймворки Quarkus и Micronaut.
Да, не такие мощные и удобные как Spring, зато приложение стартует в разы быстрее. Многим это важно, поэтому популярность Micronaut и Quarkus с каждым годом растет. Сервисы пишутся, люди работают.
Представим, что Spring и Docker серьезно вложились в поддержку CRaC. Снэпшоты создаются и развертываются одной кнопкой.
Посмотрите на картинку внизу👇Это время запуска приложений с CRaC для Spring Boot, Micronaut и Quarkus. Время везде одинаковое.
Micronaut и Quarkus теряют свое конкурентное преимущество, проигрывают спрингу и забрасываются. Недавно написанные сервисы становятся легаси. Spring матереет, и ещё больше укрепляет монополию.
Ситуация не уникальна. Сейчас в джава вселенной происходит очень похожий процесс с виртуальными потоками.
В чем суть:
Традиционный сервер обрабатывает каждый запрос в отдельном потоке. Этим ограничивается количество запросов в работе. Даже если сервер не загружен на 100%, он физически неспособен взять больше работы.
Реактивный подход предлагает альтернативу. Да, код писать не так удобно. Да, приходиться решать кучу проблем. Зато пропускная способность улетает в космос🚀
Сервисы пишутся, люди работают.
А потом появились виртуальные потоки и решили исходную проблему традиционного сервера. Реактивный подход теряет смысл, традиционная многопоточность укрепляет позиции.
Прекрасный момент, чтобы напомнить про
⭐ Лучший бесплатный курс про основы многопоточки⭐
🔥 Самый полный и практический курс по конкарренси🔥
Какой вывод можно сделать из ситуации со спрингом и виртуальными потоками? Все ли проблемы со временем сами решатся?
Увы, но это лотерея. Новые решения могут появиться, а могут и нет. Хорошая идея может выстрелить, а может споткнуться о плохую реализацию.
В любом случае, наблюдать за этим очень интересно😊 И вернёмся к посту через 5 лет, посмотрим, сбудется ли прогноз!
Как изменится результат выполнения кода выше, если заменить collect(toList()) на toList()?
Anonymous Poll
4%
В списке providers не будет элементов из special
4%
В списке providers изменится порядок элементов
13%
Возникнет ошибка компиляции
42%
Возникнет ошибка в рантайме
37%
Ничего не изменится
Коллекторы и тесты
Сразу разберём вопрос выше. Можно ли безопасно заменить collect(toList()) на toList()?
Ответ: нет, в коде выше получим ошибку в рантайме:
❤️ collect(toList()) возвращает ArrayList, который с радостью принимает новые элементы.
💔 toList() возвращает неизменяемый список. Вызов addAll выбросит исключение.
Это очевидная ошибка JDK. Коллекторы - часть Stream API. В рамках функционального стиля логично возвращать неизменяемую коллекцию.
Плюс ошибка на уровне проектирования - неизменяемые коллекции должны иметь интерфейс без методов add/set/remove. Пользователь не должен запоминать эти нюансы и получать внезапные ошибки в рантайме.
Побухтеть на JDK - дело святое, но сфокусируемся на другом. Для начала опишу историю целиком:
Человек поправил toList в коде фичи А. Там не было addAll, и все отлично работало. Затем человек сделал автозамену по всему сервису. Изменение с виду абсолютно безобидное, подвоха никто не ждал.
К счастью, код был хорошо покрыт тестами. Некоторые упали, отсюда мы и узнали эту милую особенность JDK💅
Этот случай очень наглядно показал: невозможно предусмотреть всё. Есть миллион нюансов языка, фреймворков, конфигурации, среды исполнения и тд.
Всегда могут вылезти неочевидные ошибки и странное поведение. Даже если мы сделали простейшие изменения. Даже если мы ничего не делали, но кто-то что-то делал рядом.
Пишите тесты. Это не формальность и не причуды вашего лида. Когда на проекте хорошие тесты, работа идёт на волне спокойствия и комфорта☺️
Сразу разберём вопрос выше. Можно ли безопасно заменить collect(toList()) на toList()?
Ответ: нет, в коде выше получим ошибку в рантайме:
❤️ collect(toList()) возвращает ArrayList, который с радостью принимает новые элементы.
💔 toList() возвращает неизменяемый список. Вызов addAll выбросит исключение.
Это очевидная ошибка JDK. Коллекторы - часть Stream API. В рамках функционального стиля логично возвращать неизменяемую коллекцию.
Плюс ошибка на уровне проектирования - неизменяемые коллекции должны иметь интерфейс без методов add/set/remove. Пользователь не должен запоминать эти нюансы и получать внезапные ошибки в рантайме.
Побухтеть на JDK - дело святое, но сфокусируемся на другом. Для начала опишу историю целиком:
Человек поправил toList в коде фичи А. Там не было addAll, и все отлично работало. Затем человек сделал автозамену по всему сервису. Изменение с виду абсолютно безобидное, подвоха никто не ждал.
К счастью, код был хорошо покрыт тестами. Некоторые упали, отсюда мы и узнали эту милую особенность JDK💅
Этот случай очень наглядно показал: невозможно предусмотреть всё. Есть миллион нюансов языка, фреймворков, конфигурации, среды исполнения и тд.
Всегда могут вылезти неочевидные ошибки и странное поведение. Даже если мы сделали простейшие изменения. Даже если мы ничего не делали, но кто-то что-то делал рядом.
Пишите тесты. Это не формальность и не причуды вашего лида. Когда на проекте хорошие тесты, работа идёт на волне спокойствия и комфорта☺️