--- tags: SimplyStore --- # SimplyStore: Multithreading Design SimplyStore is a new look at how to build online API's. What is the minimal, most simple, approach to build a good, scalable, usable API. One of the design decisions was to skip the traditional database. Instead all data is loaded into memory, and the query engine is just javascript. Clients can connect and send queries as javascript code. This is run in a sandbox, on immutable data. To change data, you send commands to a seperate command endpoint. Here you can only send data. The command must be defined server-side. Commands can be implemented in whatever language the server is written in, and can change the data. ## SimplyStore v1: Node + Express + V8 The first version of SimplyStore is written in javascript, using [Node](https://nodejs.org), [Express](https://expressjs.com/) and [V8](https://v8.dev/) (through [VM2](https://github.com/patriksimek/vm2)). The server uses multiple query threads and a single command thread. The original idea is that because the dataset is immutable, all threads can safely use the same data through shared memory. Unfortunately both Node and V8 block this approach. V8 uses its own garbage collection and has no way to re-use the same data across multiple instances. Node's work threads also have no provision to share memory. The only option available in Node is to use a SharedArrayBuffer. But this clearly doesn't have a way to represent the object graph. Unless you create that seperately, which is possible... if done right, it might actually be faster than using native javascript objects, because values are more tightly packed in memory and so the CPU L1,2,3 caches can be used more effectively. So in SimplyStore v1, all data is copied between threads. Any time a command changes the dataset, all query threads get replaced with new query threads with the new dataset. The dataset is passed as a jsontag string, which is parsed seperately in each thread. This adds extra cost in memory, as well as in CPU cycles. However, it does work, which is enough for a version 1. There is work ongoing in V8 to support shared objects between V8 Isolates: https://bugs.chromium.org/p/v8/issues/detail?id=12547, referencing [Shared Object in the GC](https://docs.google.com/document/d/18lYuaEsDSudzl2TDu-nc-0sVXW7WTGAs14k64GEhnFg) and [SharedObjectPool](https://docs.google.com/document/d/1ttFLFJWxf3qiq0Z7p2y3oqEzZsHRWCbA1AlibHaDL9s). But there appears nothing on the horizon for Node. ## SimplyStore v2: Rust/Go What about using Rust or Go as our server runtime? Each of these has excellent support to include V8. As long as we route all queries and commands to a V8 Isolate, the server implementation language should not matter. The choice between Rust or Go is mostly based on whether SimplyStore can make effective use of goroutines. (goroutines are a special very light-weight thread, that is managed by Go itself. goroutines can switch between threads and make efficient use of idle threads.) Unfortunately it appears that V8 requires each isolate to be tied to a single thread, so goroutines don't offer an advantage anymore. In that case the preference is for Rust, because of the additional safeguards it offers. ## SimplyStore-Rust With the choice for Rust for v2, there is a good example project that implements an API in Rust, using V8: [v8-server](https://github.com/ryanpbrewster/v8-server/blob/master/src/main.rs). Talking with a Rust expert convinced me to switch the webserver library to [Axum](https://github.com/tokio-rs/axum), since it is more widely used and should work better with [Tokio](https://github.com/tokio-rs/tokio), the main async library. Rust uses immutable data by default, which allows you to share data between threads, exactly as intended in the original SimplyStore design. The problem now is how to share data between the different V8 Isolates. As long as [SharedObjectPool](https://docs.google.com/document/d/1ttFLFJWxf3qiq0Z7p2y3oqEzZsHRWCbA1AlibHaDL9s) isn't available, we can't directly share objects between them. However, V8 is designed to allow access to non-native javascript objects. This is how you access the DOM in Chrome for example. So we can design a proxy object in each V8 Isolate, that returns a copy of a specific object in the shared immutable rust dataset. This only incurs a small cost whenever you access a specific object. You don't have to copy the entire dataset. The only problem now is that the dataset is immutable, we need a way to update it, in the command thread. To be as flexible for future changes as possible, the command should be written in javascript as well. This allows SimplyStore to migrate to any technology stack that can run javascript. The dataset shared with the command thread is the same as that shared with the query threads, and thus is immutable. [Immer ](https://immerjs.github.io/immer/) is a javascript library designed to work around that. It automatically creates mutable copies of immutable objects, whenever you want to change something. However, it explicitly only supports tree-like structures, just like JSON. JSONTag is explicitly designed for graph structures, so I've created a completely new implementation of Immers [produce()](https://github.com/SimplyEdit/SimplyStore/blob/master/src/produce.mjs) function, that does support graphs. It also clones copies using JSONTag.clone, so types and attributes are kept as well. Using this it should be possible to create change-sets, that enclose all changes caused by a specific command. Each change set has a new root, which contains the entire dataset, including links to all objects that aren't changed. Each command then returns a new root object, with all its changed data, which can either be parsed in Rust, using its own JSONTag parser, or be committed back to the Rust dataset through a special API when finalizing the dataset in javascript. Then each thread must be updated to use the new Root object as the new state of the data.