Upgrading from 2.59 #
This page covers Kuzzle 2.60.0, including what its first beta, 2.60.0-beta.1, already shipped. If you upgrade from an older version, read the previous upgrade notes too.
The API, the configuration and the TypeScript declarations stay compatible with v2.59.0. The one requirement that changes is the Node.js version. Items that may need an action on your side are marked Action.
The changelog is generated from the commits and says what was done. This page says what you may have to check.
Operators #
Node.js 22.12 or later is required #
engines.node is now >=22.12.0 <25.0.0: Node.js 20 is no longer supported, and Node.js 22 and 24 are the tested versions. Several dependencies are now ESM-only packages that Kuzzle loads with require(), which Node.js only supports from 22.12 on.
As with the drop of Node.js 18 in 2.50.0, this ships in a minor version: nothing else in the API, the configuration or the typings changes with it.
Action: check the Node.js version of your Docker images (the base image of your Dockerfile) and of your other runtimes, and move them to Node.js 22.12 or later before you upgrade.
Startup in a cluster #
Every cluster-wide lock Kuzzle takes internally now uses withLock, whose locks are extended for as long as the work they protect runs. Some of them used to expire after 5 seconds even when that work was still running, letting another node in: the locks of the installations (app.install), of the cluster handshake, of the hidden collections, and of admin:resetSecurity / admin:resetDatabase.
This changes what you can see while several nodes start together:
- Nodes wait for each other's startup imports. The mappings, fixtures and permissions imported at startup (
app.import, plugin imports,start-kuzzle-serveroptions) are run by one node, and the other nodes now wait for it to finish all of them before they go on. Before, they could go on while the import was still running. Once a waiting node gets its turn, it finds the import done and only loads it into its own cache; if the node ahead of it died mid-import, it runs the import itself. - A changed import payload is imported once. When the payload of a startup import changed, its recorded fingerprint was not updated, so every later start imported it again. It is now replaced by the new one.
- Plugin storage bootstrap.
context.accessors.storage.bootstrapused to create the collections on one node while the others waited and then only loaded them into their cache. Every node now creates them in turn, under the same lock, and waits for it with no limit. - Cluster handshake. A joining node waits for the handshake lock with no retry limit:
cluster.joinTimeout(60 seconds by default) is what still bounds the wait: it shuts the node down if it has not joined in time. - A lost lock fails the startup. If one of these locks is lost while its work is running (for instance, Redis could not extend it), the startup fails with
MutexLockLostError. A node that loses the handshake lock logs[CLUSTER] Lost the handshake lock while joining the cluster. Shutting down.and shuts down rather than stay half-joined.
A rolling upgrade from v2.59.0 keeps the nodes mutually excluded: a v2.59.0 node takes the same locks, on the same Redis keys, with the deprecated Mutex, and a Mutex and a withLock call exclude each other.
Collections with number_of_replicas only #
When a collection already exists, Kuzzle compares its number_of_shards with the configured one, as the shard count of an existing collection cannot change. A collection configured with number_of_replicas but no number_of_shards failed that check on every start: a warning in production and, in development mode (NODE_ENV=development), a startup failure with core.fatal.unexpected_error. The check is now skipped when number_of_shards is not configured.
A real mismatch in development mode now fails with its documented error, services.storage.wrong_es_static_settings_for_collection_recreation, instead of core.fatal.unexpected_error. In production, it is still only logged as a warning.
Placeholder index of an empty Kuzzle index #
An index created with index:create holds a hidden _kuzzle_keep collection, backed by its own Elasticsearch index, so that the empty index exists in Elasticsearch. Creating the first real collection was meant to drop it, but it was recreated straight away: every Kuzzle index kept that extra Elasticsearch index. It is now dropped, once the real collection has been created.
Indexes that already hold it keep it until a new collection is created in them; nothing removes it on upgrade. Deleting a collection still recreates it, as before.
Logs #
- Errors are logged under
err. Kuzzle uses kuzzle-logger 1.5 or later: an error is logged under anerrfield, with itsname,message,stack,causeand custom properties (such as aKuzzleError'sidandstatus), instead of having only its enumerable properties spread at the top level of the log entry. Thenamespace,nodeIdandrequestIdfields are applied to error entries too. Action: if a log pipeline, dashboard or alert reads error fields at the top level of Kuzzle's logs, point it toerr. - The Loki preset now runs on pino-loki 3 (through kuzzle-logger 1.7). The preset keeps the behaviour it had with pino-loki 2: logs are still batched every second by default, and
metais not sent as structured metadata unless asked for.
HTTP #
- A request with an empty body (
Content-Length: 0) whose headers span several TCP packets is now answered. It used to hang until the idle timeout, which a reverse proxy forwarding large headers turned into a502.
Dependencies #
These dependencies changed major version. Each was pinned to its previous behaviour, and nothing changes in what you configure:
- ioredis 6. ioredis 6 speaks RESP3 by default, enables TCP keep-alive after 30 seconds and retries a lost connection with an exponential backoff. Kuzzle keeps ioredis 5's values for
protocol(RESP2),keepAliveandretryStrategy, so nothing changes on the wire nor in how a lost connection is retried. A value set in theservices.internalCacheorservices.memoryStorageoptions still wins over these. - aedes 1 (MQTT). aedes 1 disconnects a client whose socket stays undrained for 60 seconds; Kuzzle 2 turns that timeout off, as aedes 0.x had none.
- protobufjs 8 (cluster messages). Kuzzle keeps the wire format of protobufjs 7, so nodes of v2.59.0 and v2.60.0 understand each other during a rolling upgrade.
- cookie 2. The
Set-Cookieheaders sent byauth:login/auth:logout, and the cookies parsed from requests, are the same as before. - redis-semaphore 6 (the lock library behind
withLock) only changes its packaging. - The unused
inquirerdependency is removed: production installs carry 23 fewer packages.
Application and plugin developers #
- New export:
MutexAcquireError.withLocknow rejects with it when it cannot acquire the lock within its attempts, instead of a plainErrorwith the same message. It extendsError, so acatchthat handled the plainErrorstill works; you can now tell a busy lock apart withinstanceof MutexAcquireError. - New type exports:
StartOptions,InstallationConfig,ImportConfigandSupportConfigcan be imported fromkuzzle. They name the options of the server's start and the shapes ofPlugin.imports. withLockandMutexon the same key. The previous upgrade notes said not to use both on the same key. They can be: both take the same Redis key withSET NX, so they exclude each other, and you can migrate yourMutexcall sites one at a time. See Migrating to withLock.- Locks released at shutdown. A node that stops now releases the locks it took with
withLockbefore it exits, as it already did forMutex, so the other nodes no longer wait for them to expire.