Эргономичный код
833 subscribers
96 photos
3 videos
24 files
440 links
Канал о разработке поддерживаемых бакэндов - про классическую школу TDD, прагматичное функциональное программирование и архитектуру и немного DDD.

Группа: https://t.me/+hrqD87p0Oa

Канал в Max: https://max.ru/id544512614458_biz

https://azhidkov.pro
Download Telegram
Привет!

Для меня работа с БД - это самая большая боль Эргономичного подхода.
Возможно, после публикации второго тома ретро (ориентировачно в четверг) - я напишу пост о том, почему мне не подходит хибер и почему подходит Spring Data JDBC.

Но SDJ хоть и подходит концептуально, в повседневной жизни бывает довольно болючь (о чём я тоже когда-нибудь напишу).

Поэтому я, стараясь не привлекать внимание санитаров, подумываю о собственном ОРМе (у меня уже даже есть небольшой прототип АПИ, работающий поверх Мап).

И в ОРМе самой большой проблемой видится загрузка развесистых агрегатов - агрегатов с несколькими вложенными коллекциями. И если оставаться в широко поддерживаемом подмножестве SQL, то у этой задачи (как казалось) нет эффективного решения - либо куча отдельных запросов, либо декартово произведение. Либо какой-то умный планировщик, который комбинирует эти подходы, и на котором если не докторскую, то кандидатскую точно можно защитить.

В качестве потенциально решения этой проблемы на "продвинутом SQL" мне виделся SQL MULTISET или его эмуляция на json-е.

А сегодня наткнулся на статью с амбициозным названием "This is the Beginning of the End of the N+1 Problem" от основного разработчика SDJ, где он описывает решение на оконных функциях. И самое чудесное, что это решение в минимальном виде уже работает в мейлстоуне SDJ 3.2.0.

Возможно, мне всё-таки не придётся писать ОРМ и SDJ станет тем инструментом работы с БД, который не только подходит мне идеологически, но и зубную боль не вызывает.

P.S.
Но не спешите переезжать на SDJ ради решения проблемы Н+1. Если вы сейчас на хибере, то у вас совершенно точно изменяемая модель данных и, скорее всего, не функциональная архитектура. И если вы не смените подход к проектированию на неизменяемую модель данных и функциональную архитектуру, то получите все те ужасы, баги и боли, которых говорит Андрей в Меняем Spring Data JPA на Spring Data JDBC!. И очень мало получите взамен.

#posts@ergonomic_code #spring_data_jdbc@ergonomic_code #ergo_approach@ergonomic_code
❤4🤔1
Привет!

Позавчера вышел Spring Boot 3.3.0 - не забудьте обновится - с каждым релизом это всё сложнее:)

#spring@ergonomic_code
👍6
Привет!

Не прошло и месяца и я сделал это!

Представляю вашему вниманию Project Mariotte - минималистичный и подробно задокументированный демопроект Эргономичного подхода на примере сервиса бронирования номеров в отелях.

У меня есть идея ещё пары-тройки проектов, с демонстрацией более развесистой бизнес-логики и более сложными взаимодействиями с инфраструктурой - надеюсь на горизонте пары лет их я тоже сделаю:)

#project_mariotte@ergonomic_code #ergo_approach@ergonomic_code #spring@ergonomic_code #tdd@ergonomic_code #functional_architecture@ergonomic_code #dop@ergonomic_code #immutable_domain_model@ergonomic_code
👍16🔥6
Привет!

Я посмотрел Migrating from (Spring Data) JPA to Spring Data JDBC.
И пересмотрел Меняем Spring Data JPA на Spring Data JDBC! (на который уже писал своё фи).

Удивительно, насколько эти доклады об одном и том же разные:
1. Дженс начинает с тестов и выделяет для них целый слайд. Андрей между делом упоминает их в конце доклада
2. Дженс топит за то, чтобы делать это очень аккуратно и постепенно и рассказывает про любопытную методику больших рефакторингов. А Андрей шарашит с распахнутым забралом и даже без тестов
3. Дженс практически ~50% доклада (~20 минут из ~40) посвящает перепроектированию модели данных. Андрей посвящает этому ~3% (~2 минуты из ~60)
4. Естественно, Дженс чморит JPA, а Андей - SDJ

Зато, оба доклада раскрывают тему генерации id 🤷‍♂️ И я до сих пор не понимаю, чем default nextval('seq') плох, раз уж generated always as identity не подходит

И эта разница навела меня на новый аргумент против jpa за sdj.

При разработке приложения на базе JPA основными сложностями будут изучить саму JPA, потом накастовать аннотаций, чтобы она делала то, что надо, а через полгода угадывать как это всё будет работать. Это всё - привнесённая сложность (Accidental complexity), которую можно и не привносить.

А при разработке приложения на базе SDJ основной сложностью будет декомпозиция модели на агрегаты. И вот это уже сложность, присущая задаче.

И хотя, теоретически, JPA позволяет так же выполнить декомпозицию модели, она не обязывает это делать.
В результате в приложениях на JPA на декомпозицию предметной области забивают и в ответ получают Big Ball of Mud. Так произошло в 100% проектах на JPA, которые я видел.

Проектирование агрегатов - действительно сложная тема и по ней написано много хороших книг. Но с ними со всеми есть одна проблема - они полагаются на экспертов предметной области.

И хотя это лучший способ выполнить декомпозицию предметной области, у него есть недостатки:
1. Эксперты предметной области зачастую недоступны в принципе.
2. А когда доступны - извлечение формальной модели из головы другого человека средствами естественного (неформализованного) языка - это в целом задача на уровне "мишшн импоссбл", а для многих технических специалистов просто за гранью понимания.

Из-за этого проектирование агрегатов зачастую хромает на обе ноги.

Поэтому я сейчас использую более технический подход к проектированию агрегатов. Об этом подходе надо написать отдельный пост, но если вкратце, он выглядит так:
1. Спроектировать ЕР модель, особое внимание уделить слабым сущностям
1.1 Признаки слабой сущности:
1.1.1 её жизненный цикл ограничен жизненным циклом другой сущности - позиция заказа не может существовать без заказа, номер не может существовать без отеля
1.1.2 она упоминается всегда в контексте другой сущности - вторая строчка заказа такого-то, номер 215 отеля такого-то
2. Все стержневые сущности (со слабыми и без них) считаются корнями агрегатов
3. Провести транзакционный анализ - в идеале в рамках одной транзакции должен меняться один агрегат и меняться целиком. Если это не так - что-то надо объединить, а что-то разделить.

Этот подход базируется на косвенных признаках (жизненный цикл сущностей и UI), поэтому он, скорее всего, даст менее точную модель, чем построение модели вместе с экспертом. Но он не требует эксперта и общения с ним. И лучше так, чем никак.

Ещё ссылки по теме:
1. Нотация описания неизменяемой модели данных (+ Ответы на вопросы по Эргономичной нотации ER-модели)
2. Агрегаты
3. Рациональный подход к декомпозиции систем на модули или микросервисы

В общем, инвестируйте свой интеллект в решение присущей сложности задачи, а не привнесённой:)

#why_sdj@ergonomic_code #whynojpa@ergonomic_code #ddd@ergonomic_code #spring_data_jdbc@ergonomic_code #jpa@ergonomic_code
👍7❤6
Привет!

Обещанные демо-проекты.

Project Moby - демо того, как я выкручиваюсь на Spring Data JDCB с энергичной загрузкой, когда она нужна.

Project Sherlok - демо того, как я выкручиваюсь с динамиеческими запросами.

Обе техники используются в Trainer Advisor.

#spring_data_jdbc@ergonomic_code #trainer_advisor@ergonomic_code #demo_projects@ergonomic_code
🔥10
Ещё пункт забыл

Неожиданная встреча
Случайно встретил бывшего студента.

Он рассказал, что следующему поколению советовал идти ко мне на курс по БД. А я не пришел - взял "декрет".
Но все равно приятно - снова задумался вернутся в следующем году.

А ещё (к вопросу про JPA в комментах) рассказал, что у них в большой корпорации пришел СТО, сказал что они не умеют в JPA и теперь они то ли все новое пишут, то ли старое переписывают на SDJ.

#spring_data_jdbc@ergonomic_code
👍2
Привет!

На днях перевёл Проект Р и Trainer Advisor на Kotlin 2.1.0 и Spring Boot 3.4.0
В целом всё прошло без проблем, делюсь впечатлениями

Проект Р/Котлин 2.0.0 -> 2.1.0
1. Т.к. переходил с версии 2.0.0 так же возникла проблема с пропажей продовых зависимостей в testFixtures source sets. И со второй попытки я её определил и починил быстро
2. В паре data классов с приватными конструкторами пришлось развесить @ConsistentCopyVisibility, чтобы варнинг убрать

Проект Р/Спринг 3.3.1 -> 3.4.0
1. Тест на корс, который ломился без авторизации на закрытый урл начал валиться по 401/3 (забыл уже) - перевёл его на запрос к публичному пути - заработало

Trainer Adviser/Котлин 2.0.21 -> 2.1.0
1. Заглючила идея 2024.2 и опять на testsFixtures - гредлом всё ок собирается, в редакторе ошибок нет, но при попытке сборки проектов идеевским билд тулом (я юзаю его для разработки, так как он быстрее гредлового) - ругается что не может в коде тестов найти символ из кода фикстур. Это поличилось обновлением самой идеи до 2024.3

Trainer Advisor/Спринг 3.3.5 -> 3.4.0
1. Начало взрываться responseEntity.contentLength = storedFileInputStream.metaData.size при size = 0. Добавил if (size > 0) - завелось
2. В одном из эндпоинтов на параметр принципала не был навешан @AuthenticationPrincipal. Раньше это как-то работало, после преезда перестало, после добавления аннотации снова заработало.

—

На обновление Проекта Р у меня ушло часа три.
Львиная доля ушла на:
1. переход на обратнонесовместимый Kover 0.8.3
2. datafaker в котором задепрекейтили один из методов
3. не связанные с обновлением разборки с гредлом

Trainer Advisor вообще за час максимум обновил.

Держите свои зависимости актуальными - если это делать своевременно, то это требует минимума усилий. А вот нагонять большое отставание - это уже беда. Для некоторых трудозатраты на такое приключение становятся фактически заградительными и проект начинает планомерно превращаться в мамонта.

#kotlin@ergonomic_code #spring@ergonomic_code #projectr@ergonomic_code #trainer_advisor@ergonomic_code
👍4❤3👌1
Привет!

Периодически гипотезы, лежащие в основе ЭП, рушатся о жестокую реальность промышленной разработки, что приводит к кризисам ЭП, которых уже было три штуки: раз по мотивам проекта Barcoder (решение), два (решение) и три (решение - нафик ООД, ДДД и private внутри одного репоза😀) - по мотивам #project_e.

И вот по мотивам #project_r за последние 5 дней у меня случился и благополучно разрешился очередной кризис ЭП:)

В связи с чем у меня пачка новостей.

Книга
Это уже новость второй свежести, но для контекста надо проговорить - я договорился с менеджером проектов издательства Питер начать с апреля плотную работу над книгой про ЭП. Так что надеюсь, на горизонте пары лет книга увидит свет.

Отказ от ФА
Номинально самая большая новость - я решил отказаться от ФА (которая когда-то была одним из трёх столпов ЭП).
Но по сути ничего не меняется - ЭП всё так же предполагает использование неизменяемых структур данных и максимально возможное разделение логики и ио.
Это просто признание реальности, что в ЭП, который начинался как сильно функциональный, я методично отказываюсь от отличительных черт ФА/ФП:
1. уже очень давно я отказался от монад и ROP-а в пользу защитных условий
2. затем я затащил операции в императивную оболочку
3. во многом по мотивам Проекта Р я вернул в милость выброс исключений вместо возвращения Result
4. по мотивам Проекта Р, я утащил сбалансированную форму системы с архитектурного уровня системы, на локальный уровень отдельных методов

Отказ от SDJ как дефолтной технологии работы с БД
А вот по сути самое больше изменение - я решил отказаться от SDJ в качестве дефолта для работы с БД.
Это всё ёще допустимый в рамках ЭП т.к. его всё ещё легче "продать" заказчикам и коллегам, но по умолчанию я теперь предлагаю использовать какой-то легковесный DSL - jooq или Exposed, ещё не решил что именно.

Это опять же обусловлено опытом Проекта Р - там у меня львиная доля запросов чтения рукописные, потому что SDJ не в состоянии эффективно доставать данные этого проекта, а после того, как мне пришлось ещё и два кастомных метода сохранения написать - стало понятно, что я борюсь с технологией. Этому решению так же поспособствовали пост из канала одного из участников нашей группы.
И решение перейти на UUID-ы, которое так же было обусловлено опытом разработки Проекта Р, где надо сетапать огромные графы объектов в тестах и с генераций ИДов на уровне БД это очень сложно.

Новая версия описания Эргономичной архитектуры
На мой взгляд самая крутая новость - я выложил новый подход к описанию ЭА.
По сути там ничего не изменилось, изменилась только подача.
Но эта новая подача мне настолько нравится, что я решил показать самый первый черновик.
Основная фишка - я отказался от попытки быть как все и уместить архитектуру на одну картинку и ввёл идею проекций. Их три:

1. проекция структуры данных (модель данных) - тут про агрегаты и связи между ними
2. проекция состояния (объектная модель) - тут про объекты в рантайме (порты, операции, ресурсы) и связи между ними
3. проекция структуры поведения (процедурная модель) - тут про методы и связи между ними

Плюс я туда сразу накидал кучу примеров (пока на словах😕), а так же процессы построения всех этих структур и итоговой архитектуры.

Над единой картинкой я тоже ещё подумаю: кажется, судя по проектам физических изделий, где есть проекции по плоскостям плюс изометрия или проекты зданий, где есть тоже должны быть разные проекции и разные слои (с электропроводкой, например) - для ЭА тоже можно что-то такое сделать.

И возвращаясь к книге - эта статья по сути - четверть книги. Заполнить там пробелы, добавить примеры и теоретическая часть по ЭА готова. Вспоминая про бейзкамповский Hill Chart, у меня сейчас такое ощущение, что я достиг холма реализации ЭА и дальше только дело техники. Лишь бы очередной проект не зафейлил очередную гипотезу 😄

#the_book@ergonomic_code #functional_architecture@ergonomic_code #ergo_arch@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥6👍4❤2
Привет!

Ток что впервые за 4 года пришлось запилить руками dirty checking в SDJ...

Хотел написать я.
Но уже буквально коммитаясь я в последний момент одуплил, что я поломал основную идею ДДД, SDJ и эргономичного персистанса - у меня одна сущность оказалось частью двух агрегатов.

По бизнес процессу, она сначала появляется в одном агрегате, а потом перемещается во второй агрегат того же типа, но в другую связь (условно - из черновика в основные данные). Добавил удаление ссылки из первого агрегата при перемещении - и обошлось:)

Это же действие, заодно сделало модель более надёжной - черновики меняются на месте и без доп логики, а основные данные с версионированием и приплясками. И в оригинальной модели как раз таки была возможно похачить сущность через черновик без версионирования, а сейчас такой фокус не проканает.

5-ый кризис ЭП пролетел буквально за часик:)

Хотел написать я.
А потом упёрся в то, что есть ещё одна сущность, которую я сделал частью нескольких агрегатов одного типа...

А если учесть, что эта сущность в перспективе будет контрибьютить в инварианты этого типа агрегатов - Проект Р, кажись, не у ЭП, а у ДДД трубу зашатал...

#project_r@ergonomic_code #ddd@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
😱4👍1
Привет!

У меня в новом сервисе в Проекте Э на Spring Boot 3.5 новый рекорд скорости тестов:)
54 кейса за 2.7 секунды, 90% тестов - < 60мс, медиана - 14мс.

Сетап:
1. инфра (PostgreSQL и Minio на RAM-диске) запускается руками по кнопке в Идее docker compose-ом.
2. Отказ от компонент скана бинов
3. Отказ от скана авто-конфигов
4. ленивая иницилазиация бинов (spring.main.lazy-initialization)
5. Работа через WebTestClient с MockMvc

Не 5мс на кейс, как у Саши Раковского, конечно, но уже близко и это полный набор тестов, после которого можно деплоить в прод:)

#ergo_testing@ergonomic_code #project_e@ergonomic_code #spring_boot@ergonomic_code
🔥8👍7
Привет!

Обещанный микропост про эксперимент с http4k.

Напомню контекст - мне недавно понадобилось сделать в Проекте Э новый небольшой сервис и у нас довольно сильно ограничена RAM на стендах, поэтому я решил поэксперементировать с http4k + jooq jdbi с целью сократить потреблением RAM.

Изначально я написал прототип на Spring Boot 3.5 (MVC + Data JDBC) буквально с парой методов - создание сущности на 3 поля плюс файла (в минио) и получение файла.
Далее методика всех измерений была следующая:
1. запустить сервис
2. curl-запросами создать сущность и получить картинку
3. померить ps-ом RSS процесса.

И для чистого Spring Boot я получил 270mb.

Тогда я написал прототип на http4k + jdbi и для него получил 236mb. В целом на этом можно было бы и закончить эксперимент, т.к. экономия была несущественная, зато с http4k пришлось бы руками писать аутентификацию, rbac, управление транзакциями, кэширование, да ещё и запросы на доменные модели руками мапить.

Но я не закончил и накопал ещё пару любопытных штук.

Первым делом я пошёл к гопатычу и спросил какой стэк под JVM дал бы большую экономию по памяти. Он накидал несколько вариантов, среди которых обещал ~150mb RAM для Micronaut. Я спросил за счёт чего Micronaut экономит память. Он сказал - за счёт Netty.

Я думаю ну ладно, в http4k переехать на Netty - дело пары строк.
И переехал. И получил 156mb RAM.

А заодно ещё и дикий WTF - у http4k API обработчиков запросов - синхронное. И нет ручки, чтобы настроить Netty на виртуальные потоки. То есть из коробки Netty в http4k - это фикция.
Пока разбирался как таки примострячить виртуальные потоки в http4k/Netty залез в исходники и нашёл там коммент с "элегантым" решением этой проблемы:

Stock version of an Netty Server. Not that if you want to configure your own server instance you
can duplicate this code and modify it as required
. We are purposefully trying to limit options
here to keep the API simple for the 99% of use-cases.

Ну т.е. хотите чтобы Нетти норм работал - копипастьте код либы и делайте с ним что хотите 🤦‍♂️ О каких 99% юзкейсах они говорят - вообще не представляю.

В общем поматерился я на хттп4к и подумал, что Spring Boot же тоже может в Netty. И там вроде как даже не обязательно делать контроллеры на Reactor-е или корутинах - вроде как можно писать обычные контроллеры и настроить (вот это ничего себе!) тредпул на котором Спринг будет их дёргать.
Сказано-сделано, минут за 15-30 переехал на WebFlux, барабанная дробь... 245mb... Тут у меня бюджет на эксперименты начал уже заканчиваться, поэтому я решил, что дело количества зависимостей, которые тащит с собой WebFlux и пошёл дальше.

А дальше я решил дать ещё один шанс Spring Boot Native. Как и в прошлый раз пришлось повоевать с рефлекшеном, но на этот раз со мной был GPT-5 и эти проблемы он щёлкал на раз-два, поэтому ещё минут через 30 я получил цифры для нативной сборки - 157mb. Неплохо.

Однако пока копался с Нативом, я наткнулся на Spring Boot Ahead-of-Time Processing, который должен был дать оптимизацию времени запуска и потребления RAM без 3 минут компиляции и сюрпризов в рантайме Нативной сборки. И он в целом дал - 234mb RAM.

Итого вышли такие цифры:
1. Spring Boot + Tomcat - 270mb
2. http4k + Jetty - 236mb
3. http4k + Netty - 156mb
4. Spring Boot + Netty - 245mb
3. Spring Boot Native + Tomcat - 157mb
4. Spring Boot AOT Processing + Tomcat - 234mb

Помедитировав на них я решил, что переход на http4k того не стоит и снова остался на Spring Boot. Не люблю его за монструозность и количество автомагии но для меня сейчас он всё ещё остаётся наименьшим из зол.

#spring_boot@ergonomic_code #project_e@ergonomic_code
Please open Telegram to view this post
VIEW IN TELEGRAM
👏8👍6🔥5❤3
Привет!

Наконец-то осилил пост про миграцию на Spring Boot 4.
Ну как осилил - процентов 10-20 от того, что реально можно было написать.

Но даже на это у меня ушли какие-то несуразные и невероятно утомительные часов 30-60 работы (включая собственно миграцию и разбивку на коммиты так, чтобы потом можно было сделать структурированное описание проблем) и 4 календарных месяца.

При том что Spring Boot вообще не моя тема и эта работа никак не продвинула меня к финализации Эргономичного подхода.

Поэтому я решил, что описать только проблемы с наиболее распространёнными технологиями - автоконфигами и Jackson-ом - будет вполне себе "good enough" ™️.

А остальное - никогда-нибдуь потом, микропостом, если будут вопросы по моему опыту.

#spring@ergonomic_code
Привет!

Вести с полей Spring Data Jdbc.

Я года 2 назад запилил небольшой DSL для SDJ, который позволяет писать запросы так:

jdbcAggregateOperations.findAll(
query(CountryCode::code isEqualIfNotNull countryCode.code),
Country::class.java
)


Но в котором есть гора дублей с типизацией и без:

infix fun String.isEqualIfNotNull(value: String?): Criteria {
return if (value != null) {
this.isEqual(value)
} else {
Criteria.empty()
}
}

infix fun <T, V> KProperty1<T, V>.isEqualIfNotNull(value: String?): Criteria {
return this.columnName().isEqualIfNotNull(value)
}


--

А недавно запилил фрагмент, который позволяет делать merge/upsert с кастомным SQL-ем:

interface DomainEventsRepo :
CrudRepository<DomainEvent, UUID>,
MergeExecutor<DomainEvent> {

fun mergeIgnoringNew(entity: DomainEvent): DomainEvent? {
return mergeIgnoringNew(
insertSql = """
INSERT INTO domain_event_inbox ( id, event_id, domain_event, created_at, updated_at, processed, version )
VALUES ( :id, :event_id, :diary_domain_event, :created_at, :updated_at, :processed, :version )
ON CONFLICT (id) DO NOTHING
RETURNING *
""",
entity = entity
)
}


И вот, в SDJ 4.1 завозят типизированные пути в Criteria API и merge из коробоки. Мелочь, а приятно.

В целом эти пара плюшек и RestTestClient (и ускорение тестов процентов на 10% за счёт перехода на него с WebTestClient) - вполне окупают заморочки на миграцию на Spring Boot 4, имхо.

#spring_data_jdbc@ergonomic_code #tools@ergonomic_code
Привет!

У меня периодически спрашивают как обстоят дела с перформансом у ЭП в целом и функционального стиля + Spring Data JDBC в частности.

Я всегда говорил "У меня не хайлоад и мне всегда хватало - никогда не приходилось ничего целенаправленно оптимизировать".

И вот у нас Проекте Э планируется переход от ~0.5 (3 в утренний пик) RPS к штатным 17 RPS 24/7. Тоже, конечно, не супер хайлоад, но уже существенная нагрузка, которая откровенных косяков не простит.

Я соответственно пошёл мерить и к моему небольшому удивлению, бэк сходу вывез эти 17 rps в течение 10 минут. При том по прочим метрикам вывез без проблем и мог бы больше, но я пока не стал искать предел - есть более приоритетные задачи.

Единственное что, это был только первый этап и БД была практически пустая - 100К записей в целевой таблице, а с такой нагрузкой рабочий объём будет 500-600М записей.

Тестируемая операция:
0. авторизация по JWT-токену с парсингом и верификацией и с SELECT-ом его активности в БД
1. нетривиальный парсинг json-а с поддержкой 8 минорных версий схемы, валидация и дедупликация данных
2. один SELECT с локом по юзеру
3. пара простых SELECT-ов в целевую таблицу
4. маппинги DTO -> Domain -> Persistence
5. один INSERT в целевую Postgres inherited table
6. один простой INSERT в таблицу transactional outbox-а
7. асинхронно - публикация события в rabbit mq и удаление по id из outbox-а

Чутка цифр по запросам:
0. всего запросов - 14199
1. ошибок - 0
2. avg - 115.5ms
3. p90 - 151.8ms
4. p95 - 296.5ms
5. p99 - 511.9ms
6. max - 1968.9ms.

И по железу:
1. бэк - 1 pod в k8s с лимитами 400m CPU/750Mi RAM, JVM heap 550Mi. Вот тут я прям удивился, что spring-то оказывается мохёт на 500мб РАМ О_О. А в проде уменя зачем-то 2гб стоит.
2. Postgres - cloud.ru-шный менеджед на rds.pg.x1.large.2 | 2 vCPUs | 4 GB

17 RPS - тоже нифига не хайлоад, конечно, но уже и не стыдно людям рассказать.

#ergo_approach@ergonomic_code #spring_data_jdbc@ergonomic_code #project_e@ergonomic_code
❤2👍2😁1
Привет!

Ну, моя эпопея с нагрузкой продолжается (часть 1, часть 2) и продолжает генерять материал для канала.

Я перешёл к этапу проверки работы с БД целевого размера (600М строк).
Идти решил постепенно: для начала залил 50М строк в целевую таблицу, запустил нагрузку и... Опять упрёся в ЦПУ на хэшировании паролей в транзакции при логине -> забитый пул подключений -> тормоза аутентификации целевого запроса на получении подключения для проверки активности токена.

Решил, что хватит извращений и надо залечить проблему с хэшированием в транзакции.

Залечил, запустил нагрузку, иии... Бэк начал 500-ить на логине. То есть стало хуже чем до фикса.

Полез копаться. Выяснилось:
1. я из транзакции вытащил чтение SDJ-агрегата, у которого было две связанных коллекции
2. т.е. это был не 1 запрос, а 1 + 2 (чтение корня + чтение коллекций).
3. при том второй запрос выполняется до завершения первого
4. и так как транзакции не было, SDJ для второго запроса захватывал новое подключение
5. в итоге 10 потоков логина на чтении корня агрегата выбирали все подключения из пулла, потом пытались сделать второй запрос и блокировались навечно, потому как все подключения уже были заняты предыдущим запросом корня, который ждал результатов запроса коллекций, который ждал... ну вы поняли:)

Завернул чтение агрегата обратно в транзакцию, запустил нагрузку (на 50М строк) и...

70 rps в течении 10 минут - медианное время ответа 137мс, 99 персентиль - 314мс, максимум - 1644мс.
т.е. в итоге корректный фикс срезал мидиану на 15%, а 99персентиль - на 30.

Мораль басни:
1. Не держите тяжёлые вычисления внутри транзакций
2. Но держите все обращения к SDJ внутри транзакций:) Вообще это прямым текстом написано в оф. доках - осталось только не забывать, не тупить и не лениться.
3. SDJ безусловно на порядок-два проще Hibernate, но всё равно слишком сложен для кожанного мешка

#spring_data_jdbc@ergonomic_code #project_e@ergonomic_code #ergo_approach@ergonomic_code
❤3