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