SlideShare a Scribd company logo
RXJAVA
AS A KEY COMPONENT
IN A MATURE
BIG DATA PRODUCT
by Igor Lozynskyi
HELLO!I am Igor Lozynskyi
I have 8 years of Java experience, currently work at
You can find me at @siromaha
2
Agenda
■ RxJava basics
■ Zoomdata (goals & architecture)
■ “Good” & “bad” RxJava experience
■ Some thoughts about Rx future
3
Disclaimer
■ This isn’t a sponsored talk!
■ This talk isn’t Zoomdata advertisement!
■ It’s just our experience.
4
WHAT IS
ZOOMDATA?
The Fastest Visual Analytics for Big Data
www.zoomdata.com
5
■ Data visualization software
■ Big Data
■ 30+ DB types
■ SQL, NoSQL, raw data (CSV)
■ Pretty charts
■ Platform!
Zoomdata (ZD):
6
cross-database JOIN
7
8
■ Static is boring
■ Dynamic is interesting
Instant feedback
■ We do not like to wait
9
ZOOMDATA
ARCHITECTURE
very simplified view
10
very
11
12
Let’s code it!
13
[JEEConf-2017] RxJava as a key component in mature Big Data product
15
class QueryProcessor implements Observable.Transformer<QueryRequest, QueryResult> {
Observable<QueryResult> call(Observable<QueryRequest> requests) {
...
}
}
[JEEConf-2017] RxJava as a key component in mature Big Data product
17
class QueryProcessor implements Observable.Transformer<QueryRequest, QueryResult> {
Observable<QueryResult> call(Observable<QueryRequest> requests) {
return requests
.takeWhile(req -> !isCancellation(req))
.map(this::validateRequest)
.compose(r -> requestProcessor(r))
.onErrorReturn(this::handleProcessingErrors)
}
Observable<QueryResult> requestProcessor(Observable<QueryRequestParsed> requests) {
return mainQuery(requests);
}
}
[JEEConf-2017] RxJava as a key component in mature Big Data product
19
class QueryProcessor implements Observable.Transformer<QueryRequest, QueryResult> {
Observable<QueryResult> call(Observable<QueryRequest> requests) {
return requests
.takeWhile(req -> !isCancellation(req))
.map(this::validateRequest)
.compose(r -> requestProcessor(r))
.onErrorReturn(this::handleProcessingErrors)
}
Observable<QueryResult> requestProcessor(Observable<QueryRequestParsed> requests) {
return merge(
mainQuery(requests),
intermediateResults(requests) // approximate result
);
}
}
[JEEConf-2017] RxJava as a key component in mature Big Data product
21
class QueryProcessor implements Observable.Transformer<QueryRequest, QueryResult> {
Observable<QueryResult> call(Observable<QueryRequest> requests) {
return requests
.takeWhile(req -> !isCancellation(req))
.map(this::validateRequest)
.compose(r -> requestProcessor(r))
.onErrorReturn(this::handleProcessingErrors)
}
Observable<QueryResult> requestProcessor(Observable<QueryRequestParsed> request) {
return merge(
mainQuery(requests),
intermediateResults(requests),
mainQueryProgress(requests) // based on main query
);
}
}
[JEEConf-2017] RxJava as a key component in mature Big Data product
It was easy!
23
OUR RXJAVA
EXPERIENCE
Disclaimer:
■ Your mileage may vary!
24
GOODRxJava
25
Effortless mapping of
WebSocket to
Observable Stream
26
27
class RxWebSocketSession<T extends WebSocketMessage<?>> {
spring.WebSocketSession nativeSession;
Subject<T, T> inputSubject;
Subscriber<T> socketSubscriber;
public RxWebSocketSession(spring.WebSocketSession nativeSession) {
...
}
public Observable<T> getInput() {
return inputSubject;
}
public Subscriber<T> getOutput() {
return socketSubscriber;
}
...
}
28
class NativeWebSocketHandler extends TextWebSocketHandler {
IRxWebSocketHandler<TextMessage> sessionHandler;
Map<WebSocketSession, RxWebSocketSession<TextMessage>> sessions;
void afterConnectionEstablished(WebSocketSession session) {
RxWebSocketSession<TextMessage> rxSession
= new RxWebSocketSession<>(synchronizedSession(session));
sessions.put(session, rxSession);
sessionHandler.afterConnectionEstablished(rxSession);
}
void handleTextMessage(WebSocketSession session, TextMessage message) {
sessions.get(session).handleMessage(message);
}
void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
RxWebSocketSession<TextMessage> rxSession = sessions.remove(session);
rxSession.close();
sessionHandler.afterConnectionClosed(rxSession, status);
}
}
29
class RxWebSocketHandler implements IRxWebSocketHandler<TextMessage> {
rx.Observable.Transformer<String, String> messageTransformer;
rx.Scheduler sendingIoScheduler;
public void afterConnectionEstablished(RxWebSocketSession<TextMessage> session) {
session
.getInput()
.map(TextMessage::getPayload)
.compose(messageTransformer)
.onBackpressureBuffer(OUTGOING_WS_BUFFER_CAPACITY,
() -> reportMessageDropped(session.getId()),
ON_OVERFLOW_DROP_OLDEST)
.observeOn(sendingIoScheduler)
.map(TextMessage::new)
.subscribe(session.getOutput());
}
}
It is easy to stream
intermediate results
to a client
30
It is easy to coordinate complex
& long running workflows
31
RxJava
allows to compose
dozens of
reactive streams
32
Query Cancellation
out-of-the-box
33
RxJava is
very powerful
Any stream
transformation
that you can imagine!
Image credits: Denys Nevozhai
“BAD”RxJava
35
Use TDD for Rx code!
Lack of unit tests
for Rx code
Many integration tests
instead
36Image credits: Matthew Henry
Stream code should not be too
complicated!
Rule: “10 lines max” for method
37
38
protected Observable<Map<QueryStream, ComputedTasks>> play(Map<QueryStream, ComputedTasks> initial) {
return concat(just(primaryTimeWindow), timeWindowStream)
.map(tw ->
playingReadRequests.columnKeySet().stream()
.collect(toMap(
calc -> calc,
calc -> calc.calculateTimewindow(tw)))
)
.buffer(2, 1)
.filter(contextsMaps -> contextsMaps.size() == 2)
.zipWith(checkPointsStream::iterator, Pair::of)
.scan(initialTasksByTimewindow, (tasksMap, actions) -> {
Map<TimewindowCalculator, List<ReadAction>> actionsByWindow = actions.getLeft();
Boolean doCheckpoint = actions.getRight();
Map<TimewindowCalculator, Map<QueryStream, ComputedTasks>> tasksByTimewindow = MapStream.of(actionsByWindow)
.map(e -> {
TimewindowCalculator timewindow = e.getKey();
List<ReadAction> timewindowActions = e.getValue();
Map<QueryStream, ComputedTasks> tasksOfTimewindow = tasksMap.get(timewindow);
Map<QueryStream, ComputedTasks> tasksByStream = calculateStreamTasksWithActions(timewindow,
tasksOfTimewindow, timewindowActions, doCheckpoint);
return Pair.of(timewindow, tasksByStream);
}).collect(toMap(Pair::getKey, Pair::getValue));
return tasksByTimewindow;
})
.map(tasksByTimewindow -> {
Collection<Map<QueryStream, ComputedTasks>> tasksByStreamMaps = tasksByTimewindow.values();
return mergeTasksMapsByStream(tasksByStreamMaps);
})
.doOnNext(this::rememberLastTasksReference)
.skip(1);
}
39
protected Observable<Map<QueryStream, ComputedTasks>> play(Map<QueryStream, ComputedTasks> initial) {
return concat(just(primaryTimeWindow), timeWindowStream)
.map(tw ->
playingReadRequests.columnKeySet().stream()
.collect(toMap(
calc -> calc,
calc -> calc.calculateTimewindow(tw)))
)
.buffer(2, 1)
.filter(contextsMaps -> contextsMaps.size() == 2)
.zipWith(checkPointsStream::iterator, Pair::of)
.scan(initialTasksByTimewindow, (tasksMap, actions) -> {
Map<TimewindowCalculator, List<ReadAction>> actionsByWindow = actions.getLeft();
Boolean doCheckpoint = actions.getRight();
Map<TimewindowCalculator, Map<QueryStream, ComputedTasks>> tasksByTimewindow = MapStream.of(actionsByWindow)
.map(e -> {
TimewindowCalculator timewindow = e.getKey();
List<ReadAction> timewindowActions = e.getValue();
Map<QueryStream, ComputedTasks> tasksOfTimewindow = tasksMap.get(timewindow);
Map<QueryStream, ComputedTasks> tasksByStream = calculateStreamTasksWithActions(timewindow,
tasksOfTimewindow, timewindowActions, doCheckpoint);
return Pair.of(timewindow, tasksByStream);
}).collect(toMap(Pair::getKey, Pair::getValue));
return tasksByTimewindow;
})
.map(tasksByTimewindow -> {
Collection<Map<QueryStream, ComputedTasks>> tasksByStreamMaps = tasksByTimewindow.values();
return mergeTasksMapsByStream(tasksByStreamMaps);
})
.doOnNext(this::rememberLastTasksReference)
.skip(1);
}
40
protected Observable<Map<QueryStream, ComputedTasks>> play(Map<QueryStream, ComputedTasks> initial) {
return concat(just(primaryTimeWindow), timeWindowStream)
.map(this::calculateTimeWindows)
.buffer(2, 1)
.filter(this::contextMapsOfSizeTwo)
.zipWith(checkPointsStream::iterator, Pair::of)
.scan(initialTasksByTimewindow, this::updateJobResultsForNewTimeWindow)
.map(tasksByTimewindow -> mergeTaskMapsByStream(tasksByTimewindow.values()))
.doOnNext(this::rememberLastTasksReference)
.skip(1);
}
41
protected Observable<Map<QueryStream, ComputedTasks>> play(Map<QueryStream, ComputedTasks> initial) {
return concat(just(primaryTimeWindow), timeWindowStream)
.map(this::calculateTimeWindows)
.buffer(2, 1)
.filter(this::contextMapsOfSizeTwo)
.map(this::transitionWindow)
.zipWith(checkPointsStream::iterator, Pair::of)
.scan(initialTasksByTimewindow, this::updateJobResultsForNewTimeWindow)
.map(tasksByTimewindow -> mergeTaskMapsByStream(tasksByTimewindow.values()))
.doOnNext(this::rememberLastTasksReference)
.skip(1);
}
It is easy to forget
about one very
important thing
42
Remember next slide
Image credits: Oscar Sutton
43
BACKPRESSURE
44
BACKPRESSURE
BACK
PRES
Image credits: Google search
Remember not to overflow
your client!
Your client will not be happy
47
Remember not to overflow
yourself!
48
Slow subscriber may cause data
buffering
Buffering may be costly
49
Rx
silently introduces
a notion of time
into your API
50
51
Track your stream lifetime!
GroupBy operator
52
53
Track your stream lifetime!
54
Child streams live along with
parent!
55
Track your stream lifetime!
Unpleasant when child streams hold
resources
56
Subscription takes some time
Be careful not to miss messages
57
58
Reactive transformations are
time dependent
59
Zoomdata had an issue due to this
60
https://github.com/ReactiveX/RxJava/issues/3522 (fixed in 1.0.15)
61
Hard to code complicated
context-dependent workflow
■ Loop vs flatMap
■ State should be incorporated into the stream!
62
Try not to mutate objects
inside Rx streams
■ Rx stream is a concurrent system
■ In concurrent systems mutation is evil
■ Zoomdata had such issues
63
Think twice before providing
pull API over Rx stream
64
■ Pull push hard & risky
■ Pull push mutability disaster
Think twice before providing
pull API over Rx stream
■ Push pull ok
65
It is better to control
execution threads
on your own
■ Doing CPU in IO thread
■ Doing IO in CPU thread
66Image credits: Haiku Deck
Care about concurrency
■ No ThreadLocal tricks!
■ flatMap/zip/merge may move execution into
unexpected (for you) thread
■ Immutability is the only reasonable way to go
67
and, last but not least...
68
Complexity is
still there!
69Image credits: sciencemag.org
OVERALL
EXPERIENCE
70
■ WebSocket to Rx stream mapping: 3 files
■ ZD query processing is almost entirely RxJava code
■ Most algorithms live in 10 files (~1000 loc)
□ playing, sharpening, progress, timeline, push/pull data
strategies, main query, timeline, and much more
■ RxJava is complicated, but very powerful
■ Zoomdata benefits a lot from RxJava usage!
Is RxJava good enough for ZD?
71
■ Java 9 & Spring 5 will spread reactive programming
■ We expect big shift towards Project Reactor
■ We will have more reactive and proactive software!
□ Zoomdata is already proactive
Some thoughts about Rx future
72
THANKS!
Contact me:
@siromaha
aigooor [at] gmail [dot] com
Presentation template by SlidesCarnival
Image credits: Denys Nevozhai

More Related Content

PDF
RxJS Schedulers - Controlling Time
PDF
NoSQL and JavaScript: a love story
PDF
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
PDF
Cassandra Summit EU 2014 - Testing Cassandra Applications
PPTX
Node.js and angular js
PDF
Fault tolerant microservices - LJC Skills Matter 4thNov2014
PDF
Cassandra is great but how do I test my application?
PDF
Android architecture component - FbCircleDev Yogyakarta Indonesia
RxJS Schedulers - Controlling Time
NoSQL and JavaScript: a love story
Matteo Collina | Take your HTTP server to Ludicrous Speed | Codmeotion Madrid...
Cassandra Summit EU 2014 - Testing Cassandra Applications
Node.js and angular js
Fault tolerant microservices - LJC Skills Matter 4thNov2014
Cassandra is great but how do I test my application?
Android architecture component - FbCircleDev Yogyakarta Indonesia

What's hot (20)

PDF
Cassandra Summit EU 2014 Lightning talk - Paging (no animation)
PDF
Qt Rest Server
PDF
JS Fest 2019. Thomas Watson. Post-Mortem Debugging in Node.js
PDF
Marble Testing RxJS streams
PDF
Instant add column for inno db in mariadb 10.3+ (fosdem 2018, second draft)
PDF
FOSDEM 2015: gdb tips and tricks for MySQL DBAs
PDF
MongoDB Performance Debugging
PDF
MySQL Parallel Replication: inventory, use-case and limitations
PDF
Circuit breaker
PDF
Tracing and profiling my sql (percona live europe 2019) draft_1
PDF
MySQL Parallel Replication (LOGICAL_CLOCK): all the 5.7 (and some of the 8.0)...
PDF
More on gdb for my sql db as (fosdem 2016)
PDF
Riding the Binlog: an in Deep Dissection of the Replication Stream
PDF
Gdb basics for my sql db as (openfest 2017) final
PDF
MySQL/MariaDB Parallel Replication: inventory, use-case and limitations
PPTX
What is row level isolation on cassandra
PDF
Compact and safely: static DSL on Kotlin
PDF
Vertically Scaled Design Patters
PDF
Construire une application JavaFX 8 avec gradle
PDF
Gdb basics for my sql db as (percona live europe 2019)
Cassandra Summit EU 2014 Lightning talk - Paging (no animation)
Qt Rest Server
JS Fest 2019. Thomas Watson. Post-Mortem Debugging in Node.js
Marble Testing RxJS streams
Instant add column for inno db in mariadb 10.3+ (fosdem 2018, second draft)
FOSDEM 2015: gdb tips and tricks for MySQL DBAs
MongoDB Performance Debugging
MySQL Parallel Replication: inventory, use-case and limitations
Circuit breaker
Tracing and profiling my sql (percona live europe 2019) draft_1
MySQL Parallel Replication (LOGICAL_CLOCK): all the 5.7 (and some of the 8.0)...
More on gdb for my sql db as (fosdem 2016)
Riding the Binlog: an in Deep Dissection of the Replication Stream
Gdb basics for my sql db as (openfest 2017) final
MySQL/MariaDB Parallel Replication: inventory, use-case and limitations
What is row level isolation on cassandra
Compact and safely: static DSL on Kotlin
Vertically Scaled Design Patters
Construire une application JavaFX 8 avec gradle
Gdb basics for my sql db as (percona live europe 2019)
Ad

Similar to [JEEConf-2017] RxJava as a key component in mature Big Data product (20)

PPTX
Rx java in action
PDF
RxJava applied [JavaDay Kyiv 2016]
PDF
Practical RxJava for Android
PDF
Using Apache Spark to Solve Sessionization Problem in Batch and Streaming
PDF
Reactive Programming Patterns with RxSwift
PPTX
Example R usage for oracle DBA UKOUG 2013
PPTX
Kick your database_to_the_curb_reston_08_27_19
PDF
Qt & Webkit
PDF
Building Scalable Stateless Applications with RxJava
POTX
Nextcon samza preso july - final
PDF
GDG DevFest 2015 - Reactive approach for slowpokes
PPTX
From zero to hero with the reactive extensions for JavaScript
PDF
Side effects-con-redux
PDF
Scaling with Scala: refactoring a back-end service into the mobile age
PPTX
From zero to hero with the reactive extensions for java script
PDF
Bulding a reactive game engine with Spring 5 & Couchbase
PDF
Compose Async with RxJS
PDF
NoSQL and JavaScript: a Love Story
PDF
Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"
PDF
Functional UIs with Java 8 and Vaadin JavaOne2014
Rx java in action
RxJava applied [JavaDay Kyiv 2016]
Practical RxJava for Android
Using Apache Spark to Solve Sessionization Problem in Batch and Streaming
Reactive Programming Patterns with RxSwift
Example R usage for oracle DBA UKOUG 2013
Kick your database_to_the_curb_reston_08_27_19
Qt & Webkit
Building Scalable Stateless Applications with RxJava
Nextcon samza preso july - final
GDG DevFest 2015 - Reactive approach for slowpokes
From zero to hero with the reactive extensions for JavaScript
Side effects-con-redux
Scaling with Scala: refactoring a back-end service into the mobile age
From zero to hero with the reactive extensions for java script
Bulding a reactive game engine with Spring 5 & Couchbase
Compose Async with RxJS
NoSQL and JavaScript: a Love Story
Василевский Илья (Fun-box): "автоматизация браузера при помощи PhantomJS"
Functional UIs with Java 8 and Vaadin JavaOne2014
Ad

Recently uploaded (20)

PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PPTX
ai tools demonstartion for schools and inter college
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PPTX
Essential Infomation Tech presentation.pptx
PDF
Softaken Excel to vCard Converter Software.pdf
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PPTX
Operating system designcfffgfgggggggvggggggggg
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
Introduction to Artificial Intelligence
PDF
Digital Strategies for Manufacturing Companies
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
medical staffing services at VALiNTRY
PDF
PTS Company Brochure 2025 (1).pdf.......
How Creative Agencies Leverage Project Management Software.pdf
Which alternative to Crystal Reports is best for small or large businesses.pdf
ai tools demonstartion for schools and inter college
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
Essential Infomation Tech presentation.pptx
Softaken Excel to vCard Converter Software.pdf
Odoo Companies in India – Driving Business Transformation.pdf
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Internet Downloader Manager (IDM) Crack 6.42 Build 41
Operating system designcfffgfgggggggvggggggggg
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Introduction to Artificial Intelligence
Digital Strategies for Manufacturing Companies
Design an Analysis of Algorithms I-SECS-1021-03
How to Choose the Right IT Partner for Your Business in Malaysia
Upgrade and Innovation Strategies for SAP ERP Customers
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
medical staffing services at VALiNTRY
PTS Company Brochure 2025 (1).pdf.......

[JEEConf-2017] RxJava as a key component in mature Big Data product

  • 1. RXJAVA AS A KEY COMPONENT IN A MATURE BIG DATA PRODUCT by Igor Lozynskyi
  • 2. HELLO!I am Igor Lozynskyi I have 8 years of Java experience, currently work at You can find me at @siromaha 2
  • 3. Agenda ■ RxJava basics ■ Zoomdata (goals & architecture) ■ “Good” & “bad” RxJava experience ■ Some thoughts about Rx future 3
  • 4. Disclaimer ■ This isn’t a sponsored talk! ■ This talk isn’t Zoomdata advertisement! ■ It’s just our experience. 4
  • 5. WHAT IS ZOOMDATA? The Fastest Visual Analytics for Big Data www.zoomdata.com 5
  • 6. ■ Data visualization software ■ Big Data ■ 30+ DB types ■ SQL, NoSQL, raw data (CSV) ■ Pretty charts ■ Platform! Zoomdata (ZD): 6
  • 8. 8
  • 9. ■ Static is boring ■ Dynamic is interesting Instant feedback ■ We do not like to wait 9
  • 11. 11
  • 12. 12
  • 15. 15 class QueryProcessor implements Observable.Transformer<QueryRequest, QueryResult> { Observable<QueryResult> call(Observable<QueryRequest> requests) { ... } }
  • 17. 17 class QueryProcessor implements Observable.Transformer<QueryRequest, QueryResult> { Observable<QueryResult> call(Observable<QueryRequest> requests) { return requests .takeWhile(req -> !isCancellation(req)) .map(this::validateRequest) .compose(r -> requestProcessor(r)) .onErrorReturn(this::handleProcessingErrors) } Observable<QueryResult> requestProcessor(Observable<QueryRequestParsed> requests) { return mainQuery(requests); } }
  • 19. 19 class QueryProcessor implements Observable.Transformer<QueryRequest, QueryResult> { Observable<QueryResult> call(Observable<QueryRequest> requests) { return requests .takeWhile(req -> !isCancellation(req)) .map(this::validateRequest) .compose(r -> requestProcessor(r)) .onErrorReturn(this::handleProcessingErrors) } Observable<QueryResult> requestProcessor(Observable<QueryRequestParsed> requests) { return merge( mainQuery(requests), intermediateResults(requests) // approximate result ); } }
  • 21. 21 class QueryProcessor implements Observable.Transformer<QueryRequest, QueryResult> { Observable<QueryResult> call(Observable<QueryRequest> requests) { return requests .takeWhile(req -> !isCancellation(req)) .map(this::validateRequest) .compose(r -> requestProcessor(r)) .onErrorReturn(this::handleProcessingErrors) } Observable<QueryResult> requestProcessor(Observable<QueryRequestParsed> request) { return merge( mainQuery(requests), intermediateResults(requests), mainQueryProgress(requests) // based on main query ); } }
  • 26. Effortless mapping of WebSocket to Observable Stream 26
  • 27. 27 class RxWebSocketSession<T extends WebSocketMessage<?>> { spring.WebSocketSession nativeSession; Subject<T, T> inputSubject; Subscriber<T> socketSubscriber; public RxWebSocketSession(spring.WebSocketSession nativeSession) { ... } public Observable<T> getInput() { return inputSubject; } public Subscriber<T> getOutput() { return socketSubscriber; } ... }
  • 28. 28 class NativeWebSocketHandler extends TextWebSocketHandler { IRxWebSocketHandler<TextMessage> sessionHandler; Map<WebSocketSession, RxWebSocketSession<TextMessage>> sessions; void afterConnectionEstablished(WebSocketSession session) { RxWebSocketSession<TextMessage> rxSession = new RxWebSocketSession<>(synchronizedSession(session)); sessions.put(session, rxSession); sessionHandler.afterConnectionEstablished(rxSession); } void handleTextMessage(WebSocketSession session, TextMessage message) { sessions.get(session).handleMessage(message); } void afterConnectionClosed(WebSocketSession session, CloseStatus status) { RxWebSocketSession<TextMessage> rxSession = sessions.remove(session); rxSession.close(); sessionHandler.afterConnectionClosed(rxSession, status); } }
  • 29. 29 class RxWebSocketHandler implements IRxWebSocketHandler<TextMessage> { rx.Observable.Transformer<String, String> messageTransformer; rx.Scheduler sendingIoScheduler; public void afterConnectionEstablished(RxWebSocketSession<TextMessage> session) { session .getInput() .map(TextMessage::getPayload) .compose(messageTransformer) .onBackpressureBuffer(OUTGOING_WS_BUFFER_CAPACITY, () -> reportMessageDropped(session.getId()), ON_OVERFLOW_DROP_OLDEST) .observeOn(sendingIoScheduler) .map(TextMessage::new) .subscribe(session.getOutput()); } }
  • 30. It is easy to stream intermediate results to a client 30
  • 31. It is easy to coordinate complex & long running workflows 31
  • 32. RxJava allows to compose dozens of reactive streams 32
  • 34. RxJava is very powerful Any stream transformation that you can imagine! Image credits: Denys Nevozhai
  • 36. Use TDD for Rx code! Lack of unit tests for Rx code Many integration tests instead 36Image credits: Matthew Henry
  • 37. Stream code should not be too complicated! Rule: “10 lines max” for method 37
  • 38. 38 protected Observable<Map<QueryStream, ComputedTasks>> play(Map<QueryStream, ComputedTasks> initial) { return concat(just(primaryTimeWindow), timeWindowStream) .map(tw -> playingReadRequests.columnKeySet().stream() .collect(toMap( calc -> calc, calc -> calc.calculateTimewindow(tw))) ) .buffer(2, 1) .filter(contextsMaps -> contextsMaps.size() == 2) .zipWith(checkPointsStream::iterator, Pair::of) .scan(initialTasksByTimewindow, (tasksMap, actions) -> { Map<TimewindowCalculator, List<ReadAction>> actionsByWindow = actions.getLeft(); Boolean doCheckpoint = actions.getRight(); Map<TimewindowCalculator, Map<QueryStream, ComputedTasks>> tasksByTimewindow = MapStream.of(actionsByWindow) .map(e -> { TimewindowCalculator timewindow = e.getKey(); List<ReadAction> timewindowActions = e.getValue(); Map<QueryStream, ComputedTasks> tasksOfTimewindow = tasksMap.get(timewindow); Map<QueryStream, ComputedTasks> tasksByStream = calculateStreamTasksWithActions(timewindow, tasksOfTimewindow, timewindowActions, doCheckpoint); return Pair.of(timewindow, tasksByStream); }).collect(toMap(Pair::getKey, Pair::getValue)); return tasksByTimewindow; }) .map(tasksByTimewindow -> { Collection<Map<QueryStream, ComputedTasks>> tasksByStreamMaps = tasksByTimewindow.values(); return mergeTasksMapsByStream(tasksByStreamMaps); }) .doOnNext(this::rememberLastTasksReference) .skip(1); }
  • 39. 39 protected Observable<Map<QueryStream, ComputedTasks>> play(Map<QueryStream, ComputedTasks> initial) { return concat(just(primaryTimeWindow), timeWindowStream) .map(tw -> playingReadRequests.columnKeySet().stream() .collect(toMap( calc -> calc, calc -> calc.calculateTimewindow(tw))) ) .buffer(2, 1) .filter(contextsMaps -> contextsMaps.size() == 2) .zipWith(checkPointsStream::iterator, Pair::of) .scan(initialTasksByTimewindow, (tasksMap, actions) -> { Map<TimewindowCalculator, List<ReadAction>> actionsByWindow = actions.getLeft(); Boolean doCheckpoint = actions.getRight(); Map<TimewindowCalculator, Map<QueryStream, ComputedTasks>> tasksByTimewindow = MapStream.of(actionsByWindow) .map(e -> { TimewindowCalculator timewindow = e.getKey(); List<ReadAction> timewindowActions = e.getValue(); Map<QueryStream, ComputedTasks> tasksOfTimewindow = tasksMap.get(timewindow); Map<QueryStream, ComputedTasks> tasksByStream = calculateStreamTasksWithActions(timewindow, tasksOfTimewindow, timewindowActions, doCheckpoint); return Pair.of(timewindow, tasksByStream); }).collect(toMap(Pair::getKey, Pair::getValue)); return tasksByTimewindow; }) .map(tasksByTimewindow -> { Collection<Map<QueryStream, ComputedTasks>> tasksByStreamMaps = tasksByTimewindow.values(); return mergeTasksMapsByStream(tasksByStreamMaps); }) .doOnNext(this::rememberLastTasksReference) .skip(1); }
  • 40. 40 protected Observable<Map<QueryStream, ComputedTasks>> play(Map<QueryStream, ComputedTasks> initial) { return concat(just(primaryTimeWindow), timeWindowStream) .map(this::calculateTimeWindows) .buffer(2, 1) .filter(this::contextMapsOfSizeTwo) .zipWith(checkPointsStream::iterator, Pair::of) .scan(initialTasksByTimewindow, this::updateJobResultsForNewTimeWindow) .map(tasksByTimewindow -> mergeTaskMapsByStream(tasksByTimewindow.values())) .doOnNext(this::rememberLastTasksReference) .skip(1); }
  • 41. 41 protected Observable<Map<QueryStream, ComputedTasks>> play(Map<QueryStream, ComputedTasks> initial) { return concat(just(primaryTimeWindow), timeWindowStream) .map(this::calculateTimeWindows) .buffer(2, 1) .filter(this::contextMapsOfSizeTwo) .map(this::transitionWindow) .zipWith(checkPointsStream::iterator, Pair::of) .scan(initialTasksByTimewindow, this::updateJobResultsForNewTimeWindow) .map(tasksByTimewindow -> mergeTaskMapsByStream(tasksByTimewindow.values())) .doOnNext(this::rememberLastTasksReference) .skip(1); }
  • 42. It is easy to forget about one very important thing 42 Remember next slide Image credits: Oscar Sutton
  • 47. Remember not to overflow your client! Your client will not be happy 47
  • 48. Remember not to overflow yourself! 48 Slow subscriber may cause data buffering Buffering may be costly
  • 49. 49
  • 50. Rx silently introduces a notion of time into your API 50
  • 51. 51
  • 52. Track your stream lifetime! GroupBy operator 52
  • 53. 53
  • 54. Track your stream lifetime! 54 Child streams live along with parent!
  • 55. 55
  • 56. Track your stream lifetime! Unpleasant when child streams hold resources 56
  • 57. Subscription takes some time Be careful not to miss messages 57
  • 58. 58
  • 59. Reactive transformations are time dependent 59 Zoomdata had an issue due to this
  • 60. 60
  • 62. Hard to code complicated context-dependent workflow ■ Loop vs flatMap ■ State should be incorporated into the stream! 62
  • 63. Try not to mutate objects inside Rx streams ■ Rx stream is a concurrent system ■ In concurrent systems mutation is evil ■ Zoomdata had such issues 63
  • 64. Think twice before providing pull API over Rx stream 64
  • 65. ■ Pull push hard & risky ■ Pull push mutability disaster Think twice before providing pull API over Rx stream ■ Push pull ok 65
  • 66. It is better to control execution threads on your own ■ Doing CPU in IO thread ■ Doing IO in CPU thread 66Image credits: Haiku Deck
  • 67. Care about concurrency ■ No ThreadLocal tricks! ■ flatMap/zip/merge may move execution into unexpected (for you) thread ■ Immutability is the only reasonable way to go 67
  • 68. and, last but not least... 68
  • 69. Complexity is still there! 69Image credits: sciencemag.org
  • 71. ■ WebSocket to Rx stream mapping: 3 files ■ ZD query processing is almost entirely RxJava code ■ Most algorithms live in 10 files (~1000 loc) □ playing, sharpening, progress, timeline, push/pull data strategies, main query, timeline, and much more ■ RxJava is complicated, but very powerful ■ Zoomdata benefits a lot from RxJava usage! Is RxJava good enough for ZD? 71
  • 72. ■ Java 9 & Spring 5 will spread reactive programming ■ We expect big shift towards Project Reactor ■ We will have more reactive and proactive software! □ Zoomdata is already proactive Some thoughts about Rx future 72
  • 73. THANKS! Contact me: @siromaha aigooor [at] gmail [dot] com Presentation template by SlidesCarnival Image credits: Denys Nevozhai