Slap.SlateDB.ObjectStore (slap_slatedb v0.3.0)

Copy Markdown View Source

Plain objects in the store a database lives in: small objects with conditional writes (get/3, put/4), such as leases (see Slap.Cluster.Strategy.ObjectLease), and large ones streamed in and out (upload/4, download/3), such as file bodies. Every call takes the :timeout option (see Slap.SlateDB); for a stream it bounds each step.

{:ok, store} = Slap.SlateDB.ObjectStore.open("_cluster", store: {:url, "s3://bucket/prefix"})
{:ok, version} = Slap.SlateDB.ObjectStore.put(store, "leases/0", "a", mode: :create)
{:ok, {"a", ^version}} = Slap.SlateDB.ObjectStore.get(store, "leases/0")
{:error, :conflict} = Slap.SlateDB.ObjectStore.put(store, "leases/0", "b", mode: :create)
{:ok, _} = Slap.SlateDB.ObjectStore.put(store, "leases/0", "b", mode: {:update, version})

Conditional updates ({:update, version}, an If-Match PUT) need a store that supports them: S3 and S3-compatible stores such as RustFS, Azure, GCS, :memory, and local stores. Local mutations share one OS lock per canonical store root, including across handles and processes. On a filesystem without microsecond modification times, local updates return :unsupported.

Local stores sync file contents before PUT or upload completion returns success. On Unix, they also sync directory changes for object creation and deletion. This can increase latency. Persistence requires the filesystem and device to honor sync requests; other platforms do not sync directory entries.

An atom error means the store refused a condition or does not support it; %Slap.SlateDB.Error{} means the operation failed.

Summary

Types

t()

An object's version: opaque, compare it only for equality.

Functions

Deletes an object. Deleting one that does not exist is not an error.

Opens the object at key for reading. Returns {:ok, {chunks, size, version}}, where chunks is a lazy stream of binaries (reading it raises Slap.SlateDB.Error if the store fails) and size is the size of the whole object, or {:ok, nil} if there is no object.

The object's body and version, or {:ok, nil} if there is none.

The keys under prefix, in no particular order.

Opens store (as for Slap.SlateDB.open/2). Keys are relative to path within it (and to the prefix of a store URL). The :store option is required.

Writes an object. :mode is

Writes the object at key from chunks, an enumerable of binaries, and returns {:ok, version}. Nothing is visible at key until the whole body is written.

Types

t()

@opaque t()

version()

@opaque version()

An object's version: opaque, compare it only for equality.

Functions

delete(object_store, key, opts \\ [])

@spec delete(t(), String.t(), keyword()) :: :ok | {:error, Slap.SlateDB.Error.t()}

Deletes an object. Deleting one that does not exist is not an error.

download(object_store, key, opts \\ [])

@spec download(t(), String.t(), keyword()) ::
  {:ok, {Enumerable.t(), non_neg_integer(), version()} | nil}
  | {:error, Slap.SlateDB.Error.t()}

Opens the object at key for reading. Returns {:ok, {chunks, size, version}}, where chunks is a lazy stream of binaries (reading it raises Slap.SlateDB.Error if the store fails) and size is the size of the whole object, or {:ok, nil} if there is no object.

range: {first, last} reads only the bytes from first to last, inclusive, as an HTTP range does. A last past the end of the object reads to its end. A range that starts past the end returns an error whose form depends on the store, so check the range against the object's size first.

get(object_store, key, opts \\ [])

@spec get(t(), String.t(), keyword()) ::
  {:ok, {binary(), version()} | nil} | {:error, Slap.SlateDB.Error.t()}

The object's body and version, or {:ok, nil} if there is none.

list(object_store, prefix, opts \\ [])

@spec list(t(), String.t(), keyword()) ::
  {:ok, [String.t()]} | {:error, Slap.SlateDB.Error.t()}

The keys under prefix, in no particular order.

open(path, opts)

@spec open(String.t(), keyword()) :: {:ok, t()} | {:error, Slap.SlateDB.Error.t()}

Opens store (as for Slap.SlateDB.open/2). Keys are relative to path within it (and to the prefix of a store URL). The :store option is required.

A :memory store is new and empty each time it is opened.

put(object_store, key, body, opts \\ [])

@spec put(t(), String.t(), binary(), keyword()) ::
  {:ok, version()} | {:error, :conflict | :unsupported | Slap.SlateDB.Error.t()}

Writes an object. :mode is:

  • :overwrite (default) - unconditionally.
  • :create - only if there is no object at key.
  • {:update, version} - only if the object is still at version.

Returns {:ok, version}, {:error, :conflict} when the condition does not hold, or {:error, :unsupported} when the store cannot check it.

upload(store, key, chunks, opts \\ [])

@spec upload(t(), String.t(), Enumerable.t(), keyword()) ::
  {:ok, version()} | {:error, Slap.SlateDB.Error.t()}

Writes the object at key from chunks, an enumerable of binaries, and returns {:ok, version}. Nothing is visible at key until the whole body is written.

A body smaller than one part (5 MiB) is written with a single PUT; a larger one as a multipart upload, a few parts at a time, which is aborted if chunks raises or the upload fails. A process that dies during an upload leaves its parts behind until the store expires them (S3 needs a lifecycle rule for incomplete multipart uploads).

On a local store, get/3 can briefly return an earlier version of key just after an upload of it completes; conditional updates are not affected. Use put/4 for objects whose versions you compare.