1[[connection_pool]]
2=== Connection Pool
3
4The connection pool is an object inside the client that is responsible for
5maintaining the current list of nodes. Theoretically, nodes are either dead or
6alive. However, in the real world, things are never so clear. Nodes are
7sometimes in a gray-zone of _"probably dead but not confirmed"_, _"timed-out but
8unclear why"_ or _"recently dead but now alive"_. The job of the connection pool
9is to manage this set of unruly connections and try to provide the best behavior
10to the client.
11
12If a connection pool is unable to find an alive node to query against, it
13returns a `NoNodesAvailableException`. This is distinct from an exception due to
14maximum retries. For example, your cluster may have 10 nodes. You execute a
15request and 9 out of the 10 nodes fail due to connection timeouts. The tenth
16node succeeds and the query executes. The first nine nodes are marked dead
17(depending on the connection pool being used) and their "dead" timers begin
18ticking.
19
20When the next request is sent to the client, nodes 1-9 are still considered
21"dead", so they are skipped. The request is sent to the only known alive node
22(#10), if this node fails, a `NoNodesAvailableException` is returned. You
23will note this much less than the `retries` value, because `retries` only
24applies to retries against alive nodes. In this case, only one node is known to
25be alive, so `NoNodesAvailableException` is returned.
26
27There are several connection pool implementations that you can choose from:
28
29
30[discrete]
31==== staticNoPingConnectionPool (default)
32
33This connection pool maintains a static list of hosts which are assumed to be
34alive when the client initializes. If a node fails a request, it is marked as
35`dead` for 60 seconds and the next node is tried. After 60 seconds, the node is
36revived and put back into rotation. Each additional failed request causes the
37dead timeout to increase exponentially.
38
39A successful request resets the "failed ping timeout" counter.
40
41If you wish to explicitly set the `StaticNoPingConnectionPool` implementation,
42you may do so with the `setConnectionPool()` method of the ClientBuilder object:
43
44[source,php]
45----
46$client = ClientBuilder::create()
47            ->setConnectionPool('\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool', [])
48            ->build();
49----
50
51Note that the implementation is specified via a namespace path to the class.
52
53
54[discrete]
55==== staticConnectionPool
56
57Identical to the `StaticNoPingConnectionPool`, except it pings nodes before they
58are used to determine if they are alive. This may be useful for long-running
59scripts but tends to be additional overhead that is unnecessary for average PHP
60scripts.
61
62To use the `StaticConnectionPool`:
63
64[source,php]
65----
66$client = ClientBuilder::create()
67            ->setConnectionPool('\Elasticsearch\ConnectionPool\StaticConnectionPool', [])
68            ->build();
69----
70
71Note that the implementation is specified via a namespace path to the class.
72
73
74[discrete]
75==== simpleConnectionPool
76
77The `SimpleConnectionPool` returns the next node as specified by the selector;
78it does not track node conditions. It returns nodes either they are dead or
79alive. It is a simple pool of static hosts.
80
81The `SimpleConnectionPool` is not recommended for routine use but it may be a
82useful debugging tool.
83
84To use the `SimpleConnectionPool`:
85
86[source,php]
87----
88$client = ClientBuilder::create()
89            ->setConnectionPool('\Elasticsearch\ConnectionPool\SimpleConnectionPool', [])
90            ->build();
91----
92
93Note that the implementation is specified via a namespace path to the class.
94
95
96[discrete]
97==== sniffingConnectionPool
98
99Unlike the two previous static connection pools, this one is dynamic. The user
100provides a seed list of hosts, which the client uses to "sniff" and discover the
101rest of the cluster by using the Cluster State API. As new nodes are added or
102removed from the cluster, the client updates its pool of active connections.
103
104To use the `SniffingConnectionPool`:
105
106[source,php]
107----
108$client = ClientBuilder::create()
109            ->setConnectionPool('\Elasticsearch\ConnectionPool\SniffingConnectionPool', [])
110            ->build();
111----
112
113Note that the implementation is specified via a namespace path to the class.
114
115
116[discrete]
117==== Custom Connection Pool
118
119If you wish to implement your own custom Connection Pool, your class must
120implement `ConnectionPoolInterface`:
121
122[source,php]
123----
124class MyCustomConnectionPool implements ConnectionPoolInterface
125{
126
127    /**
128     * @param bool $force
129     *
130     * @return ConnectionInterface
131     */
132    public function nextConnection($force = false)
133    {
134        // code here
135    }
136
137    /**
138     * @return void
139     */
140    public function scheduleCheck()
141    {
142        // code here
143    }
144}
145----
146
147
148You can then instantiate an instance of your ConnectionPool and inject it into
149the ClientBuilder:
150
151[source,php]
152----
153$myConnectionPool = new MyCustomConnectionPool();
154
155$client = ClientBuilder::create()
156            ->setConnectionPool($myConnectionPool, [])
157            ->build();
158----
159
160If your connection pool only makes minor changes, you may consider extending
161`AbstractConnectionPool` which provides some helper concrete methods. If you
162choose to go down this route, you need to make sure your ConnectionPool
163implementation has a compatible constructor (since it is not defined in the
164interface):
165
166[source,php]
167----
168class MyCustomConnectionPool extends AbstractConnectionPool implements ConnectionPoolInterface
169{
170
171    public function __construct($connections, SelectorInterface $selector, ConnectionFactory $factory, $connectionPoolParams)
172    {
173        parent::__construct($connections, $selector, $factory, $connectionPoolParams);
174    }
175
176    /**
177     * @param bool $force
178     *
179     * @return ConnectionInterface
180     */
181    public function nextConnection($force = false)
182    {
183        // code here
184    }
185
186    /**
187     * @return void
188     */
189    public function scheduleCheck()
190    {
191        // code here
192    }
193}
194----
195
196
197If your constructor matches AbstractConnectionPool, you may use either object
198injection or namespace instantiation:
199
200[source,php]
201----
202$myConnectionPool = new MyCustomConnectionPool();
203
204$client = ClientBuilder::create()
205            ->setConnectionPool($myConnectionPool, [])                                      // object injection
206            ->setConnectionPool('/MyProject/ConnectionPools/MyCustomConnectionPool', [])    // or namespace
207            ->build();
208----
209
210
211[discrete]
212==== Which connection pool to choose? PHP and connection pooling
213
214At first glance, the `sniffingConnectionPool` implementation seems superior. For
215many languages, it is. In PHP, the conversation is a bit more nuanced.
216
217Because PHP is a share-nothing architecture, there is no way to maintain a
218connection pool across script instances. This means that every script is
219responsible for creating, maintaining, and destroying connections everytime the
220script is re-run.
221
222Sniffing is a relatively lightweight operation (one API call to
223`/_cluster/state`, followed by pings to each node) but it may be a
224non-negligible overhead for certain PHP applications. The average PHP script
225likely loads the client, executes a few queries and then closes. Imagine that
226this script being called 1000 times per second: the sniffing connection pool
227performS the sniffing and pinging process 1000 times per second. The sniffing
228process eventually adds a large amount of overhead.
229
230In reality, if your script only executes a few queries, the sniffing concept is
231_too_ robust. It tends to be more useful in long-lived processes which
232potentially "out-live" a static list.
233
234For this reason the default connection pool is currently the
235`staticNoPingConnectionPool`. You can, of course, change this default - but we
236strongly recommend you to perform load test and to verify that the change does
237not negatively impact the performance.
238
239
240[discrete]
241==== Quick setup
242
243As you see above, there are several connection pool implementations available,
244and each has slightly different behavior (pinging vs no pinging, and so on).
245Connection pools are configured via the `setConnectionPool()` method:
246
247[source,php]
248----
249$connectionPool = '\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool';
250$client = ClientBuilder::create()
251            ->setConnectionPool($connectionPool)
252            ->build();
253----
254