The FoundationDB Ruby API is distributed in our Downloads.
Note
For the language binding to function, FoundationDB client binaries whose version is at least as recent must be installed. If you upgrade a language binding to a new version, you may need to upgrade the FoundationDB client binaries as well. See Installing FoundationDB client binaries.
Note
If you have a project with automatic dependency installation and have expressed a dependency on foundationdb, it may automatically install the lastest version of the language binding when you deploy your project to a new machine. If you have not also upgraded the Foundation client binary, an unplanned upgrade of the language binding may encounter an incompatibility. You should therefore configure any project dependency on foundationdb in coordination with your overall upgrade strategy.
When you require the FDB
gem, it exposes only one useful method:
Note
You must call FDB.api_version(...)
before using any other part of the API. Once you have done so, the rest of the API will become available in the FDB
module.
Note
FoundationDB encapsulates multiple versions of its interface by requiring the client to explicitly specify the version of the API it uses. The purpose of this design is to allow you to upgrade the server, client libraries, or bindings without having to modify client code. The client libraries support all previous versions of the API. The API version specified by the client is used to control the behavior of the binding. You can therefore upgrade to more recent packages (and thus receive various improvements) without having to change your code.
Warning
When using the multi-version client API, setting an API version that is not supported by a particular client library will prevent that client from being used to connect to the cluster. In particular, you should not advance the API version of your application after upgrading your client until the cluster has also been upgraded.
For API changes between version 14 and 520 (for the purpose of porting older programs), see Release Notes.
After requiring the FDB
gem and selecting an API version, you probably want to open a :class:`Database`. The simplest way of doing this is using :func:`open`:
require 'fdb'
FDB.api_version 520
db = FDB.open
Keys and values in FoundationDB are simple byte strings.
To encode other data types, see Encoding data types and the tuple layer.
as_foundationdb_key
and as_foundationdb_value
In some cases, you may have objects that are used to represent specific keys or values (for example, see |subspace|). As a convenience, the language binding API can work seamlessly with such objects if they implement the |as-foundationdb-key| or |as-foundationdb-value| methods, respectively. API methods that accept a key will alternately accept an object that implements the |as-foundationdb-key| method. Likewise, API methods accepting a value will also accept an object that implements the |as-foundationdb-value| method.
Warning
|as-foundationdb-key| and |as-foundationdb-value| are not intended to implement serialization protocols for object storage. Use these functions only when your object represents a specific key or value.
Represents a single key-value pair in the database. This is a simple value type; mutating it won’t affect your :class:`Transaction` or :class:`Database`.
FoundationDB’s lexicographically ordered data model permits finding keys based on their order (for example, finding the first key in the database greater than a given key). Key selectors represent a description of a key in the database that could be resolved to an actual key by |get-key-func| or used directly as the beginning or end of a range in |get-range-func|.
For more about how key selectors work, see Key selectors.
Creates a key selector with the given reference key, equality flag, and offset. It is usually more convenient to obtain a key selector with one of the following methods:
A Database
represents a FoundationDB database — a mutable, lexicographically ordered mapping from binary keys to binary values. Although Database
provides convenience methods for reading and writing, modifications to a database are usually via transactions, which are usually created and committed automatically by |database-auto|.
Note
The convenience methods provided by Database
have the same signature as the corresponding methods of Transaction
. However, most of the Database
methods are fully synchronous. (An exception is the methods for watches.) As a result, the Database
methods do not support the use of implicit parallelism with futures.
Database options alter the behavior of FoundationDB databases.
A Transaction
object represents a FoundationDB database transaction. All operations on FoundationDB take place, explicitly or implicitly, through a Transaction
.
In FoundationDB, a transaction is a mutable snapshot of a database. All read and write operations on a transaction see and modify an otherwise-unchanging version of the database and only change the underlying database if and when the transaction is committed. Read operations do see the effects of previous write operations on the same transaction. Committing a transaction usually succeeds in the absence of conflicts.
Transactions group operations into a unit with the properties of atomicity, isolation, and durability. Transactions also provide the ability to maintain an application’s invariants or integrity constraints, supporting the property of consistency. Together these properties are known as ACID.
Transactions are also causally consistent: once a transaction has been successfully committed, all subsequently created transactions will see the modifications made by it.
The most convenient way to create and use transactions is using the :meth:`Database.transact` method.
Keys and values in FoundationDB are byte strings. FoundationDB will accept Ruby strings with any encoding, but will always return strings with ASCII-8BIT
encoding (also known as BINARY
). To encode other data types, see the :mod:`FDB::Tuple` module and Encoding data types.
An atomic operation is a single database command that carries out several logical steps: reading the value of a key, performing a transformation on that value, and writing the result. Different atomic operations perform different transformations. Like other database operations, an atomic operation is used within a transaction; however, its use within a transaction will not cause the transaction to conflict.
Atomic operations do not expose the current value of the key to the client but simply send the database the transformation to apply. In regard to conflict checking, an atomic operation is equivalent to a write without a read. It can only cause other transactions performing reads of the key to conflict.
By combining these logical steps into a single, read-free operation, FoundationDB can guarantee that the transaction will not conflict due to the operation. This makes atomic operations ideal for operating on keys that are frequently modified. A common example is the use of a key-value pair as a counter.
Warning
If a transaction uses both an atomic operation and a serializable read on the same key, the benefits of using the atomic operation (for both conflict checking and performance) are lost.
In each of the methods below, param
should be a string appropriately packed to represent the desired value. For example:
# wrong
tr.add('key', 1)
# right
tr.add('key', [1].pack('q<'))
Note
Most applications will use the serializable isolation that transactions provide by default and will not need to manipulate conflict ranges.
The following make it possible to add conflict ranges to a transaction.
Most applications should use the read version that FoundationDB determines automatically during the transaction’s first read, and ignore all of these methods.
Transaction options alter the behavior of FoundationDB transactions. FoundationDB defaults to extremely safe transaction behavior, and we have worked hard to make the performance excellent with the default setting, so you should not often need to use transaction options.
When performing a database transaction, any read operation, as well as the commit itself, may fail with one of a number of errors. If the error is a retryable error, the transaction needs to be restarted from the beginning. Committing a transaction is also an asynchronous operation, and the returned :class:`FutureNil` object needs to be waited on to ensure that no errors occurred.
The methods :meth:`Database.transact` and :meth:`Transaction.transact` are convenient wrappers that allow much of this complexity to be handled automatically. A call like
db.transact do |tr|
tr['a'] = 'A'
tr['b'] = 'B'
end
is equivalent to
tr = db.create_transaction
committed = false
while !committed
begin
tr['a'] = 'A'
tr['b'] = 'B'
tr.commit.wait
committed = true
rescue FDB::Error => e
tr.on_error(e).wait
end
end
The first form is considerably easier to read, and ensures that the transaction is correctly committed (and retried, when necessary).
Note
Be careful when using control flow constructs within the block passed to :meth:`transact`. return
or break
will exit the retry loop without committing the transaction. Use next
to exit the block and commit the transaction.
The :meth:`Transaction.transact` method, which logically does nothing, makes it easy to write functions that operate on either a :class:`Database` or :class:`Transaction`. Consider the following method:
def increment(db_or_tr, key)
db_or_tr.transact do |tr|
tr[key] = (tr[key].to_i + 1).to_s
end
end
This method can be called with a :class:`Database`, and it will do its job atomically:
increment(db, 'number')
It can also be called by another transactional method with a transaction:
def increment_both(db_or_tr, key1, key2)
db_or_tr.transact do |tr|
increment(tr, key1)
increment(tr, key2)
end
end
In the second case, increment
will use provided transaction and will not commit it or retry errors, since that is the responsibility of its caller, increment_both
.
Note
In some failure scenarios, it is possible that your transaction will be executed twice. See Transactions with unknown results for more information.
Many FoundationDB API functions return “future” objects. A brief overview of futures is included in the class scheduling tutorial. Most future objects behave just like a normal object, but block when you use them for the first time if the asynchronous function which returned the future has not yet completed its action. A future object is considered ready when either a value is available, or when an error has occurred.
When a future object “blocks”, the ruby thread is blocked, but the global interpreter lock is released.
When used in a conditional expression, a future object will evaluate to true, even if its value is nil. To test for nil, you must explicitly use the nil?() method:
if tr['a'].nil?
All future objects are a subclass of the :class:`Future` type.
Asynchronous methods return one of the following subclasses of :class:`Future`:
Both types are future :class:`String` objects. Objects of these types respond to the same methods as objects of type :class:`String`, and may be passed to any method that expects a :class:`String`.
An implementation quirk of :class:`Value` is that it will never evaluate to false
, even if its value is nil
. It is important to use if value.nil?
rather than if ~value
when checking to see if a key was not present in the database.
This type is a future :class:`Integer` object. Objects of this type respond to the same methods as objects of type :class:`Integer`, and may be passed to any method that expects a :class:`Integer`.
This type is a future :class:`Array` object. Objects of this type respond to the same methods as objects of type :class:`Array`, and may be passed to any method that expects a :class:`Array`.
This type is a future returned from asynchronous methods that logically have no return value.
When using |get-range-func| and similar interfaces, API clients can request large ranges of the database to iterate over. Making such a request doesn’t necessarily mean that the client will consume all of the data in the range - sometimes the client doesn’t know how far it intends to iterate in advance. FoundationDB tries to balance latency and bandwidth by requesting data for iteration in batches.
Streaming modes permit the API client to customize this performance tradeoff by providing extra information about how the iterator will be used.
The following streaming modes are available:
Errors in the FoundationDB API are raised as exceptions of type :class:`FDB::Error`. These errors may be displayed for diagnostic purposes, but generally should be passed to :meth:`Transaction.on_error`. When using :meth:`Database.transact`, appropriate errors will be retried automatically.
Warning
You should only use the :attr:`code` attribute for programmatic comparisons, as the description of the error may change at any time. Whenever possible, use the :meth:`Transaction.on_error` method to handle :class:`FDB::Error` exceptions.
The FoundationDB API comes with a built-in layer for encoding tuples into keys usable by FoundationDB. The encoded key maintains the same sort order as the original tuple: sorted first by the first element, then by the second element, etc. This makes the tuple layer ideal for building a variety of higher-level data models.
Note
For general guidance on tuple usage, see the discussion in the document on Data Modeling.
In the FoundationDB Ruby API, a tuple is an :class:`Enumerable` of elements of the following data types:
Type | Legal Values | Canonical Value |
---|---|---|
Null value | nil |
nil |
Byte string | Any value v where v.kind_of? String == true and v.encoding is
either Encoding::ASCII_8BIT (aka Encoding::BINARY ) or
Encoding::US_ASCII (aka Encoding::ASCII ) |
String with encoding Encoding::ASCII_8BIT |
Unicode string | Any value v where v.kind_of? String == true and v.encoding is
Encoding::UTF_8 |
String with encoding Encoding::UTF_8 |
64-bit signed integer | Any value v where v.kind_of? Integer == true and -2**64+1 <= v <=
2**64-1 |
Fixnum or Bignum (depending on the magnitude of the value) |
Floating point number (single-precision) | Any value v where v.kind_of? FDB::Tuple::SingleFloat where
v.value.kind_of? Float and v.value fits inside an IEEE 754 32-bit
floating-point number. |
:class:`FDB::Tuple::SingleFloat` |
Floating point number (double-precision) | Any value v where v.kind_of? Float |
Float |
Boolean | Any value v where v.kind_of? Boolean |
Boolean |
UUID | Any value v where v.kind_of? FDB::Tuple::UUID where
v.data.kind_of? String and v.data.encoding is Encoding::BINARY
and v.data.length == 16 |
:class:`FDB::Tuple::UUID` |
Array | Any value v such that v.kind_of? Array and each element within
v is one of the supported types with a legal value. |
Array |
Note that as Ruby does not have native support for single-precision floating point values and UUIDs, tuple elements of those types are returned instead as :class:`FDB::Tuple::SingleFloat` and :class:`FDB::Tuple::UUID` instances. These are simple classes that just wrap their underlying values, and they are not intended to offer all of the methods that a more fully-featured library for handling floating point values or UUIDs might offer. Most applications should use their library of choice for handling these values and then convert to the appropriate tuple-type when serializing for storage into the key-value store.
A single tuple element is ordered first by its type, and then by its value.
If T
is an :class:`Enumerable` meeting these criteria, then conceptually:
T == FDB::Tuple.unpack(FDB::Tuple.pack(T))
Note
Unpacking a tuple always returns an :class:`Array` of elements in a canonical representation, so packing and then unpacking a tuple may result in an equivalent but not identical representation.
Wrapper around a single-precision floating point value. The value
parameter should be a Float
that
can be encoded as an IEEE 754 floating point number. If the float does not fit within a IEEE 754 floating point
integer, there may be a loss of precision.
Wrapper around a 128-bit UUID. The data
parameter should be a byte string of length 16 and is taken to
be the big-endian byte representation of the UUID. If data
is not of length 16, an exception is thrown.
Subspaces provide a convenient way to use the tuple layer to define namespaces for different categories of data. The namespace is specified by a prefix tuple which is prepended to all tuples packed by the subspace. When unpacking a key with the subspace, the prefix tuple will be removed from the result.
As a best practice, API clients should use at least one subspace for application data.
Note
For general guidance on subspace usage, see the discussion in the Developer Guide.
Creates a subspace with the specified prefix tuple. If the raw prefix byte string is specified, then it will be prepended to all packed keys. Likewise, the raw prefix will be removed from all unpacked keys.
The FoundationDB API provides directories as a tool for managing related subspaces. Directories are a recommended approach for administering applications. Each application should create or open at least one directory to manage its subspaces.
Note
For general guidance on directory usage, see the discussion in the Developer Guide.
Directories are identified by hierarchical paths analogous to the paths in a Unix-like file system. A path is represented as |dir-path-type| of strings. Each directory has an associated subspace used to store its content. The directory layer maps each path to a short prefix used for the corresponding subspace. In effect, directories provide a level of indirection for access to subspaces.
Except where noted, directory methods interpret the provided path(s) relative to the path of the directory object. When opening a directory, a byte string layer
option may be specified as a metadata identifier.
Each instance defines a new root directory. The subspaces node_subspace
and content_subspace
control where the directory metadata and contents, respectively, are stored. The default root directory has a node_subspace
with raw prefix \xFE
and a content_subspace
with no prefix. Specifying more restrictive values for node_subspace
and content_subspace
will allow using the directory layer alongside other content in a database. If allow_manual_prefixes
is false, attempts to create a directory with a manual prefix under the directory layer will raise an exception. The default root directory does not allow manual prefixes.
A directory subspace represents a specific directory and its contents. It stores the path
with which it was opened and supports all |directory-layer| methods for operating on itself and its subdirectories. It also implements all |subspace| methods for working with the contents of that directory.
The FoundationDB API comes with a set of functions for discovering the storage locations of keys within your cluster. This information can be useful for advanced users who wish to take into account the location of keys in the design of applications or processes.