SlideShare a Scribd company logo
Introduction to ParSeq
To make asynchronous Java easier
Synchronous
● Result is ready when method returns
● What about methods void e.g.
void deletePerson(Long id)
● Unchecked exception (or it’s lack) is part of a
result
● Error handling through optional try-catch
mechanism
Person fetchPerson(Long id)
2
Asynchronous
● Result may be ready long after method
returns
● There must be a way to access result e.g.
using Callback or Promise
● Custom error handling
void fetchPerson(Long id, Callback callback)
void Promise<Person> fetchPerson(Long id)
3
Synchronous
fetch wait processBodyThread 1 fetch wait processBody
String googleBody = fetchBody("http://www.google.com"); //wait
processBody(googleBody);
String bingBody = fetchBody("http://www.bing.com"); //wait
processBody(bingBody);
time
waitThread 1 wait
In reality:
4
other tasks
Asynchronous
Task.par(
fetchBody("http://www.google.com").andThen(this::processBody),
fetchBody("http://www.bing.com").andThen(this::processBody)
);
fetch
wait
processBody
Thread 1 fetch
wait
processBodyThread 2
other tasks
time
5
Challenges
● Accidental complexity, unreadable code e.g.
doAsync1(function () {
doAsync2(function () {
doAsync3(function () {
doAsync4(function () {
...
})
})
})
6
Challenges
● Indeterminism
○ n! many orders in which all asynchronous operations
can finish
○ Difficult to test
○ No “current state”
● Debugging
○ State of the art is println() sprinkled around
○ No meaningful stack trace
7
Challenges
● Thread safety
○ Synchronized, locks, volatile, atomic, j.u.c.*
○ Immutability
○ 3rd party libraries
○ Thinking about Java Memory Model
○ Thread safety does not mean atomicity
8
Challenges
● Often requires all-async
//fetching Alice and Bob asynchronously
fetchPerson(aliceId, handleAliceCallback);
fetchPerson(bobId, handleBobCallback);
// hmmm... now what?
9
Core concepts: Promise<T>
● Represents state of an asynchronous
operation
● Possible states: completed (resolved),
uncompleted (unresolved)
● Container for a result of an asynchronous
operation
● Result of completed Promise<T> is either
value of type T or an exception 10
Core concepts: Task<T>
● Main abstraction and building block in
ParSeq, represents asynchronous operation
● Main ingredient of a Task<T> is a clojure
that (synchronously) returns a Promise<T>,
as if Task<T> had void Promise<T> run(...)
● Clojure starts asynchronous operation and
returns a Promise that represents the state
of that operation 11
Core concepts: Task<T>
Callable<Promise<String>> callable = () -> {
SettablePromise<String> promise = Promises.settable();
return promise;
};
● Closest construct in pure Java is a
Callable<Promise<T>>
Task<String> task = Task.async(name, () -> {
SettablePromise<String> promise = Promises.settable();
return promise;
}); 12
Core concepts: Task<T>
Callable<Promise<String>> callable = () -> {
SettablePromise<String> promise = Promises.settable();
return promise;
};
● Closest construct in pure Java is a
Callable<Promise<T>>
Task<String> task = Task.async(name, () -> {
SettablePromise<String> promise = Promises.settable();
return promise;
});
clojure
13
Core concepts: Task<T>
public <T> Promise<Response<T>> sendRequest(Request<T> request,
RequestContext requestContext) {
SettablePromise<Response<T>> promise = Promises.settable();
_restClient.sendRequest(request, requestContext,
new PromiseCallbackAdapter<T>(promise));
return promise;
}
Task<Response<T>> task =
Task.async(name, () -> sendRequest(request, requestContext));
● Real example from ParSeqRestClient
14
Core concepts: Task<T>
public <T> Promise<Response<T>> sendRequest(Request<T> request,
RequestContext requestContext) {
SettablePromise<Response<T>> promise = Promises.settable();
_restClient.sendRequest(request, requestContext,
new PromiseCallbackAdapter<T>(promise));
return promise;
}
Task<Response<T>> task =
Task.async(name, () -> sendRequest(request, requestContext));
● Real example from ParSeqRestClient
clojure
15
Core concepts: Task<T>
time
● Example execution of tasks
○ clojure synchronously returns a Promise<T>
○ Promise is completed at some point in the future
clojure
execution
Promise
completion
16
Properties of Tasks
● Tasks are lazy - creating a Task instance
does not automatically run it
● Task runs at most once
● Tasks are immutable - all Task.* methods
return new Tasks
● Programming with Tasks comes down to
creating new Tasks that depend on existing
ones (composition) 17
Creating Tasks from scratch
● Usually there is no need to create Task from
scratch using Task.async(...), instead use
existing library e.g.
_parseqRestClient.createTask(...)
● If there is a need to create a Task from
scratch use Task.async(...), don’t extend
BaseTask class
18
Parallel Composition
● Task.par(t1, t2, …)
Task<?> par3 = Task.par(fetchBing, fetchGoogle, fetchYahoo)
19
Parallel Composition
● Task.par(t1, t2, …)
Task<?> par3 = Task.par(fetchBing, fetchGoogle, fetchYahoo)
time
20
Parallel Composition
● Task.par(t1, t2, …)
Task<?> par3 = Task.par(fetchBing, fetchGoogle, fetchYahoo)
time
clojure
execution
Promise
completion
21
Sequential Composition
● Do something after Task (it’s Promise) is completed
Task<String> seq = fetchBing.andThen("seq", fetchGoogle);
22
Sequential Composition
● Do something after Task (it’s Promise) is completed
Task<String> seq = fetchBing.andThen("seq", fetchGoogle);
time
23
Sequential Composition
● Do something after Task (it’s Promise) is completed
Task<String> seq = fetchBing.andThen("seq", fetchGoogle);
time
clojure
execution
Promise
completion
24
ParSeq API: map
● Transforms result of a task
Task<String> name = Task.value("Jaroslaw");
Task<Integer> length = name.map(String::length);
25
ParSeq API: flatMap
● Runs Task that depends on result of previous Task
● fetchCompany() invoked after fetchPerson() completes
public Task<Company> fetchCompany(int id) {...}
public Task<Person> fetchPerson(int id) {...}
Task<Person> personTask = fetchPerson(id);
Task<Company> companyTask =
personTask.flatMap(person ->
fetchCompany(person.getCompanyId()));
vs
Task<Task<Company>> companyTask =
personTask.map(person ->
fetchCompany(person.getCompanyId()));
26
Exceptions handling
● All exceptions are automatically propagated
Task<Integer> fetchAndLength =
fetch404Url("http://www.google.com/idontexist")
.map("length", String::length);
● Clojures can throw checked and unchecked exceptions
fetchBody("http://www.bing.com")
.andThen(s -> { throw new IOException(); });
27
Exceptions handling
● Task API has methods to handle exceptions, equivalent
to try-catch for synchronous methods, e.g. recover()
Task<Integer> fetchAndLength =
fetch404Url("http://www.google.com/idontexist")
.recover("default", t -> "")
.map("length", s -> s.length());
28
Exceptions handling
● Exceptions can be handled explicitly, see methods
toTry(), transform()
● Try<T> interface has only two implementation:
Success<T> and Failure<T>
Task<Try<String>> tryFetch =
fetch404Url("http://www.google.com/idontexist")
.toTry();
29
withTimeout
● Fails Task if it is not completed within specified amount
of time
Task<String> fetchWithTimeout =
fetchUrl("http://www.google.com")
.withTimeout(15, TimeUnit.MILLISECONDS);
30
withTimeout
● Fails Task if it is not completed within specified amount
of time
Task<String> fetchWithTimeout =
fetchUrl("http://www.google.com")
.withTimeout(15, TimeUnit.MILLISECONDS);
time
31
ParSeq API
● ParSeq API is declarative
● Focus on “what” not “how”
● Task<T> interface does not have run() method
32
ParSeq programming model
● Express everything by composing and
transforming Tasks
● End up with one Task (Plan) that is passed
to ParSeq Engine for execution
● Rest.li implements this idea
@RestMethod.Get
public Task<Greeting> get(final Long id) {
// rest.li resource implementation
} 33
ParSeq execution model
● ParSeq Engine provides the following
guarantees:
○ Rules expressed in ParSeq API are followed e.g.
first.andThen(second) - “second” will run after “first”
is completed
○ All clojures are executed sequentially, such that
clojure of a previous Task in a sequence “happens
before” clojure of a next Task (“happens before” in
the Java Memory Model sense)
34
Clojures are executed sequentially
● All operations passed to Task.* API are
executed within a Task’s clojure
Task<Integer> length =
fetchBody("http://www.google.com")
.map("length", String::length);
time
35
Clojures are executed sequentially
● All operations passed to Task.* API are
executed within a clojure
Task<Integer> length =
fetchBody("http://www.google.com")
.map("length", String::length);
time
clojure
36
Clojures are executed sequentially
● All operations passed to Task.* API are
automatically thread safe and atomic
● No need to be concerned about:
○ Synchronized, locks, volatile, atomic, j.u.c.*
○ Immutability
○ 3rd party libraries
○ Thinking about Java Memory Model
○ Thread safety does not mean atomicity
37
Clojures are executed sequentially
● Example: find top N Candidates (synchronous)
PriorityQueue<Candidate> topCandidates = new PriorityQueue<>();
void considerCandidate(Candidate candidate) {
topCandidates.add(candidate);
if (topCandidates.size() > N) {
topCandidates.remove();
}
}
38
Clojures are executed sequentially
● Example: find top N Candidates (asynchronous)
Task<Candidate> fetchCandidate(Long Id) {...}
Task<?> considerCandidate(Long id) {
return fetchCandidate(id)
.andThen(this::considerCandidate);
}
● Using thread safe PriorityQueue is not enough
● No need to test concurrency aspect 39
Clojures are executed sequentially
Task<?> par3 = Task.par(fetchBing, fetchGoogle, fetchYahoo)
time
● Task.par(t1, t2, …) example
40
Clojures are executed sequentially
41
Clojures are executed sequentially
Task.par(
Task.callable("1000th prime", () -> nthPrime(1000)),
Task.callable("1000th prime", () -> nthPrime(1000)),
Task.callable("1000th prime", () -> nthPrime(1000))
);
● Task.par(t1, t2, …) example
time
42
Clojures are executed sequentially
● Does it mean that ParSeq is single threaded?
● No
Task.par(
fetchBody("http://www.google.com").andThen(this::processBody),
fetchBody("http://www.bing.com").andThen(this::processBody)
);
43
Clojures are executed sequentially
● Does it mean that ParSeq is single threaded?
● No
time
other tasks
other tasks
wait
processBodyThread 1
wait
Thread 2
fetch
fetch processBody
44
other tasks
Clojures are executed sequentially
● Does it mean that ParSeq is single threaded?
● No
other tasks
wait
processBodyThread 1
wait
Thread 2
time
fetch
happens
before
fetch processBody
happens
before
45
Clojures are executed sequentially
● In order to perform well ParSeq requires
developer’s cooperation. In clojures avoid:
○ blocking e.g. JDBC calls, Thread.sleep(), ...
○ CPU intensive operations
46
Blocking
● Blocking API e.g. Voldemort, JDBC
● CPU intensive computation
● Offload operation to external Executor
Task.blocking(String name,
Callable<? extends T> callable,
Executor executor)
● Supports multiple executors
● Executors management is outside ParSeq 47
Automatic cancellation
● Task abstraction borrows ideas from functional
programming, think: Task ~ function call
○ Task is lazy, only runs when needed
○ Task is immutable
○ It’s purpose is to calculate a result
○ Task runs at most once
○ Once result is known, everything that is still not
completed (for some reason) can be cancelled because
it can’t affect result
48
Automatic cancellation
Task<String> google = fetchBody("http://www.google.com");
Task<String> yahoo = fetchBody("http://www.yahoo.com");
Task<String> bing = fetchBody("http://www.~#fdcm x 0eirw.com");
Task<Integer> sumLengths =
Task.par(google.map("length", String::length),
yahoo.map("length", String::length),
bing.map("length", String::length))
.map("sum", (g, y, b) -> g + y + b);
49
Automatic cancellation
50
Tracing
● Trace is information about what happened
after calling Engine.run()
● Tracing is always enabled for all tasks
● Allows reasoning about what happened
● Includes timing information, exceptions, etc.
● Obtain trace JSON at any time by calling
Task.getTrace().toString()
● Paste JSON at go/tracevis to visualize 51
Tracing best practices
● Add short, meaningful description to every
task
HttpClient.get(url).task()
.map("getBody", response -> response.getResponseBody())
.map("length", s -> s.length());
52
Tasks Fusion
● Sequence of synchronous transformations
can be optimized and executed as a chain of
method calls
Task<Integer> length =
HttpClient.get("http://www.google.com").task()
.map("getBody", Response::getResponseBody)
.map("length", s -> s.length());
53
Batching
● Allows automatic batching of individual
asynchronous operations without affecting
code readability
54
Unit testing
● Use BaseEngineTest as a base class for a
unit test
○ Initialization and shutdown of Engine
○ Lot of helper methods
● Log trace for the tested Task
55
Best Practices
● Focus on readable, clean code
○ Split code into small, unit-testable methods that may
return Task<T>
○ Prefer method handles over lambdas
○ Limit length of lambda code blocks to few lines
○ Don’t mix functional APIs inside lambda code block
e.g. Java Stream map() and Java Optional map()
with ParSeq map()
56
Upcoming feature
● Automatic batching of GET / BATCH_GET
requests in ParSeqRestClient
● Configurable timeouts in ParSeqRestClient
● Automatic logging traces of failed plans
● Sending sample of traces to kafka for
analysis e.g. finding bottlenecks,
regressions, thread blocking, …
● ParSeq “Collections” 57
Reference
● https://github.com/linkedin/parseq/wiki
● http://linkedin.github.io/parseq/javadoc/latest
58

More Related Content

PDF
Java 8 Workshop
PDF
Java 8 Lambda Built-in Functional Interfaces
PDF
Completable future
PPTX
Spring data jpa
PPTX
Spring boot
PDF
Spring Framework - Core
PDF
Java8 features
PPT
Spring Core
Java 8 Workshop
Java 8 Lambda Built-in Functional Interfaces
Completable future
Spring data jpa
Spring boot
Spring Framework - Core
Java8 features
Spring Core

What's hot (20)

PPTX
JavaScript Promises
PPTX
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
PDF
Spring Framework - AOP
PPTX
Java 8 Lambda and Streams
PPTX
Springboot Microservices
PDF
Clean Lambdas & Streams in Java8
PPT
Core java concepts
PDF
Spring Boot
PDF
Spring boot introduction
PPTX
Java 8 presentation
PPTX
JPA For Beginner's
PPSX
Elements of Java Language
PDF
React Router: React Meetup XXL
PDF
Java 8 Lambda Expressions
PDF
Spring Boot
PDF
L'API Collector dans tous ses états
PPTX
Saturn 2018 rest.li
ODP
Introduction to Java 8
PPTX
Discover Quarkus and GraalVM
PDF
Interface fonctionnelle, Lambda expression, méthode par défaut, référence de...
JavaScript Promises
Callbacks, Promises, and Coroutines (oh my!): Asynchronous Programming Patter...
Spring Framework - AOP
Java 8 Lambda and Streams
Springboot Microservices
Clean Lambdas & Streams in Java8
Core java concepts
Spring Boot
Spring boot introduction
Java 8 presentation
JPA For Beginner's
Elements of Java Language
React Router: React Meetup XXL
Java 8 Lambda Expressions
Spring Boot
L'API Collector dans tous ses états
Saturn 2018 rest.li
Introduction to Java 8
Discover Quarkus and GraalVM
Interface fonctionnelle, Lambda expression, méthode par défaut, référence de...
Ad

Similar to Introduction to ParSeq: to make asynchronous java easier (20)

PDF
The evolution of java script asynchronous calls
PDF
Advanced patterns in asynchronous programming
PPTX
The Promised Land (in Angular)
PDF
Google App Engine Developer - Day3
PDF
Promise: async programming hero
PDF
Leverage CompletableFutures to handle async queries. DevNexus 2022
PDF
Scheduling tasks the human way - Brad Wood - ITB2021
PDF
Leveraging Completable Futures to handle your query results Asynchrhonously
PDF
Promises look into the async future
PPTX
Binary Studio Academy: Concurrency in C# 5.0
PPTX
Jdk 7 4-forkjoin
PPTX
동기화 시대를 뛰어넘는 비동기 프로그래밍
ODP
CompletableFuture
PDF
Threads, Queues, and More: Async Programming in iOS
ODP
Scala Future & Promises
PDF
Async js - Nemetschek Presentaion @ HackBulgaria
PDF
Silicon Valley JUG: JVM Mechanics
PDF
Flink Forward Berlin 2017: Maciek Próchniak - TouK Nussknacker - creating Fli...
PDF
function* - ES6, generators, and all that (JSRomandie meetup, February 2014)
PDF
Node.js flow control
The evolution of java script asynchronous calls
Advanced patterns in asynchronous programming
The Promised Land (in Angular)
Google App Engine Developer - Day3
Promise: async programming hero
Leverage CompletableFutures to handle async queries. DevNexus 2022
Scheduling tasks the human way - Brad Wood - ITB2021
Leveraging Completable Futures to handle your query results Asynchrhonously
Promises look into the async future
Binary Studio Academy: Concurrency in C# 5.0
Jdk 7 4-forkjoin
동기화 시대를 뛰어넘는 비동기 프로그래밍
CompletableFuture
Threads, Queues, and More: Async Programming in iOS
Scala Future & Promises
Async js - Nemetschek Presentaion @ HackBulgaria
Silicon Valley JUG: JVM Mechanics
Flink Forward Berlin 2017: Maciek Próchniak - TouK Nussknacker - creating Fli...
function* - ES6, generators, and all that (JSRomandie meetup, February 2014)
Node.js flow control
Ad

Recently uploaded (20)

PDF
BMEC211 - INTRODUCTION TO MECHATRONICS-1.pdf
PPTX
MET 305 2019 SCHEME MODULE 2 COMPLETE.pptx
PDF
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
PPT
Mechanical Engineering MATERIALS Selection
PPT
Project quality management in manufacturing
PPTX
OOP with Java - Java Introduction (Basics)
PPTX
web development for engineering and engineering
PDF
keyrequirementskkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
PDF
PPT on Performance Review to get promotions
PDF
Evaluating the Democratization of the Turkish Armed Forces from a Normative P...
PPT
CRASH COURSE IN ALTERNATIVE PLUMBING CLASS
PPTX
CH1 Production IntroductoryConcepts.pptx
PPTX
KTU 2019 -S7-MCN 401 MODULE 2-VINAY.pptx
PPTX
Lecture Notes Electrical Wiring System Components
PPTX
MCN 401 KTU-2019-PPE KITS-MODULE 2.pptx
PPTX
UNIT 4 Total Quality Management .pptx
PDF
Embodied AI: Ushering in the Next Era of Intelligent Systems
PPTX
Foundation to blockchain - A guide to Blockchain Tech
PPTX
Engineering Ethics, Safety and Environment [Autosaved] (1).pptx
PPTX
Geodesy 1.pptx...............................................
BMEC211 - INTRODUCTION TO MECHATRONICS-1.pdf
MET 305 2019 SCHEME MODULE 2 COMPLETE.pptx
Enhancing Cyber Defense Against Zero-Day Attacks using Ensemble Neural Networks
Mechanical Engineering MATERIALS Selection
Project quality management in manufacturing
OOP with Java - Java Introduction (Basics)
web development for engineering and engineering
keyrequirementskkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
PPT on Performance Review to get promotions
Evaluating the Democratization of the Turkish Armed Forces from a Normative P...
CRASH COURSE IN ALTERNATIVE PLUMBING CLASS
CH1 Production IntroductoryConcepts.pptx
KTU 2019 -S7-MCN 401 MODULE 2-VINAY.pptx
Lecture Notes Electrical Wiring System Components
MCN 401 KTU-2019-PPE KITS-MODULE 2.pptx
UNIT 4 Total Quality Management .pptx
Embodied AI: Ushering in the Next Era of Intelligent Systems
Foundation to blockchain - A guide to Blockchain Tech
Engineering Ethics, Safety and Environment [Autosaved] (1).pptx
Geodesy 1.pptx...............................................

Introduction to ParSeq: to make asynchronous java easier

  • 1. Introduction to ParSeq To make asynchronous Java easier
  • 2. Synchronous ● Result is ready when method returns ● What about methods void e.g. void deletePerson(Long id) ● Unchecked exception (or it’s lack) is part of a result ● Error handling through optional try-catch mechanism Person fetchPerson(Long id) 2
  • 3. Asynchronous ● Result may be ready long after method returns ● There must be a way to access result e.g. using Callback or Promise ● Custom error handling void fetchPerson(Long id, Callback callback) void Promise<Person> fetchPerson(Long id) 3
  • 4. Synchronous fetch wait processBodyThread 1 fetch wait processBody String googleBody = fetchBody("http://www.google.com"); //wait processBody(googleBody); String bingBody = fetchBody("http://www.bing.com"); //wait processBody(bingBody); time waitThread 1 wait In reality: 4
  • 6. Challenges ● Accidental complexity, unreadable code e.g. doAsync1(function () { doAsync2(function () { doAsync3(function () { doAsync4(function () { ... }) }) }) 6
  • 7. Challenges ● Indeterminism ○ n! many orders in which all asynchronous operations can finish ○ Difficult to test ○ No “current state” ● Debugging ○ State of the art is println() sprinkled around ○ No meaningful stack trace 7
  • 8. Challenges ● Thread safety ○ Synchronized, locks, volatile, atomic, j.u.c.* ○ Immutability ○ 3rd party libraries ○ Thinking about Java Memory Model ○ Thread safety does not mean atomicity 8
  • 9. Challenges ● Often requires all-async //fetching Alice and Bob asynchronously fetchPerson(aliceId, handleAliceCallback); fetchPerson(bobId, handleBobCallback); // hmmm... now what? 9
  • 10. Core concepts: Promise<T> ● Represents state of an asynchronous operation ● Possible states: completed (resolved), uncompleted (unresolved) ● Container for a result of an asynchronous operation ● Result of completed Promise<T> is either value of type T or an exception 10
  • 11. Core concepts: Task<T> ● Main abstraction and building block in ParSeq, represents asynchronous operation ● Main ingredient of a Task<T> is a clojure that (synchronously) returns a Promise<T>, as if Task<T> had void Promise<T> run(...) ● Clojure starts asynchronous operation and returns a Promise that represents the state of that operation 11
  • 12. Core concepts: Task<T> Callable<Promise<String>> callable = () -> { SettablePromise<String> promise = Promises.settable(); return promise; }; ● Closest construct in pure Java is a Callable<Promise<T>> Task<String> task = Task.async(name, () -> { SettablePromise<String> promise = Promises.settable(); return promise; }); 12
  • 13. Core concepts: Task<T> Callable<Promise<String>> callable = () -> { SettablePromise<String> promise = Promises.settable(); return promise; }; ● Closest construct in pure Java is a Callable<Promise<T>> Task<String> task = Task.async(name, () -> { SettablePromise<String> promise = Promises.settable(); return promise; }); clojure 13
  • 14. Core concepts: Task<T> public <T> Promise<Response<T>> sendRequest(Request<T> request, RequestContext requestContext) { SettablePromise<Response<T>> promise = Promises.settable(); _restClient.sendRequest(request, requestContext, new PromiseCallbackAdapter<T>(promise)); return promise; } Task<Response<T>> task = Task.async(name, () -> sendRequest(request, requestContext)); ● Real example from ParSeqRestClient 14
  • 15. Core concepts: Task<T> public <T> Promise<Response<T>> sendRequest(Request<T> request, RequestContext requestContext) { SettablePromise<Response<T>> promise = Promises.settable(); _restClient.sendRequest(request, requestContext, new PromiseCallbackAdapter<T>(promise)); return promise; } Task<Response<T>> task = Task.async(name, () -> sendRequest(request, requestContext)); ● Real example from ParSeqRestClient clojure 15
  • 16. Core concepts: Task<T> time ● Example execution of tasks ○ clojure synchronously returns a Promise<T> ○ Promise is completed at some point in the future clojure execution Promise completion 16
  • 17. Properties of Tasks ● Tasks are lazy - creating a Task instance does not automatically run it ● Task runs at most once ● Tasks are immutable - all Task.* methods return new Tasks ● Programming with Tasks comes down to creating new Tasks that depend on existing ones (composition) 17
  • 18. Creating Tasks from scratch ● Usually there is no need to create Task from scratch using Task.async(...), instead use existing library e.g. _parseqRestClient.createTask(...) ● If there is a need to create a Task from scratch use Task.async(...), don’t extend BaseTask class 18
  • 19. Parallel Composition ● Task.par(t1, t2, …) Task<?> par3 = Task.par(fetchBing, fetchGoogle, fetchYahoo) 19
  • 20. Parallel Composition ● Task.par(t1, t2, …) Task<?> par3 = Task.par(fetchBing, fetchGoogle, fetchYahoo) time 20
  • 21. Parallel Composition ● Task.par(t1, t2, …) Task<?> par3 = Task.par(fetchBing, fetchGoogle, fetchYahoo) time clojure execution Promise completion 21
  • 22. Sequential Composition ● Do something after Task (it’s Promise) is completed Task<String> seq = fetchBing.andThen("seq", fetchGoogle); 22
  • 23. Sequential Composition ● Do something after Task (it’s Promise) is completed Task<String> seq = fetchBing.andThen("seq", fetchGoogle); time 23
  • 24. Sequential Composition ● Do something after Task (it’s Promise) is completed Task<String> seq = fetchBing.andThen("seq", fetchGoogle); time clojure execution Promise completion 24
  • 25. ParSeq API: map ● Transforms result of a task Task<String> name = Task.value("Jaroslaw"); Task<Integer> length = name.map(String::length); 25
  • 26. ParSeq API: flatMap ● Runs Task that depends on result of previous Task ● fetchCompany() invoked after fetchPerson() completes public Task<Company> fetchCompany(int id) {...} public Task<Person> fetchPerson(int id) {...} Task<Person> personTask = fetchPerson(id); Task<Company> companyTask = personTask.flatMap(person -> fetchCompany(person.getCompanyId())); vs Task<Task<Company>> companyTask = personTask.map(person -> fetchCompany(person.getCompanyId())); 26
  • 27. Exceptions handling ● All exceptions are automatically propagated Task<Integer> fetchAndLength = fetch404Url("http://www.google.com/idontexist") .map("length", String::length); ● Clojures can throw checked and unchecked exceptions fetchBody("http://www.bing.com") .andThen(s -> { throw new IOException(); }); 27
  • 28. Exceptions handling ● Task API has methods to handle exceptions, equivalent to try-catch for synchronous methods, e.g. recover() Task<Integer> fetchAndLength = fetch404Url("http://www.google.com/idontexist") .recover("default", t -> "") .map("length", s -> s.length()); 28
  • 29. Exceptions handling ● Exceptions can be handled explicitly, see methods toTry(), transform() ● Try<T> interface has only two implementation: Success<T> and Failure<T> Task<Try<String>> tryFetch = fetch404Url("http://www.google.com/idontexist") .toTry(); 29
  • 30. withTimeout ● Fails Task if it is not completed within specified amount of time Task<String> fetchWithTimeout = fetchUrl("http://www.google.com") .withTimeout(15, TimeUnit.MILLISECONDS); 30
  • 31. withTimeout ● Fails Task if it is not completed within specified amount of time Task<String> fetchWithTimeout = fetchUrl("http://www.google.com") .withTimeout(15, TimeUnit.MILLISECONDS); time 31
  • 32. ParSeq API ● ParSeq API is declarative ● Focus on “what” not “how” ● Task<T> interface does not have run() method 32
  • 33. ParSeq programming model ● Express everything by composing and transforming Tasks ● End up with one Task (Plan) that is passed to ParSeq Engine for execution ● Rest.li implements this idea @RestMethod.Get public Task<Greeting> get(final Long id) { // rest.li resource implementation } 33
  • 34. ParSeq execution model ● ParSeq Engine provides the following guarantees: ○ Rules expressed in ParSeq API are followed e.g. first.andThen(second) - “second” will run after “first” is completed ○ All clojures are executed sequentially, such that clojure of a previous Task in a sequence “happens before” clojure of a next Task (“happens before” in the Java Memory Model sense) 34
  • 35. Clojures are executed sequentially ● All operations passed to Task.* API are executed within a Task’s clojure Task<Integer> length = fetchBody("http://www.google.com") .map("length", String::length); time 35
  • 36. Clojures are executed sequentially ● All operations passed to Task.* API are executed within a clojure Task<Integer> length = fetchBody("http://www.google.com") .map("length", String::length); time clojure 36
  • 37. Clojures are executed sequentially ● All operations passed to Task.* API are automatically thread safe and atomic ● No need to be concerned about: ○ Synchronized, locks, volatile, atomic, j.u.c.* ○ Immutability ○ 3rd party libraries ○ Thinking about Java Memory Model ○ Thread safety does not mean atomicity 37
  • 38. Clojures are executed sequentially ● Example: find top N Candidates (synchronous) PriorityQueue<Candidate> topCandidates = new PriorityQueue<>(); void considerCandidate(Candidate candidate) { topCandidates.add(candidate); if (topCandidates.size() > N) { topCandidates.remove(); } } 38
  • 39. Clojures are executed sequentially ● Example: find top N Candidates (asynchronous) Task<Candidate> fetchCandidate(Long Id) {...} Task<?> considerCandidate(Long id) { return fetchCandidate(id) .andThen(this::considerCandidate); } ● Using thread safe PriorityQueue is not enough ● No need to test concurrency aspect 39
  • 40. Clojures are executed sequentially Task<?> par3 = Task.par(fetchBing, fetchGoogle, fetchYahoo) time ● Task.par(t1, t2, …) example 40
  • 41. Clojures are executed sequentially 41
  • 42. Clojures are executed sequentially Task.par( Task.callable("1000th prime", () -> nthPrime(1000)), Task.callable("1000th prime", () -> nthPrime(1000)), Task.callable("1000th prime", () -> nthPrime(1000)) ); ● Task.par(t1, t2, …) example time 42
  • 43. Clojures are executed sequentially ● Does it mean that ParSeq is single threaded? ● No Task.par( fetchBody("http://www.google.com").andThen(this::processBody), fetchBody("http://www.bing.com").andThen(this::processBody) ); 43
  • 44. Clojures are executed sequentially ● Does it mean that ParSeq is single threaded? ● No time other tasks other tasks wait processBodyThread 1 wait Thread 2 fetch fetch processBody 44
  • 45. other tasks Clojures are executed sequentially ● Does it mean that ParSeq is single threaded? ● No other tasks wait processBodyThread 1 wait Thread 2 time fetch happens before fetch processBody happens before 45
  • 46. Clojures are executed sequentially ● In order to perform well ParSeq requires developer’s cooperation. In clojures avoid: ○ blocking e.g. JDBC calls, Thread.sleep(), ... ○ CPU intensive operations 46
  • 47. Blocking ● Blocking API e.g. Voldemort, JDBC ● CPU intensive computation ● Offload operation to external Executor Task.blocking(String name, Callable<? extends T> callable, Executor executor) ● Supports multiple executors ● Executors management is outside ParSeq 47
  • 48. Automatic cancellation ● Task abstraction borrows ideas from functional programming, think: Task ~ function call ○ Task is lazy, only runs when needed ○ Task is immutable ○ It’s purpose is to calculate a result ○ Task runs at most once ○ Once result is known, everything that is still not completed (for some reason) can be cancelled because it can’t affect result 48
  • 49. Automatic cancellation Task<String> google = fetchBody("http://www.google.com"); Task<String> yahoo = fetchBody("http://www.yahoo.com"); Task<String> bing = fetchBody("http://www.~#fdcm x 0eirw.com"); Task<Integer> sumLengths = Task.par(google.map("length", String::length), yahoo.map("length", String::length), bing.map("length", String::length)) .map("sum", (g, y, b) -> g + y + b); 49
  • 51. Tracing ● Trace is information about what happened after calling Engine.run() ● Tracing is always enabled for all tasks ● Allows reasoning about what happened ● Includes timing information, exceptions, etc. ● Obtain trace JSON at any time by calling Task.getTrace().toString() ● Paste JSON at go/tracevis to visualize 51
  • 52. Tracing best practices ● Add short, meaningful description to every task HttpClient.get(url).task() .map("getBody", response -> response.getResponseBody()) .map("length", s -> s.length()); 52
  • 53. Tasks Fusion ● Sequence of synchronous transformations can be optimized and executed as a chain of method calls Task<Integer> length = HttpClient.get("http://www.google.com").task() .map("getBody", Response::getResponseBody) .map("length", s -> s.length()); 53
  • 54. Batching ● Allows automatic batching of individual asynchronous operations without affecting code readability 54
  • 55. Unit testing ● Use BaseEngineTest as a base class for a unit test ○ Initialization and shutdown of Engine ○ Lot of helper methods ● Log trace for the tested Task 55
  • 56. Best Practices ● Focus on readable, clean code ○ Split code into small, unit-testable methods that may return Task<T> ○ Prefer method handles over lambdas ○ Limit length of lambda code blocks to few lines ○ Don’t mix functional APIs inside lambda code block e.g. Java Stream map() and Java Optional map() with ParSeq map() 56
  • 57. Upcoming feature ● Automatic batching of GET / BATCH_GET requests in ParSeqRestClient ● Configurable timeouts in ParSeqRestClient ● Automatic logging traces of failed plans ● Sending sample of traces to kafka for analysis e.g. finding bottlenecks, regressions, thread blocking, … ● ParSeq “Collections” 57

Editor's Notes

  • #6: waiting is eliminated, threads are busy note 1: two threads are involved in execution note 2: req1, req2, process response 1, process response 2 execute sequentially
  • #17: waiting is eliminated, threads are busy note 1: two threads are involved in execution note 2: req1, req2, process response 1, process response 2 execute sequentially