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