[[future_mode]]
== Future Mode

The client offers a mode called "future" or "async" mode. This allows batch 
processing of requests (sent in parallel to the cluster), which can have a 
dramatic impact on performance and throughput.

PHP is fundamentally single-threaded, however, libcurl provides a functionality 
called the "multi interface". This functionality allows languages like PHP to 
gain concurrency by providing a batch of requests to process. The batch is 
executed in parallel by the underlying multithreaded libcurl library, and the 
batch of responses is then returned to PHP.

In a single-threaded environment, the time to execute `n` requests is the sum of 
those `n` request's latencies. With the multi interface, the time to execute `n` 
requests is the latency of the slowest request (assuming enough handles are 
available to execute all requests in parallel).

Furthermore, the multi-interface allows requests to different hosts 
simultaneously, which means the Elasticsearch-PHP client can more effectively 
utilize your full cluster.


=== Using Future Mode

Utilizing this feature is straightforward, but it does introduce more 
responsibility into your code. To enable future mode, set the `future` flag in 
the client options to `'lazy'`:

[source,php]
----
$client = ClientBuilder::create()->build();

$params = [
    'index'  => 'test',
    'id'     => 1,
    'client' => [
        'future' => 'lazy'
    ]
];

$future = $client->get($params);
----

This returns a _future_, rather than the actual response. A future represents a 
_future computation_ and acts like a placeholder. You can pass a future around 
your code like a regular object. When you need the result values, you can 
_resolve_ the future. If the future has already resolved (due to some other 
activity), the values are immediately available. If the future has not resolved 
yet, the resolution blocks until those values become available (for example, 
after the API call completes).

In practice, this means you can queue up a batch of requests by using 
`future: lazy` and they pend until you resolve the futures, at which time all 
requests will be sent in parallel to the cluster and return asynchronously to 
curl.

This sounds tricky, but it is actually simple thanks to RingPHP's `FutureArray` 
interface, which makes the future act like a simple associative array. For 
example:

[source,php]
----
$client = ClientBuilder::create()->build();

$params = [
    'index'  => 'test',
    'id'     => 1,
    'client' => [
        'future' => 'lazy'
    ]
];

$future = $client->get($params);

$doc = $future['_source'];    // This call blocks and forces the future to resolve
----

Interacting with the future as an associative array, just like a normal 
response, causes the future to resolve that particular value (which in turn 
resolves all pending requests and values). This allows patterns such as:

[source,php]
----
$client = ClientBuilder::create()->build();
$futures = [];

for ($i = 0; $i < 1000; $i++) {
    $params = [
        'index'  => 'test',
        'id'     => $i,
        'client' => [
            'future' => 'lazy'
        ]
    ];

    $futures[] = $client->get($params);     //queue up the request
}


foreach ($futures as $future) {
    // access future's values, causing resolution if necessary
    echo $future['_source'];
}
----

The queued requests will execute in parallel and populate their futures after 
execution. Batch size defaults to 100 requests/batch.

If you wish to force future resolution, but don't need the values immediately, 
you can call `wait()` on the future to force resolution, too:

[source,php]
----
$client = ClientBuilder::create()->build();
$futures = [];

for ($i = 0; $i < 1000; $i++) {
    $params = [
        'index'  => 'test',
        'id'     => $i,
        'client' => [
            'future' => 'lazy'
        ]
    ];

    $futures[] = $client->get($params);     //queue up the request
}

//wait() forces future resolution and will execute the underlying curl batch
$futures[999]->wait();
----

=== Changing batch size

The default batch size is 100, meaning 100 requests queue up before the client 
forces futures to begin resolving (for example, initiate a `curl_multi` call). 
The batch size can be changed depending on your preferences. The batch size can 
be set via the `max_handles` setting when configuring the handler:

[source,php]
----
$handlerParams = [
    'max_handles' => 500
];

$defaultHandler = ClientBuilder::defaultHandler($handlerParams);

$client = ClientBuilder::create()
            ->setHandler($defaultHandler)
            ->build();
----

This changes the behavior to wait on 500 queued requests before sending the 
batch. Note, however, that forcing a future to resolve causes the underlying 
curl batch to execute, regardless of if the batch is "full" or not. In this 
example, only 499 requests are added to the queue, but the final future 
resolution forces the batch to flush anyway:

[source,php]
----
$handlerParams = [
    'max_handles' => 500
];

$defaultHandler = ClientBuilder::defaultHandler($handlerParams);

$client = ClientBuilder::create()
            ->setHandler($defaultHandler)
            ->build();

$futures = [];

for ($i = 0; $i < 499; $i++) {
    $params = [
        'index'  => 'test',
        'id'     => $i,
        'client' => [
            'future' => 'lazy'
        ]
    ];

    $futures[] = $client->get($params);     //queue up the request
}

// resolve the future, and therefore the underlying batch
$body = $future[499]['body'];
----


=== Heterogeneous batches are OK

It is possible to queue up heterogeneous batches of requests. For example, you 
can queue up several GETs, indexing requests, and a search:

[source,php]
----
$client = ClientBuilder::create()->build();
$futures = [];

$params = [
    'index'  => 'test',
    'id'     => 1,
    'client' => [
        'future' => 'lazy'
    ]
];

$futures['getRequest'] = $client->get($params);     // First request

$params = [
    'index' => 'test',
    'id'    => 2,
    'body'  => [
        'field' => 'value'
    ],
    'client' => [
        'future' => 'lazy'
    ]
];

$futures['indexRequest'] = $client->index($params);       // Second request

$params = [
    'index' => 'test',
    'body'  => [
        'query' => [
            'match' => [
                'field' => 'value'
            ]
        ]
    ],
    'client' => [
        'future' => 'lazy'
    ]
];

$futures['searchRequest'] = $client->search($params);      // Third request

// Resolve futures...blocks until network call completes
$searchResults = $futures['searchRequest']['hits'];

// Should return immediately, since the previous future resolved the entire batch
$doc = $futures['getRequest']['_source'];
----


=== Caveats to Future mode

There are a few caveats to using future mode. The biggest is also the most 
obvious: you need to deal with resolving the future yourself. This is usually 
trivial, but can sometimes introduce unexpected complications.

For example, if you resolve manually using `wait()`, you may need to call 
`wait()` several times if there were retries. This is because each retry 
introduces another layer of wrapped futures, and each needs to be resolved to 
get the final result.

However, this is not needed if you access values via the ArrayInterface (for 
example, `$response['hits']['hits']`) since FutureArrayInterface automatically 
and fully resolves the future to provide values.

Another caveat is that certain APIs will lose their "helper" functionality. For 
example, "exists" APIs (`$client->exists()`, `$client->indices()->exists`, 
`$client->indices->templateExists()`, and so on) typically return a true or 
false under normal operation.

When operated in future mode, the unwrapping of the future is left to your 
application, which means the client can no longer inspect the response and 
return a simple true/false. Instead, you'll see the raw response from {es} and 
will have to take action appropriately.

This also applies to `ping()`.