Async vs Blocking

BonsaiDb supports both async and blocking (threaded) access. Its aim is to provide a first-class experience no matter which architecture you choose for your Rust application.

Local-only

Storage and Database are the blocking implementations of BonsaiDb. These types provide the lowest overhead access to BonsaiDb as they will block the currently executing thread to perform the operations.

AsyncStorage and AsyncDatabase are simple types that "wrap" Storage and Database instances with an asynchronous API. BonsaiDb does this by spawning a blocking task in Tokio. Internally, Tokio uses a pool of threads to drive blocking operations. This may sound like a lot of overhead, but it is surprisingly lightweight.

Our recommendation is to pick the programming style that fits your needs the best. Do you need lightweight task concurrency, or is basic threading enough? If this application grew in scope, would it ever need to be a networked application?

If you anticipate needing to use BonsaiDb's networked server, you should review the next section to consider how Tokio benefits a networked server.

Networked Server

When building a networked server, a common strategy to handle inbound connections is to allow each connection to have a thread. This is expensive, however, as each thread needs its own stack allocated and is managed by the kernel. When designing a server with long-running connections, async allows handling more connections with fewer system resources. As such, BonsaiDb's server is built atop Tokio, and the traits used to extend the server are async_traits.

The networked server is built atop AsyncStorage, which means that you can convert a server instance into a blocking Storage instance, allowing local access to your server to remain blocking.

Networked Client

BonsaiDb's networked client uses Tokio for all networking on non-WASM targets, and uses the browser's WebSocket APIs for WASM targets.

On all non-WASM targets, the networked client can be used without a Tokio runtime present. When instantiated this way, a runtime will automatically be run powering the client's networking. In the future, it is possible that non-Tokio-based networking implementations could be provided instead for the blocking client implementation.

For WASM, the networked client does not provide blocking trait implementations. If you are building for WASM, you must use the async traits.

The differences between the APIs

The core traits are split into two types: blocking and async.

| Blocking             |   Async                   |
|----------------------|---------------------------|
| `Connection`         | `AsyncConnection`         |
| `StorageConnection`  | `AsyncStorageConnection`  |
| `PubSub`             | `AsyncPubSub`             |
| `Subscriber`         | `AsyncSubscriber`         |
| `KeyValue`           | `AsyncKeyValue`           |
| `LowLevelConnection` | `AsyncLowLevelConnection` |

By splitting these traits, BonsaiDb tries to make it harder to accidentally use a blocking API in an asynchronous context. In general, all other functions are exposed in pairs: a blocking version, and an async version with the suffix "_async". For example, SerializedCollection::get is the blocking API, and SerializedCollection::get_async is the async API.

When developing a project that uses both async and blocking modes of access, it is considered a good practice to separate modules based on whether they are blocking or not. This can help spot mistakes when the wrong type of trait is imported in the wrong type of module.