Features and Improvements in ArangoDB 3.12
Improved memory accounting, wildcard search, and dump performance, multi-dimensional indexes, external versioning, and transparent compression
The following list shows in detail which features have been added or improved in ArangoDB 3.12. ArangoDB 3.12 also contains several bug fixes that are not listed here.
ArangoSearch
WAND optimization (Enterprise Edition)
For arangosearch
Views and inverted indexes (and by extension search-alias
Views), you can define a list of sort expressions that you want to optimize.
This is also known as WAND optimization.
If you query a View with the SEARCH
operation in combination with a
SORT
and LIMIT
operation, search results can be retrieved faster if the
SORT
expression matches one of the optimized expressions.
Only sorting by highest rank is supported, that is, sorting by the result
of a scoring function in descending order (DESC
).
See Optimizing View and inverted index query performance for examples.
This feature is only available in the Enterprise Edition.
SEARCH
parallelization
In search queries against Views, you can set the new parallelism
option for
SEARCH
operations to optionally process index segments in parallel using
multiple threads. This can speed up search queries.
The default value for the parallelism
option is defined by the new
--arangosearch.default-parallelism
startup option that defaults to 1
.
The new --arangosearch.execution-threads-limit
startup option controls how
many threads can be used in total for search queries. The new
arangodb_search_execution_threads_demand
metric reports the number of threads
that queries request. If it is below the configured thread limit, it coincides
with the number of active threads. If it exceeds the limit, some queries cannot
currently get the threads as requested and may have to use a single thread until
more become available.
See SEARCH
operation in AQL
for details.
Analyzers
wildcard
Analyzer
You can use the new wildcard
Analyzer in combination with an inverted index or
View to accelerate wildcard searches, especially if you want to find non-prefix
partial matches in long strings.
The Analyzer can apply another Analyzer of your choice before creating n-grams
that are then used in LIKE
searches with _
and %
wildcards.
See Transforming data with Analyzers for details.
multi_delimiter
Analyzer
The new multi_delimiter
Analyzer type accepts an array of strings to define
multiple delimiters to split the input at. Each string is considered as one
delimiter that can be one or multiple characters long.
Unlike with the delimiter
Analyzer, the multi_delimiter
Analyzer does not
support quoting fields.
If you want to split text using multiple delimiters and don’t require CSV-like
quoting, use the multi_delimiter
Analyzer instead of chaining multiple
delimiter
Analyzers in a pipeline
Analyzer.
var analyzers = require("@arangodb/analyzers");
var a = analyzers.save("delimiter_multiple", "multi_delimiter", {
delimiters: [",", ";", "||"]
}, []);
db._query(`RETURN TOKENS("differently,delimited;words||one|token", "delimiter_multiple")`).toArray();
// [ ["differently", "delimited", "words", "one|token"] ]
See Analyzers for details.
Improved memory accounting and usage
Version 3.12 features multiple improvements to the observability of ArangoDB deployments. Memory usage is more accurately tracked and additional metrics have been added for monitoring the memory consumption.
AQL queries may now report a higher memory usage and thus run into memory limits sooner, see Higher reported memory usage for AQL queries.
The RocksDB block cache metric rocksdb_block_cache_usage
now also includes the
memory used for table building, table reading, file metadata, flushing and
compactions by default.
Furthermore, the memory usage of some subsystems has been optimized. When dropping a database, all contained collections are now marked as dropped immediately. Ongoing operations on these collections can be stopped earlier, and memory for the underlying collections and indexes can be reclaimed sooner. Memory used for index selectively estimates is now also released early. ArangoSearch has a smaller memory footprint for removal operations now.
The following new metrics have been added for memory observability:
Label | Description |
---|---|
arangodb_agency_node_memory_usage | Memory used by Agency store/cache. |
arangodb_aql_cursors_memory_usage | Total memory usage of active AQL query result cursors. |
arangodb_index_estimates_memory_usage | Total memory usage of all index selectivity estimates. |
arangodb_internal_cluster_info_memory_usage | Amount of memory spent in ClusterInfo. |
arangodb_requests_memory_usage | Memory consumed by incoming, queued, and currently processed requests. |
arangodb_revision_tree_buffered_memory_usage | Total memory usage of buffered updates for all revision trees. |
arangodb_scheduler_queue_memory_usage | Number of bytes allocated for tasks in the scheduler queue. |
arangodb_scheduler_stack_memory_usage | Approximate stack memory usage of worker threads. |
arangodb_search_consolidations_memory_usage | Amount of memory in bytes that is used for consolidating an ArangoSearch index. |
arangodb_search_mapped_memory | Amount of memory in bytes that is mapped for an ArangoSearch index. |
arangodb_search_readers_memory_usage | Amount of memory in bytes that is used for reading from an ArangoSearch index. |
arangodb_search_writers_memory_usage | Amount of memory in bytes that is used for writing to an ArangoSearch index. |
arangodb_transactions_internal_memory_usage | Total memory usage of internal transactions. |
arangodb_transactions_rest_memory_usage | Total memory usage of user transactions (excluding top-level AQL queries). |
Web interface
Shard rebalancing
The feature for rebalancing shards in cluster deployments has been moved from the Rebalance Shards tab in the Nodes section to the Distribution tab in the Cluster section of the web interface.
The updated interface now offers the following options:
- Move Leaders
- Move Followers
- Include System Collections
Unified list view
ArangoDB 3.12 brings a significant enhancement to the display of collections, Views, graphs, users, services, and databases in the web interface. The previous tile format has been replaced with a user-friendly tabular layout, providing a consistent and intuitive experience that is visually aligned across all components. The existing tabular views have also been reworked to ensure a seamless transition.
The new tabular format includes the following features:
- Dynamic filters on columns: Each column now has a dynamic filter box, allowing you to efficiently search and filter based on keywords. This makes it easy to locate specific items within the list.
- Dynamic sorting on columns: Sort elements easily based on column data such as name, date, or size. This functionality provides a flexible way to organize and view your data.
Options & optimizer rules management in the Query Editor
You can now specify extra options for your queries via the query editor. You can find all available options in the new Options tab in the right-hand pane of the editor view, positioned alongside the Bind Variables tab.
In the new Options tab, you also have access to the optimizer rules. This section allows you to selectively disable multiple optimizer rules, giving you more control over query optimization according to your specific requirements.
Swagger UI
The interactive tool for exploring HTTP APIs has been updated to version 5.4.1.
You can find it in the web interface in the Rest API tab of the Support
section, as well as in the API tab of Foxx services and Foxx routes that use
module.context.createDocumentationRouter()
.
The new version adds support for OpenAPI 3.x specifications in addition to Swagger 2.x compatibility.
External versioning support
Document operations that update or replace documents now support a versionAttribute
option.
If set, the attribute with the name specified by the option is looked up in the
stored document and the attribute value is compared numerically to the value of
the versioning attribute in the supplied document that is supposed to update/replace it.
The document is only changed if the new number is higher.
This simple versioning can help to avoid overwriting existing data with older versions in case data is transferred from an external system into ArangoDB and the copies are currently not in sync.
This new feature is supported in AQL, the JavaScript API, and the HTTP API for
the respective operations to update and replace documents, including the insert
operations when used to update/replace a document with overwriteMode: "update"
or overwriteMode: "replace"
.
Examples:
Insert a new document normally using arangosh:
db.collection.insert({ _key: "123", externalVersion: 1 });
Update the document if the versioning attribute is higher in the new document, which is true in this case:
db.collection.update("123",
{ externalVersion: 5, anotherAttribute: true },
{ versionAttribute: "externalVersion" });
Updating the document is skipped if the versioning attribute is lower or equal in the supplied document compared to what is currently stored in ArangoDB:
db.collection.update("123",
{ externalVersion: 4, anotherAttribute: false },
{ versionAttribute: "externalVersion" });
You can also make use of the versionAttribute
option in an insert-update
operation for the update case, including in AQL:
db.collection.insert({ _key: "123", externalVersion: 6, value: "foo" },
{ overwriteMode: "update", versionAttribute: "externalVersion" });
db._query(`UPDATE { _key: "123", externalVersion: 7, value: "bar" } IN collection
OPTIONS { versionAttribute: "externalVersion"}`);
External versioning is opt-in and no version checking is performed for
operations for which the versionAttribute
option isn’t set. Document removal
operations do not support external versioning. Removal operations are always
carried out normally.
Note that version checking is performed only if both the existing version of
the document in the database and the new document version contain the version
attribute with numeric values of 0
or greater. If neither the existing document
in the database nor the new document contains the version attribute, or if the
version attribute in any of the two is not a number inside the valid range, the
update/replace operations behave as if no version checking was requested.
This may overwrite the versioning attribute in the database.
Also see:
- The AQL INSERT, UPDATE and REPLACE operations
- The
insert()
,update()
,replace()
methods of the collection object in the JavaScript API - The endpoints to create, update, and replace a single or multiple documents in the HTTP API
AQL
Improved joins
The AQL optimizer now automatically recognizes whether a better strategy for
joining collections can be used, using the new join-index-nodes
optimizer rule.
If two or more collections are joined using nested FOR
loops and the
attributes you join on are indexed by primary indexes or persistent indexes,
then a merge join can be performed because they are sorted.
Note that returning document attributes from the outer loop is limited to attributes covered by the index, or the improved join strategy cannot be used.
The following example query shows an inner join between orders and users on
user ID. Each document in the orders
collection references a user
, and the
users
collection stores the user ID in the _key
attribute. The query returns
the total
attribute of every order along with the user information:
FOR o IN orders
FOR u IN users
FILTER o.user == u._key
RETURN { orderTotal: o.total, user: u }
The _key
attribute is covered by the primary index of the users
collection.
If the orders
collection has a persistent index defined over the user
attribute and additionally includes the total
attribute in
storedValues
,
then the query is eligible for a merge join.
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
10 JoinNode ✓ 500000 - JOIN
10 JoinNode 500000 - FOR o IN orders LET #8 = o.`total` /* index scan (projections: `total`) */
10 JoinNode 1 - FOR u IN users /* index scan + document lookup */
6 CalculationNode ✓ 500000 - LET #4 = { "orderTotal" : #8, "user" : u } /* simple expression */ /* collections used: u : users */
7 ReturnNode 500000 - RETURN #4
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
10 idx_1784521139132825600 persistent orders false false false 100.00 % [ `user` ] [ `total` ] *
10 primary primary users true false false 100.00 % [ `_key` ] [ ] (o.`user` == u.`_key`)
Filter matching syntax for UPSERT
operations
Version 3.12 introduces an alternative syntax for
UPSERT
operations that allows
you to use more flexible filter conditions to look up documents. Previously,
the expression used to look up a document had to be an object literal, and this
is now called the “exact-value matching” syntax.
The new “filter matching” syntax lets you use a FILTER
statement for the
UPSERT
operation. The filter condition for the lookup can make use of the
CURRENT
pseudo-variable to access the lookup document.
UPSERT FILTER CURRENT.name == 'superuser'
INSERT { name: 'superuser', logins: 1, dateCreated: DATE_NOW() }
UPDATE { logins: OLD.logins + 1 } IN users
UPSERT { name: 'superuser' }
INSERT { name: 'superuser', logins: 1, dateCreated: DATE_NOW() }
UPDATE { logins: OLD.logins + 1 } IN users
The FILTER
expression can contain operators such as AND
and OR
to create
complex filter conditions, and you can apply more filters on the CURRENT
pseudo-variable than just equality matches.
UPSERT FILTER CURRENT.age < 30 AND (STARTS_WITH(CURRENT.name, "Jo") OR CURRENT.gender IN ["f", "x"])
INSERT { name: 'Jordan', age: 29, logins: 1, dateCreated: DATE_NOW() }
UPDATE { logins: OLD.logins + 1 } IN users
Read more about UPSERT
operations in AQL.
readOwnWrites
option for UPSERT
operations
A readOwnWrites
option has been added for UPSERT
operations. The default
value is true
and the behavior is identical to previous versions of ArangoDB that
do not have this option. When enabled, an UPSERT
operation processes its
inputs one by one. This way, the operation can observe its own writes and can
handle modifying the same target document multiple times in the same query.
When the option is set to false
, an UPSERT
operation processes its inputs
in batches. Normally, a batch has 1000 inputs, which can lead to a faster execution.
However, when using batches, the UPSERT
operation cannot observe its own writes.
Therefore, you should only set the readOwnWrites
option to false
if you can
guarantee that the input of the UPSERT
leads to disjoint documents being
inserted, updated, or replaced.
Parallel execution within an AQL query
The new async-prefetch
optimizer rule allows certain operations of a query to
asynchronously prefetch the next batch of data while processing the current batch,
allowing parts of the query to run in parallel. This can lead to performance
improvements if there is still reserve (scheduler) capacity.
The new Par
column in a query explain output shows which nodes of a query are
eligible for asynchronous prefetching. Write queries, graph execution nodes,
nodes inside subqueries, LIMIT
nodes and their dependencies above, as well as
all query parts that include a RemoteNode
are not eligible.
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
2 EnumerateCollectionNode ✓ 18 - FOR doc IN places /* full collection scan */ FILTER (doc.`label` IN [ "Glasgow", "Aberdeen" ]) /* early pruning */
5 CalculationNode ✓ 18 - LET #2 = doc.`label` /* attribute expression */ /* collections used: doc : places */
6 SortNode ✓ 18 - SORT #2 ASC /* sorting strategy: standard */
7 ReturnNode 18 - RETURN doc
The profiling output for queries includes a new Par
column as well, but it
shows the number of successful parallel asynchronous prefetch calls.
To not overwhelm the server, async prefetching is restricted and the limits are adjustable. See Configurable async prefetch limits.
Added AQL functions
The new PARSE_COLLECTION()
and PARSE_KEY()
functions let you extract the
collection name respectively the document key from a document identifier with
less overhead.
See Document and object functions in AQL.
The new REPEAT()
function repeats the input value a given number of times,
optionally with a separator between repetitions, and returns the resulting string.
The new TO_CHAR()
functions lets you specify a numeric Unicode codepoint and
returns the corresponding character as a string.
A numeric function RANDOM()
has been added as an alias for the existing RAND()
.
Introduced in: v3.12.1
The new ENTRIES()
functions returns the top-level attributes of an object in
pairs of attribute keys and values.
See Document and object functions in AQL.
Timezone parameter for date functions
The following AQL date functions now accept an optional timezone argument to perform date and time calculations in certain timezones:
DATE_DAYOFWEEK(date, timezone)
DATE_YEAR(date, timezone)
DATE_MONTH(date, timezone)
DATE_DAY(date, timezone)
DATE_HOUR(date, timezone)
DATE_MINUTE(date, timezone)
DATE_DAYOFYEAR(date, timezone)
DATE_ISOWEEK(date, timezone)
DATE_ISOWEEKYEAR(date, timezone)
DATE_LEAPYEAR(date, timezone)
DATE_QUARTER(date, timezone)
DATE_DAYS_IN_MONTH(date, timezone)
DATE_TRUNC(date, unit, timezone)
DATE_ROUND(date, amount, unit, timezone)
DATE_FORMAT(date, format, timezone)
DATE_ADD(date, amount, unit, timezone)
DATE_SUBTRACT(date, amount, unit, timezone)
The following two functions accept up to two timezone arguments. If you only specify the first, then both input dates are assumed to be in this one timezone. If you specify two timezones, then the first date is assumed to be in the first timezone, and the second date in the second timezone:
DATE_DIFF(date1, date2, unit, asFloat, timezone1, timezone2)
(asFloat
can be left out)DATE_COMPARE(date1, date2, unitRangeStart, unitRangeEnd, timezone1, timezone2)
Improved move-filters-into-enumerate
optimizer rule
The move-filters-into-enumerate
optimizer rule can now also move filters into
EnumerateListNodes
for early pruning. This can significantly improve the
performance of queries that do a lot of filtering on longer lists of
non-collection data.
Improved late document materialization
When FILTER
operations can be covered by primary
, edge
, or persistent
indexes, ArangoDB utilizes the index information to only request documents from
the storage engine that fulfill the criteria. This late document materialization
has been improved to load documents in batches for efficiency. It now also
supports projections to fetch subsets of the documents if only a few attributes
are accessed in an AQL query.
For example, a query like below can use late materialization if there is a
persistent
index over the x
attribute:
FOR doc IN coll
FILTER doc.x > 5
RETURN [doc.y, doc.z, doc.a]
If the y
, z
, and a
attributes are not covered by the index (e.g. storedValues
),
then they need to be fetched from the storage engine. This no longer requires to
load the full documents, as indicated by the /* (projections: … ) /*
comment
on the MaterializeNode
due to an improved reduce-extraction-to-projection
optimizer rule. The loading is performed in batches due to the new
batch-materialize-documents
optimization.
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
7 IndexNode ✓ 1000 - FOR doc IN coll /* persistent index scan, index scan + document lookup */ /* with late materialization */
8 MaterializeNode 1000 - MATERIALIZE doc /* (projections: `a`, `y`, `z`) */
5 CalculationNode ✓ 1000 - LET #2 = [ doc.`y`, doc.`z`, doc.`a` ] /* simple expression */ /* collections used: doc : coll */
6 ReturnNode 1000 - RETURN #2
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
7 idx_1788354556957032448 persistent coll false false false 100.00 % [ `x` ] [ `b` ] (doc.`x` > 5)
Optimization rules applied:
Id Rule Name Id Rule Name Id Rule Name
1 move-calculations-up 5 use-indexes 9 batch-materialize-documents
2 move-filters-up 6 remove-filter-covered-by-index 10 async-prefetch
3 move-calculations-up-2 7 remove-unnecessary-calculations-2
4 move-filters-up-2 8 reduce-extraction-to-projection
The number of document lookups caused by late materialization is now reported
under extra.stats.documentLookups
by the Cursor HTTP API and shown in a column
of the query profile output:
Query Statistics:
Writes Exec Writes Ign Doc. Lookups Scan Full Scan Index Cache Hits/Misses Filtered Peak Mem [b] Exec Time [s]
0 0 290 0 3708 0 / 0 3418 32768 0.01247
Introduced in: v3.12.1
The late materialization has been further improved via two new optimizer rules:
push-down-late-materialization
materialize-into-separate-variable
Loading documents is deferred in more cases, namely when attributes accessed in
FILTER
and SORT
operations are covered by an index.
For example, if you have a collection coll
with a persistent
index over the
attribute x
and additionally the attribute b
in storedValues
, then the
materialization would previously occur before the SORT
operation of the
following query:
FOR doc IN coll
FILTER doc.x > 5
SORT doc.b
RETURN doc
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
8 IndexNode ✓ 120 - FOR doc IN coll /* persistent index scan, scan only */ /* with late materialization */
9 MaterializeNode 120 - MATERIALIZE doc INTO #4
5 CalculationNode ✓ 120 - LET #2 = #4.`b` /* attribute expression */
6 SortNode ✓ 120 - SORT #2 ASC /* sorting strategy: standard */
7 ReturnNode 120 - RETURN #4
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
8 idx_1799108758565027840 persistent coll false false false 83.33 % [ `x` ] [ `b` ] (doc.`x` > 5)
The push-down-late-materialization
rule moves the materialization down in the
execution plan, below the SORT
operation:
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
8 IndexNode ✓ 120 - FOR doc IN coll /* persistent index scan, index only (projections: `b`) */ LET #5 = doc.`b` /* with late materialization */
6 SortNode ✓ 120 - SORT #5 ASC /* sorting strategy: standard */
9 MaterializeNode 120 - MATERIALIZE doc INTO #4
7 ReturnNode 120 - RETURN #4
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
8 idx_1799108758565027840 persistent coll false false false 83.33 % [ `x` ] [ `b` ] (doc.`x` > 5)
The rule can also push the materialization past FOR
loops in case they are
optimized to a merge join (JoinNode
, see Improved joins).
The materialize-into-separate-variable
rule optimizes how projections of
materializations are managed. Using separate internal variables for projected
attributes avoids the creation of temporary objects and having to look up the
attributes by key in these objects, which is faster. Example:
FOR doc IN coll
FILTER doc.x > 5
RETURN [doc.a, doc.b]
In previous versions, the execution plan looks like this:
Execution plan:
Id NodeType Est. Comment
1 SingletonNode 1 * ROOT
7 IndexNode 100 - FOR doc IN coll /* persistent index scan, index scan + document lookup (projections: `a`, `b`) */
5 CalculationNode 100 - LET #3 = [ doc.`a`, doc.`b` ] /* simple expression */ /* collections used: doc : coll */
6 ReturnNode 100 - RETURN #3
While not directly visible, the extracted document attributes a
and b
are
internally stored in a temporary object with only these two attributes.
The attributes need to be looked up in the object for constructing the result.
From v3.12.1 onward, the execution plan indicates the use of separate internal variables and that no attribute access is needed to construct the result:
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
7 IndexNode ✓ 100 - FOR doc IN coll /* persistent index scan, scan only */ /* with late materialization */
8 MaterializeNode 100 - MATERIALIZE doc INTO #4 /* (projections: `a`, `b`) */ LET #5 = #4.`a`, #6 = #4.`b`
5 CalculationNode ✓ 100 - LET #2 = [ #5, #6 ] /* simple expression */
6 ReturnNode 100 - RETURN #2
Short-circuiting subquery evaluation
Introduced in: v3.12.1
A ternary operator generally evaluates a condition to decide whether to execute
the true branch or the false branch. For example, count < 5 ? "few" : "many"
evaluates to the string "few"
if the count
variable is less than five,
or to the string "many"
otherwise.
In AQL, the branches are not limited to simple expressions but they can also be
subqueries. Up until ArangoDB v3.12.0, the catch is that subqueries are pulled
out by the query optimizer into separate LET
operations that are executed
before the condition is evaluated. Regardless of which branch is taken,
the subquery code is always executed.
Similarly, when you use subqueries as sub-expressions that are combined with
logical AND
or OR
, the subqueries are executed unconditionally. This is
typically not what you want and can lead to logical errors.
Ternary operator example:
LET doc = FIRST(FOR d IN coll FILTER d._key == "A" RETURN d)
RETURN doc ?: FIRST(INSERT { _key: "A" } INTO coll RETURN NEW)
// doc ?: FIRST(...) is a shorthand for doc ? doc : FIRST(...)
The above query looks up a document with the key A
in a collection called coll
.
If it exists (the doc
variable is truthy), then it is returned. Otherwise, the
document is supposed to be created. However, the false branch is a subquery that
gets executed unconditionally. If the document with key A
already exists, an
attempt to create this document is still made (but fails because of a key conflict).
Execution plan:
Id NodeType Est. Comment
1 SingletonNode 1 * ROOT
10 CalculationNode 1 - LET #9 = { "_key" : "A" } /* json expression */ /* const assignment */
20 SubqueryStartNode 1 - LET #5 = ( /* subquery begin */
11 InsertNode 1 - INSERT #9 IN coll
21 SubqueryEndNode 1 - RETURN $NEW ) /* subquery end */
18 SubqueryStartNode 1 - LET #1 = ( /* subquery begin */
17 IndexNode 1 - FOR d IN coll /* primary index scan, index scan + document lookup */
16 LimitNode 1 - LIMIT 0, 1
19 SubqueryEndNode 1 - RETURN d ) /* subquery end */
14 CalculationNode 1 - LET #11 = (FIRST(#1) ?: #5) /* simple expression */
15 ReturnNode 1 - RETURN #11
As you can see, the false branch execution starts at node 18
, and the
evaluation of the ternary operator happens afterwards in node 14
.
From v3.12.1 onward, the evaluation behavior is changed so that only the applicable branch of a ternary operator is executed and subqueries that are used as sub-expressions are effectively evaluated lazily. For example, you can observe the query optimizer rewriting ternary operators differently to support short-circuiting when using subqueries:
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
22 SubqueryStartNode 1 - LET #1 = ( /* subquery begin */
19 IndexNode 1 - FOR d IN coll /* primary index scan, index scan + document lookup */
18 LimitNode 1 - LIMIT 0, 1
23 SubqueryEndNode 1 - RETURN d ) /* subquery end */
8 CalculationNode 1 - LET doc = FIRST(#1) /* simple expression */
20 SubqueryStartNode 1 - LET #6 = ( /* subquery begin */
10 CalculationNode 1 - LET #9 = ! doc /* simple expression */
11 FilterNode 1 - FILTER #9
12 CalculationNode 1 - LET #10 = { "_key" : "A" } /* json expression */ /* const assignment */
13 InsertNode 1 - INSERT #10 IN coll
21 SubqueryEndNode 1 - RETURN $NEW ) /* subquery end */
16 CalculationNode 1 - LET #11 = (doc ?: FIRST(#6)) /* simple expression */
17 ReturnNode 1 - RETURN #11
The false branch subquery starts at node 20
, still before the evaluation of
the ternary operator at node 16
. However, the subquery has a FILTER
operation
using the negated condition. This prevents the INSERT
operation from running
when the doc
variable is truthy because the opposite is false and
FILTER false
leaves nothing to process.
Also see Evaluation of subqueries.
Index hints for traversals
Introduced in: v3.12.1
For graph traversals, you can now specify index hints in AQL.
If vertex-centric indexes are known to perform better than the edge index but aren’t chosen automatically, you can make the optimizer prefer the indexes you specify. This can be done per edge collection, direction, and level/depth:
FOR v, e, p IN 1..4 OUTBOUND startVertex edgeCollection
OPTIONS {
indexHint: {
"edgeCollection": {
"outbound": {
"base": ["edge"],
"1": "myIndex1",
"2": ["myIndex2", "myIndex1"],
"3": "myIndex3",
}
}
}
}
FILTER p.edges[1].foo == "bar" AND
p.edges[2].foo == "bar" AND
p.edges[2].baz == "qux"
See the Traversal options for details.
Query logging
Introduced in: v3.12.2
A new feature for logging metadata of past AQL queries has been added.
You can optionally let ArangoDB store information such as run time, memory usage,
and failure reasons to the _queries
system collection in the _system
database
with a configurable sampling probability and retention period. This allows you
to analyze the metadata directly in the database system to debug query issues
and understand usage patterns.
See Query logging for details.
Bypass edge cache for graph operations
Introduced in: v3.12.2
The useCache
option is now supported for graph traversals and path searches.
You can set this option to false
to not make a large graph operation pollute
the edge cache.
FOR v, e, p IN 1..5 OUTBOUND "vertices/123" edges
OPTIONS { useCache: false }
...
Push limit into index optimization
Introduced in: v3.12.2
A new push-limit-into-index
optimizer rule has been added to better utilize
persistent
indexes over multiple fields when there are a subsequent SORT
and
LIMIT
operation. The following conditions need to be met for the rule to be
applied:
- The index must be a compound index
- The index condition must use the
IN
comparison operator - The attributes used for the
IN
comparison must be the ones used by the index - There must not be an outer loop and there must not be post-filtering
- The attributes to sort by must be the same ones as in index and in the same
order, and they must all be in the same direction (all
ASC
or allDESC
).
Under these circumstances, fetching all the data to sort it is unnecessary because it is already sorted in the index. This makes it possible to get results from the index in batches. This can greatly improve the performance of certain queries.
Example query:
FOR doc IN coll
FILTER doc.x IN ["foo", "bar"]
SORT doc.y
LIMIT 10
RETURN doc
With a persistent index over x
and y
but without the optimization, the
query explain output looks as follows:
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
9 IndexNode 500 - FOR doc IN coll /* persistent index scan, index only (projections: `y`) */ LET #5 = doc.`y` /* with late materialization */
6 SortNode 500 - SORT #5 ASC /* sorting strategy: constrained heap */
7 LimitNode 10 - LIMIT 0, 10
10 MaterializeNode 10 - MATERIALIZE doc INTO #4
8 ReturnNode 10 - RETURN #4
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
9 idx_1806008994935865344 persistent coll false false false 100.00 % [ `x`, `y` ] [ ] (doc.`x` IN [ "bar", "foo" ])
With the optimization, the LIMIT 10
is pushed into the index node with the
comment /* early reducing results */
:
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
9 IndexNode 500 - FOR doc IN coll /* persistent index scan, index only (projections: `y`) */ LET #5 = doc.`y` LIMIT 10 /* early reducing results */ /* with late materialization */
6 SortNode 500 - SORT #5 ASC /* sorting strategy: constrained heap */
7 LimitNode 10 - LIMIT 0, 10
10 MaterializeNode 10 - MATERIALIZE doc INTO #4
8 ReturnNode 10 - RETURN #4
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
9 idx_1806008994935865344 persistent coll false false false 100.00 % [ `x`, `y` ] [ ] (doc.`x` IN [ "bar", "foo" ])
Array and object destructuring
Introduced in: v3.12.2
Destructuring lets you assign array values and object attributes to one or
multiple variables with a single LET
operation and as part of regular FOR
loops. This can be convenient to extract a subset of values and name them in a
concise manner.
Array values are assigned by position and you can skip elements by leaving out variable names.
Object attributes are assigned by name but you can also map them to different variable names.
You can mix both array and object destructuring.
LET [x, y] = [1, 2, 3] // Assign 1 to variable x and 2 to y
LET [, y, z] = [1, 2, 3] // Assign 2 to variable y and 3 to z
// Assign "Luna Miller" to variable name and 39 to age
LET { name, age } = { vip: true, age: 39, name: "Luna Miller" }
// Assign the vip attribute value to variable status
LET { vip: status } = { vip: true, age: 39, name: "Luna Miller" }
// Assign 1 to variable x, 2 to y, and 3 to z
LET { obj: [x, [y, z]] } = { obj: [1, [2, 3]] }
// Iterate over array of objects and extract the firstName attribute
LET names = [ { firstName: "Luna"}, { firstName: "Sam" } ]
FOR { firstName } IN names
RETURN firstName
See Array destructuring and Object destructuring for details.
Improved utilization of sorting order for COLLECT
Introduced in: v3.12.3
The query optimizer now automatically recognizes additional cases that allow
using the faster sorted method for a COLLECT
operation. For example, the
following query previously created a query plan with two SORT
operations and
used the hash method for COLLECT
.
FOR doc IN coll
SORT doc.value DESC
COLLECT val = doc.value
RETURN val
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
2 EnumerateCollectionNode ✓ 36 - FOR doc IN coll /* full collection scan (projections: `value`) */ LET #4 = doc.`value`
4 SortNode ✓ 36 - SORT #4 DESC /* sorting strategy: standard */
6 CollectNode ✓ 28 - COLLECT val = #4 /* hash */
8 SortNode ✓ 28 - SORT val ASC /* sorting strategy: standard */
7 ReturnNode 28 - RETURN val
Now, the optimizer checks whether all grouping values are covered by the
user-requested SORT
, ignoring the direction, and doesn’t create an additional
SORT
node in that case. A sort in descending order can thus now be utilized
for grouping using the sorted method, and possibly even utilize an index.
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
2 EnumerateCollectionNode ✓ 36 - FOR doc IN coll /* full collection scan (projections: `value`) */ LET #5 = doc.`value`
4 SortNode ✓ 36 - SORT #5 DESC /* sorting strategy: standard */
6 CollectNode ✓ 28 - COLLECT val = #5 /* sorted */
7 ReturnNode 28 - RETURN val
Fast object enumeration with ENTRIES()
Introduced in: v3.12.3
A new optimization has been implemented to improve the efficiency of the
ENTRIES()
function in AQL.
The new replace-entries-with-object-iteration
optimizer rule can recognize a
query pattern like FOR obj IN source FOR [key, value] IN ENTRIES(obj) ...
and use a faster code path for iterating over the object that avoids copying a
lot of key/value pairs and storing intermediate results.
LET source = [ { a: 1, b: 2 }, { c: 3 } ]
FOR doc IN source // collection or array of objects
FOR [key, value] IN ENTRIES(doc)
RETURN CONCAT(key, value)
Without the optimization, the node with ID 6
uses a regular list iteration:
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
2 CalculationNode ✓ 1 - LET source = [ { "a" : 1, "b" : 2 }, { "c" : 3 } ] /* json expression */ /* const assignment */
4 EnumerateListNode ✓ 2 - FOR doc IN source /* list iteration */
5 CalculationNode ✓ 2 - LET #7 = ENTRIES(doc) /* simple expression */
6 EnumerateListNode ✓ 200 - FOR #4 IN #7 /* list iteration */
9 CalculationNode ✓ 200 - LET #8 = CONCAT(#4[0], #4[1]) /* simple expression */
10 ReturnNode 200 - RETURN #8
With the optimization applied, the faster object iteration is used:
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
2 CalculationNode ✓ 1 - LET source = [ { "a" : 1, "b" : 2 }, { "c" : 3 } ] /* json expression */ /* const assignment */
4 EnumerateListNode ✓ 2 - FOR doc IN source /* list iteration */
6 EnumerateListNode ✓ 200 - FOR [key, value] OF doc /* object iteration */
9 CalculationNode ✓ 200 - LET #8 = CONCAT(key, value) /* simple expression */
10 ReturnNode 200 - RETURN #8
Indexing
Multi-dimensional indexes
The previously experimental zkd
index type is now stable and has been renamed
to mdi
. Existing indexes keep the zkd
type.
Multi-dimensional indexes can now be declared as sparse
to exclude documents
from the index that do not have the defined attributes or are explicitly set to
null
values. If a value other than null
is set, it still needs to be numeric.
Multi-dimensional indexes now support storedValues
to cover queries for better
performance.
An additional mdi-prefixed
index variant has been added that lets you specify
additional attributes for the index to narrow down the search space using
equality checks. It can be used as a vertex-centric index for graph traversals
if created on an edge collection with the first attribute in prefixFields
set
to _from
or _to
.
See Multi-dimensional indexes for details.
Native strict ranges
Introduced in: v3.12.3
Multi-dimensional indexes no longer require post-filtering when using strict ranges like in the following query:
FOR d IN coll
FILTER 0 < d.x && d.x < 1
RETURN d.x
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
7 IndexNode ✓ 71 - FOR d IN coll /* mdi index scan, index scan + document lookup (filter projections: `x`) (projections: `x`) */ LET #3 = d.`x` FILTER ((d.`x` > 0) && (d.`x` < 1)) /* early pruning */
6 ReturnNode 71 - RETURN #3
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
7 idx_1812443690233233408 mdi coll false false false n/a [ `x`, `y` ] [ ] ((d.`x` >= 0) && (d.`x` <= 1))
Native support for strict ranges removes a potential bottleneck when working with large datasets.
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
7 IndexNode ✓ 71 - FOR d IN coll /* mdi index scan, index scan + document lookup (projections: `x`) */ LET #3 = d.`x`
6 ReturnNode 71 - RETURN #3
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
7 idx_1812443856099082240 mdi coll false false false n/a [ `x`, `y` ] [ ] ((d.`x` > 0) && (d.`x` < 1))
Extended utilization of sparse indexes
Introduced in: v3.12.3
The null
value is less than all other values in AQL. Therefore, range queries
without a lower bound need to include null
but sparse indexes do not include
null
values. However, if you explicitly exclude null
in range queries,
sparse indexes can be utilized after all. This was not previously supported for
multi-dimensional indexes. Even with a sparse mdi
index over the fields x
and y
and the exclusion of null
, the following example query cannot take
advantage of the index up to v3.12.2:
FOR d IN coll
FILTER d.x < 10 && d.x != null RETURN d
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
2 EnumerateCollectionNode ✓ 100 - FOR d IN coll /* full collection scan */ FILTER ((d.`x` < 10) && (d.`x` != null)) /* early pruning */
5 ReturnNode 100 - RETURN d
From v3.13.3 onward, such a query gets optimized to utilize the sparse
multi-dimensional index and the condition for excluding null
is removed from
the query plan because it is unnecessary – a sparse index contains values other
than null
only:
Execution plan:
Id NodeType Par Est. Comment
1 SingletonNode 1 * ROOT
6 IndexNode ✓ 71 - FOR d IN coll /* mdi index scan, index scan + document lookup */
5 ReturnNode 71 - RETURN d
Indexes used:
By Name Type Collection Unique Sparse Cache Selectivity Fields Stored values Ranges
6 idx_1812445012396343296 mdi coll false true false n/a [ x
, y
] [ ] (d.x
< 10)
Stored values can contain the _id
attribute
The usage of the _id
system attribute was previously disallowed for
persistent
indexes inside of storedValues
. This is now allowed in v3.12.
Note that it is still forbidden to use _id
as a top-level attribute or
sub-attribute in fields
of persistent indexes. On the other hand, inverted
indexes have been allowing to index and store the _id
system attribute.
Server options
Effective and available startup options
The new GET /_admin/options
and GET /_admin/options-description
HTTP API
endpoints allow you to return the effective configuration and the available
startup options of the queried arangod instance.
Previously, it was only possible to fetch the current configuration on
single servers and Coordinators using a JavaScript Transaction, and to list
the available startup options with --dump-options
.
See the HTTP interface for administration for details.
Protocol aliases for endpoints
You can now use http://
and https://
as aliases for tcp://
and ssl://
in the --server.endpoint
startup option of the server.
Adjustable Stream Transaction size
The previously fixed limit of 128 MiB for Stream Transactions
can now be configured with the new --transaction.streaming-max-transaction-size
startup option. The default value remains 128 MiB.
Transparent compression of requests and responses between ArangoDB servers and client tools
The following startup options have been added to all client tools (except the ArangoDB Starter) and can be used to enable transparent compression of the data that is sent between a client tool and an ArangoDB server:
--compress-transfer
--compress-request-threshold
If the --compress-transfer
option is set to true
, the client tool adds an
extra Accept-Encoding: deflate
HTTP header to all requests made to the server.
This allows the server to compress its responses before sending them back to the
client tool.
The client also transparently compresses its own requests to the server if the
size of the request body (in bytes) is at least the value of the
--compress-request-threshold
startup option. The default value is 0
, which
disables the compression of the request bodies in the client tool. To opt in to
sending compressed data, set the option to a value greater than 0
.
The client tool adds a Content-Encoding: deflate
HTTP header to the request
if the request body is compressed using the deflate compression algorithm.
The following options have been added to the ArangoDB server:
--http.compress-response-threshold
--http.handle-content-encoding-for-unauthenticated-requests
The value of the --http.compress-response-threshold
startup option specifies
the threshold value (in bytes) from which on response bodies are sent out
compressed by the server. The default value is 0
, which disables sending out
compressed response bodies. To enable compression, the option should be set to a
value greater than 0
. The selected value should be large enough to justify the
compression overhead. Regardless of the value of this option, the client has to
signal that it expects a compressed response body by sending an
Accept-Encoding: gzip
or Accept-Encoding: deflate
HTTP header with its request.
If that header is missing, no response compression is performed by the server.
If the --http.handle-content-encoding-for-unauthenticated-requests
startup option is set to true
, the ArangoDB server automatically decompresses
incoming HTTP requests with Content-Encodings: gzip
or
Content-Encoding: deflate
HTTP header even if the request is not authenticated.
If the option is set to false
, any unauthenticated request that has a
Content-Encoding
header set is rejected. This is the default setting.
As compression uses CPU cycles, it should be activated only when the network communication between the server and clients is slow and there is enough CPU capacity left for the extra compression/decompression work.
Furthermore, requests and responses should only be compressed when they exceed a certain minimum size, e.g. 250 bytes.
LZ4 compression for values in the in-memory edge cache
Introduced in: v3.11.2
LZ4 compression of edge index cache values allows to store more data in main memory than without compression, so the available memory can be used more efficiently. The compression is transparent and does not require any change to queries or applications. The compression can add CPU overhead for compressing values when storing them in the cache, and for decompressing values when fetching them from the cache.
The new startup option --cache.min-value-size-for-edge-compression
can be
used to set a threshold value size for compression edge index cache payload
values. The default value is 1GB
, which effectively turns compression
off. Setting the option to a lower value (i.e. 100
) turns on the
compression for any payloads whose size exceeds this value.
The new startup option --cache.acceleration-factor-for-edge-compression
can
be used to fine-tune the compression. The default value is 1
.
Higher values typically mean less compression but faster speeds.
The following new metrics can be used to determine the usefulness of compression:
rocksdb_cache_edge_inserts_effective_entries_size_total
: returns the total number of bytes of all entries that were stored in the in-memory edge cache, after compression was attempted/applied. This metric is populated regardless of whether compression is used or not.rocksdb_cache_edge_inserts_uncompressed_entries_size_total
: returns the total number of bytes of all entries that were ever stored in the in-memory edge cache, before compression was applied. This metric is populated regardless of whether compression is used or not.rocksdb_cache_edge_compression_ratio
: returns the effective compression ratio for all edge cache entries ever stored in the cache.
Note that these metrics are increased upon every insertion into the edge cache, but not decreased when data gets evicted from the cache.
Limit the number of databases in a deployment
Introduced in: v3.10.10, v3.11.2
The --database.max-databases
startup option allows you to limit the
number of databases that can exist in parallel in a deployment. You can use this
option to limit the resources used by database objects. If the option is used
and there are already as many databases as configured by this option, any
attempt to create an additional database fails with error
32
(ERROR_RESOURCE_LIMIT
). Additional databases can then only be created
if other databases are dropped first. The default value for this option is
unlimited, so an arbitrary amount of databases can be created.
--database.extended-names
enabled by default
The --database.extended-names
startup option is now enabled by default.
This allows you to use Unicode characters inside database names, collection names,
view names and index names by default, unless you explicitly turn off the
functionality.
Note that once a server in your deployment has been started with the flag set to
true
, it stores this setting permanently. Switching the startup option back to
false
raises a warning about the option change at startup, but it is not
blockig the startup.
Existing databases, collections, views and indexes with extended names can still
be used even with the option set back to false
, but no new database objects
with extended names can be created with the option disabled. This state is only
meant to facilitate downgrading or reverting the option change. When the option
is set to false
, all database objects with extended names that were created
in the meantime should be removed manually.
Cluster-internal connectivity checks
Introduced in: v3.11.5
This feature makes Coordinators and DB-Servers in a cluster periodically send check requests to each other, in order to see if all nodes can connect to each other. If a cluster-internal connection to another Coordinator or DB-Server cannot be established within 10 seconds, a warning is now logged.
The new --cluster.connectivity-check-interval
startup option can be used
to control the frequency of the connectivity check, in seconds.
If set to a value greater than zero, the initial connectivity check is
performed approximately 15 seconds after the instance start, and subsequent
connectivity checks are executed with the specified frequency.
If set to 0
, connectivity checks are disabled.
You can also use the following metrics to monitor and detect temporary or permanent connectivity issues:
arangodb_network_connectivity_failures_coordinators
: Number of failed connectivity check requests sent by this instance to Coordinators.arangodb_network_connectivity_failures_dbservers_total
: Number of failed connectivity check requests sent to DB-Servers.
Configurable maximum for queued log entries
Introduced in: v3.10.12, v3.11.5
The new --log.max-queued-entries
startup option lets you configure how many
log entries are queued in a background thread.
Log entries are pushed on a queue for asynchronous writing unless you enable the
--log.force-direct
startup option. If you use a slow log output (e.g. syslog),
the queue might grow and eventually overflow.
You can configure the upper bound of the queue with this option. If the queue is full, log entries are written synchronously until the queue has space again.
Configurable maximal size for cache entries
Introduced in: v3.12.1
A new --cache.max-cache-value-size
startup option has been added to limit the
maximum size of individual values stored in the in-memory cache. The cache is
used for edge indexes, persistent indexes with caching enabled, and collections
with document caches enabled.
The startup option defaults to 4 MiB, meaning that no individual values larger than this are stored in the in-memory cache. It avoids storing very large values in the cache for huge documents or for all the connections of super nodes, and thus leaves more memory for other purposes.
Configurable async prefetch limits
Introduced in: v3.12.1
While async prefetching is normally beneficial for query performance, the async prefetch operations may cause congestion in the ArangoDB scheduler and interfere with other operations. The amount of prefetch operations is limited and you adjust these limits with he following startup options:
--query.max-total-async-prefetch-slots
: The maximum total number of slots available for asynchronous prefetching, across all AQL queries. Default:256
--query.max-query-async-prefetch-slots
: The maximum per-query number of slots available for asynchronous prefetching inside any AQL query. Default:32
The total number of concurrent prefetch operations across all AQL queries can be limited using the first option, and the maximum number of prefetch operations in every single AQL query can be capped with the second option.
These options prevent that running a lot of AQL queries with async prefetching fully congests the scheduler queue, and also they prevent large AQL queries to use up all async prefetching capacity on their own.
New server scheduler type
Introduced in: v3.12.1
A new --server.scheduler
startup option has been added to let you select the
scheduler type. The scheduler currently used by ArangoDB has the value
supervised
. A new work-stealing scheduler is being implemented and can be
selected using the value threadpools
. This new scheduler is experimental and
should not be used in production.
Query logging options
Introduced in: v3.12.2
The following startup options related to the Query logging feature have been added:
--query.collection-logger-enabled
: Whether to enable the logging of metadata for past AQL queries--query.collection-logger-include-system-database
: Whether to log queries that run in the_system
database--query.collection-logger-probability
: The sampling probability for logging queries (in percent)--query.collection-logger-all-slow-queries
: Whether to always log slow queries regardless of whether they are selected for sampling or not--query.collection-logger-retention-time
: The retention period for entries in the_queries
system collection (in seconds)--query.collection-logger-cleanup-interval
: The interval for running the cleanup process for the retention configuration (in milliseconds)--query.collection-logger-push-interval
: How long to buffer query log entries in memory before they are actually written to the system collection (in milliseconds)--query.collection-logger-max-buffered-queries
: The number of query log entries to buffer in memory before they are flushed to the system collection, discarding additional query metadata if the logging thread cannot keep up
Miscellaneous changes
V8 and ICU library upgrades
The bundled V8 JavaScript engine has been upgraded from version 7.9.317 to 12.1.165. As part of this upgrade, the Unicode character handling library ICU has been upgraded as well, from version 64.2 to 73.1 (but only for JavaScript contexts, see Incompatible changes in ArangoDB 3.12).
Note that ArangoDB’s build of V8 has pointer compression disabled to allow for more than 4 GB of heap memory.
The V8 upgrade brings various language features to JavaScript contexts in ArangoDB, like arangosh and Foxx. These features are part of the ECMAScript specifications ES2020 through ES2024. The following list is non-exhaustive:
Optional chaining, like
obj.foo?.bar?.length
to easily access an object property or call a function but stop evaluating the expression as soon as the value isundefined
ornull
and returnundefined
instead of throwing an errorNullish coalescing operator, like
foo ?? bar
to evaluate to the value ofbar
iffoo
isnull
orundefined
, otherwise to the value offoo
Return the array element at the given index, allowing positive as well as negative integer values, like
[1,2,3].at(-1)
Copying versions of the array methods
reverse()
,sort()
, andsplice()
that perform in-place operations, and a copying version of the bracket notation for changing the value at a given index- Return a new array with the elements in reverse order, like
[1,2,3].toReversed()
- Return a new array with the elements sorted in ascending order, like
[2,3,1].toSorted()
- Return a new array with elements removed and optionally inserted at a given
index, like
[1,2,3,4].toSpliced(1,2,"new", "items")
- Return a new array with one element replaced at a given index, allowing
positive and negative integer values, like
[1,2,3,4].with(-2, "three")
- Return a new array with the elements in reverse order, like
Find array elements from the end with
findLast()
andfindLastIndex()
, like[1,2,3,4].findLast(v => v % 2 == 1)
Return a new string with all matches of a pattern replaced with a provided value, not requiring a regular expression, like
"foo bar foo".replaceAll("foo", "baz")
If the
matchAll()
method of a string is used with a regular expression that misses the globalg
flag, an error is thrownA new regular expression flag
d
to include capture group start and end indices, like/f(o+)/d.exec("foobar").indices[1]
A new regular expression flag
v
to enable the Unicode sets mode, like/^\p{RGI_Emoji}$/v.test("👨🏾⚕️")
or/[\p{Script_Extensions=Greek}--[α-γ]]/v.test('β')
A static method to check whether an object directly defines a property, like
Object.hasOwn({ foo: 42 }, "toString")
, supersedingObject.prototype.hasOwnProperty.call(…)
Object.groupBy()
andMap.groupBy()
to group the elements of an iterable according to the string values returned by a provided callback functionLogical assignment operators
&&=
,||=
,??=
Private properties that cannot be referenced outside of the class, like
class P { #privField = 42; #privMethod() { } }
Static initialization blocks in classes that run when the class itself is evaluated, like
class S { static { console.log("init block") } }
WeakRef
to hold a weak reference to another object, without preventing that object from getting garbage-collectedA
cause
property for Error instances to indicate the original cause of the errorExtended internationalization APIs. Examples:
let egyptLocale = new Intl.Locale("ar-EG") egyptLocale.numberingSystems // [ "arab" ] egyptLocale.calendars // [ "gregory", "coptic", "islamic", "islamic-civil", "islamic-tbla" ] egyptLocale.hourCycles // [ "hc12" ] egyptLocale.timeZones // [ "Africa/Cairo" ] egyptLocale.textInfo // { "direction": "rtl" } egyptLocale.weekInfo // { "firstDay": 6, "weekend" : [5, 6], "minimalDays": 1 } Intl.supportedValuesOf("collation"); // [ "compat", "emoji", "eor", "phonebk", ... ] Intl.supportedValuesOf("calendar"); // [ "buddhist", "chinese", "coptic", "dangi", ... ] // Other supported values: "currency", "numberingSystem", "timeZone", "unit" let germanLocale = new Intl.Locale("de") germanLocale.collations // [ "emoji", "eor", "phonebk" ] germanLocale.weekInfo // { "firstDay": 1, "weekend" : [6, 7], "minimalDays": 4 } let ukrainianCalendarNames = new Intl.DisplayNames(["uk"], { type: "calendar" }) ukrainianCalendarNames.of("buddhist") // "буддійський календар" let frenchDateTimeFieldNames = new Intl.DisplayNames(["fr"], { type: "dateTimeField" }) frenchDateTimeFieldNames.of("day") // "jour" let japaneseDialectLangNames = new Intl.DisplayNames(["ja"], { type: "language" }) let japaneseStandardLangNames = new Intl.DisplayNames(["ja"], { type: "language", languageDisplay: "standard" }) japaneseDialectLangNames.of('en-US') // "アメリカ英語" japaneseDialectLangNames.of('en-GB') // "イギリス英語" japaneseStandardLangNames.of('en-US') // "英語 (アメリカ合衆国)" japaneseStandardLangNames.of('en-GB') // "英語 (イギリス)" let americanDateTimeFormat = new Intl.DateTimeFormat("en-US", { timeZoneName: "longGeneric" }) americanDateTimeFormat.formatRange(new Date(0), new Date()) // e.g. with a German local time: // "1/1/1970, Central European Standard Time – 1/16/2024, Central European Time" let swedishCurrencyNames = new Intl.DisplayNames(["sv"], { type: "currency" }) swedishCurrencyNames.of("TZS") // "tanzanisk shilling" let americanNumberFormat = new Intl.NumberFormat("en-US", { style: "currency", currency: "EUR", maximumFractionDigits: 0 }) americanNumberFormat.formatRange(1.5, 10) // "€2 – €10" let welshPluralRules = new Intl.PluralRules("cy") welshPluralRules.selectRange(1, 3) // "few"
Active AQL query cursors metric
The arangodb_aql_cursors_active
metric has been added and shows the number
of active AQL query cursors.
AQL query cursors are created for queries that produce more results than
specified in the batchSize
query option (default value: 1000
). Such results
can be fetched incrementally by client operations in chunks.
As it is unclear if and when a client will fetch any remaining data from a
cursor, every cursor has a server-side timeout value (TTL) after which it is
considered inactive and garbage-collected.
Per-collection compaction in cluster
The PUT /_api/collection/{collection-name}/compact
endpoint of the HTTP API
can now be used to start the compaction for a specific collection in cluster
deployments. This feature was previously available for single servers only.
RocksDB .sst file partitioning (experimental)
The following experimental startup options for RockDB .sst file partitioning have been added:
--rocksdb.partition-files-for-documents
--rocksdb.partition-files-for-primary-index
--rocksdb.partition-files-for-edge-index
--rocksdb.partition-files-for-persistent-index
Enabling any of these options makes RocksDB’s compaction write the data for different collections/shards/indexes into different .sst files. Otherwise, the document data from different collections/shards/indexes can be mixed and written into the same .sst files.
When these options are enabled, the RocksDB compaction is more efficient since a lot of different collections/shards/indexes are written to in parallel. The disadvantage of enabling these options is that there can be more .sst files than when the option is turned off, and the disk space used by these .sst files can be higher. In particular, on deployments with many collections/shards/indexes this can lead to a very high number of .sst files, with the potential of outgrowing the maximum number of file descriptors the ArangoDB process can open. Thus, these options should only be enabled on deployments with a limited number of collections/shards/indexes.
ArangoSearch file descriptor metric
The following metric has been added:
arangodb_search_file_descriptors
: Current count of opened file descriptors for an ArangoSearch index.
More instant Hot Backups
Introduced in: v3.10.10, v3.11.3
Cluster deployments no longer wait for all in-progress transactions to get committed when a user requests a Hot Backup. The waiting could cause deadlocks and thus Hot Backups to fail, in particular in ArangoGraph. Now, Hot Backups are created immediately and commits have to wait until the backup process is done.
In-memory edge cache startup options and metrics
Introduced in: v3.11.4
The following startup options have been added:
--cache.max-spare-memory-usage
: the maximum memory usage for spare tables in the in-memory cache.--cache.high-water-multiplier
: controls the cache’s effective memory usage limit. The user-defined memory limit (i.e.--cache.size
) is multiplied with this value to create the effective memory limit, from which on the cache tries to free up memory by evicting the oldest entries. The default value is0.56
, matching the previously hardcoded 56% for the cache subsystem.You can increase the multiplier to make the cache subsystem use more memory, but this may overcommit memory because the cache memory reclamation procedure is asynchronous and can run in parallel to other tasks that insert new data. In case a deployment’s memory usage is already close to the maximum, increasing the multiplier can lead to out-of-memory (OOM) kills.
The following metrics have been added:
Label | Description |
---|---|
rocksdb_cache_edge_compressed_inserts_total | Total number of compressed inserts into the in-memory edge cache. |
rocksdb_cache_edge_empty_inserts_total | Total number of insertions into the in-memory edge cache for non-connected edges. |
rocksdb_cache_edge_inserts_total | Total number of insertions into the in-memory edge cache. |
Observability of in-memory cache subsystem
Introduced in: v3.10.11, v3.11.4
The following metrics have been added to improve the observability of the in-memory cache subsystem:
rocksdb_cache_free_memory_tasks_total
: Total number of free memory tasks that were scheduled by the in-memory edge cache subsystem. This metric will be increased whenever the cache subsystem schedules a task to free up memory in one of the managed in-memory caches. It is expected to see this metric rising when the cache subsystem hits its global memory budget.rocksdb_cache_free_memory_tasks_duration_total
: Total amount of time spent inside the free memory tasks of the in-memory cache subsystem. Free memory tasks are scheduled by the cache subsystem to free up memory in existing cache hash tables.rocksdb_cache_migrate_tasks_total
: Total number of migrate tasks that were scheduled by the in-memory edge cache subsystem. This metric will be increased whenever the cache subsystem schedules a task to migrate an existing cache hash table to a bigger or smaller size.rocksdb_cache_migrate_tasks_duration_total
: Total amount of time spent inside the migrate tasks of the in-memory cache subsystem. Migrate tasks are scheduled by the cache subsystem to migrate existing cache hash tables to a bigger or smaller table.
Detached scheduler threads
Introduced in: v3.10.13, v3.11.5
A scheduler thread now has the capability to detach itself from the scheduler if it observes the need to perform a potentially long running task, like waiting for a lock. This allows a new scheduler thread to be started and prevents scenarios where all threads are blocked waiting for a lock, which has previously led to deadlock situations. From v3.12.1 onward, coroutines are used instead of detaching threads as the underlying mechanism.
Threads waiting for more than 1 second on a collection lock will detach themselves.
The following startup option has been added:
--server.max-number-detached-threads
: The maximum number of detached scheduler threads. Note that this startup option is deprecated from v3.12.1 onward because a different mechanism than detaching is used, obsoleting the option and no longer having an effect.
The following metric as been added:
arangodb_scheduler_num_detached_threads
: The number of worker threads currently started and detached from the scheduler.
Monitoring per collection/database/user
Introduced in: v3.10.13, v3.11.7
The following metrics have been introduced to track per-shard requests on DB-Servers:
arangodb_collection_leader_reads_total
: The number of read requests on leaders, per shard, and optionally also split by user.arangodb_collection_leader_writes_total
: The number of write requests on leaders, per shard, and optionally also split by user.arangodb_collection_requests_bytes_read_total
: The number of bytes read in read requests on leaders.arangodb_collection_requests_bytes_written_total
: The number of bytes written in write requests on leaders and followers.
To opt into these metrics, you can use the new --server.export-shard-usage-metrics
startup option. It can be set to one of the following values on DB-Servers:
disabled
: No shard usage metrics are recorded nor exported. This is the default value.enabled-per-shard
: This makes DB-Servers collect per-shard usage metrics.enabled-per-shard-per-user
: This makes DB-Servers collect per-shard and per-user metrics. This is more granular thanenabled-per-shard
but can produce a lot of metrics.
Whenever a shard is accessed in read or write mode by one of the following operations, the metrics are populated dynamically, either with a per-user label or not, depending on the above setting. The metrics are retained in memory on DB-Servers. Removing databases, collections, or users that are already included in the metrics won’t remove the metrics until the DB-Server is restarted.
The following operations increase the metrics:
- AQL queries: an AQL query increases the read or write counters exactly once for each involved shard. For shards that are accessed in read/write mode, only the write counter is increased.
- Single-document insert, update, replace, and remove operations: for each such operation, the write counter is increased once for the affected shard.
- Multi-document insert, update, replace, and remove operations: for each such operation, the write counter is increased once for each shard that is affected by the operation. Note that this includes collection truncate operations.
- Single and multi-document read operations: for each such operation, the read counter is increased once for each shard that is affected by the operation.
The metrics are increased when any of the above operations start, and they are not decreased should an operation abort or if an operation does not lead to any actual reads or writes.
As there can be many of these dynamic metrics based on the number of shards
and/or users in the deployment, these metrics are turned off by default.
When turned on, the metrics are exposed only via the new
GET /_admin/usage-metrics
endpoint. They are not exposed via the existing
metrics GET /_admin/metrics
endpoint.
Note that internal operations, such as internal queries executed for statistics gathering, internal garbage collection, and TTL index cleanup are not counted in these metrics. Additionally, all requests that are using the superuser JWT for authentication and that do not have a specific user set are not counted.
Enabling these metrics can likely result in a small latency overhead of a few percent for write operations. The exact overhead depends on several factors, such as the type of operation (single or multi-document operation), replication factor, network latency, etc.
Compression for cluster-internal traffic
The following startup options have been added to optionally compress relevant cluster-internal traffic:
--network.compression-method
: The compression method used for cluster-internal requests.--network.compress-request-threshold
: The HTTP request body size from which on cluster-internal requests are transparently compressed.
If the --network.compression-method
startup option is set to none
(default), then no
compression is performed. To enable compression for cluster-internal requests,
you can set this option to either deflate
, gzip
, lz4
, or auto
.
The deflate
and gzip
compression methods are general purpose but can
have significant CPU overhead for performing the compression work.
The lz4
compression method compresses slightly worse but has a lot lower
CPU overhead for performing the compression.
The auto
compression method uses deflate
by default and lz4
for
requests that have a size that is at least 3 times the configured threshold
size.
The compression method only matters if --network.compress-request-threshold
is set to a value greater than zero. This option configures a threshold value
from which on the outgoing requests will be compressed. If the threshold is
set to a value of 0, then no compression is performed. If the threshold
is set to a value greater than 0, then the size of the request body is
compared against the threshold value, and compression happens if the
uncompressed request body size exceeds the threshold value.
The threshold can thus be used to avoid futile compression attempts for too
small requests.
Compression for all Agency traffic is disabled regardless of the settings of these options.
Read-only shards metric
Introduced in: v3.12.1
The following cluster health metric has been added:
Label | Description |
---|---|
arangodb_vocbase_shards_read_only_by_write_concern | Number of shards that are read-only due to an undercut of the write concern. |
Log queue overwhelm metric
Introduced in: v3.12.1
The following metric has been added, indicating whether the log queue is overwhelmed:
Label | Description |
---|---|
arangodb_logger_messages_dropped_total | Total number of dropped log messages. |
The related startup option for controlling the size of the log queue has a
default of 16384
instead of 10000
now.
Scheduler dequeue time metrics
Introduced in: v3.12.1
The following scheduler metrics have been added, reporting how long it takes to pick items from different job queues.
Label | Description |
---|---|
arangodb_scheduler_high_prio_dequeue_hist | Time required to take an item from the high priority queue. |
arangodb_scheduler_medium_prio_dequeue_hist | Time required to take an item from the medium priority queue. |
arangodb_scheduler_low_prio_dequeue_hist | Time required to take an item from the low priority queue. |
arangodb_scheduler_maintenance_prio_dequeue_hist | Time required to take an item from the maintenance priority queue. |
Option to skip fast lock round for Stream Transactions
Introduced in: v3.12.1
A skipFastLockRound
option has been added to let you disable the fast lock
round for Stream Transactions. The option defaults to false
so that
fast locking is tried.
Disabling the fast lock round is not necessary unless there are many concurrent Stream Transactions queued that all try to lock the same collection exclusively. In this case, the fast locking is subpar because exclusive locks will prevent it from succeeding. Skipping the fast locking makes each actual locking operation take longer than with fast locking but it guarantees a deterministic locking order. This avoids deadlocking and retrying which can occur with the fast locking. Overall, skipping the fast lock round can be faster in this scenario.
The fast lock round should not be skipped for read-only Stream Transactions as it degrades performance if there are no concurrent transactions that use exclusive locks on the same collection.
See the JavaScript API and the HTTP API for details.
Individual log levels per log output
Introduced in: v3.12.2
You can now configure the log level for each log topic per log output. You can use this feature to log verbosely to a file but print less information in the command-line, for instance.
The repeatable --log.level
startup option lets you set the log levels for
log topics as before. You can now additionally specify the levels for individual
topics in --log.output
by appending a semicolon and a comma-separated mapping
of log topics and levels after the destination to override the --log.level
configuration:
--log.level memory=warning
--log.output "file:///path/to/file;queries=trace,requests=info"
--log.output "-;all=error"
This sets the log level to warning
for the memory
topic, which applies to
all outputs unless overridden. The first output is a file with verbose trace
logging for the queries
topic, info
-level logging for requests
,
warning
-level logging for memory
, and default levels for all other topics.
The second output is to the standard output (command-line), using the all
pseudo-topic to set the log levels to error
for all topics.
Furthermore, the HTTP API has been extended to let you query and set the log levels for individual outputs at runtime.
Client tools
Protocol aliases for endpoints
You can now use http://
and https://
as aliases for tcp://
and ssl://
in the --server.endpoint
startup option with all client tools.
arangodump
--ignore-collection
startup option
arangodump now supports a --ignore-collection
startup option that you can
specify multiple times to exclude the specified collections from a dump.
It cannot be used together with the existing --collection
option for specifying
collections to include.
Improved dump performance and size
arangodump has extended parallelization capabilities to work not only at the collection level, but also at the shard level. In combination with the newly added support for the VelocyPack format that ArangoDB uses internally, database dumps can now be created and restored more quickly and occupy less disk space. This major performance boost makes dumps and restores up to several times faster, which is extremely useful when dealing with large shards.
Whether the new parallel dump variant is used is controlled by the newly added
--use-parallel-dump
startup option. The default value istrue
.To achieve the best dump performance and the smallest data dumps in terms of size, you can additionally use the
--dump-vpack
option. The resulting dump data is then stored in the more compact but binary VelocyPack format instead of the text-based JSON format. The output file size can be less even compared to compressed JSON. It can also lead to faster dumps because there is less data to transfer and no conversion from the server-internal format (VelocyPack) to JSON is needed. Note, however, that this option is experimental and disabled by default.Optionally, you can make arangodump write multiple output files per collection/shard. The file splitting allows for better parallelization when writing the results to disk, which in case of non-split files must be serialized. You can enable it by setting the
--split-files
option totrue
. This option is disabled by default because dumps created with this option enabled cannot be restored into previous versions of ArangoDB.You can enable the new
--compress-transfer
startup option for compressing the dump data on the server for a faster transfer. This is helpful especially if the network is slow or its capacity is maxed out. The data is decompressed on the client side and recompressed if you enable the--compress-output
option.
Server-side resource usage limits and metrics
The following arangod
startup options can be used to limit
the resource usage of parallel arangodump invocations:
--dump.max-memory-usage
: Maximum memory usage (in bytes) to be used by the server-side parts of all ongoing arangodump invocations. This option can be used to limit the amount of memory for prefetching and keeping results on the server side when arangodump is invoked with the--parallel-dump
option. It does not have an effect for arangodump invocations that did not use the--parallel-dump
option. Note that the memory usage limit is not exact and that it can be slightly exceeded in some situations to guarantee progress.- -
-dump.max-docs-per-batch
: Maximum number of documents per batch that can be used in a dump. If an arangodump invocation requests higher values than configured here, the value is automatically capped to this value. Will only be followed for arangodump invocations that use the--parallel-dump
option. --dump.max-batch-size
: Maximum batch size value (in bytes) that can be used in a dump. If an arangodump invocation requests larger batch sizes than configured here, the actual batch sizes is capped to this value. Will only be followed for arangodump invocations that use the --parallel-dump
option.--dump.max-parallelism
: Maximum parallelism (number of server-side threads) that can be used in a dump. If an arangodump invocation requests a higher number of prefetch threads than configured here, the actual number of server-side prefetch threads is capped to this value. Will only be followed for arangodump invocations that use the--parallel-dump
option.
The following metrics have been added to observe the behavior of parallel arangodump operations on the server:
arangodb_dump_memory_usage
: Current memory usage of all ongoing arangodump operations on the server.arangodb_dump_ongoing
: Number of currently ongoing arangodump operations on the server.arangodb_dump_threads_blocked_total
: Number of times a server-side dump thread was blocked because it honored the server-side memory limit for dumps.
Automatic retries
Introduced in: v3.12.1
arangodump retries dump requests to the server in more cases: read, write and connection errors. This makes the creation of dumps more likely to succeed despite any temporary errors that can occur.
arangorestore
The following startup option has been added that allows arangorestore to override
the writeConcern
value specified in a database dump when creating new
collections:
--write-concern
: Override thewriteConcern
value for collections. Can be specified multiple times, e.g.--write-concern 2 --write-concern myCollection=3
to set the write concern for the collection calledmyCollection
to 3 and for all other collections to 2.
arangoimport
Maximum number of import errors
The following startup option has been added to arangoimport:
--max-errors
: The maximum number of errors after which the import is stopped. The default value is20
.
You can use this option to limit the amount of errors displayed by arangoimport, and to abort the import after this value has been reached.
Automatic file format detection
The default value for the --type
startup option has been changed from json
to auto
. arangoimport now automatically detects the type of the import file
based on the file extension.
The following file extensions are automatically detected:
.json
.jsonl
.csv
.tsv
If the file extension doesn’t correspond to any of the mentioned types, the
import defaults to the json
format.
Transparent compression of requests and responses
Startup options to enable transparent compression of the data that is sent between a client tool and the ArangoDB server have been added. See the Server options section above that includes a description of the added client tool options.
Internal changes
Upgraded bundled library versions
For ArangoDB 3.12, the bundled version of rclone is 1.65.2. Check if your rclone configuration files require changes.
The bundled version of the OpenSSL library has been upgraded to 3.2.1.
From version 3.11.10 onward, ArangoDB uses the glibc C standard library implementation with an LGPL-3.0 license instead of libmusl. Notably, it features string functions that are better optimized for common CPUs.