Persistit™ is a small, lightweight Java™ library that provides simple, fast and reliable data persistence for Java applications. It is designed to be embedded in Java application programs and to operate free of administration by the end-user.
This section provides a brief overview. See http://www.akiban.com/ak-docs/admin/persistit for complete documentation.
API Overview
Persistit stores data as key-value pairs in highly optimized B-Tree structures. Much like a Java Map implementation, Persistit associates at most one value with each unique instance of a key.
Persistit provides interfaces to access and modify keys and their associated values. The developer writes code to construct key and value instances and to store, fetch, traverse and remove keys and records to and from the database. Persistit permits efficient multi-threaded concurrent access to database volumes. It is designed to minimize contention for critical resources and to maximize throughput on multi-processor machines.
In addition to low-level access methods on keys and values, Persistit provides com.persistit.PersistitMap, which implements the java.util.SortedMap interface. PersistitMap uses the Persistit database as a backing store so that key/value pairs are persistent, potentially shared with all threads, and limited in number only by disk storage. (See PersistitMap.)
Within Persistit, key values are segmented and ordered. Segmented means that you can append multiple primitive values or Strings to construct a concatenated key. Ordered means that the methods that enumerate key values within a Persistit database do so in a specified natural order. (See Keys).
A Persistit value may be any primitive value, any Serializable Java object, or an object of any class supported by a custom serialization helper class. When stored in the B-Tree, keys and values are represented by sequences of bytes. The byte sequence that represents a value may be of arbitrary length, bounded only by available heap memory. (See Values.)
Access Methods
The primary low-level interface for interacting with Persistit is com.persistit.Exchange. The Exchange class provides all methods for storing, deleting, fetching and traversing key/value pairs. These methods are summarized here and described in detail in the API documentation.
Although the underlying Persistit database is designed for highly concurrent multi-threaded operation, the Exchange object itself is not thread-safe. Each thread should create and use its own Exchange object(s) when accessing the database.
To create an Exchange you provide a Volume name (or alias) and a tree name in its constructor. The constructor will optionally create a new tree in that Volume if a tree having the specified name is not found. An application may construct an arbitrary number of Exchange objects. Creating a new Exchange has no effect on the database if the specified tree already exists. Tree creation is thread-safe: multiple threads concurrently constructing Exchanges using the same Tree name will safely result in the creation of only one new tree.
An Exchange is a moderately complex object that requires several thousand bytes of heap space. Memory-constrained applications should construct Exchanges in moderate numbers. An Exchange internally maintains some optimization information such that references to nearby Keys within a tree are accelerated. Performance may benefit from using a different Exchange for each area of the Tree being accessed.
Persistit offers Exchange pooling to avoid rapidly creating and destroying Exchange objects in multi-threaded applications. In particular, web applications may benefit from using the Exchange pool.
An Exchange is always associated with a com.persistit.Key and a com.persistit.Value. Typically you work with an Exchange in one of the following patterns: . Modify the Key, perform a fetch operation, and extract the Value. . Modify the Key, modify the Value, and then perform a store operation. . Modify the Key, and then perform a remove operation. . Optionally modify the Key, perform a traverse operation, then read the resulting Key and/or Value.
These four methods, plus a few other methods listed here, are the primary low-level interface to the database. Semantics are as follows:
fetch
|
Reads the stored value associated with this Exchange’s Key and modifies the Exchange’s Value to reflect that value. |
store
|
Inserts or replaces the key/value pair for the specified key in the Tree either by replacing the former value, if there was one, or inserting a new value. |
fetchAndStore
|
Fetches and then replaces the stored value. Upon completion, Value reflects the formerly stored value for the current Key. This operation is atomic, as opposed to sequential calls to fetch and store. |
remove, removeAll, removeKeyRange
|
Removes key/value pairs from the Tree. Versions of this method specify either a single key or a range of keys to be removed. |
fetchAndRemove
|
Fetches and then removes the stored value. Upon completion, Value reflects the formerly stored value for the current Key. This operation is atomic, as opposed to sequential calls to fetch and remove. |
traverse, next, previous
|
Modifies the Exchange’s Key and Value to reflect a successor or predecessor key within the tree. (See API documentation for com.persistit.Key for information on the order of traversal.) |
incrementValue
|
Atomically increments or decrements a long (64-bit integer) value associated with the current Key, and returns the modified value. If there is currently no value associated with the key then incrementValue creates one and assigns an initial value to it. This operation provides a convenient way for concurrent threads to safely allocate unique long integers without an explicit transaction scope. |
hasNext, hasPrevious
|
Indicates, without modifying the Exchange’s Value or Key objects, whether there is a successor or predecessor key in the Tree. |
getChangeCount
|
Number of times the Tree for this Exchange has changed. This count may be used as a reliable indicator of whether the Tree has changed since some earlier instant in time. For example, it is used to detect concurrent modifications by PersistitMap. |
Because Persistit permits concurrent operations by multiple threads, there is no guarantee that the underlying database will remain unchanged after any of these operations is completed. However, each of these methods operates atomically. That is, the inputs and outputs of each method are consistent with some valid state of the underlying Persistit backing store at some instant in time. The Value and Key objects for the Exchange represent that consistent state even if some other thread subsequently modifies the underlying database.
PersistitMap
Persistit provides an implementation of the java.util.SortedMap interface called com.persistit.PersistitMap. PersistitMap uses Persistit as its backing store, permitting large maps to be stored efficiently on disk using constant heap memory space.
Keys for PersistitMap must conform to the constraints described above under Keys. Values must conform to the constraints described for Values.
The constructor for PersistitMap takes an Exchange as its sole parameter. All key/value pairs of the Map are stored within the tree identified by this Exchange. The Key supplied by the Exchange becomes the root of a logical tree. For example:
Exchange ex = new Exchange("myVolume", "myTree", true); ex.append("USA").append("MA"); PersistitMap<String, String> map = new PersistitMap<String, String>(ex); map.put("Boston", "Hub");
places a key/value pair into the myTree with the concatenated key {"USA ","MA","Boston"} and a value of "Hub".
Because Persistit is designed for concurrent operation it is possible (and often intended) for the backing store of PersistitMap to be changed by other threads while a java.util.Iterator is in use. Generally the expected behavior for an Iterator on a Map collection view is to throw a ConcurrentModificationException if the underlying collection changes. This is known as fail-fast behavior. PersistitMap implements this behavior by throwing a ConcurrentModificationException in the event the Tree containing the map changes. An application can detect that the map may have changed due to a programming error in case the design contract calls for it to remain unchanged by catching this exception.
However, sometimes it may be desirable to use PersistitMap and its collections view interfaces to iterate across changing data. Internally, Persistit uses the traverse method to retrieve the next highest key in the key sort order in order to implement the Iterator’s hasNext and next methods. The result will depend on the content of the database at the instant these operations are performed. PersistitMap provides the method setAllowConcurrentModification to enable this behavior. By default, concurrent modifications are not allowed. === KeyFilter
A com.persistit.KeyFilter defines a subset of all possible key values. You can supply a KeyFilter to the traverse methods of an Exchange. You can also specify a KeyFilter for any Iterator returned by the collection views of a PersistitMap. In either case, the key/value pairs covered by traversing the database or iterating over the collection view are restricted to those selected by the KeyFilter.
Use of a KeyFilter is illustrated by the following code fragment:
Exchange ex = new Exchange("myVolume", "myTree", true); KeyFilter kf = new KeyFilter("{\"Bellini\":\"Busoni\"}"); ex.append(Key.BEFORE); while (ex.next(kf)) { System.out.println(ex.getKey().reset().decodeString()); }
This simple example emits the string-valued keys within myTree whose values fall alphabetically between “Bellini” and “Busoni”, inclusive.
Transactions
Persistit supports transactions with full isolation and optimistic concurrency control. An application may begin, commit or roll back the current transaction scope explicitly, executing multiple database operations in an atomic, consistent, isolated and (optionally) durable (ACID) manner. If the application does not explicitly define the scope of a transaction, each database operation implicitly runs within the scope of a separate transaction. Each Persistit transaction may optionally be committed to either memory or disk. Transactions committed to memory are much faster, but are not immediately durable. (See Transactions.)
Configuration
To initialize Persistit the embedding application invokes one of the initialize methods of com.persistit.Persistit, passing either a java.util.Properties object or the name of a properties file from which the Properties object derives its content. The following properties are defined for Persistit. Other properties may also reside in the Properties object or its backing file; Persistit simply ignores any property not listed here.
Logging API
Persistit is writes various diagnostic and informational messages to a log. By default, the log is written as text to the file persistit.log in the current working directory. However, a container application will usually have a logging architecture already in place, and Persistit provides a simple way to redirect its log output to the container application’s log. Adapters for Log4J and the Java Logging API are included; other logging systems are easy to adapt.