Document persistence options (#13)

* Document persistence options

* Fix typo

* Add note concerning mod security

* Update doc/persistence.adoc

Co-authored-by: benrob0329 <ben@innovationplex.com>

* Update doc/persistence.adoc

Co-authored-by: benrob0329 <ben@innovationplex.com>

* Updates

Co-authored-by: benrob0329 <ben@innovationplex.com>
This commit is contained in:
Lars Müller 2022-08-28 17:52:17 +02:00 committed by GitHub
parent 9795a16151
commit 13edf2b235
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

198
doc/persistence.adoc Normal file

@ -0,0 +1,198 @@
= Persistence
include::../include/config.adoc[]
include::../include/types.adoc[]
:description: Persistence options
:keywords: persistence, data, storage
This page aims to give an overview of the serialization options Minetest provides.
Your choice for persistence depends on the data you want to persist:
* Structure & types of the data: How is the data structured? What data types occur?
* Size of the data, frequency of changes to the data: Can it fit into memory? How expensive can updates be?
* Granularity needed: How often must the data be persisted?
* Required queryability of the data: Are indices for fast lookups required?
* Required data access, objects the data is tied to: Items? Players?
== (De-)Serialization
=== JSON
Minetest provides a JSON serializer based on `jsoncpp`.
The following types are supported by JSON:
* booleans
* numbers
* strings
* tables (objects/arrays)
NOTE: Negative and positive infinity are represented by numbers which exceed the double number range by a lot (plus/minus `1e9999`).
WARNING: The empty table `{}` (ambiguous: might be either `{}` or `[]`) and `nan` aren't supported by JSON and will be turned into `null`.
Tables may either:
. Contain only positive integer keys (represented as array) or
. Contain only string keys (represented as object/dictionary)
obviously, plenty of Lua tables aren't representable this way (boolean keys, mixed hash/list keys, fractional keys, inf keys)
==== `minetest.write_json(data, [styled])`
If the data is serializable as outlined above:: Returns a JSON string representing `data`. Formats the JSON nicely to improve readability if `styled` is truthy.
Else:: Returns `nil` and one of the following errors if it fails:
* `Can't use indexes with fractional part in JSON`
* `Lua key to convert to JSON is not a string or number`
* `Can't mix array and object values in JSON`
WARNING: Hash tables containing only positive integer keys - a table of `minetest.hash_node_position` hashes for instance -
will be turned into *an array with holes*, that is, all `nil` values from `1` to the maximum index will be filled with `null`,
which means terrible performance and possibly very large output generated from very small input.
*Do not allow direct access to `minetest.write_json`* as it can be trivially used to DoS your server.
TIP: Sparse hash maps, should always be converted to objects, using only string keys, for JSON. You might do this just for serialization.
WARNING: The JSON serializer is furthermore limited by its recursion depth: Only a recursion depth up to *16* is allowed. Very nested data structures can't be (de)serialized. Circular references will cause the JSON serializer to recurse until the maximum recursion depth is exceeded.
TIP: Use the `json = assert(minetest.write_json(data))` idiom to not silently ignore errors when serializing.
==== `minetest.parse_json(json, [nullvalue])`
If `json` is valid JSON:: Returns the Lua value represented by the JSON string `json`, with `null` values deserialized to `nullvalue` (which defaults to `nil`).
Else:: Returns `nil` and logs an error.
TIP: If you must use JSON (to interact with a web interface, for instance), consider using your JSON library of choice;
Lua-only implementations such as https://github.com/grafi-tt/lunajson/[`lunajson`] are available.
=== Lua
Minetest alternatively offers a serializer implemented in Lua, which serializes to Lua source code, available as `minetest.serialize` and `minetest.deserialize`. The following values are supported:
* `nil`
* booleans
* numbers
* strings
* tables, including circular references
* functions: *not recommended*; uses `string.dump` internally
** Deserialization of functions will error unless mod security is disabled
** Lua-implementation and platform-specific bytecode: Functions serialized by PUC Lua won't work on LuaJIT and vice versa; no cross-platform portability
** Upvalues or the context of the function (function environment) aren't preserved
** C functions which lack Lua bytecode (such as `math.sqrt` or most Minetest API functions) can't be (de)serialized at all
Userdata objects (like `ItemStack` for example) aren't supported. Threads (coroutines) aren't supported either.
==== `minetest.serialize(data)`
Serializes `data`, which may be a value of any of the above types.
Returns a Lua chunk in `string` form that returns `data` if executed.
Will error with `Can't serialize data of type <type>` if it encounters an unsupported type.
Fully supports circular references.
==== `minetest.deserialize(str, [safe])`
If `str` is not of type `string`:: Returns `nil, "Cannot deserialize type '<type>'. Argument must be a string."`
If `str` starts with `"\27"`:: Returns `nil, "Bytecode prohibited"`
If `str` triggers a Lua error at run- or load-time:: Returns `nil, error`
If `str` returns without errors:: Returns the first value returned by executing the chunk `str` without arguments
If `safe` is truthy, serialized functions will be deserialized to `nil`.
This will trigger an error if functions are used as table keys (`{[function()end] = true}`).
Otherwise, serialized functions will get an empty function environment set - only being able to operate on literals and arguments.
TIP: Use of the `data = assert(minetest.deserialize(lua, safe))` idiom is recommended.
WARNING: https://github.com/minetest/minetest/issues/7574[`minetest.deserialize` errors on large objects on LuaJIT]
== Engine-provided default persistence
Nodes (consisting of nodename, param1, param2, meta) are persisted automatically as part of mapblocks.
A handful of player properties (HP, breath, position, look direction, inventory, meta) are persisted as well.
Granularity is controlled by the `server_map_save_interval` setting.
== Storage options
=== Database server / the ominous cloud
You can use Minetest's HTTP library to communicate with webservers, which might store data for you.
Other ways of Inter-Process Communication that can be leveraged to communicate with a database include *sockets*,
provided through the `luasockets` library (requiring an insecure environment and an accessible installation).
If the database server runs on the same machine, you might decide to use file bridges for IPC.
=== Lightweight database library
Requires an insecure environment and an installation of the database library that is accessible to Minetest.
SQLite3, available through the `lsqlite3` luarocks package,
is a popular choice here and used for instance by the https://github.com/shivajiva101/sban[sban] mod.
=== String stores
==== Entity staticdata
Tied to entities. The serialized string must be returned by `get_staticdata` and is passed to `on_activate`.
==== File store
Usually tied to world or mod paths. The simplest approach reads the file at load time and writes it on shutdown.
As `on_shutdown` may however not be called in the case of a crash -
or even worse, a power outage might abruptly shut down the server without calling anything -
this provides a rather poor granularity, as all changes to the data during the uptime may be lost.
You may simply serialize your data and write it to a file on every update.
If your data is rather larger or gets updated frequently, a full serialization might negatively impact performance.
Performance can be improved at the expense of granularity by saving periodically and choosing "long" periods.
A transaction log improves performance by only storing changes, at the expense of disk space.
TIP: A mix of both approaches can provide satisfying results, logging only changes and rewriting the logfile periodically to keep disk space waste acceptable.
For special cases like logging, an append-only file may be the ideal solution if using the global `minetest.log` is not desirable.
=== Key-value store
==== Filesystem
On systems that provide a decent filesystem implementation (that is, everything except Windows),
you can use filenames/filepaths as keys and files as values.
On poor filesystems, you might be heavily limited by absolute path character limits;
lots of small files might lead to fragmentation.
A nested hierarchical key-value store is possible through directory structures, which can be managed and traversed using:
* `minetest.mkdir`
* `minetest.rmdir`
* `minetest.cpdir`
* `minetest.mvdir`
* `minetest.get_dir_list`
If you want to mitigate the risk of data loss, you can use `minetest.safe_file_write` when (re)writing files.
==== Configuration files
The `Settings` object allows you to operate on configuration files, getting & setting key-value entries and saving the file.
The main `Settings` object `minetest.settings` can be used to persist a few settings "globally" - bleeding everywhere.
This is horribly abused by the mainmenu to store stuff like the last selected game. Don't be like the mainmenu;
distinguish persistent game data and settings/configuration properly and _namespace_ your data properly.
That said, `Settings` can be used to read from & write to simple & limited key-value store files.
The main advantage of them is that they are presumably easy to edit for users,
but this is usually not a requirement for game data which is "edited" by in-game interactions;
indeed, you might not want to tempt players to cheat in singleplayer by editing their "saves".
==== MetaData
Minetest provides metadata objects which all provide a simple string key-value store, tied to four different game "objects":
. ItemStacks: xref:classes/ItemStackMetaData.adoc[ItemStackMetaData]: Fully sent to clients; serialized within inventories, which may be serialized within mapblocks
. Node positions: xref:classes/NodeMetaData.adoc[NodeMetaData]: Sent to clients, but fields can be marked as private; serialized somewhere within mapblocks
. Players: xref:classes/PlayerMetaData.adoc[PlayerMetaData]: SQLite-backed key-value storage, however only available while the player is online
. Mods: xref:classes/ModStorage.adoc[ModStorage]: SQLite-backed key-value storage; older versions use a JSON-backed store unsuitable for large data (as serialization will block the main thread)
<<<<<<< HEAD
Utilities for setting & getting non-string datatypes like integers and floats are provided; the datatype is however not stored with the entries.
The granularity of all these key-value stores is determined by the `server_map_save_interval` setting.
=======
TIP: Store only small data in ItemStackMetaRefs. Make sure to limit user input.
>>>>>>> 13da8f0e9024f94f28a5e0c1d4fc42b3d4cd9049