1[[future_mode]]
2== Future Mode
3
4The client offers a mode called "future" or "async" mode.  This allows batch processing of requests (sent in parallel
5to the cluster), which can have a dramatic impact on performance and throughput.
6
7PHP is fundamentally single-threaded, however libcurl provides functionality called the "multi interface".  This allows
8languages like PHP to gain concurrency by providing a batch of requests to process.  The batch is executed in a parallel
9by the underlying multithreaded libcurl library, and the batch of responses is then returned to PHP.
10
11In a single-threaded environment, the time to execute `n` requests is the sum of those `n` request's latencies.  With
12the multi interface, the time to execute `n` requests is the latency of the slowest request (assuming enough handles
13are available to execute all requests in parallel).
14
15Furthermore, the multi-interface allows requests to different hosts simultaneously, which means the Elasticsearch-PHP
16client can more effectively utilize your full cluster.
17
18=== Using Future Mode
19
20Utilizing this feature is relatively straightforward, but it does introduce more responsibility into your code.  To enable
21future mode, set the `future` flag in the client options to `'lazy'`:
22
23[source,php]
24----
25$client = ClientBuilder::create()->build();
26
27$params = [
28    'index'  => 'test',
29    'id'     => 1,
30    'client' => [
31        'future' => 'lazy'
32    ]
33];
34
35$future = $client->get($params);
36----
37
38This will return a _future_, rather than the actual response.  A future represents a _future computation_ and acts like
39a placeholder.  You can pass a future around your code like a regular object.  When you need the result values, you
40can _resolve_ the future.  If the future has already resolved (due to some other activity), the values will be immediately
41available.  If the future has not resolved yet, the resolution will block until those values have become available (e.g.
42after the API call completes).
43
44In practice, this means you can queue up a batch of requests by using `future: lazy` and they will pend until you resolve
45the futures, at which time all requests will be sent in parallel to the cluster and return asynchronously to curl.
46
47This sounds tricky, but it is actually very simple thanks to RingPHP's `FutureArray` interface, which makes the future
48act like a simple associative array.  For example:
49
50[source,php]
51----
52$client = ClientBuilder::create()->build();
53
54$params = [
55    'index'  => 'test',
56    'id'     => 1,
57    'client' => [
58        'future' => 'lazy'
59    ]
60];
61
62$future = $client->get($params);
63
64$doc = $future['_source'];    // This call will block and force the future to resolve
65----
66
67Interacting with the future as an associative array, just like a normal response, will cause the future to resolve
68that particular value (which in turn resolves all pending requests and values).  This allows patterns such as:
69
70[source,php]
71----
72$client = ClientBuilder::create()->build();
73$futures = [];
74
75for ($i = 0; $i < 1000; $i++) {
76    $params = [
77        'index'  => 'test',
78        'id'     => $i,
79        'client' => [
80            'future' => 'lazy'
81        ]
82    ];
83
84    $futures[] = $client->get($params);     //queue up the request
85}
86
87
88foreach ($futures as $future) {
89    // access future's values, causing resolution if necessary
90    echo $future['_source'];
91}
92----
93
94The queued requests will execute in parallel and populate their futures after execution.  Batch size defaults to
95100 requests-per-batch.
96
97If you wish to force future resolution, but don't actually need the values immediately, you can call `wait()` on the future
98to force resolution too:
99
100[source,php]
101----
102$client = ClientBuilder::create()->build();
103$futures = [];
104
105for ($i = 0; $i < 1000; $i++) {
106    $params = [
107        'index'  => 'test',
108        'id'     => $i,
109        'client' => [
110            'future' => 'lazy'
111        ]
112    ];
113
114    $futures[] = $client->get($params);     //queue up the request
115}
116
117//wait() forces future resolution and will execute the underlying curl batch
118$futures[999]->wait();
119----
120
121=== Changing batch size
122
123The default batch size is 100, meaning 100 requests will queue up before the client forces futures to begin resolving
124(e.g. initiate a `curl_multi` call).  The batch size can be changed depending on your preferences.  The batch size
125is controllable via the `max_handles` setting when configuring the handler:
126
127[source,php]
128----
129$handlerParams = [
130    'max_handles' => 500
131];
132
133$defaultHandler = ClientBuilder::defaultHandler($handlerParams);
134
135$client = ClientBuilder::create()
136            ->setHandler($defaultHandler)
137            ->build();
138----
139
140This will change the behavior to wait on 500 queued requests before sending the batch.  Note, however, that forcing a
141future to resolve will cause the underlying curl batch to execute, regardless of if the batch is "full" or not.  In this
142example, only 499 requests are added to the queue...but the final future resolution will force the batch to flush
143anyway:
144
145[source,php]
146----
147$handlerParams = [
148    'max_handles' => 500
149];
150
151$defaultHandler = ClientBuilder::defaultHandler($handlerParams);
152
153$client = ClientBuilder::create()
154            ->setHandler($defaultHandler)
155            ->build();
156
157$futures = [];
158
159for ($i = 0; $i < 499; $i++) {
160    $params = [
161        'index'  => 'test',
162        'id'     => $i,
163        'client' => [
164            'future' => 'lazy'
165        ]
166    ];
167
168    $futures[] = $client->get($params);     //queue up the request
169}
170
171// resolve the future, and therefore the underlying batch
172$body = $future[499]['body'];
173----
174
175=== Heterogeneous batches are OK
176
177It is possible to queue up heterogeneous batches of requests.  For example, you can queue up several GETs, indexing requests
178and a search:
179
180[source,php]
181----
182$client = ClientBuilder::create()->build();
183$futures = [];
184
185$params = [
186    'index'  => 'test',
187    'id'     => 1,
188    'client' => [
189        'future' => 'lazy'
190    ]
191];
192
193$futures['getRequest'] = $client->get($params);     // First request
194
195$params = [
196    'index' => 'test',
197    'id'    => 2,
198    'body'  => [
199        'field' => 'value'
200    ],
201    'client' => [
202        'future' => 'lazy'
203    ]
204];
205
206$futures['indexRequest'] = $client->index($params);       // Second request
207
208$params = [
209    'index' => 'test',
210    'body'  => [
211        'query' => [
212            'match' => [
213                'field' => 'value'
214            ]
215        ]
216    ],
217    'client' => [
218        'future' => 'lazy'
219    ]
220];
221
222$futures['searchRequest'] = $client->search($params);      // Third request
223
224// Resolve futures...blocks until network call completes
225$searchResults = $futures['searchRequest']['hits'];
226
227// Should return immediately, since the previous future resolved the entire batch
228$doc = $futures['getRequest']['_source'];
229----
230
231=== Caveats to Future mode
232
233There are a few caveats to using future mode.  The biggest is also the most obvious: you need to deal with resolving the
234future yourself.  This is usually trivial, but can sometimes introduce unexpected complications.
235
236For example, if you resolve manually using `wait()`, you may need to call `wait()` several times if there were retries.
237This is because each retry will introduce another layer of wrapped futures, and each needs to be resolved to get the
238final result.
239
240This is not needed if you access values via the ArrayInterface however (e.g. `$response['hits']['hits']`), since
241FutureArrayInterface will automatically and fully resolve the future to provide values.
242
243Another caveat is that certain APIs will lose their "helper" functionality.  For example, "exists" APIs (e.g.
244`$client->exists()`, `$client->indices()->exists`, `$client->indices->templateExists()`, etc) typically return a true
245or false under normal operation.
246
247When operated in future mode, unwrapping of the future is left to your application,
248which means the client can no longer inspect the response and return a simple true/false.  Instead, you'll see the raw
249response from Elasticsearch and will have to take action appropriately.
250
251This also applies to `ping()`.