Друкарня від WE.UA

Java Persistence. Production problem

Передмова

Цей пост не є туторіалом. Це лише проблема, яка в мене виникла і я ділюсь думками як би я її вирішував, спираючись на свої знання та досвід.

Що взагалі сталось?

Після деплою, на одному з серверів при старті вилетів OutOfMemoryError.

Дослідивши хіпдамп/стектрейс, проблема була при витягуванні даних з бази:

@Component
@RequiredArgsConstructor
@Slf4j
public class SomeDataInMemoryCache {
    private final MyDataRepository myDataRepository;
    private final Map<UUID, MyData> myDataCache = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        // ERROR here -> myDataRepository.getDataList(..);
        List<MyData> dataList = myDataRepository.getDataList(..);
        dataList.forEach(data -> myDataCache.put(someKey, data));
        log.debug("Size of data list={}", dataList.size());
    }
}

Сутність, яку ми дістаємо, не є громіздкою, в неї немає зв’язків, і дані, витягуються не всі, а за певний проміжок часу. Ми їх кладемо в ін-меморі кеш на рівні джави, оскільки нам потрібний швидкий і багаторазовий доступ до даних (зараз немає змоги використовувати Redis або його аналоги).

Я розраховував к-ть сутностей і пам’ять, яку вони затратять, будучи в кеші при хайлоаді. Чого ми не змогли врахувати — це пікове навантаження на хіп при витягування даних, включаюючи оверхед, який дає Spring Data JPA. Про що я? →

Щоб репродюснути проблему локально, я поставлю -Xmx850M, симулюючи обмежену к-ть пам’яті хіпа JVM (на проді стоїть інше кастомне значення, яке є не дуже великим в сучасних реаліях).

Запустимо код →

Used heap до витягування даних
Used heap під час витягування — Error

Caused by: java.lang.OutOfMemoryError: Java heap space

Давайте заради експерименту, спробуємо використати Hibernate, а не Spring Data JPA.

Пікове навантаження на хіп при використанні Hibernate є набагато нижчим

893 mb vs 284 mb

Давайте подивимось, які об’єкти повертають відповідні репозиторії.

JPA(Hibernate) repo:

@Component
@RequiredArgsConstructor
public class MyDataJpaRepo {
    private final EntityManager entityManager;

    public List<Object[]> getDataList() {
        Query nativeQuery = entityManager.createNativeQuery(MyDataRepository.QUERY);
        return nativeQuery.getResultList();
    }
}

Тут все окей, повертається result set з нашими полями.
Розмір однієї сутності: 205 байт

Spring Data JPA Repo:

public interface MyDataRepository extends JpaRepository<SomeEntity, Long> {

    // in real code there is no "*", we use projection to fetch specific fields and I filter by some parameters
    String QUERY = "SELECT * FROM mydata limit 140000;";

    @Query(value = QUERY, nativeQuery = true)
    List<MyData> getDataList(/*parameters*/);
}

Розмір однієї сутності: 581 байт, через проксювання.

Що можна зробити? (1-3 варіанти ненайкращі)

1) Використати звичайний Hibernate, через відсутність оверхеду з боку спрінга
2) Оскільки ми боремось з піковим навантаженням, спричиненим витягуванням великого об’єму даних за раз, ми можемо розбити його на частини. Тобто виконати N запитів, замість одного.

Знизу приклад. Варто зауважити, що к-ть частин варто ретельно рахувати. Давайте просто спробуємо виконати 10 запитів, замість 1

Застосунок справився з піковим навантаженням (Spring Data JPA Projection)
public void callPartition() {
    log.info("Calling spring data jpa projections partition");
    int entitiesPerPartition = 14_000; // Number of entities per partition
    int totalPartitions = 10; // Total number of partitions
    for (int i = 0; i < totalPartitions; i++) {
        int offset = i * entitiesPerPartition;
        List<MyData> chatSessions = myDataRepository.getDataList(entitiesPerPartition, offset);
    }
}

3) Поєднати 1 і 2 варіанти

4) Використати Stream API в Spring Data JPA Repo

До:

-Xmx850M

w/o Stream API. Fetch 140k records
Error :(

Після:

Stream API. 140k entities. Fetch size=1000. Works OK

Використання стрімів дозволяє ефективно обробляти великі набори даних, не завантажуючи всі дані в пам'ять одночасно. Це покращує продуктивність і знижує споживання пам'яті порівняно з вибіркою всіх сутностей за один раз.

Під капотом там звичайний JDBC connection fetch size.

org/springframework/data/jpa/repository/query/JpaQueryExecution.java

static class StreamExecution extends JpaQueryExecution { .. }

Якщо полізти всередину, то можемо побачити таке:

public ScrollableResultsImplementor<R> performScroll(ScrollMode scrollMode, DomainQueryExecutionContext executionContext) {
    return executionContext.getSession().getJdbcServices().getJdbcSelectExecutor().scroll(jdbcSelect,scrollMode,jdbcParameterBindings,SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext ),null);

}
default JdbcSelectExecutor getJdbcSelectExecutor() {
   return JdbcSelectExecutorStandardImpl.INSTANCE;
}
protected void bindParameters(PreparedStatement preparedStatement) throws SQLException {
   final QueryOptions queryOptions = executionContext.getQueryOptions();

   // set options
   if ( queryOptions != null ) {
      if ( queryOptions.getFetchSize() != null ) {
         preparedStatement.setFetchSize( queryOptions.getFetchSize() );
      }
      if ( queryOptions.getTimeout() != null ) {
         preparedStatement.setQueryTimeout( queryOptions.getTimeout() );
      }
   }
preparedStatement.setFetchSize(queryOptions.getFetchSize()

  1. Стаття на Medium про бетчінг юзаючи стріми

  2. Профайлер — VisualVM

Статті про вітчизняний бізнес та цікавих людей:

  • Бронеплівка для вікон: коли вона доречна та як обрати рішення

    Як бронеплівка утримує уламки, де її встановлюють та чому перед монтажем важливо оцінити стан і конструкцію скління.

    Теми цього довгочиту:

    Бронеплівка На Вікна
  • Шлейф сучасного смартфону та його призначення

    Шлейф - це тонка пластикова стрічка з ледь видимими доріжками і саме вона зʼєднує важливі компоненти мобільного, без яких він не запрацює. Власники пристроїв Xiaomi, які стикаються з потребою заміни цієї деталі, можуть підібрати відповідний варіант на сайті AKS.UA

    Теми цього довгочиту:

    Шлейф Для Мобільного
  • Чохли для iPhone 15: повний гід по кольорах, матеріалах і виробниках

    Перед тим як вибрати чохли для iPhone 15, варто визначитися, що саме ви хочете отримати від аксесуара. Комусь потрібен тонкий прозорий корпус, інший покупець шукає посилений захист, а для когось вирішальним стане колір або підтримка MagSafe

    Теми цього довгочиту:

    Чохли Для Iphone
  • Як побудована програма Meest China Academy

    Курс про товарний бізнес охоплює різні етапи роботи: від пошуку ідеї до перевірки товару, логістики та масштабування. Програма Meest China Academy містить 17 модулів, які послідовно розкривають ці теми без зведення всього навчання до однієї універсальної поради.

    Теми цього довгочиту:

    Meest
  • Які технології використані в Айфон 17

    Айфон 17 поєднує OLED-дисплей із частотою до 120 Гц, чип A19, дві камери Fusion 48 Мп і швидке заряджання через USB-C. У COMFY можна купити Айфон 17 з накопичувачем на 256 або 512 ГБ, вибравши конфігурацію відповідно до обсягу фото, відео та застосунків

    Теми цього довгочиту:

    Iphone 17
Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Oleksandr Klymenko
Oleksandr Klymenko@overpathz

Java Software Engineer

40Довгочити
9.4KПерегляди
104Підписники
На Друкарні з 19 квітня 2023

Більше від автора

  • Java Memory Model ↔ PostgreSQL MVCC: один і той самий фундамент конкурентності

    Java Memory Model та PostgreSQL MVCC/Isolation Levels — це дві відповіді на одне й те саме фундаментальне питання: за яких умов зміна, зроблена одним учасником (потоком / транзакцією), стає видимою для іншого?

    Теми цього довгочиту:

    Java
  • Як виконується SQL-запит

    Сьогодні розберемо, що відбувається з вашим SELECT * FROM users WHERE age > 21 перед тим, як БД поверне вам дані

    Теми цього довгочиту:

    Computer Science

Це також може зацікавити:

Коментарі (0)

Підтримайте автора першим.
Напишіть коментар!

Це також може зацікавити: