A bunch of smaller follow-up tasks to #4381. * storage/disk_test: check invalid patches with wildcard partition, too * docs/disk: add caveat re: bundles loaded into memory * storage/disk: auto-manage /system partitions If these are found in the user-provided partitions, we'll error out. * storage/disk: pretty-print partitions with "*" instead of %2A * storage/disk: respect wildcard-replacement in partition validation It is now allowed to replace a partition like /foo/bar by /foo/* also if multiple wildcards are used. Caveats: You cannot add a wildcard partition like /*/*, since it would overlap the managed "/system/*" partition. When attempting to go back from /foo/* to /foo/bar, an error is raised _unconditionally_ -- we could check the existing data, but currently don't. * storage/disk: check prefix when adding wildcard partitions The previously done check would have falsely returned that there is no problem when adding a wildcard partition: lookup of "/foo/*" with '*' not interpreted as a wildcard, but as a string, would yield a not-found, even if there was any data under /foo/. Now, we'll check the prefix-until-wildcard. It's more cautious than theoretically necessary, but safe. Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
6.8 KiB
title, kind, weight
| title | kind | weight |
|---|---|---|
| Disk Storage | misc | 10 |
This page outlines configuration options relevant to using the disk storage feature of OPA. Configuration options are to be found in the configuration docs.
{{< info >}} The persistent disk storage enables OPA to work with data that does not fit into the memory resources granted to the OPA server. It is not supposed to be used as the primary source of truth for that data.
The on-disk storage should be considered ephemeral: you need to secure the means to restore that data. Backup and restore, or repair procedures for data corruption are not provided at this time. {{< /info >}}
While it's possible to load data larger than the allotted memory resources into OPA using disk storage, there are limitations to be aware of:
{{< danger >}} Bundles are loaded into memory entirely even when disk storage is used: the decompressed, parsed bundle content is then inserted into the disk store.
It is planned to fix this limitation in the future. {{< /danger >}}
Partitions
Partitions determine how the JSON data is split up when stored in the underlying key-value store. For example, this table shows how an example document would be stored given different configured partitions:
{
"users": {
"alice": { "roles": ["admin"] },
"bob": { "roles": ["viewer"] }
}
}
| Partitions | Keys | Values |
|---|---|---|
| (1) none | /users |
{"alice": {"roles": ["admin"]}, "bob": {"roles": ["viewer"]}}} |
| --- | --- | --- |
(2) /users |
/users/alice |
{"roles": ["admin"]} |
/users/bob |
{"roles": ["viewer"]} |
|
| --- | --- | --- |
(3) /users/* |
/users/alice/roles |
["admin"] |
/users/bob/roles |
["viewer"] |
Partitioning has consequences on performance: in the example above, the number of keys to retrieve from the database (and the amount of data of its values) varies.
| Query | Partitions | Number of keys read |
|---|---|---|
data.users |
(1) | 1 |
| (2) | 2 | |
| (3) | 2 | |
| --- | --- | --- |
data.users.alice |
(1) | 1 with bob data thrown away |
| (2) | 2 | |
| (3) | 2 |
For example, retrieving the full extent of data.users from the disk store
will require a single key fetch with the partitions of (1).
With (2), the storage engine will fetch two keys and their values.
Retrieving a single user's data, e.g. data.users.alice, will require
reading a single key and all the users data with (1); but throw away most
of it: all the data not belonging to alice.
There is no one-size-fits-all setting for partitions: good settings depend on the actual usage, and that comes down to the policies that are used with OPA. Commonly, you would optimize the partition settings for those queries that are performance critical.
To figure out suboptimal partitioning, please have a look at the exposed metrics.
OPA stores some internal values (such as bundle metadata) in the data store,
under /system. Partitions for that part of the data store are managed by
OPA, and providing any overlapping partitions in the config will raise an
error.
Metrics
Using the REST API, you can include the ?metrics query string
to gain insights into the disk storage access related to a certain OPA query.
$ curl 'http://localhost:8181/v1/data/tenants/acme1/bindings/user1?metrics' | opa eval -I 'input.metrics' -fpretty
{
"counter_disk_read_bytes": 339,
"counter_disk_read_keys": 3,
"counter_server_query_cache_hit": 1,
"timer_disk_read_ns": 40736,
"timer_rego_external_resolve_ns": 251,
"timer_rego_input_parse_ns": 656,
"timer_rego_query_eval_ns": 66616,
"timer_server_handler_ns": 117539
}
The timer_disk_*_ns timers give an indication about how much time
was spent with the different disk operations.
Available timers are
timer_disk_read_nstimer_disk_write_nstimer_disk_commit_ns
Also note the counter_disk_* counters in the metrics:
counter_disk_read_keys: number of keys retrievedcounter_disk_written_keys: number of keys writtencounter_disk_deleted_keys: number of keys deletedcounter_disk_read_bytes: bytes retrieved
Suboptimal partition settings can be spotted when the amount of keys and bytes retrieved for a query is unproportional to the actual data returned: the query likely had to retrieve a giant JSON object, and most of it was thrown away.
Debug Logging
Pass --log-level debug to opa run to see all the underlying storage
engine's logs.
When debug logging is enabled, the service will output some statistics about the configured disk partitions and their key sizes.
[DEBUG] partition /tenants/acme3/bindings (pattern /tenants/*/bindings): key count: 10000 (estimated size 598890 bytes)
[DEBUG] partition /tenants/acme4/bindings (pattern /tenants/*/bindings): key count: 10000 (estimated size 598890 bytes)
[DEBUG] partition /tenants/acme8/bindings (pattern /tenants/*/bindings): key count: 10000 (estimated size 598890 bytes)
[DEBUG] partition /tenants/acme9/bindings (pattern /tenants/*/bindings): key count: 10000 (estimated size 598890 bytes)
[DEBUG] partition /tenants/acme0/bindings (pattern /tenants/*/bindings): key count: 10000 (estimated size 598890 bytes)
[DEBUG] partition /tenants/acme2/bindings (pattern /tenants/*/bindings): key count: 10000 (estimated size 598890 bytes)
[DEBUG] partition /tenants/acme6/bindings (pattern /tenants/*/bindings): key count: 10000 (estimated size 598890 bytes)
Note that this process will iterate over all database keys. It only happens on startup, when debug logging is enabled.
Fine-tuning Badger settings (superflags)
While partitioning should be the first thing to look into to tune the memory usage and performance of the on-disk storage engine, this configurable gives you the means to change many internal aspects of how Badger uses memory and disk storage.
{{< danger >}} To be used with care!
Any of the Badger settings used by OPA can be overridden using this feature. There is no validation happening for configurables set using this flag.
When the embedded Badger version changes, these configurables could change, too. {{< /danger >}}
The configurables correspond to Badger options that can be set on the library's Options struct.
The following configurables can not be overridden:
dirvaluedirdetectconflicts
Aside from conflict detection, Badger in OPA uses the default options you can find here.
Conflict detection is disabled because the locking scheme used within OPA does not allow for having multiple concurrent writes.
Example
storage:
disk:
directory: /tmp/disk
badger: nummemtables=1; numgoroutines=2; maxlevels=3