JDisc Container Components

This document explains the common concepts necessary to develop all types of JDisc Container components. A basic knowledge of the JDisc Container is required.

All components must extend a base class from the JDisc Container code module. For example, searchers must extend the class com.yahoo.search.Searcher. The main available component types are:

Searchers and document processors belong to a subclass of components called chained components. For an introduction to how the different component types interact, refer to the overview of component types.

The components of the search container are usually deployed as part of an OSGi bundle. Build the bundles using maven and the bundle plugin.

Concurrency

Components will be executed concurrently by multiple threads. This places a very important constraint on all component classes: non-final instance variables are not safe. They must be eliminated, or made thread-safe somehow.

Resource management

Components that use threads, file handles or other native resources that needs to be released when the component falls out of scope, must override a method called deconstruct. Here is an example implementation from a component that uses a thread pool named 'executor':

@Override
public void deconstruct() {
    super.deconstruct();
    try {
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}
Note that it is always advisable to call the super-method first.

Dependency injection

The components might need to access resources, such as other components or config. These are injected directly into the constructor. The following types of constructor dependencies are allowed:

Ideally, any class should have only one public constructor, but if the component needs more than one, annotate the one to be used by the container with @Inject (full name: com.google.inject.Inject).

Deploying a Component

The container will create one or more instances of the component, as specified in the application package. The container will create a new instance of this component only when it is reconfigured, so any data needed by the component can be read and prepared from a constructor in the component.

See the full API available to components at the Container Javadoc.

Once the component passes unit tests, it can be deployed. The steps involved are building the component jar file, adding it to the Vespa application package and deploying the application package. These steps are described in the following sections, using a searcher as example.

Building the Plugin .jar

To build the plugin jar, call mvn install in the project directory. It can then be found in the target directory, and will have the suffix -deploy.jar.

Assume for the rest of the document that the artifactId is com.yahoo.search.example.SimpleSearcher and the version is 1.0. The plugin built will then have the name com.yahoo.search.example.SimpleSearcher-1.0-deploy.jar.

Adding the Plugin to the Vespa Application Package

The previous step should produce a plugin jar file, which may now be deployed to Vespa by adding it to an application package: A directory containing at minimum hosts.xml and services.xml.

  • placing the com.yahoo.search.example.SimpleSearcher-1.0-deploy.jar the components/ directory under the application package root
  • Changing services.xml to include the Searcher
To include the searcher in services.xml, define a search chain and add the searcher to it. Example:
<?xml version="1.0" encoding="utf-8" ?>
<services version="1.0">

    <admin version="2.0">
      <adminserver hostalias="node1" />
      </configservers>
      <logserver hostalias="node1" />
    </admin>

    <container version="1.0">
      <search>
        <chain id="default" inherits="vespa">
          <searcher id="com.yahoo.search.example.SimpleSearcher"/>
        </chain>
      </search>
      <nodes>
        <node hostalias="node1" />
      </nodes>
    </container>

</services>
The searcher id above is resolved to the plugin jar we added by the Bundle-SymbolicName (a field in the manifest of the jar file), which is determined by the artifactId, and to the right class within the bundle by the class name. By keeping the searcher id, class name and artifactId the same, we keep things simple, but more advanced use where this is possible is also supported. This will be explained in later sections.

For a reference to these tags, see the search chains reference.

Example hosts.xml:

<?xml version="1.0" encoding="utf-8" ?>
<hosts>
  <host name="localhost">
    <alias>node1</alias>
  </host>
</hosts>
By creating a directory containing this services.xml, hosts.xml and components/com.yahoo.search.example.SimpleSearcher-1.0-deploy.jar, that directory becomes a complete application package containing a bundle, which can now be deployed.

Deploying the Application Package

Set up a Vespa instance using the quick start. Once the component and the config is added to the application package, it can be deployed by running vespa-deploy. These steps will copy any changed bundles to the nodes in the cluster which needs them and switch queries over to running the new component versions.

This works safely without requiring any processes to be restarted, even if the application package contains changes to classes which are already running queries. The switch is atomic from the point of view of the query - all queries will execute to completion, either using only the components of the last version of the application package or only the new ones, so interdependent changes in multiple searcher components can be deployed without problems.

JNI requires restart

The exception to the above is bundles containing JNI packages. There can only be one instance of the native library - a bundle hence cannot reload. Best practise is to load the JNI library in the constructor, as this will cause the new bundle not to load, but continue on the current version. A subsequent restart will load the new bundle. This will hence not cause failures. Alternatively, if the JNI library is initialized lazily (e.g. on first invocation), bundle reloads will succeed, but subsequent invocations of code using the JNI library will fail. Hence, the new version will run, but fail.

A warning is issued in the log when deploying rather than the normal Switching to the latest deployed set of handlers - example:

[2016-09-21 14:22:05.387] WARNING : qrserver         stderr     Cannot load mylib native library
To minimize restarts, it is recommended to put JNI components in minimal, separate bundles. This will prevent reload of the JNI-bundles, unless the JNI-bundle itself is changed.

Troubleshooting

If there is some error in the application package, it will usually be detected during the vespa-deploy prepare step and cause an error message. However, some classes of errors are only detected once the application is deployed. When redeploying an application, it is therefore recommended to watch the vespa log by running:

vespa-logfmt -N
The new application is active after the INFO message:
Switched to the latest deployed set of handlers...;
If this message does not appear after a reasonable amount of time after completion of vespa-deploy activate, one will see some errors or warnings instead, that will help debug the application.

Monitoring the active Application

All containers also provide a built-in handler that outputs JSON formatted information about the active application, including its components and chains (it can also be configured to show a user-defined version). The handler answers to requests with the path /ApplicationStatus. For example, if 'localhost' runs a a container with HTTP configured on port 8080:

http://localhost:8080/ApplicationStatus
In order to view nicely formatted output in a web browser, it might be necessary to install a browser extension, like JSONView for Firefox.

Including third-party libraries

External dependencies can be included into the bundle.

Exporting, importing and including packages in bundles

OSGi features information hiding - by default all the classes used inside a bundle are invisible from the outside.

Global and exported packages

The JDisc Container has one set of global packages. These are packages that are available with no import, and constitutes the supported API of the JDisc Container. Backwards incompatible changes are not made to these packages.

There is also a set of exported packages. These are available for import, and includes all legacy packages, plus extension packages which are not part of the core API. Note that these are not considered to be "public" APIs, as global packages are, and backwards incompatible changes can be made to these packages, or they may be removed.

The list of exported and global packages is available in the container-core pom.xml, in project/properties/exportedPackages and project/properties/globalPackages.

Versions

All the elements of the search container which may be referenced by an id may be versioned, that includes chains, components and query profiles. This allows multiple versions of these elements to be used at the same time, including multiple versions of the same classes, which is handy for bucket testing new versions.

An id or id reference may include a version by using the following syntax: name:version. This works with ids in search requests, services.xml, code and query profiles.

A version has the format:

major.minor.micro.qualifier
where major, minor and micro are integers and qualifier is a string. Any right-hand portion of the version string may be skipped. In versions, skipped values mean "0" (and empty for the qualifier). In version references skipped values means "unspecified". Any unspecified number will be matched to the highest number available, while a qualifier specified must be matched exactly if it is specified (qualifiers are rarely used).

To specify the version of a bundle, specify version in pom.xml (we recommend not using qualifier):

<groupId>com.yahoo.example</groupId>
<artifactId>MyPlugin</artifactId>
<version>major.minor.micro</version>
This will automatically be used to set the Bundle-Version in the bundle manifest.

For more details, see component versioning.