The StorageDriver Contract #

Available since 2.61.0

kuzzle exports the contract as TypeScript types: StorageDriver for an instance, StorageDriverClass for what the package exports, and their parts (StorageDriverCore, StorageUpdateByQuery, StorageMappingUpdates, StorageStatsCapability, StorageEngineHooks). They are the reference for every signature: the source documents each method. Declare your class with implements StorageDriver and the compiler checks it.

Lifecycle #

Kuzzle builds two instances of the class at startup, one per storage scope:

ScopestoreScopeEnumHolds
publicPUBLICthe application's indexes, those the API reaches
privatePRIVATEKuzzle's internal index (users, profiles, roles, API keys...) and the plugins' private storage
new Driver(config: StorageEngineElasticsearch, scope: storeScopeEnum)

config is the whole services.storageEngine section. Then Kuzzle calls init() on each instance, under the service init timeout (services.storageEngine.initTimeout): connect to the engine there. The two scopes must not see each other's indexes: how the driver keeps them apart on the engine (a prefix, a database, a schema...) is up to it.

Kuzzle also calls the static createNativeClient(clientConfig) whenever an application or a plugin asks for the engine's own client (app.storage.storageClient, app.storage.StorageClient, context.constructors.ESClient): it returns a new client each time, built from services.storageEngine.client. That client belongs to whoever asked for it: Kuzzle never closes it.

On a graceful shutdown, once the requests in flight are done, Kuzzle calls the optional close() hook of each instance (see Engine hooks): release the engine connections there.

The mandatory core #

Every driver implements these methods. Indexes and collections are Kuzzle's names; documents are JSON objects with an _id.

GroupMethods
Engineinit, info, translateKoncordeFilters
IndexescreateIndex, deleteIndex, deleteIndexes, getSchema
CollectionscreateCollection, deleteCollection, truncateCollection, refreshCollection, getMapping, getSettings
One documentget, exists, count, create, createOrReplace, update, upsert, replace, delete, deleteFields
Several documentsmGet, mExists, mCreate, mCreateOrReplace, mUpdate, mUpsert, mReplace, mDelete, import
Searchsearch, scroll, deleteByQuery, mExecute

At startup, Kuzzle refuses a driver instance lacking one of them (services.storage_driver.invalid_driver), rather than failing on the first request needing it.

A few rules hold across them:

  • Search bodies, mappings and settings are in the Elasticsearch format, as the API receives them: the driver translates what it supports.
  • translateKoncordeFilters turns Koncorde filters into the driver's query language. An Elasticsearch-family driver uses QueryTranslator, which kuzzle exports.
  • Writes stamp _kuzzle_info (author, createdAt, updater, updatedAt) from the userId option, unless injectKuzzleMeta is false, and honour refresh (false, true or "wait_for").
  • An update meeting a version conflict retries up to its retryOnConflict option, then rejects with services.storage.too_many_changes.
  • The m* writes are partial: a document that cannot be written goes in errors, the others are written. mCreateOrReplace rejects more documents than limits.documentsWriteCount with services.storage.write_limit_exceeded, unless its limits option is false; source: false leaves _source out of its answer.
  • scroll: a search started with the scroll option answers a scrollId, which scroll() then pages through. Keep the cursor's state where every Kuzzle node reads it: the internal cache, through global.kuzzle.ask("core:cache:internal:store" | "core:cache:internal:get" | "core:cache:internal:del", ...). A scroll that is over, expired or unknown rejects with services.storage.unknown_scroll_id.
  • deleteByQuery: a size of -1 is no limit; with fetch: false, it answers no documents, only the deleted count (bulk:deleteByQuery asks for both).
  • createCollection on an existing collection keeps it and its documents (and applies the new mappings, with mappingUpdates); deleteIndexes skips the indexes that do not exist and answers the ones it deleted.

Capabilities #

The optional groups, declared in the manifest's capabilities:

CapabilityMethods or feature
aggregationsaggregations / aggs in a search body
mappingUpdatesupdateMapping, updateCollection
statsstats
updateByQueryupdateByQuery, bulkUpdateByQuery

At startup, Kuzzle refuses a driver that declares a capability without its methods (services.storage_driver.invalid_driver). Afterwards, it rejects a request needing a capability the driver did not declare, before reaching the driver, with a services.storage_driver.unsupported_capability error. Declare only what the driver fully implements: no silent approximation.

Engine hooks #

Optional methods Kuzzle calls when the driver has them, and skips otherwise:

HookWhen Kuzzle calls it
generateMissingAliases()at startup, when services.storageEngine.generateMissingAliases is set: repairs the engine-side naming of existing data
waitForCollectionShards(index, collection, timeout?)before using a collection another node of the cluster has just created: waits, for a bounded time, until it can be written to
close()on a graceful shutdown, once the requests in flight are done: releases the engine connections (a connection pool, a socket...)

Kuzzle calls close() once per instance and never calls that instance again afterwards. It may come before init() resolved, or after it failed, when Kuzzle stops during its startup: close whatever was opened, and nothing else. Kuzzle waits for it for 5 seconds at most; a failure or a timeout is logged as a warning, and the shutdown goes on.

What a driver may use of Kuzzle #

Inside its methods, a driver may use the running Kuzzle instance, global.kuzzle:

  • log (prefer log.child("...")), config, hash(), emit();
  • the internal cache, through ask("core:cache:internal:get" | "core:cache:internal:store" | "core:cache:internal:del");
  • withLock(key, callback), exported by kuzzle: a lock shared by every node of the cluster.

That is the set the conformance kit provides when it runs the driver outside of Kuzzle: anything else is not available there.