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()`.