Java8 Stream или Reactive / Observer для запросов к базе данных

Я переосмысливаю поведение нашего приложения Spring MVC: лучше ли вытащить (поток Java8) данные из базы данных или позволить базе данных протолкнуть (реактивный / наблюдаемый) данные и используйте противодавление, чтобы контролировать количество.


Текущая ситуация:

  1. User запрашивает 30 самых последних статей
  2. Service выполняет запрос к базе данных и помещает 30 результатов в List
  3. Jackson перебирает List и генерирует ответ JSON

Зачем менять реализацию?

Это довольно затратно по памяти, потому что мы все время храним эти 30 объектов в памяти. В этом нет необходимости, потому что приложение обрабатывает один объект за раз. Хотя приложение должно иметь возможность получить один объект, обработать его, выбросить и получить следующий.


Потоки Java8? (тянуть)

С java.util.Stream это довольно просто: Service создает Stream, который использует курсор базы данных за кулисами. И каждый раз, когда Jackson записывает строку JSON для одного элемента Stream, он запрашивает следующий, который затем запускает курсор базы данных для возврата следующей записи.


RxJava / Reactive / Observable? (нажать)

Здесь у нас противоположный сценарий: база данных должна продвигать запись за записью, а Jackson должен создавать строку JSON для каждого элемента, пока не будет вызван метод onComplete.

то есть Controller сообщает Service: дайте мне Observable<Article>. Затем Jackson может запросить столько записей в базе данных, сколько сможет обработать.


Различия и проблемы:

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

При использовании RxJava данные должны быть всегда доступны для обработки. А если это слишком много, мы можем использовать противодавление, чтобы замедлить передачу данных из базы данных в наше приложение. В худшем случае буфер / очередь будут содержать все запрошенные записи базы данных. Тогда потребление памяти будет равно нашему текущему решению с использованием List.


Почему я спрашиваю / о чем прошу?

  • Что я пропустил? Есть ли другие плюсы / минусы?

  • Почему (особенно) Spring Data Team расширила свой API для поддержки Stream ответов из базы данных, если всегда есть (короткая) задержка между каждым запросом / ответом базы данных? Это может привести к некоторой заметной задержке для большого количества запрошенных записей.

  • Рекомендуется ли использовать RxJava (или другую реактивную реализацию) для этого сценария? Или я упустил какие-то недостатки?


person Benjamin M    schedule 17.06.2016    source источник


Ответы (1)


Кажется, вы говорите о размере выборки для основного движка базы данных.

Если вы уменьшите его до одного (выборка и обработка одной строки за раз), да, вы сэкономите место во время запроса ...

Но обычно имеет смысл иметь разумный размер блока. Если он слишком мал, у вас будет много дорогостоящих сетевых обходов. Если размер блока слишком велик, вы рискуете исчерпать память или вызвать слишком большую задержку при выборке. Так что это компромисс, и правильный размер блока / выборки зависит от вашего конкретного варианта использования.

Что касается реактивного подхода или нет, я считаю, что это не актуально. Как и в случае с RxJava и, скажем, Cassandra, можно создать Observable из асинхронного набора результатов, и это зависит от запроса (конфигурации), сколько элементов должно быть извлечено и отправлено за раз.

person yurgis    schedule 17.06.2016
comment
Да, я полагаю, что оба пути - это, по сути, тяга. Stream будет извлекать по одному элементу за раз (что делает 1 возврат на каждую запись в базе данных). RxJava было бы чем-то вроде буферизованной тяги. Он извлекает, например, 10 элементов, помещает их в очередь / буфер, а затем отправляет по одному элементу в Oberserver. (Интересно, можно ли держать буфер заполненным все время. Т.е. запросить еще 10 элементов, когда в буфере осталось только 5 элементов) ... Также должна быть возможна реализация такого поведения с использованием java.util.stream, но потребует серьезной работы и времени. - person Benjamin M; 18.06.2016
comment
В этом случае все сводится к тому, насколько быстро ваш производитель (db) и потребитель относительно друг друга. Если производитель может идти в ногу с потребителем, то противодавление определенно поможет, если вы решите использовать реактивный подход. Это также, вероятно, поможет вам минимизировать размер выборки до такой степени, что он не поспевает за потребителем. - person yurgis; 18.06.2016
comment
Так что ... поправьте меня, если я ошибаюсь. В общем, я бы сказал, что 1. использование java.util.Stream не является правильным решением, когда время отклика имеет решающее значение, потому что в конце есть forEach(...), который вызовет вызов базы данных для каждого элемента. По-прежнему может быть хорошим решением для фоновых задач, когда время отклика не имеет значения. 2. реактивный шаблон имеет интегрированный буфер / очередь, что позволяет получать сразу несколько записей БД. И можно по-прежнему установить размер выборки 1 и получить то же поведение, что и с потоком. 3. размер выборки = компромисс между задержкой и использованием памяти - person Benjamin M; 18.06.2016
comment
№2. Да, в значительной степени я имел в виду именно это. Я считаю, что с номером 1 вы все еще можете создать интеллектуальный источник потоковых данных, который извлекает несколько элементов за раз - например, поток списков, в котором каждый список вы картографируете до отдельных элементов. - person yurgis; 18.06.2016
comment
Конечно, я мог бы это написать, но зачем мне тратить на это время, если уже есть хорошо протестированное и работающее решение ;-). В любом случае спасибо за вашу помощь и разъяснение плюсов и минусов !! - person Benjamin M; 18.06.2016