Статьи

Потребность в скорости, доступ к существующим данным в 1000 раз быстрее

Узнайте, как вы можете ускорить свои аналитические базы данных в 1000 раз с помощью стандартных потоков Java 8 и ускорителя Speedment In-JVM-Memory.

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

В этой статье мы узнаем, как аналитические базы данных могут быть ускорены на порядки, используя стандартные потоки Java 8 и технологию ускорения памяти в JVM Speedment. В конце мы запустим тестовый костюм JMH с репрезентативными тестами, которые показывают коэффициент ускорения, превышающий 1000 раз.

Просмотр базы данных в виде потоков

Speedment — это современный ORM на основе потоков, означающий, что таблицы рассматриваются как стандартные потоки Java 8. В этой статье мы будем использовать базу данных «Sakila», которая является примером базы данных с открытым исходным кодом, доступной непосредственно от Oracle здесь . База данных примера Сакилы содержит фильмы, актеров и так далее. Вот как может выглядеть поток Java 8 из базы данных:

1
2
3
4
5
6
List<Film> secondPage = films.stream()
        .filter(Film.RATING.equal("PG-13"))
        .sorted(Film.TITLE.comparator())
        .skip(50)
        .limit(50)
        .collect(Collectors.toList());

Этот поток отфильтрует только те фильмы, которые имеют рейтинг, равный «PG-13», а затем отсортирует оставшиеся фильмы по названию фильма. После этого первые 50 фильмов пропускаются, а затем следующие 50 фильмов собираются в список. Таким образом, мы получаем вторую страницу всех фильмов PG-13, отсортированных по порядку названий. Обычно нам также нужно знать, сколько всего фильмов имеет рейтинг «PG-13», чтобы показать правильно масштабированную полосу прокрутки в нашем приложении. Это можно сделать так:

1
2
3
long count = films.stream()
        .filter(Film.RATING.equal("PG-13"))
        .count();

Использование базы данных

Speedment автоматически отобразит потоки в SQL. Таким образом, мы можем оставаться в чистой типизированной среде Java без необходимости написания кода SQL. Включив ведение журнала, мы можем видеть, что первый поток подкачки будет отображен в следующем запросе SQL (при условии, что мы используем MySQL):

01
02
03
04
05
06
07
08
09
10
11
12
13
SELECT
    `film_id`,`title`,`description``release_year`,
    `language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
    `length`,`replacement_cost`,`rating`,`special_features`,`last_update`
FROM
    `sakila`.`film`
WHERE
    (`sakila`.`film`.`rating`  = ? COLLATE utf8_bin)
ORDER BY
    `sakila`.`film`.`title` ASC
LIMIT ? OFFSET ?
 
values:[PG-13, 50, 50]

Второй поток подсчета будет представлен:

01
02
03
04
05
06
07
08
09
10
11
12
SELECT COUNT(*) FROM (
    SELECT
        `film_id`,`title`,`description``release_year`,
        `language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
        `length`,`replacement_cost`,`rating`,`special_features`,`last_update`
     FROM
             `sakila`.`film`
        WHERE
            (`sakila`.`film`.`rating`  = ? COLLATE utf8_bin)
) AS A
 
values:[PG-13]

Таким образом, потоковые операции отображаются в эффективный SQL. При выполнении тысячи таких запросов параллельно на ноутбуке класса компьютера со стандартной конфигурацией сервера MySQL они выполняются с суммарной задержкой 700 мс и 175 мс соответственно. Если вы думаете о том, как второй SQL-оператор может быть эффективным, то факт заключается в том, что база данных сможет в основном устранить внутренний выбор.

Использование ускорения памяти в JVM

Теперь самое интересное. Давайте активируем компонент ускорения памяти в JVM в Speedment, который называется DataStore, в нашем приложении. Это делается следующим образом:

1
2
3
4
5
6
7
8
9
SakilaApplication app = new SakilaApplicationBuilder()
        .withPassword("sakila-password")
        // Activate DataStore
        .withBundle(DataStoreBundle.class)
        .build();
 
        // Load a snapshot of the database into off heap memory
        app.get(DataStoreComponent.class)
            .ifPresent(DataStoreComponent::load);

Когда приложение запускается, снимок базы данных помещается в JVM и сохраняется вне кучи. Поскольку данные хранятся вне кучи, данные не будут влиять на сборку мусора, а объем данных ограничен только доступной оперативной памятью. Ничто не мешает нам загружать терабайты данных, если у нас есть такой объем доступной оперативной памяти.

Если мы снова запустим то же приложение, мы получим совокупную задержку в 22 мс и 1 мс. Это означает, что задержка уменьшается в 30 и 170 раз соответственно. Значительное улучшение должно быть сказано. Но все лучше и лучше.

Использование ускорения памяти In-JVM и Json

REST и JSON обычно используются для обслуживания клиентов, которые запрашивают данные в эти дни. У Speedment есть специальный сборщик, который может собирать данные JSON, используя так называемую десериализацию на месте, в результате чего только те поля, которые необходимы сборщику, десериализуются из памяти вне кучи. Мы можем зависеть от плагина Json, сначала добавив зависимость в наш pom-файл:

1
2
3
4
5
<dependency>
        <groupId>com.speedment.enterprise.plugins</groupId>
        <artifactId>json-stream</artifactId>
        <version>${speedment.enterprise.version}</version>
    </dependency>

Затем мы устанавливаем плагин в ApplicationBuilder, как показано ниже:

1
2
3
4
5
6
SakilaApplication app = new SakilaApplicationBuilder()
        .withPassword("sakila-password")
        .withBundle(DataStoreBundle.class)
        // Install the Json Plugin
        .withBundle(JsonBundle.class)
        .build();

Если нам нужны только поля Film «title», «rating» и «length» в выводе json, мы можем создать кодировщик Json следующим образом:

1
2
3
4
5
6
7
final JsonComponent json = app.getOrThrow(JsonComponent.class);
 
    final JsonEncoder<Film> filmEncoder = json.<Film>emptyEncoder()
        .put(Film.TITLE)
        .put(Film.RATING)
        .put(Film.LENGTH)
        .build();

Этот декодер является неизменным и может многократно использоваться в нашем приложении:

1
2
3
4
5
6
String json = films.stream()
        .filter(Film.RATING.equal("PG-13"))
        .sorted(Film.TITLE.comparator())
        .skip(50 * pageNo)
        .limit(50)
        .collect(JsonCollectors.toList(filmEncoder));

Это дает нам дополнительный коэффициент ускорения 2 по сравнению с целыми объектами. JsonComponent может сделать гораздо больше, чем просто собирать вещи в список. Например, он может создавать агрегаты, также используя десериализацию на месте.

Запустите свои собственные проекты с ускорением памяти в JVM

Попробуйте ускорение памяти в JVM самостоятельно. Существует бесплатный инициализатор, который можно найти здесь . Просто отметьте желаемый тип базы данных, и вы получите POM и шаблон приложения, автоматически сгенерированный для вас. Вам также нужен лицензионный ключ для запуска. Просто нажмите «Запросить бесплатный лицензионный ключ» на той же странице, чтобы получить его. Если вам нужна дополнительная помощь в настройке вашего проекта, зайдите на страницу Speedment GitHub или изучите руководство .

Как быстро это по-настоящему?

Speedment поддерживает несколько типов баз данных, включая Oracle, MySQL, MariaDB, PostgreSQL, Microsoft SQL Server, DB2 и AS400. Ускорение также может работать с файлами Avro, которые используются Hadoop. В этом примере мы запустим MySQL.

Тестирование производительности в Java-приложении чрезвычайно сложно. Используя среду JMH, я написал несколько типичных приложений и провел каждый тест сотни тысяч раз и сравнил результаты для чистых MySQL и MySQL с ускорителем Speedment in-JVM. Приведенные ниже показатели производительности приведены в виде операций в секунду (чем выше, тем лучше).

эталонный тест Чистый MySQL MySQL с ускорением в JVM Коэффициент ускорения
Посчитать все 5324 43615967 8000
Посчитай с фильтром 5107 2465928 400
фильтрация 449 597702 1300
Сортировка 109 171304 1500
Paging 1547 1443015 900
Перебрать все 108 5556 50
агрегирование 117 167728 +1400
Фильтр агрегации 453 608763 1300

Как видно, MySQL с ускорителем Speedment In-JVM превосходит Pure MySQL в 1000 и более раз в большинстве случаев. Наименьший наблюдаемый коэффициент ускорения был в 50 раз, что все еще очень хорошо.

Тестовая среда

MySQL, стандартная установка 5.7.16, драйвер MySQL JDBC 5.1.42, Oracle Java 1.8.0_131, Speedment Enterprise 1.1.10, macOS Sierra 10.12.6, Macbook Pro 2.2 ГГц i7 (середина 2015 г.), 16 ГБ ОЗУ.

Контрольный код

Ниже приведены некоторые примеры того, как выглядит код теста. Полное приложение для тестирования производительности можно найти на GitHub здесь . Я рекомендую вам клонировать его и запустить, чтобы увидеть фактор ускорения на вашей целевой машине.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Benchmark
    public String paging() {
        return films.stream()
            .filter(Film.RATING.equal("PG-13"))
            .skip(50)
            .limit(50)
            .collect(filmCollector);
    }
 
 
    @Benchmark
    public String aggregationWithFilter() {
        return films.stream()
            .filter(Film.RATING.equal("PG-13"))
            .collect(sumLengthCollector);
    }

Сколько ОЗУ нужно?

Ускорение часто может хранить данные в оперативной памяти более эффективно, чем сама база данных. База данных Sakila в тесте производительности занимает 6,6 МБ на диске, но Speedment использует только 3 МБ памяти. Принимая во внимание, что Speedment индексирует все столбцы по умолчанию, тогда как база данных индексирует только несколько столбцов, Speedment удивительно эффективно использует память.

Сколько времени занимает загрузка данных?

База данных Sakila была загружена и проиндексирована Speedment менее чем за 1 секунду. Ускорение может обновить данные из базы данных в фоновом режиме и будет отслеживать, какие потоки работают против какой версии моментального снимка базы данных (MVCC).

Насколько быстрее будут работать мои собственные приложения?

Насколько можно снизить задержку в конкретном проекте, можно только догадываться. Это х10, х50, х100 или даже больше? Воспользуйтесь возможностью и узнайте, сколько скорости вы можете набрать в своих собственных проектах!

Возьми это за спин

Узнайте больше о Speedment на GitHub и начните свой собственный проект с помощью Speedment Initializer и не забудьте поставить галочку «включить ускорение в памяти», а также использовать инициализатор, чтобы получить бесплатный лицензионный ключ для ознакомления. Ознакомьтесь с разделом руководства для ускорителя Speedment in-JVM здесь или используйте мой твиттер @PMinborg