Pre-1.0 hoardDB is pre-1.0. Expect breaking changes.

Limits

Every size and count that hoardDB enforces, with its default, how to change it and what you see when you reach it. This page is the numbers; Limitations is the list of things hoardDB does not do, and explains the reasoning behind several of these.

A limit that an operator can change is a setting of one node. A write that reaches a replica is not checked against the replica’s own value, so keep these settings the same on every node of a cluster.

How big can one thing be

WhatDefaultYou can setSettingWhen you reach it
A document24 MiB (25,165,824 bytes)4 KiB to 30 MiB (4,096 to 31,457,280 bytes)HOARDDB_MAX_DOCUMENT_BYTES, storage.max_document_bytesThe write is refused; an update that would grow a document past it is refused and leaves the document unchanged
A blob16 MiB (16,777,216 bytes)1 byte to 31.9 MiB (33,488,896 bytes)HOARDDB_MAX_BLOB_BYTES, storage.max_blob_bytesThe write is refused
One protocol frame32 MiB (33,554,432 bytes)fixednoneThe client library refuses the request before sending it, with an error that names the 32 MiB limit and says to split it, and the connection stays usable. A client that sends one anyway gets an INVALID_ARGUMENT reply that names the limit; the server discards the body and keeps the connection, unless the frame is over 64 MiB or the connection has not logged in, when it replies and closes
One request sent before the connection has logged in64 KiB (65,536 bytes)fixednoneThe server replies INVALID_ARGUMENT, naming the limit and saying to send AUTH first and wait for AUTH_OK, and closes the connection. AUTH and PING, the only requests served before login, are far smaller
What one query may hold in memory16 MiB1 MiB to 24 MiB (a smaller value is raised to 1 MiB, with a warning)HOARDDB_QUERY_MAX_RESULT_BYTES, query.max_result_bytesThe query is refused with RESULT_TOO_LARGE
One atomic batch15% of the memtable (20 MB at 128 MiB)follows storage.memtable_mbHOARDDB_MEMTABLE_MBThe whole batch is refused and nothing of it is written
One batch (BATCH, HQL batch { ... })1,000 items and 16 MiBfixednoneThe batch is refused whole with BATCH_TOO_LARGE and nothing in it runs; the message says how to split it (for 1,204 items: items 1 to 1,000, then 1,001 to 1,204). One item over 16 MiB by itself is refused with INVALID_ARGUMENT: send it on its own
The answers of one batchthe smaller of query.max_result_bytes and 16 MiBfollows query.max_result_bytesHOARDDB_QUERY_MAX_RESULT_BYTESAnswers are counted in batch order. A read whose answer would pass it is answered RESULT_TOO_LARGE and changed nothing; an update past it is applied and answered without its document (doc_omitted: true). Every write’s outcome is always answered
One statement typed in the CLI64 KiB per linefixednoneCLI error: read input: bufio.Scanner: token too long
How deep a document nests100 levels (the document is level 1; each embedded document or array inside it adds one)fixednoneThe request is refused with INVALID_ARGUMENT and nothing is written; an update whose result would nest deeper is refused and leaves the document unchanged
One $regex pattern in a filter4,096 bytes of pattern; 512 steps once repeats such as a{100} are expanded; 2,048 steps for all the patterns of one filterfixednoneThe query is refused with INVALID_ARGUMENT, naming the limit, before any document is read
The regex work on one document1,500,000 steps times bytes scanned (about 11 ms), shared by every $regex of the filter that document meetsfixednoneThe query is refused with INVALID_ARGUMENT, naming the field, its length and the fix. It happens at the first document whose field is too long for the pattern, so documents before it were read
The filters inside one $and or $or64 filters, nested at most 8 levels deepfixednoneThe query is refused with INVALID_ARGUMENT, naming the limit
The values in one $in or $nin1,000 valuesfixednoneThe query is refused with INVALID_ARGUMENT, naming the limit
The clauses of one whole filter1,000fixednoneThe query is refused with INVALID_ARGUMENT, naming the limit
A database name, or a bucket name255 bytes eachfixednoneThe create is refused before anything is written, with an error that names the limit
The name of a hash bucket created with placement: spread (opt-in; whole is the default)250 bytesfixednoneThe create is refused, naming the limit and the fix: a shorter name, or placement: whole, which takes the 255 bytes above
Free disk a restore needs, per buckettwice the bucket’s dump bytes; three times for a LIFO bucket, which waits in a spill file in the data directory until the restore finishes0 or less means the defaultHOARDDB_RESTORE_DISK_HEADROOM_MULTIPLIER (the spill file’s share is fixed)The bucket’s restore is refused before it writes anything, naming the bytes needed, the bytes free and the setting
A bucket copied in from another node (a join, a rebalance, migrate)half of the receiving node’s memory limitfixednoneThe receiving node refuses the copy before it fetches any of it, naming the bucket and the fix (give that node more memory, restart it, and run migrate <database> <bucket> on it); nodes that already hold the bucket are unaffected
A bucket’s size, or the documents in itno limitThe disk is the bound

Why a filter has limits. A filter runs once for every document a scan, search, count or distinct examines, so a filter that looks small can cost a lot of work in a large bucket. The limits keep that work in proportion to the size of the filter. A filter that is too big on its own (a pattern, a list or a nesting past its limit) is refused before any document is read. A $regex is also compiled once per query, not once per document, and what it costs a document is its steps times the bytes of the field it scans: a long field can make an ordinary pattern too costly, so that refusal comes at the first document whose field is too long, not before the query starts. A case-sensitive pattern that is only a literal anchored at the start (^foo, or ^foo$) is a prefix comparison and costs one step per byte, so it runs over fields of up to about 1.4 MB. A literal that is not anchored at the start (foo, foo$) is charged like any other pattern: Go checks each place it might occur, which on repetitive text costs about its length times the field’s. An email shape or a list of words in (?i) form is a few dozen steps and runs over fields of tens of thousands of bytes, (?i)foo is charged as a program and not as a literal, a pattern of 100 steps runs over up to 15,000 bytes, and one of 512 steps, up to 2,900. Bytes are counted, not characters. Each refusal names the limit and what to do instead, for example a $or of 200 filters on one field becomes an $in, or two queries; for a long field, use a shorter pattern, or narrow the query with an indexed field first.

Why a name has a limit. A node keeps one record per bucket, under a key made of the database name and the bucket name, and its store refuses a key over 65,000 bytes. Replication keeps further records under the same names. 255 bytes each, counted in UTF-8 bytes (so fewer characters if a name uses non-Latin letters), is the limit you already know from file names, and it leaves those records plenty of room. If you want to keep a long identifier, store it as a field of a document and name the bucket by a short id.

Why the document and blob limits are not 32 MiB. Everything a client sends, and the copy a node sends to a replica, travels in one frame of at most 32 MiB, and the frame also carries the key, the bucket name and the request’s envelope. The ceilings leave room for that: a document tops out at 30 MiB, and a blob at 32 MiB less 64 KiB. A value outside the range you can set stops the server from starting, with a message naming the setting and the range; it is never quietly lowered.

What the refusals say. They state the size you sent, the limit this server is running with, the setting that changes it and what to do instead:

Blob "videos/intro.mp4" is 5.0 GiB; this server's limit is 16 MiB
(storage.max_blob_bytes, maximum 31.9 MiB). For larger files, keep the file on
your own storage and store a pointer document — see "Large file stores" in the
docs.
<label> is <size>; this server's limit is <limit> per document
(storage.max_document_bytes, maximum 30 MiB).

The CLI refuses a blob larger than the 31.9 MiB ceiling before it sends any of it, since no server would accept it.

What to do instead of raising a limit.

  • A list that keeps growing belongs in a nested store field, whose elements are stored separately and do not count towards the parent document.
  • A file that is large belongs on storage you run, with a pointer document in hoardDB. Large file stores is the pattern.
  • A bulk load that is refused for its size is a bulk load that should be split: hoarddb ingest halves a refused batch and retries, so a file of any size loads. See Load a file.

How many buckets

A bucket is a key range in the one store a node keeps for all its buckets. It costs no file, no file descriptor, no memory mapping and no goroutine of its own, and it adds one small entry to the node’s in-memory catalog. Adding a bucket is adding keys, not opening anything.

The figure to plan around is thousands of buckets per node. hoardDB keeps data with what it describes, so a node can hold many small buckets, and it does not publish a larger number until it has measured one on this layout. Higher counts may work on your hardware; they are not a promise. What a bucket costs, and how to see how many a node serves, is in Is this node full?.

hoardDB keeps no metric per bucket. A series per bucket is not viable at these counts, so the metrics are totals for the node, and per-bucket detail is a command you run (describe bucket, stats).

The memtable

The memtable is the write buffer in front of the node’s one store. It is a per-node setting: every bucket shares it, so it does not grow with the number of buckets.

A new node picks its size once, from the memory it is allowed (its container or cgroup limit, or the machine’s RAM), and keeps it on every restart:

Memory limitMemtable chosenLargest atomic batch (15%)Memory a heavy write burst can reach
below 3.5 GiB32 MiBabout 5 MB (5,033,164 bytes)about 460 MB
3.5 GiB to below 7 GiB64 MiBabout 10 MB (10,066,329 bytes)about 725 MB
7 GiB and above128 MiBabout 20 MB (20,132,659 bytes)about 1.25 GB

The automatic choice never goes above 128 MiB. The last column is a prediction for one client loading in batches, about 200 MB plus 8.2 MB for every MiB of memtable; many concurrent writers need more.

To choose it yourself, set storage.memtable_mb (HOARDDB_MEMTABLE_MB) to a value from 32 to 1024 MiB. Sizes of 256 MiB and above are only ever set this way. A value outside the range stops the server at start:

storage.memtable_mb is 8, below the minimum of 32. The store's largest atomic
batch is 15% of the memtable, and under 32 MiB it drops below 5 MB, which the
ingest batch budget and batch splitting assume. Set storage.memtable_mb (or
HOARDDB_MEMTABLE_MB) to a value from 32 to 1024, or remove it to run at the size
this node's store chose at its first start

A bigger memtable stalls writes less and holds up to three of them in memory, each needing about 1.3 times its size, so 1024 MiB is already about 3.9 GB. It is fixed at start. A node that crashed at a larger size cannot be opened at a smaller one until it has been started once at the old size and stopped cleanly. Configuration shows how to read the size a node chose, and why.

Connections and requests

WhatDefaultRangeSetting
Concurrent client connections per node1,000HOARDDB_MAX_CONNS
Concurrent connections from one address, counted from the moment a connection is accepted, before TLS640 turns the cap offHOARDDB_TCP_MAX_CONNS_PER_IP
Connections kept for other nodes64HOARDDB_MAX_PEER_CONNS
Idle time before a connection is closed, counted from its last answer with nothing in flight300 secondsHOARDDB_TCP_IDLE_TIMEOUT
Time to read one frame30 secondsHOARDDB_TCP_FRAME_TIMEOUT
Requests a client may have in flight on one connection41 to 64HOARDDB_TCP_PIPELINE_FRAMES
Bytes of those requests1 MiB1 MiB to 16 MiB (a smaller value is raised to 1 MiB, with a warning)HOARDDB_TCP_PIPELINE_BYTES
Queries running at oncetwice the CPU count4 to 256HOARDDB_QUERY_CONCURRENCY
Failed logins in a row before an address is refused; also how many logins from one address are checked at once200 turns the limit offHOARDDB_TCP_AUTH_MAX_FAILS
Logins from one address waiting for those being checkedfour times HOARDDB_TCP_AUTH_MAX_FAILS (80), each for up to 5 secondsfixedfollows HOARDDB_TCP_AUTH_MAX_FAILS
Shortest user password, checked by the CLI on create user and alter user ... password12 characters1 to 1024HOARDDB_PASSWORD_MIN_LENGTH

The client budget (HOARDDB_MAX_CONNS) and the node-to-node budget are separate, so a flood of clients cannot starve replication. A connection over a cap is answered with OVERLOADED, naming the cap and its setting, and closed. While 64 such refusals are already in progress on a port, a further refused connection is closed without the message: retry after a short wait. A connection carries one request at a time unless the client reads ahead within the window above; a client that wants concurrency opens another connection. A query past the concurrency limit waits; it is not refused.

A login still being checked holds one of the HOARDDB_TCP_AUTH_MAX_FAILS places for its address until its password is checked, because it may yet fail. A login that finds every place held this way waits for one to come free and is then let in, so a burst of correct logins larger than the limit (an application starting a big connection pool, a load generator) succeeds, a little slower. A login that waits more than 5 seconds, or arrives while 80 others from its address already wait, is answered RATE_LIMITED with “too many logins from this address are being checked at once”, and its connection is closed. Its password was not checked and it does not count as a failed login: reconnect and retry, or raise HOARDDB_TCP_AUTH_MAX_FAILS. Raising it also lets more wrong passwords from one address be tried before the address is refused.

A value outside a range with a stated clamp is brought to the nearest end of it when the server loads its configuration. The sizes in the first table are the other kind: outside their range, the server refuses to start.

Checking a limit against your data

Before you rely on a limit, try it at its edge on your own data. The sizes and counts above are what the build enforces, and they were read from its configuration, not measured on your workload: your hardware, your kernel’s file-descriptor and memory-mapping limits, and the shape of your documents decide where a node becomes slow before any of them is reached. Is this node full? lists the resources that bind first and what to do about each.

Source: docs/user/limits.md in the repository.