Search (Elasticsearch)¶
Note
Not part of core. Install it separately:
composer require kinetis/search-elasticsearch
Builds a real Elastic\Elasticsearch\Client (from
elasticsearch/elasticsearch) for searching and indexing documents.
Every request it makes runs without blocking the rest of your
application.
With SEARCH_ELASTICSEARCH_HOST set, installing the package binds
Elastic\Elasticsearch\Client and the engine-neutral
Kinetis\Search\SearchClient over it, so a controller, command, or
queued job constructor-injects either with nothing to register. The
transport is built while the package registers, and the credentials are
checked with it, opening no connection — so a host that isn’t one usable
origin, an unusable deadline or response bound, or two credentials for
one request fails at boot rather than inside the first search. No client
is built until something resolves one. Your own bootstrap.php runs after that and can bind a different
client. Build one directly with
ElasticsearchClientFactory::fromConfig($config) for a second, named
connection, or outside the container.
Each injection gets its own client over the one shared transport, rather than one client for the whole worker — see Why the client is short-lived.
use Elastic\Elasticsearch\Client;
$client->index([
'index' => 'articles',
'id' => '1',
'body' => ['title' => 'Kinetis', 'category' => 'framework'],
]);
$results = $client->search([
'index' => 'articles',
'body' => ['query' => ['match' => ['category' => 'framework']]],
]);
The returned $client is the real, unmodified
Elastic\Elasticsearch\Client — every method it documents (search(),
index(), get(), delete(), indices(), cluster(), esql(), and
the rest) works exactly as the library’s own documentation describes,
answering its own Elastic\Elasticsearch\Response\Elasticsearch objects.
Search covers what this package shares with
Search (OpenSearch): the configuration keys, named connections, the
SearchClient contract, the failure types, and the transport-decorator
seam. This page is what belongs to Elasticsearch alone.
Matching the client to your cluster¶
The package accepts elasticsearch/elasticsearch ^8.19 || ^9.0, and
the major has to match your cluster’s: a 9.x client sends
compatible-with=9, which an 8.x cluster rejects. Composer resolves to
the newest allowed release, so an Elasticsearch 8 cluster needs the
constraint pinned in your own application:
composer require elasticsearch/elasticsearch:^8.19
Everything on this page and in Search behaves identically on either major.
Authenticating with an API key¶
Basic auth is configured as Search describes. Elastic Cloud issues API keys instead, and this package takes them:
SEARCH_ELASTICSEARCH_API_KEY=VnVhQ2ZHY0JDZGJrU...
Key |
Default |
Purpose |
|---|---|---|
|
— |
The |
|
— |
The key’s id, when you hold the id and secret separately rather than the encoded pair. |
The key travels as an Authorization: ApiKey header and never in the
URL. Configuring an API key and SEARCH_ELASTICSEARCH_USERNAME together
raises a SearchConfigurationException while the client is built: they
are two credentials for one request, and which one reached the cluster
would depend on header precedence rather than on your decision.
What this package pins on the official client¶
Three of ClientBuilder’s own settings are deliberately not used, and
one default is replaced. Each is a guarantee the rest of Kinetis makes.
No retry. Elastic\Transport\Transport catches PSR-18’s
NetworkExceptionInterface and re-sends the request, and ClientBuilder
otherwise leaves one retry armed. A deadline or a dropped connection
part-way through an index or bulk request has an unknown dispatch
outcome, and re-sending it can write the document twice, so retries are
pinned to zero on the built transport. A request that never completed
therefore surfaces as
Elastic\Transport\Exception\NoNodeAvailableException with this
project’s own SearchNetworkException as its previous, where the reason
and the request are.
One node, always in service. The node pool is this package’s
SingleNode rather than Elasticsearch’s SimpleNodePool. That default
marks the node it failed on dead and never revives it, which for a
one-node client would mean one dropped connection ends searching for as
long as that client lives.
Credentials outside the URL.
ClientBuilder::setBasicAuthentication() is never called: it reaches
Transport::setUserInfo(), which puts the credentials into the request
URI’s userinfo, where a transport error message can quote them. Basic
credentials stay in the HTTP client’s own option — which is also why
SEARCH_ELASTICSEARCH_HOST refuses a host carrying userinfo.
TLS on the transport. No setSSLVerification(), setCABundle() or
setSSLKey() call is made; ClientBuilder routes those through an
adapter chosen by the HTTP client’s class name and rejects one it does
not recognize. TLS is configured by SEARCH_ELASTICSEARCH_VERIFY_PEER.
Why the client is short-lived¶
Elastic\Transport\Transport keeps the last request and the last
response it saw, for getLastRequest() and getLastResponse(), and
Client::setAsync() is a mode any holder can flip. A single client for
the whole worker would therefore hold one request’s documents and its
search results — up to SEARCH_ELASTICSEARCH_MAX_RESPONSE_BYTES of
them — until the next search displaced them, which is request-owned state
outliving its request.
So the binding is not shared: the transport, which owns the connection
pool and keeps nothing per call, is built once for the worker, and each
resolution builds its own client over it. A client holds no connection,
so this costs a few objects and no I/O.
ElasticsearchClientFactory::over() is that seam if you wire the client
yourself.
A client then lives exactly as long as whatever resolved it. A controller or queued job, resolved per request, lets its client go with the request. A service that is itself worker-lifetime and injects the client once keeps that one alive — and with it the last request it made — so such a service should resolve a client per operation instead.
Search (OpenSearch) binds one shared client instead: OpenSearch’s
HttpTransport keeps nothing between calls, and its endpoint factory
builds a fresh endpoint per call.
The product check¶
Elasticsearch’s client verifies an X-Elastic-Product: Elasticsearch
response header on every successful response and raises
Elastic\Elasticsearch\Exception\ProductCheckException without it. That
check is the library’s own and reaches it intact, so pointing this
package at an OpenSearch cluster fails loudly rather than half-working.
Use Search (OpenSearch) for OpenSearch.
See also¶
Search — configuration, the engine-neutral
SearchClient, failures, and the transport seam, all shared with Search (OpenSearch).HTTP Client — the non-blocking HTTP client this package builds every request on.
Telemetry — a span per search call over the
transportDecoratorseam, and the bindings that trace it without making a client worker-lifetime.