1,847 karma · joined December 27, 2018
I'm working on an evolutionary, accumulate-only database system called SirixDB written in Java (and a module in Kotlin) in my spare time and in my day to day job I'm also working as a software engineer (embedded; besides I'm dancing a lot -- swing dances, mainly Lindy Hop and travel to festivals in Europe often times in my holidays) :-)
Furthermore, I'm working on Brackit (http://brackit.io), a query engine for JSON and XML using a JSONiq like query language. It can be used as ad-hoc in-memory query processor as the query processor of a data store. We envision, that it encapsulates common proven optimizations whereas the individual data stores can add further stages to rewrite the queries for index matching and physical optimizations.
SirixDB (https://sirix.io or https://github.com/sirixdb/sirix) is all about efficient versioning of your data. That is on the one hand it reduces the storage cost of storing a new revision during each transaction-commit while balancing read- and write-performance through a novel sliding snapshot algorithm and dynamic page compression. On the other hand Sirix supports easy query capabilities for instance to open a specific revision by a timestamp or several revisions by a given timespan, to navigate to future or past versions of nodes in the tree-structure and so on. It basically never overwrites data and is heavily inspired by ZFS and Git and borrows some ideas and puts these to test on the sub-file level.
In stark contrast to other approaches SirixDB combines copy-on-write semantics with node-level versioning and does not require a write-ahead-log for consistency.
It all started around 2006 as a university / Ph.D. project of Marc Kramis and I worked on the project since 2007 and already did my Bachelor's Thesis, Master's Thesis as well as several HiWi-Jobs on topics regarding the project and I'm still more eager than ever to put forth the idea of a versioned, analytics plattform to perform analytical tasks based on current as well as the history of the data.
e-mail address: lichtenberger.johannes@gmail.com
Regarding tamper proof audit logs not much is missing. Cryptographic hashes instead of XXH3, a commit hash chain and signed commits. Actually, I think that's a great addition with minimal changes needed.
What you can audit currently is "who changed what" for instance.
Furthermore, a (versioned, as always) path summary keeps track of all distinct paths, which is a key ingredient for the optional secondary indexes, which can index paths or paths and content among other stuff as simply indexing fields. Optionally you can also add indexes to speed up aggregate queries (which are basically column projections).
Furthermore, the whole storage can self-validate through checksums stored in parent pages up to the root as in ZFS for instance.
DeweyIDs can be optionally stored for each node, which can speed up the comparison of subtrees between revisions (in order to detect simply which changesets belong to a certain subtree without having to traverse ancestor chains). They lend themselves well for compression.
We even have importers which can identify based on heuristics minimal edit operations to import existing revisions of XML or JSON documents and to commit these with hopefully a minimal or close to minimal set of update operations, but it depends heavily on the data.
https://sirix.io | https://github.com/sirixdb/sirix | http://brackit.io
The control is highly customizable, with a lot of views to chose from, daily, monthly, yearly... but also resource views (you can book resources with custom groupings, by plugin, by the resource-ID, whatever...), define "plugins" on the data sources, what's the from- and to- columns, the title column, what's the resource (may be from a foreign key / 1:1 relationship or 1:N if it's from a "child" data source or from the same data source/table).
Furthermore I've implemented different appointment series, to chose from (monthly, weekly (which weekdays), daily...), which column values should be copied. Also appointment conflicts (or only conflicts if they book the same resource). You could also configure buffers before and after appointments where no other appointment can be.
That was a lot of fun and also challenge sometimes regarding time zones and summer/winter time in Europe and so on :-)
Guess I just felt a bit frustrated...
You can simply compute from a unique nodeId each data is assigned (64bit) the page and reference to traverse on each level in the trie through some bit shifting.
Also, of course you may have covering indexes.
For actual research I'd recommend all stuff from Andy (Pavlo), Viktor Leis, Thorsten Grust, Thomas Neumann...
Every commit directly syncs the binary data to the durable storage (currently a file) and incrementally adds data. Furthermore, it stores optionally the changes (type of change/ctx node/updatePosition... in JSON files). For instance, lately I've implemented a simple copy mechanism based on this. Copy a given revision and optionally apply all changes with intermediate commits to also copy the full history up to the most recent revision). However, the main idea is to use the change tracking also for diff visualizations... maybe even stream these via web sockets.
A production ready system BTW may be Datomic.
And it also reminds me of this paper: https://dl.acm.org/doi/abs/10.5555/3275366.3284969
The leaf pages of the trie store either the data itself/the nodes or nodes of the path summary, nodes of the secondary indexes...
Thus, we have the main document index, but a RevisionRootPage also has references to the tries, which store the secondary indexes. The secondary indexes are read into main memory / are reconstructed from the leaf pages of the tries (usually small), also a small path summary.
The data pages are not simply copied... only nodes, which changed or fall out of a sliding window. Thus, a page may have to be reconstructed in-memory from at most a small number N of page fragments in the worst case. Thus, it needs a device, which is suitable for fast random, small sized parallel reads and sequential writes.
Currently you have to copy a resource starting from a given revision and applying all updates up to the most recent revision with intermediate commits in order to get rid of old revisions, as it only uses one data file per resource (a resource is equivalent to a table in a relational system). Thus, the data files are basically logs. Another file simply stores offsets and timestamps read into memory to retrieve a given revision.
https://sirix.io/docs/concepts.html
and
https://sirix.io/docs/jsoniq-tutorial.html
Should probably help to get a further understanding.
HTH and let me know if you're interested in more details :-)
Thanks for asking
https://sirix.io/docs/concepts.html and in progress tutorial https://sirix.io/docs/jsoniq-tutorial.html may be especially helpful.
It basically turns updates into appends and is based on a persistent tree structure (the header with a reference to the (revision) root page has to be swapped atomically. Other than that the revision indexes for new data are always appended. In order to reduce copy-on-write overhead for updated page (fragments) a sliding snapshot for the data pages is applied.
Naturally, unchanged pages are simply referenced (e.g. through offsets into the file, thus sharing unchanged pages between revisions).
What's also special is a path summary of all unordered paths in a resource, which enables user-defined smaller tailored secondary indexes and other query rewrites :-)
Another issue may be that Optane DC persistent memory simply was not fast enough to replace non persistent RAM.
Still, I hope that another technology will arise, which is byte-addressable and persistent/durable. I think it could radically change the design of database systems again. You wouldn't have to have a page cache / buffer manager, which retrieves same sized blocks (or multiples of blocks) for instance. You probably wouldn't even need serialization/deserialization to disk.
It would be great if it'd be possible to for instance read 512byte blocks from disk with current SSDs, but I guess the block overhead for meta data might be too big.
It means it would be possible to read/write data in much more fine granular chunks (potentially saving a lot of storage space in some cases).
JSON is shredded during an initial import into a tree structure with fine granular nodes. Thus, an import can be done with very low memory consumption (permitted that auto-commit issues a sync to disk before RAM space is exceeded). Furthermore, it doesn't require a WAL for consistency. Instead the indexes are stored in a log-structure using a persistent tree (as in every commit creates a new tree root). A sliding snapshot algorithm makes sure, that only a fragment of a page has to be copied on a write.
As thus, it's also a perfect candidate for an event store, storing both the (lightweight) snapshots and tracking the changes optionally.
https://github.com/sirixdb/sirix
The architecture is described over here:
https://sirix.io/docs/concepts.html
Furthermore I'm working on a tutorial for a local client usage (work in progress):
https://sirix.io/docs/jsoniq-tutorial.html
Kind regards
Johannes