spacejam / sled

the champagne of beta embedded databases
Apache License 2.0
8.08k stars 383 forks source link

WASM Support #1112

Open bitmage opened 4 years ago

bitmage commented 4 years ago

Use Case:

Use Sled inside a WASM environment.

Known Issues:

Sled compiles fine for the wasm32-unknown-unknown target, but panics at runtime.

Currently I'm getting a panic due to time not implemented on wasm32-unknown-unknown.

I assume if this were resolved I would run into errors with disk storage.

Who Benefits From The Change(s)?

I want to use Sled to create a very portable database which will be distributed with and run right next to application code. WASM target is mandatory for my use case. The primary reason I'm using Sled instead of RocksDB is that I figure it would be much more feasible to get it working in WASM. I think this also could become a major selling point of Sled?

Happy to help

I'm interested in working on this. It looks like it may be straightforward, but I have no idea how long the stack of runtime panics will be. Let me know if I'm in over my head. I would appreciate guidance on how you would like the code to be organized, and how to go about the overall process.

  1. My first pass would be to try and resolve the Time issue. I will start by creating a new branch, a WASM compilation target and a test.
  2. Your use of cfg attributes elsewhere in the code looks pretty standard. I would start by refactoring calls to SystemTime::now() and Instant::now() into a pair of utility functions. Then placing the cfg attribute to control implementations for different platforms. I'm looking at js-sys Date.now(). I know the granularity is poor. I think it could be combined with performance.now() to get a monotonic and more accurate time. Actually doing this might involve some tricks.
  3. Use the Storage API for persistence to disk. Will look into this in more detail. Suggestions welcome.
  4. Continue running test to find the next runtime panic. Request code review after each major issue is fixed.
  5. Convert the entire test bank (or as much as possible) over to WASM.

Are you aware of other platform incompatibilities we'll likely run into, besides time and disk storage?

I've read contributing. Any other guidance you have specific to this request?

olanod commented 4 years ago

Maybe with something like this? https://github.com/tomaka/wasm-timer

bitmage commented 4 years ago

@olanod Thanks for chiming in! It looks like you put a lot of work into this. I don't feel qualified to evaluate it to be honest. I'd be interested to see a writeup of the implementation decisions that led to the current form.

Maybe @spacejam or one of the other contributors has more to say.

olanod commented 4 years ago

No worries, but credit where it's due, @tomaka is the author and he would know better about the troubles of porting this kind of libraries to WASM. I just happen be a stalker that knows about its existence because aside from sled I'm building stuff with libp2p and substrate(projects he works on) which is not trivial at all software that I believe can already run in the browser probably thanks to that library.

As a sled user I'd be cool to see it running in the browser but I'm not sure it's practical to implement it on top of something like the storage API, what I see substrate did was to to create their on facade with reduced API https://crates.io/crates/kvdb and on native uses a rocksdb backend and in the web an indexeddb one, sled could be one of those backends for example. If the goal is using only sled(not abstractions on top) maybe the internals of sled have some abstractions for the file system that can be replaced by an indexeddb/localstorage implementation but that might just be implementing a new sled leaving only its API. In the future might be more interesting to see WASI support in the browser, if it doesn't already, sled should be easy to make it work on WASI since the MVP was all about dealing with file descriptors. Another interesting option would be the native file system API which is a chrome experiment for now.
A fun thing to try that might work with today's tech with experiment-only quality could be to compile sled to WASI and have a WASI polyfill that uses that native file system API :thinking:

tomaka commented 4 years ago

For what it's worth, my wasm-timer, while fully-working, is not meant to be a proper long-term solution.

In my opinion, there are only two possible ways to properly handle the time in a WASM environment:

spacejam commented 4 years ago

I believe wasm32-wasi is the preferred way to go forward with this, at least initially. Using https://github.com/bytecodealliance/cargo-wasi I'm able to build but running currently hits this issue:

λ cargo +nightly wasi run --release
   ...
   Compiling struct_sled v0.1.0 (/tmp/struct_sled)
    Finished release [optimized] target(s) in 12.97s
     Running `/home/t/.cargo/bin/cargo-wasi target/wasm32-wasi/release/struct_sled.wasm`
  Optimizing with wasm-opt
 Downloading precompiled wasm-opt version_92
     Running `target/wasm32-wasi/release/struct_sled.wasm`
error while processing main module target/wasm32-wasi/release/struct_sled.wasm: Instantiation error: Link error: wasi_snapshot_preview1/proc_exit: no provided import function

I feel that wasm is in general a high priority long-term goal, and if anyone is able to dig in more I'd love to see this working better. I need to spend a bit more energy on reliability efforts for the short term.

bitmage commented 3 years ago

I've been traveling the whole summer. Tried to hack on this a couple of times... I'm back home now with more time to dedicate to it, but still not making progress.

@spacejam in your example above it seems like you have created a demo project to test sled with. I tried to replicate with this example but I get a panic on getpid().

Ideally I would like to run the tests instead, so I can stay in sled source code and make changes as necessary. By running the command cargo +nightly wasi test --features testing I get 1: unknown import: 'env::now' has not been defined.

From what I can find out about the WASM spec, the imports of a module are known statically, and compiled into a prelude or header at the beginning of the wasm file. I believe the attempt to satisfy the dependencies happens before any of the body of WASM code is run, which explains why there is no stack trace on an unknown import. From Rust, the way WASM imports are declared is with a #[link(wasm_import_module = "the-wasm-import-module") and an optional #[link_name = "bar"] on the function to be imported. I don't see code like this anywhere in the Rust WASI source, so I wonder how this is implemented.

I can see that getpid() is implemented for WASI in the rust source as panic!("unsupported"), which explains the behavior I see in my sled-wasi project.

It looks like Instant::now() and SystemTime::now() are implemented in Rust WASI, so I'm scratching my head as to why the tests fail on this. The import being attempted is env::now, which is different than the calls that any of the sled code is making. I assume that somewhere within the Rust WASI implementation the imports are defined in this way. Again, because the imports become a static fixture of the WASM file, there is no stack trace which would reveal the code that is actually responsible for generating them.

So this is basically where I'm at at this time. I would welcome some additional context if anyone is willing to share:

  1. Thoughts on how to move forward in satisfying the needed dependencies?
  2. Where are the WASI bindings within Rust, so I can relate the imported name to the name being called by Rust?
  3. Anything I missed?

I'll try to reach out within the WASM community as well and see if I can get help. Thanks!

bitmage commented 3 years ago

Update: I've resolved the following:

You can find the updated code here, and the test project here.

Currently unresolved are the file.try_clone() calls. I'm unsure how to proceed - is there a polyfill possible here, and what would be the ideal approach?

The std implementation of try_clone calls self.inner.duplicate(). In the case of WASI this is unsupported, and they link to this rationale referencing mmap. I'm unfamiliar with these things - is try_clone expected to be implemented by mmap or have similar behavior?

It looks like even the userspace mmap support requires conditional compilation of the VM to support it. Any thoughts on how to proceed, or what would make sense for the use cases of Sled?

bitmage commented 3 years ago

@spacejam could you provide perspective on how you're using this API, and whether there is a reasonable substitution?

spacejam commented 3 years ago

@bitmage thanks for making progress on this!

The fundamental thing happening in the polyfill is to start with a &File, take out the global lock, seek it, and then do a read/write.

For wasi, I see that pread/pwrite are defined symbols but I don't know how hard they are to access. Is it possible to get the underlying file descriptor of an &File on wasi, so that it can be passed to pread/pwrite in libc? If so, we can just use those without any global file lock from a wasi-specific module, similar to what we have for linux and windows.

petermetz commented 3 years ago

My use case for this would be to run sled as part of a NodeJS application (which would load sled as a .WASM module)

Tpt commented 3 years ago

Thank you @spacejam @bitmage for moving forward on WASM and WASI support.

I have spent some time to make an attempt at it. Here is the result. It seems to work with Rust nightly compiler.

There are still some rought edges:

  1. WASI does not provide anything to get the systemp temporary directory. I have hacked something using the TMPDIR environment variable and default to /tmp. But a better way to go might be to completely disable the temporary database feature from Sled to avoid having to use this hack.
  2. The code is currently only compatible with Rust nightly to allow to use the wasi_ext feature that provides a FileExt trait similar to the UNIX one. It allowed me to reuse the parallel_io_unix implementation. Moving to Rust stable might be a bit "fun" because getting the file descriptor from a File object is also unstable in Rust std. Like @bitmage said, reusing naively the parallel_io_polyfill implementation requires the File::try_clone operation that is not availlable on WASI.
  3. I have hackily disabled the compression feature because zstd does not compile to WASM.
  4. Most of the tests should pass using cargo wasi. There are still some issues because most WASI runtimes mandate to provide a whitelist of files/directories that the code is allowed to open.
Teebor-Choka commented 1 year ago

What is the status of this effort? @Tpt seems to have gone far along, is there a plan to provide WASM support eventually? Most usable embedded DBs lack this at the moment.

feynon commented 6 months ago

Chiming in, would love to know the status of ongoing efforts in this area