UPDATE: This article refers to our hosted Elasticsearch offering by an older name, Found. Please note that Found is now known as Elastic Cloud.
In order to write a great plugin, we need to have an overview of the different plugin types and scopes Elasticsearch has made available to us. This article gives a rundown of these and also some tips on how to write a 'well behaved' plugin.
Plugins make up an important part of the Elasticsearch ecosystem, and plugin authors are allowed to redefine or add - and even remove - functionality to/from Elasticsearch. In Writing an Elasticsearch Plugin: Getting Started we showed how to start developing a plugin from scratch; this time around we’re going to look at the different scopes a plugin can add functionality to.
Plugins are discovered by looking for plugin definition files within the Java classpath of the Elasticsearch process. These provide the exact class names of the
Plugin subclasses that Elasticsearch then loads and initializes. This process is described here. After being loaded, Elasticsearch will call certain methods on the
Plugin instances depending on the scope the plugin currently can affect.
In essence, there’s three levels, or scopes, of components that plugins may provide. These scopes are:
The scopes determine when the components they define are used. Global scope is used whenever the associated Elasticsearch instance is initializing, index scope whenever an index is created (or opened after being closed), and shard for every shard started on the given node. This feature allows different functionality to be attached to different indexes (or even shards) within the same Elasticsearch cluster.
Each of the scopes has two kinds of components:
Modules are actually Guice modules that usually “bind” classes to implementations. Often, this simply implies that one or more instances of the class will be instantiated at a later time, when it’s required. An introduction to Guice is outside the scope of this article, but interested developers can find multiple tutorials and great introductions to the Guice framework here.
Services are anything that starts and may be stopped at a later time. Services in the global scope are implementations of
LifecycleComponent, which has two important methods:
stop that are invoked by Elasticsearch when it starts and stops respectively.
Plugins also get notified when Modules are created, which enables calling public methods on the modules before they’re used. The most common use case of this is registering additional analyzers, tokenizers or token filters on the
For an overview over which modules are available, have a look at Elasticsearch Internals: An Overview.
Additionally, plugins are allowed to provide additional settings that may be used by both Elasticsearch core and other plugins/modules.
Classes instantiated via modules may of course contain start/stop logic as well, but they have to perform their own bookkeeping, for example by registering a
LifecycleListener on an external
LifecycleComponent. It’s generally considered good practice to avoid doing heavy lifting or spawning threads during initialization, and rather use
LifecycleComponent callbacks to actually create and - more importantly - stop and clean up resources when they’re no longer required. Not freeing resources when a module is no longer required, for example for shard scoped modules, may lead to memory leaks.
In the above table, notice that index and shard services are not actually
ClosableIndexComponents. The difference between them is that the index and shard services are implicitly started when they are created, due to their nature (they’re only invoked when required).
Not all functionality in Elasticsearch is currently intended (or even possible) to be extended or replaced. Some pieces of functionality are commonly either properly named with the
Internal prefix, such as
InternalTransportClient, hard-coded via Guice-bindings in a module (and not configurable) or not separated into the common interface/implementation pairs.
One example of a commonly used service that is not possible to replace or extend by writing a plugin is the
ClusterService. This service is responsible for maintaining and handling updates to the cluster state, as well as notifying cluster state listeners about any changes. The binding of this service is hard-coded into Elasticsearch and is not configurable.
Another example is the
TransportService is responsible for handling the connections to the other instances in the cluster, sending requests to the other nodes and providing the caller with the response via a callback. While it is possible to configure Elasticsearch to bind a different implementation by using the
transport.service.type-configuration key, the
TransportService is a class, not an interface, and is used extensively in different parts of the Elasticsearch code-base, so a significantly different implementation would be difficult since there’s a lot of compatibility that has to be kept.
Plugins are a source of almost endless possibilities of extensions to Elasticsearch and is a great way to provide additional functionality to Elasticsearch without having to run a custom fork. We can’t wait to see what plugins the community comes up with next!