Как избежать выгорания, часть 1
В конце года люди делятся на две группы. Одна бодро подводит итоги года и строит планы на следующий. Другая устало лежит на диване и смотрит в стену.
Сегодня хочу поговорить про выгорание.
Среди разработчиков выгорание случается сплошь и рядом. А многие живут в таком состоянии несколько лет!
Обычно под выгоранием понимают апатию и потерю интереса к работе. Но это только одна из граней. Выгорание делится на 4 стадии, каждая отличается своим “настроением”, симптомами и лечением. В этом посте обсудим первую стадию:
Накопление усталости
Обычно начинается, когда работа занимает слишком много места в жизни:
🧑💻 Нет четкого разделения работа/отдых, работа размазывается на весь день
🧑💻 В выходные часто думаете над рабочими задачами
🧑💻 Постоянно проходите курсы, смотрите конференции и учитесь
🧑💻 Нацелились на 5+ на перформанс ревью и очень стараетесь
🧑💻 Пришли на новый проект и хотите быстро показать отличные результаты
Ничего удивительного, если много работать, то устанешь:) Но есть и менее очевидные ситуации.
Представьте, работа вам очень нравится. Задачи интересные, коллеги вдохновляют. С работы не хочется уходить. Казалось бы, мечта.
Но в перспективе все не так радужно. И переработки, и горящие глаза со временем приводят к накоплению стресса и усталости.
Как понять, что я на первой стадии выгорания?
Вы можете чувствовать себя нормально. Большинство людей не чувствуют усталости, пока она не станет 10/10.
На первой стадии остальные сферы жизни проседают, а сама жизнь концентрируется вокруг работы. Человек становится менее радостным, скучным, не любопытным.
Все в целом нормально, но довольно серенько.
Психологи рекомендуют периодически оценивать свое состояние и учиться замечать усталость на ранней стадии. Не когда она 10/10, а хотя бы 5/10.
Если это звучит слишком абстрактно, вот пара приемов, как косвенно оценить свое состояние:
🤔 Посмотреть, как с активностями после работы и с остальными сферами жизни. Если на них все чаще не остаётся ни времени, ни сил, если по вечерам и на выходных хочется полежать, это плохой знак
🤔 Оценить работоспособность в привычные “пики продуктивности”. У кого-то работа идёт бодрее с утра, у других - после обеда. Кто-то в понедельник делает большую часть дел, кто-то окрыляется в четверг. Но если запланированной продуктивности не случается, значит в организме копится усталость
Что делать?
Из первой стадии выбраться довольно просто:
✅ Временно снизить рабочие активности
✅ Четко определить рабочее время
✅ Спланировать вечера и выходные заранее: спорт, прогулки, общение, хобби. Что угодно, не связанное с работой
Если в первые рабочие дни после нового года вы чувствовали себя бодро, запомните это состояние. Это состояние отдохнувшего человека, оно нам нужно почаще:)
В следующем посте обсудим более тяжёлые стадии выгорания. Но давайте не попадать даже в первую. Пусть горят только огоньки под постами, а вас выгорание обойдет стороной🔥
В конце года люди делятся на две группы. Одна бодро подводит итоги года и строит планы на следующий. Другая устало лежит на диване и смотрит в стену.
Сегодня хочу поговорить про выгорание.
Среди разработчиков выгорание случается сплошь и рядом. А многие живут в таком состоянии несколько лет!
Обычно под выгоранием понимают апатию и потерю интереса к работе. Но это только одна из граней. Выгорание делится на 4 стадии, каждая отличается своим “настроением”, симптомами и лечением. В этом посте обсудим первую стадию:
Накопление усталости
Обычно начинается, когда работа занимает слишком много места в жизни:
🧑💻 Нет четкого разделения работа/отдых, работа размазывается на весь день
🧑💻 В выходные часто думаете над рабочими задачами
🧑💻 Постоянно проходите курсы, смотрите конференции и учитесь
🧑💻 Нацелились на 5+ на перформанс ревью и очень стараетесь
🧑💻 Пришли на новый проект и хотите быстро показать отличные результаты
Ничего удивительного, если много работать, то устанешь:) Но есть и менее очевидные ситуации.
Представьте, работа вам очень нравится. Задачи интересные, коллеги вдохновляют. С работы не хочется уходить. Казалось бы, мечта.
Но в перспективе все не так радужно. И переработки, и горящие глаза со временем приводят к накоплению стресса и усталости.
Как понять, что я на первой стадии выгорания?
Вы можете чувствовать себя нормально. Большинство людей не чувствуют усталости, пока она не станет 10/10.
На первой стадии остальные сферы жизни проседают, а сама жизнь концентрируется вокруг работы. Человек становится менее радостным, скучным, не любопытным.
Все в целом нормально, но довольно серенько.
Психологи рекомендуют периодически оценивать свое состояние и учиться замечать усталость на ранней стадии. Не когда она 10/10, а хотя бы 5/10.
Если это звучит слишком абстрактно, вот пара приемов, как косвенно оценить свое состояние:
🤔 Посмотреть, как с активностями после работы и с остальными сферами жизни. Если на них все чаще не остаётся ни времени, ни сил, если по вечерам и на выходных хочется полежать, это плохой знак
🤔 Оценить работоспособность в привычные “пики продуктивности”. У кого-то работа идёт бодрее с утра, у других - после обеда. Кто-то в понедельник делает большую часть дел, кто-то окрыляется в четверг. Но если запланированной продуктивности не случается, значит в организме копится усталость
Что делать?
Из первой стадии выбраться довольно просто:
✅ Временно снизить рабочие активности
✅ Четко определить рабочее время
✅ Спланировать вечера и выходные заранее: спорт, прогулки, общение, хобби. Что угодно, не связанное с работой
Если в первые рабочие дни после нового года вы чувствовали себя бодро, запомните это состояние. Это состояние отдохнувшего человека, оно нам нужно почаще:)
В следующем посте обсудим более тяжёлые стадии выгорания. Но давайте не попадать даже в первую. Пусть горят только огоньки под постами, а вас выгорание обойдет стороной🔥
🔥286❤52👍36👎8
Что делать при выгорании?
В прошлом посте рассмотрели первую стадию выгорания, сегодня обсудим остальные.
Вторая стадия: усталость
Состояние, которое обычно и понимают под выгоранием. Лень, прокрастинация, работа не радует, коллеги бесят. Сон ухудшается, стресс заедается. Сил нет, выходные проходят на диване. Здоровье начинает подводить.
Здесь человек сам чувствует, что сильно устал. Решение очевидно - пора в отпуск! Но сознание загоняет в ловушку: чем ниже продуктивность, тем выше чувство вины, и тем сложнее разрешить себе уехать на отдых.
Но это нужно сделать. Само такое состояние не пройдет. Чем быстрее примете меры, тем быстрее вернётесь к нормальной жизни.
Как отдохнуть?
Отличная схема описана в old but gold статье Разъ*б, безделье, чилл. Хороший отдых состоит из 3 частей:
🤪 Перезагрузка. Активный отдых, путешествия, новые опыт и знакомства. Чем непривычнее, тем лучше получится отвлечься
🤗 Гедонизм. Заняться любимыми делами. Хобби, еда, сериалы, игры, что угодно. Никакого чувства вины, все для себя любимого!
🥱 Скука. Дать мозгу отдохнуть от потоков информации и впечатлений: много спать, гулять на природе, смотреть в окно, наблюдать за волнами.
Не всегда для полноценного отдыха хватает двух недель отпуска. Я обычно увольняюсь и отдыхаю 2-3 месяца. Если отдых прошел успешно, вы почувствуете, что соскучились по работе. Очень приятное чувство🥰
Но если не отдыхать и продолжать копить усталость, рано или поздно наступит
Третья стадия: обратимое истощение
Апатия трансформируется в злость. Человека все раздражает, токсичность зашкаливает, идет огромное сопротивление к работе и саботаж. Может появиться депрессия и тревожность, организм начинает разваливаться то тут, то там.
Здесь отпуск уже не поможет. Нужен длительный перерыв от полугода.
Четвертая стадия: необратимое выгорание
Ненависть к работе, деформация личности, рушатся все сферы жизни. Вернуться к нормальной жизни очень сложно, поможет только смена деятельности и помощь психиатра.
3 и 4 стадии встречаются редко, поэтому описала их кратко. В айти в основном все сидят в первой-второй.
Физическое и психическое здоровье - это база. Без него не получится достигнуть целей, реализовать свои амбиции и таланты. Так что заботимся о себе, отдыхаем почаще. Тогда найдутся силы на рабочие подвиги и профессиональный рост в новом году💪
В прошлом посте рассмотрели первую стадию выгорания, сегодня обсудим остальные.
Вторая стадия: усталость
Состояние, которое обычно и понимают под выгоранием. Лень, прокрастинация, работа не радует, коллеги бесят. Сон ухудшается, стресс заедается. Сил нет, выходные проходят на диване. Здоровье начинает подводить.
Здесь человек сам чувствует, что сильно устал. Решение очевидно - пора в отпуск! Но сознание загоняет в ловушку: чем ниже продуктивность, тем выше чувство вины, и тем сложнее разрешить себе уехать на отдых.
Но это нужно сделать. Само такое состояние не пройдет. Чем быстрее примете меры, тем быстрее вернётесь к нормальной жизни.
Как отдохнуть?
Отличная схема описана в old but gold статье Разъ*б, безделье, чилл. Хороший отдых состоит из 3 частей:
🤪 Перезагрузка. Активный отдых, путешествия, новые опыт и знакомства. Чем непривычнее, тем лучше получится отвлечься
🤗 Гедонизм. Заняться любимыми делами. Хобби, еда, сериалы, игры, что угодно. Никакого чувства вины, все для себя любимого!
🥱 Скука. Дать мозгу отдохнуть от потоков информации и впечатлений: много спать, гулять на природе, смотреть в окно, наблюдать за волнами.
Не всегда для полноценного отдыха хватает двух недель отпуска. Я обычно увольняюсь и отдыхаю 2-3 месяца. Если отдых прошел успешно, вы почувствуете, что соскучились по работе. Очень приятное чувство🥰
Но если не отдыхать и продолжать копить усталость, рано или поздно наступит
Третья стадия: обратимое истощение
Апатия трансформируется в злость. Человека все раздражает, токсичность зашкаливает, идет огромное сопротивление к работе и саботаж. Может появиться депрессия и тревожность, организм начинает разваливаться то тут, то там.
Здесь отпуск уже не поможет. Нужен длительный перерыв от полугода.
Четвертая стадия: необратимое выгорание
Ненависть к работе, деформация личности, рушатся все сферы жизни. Вернуться к нормальной жизни очень сложно, поможет только смена деятельности и помощь психиатра.
3 и 4 стадии встречаются редко, поэтому описала их кратко. В айти в основном все сидят в первой-второй.
Физическое и психическое здоровье - это база. Без него не получится достигнуть целей, реализовать свои амбиции и таланты. Так что заботимся о себе, отдыхаем почаще. Тогда найдутся силы на рабочие подвиги и профессиональный рост в новом году💪
🔥140❤50👍43👎11
Паттерн 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. Поэтому такое решение может легко пройти мимо.
При работе с паттернами нужно четко понимать, какая проблема решается и за счёт чего. Не хватать первое же решение с нужным словом, смотреть не только на форму, но и на содержание🤌
🔥127👍75❤17👎8
Какое хэширование используется в 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 использует открытое хэширование. Ставь ❤️, если выбирал ответ сердцем, и 👍 если выбирал умом
❤247👍64🔥48👎5
Как найти и починить 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💚
🔥158👍71❤31👎5
Обычно при работе с Postgres используется пул соединений. Объясняется это тем, что установка соединения с БД - это долго и дорого. А почему?
Anonymous Poll
15%
Сервису и Postgres нужно договориться о протоколах, форматах, алгоритмах и тд
6%
Каждое соединение создает свою копию индексов для ускорения поиска
16%
Внутри сервиса выделяется область памяти для приема данных от БД
63%
Для каждого соединения стартует отдельный процесс в ОС
🔥79👍11❤1
База по 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 используется для некоторых видов репликации.
Вот и всё, ничего сложного:) Ставьте огонечки постам, это меня очень радует😊
🔥710👍54❤38
Брейкпойнты в 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
▫️Слева видим список брейкпойнтов
▫️Удаляем ненужные
Обязательно попробуйте! Пусть дебаг проходит легко и приятно😊
🔥387👍95❤52👎1
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 большие перспективы. При хорошей поддержке он серьезно повлияет на расстановку сил в джава мире.
Мнение субъективное, но опыт позволяет делать такие прогнозы. Ставьте огонечки, если надо рассказать подробнее 🔥
🔥346👍40❤21👎3
Прогноз на 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 лет, посмотрим, сбудется ли прогноз!
🔥162👍64❤28👎11
Как изменится результат выполнения кода выше, если заменить collect(toList()) на toList()?
Anonymous Poll
4%
В списке providers не будет элементов из special
4%
В списке providers изменится порядок элементов
13%
Возникнет ошибка компиляции
42%
Возникнет ошибка в рантайме
37%
Ничего не изменится
👍24🔥15❤6
Коллекторы и тесты
Сразу разберём вопрос выше. Можно ли безопасно заменить 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💅
Этот случай очень наглядно показал: невозможно предусмотреть всё. Есть миллион нюансов языка, фреймворков, конфигурации, среды исполнения и тд.
Всегда могут вылезти неочевидные ошибки и странное поведение. Даже если мы сделали простейшие изменения. Даже если мы ничего не делали, но кто-то что-то делал рядом.
Пишите тесты. Это не формальность и не причуды вашего лида. Когда на проекте хорошие тесты, работа идёт на волне спокойствия и комфорта☺️
👍235🔥80❤30👎6
Что будет в консоли при выполнении кода выше?
Anonymous Poll
22%
3
14%
4
25%
Ошибка компиляции
39%
Ошибка в рантайме
👍33
Загадка Set.of🔮
В прошлом посте обсуждали toList, в этом обсудим Set.of. Здесь ситуация сложнее и гораздо интереснее! Прекрасный пример для тренировки анализа и критического мышления.
Начнем с ответа: код с Set.of выбросит IllegalArgumentException(duplicate element: 2)
Неожиданно! Set - коллекция уникальных элементов. Мы привыкли, что сет сам фильтрует дубликаты и часто это используем.
Почему теперь сет ругается на дубликаты? Почему фильтрацию по уникальности должен делать разработчик перед вставкой?
Но это ещё не всё. Если убрать дубликаты и оставить код таким:
получим NPE Cannot invoke "Object.hashCode()" because "pe" is null. Какая-то ошибка внутри реализации🤯
Поищем ответ в документации, точнее в JEP. Set.of появился в java 9, его цель обозначена явно - создать удобную альтернативу ручному заполнению сета:
Удобно, конечно, но альтернатива получилась не равнозначная.
Почему новый код не умеет фильтровать дубликаты? Почему не умеет работать с null? Почему “удобная” версия работает по-другому?
Я с джавой работаю давно, поэтому причину знаю и расскажу вам в следующем посте. А пока давайте оценим единственную версию из интернета:
🧔 Элементы задаются все и сразу, программист их видит. Если передаются дубликаты или null - это точно ошибка разработчика.
Здесь можно потренировать критическое мышление и подумать, что не так с этой версией.
А потом прочитать 3 причины:
✨ Не всегда значения наглядно записаны. Их могут передать через конструктор или через поля обьекта:
✨Бросать исключение в рантайме при подозрении на опечатку как-то слишком брутально
✨ Если мы не считаем null нормальным элементом, почему не отфильтровать его в начале? Почему мы видим NPE из глубин реализации?
Итого, вариант “чтобы разработчик был внимательнее” нам не подходит. Причина в чем-то другом.
Оставлю вас подумать над этим, а в следующем посте расскажу, почему Set.of такой странный. Там интересно😊
В прошлом посте обсуждали toList, в этом обсудим Set.of. Здесь ситуация сложнее и гораздо интереснее! Прекрасный пример для тренировки анализа и критического мышления.
Начнем с ответа: код с Set.of выбросит IllegalArgumentException(duplicate element: 2)
Неожиданно! Set - коллекция уникальных элементов. Мы привыкли, что сет сам фильтрует дубликаты и часто это используем.
Почему теперь сет ругается на дубликаты? Почему фильтрацию по уникальности должен делать разработчик перед вставкой?
Но это ещё не всё. Если убрать дубликаты и оставить код таким:
Set.of(1, 2, null)получим NPE Cannot invoke "Object.hashCode()" because "pe" is null. Какая-то ошибка внутри реализации🤯
Поищем ответ в документации, точнее в JEP. Set.of появился в java 9, его цель обозначена явно - создать удобную альтернативу ручному заполнению сета:
// 8
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set = Collections.unmodifiableSet(set);
// 9
Set<String>set = Set.of(“a“, “b“);
Удобно, конечно, но альтернатива получилась не равнозначная.
Почему новый код не умеет фильтровать дубликаты? Почему не умеет работать с null? Почему “удобная” версия работает по-другому?
Я с джавой работаю давно, поэтому причину знаю и расскажу вам в следующем посте. А пока давайте оценим единственную версию из интернета:
🧔 Элементы задаются все и сразу, программист их видит. Если передаются дубликаты или null - это точно ошибка разработчика.
Здесь можно потренировать критическое мышление и подумать, что не так с этой версией.
А потом прочитать 3 причины:
✨ Не всегда значения наглядно записаны. Их могут передать через конструктор или через поля обьекта:
Set<Long> adminIds = Set.of(user1.getId(), user2.getId());
✨Бросать исключение в рантайме при подозрении на опечатку как-то слишком брутально
✨ Если мы не считаем null нормальным элементом, почему не отфильтровать его в начале? Почему мы видим NPE из глубин реализации?
Итого, вариант “чтобы разработчик был внимательнее” нам не подходит. Причина в чем-то другом.
Оставлю вас подумать над этим, а в следующем посте расскажу, почему Set.of такой странный. Там интересно😊
🔥248👍65❤29👎3
Почему в Set.of нельзя добавить дубликаты и null?
Потому что разработчики замечтались и немного выпали из реальности😅 Но обо всем по порядку.
Set.of вышел в рамках Java 9 в 2017 году.
В 2014 в джаве начали Project Valhalla. Его основная фича - value types. Грубо говоря, это компактные классы-значения. Все поля лежат в памяти рядышком, без лишних заголовков и прыжков по куче.
Энтузиазм зашкаливает, у новой фичи большие перспективы. Идёт активная работа и обсуждение. Stuart Marks, наш сегодняшний герой, активно вовлечён в этот процесс.
Но в джаве много направлений. Одна из задач Стюарта - реализовать апи для маленького сета, сделать удобную форму для такого кода:
Просто скрыть эту логику за Set.of кажется неэффективным. Значений мало, они не будут меняться. Так и просится какая-нибудь оптимизация.
Зимой у меня выходил пост Как ускорить HashMap. Там я описала альтернативный алгоритм для сета, в котором значения лежат рядом и не надо прыгать по ссылкам по всей куче. Сейчас в джаве его применить нельзя из-за особенностей работы с объектами.
“Но ведь скоро мы реализуем value types!”: подумал Стюарт и взял этот алгоритм за основу Set.of.
Реализация Set.of - линейное пробирование в чистом виде. Сверху стоит аннотация @ValueBased - отметка для будущего использования value types.
Отсюда понятно, почему Set.of не принимает дубликаты и null: в концепции value types таких понятий просто не существует. Любая сущность уникальна и не может быть null. С этой точки зрения, поведение Set.of абсолютно логично.
Чем меня веселит эта история.
Работу над Project Valhalla начали в 2014. Java 9 и Set.of вышли в 2017. Сейчас 2025, прошло 8 лет.
Как говорится: если разработчики сказали, что сделают value types, они сделают. Не нужно напоминать об этом каждые полгода.
Задел на прекрасную джаву будущего понятен. Но по сути разработчики выкатили реализацию с оглядкой на фичу, которая не вышла и в обозримом будущем не выйдет. Очаровательно😄
Потому что разработчики замечтались и немного выпали из реальности😅 Но обо всем по порядку.
Set.of вышел в рамках Java 9 в 2017 году.
В 2014 в джаве начали Project Valhalla. Его основная фича - value types. Грубо говоря, это компактные классы-значения. Все поля лежат в памяти рядышком, без лишних заголовков и прыжков по куче.
Энтузиазм зашкаливает, у новой фичи большие перспективы. Идёт активная работа и обсуждение. Stuart Marks, наш сегодняшний герой, активно вовлечён в этот процесс.
Но в джаве много направлений. Одна из задач Стюарта - реализовать апи для маленького сета, сделать удобную форму для такого кода:
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set = Collections.unmodifiableSet(set);
Просто скрыть эту логику за Set.of кажется неэффективным. Значений мало, они не будут меняться. Так и просится какая-нибудь оптимизация.
Зимой у меня выходил пост Как ускорить HashMap. Там я описала альтернативный алгоритм для сета, в котором значения лежат рядом и не надо прыгать по ссылкам по всей куче. Сейчас в джаве его применить нельзя из-за особенностей работы с объектами.
“Но ведь скоро мы реализуем value types!”: подумал Стюарт и взял этот алгоритм за основу Set.of.
Реализация Set.of - линейное пробирование в чистом виде. Сверху стоит аннотация @ValueBased - отметка для будущего использования value types.
Отсюда понятно, почему Set.of не принимает дубликаты и null: в концепции value types таких понятий просто не существует. Любая сущность уникальна и не может быть null. С этой точки зрения, поведение Set.of абсолютно логично.
Чем меня веселит эта история.
Работу над Project Valhalla начали в 2014. Java 9 и Set.of вышли в 2017. Сейчас 2025, прошло 8 лет.
Как говорится: если разработчики сказали, что сделают value types, они сделают. Не нужно напоминать об этом каждые полгода.
Задел на прекрасную джаву будущего понятен. Но по сути разработчики выкатили реализацию с оглядкой на фичу, которая не вышла и в обозримом будущем не выйдет. Очаровательно😄
🔥217👍63❤33👎9
Сколько операций на запись может одновременно проводить ConcurrentHashMap?
Anonymous Poll
13%
1
7%
16
11%
Количество бакетов / 16
54%
Количество бакетов
3%
Количество бакетов * 2
12%
Неограниченное количество
👍42
Необычный вопрос на собеседовании
Искали мы как-то человека в команду. Пришел кандидат, обсудили опыт, прошлись по основным вопросам.
Дошли до многопоточки. Человек сказал, что нужно ее избегать, потому что тема сложная и легко ошибиться. А потом добавил:
🧑🦰: Вот Redis однопоточный, внутри у него нет блокировок, поэтому он такой быстрый!
У меня в голове сразу возникла задача “на подумать”. Решила с вами поделиться, тк такое утверждение про Redis слышу довольно часто.
Рассмотрим два сервиса:
🍓Первый - тот самый однопоточный Redis.
🚲 Второй - key-value хранилище на базе ConcurrentHashMap и Spring MVC. Код примерно такой:
Задача: сравнить оба варианта. В расчет берём только базовую функцию - записать и прочитать ключ-значение из оперативной памяти.
В итоге получился очень интересный разговор. Люблю такое на собеседованиях😊
Ещё немного вводных, чтобы глубже погрузиться в вопрос.
Структура данных
В Redis используется простая хэш-таблица. Считаем хэш ключа, определяем бакет, добавляем в список. Алгоритм даже проще, чем в джаве. В джаве список перестраивается в дерево, когда элементов много. В Redis такого нет.
Многопоточный доступ
В Redis хэш-таблица никак не синхронизирована, безопасно работать может только один поток.
ConcurrentHashMap не зря называется concurrent. Область синхронизации при записи ограничена одним бакетом, т.е число одновременно пишущих потоков ~ числу бакетов. На чтение ограничений вообще нет.
Потенциально ConcurrentHashMap способен обслужить миллионы запросов одновременно. Redis в каждый момент времени работает с одним запросом.
Явный перевес в сторону нашего велосипеда🧐
На этом этапе кандидат согласился, что всё очень загадочно. И фраза, что Redis однопоточный и потому такой быстрый, звучит странно.
Почему же считается, что Redis лучше, и как он справляется с нагрузкой своим одним потоком? Об этом расскажу завтра❤️
Искали мы как-то человека в команду. Пришел кандидат, обсудили опыт, прошлись по основным вопросам.
Дошли до многопоточки. Человек сказал, что нужно ее избегать, потому что тема сложная и легко ошибиться. А потом добавил:
🧑🦰: Вот Redis однопоточный, внутри у него нет блокировок, поэтому он такой быстрый!
У меня в голове сразу возникла задача “на подумать”. Решила с вами поделиться, тк такое утверждение про Redis слышу довольно часто.
Рассмотрим два сервиса:
🍓Первый - тот самый однопоточный Redis.
🚲 Второй - key-value хранилище на базе ConcurrentHashMap и Spring MVC. Код примерно такой:
public class MapController {
private final Map<String, Object> map = new ConcurrentHashMap<>();
@PostMapping
public void putValue(@RequestParam String k, @RequestParam Object v) {
map.put(k, v);
}
@GetMapping
public Object getValue(@RequestParam String k) {
return map.get(k);
}
}Задача: сравнить оба варианта. В расчет берём только базовую функцию - записать и прочитать ключ-значение из оперативной памяти.
В итоге получился очень интересный разговор. Люблю такое на собеседованиях😊
Ещё немного вводных, чтобы глубже погрузиться в вопрос.
Структура данных
В Redis используется простая хэш-таблица. Считаем хэш ключа, определяем бакет, добавляем в список. Алгоритм даже проще, чем в джаве. В джаве список перестраивается в дерево, когда элементов много. В Redis такого нет.
Многопоточный доступ
В Redis хэш-таблица никак не синхронизирована, безопасно работать может только один поток.
ConcurrentHashMap не зря называется concurrent. Область синхронизации при записи ограничена одним бакетом, т.е число одновременно пишущих потоков ~ числу бакетов. На чтение ограничений вообще нет.
Потенциально ConcurrentHashMap способен обслужить миллионы запросов одновременно. Redis в каждый момент времени работает с одним запросом.
Явный перевес в сторону нашего велосипеда🧐
На этом этапе кандидат согласился, что всё очень загадочно. И фраза, что Redis однопоточный и потому такой быстрый, звучит странно.
Почему же считается, что Redis лучше, и как он справляется с нагрузкой своим одним потоком? Об этом расскажу завтра❤️
🔥279👍77❤58👎11