View Source EctoFoundationDB.Layer (Ecto.Adapters.FoundationDB v0.4.0)
The Ecto FoundationDB Adapter implements a Layer on the underlying key-value store provided by FoundationDB. Via this layer, some common data access patterns are achieved. For those familiar with relational databases such as Postgres, these patterns will be familiar. However, there are many differences (for example SQL is not supported), so this document seeks to describe the capabilities of the Ecto FoundationDB Layer in detail.
Keyspace Design
All keys used by :ecto_foundationdb
are encoded with FoundationDB's Tuple Encoding.
With the default :tenant_backend
of EctoFoundationDB.Tenant.DirectoryTenant
, each tuple written by EctoFDB is
prefixed with a short binary string as allocated by :erlfdb_directory. This tenant prefixing is the most critical element of the keyspace because it's the mechanism that guarantees tenants cannot cross their boundaries. **The rest of this documentation is written assuming you're using the DirectoryTenant
.**
The first element of the tuple after the tenant prefix is a string prefix that is intended to keep the
:ecto_foundationdb
keyspace separate from other keys in the FoundationDB cluster.
Your Schema data and Default indexes are stored with "\xFD".
The data associated with schema migrations is stored with "\xFE".
The rest of the tenant's keyspace is open for use by you, the application developer. For example, it is safe to write:
db = FoundationDB.open(MyApp.Repo)
tenant = Tenant.open(MyApp.Repo, "some-org")
# Multitenancy-Safe. The key is properly packed
:erlfdb.set(db, Tenant.pack(tenant, {"hello"}), "world")
# Multitenancy-Unsafe. The key is not packed into the tenant's keyspace
:erlfdb.set(db, :erlfdb_tuple.pack({"hello"}), "world")
:erlfdb.set(db, "hello", "world")
A value (the binary stored at each key) is either
- some other keys (in the case of Default indexes) or
- Erlang term data encoded with
:erlang.term_to_binary/1
Primary Write and Read
Your Ecto Schema has a primary key field, which is usually a string or an integer. This primary key uniquely identifies an object of your schema within the tenant in which it lives.
defmodule EctoFoundationDB.Schemas.User do
use Ecto.Schema
schema "users" do
field(:name, :string)
field(:department, :string)
timestamps()
end
end
In this example, a User has an :id
and a :name
. Also remember that the User is defined within
a tenant which provides a scope under which the User lives. For example, a typical tenant
would be the organization the User belongs to. Since the User is in this tenant, we do not need
to provide an identifier for this organization on the User object itself.
"Primary Write" refers to the insertion of the User struct into the FoundationDB key-value store
under a single key that uniquely identifies the User. This key includes the :id
value.
Your struct data is stored as a Keyword
encoded with :erlang.term_to_binary/1
.
Note: The Primary Write can be skipped by providing the write_primary: false
option on the @schema_context
.
See below for more.
Default Indexes
When a Default index is created via a migration, the Ecto FoundationDB Adapter writes a set of keys and values to facilitate lookups based on the indexed field.
Advanced Options: write_primary: false
, mapped?: false
If you choose to use write_primary: false
on your schema, this skips the Primary Write. The consequence of this are as follows:
- You'll want to make sure your index is created with an option
mapped?: false
. This ensures that the struct data is written to the index keyspace. - Your index queries will now have performance characteristics similar to a primary key query. That is, you'll
be able to retrieve the struct data with a single
erlfdb:get_range/3
. - The data can only be managed by providing a query on the index. You will not be able to access the data via the primary key.
- If
write_primary: false
, then only one index can be created.