All Downloads are FREE. Search and download functionalities are using the official Maven repository.

swim.db.Database Maven / Gradle / Ivy

Go to download

Lock-free document store—optimized for high rate atomic state changes—that concurrently commits and compacts on-disk log-structured storage files without blocking parallel in-memory updates to associative B-tree maps, spatial Q-tree maps, sequential S-tree lists, and singleton U-tree values

The newest version!
// Copyright 2015-2024 Nstream, inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package swim.db;

import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import swim.collections.FingerTrieSeq;
import swim.collections.HashTrieMap;
import swim.concurrent.Cont;
import swim.concurrent.Stage;
import swim.concurrent.Sync;
import swim.math.Z2Form;
import swim.structure.Item;
import swim.structure.Num;
import swim.structure.Record;
import swim.structure.Text;
import swim.structure.Value;
import swim.util.Builder;
import swim.util.Cursor;
import swim.util.Murmur3;

public class Database {

  final Store store;
  volatile DatabaseDelegate delegate;
  volatile Germ germ;
  volatile int stem;
  volatile int post;
  int stablePost;
  volatile long version;
  volatile long diffSize;
  volatile long treeSize;
  final Trunk metaTrunk;
  final Trunk seedTrunk;
  volatile HashTrieMap> trunks;
  volatile HashTrieMap> sprouts;
  Value commitKey;
  Value evacuateKey;
  int evacuationPass;
  volatile int status;

  Database(Store store, int stem, long version) {
    this.store = store;
    this.stem = stem;
    this.post = store.oldestZoneId();
    this.stablePost = this.post;
    this.version = version;
    this.metaTrunk = new Trunk(this, Record.create(1).attr("meta"), null);
    this.metaTrunk.tree = new BTree(this.metaTrunk, 0, version, true, false);
    this.seedTrunk = new Trunk(this, Record.create(1).attr("seed"), null);
    this.seedTrunk.tree = new BTree(this.seedTrunk, 1, version, true, false);
    this.trunks = HashTrieMap.empty();
    this.sprouts = HashTrieMap.empty();

    final long time = System.currentTimeMillis();
    this.germ = new Germ(stem, version, time, time, this.seedTrunk.tree.rootRef().toValue());
  }

  Database(Store store, Germ germ) {
    this.store = store;
    this.germ = germ;
    this.stem = germ.stem();
    this.post = store.oldestZoneId();
    this.stablePost = this.post;
    this.version = germ.version() + 1L;
    this.metaTrunk = new Trunk(this, Record.create(1).attr("meta"), null);
    this.metaTrunk.tree = new BTree(this.metaTrunk, 0, this.version, true, false);
    this.seedTrunk = new Trunk(this, Record.create(1).attr("seed"), null);
    this.seedTrunk.tree = new BTree(this.seedTrunk, germ.seed(), true, false);
    this.trunks = HashTrieMap.empty();
    this.sprouts = HashTrieMap.empty();
  }

  Database(Store store) {
    this(store, 10, 1L);
  }

  public Store store() {
    return this.store;
  }

  public StoreSettings settings() {
    return this.store.settings();
  }

  public Stage stage() {
    return this.store.stage();
  }

  public DatabaseDelegate databaseDelegate() {
    return this.delegate;
  }

  public void setDatabaseDelegate(DatabaseDelegate delegate) {
    this.delegate = delegate;
  }

  public Germ germ() {
    return this.germ;
  }

  public int stem() {
    return this.stem;
  }

  public int post() {
    return this.post;
  }

  public long version() {
    return this.version;
  }

  public long diffSize() {
    return Database.DIFF_SIZE.get(this);
  }

  public long treeSize() {
    return this.treeSize;
  }

  public long treeCount() {
    return Trunk.TREE.get(this.seedTrunk).span();
  }

  public int trunkCount() {
    return this.trunks.size();
  }

  public boolean isCompacting() {
    return this.evacuationPass != 0;
  }

  public boolean open() {
    // Load the current database status, without ordering constraints.
    //int status = (int) Database.STATUS_VAR.getOpaque(this);
    int status = Database.STATUS.get(this);
    int state = status & Database.STATE_MASK;
    // Track whether or not this operation causes the database to open.
    boolean opened = false;
    // Track opening interrupts and failures.
    boolean interrupted = false;
    StoreException error = null;
    // Loop while the database has not been opened.
    do {
      if (state == Database.OPENED_STATE) {
        // The database has already been opened;
        // check if we're the thread that opened it.
        if (opened) {
          // Our thread caused the database to open.
          try {
            // Invoke database lifecycle callback.
            this.didOpen();
          } catch (Throwable cause) {
            if (Cont.isNonFatal(cause)) {
              if (error == null) {
                // Capture non-fatal exceptions.
                error = new StoreException("lifecycle callback failure", cause);
              }
            } else {
              // Rethrow fatal exceptions.
              throw cause;
            }
          }
        }
        // Because the initial status load was unordered, the database may
        // technically have already been closed. We don't bother ordering
        // our state check because all we can usefully guarantee is that
        // the database was at some point opened.
        break;
      } else if (state == Database.OPENING_STATE) {
        // The database is concurrently opening;
        // check if we're not the thread opening the database.
        if (!opened) {
          // Another thread is opening the database;
          // prepare to wait for the database to finish opening.
          synchronized (this) {
            // Loop while the database is transitioning.
            do {
              // Re-check database status before waiting, synchronizing with
              // concurrent databases.
              //status = (int) Database.STATUS_VAR.getAcquire(this);
              status = Database.STATUS.get(this);
              state = status & Database.STATE_MASK;
              // Ensure the database is still transitioning before waiting.
              if (state == Database.OPENING_STATE) {
                try {
                  this.wait(100);
                } catch (InterruptedException e) {
                  // Defer interrupt.
                  interrupted = true;
                }
              } else {
                // The database is no longer transitioning.
                break;
              }
            } while (true);
          }
        } else {
          // We're responsible for opening the database.
          try {
            // Invoke database lifecycle callback.
            this.onOpen();
          } catch (Throwable cause) {
            if (Cont.isNonFatal(cause)) {
              if (error == null) {
                // Capture non-fatal exceptions.
                error = new StoreException("lifecycle callback failure", cause);
              }
            } else {
              // Rethrow fatal exceptions.
              throw cause;
            }
          } finally {
            // Always finish openening the database.
            synchronized (this) {
              do {
                final int oldStatus = status;
                final int newStatus = (oldStatus & ~Database.STATE_MASK) | Database.OPENED_STATE;
                // Set the database state to opened, synchronizing with concurrent
                // status loads; linearization point for database open completion.
                //status = (int) Database.STATUS_VAR.compareAndExchangeAcquire(this, oldStatus, newStatus);
                status = Database.STATUS.compareAndSet(this, oldStatus, newStatus) ? oldStatus : Database.STATUS.get(this);
                state = status & Database.STATE_MASK;
                // Check if we succeeded at transitioning into the opened state.
                if (state == oldStatus) {
                  // Notify waiters that opening is complete.
                  this.notifyAll();
                  break;
                }
              } while (true);
            }
          }
        }
        // Re-check database status.
        continue;
      } else if (state == Database.INITIAL_STATE) {
        // The database has not yet been opened.
        final int oldStatus = status;
        final int newStatus = (oldStatus & ~Database.STATE_MASK) | Database.OPENING_STATE;
        // Try to initiate database opening, synchronizing with concurrent databases;
        // linearization point for database open.
        //status = (int) Database.STATUS_VAR.compareAndExchangeAcquire(this, oldStatus, newStatus);
        status = Database.STATUS.compareAndSet(this, oldStatus, newStatus) ? oldStatus : Database.STATUS.get(this);
        state = status & Database.STATE_MASK;
        // Check if we succeeded at transitioning into the opening state.
        if (status == oldStatus) {
          // This operation caused the opening of the database.
          opened = true;
          try {
            // Invoke database lifecycle callback.
            this.willOpen();
          } catch (Throwable cause) {
            if (Cont.isNonFatal(cause)) {
              // Capture non-fatal exceptions.
              error = new StoreException("lifecycle callback failure", cause);
            } else {
              // Rethrow fatal exceptions.
              throw cause;
            }
          }
          // Comtinue opening sequence.
          continue;
        } else {
          // CAS failed; try again.
          continue;
        }
      } else if (state == Database.CLOSING_STATE || state == Database.CLOSED_STATE) {
        // The database is either currently closing, or has already been closed.
        // Although not currently open, the contract that the database has been
        // opened is met, so we're ready to return.
        break;
      } else {
        throw new AssertionError(Integer.toString(state)); // unreachable
      }
    } while (true);
    if (interrupted) {
      // Resume interrupt.
      Thread.currentThread().interrupt();
    }
    if (error != null) {
      // Close the database.
      this.close();
      // Rethrow the caught exception.
      throw error;
    }
    // Return whether or not this operation caused the database to open.
    return opened;
  }

  /**
   * Lifecycle callback invoked upon entering the opening state.
   */
  protected void willOpen() {
    this.store.databaseWillOpen(this);
  }

  /**
   * Lifecycle callback invoked to actually open the database.
   */
  protected void onOpen() {
    final BTree seedTree = ((BTree) Trunk.TREE.get(this.seedTrunk));
    seedTree.load();
    long treeSize = 0L;
    final Cursor> seedCursor = seedTree.cursor();
    while (seedCursor.hasNext()) {
      final Value seedValue = seedCursor.next().getValue();
      final Value sizeValue = seedValue.get("root").head().toValue().get("area");
      treeSize += sizeValue.longValue(0L);
    }
    Database.TREE_SIZE.set(this, treeSize);
  }

  /**
   * Lifecycle callback invoked upon entering the opened state.
   */
  protected void didOpen() {
    this.store.databaseDidOpen(this);
  }

  public boolean close() {
    // Load the current database status, without ordering constraints.
    //int status = (int) Database.STATUS_VAR.getOpaque(this);
    int status = Database.STATUS.get(this);
    int state = status & Database.STATE_MASK;
    // Track whether or not this operation causes the database to close.
    boolean closed = false;
    // Track closing interrupts and failures.
    boolean interrupted = false;
    StoreException error = null;
    // Loop while the database has not been closed.
    do {
      if (state == Database.CLOSED_STATE) {
        // The database has already been closed;
        // check if we're the thread that closed it.
        if (closed) {
          // Our thread caused the database to close.
          try {
            // Invoke database lifecycle callback.
            this.didClose();
          } catch (Throwable cause) {
            if (Cont.isNonFatal(cause)) {
              if (error == null) {
                // Capture non-fatal exceptions.
                error = new StoreException("lifecycle callback failure", cause);
              }
            } else {
              // Rethrow fatal exceptions.
              throw cause;
            }
          }
        }
        // The initial status load was unordered, but that's ok because
        // the transition to the closed state is final.
        break;
      } else if (state == Database.CLOSING_STATE
              || state == Database.OPENING_STATE) {
        // The database is concurrently closing or opening; capture which.
        final int oldState = state;
        // Check if we're not the thread closing the database.
        if (!closed) {
          // Prepare to wait for the database to finish transitioning.
          synchronized (this) {
            // Loop while the database is transitioning.
            do {
              // Re-check database status before waiting, synchronizing with
              // concurrent databases.
              //status = (int) Database.STATUS_VAR.getAcquire(this);
              status = Database.STATUS.get(this);
              state = status & Database.STATE_MASK;
              // Ensure the database is still transitioning before waiting.
              if (state == oldState) {
                try {
                  this.wait(100);
                } catch (InterruptedException e) {
                  // Defer interrupt.
                  interrupted = true;
                }
              } else {
                // The database is no longer transitioning.
                break;
              }
            } while (true);
          }
        } else {
          // We're responsible for closing the database.
          try {
            // Invoke database lifecycle callback.
            this.onClose();
          } catch (Throwable cause) {
            if (Cont.isNonFatal(cause)) {
              if (error == null) {
                // Capture non-fatal exceptions.
                error = new StoreException("lifecycle callback failure", cause);
              }
            } else {
              // Rethrow fatal exceptions.
              throw cause;
            }
          } finally {
            // Always finish closing the database.
            synchronized (this) {
              do {
                final int oldStatus = status;
                final int newStatus = (oldStatus & ~Database.STATE_MASK) | Database.CLOSED_STATE;
                // Set the database state to closed, synchronizing with concurrent
                // status loads; linearization point for database close completion.
                //status = (int) Database.STATUS_VAR.compareAndExchangeAcquire(this, oldStatus, newStatus);
                status = Database.STATUS.compareAndSet(this, oldStatus, newStatus) ? oldStatus : Database.STATUS.get(this);
                state = status & Database.STATE_MASK;
                // Check if we succeeded at transitioning into the closed state.
                if (state == oldStatus) {
                  // Notify waiters that closing is complete.
                  this.notifyAll();
                  break;
                }
              } while (true);
            }
          }
        }
        // Re-check database status.
        continue;
      } else if (state == Database.OPENED_STATE) {
        // The database is open, and has not yet been closed.
        final int oldStatus = status;
        final int newStatus = (oldStatus & ~Database.STATE_MASK) | Database.CLOSING_STATE;
        // Try to initiate database closing, synchronizing with concurrent databases;
        // linearization point for database close.
        //status = (int) Database.STATUS_VAR.compareAndExchangeAcquire(this, oldStatus, newStatus);
        status = Database.STATUS.compareAndSet(this, oldStatus, newStatus) ? oldStatus : Database.STATUS.get(this);
        state = status & Database.STATE_MASK;
        // Check if we succeeded at transitioning into the closing state.
        if (status == oldStatus) {
          // This operation caused the closing of the database.
          closed = true;
          try {
            // Invoke database lifecycle callback.
            this.willClose();
          } catch (Throwable cause) {
            if (Cont.isNonFatal(cause)) {
              // Capture non-fatal exceptions.
              error = new StoreException("lifecycle callback failure", cause);
            } else {
              // Rethrow fatal exceptions.
              throw cause;
            }
          }
          // Continue closing sequence.
          continue;
        } else {
          // CAS failed; try again.
          continue;
        }
      } else if (state == Database.INITIAL_STATE) {
        // The database has not yet been started; to ensure an orderly
        // sequence of lifecycle state changes, we must first open
        // the database before we can close it.
        this.open();
        continue;
      } else {
        throw new AssertionError(Integer.toString(state)); // unreachable
      }
    } while (true);
    if (interrupted) {
      // Resume interrupt.
      Thread.currentThread().interrupt();
    }
    if (error != null) {
      // Rethrow the caught exception.
      throw error;
    }
    return closed;
  }

  /**
   * Lifecycle callback invoked upon entering the closing state.
   */
  protected void willClose() {
    // hook
  }

  /**
   * Lifecycle callback invoked to actually close the database.
   */
  protected void onClose() {
    // hook
  }

  /**
   * Lifecycle callback invoked upon entering the closed state.
   */
  protected void didClose() {
    this.store.databaseDidClose(this);
  }

  @SuppressWarnings("unchecked")
  public  Trunk openTrunk(Value name, TreeType treeType, boolean isResident, boolean isTransient) {
    Trunk newTrunk = null;
    boolean created = false;
    do {
      final HashTrieMap> oldTrunks = this.trunks;
      final Trunk oldTrunk = oldTrunks.get(name);
      if (oldTrunk == null) {
        if (newTrunk == null) {
          final Seed seed = Seed.fromValue(((BTree) Trunk.TREE.get(this.seedTrunk)).get(name));
          newTrunk = new Trunk(this, name, null);
          if (seed != null) {
            newTrunk.tree = (T) seed.treeType().treeFromSeed(newTrunk, seed, isResident, isTransient);
          } else if (treeType != null) {
            final int stem = Database.STEM.getAndIncrement(this);
            newTrunk.tree = (T) treeType.emptyTree(newTrunk, stem, this.version, isResident, isTransient);
            created = true;
          } else {
            return null;
          }
        }
        final HashTrieMap> newTrunks = oldTrunks.updated(name, (Trunk) newTrunk);
        if (Database.TRUNKS.compareAndSet(this, oldTrunks, newTrunks)) {
          if (created) {
            this.databaseDidCreateTrunk(newTrunk);
          }
          this.databaseDidOpenTrunk(newTrunk);
          return newTrunk;
        }
      } else {
        return (Trunk) oldTrunk;
      }
    } while (true);
  }

  public Trunk openBTreeTrunk(Value name, boolean isResident, boolean isTransient) {
    return this.openTrunk(name, TreeType.BTREE, isResident, isTransient);
  }

  public BTreeMap openBTreeMap(Value name, boolean isResident, boolean isTransient) {
    return new BTreeMap(this.openBTreeTrunk(name, isResident, isTransient));
  }

  public BTreeMap openBTreeMap(Value name) {
    return new BTreeMap(this.openBTreeTrunk(name, false, false));
  }

  public BTreeMap openBTreeMap(String name) {
    return new BTreeMap(this.openBTreeTrunk(Text.from(name), false, false));
  }

  public Trunk openQTreeTrunk(Value name, boolean isResident, boolean isTransient) {
    return this.openTrunk(name, TreeType.QTREE, isResident, isTransient);
  }

  public  QTreeMap openQTreeMap(Value name, Z2Form shapeForm, boolean isResident, boolean isTransient) {
    return new QTreeMap(this.openQTreeTrunk(name, isResident, isTransient), shapeForm);
  }

  public  QTreeMap openQTreeMap(Value name, Z2Form shapeForm) {
    return new QTreeMap(this.openQTreeTrunk(name, false, false), shapeForm);
  }

  public  QTreeMap openQTreeMap(String name, Z2Form shapeForm) {
    return new QTreeMap(this.openQTreeTrunk(Text.from(name), false, false), shapeForm);
  }

  public Trunk openSTreeTrunk(Value name, boolean isResident, boolean isTransient) {
    return this.openTrunk(name, TreeType.STREE, isResident, isTransient);
  }

  public STreeList openSTreeList(Value name, boolean isResident, boolean isTransient) {
    return new STreeList(this.openSTreeTrunk(name, isResident, isTransient));
  }

  public STreeList openSTreeList(Value name) {
    return new STreeList(this.openSTreeTrunk(name, false, false));
  }

  public STreeList openSTreeList(String name) {
    return new STreeList(this.openSTreeTrunk(Text.from(name), false, false));
  }

  private Trunk openUTreeTrunk(Value name, boolean isResident, boolean isTransient) {
    return this.openTrunk(name, TreeType.UTREE, isResident, isTransient);
  }

  public UTreeValue openUTreeValue(Value name) {
    return new UTreeValue(this.openUTreeTrunk(name, false, false));
  }

  public UTreeValue openUTreeValue(String name) {
    return new UTreeValue(this.openUTreeTrunk(Text.from(name), false, false));
  }

  public Trunk closeTrunk(Value name) {
    do {
      final HashTrieMap> oldTrunks = this.trunks;
      final HashTrieMap> newTrunks = oldTrunks.removed(name);
      if (Database.TRUNKS.compareAndSet(this, oldTrunks, newTrunks)) {
        final Trunk oldTrunk = oldTrunks.get(name);
        if (oldTrunk != null) {
          this.databaseDidCloseTrunk(oldTrunk);
        }
        return oldTrunk;
      }
    } while (true);
  }

  public void removeTree(Value name) {
    final Trunk oldTrunk = this.closeTrunk(name);
    do {
      final HashTrieMap> oldSprouts = Database.SPROUTS.get(this);
      final HashTrieMap> newSprouts = oldSprouts.removed(name);
      if (Database.SPROUTS.compareAndSet(this, oldSprouts, newSprouts)) {
        break;
      }
    } while (true);
    if (oldTrunk != null) {
      final int oldDiffSize = Trunk.DIFF_SIZE.getAndSet(oldTrunk, 0);
      Database.DIFF_SIZE.addAndGet(this, -((long) oldDiffSize));
    }
    do {
      final long newVersion = this.version;
      final BTree oldSeedTree = (BTree) Trunk.TREE.get(this.seedTrunk);
      final BTree newSeedTree = oldSeedTree.removed(name, newVersion, this.post);
      if (oldSeedTree != newSeedTree) {
        if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, newSeedTree)) {
          final Value seedValue = oldSeedTree.get(name);
          final Value sizeValue = seedValue.get("root").head().toValue().get("area");
          final long treeSize = sizeValue.longValue(0L);
          Database.TREE_SIZE.addAndGet(this, -treeSize);
          break;
        }
      } else {
        break;
      }
    } while (true);
  }

  public void commitAsync(Commit commit) {
    try {
      if (Database.DIFF_SIZE.get(this) > 0L) {
        this.store.commitAsync(commit);
      } else {
        commit.bind(null);
      }
    } catch (Throwable cause) {
      if (Cont.isNonFatal(cause)) {
        commit.trap(cause);
      } else {
        throw cause;
      }
    }
  }

  public Chunk commit(Commit commit) throws InterruptedException {
    if (Database.DIFF_SIZE.get(this) > 0L) {
      final Sync syncChunk = new Sync();
      this.commitAsync(commit.andThen(syncChunk));
      return syncChunk.await();
    } else {
      return null;
    }
  }

  public void shiftZone() {
    this.store.shiftZone();
  }

  public Chunk commitChunk(Commit commit, int zone, long base) {
    final long version = Database.VERSION.getAndIncrement(this);
    final long time = System.currentTimeMillis();
    int post = this.post;

    final Builder> treeBuilder = FingerTrieSeq.builder();
    final Builder> pageBuilder = FingerTrieSeq.builder();
    long step = base;

    if (this.evacuationPass == 0 && post < zone) {
      final long treeSize = this.treeSize;
      final long storeSize = this.store.size();
      final double treeFill = (double) treeSize / (double) storeSize;
      if (storeSize > this.settings().minCompactSize && treeFill < this.settings().minTreeFill) {
        post = zone;
        this.evacuateKey = null;
        this.evacuationPass = 1;
        this.post = post;
        this.databaseWillCompact(post);
        this.store.databaseWillCompact(this, post);
      }
    }

    final Value startCommitKey = this.commitKey;
    final int startCommitKeyHash = Murmur3.hash(startCommitKey);
    boolean commitKeyRollover = false;

    // Commit data pages
    Value prevCommitKey = startCommitKey;
    do {
      Value nextCommitKey;
      Trunk nextTrunk = null;

      // Get the next tree to commit
      do {
        final HashTrieMap> oldSprouts = Database.SPROUTS.get(this);
        nextCommitKey = oldSprouts.nextKey(prevCommitKey);
        boolean rollover = commitKeyRollover;
        if (nextCommitKey == null) {
          rollover = true;
          nextCommitKey = oldSprouts.nextKey(null);
        }
        if (nextCommitKey == null) {
          // Nothing to commit
          break;
        } else if (rollover) {
          final int nextKeyHash = Murmur3.hash(nextCommitKey);
          if (HashTrieMap.compareKeyHashes(startCommitKeyHash, nextKeyHash) <= 0) {
            // Cycled through all trees
            break;
          }
        }
        nextTrunk = oldSprouts.get(nextCommitKey);
        final HashTrieMap> newSprouts = oldSprouts.removed(nextCommitKey);
        if (Database.SPROUTS.compareAndSet(this, oldSprouts, newSprouts)) {
          this.commitKey = nextCommitKey;
          prevCommitKey = nextCommitKey;
          commitKeyRollover = rollover;
          break;
        }
      } while (true);

      // Commit the next tree
      if (nextTrunk != null) {
        do {
          final Tree oldTree = Trunk.TREE.get(nextTrunk);
          final Tree newTree = oldTree.committed(zone, step, version, time);
          if (oldTree == newTree) {
            break;
          } else if (Trunk.TREE.compareAndSet(nextTrunk, oldTree, newTree)) {
            Database.DIFF_SIZE.addAndGet(this, -((long) Trunk.DIFF_SIZE.getAndSet(nextTrunk, 0)));
            do {
              final BTree oldSeedTree = (BTree) Trunk.TREE.get(this.seedTrunk);
              final BTree newSeedTree = oldSeedTree.updated(nextTrunk.name, newTree.seed().toValue(), version, post);
              if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, newSeedTree)) {
                break;
              }
            } while (true);
            treeBuilder.add(newTree);
            newTree.buildDiff(version, pageBuilder);
            Database.TREE_SIZE.addAndGet(this, newTree.treeSize() - oldTree.treeSize());
            newTree.treeContext().treeDidCommit(newTree, oldTree);
            step += newTree.diffSize(version);
            break;
          }
        } while (true);
      } else {
        break;
      }

      if ((step - base) > this.settings().maxCommitSize) {
        break;
      }
      if (System.currentTimeMillis() - time > this.settings().maxCommitTime) {
        break;
      }
    } while (true);

    // Incrementally compact the store
    if (this.evacuationPass != 0) {
      final long startEvacuationBase = base;
      final long startEvacuationTime = System.currentTimeMillis();

      // Evacuate data pages
      do {
        final BTree seedTree = (BTree) Trunk.TREE.get(this.seedTrunk);
        final Value nextEvacuateKey = seedTree.nextKey(this.evacuateKey);
        this.evacuateKey = nextEvacuateKey;
        if (nextEvacuateKey == null) {
          this.evacuationPass += 1;
          if (this.evacuationPass <= 2) {
            // Finished evacuation pass
            break;
          } else {
            // Completed evacuation
            this.stablePost = post;
            this.evacuationPass = 0;
            this.databaseDidCompact(post);
            this.store.databaseDidCompact(this, post);
            break;
          }
        }
        final Value seedValue = seedTree.get(nextEvacuateKey);
        final Seed seed = Seed.fromValue(seedValue);
        Trunk trunk = new Trunk(this, nextEvacuateKey, null);
        Tree tree = seed.treeType().treeFromSeed(trunk, seed, false, false);
        trunk.tree = tree;
        final int treePost = tree.rootRef().post();
        if (treePost != 0 && treePost < post) {
          trunk = this.openTrunk(nextEvacuateKey, null, false, false);
          tree = trunk.tree;
          // Evacuate next tree
          do {
            final Tree oldTree = Trunk.TREE.get(trunk);
            final Tree newTree = oldTree.evacuated(post, version);
            if (oldTree == newTree) {
              break;
            } else if (Trunk.TREE.compareAndSet(trunk, oldTree, newTree)) {
              final int newPost = newTree.post();
              if (newPost == 0 || newPost >= post) {
                break;
              }
            }
          } while (true);
          // Commit next tree
          do {
            final Tree oldTree = Trunk.TREE.get(trunk);
            final Tree newTree = oldTree.committed(zone, step, version, time);
            if (oldTree == newTree) {
              break;
            } else if (Trunk.TREE.compareAndSet(trunk, oldTree, newTree)) {
              Database.DIFF_SIZE.addAndGet(this, -((long) Trunk.DIFF_SIZE.getAndSet(trunk, 0)));
              do {
                final BTree oldSeedTree = (BTree) Trunk.TREE.get(this.seedTrunk);
                final BTree newSeedTree = oldSeedTree.updated(trunk.name, newTree.seed().toValue(), version, post);
                if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, newSeedTree)) {
                  break;
                }
              } while (true);
              treeBuilder.add(newTree);
              newTree.buildDiff(version, pageBuilder);
              Database.TREE_SIZE.addAndGet(this, newTree.treeSize() - oldTree.treeSize());
              newTree.treeContext().treeDidCommit(newTree, oldTree);
              step += newTree.diffSize(version);
              break;
            }
          } while (true);
        }

        if ((step - startEvacuationBase) > this.settings().maxCompactSize) {
          break;
        }
        if (System.currentTimeMillis() - startEvacuationTime > this.settings().maxCompactTime) {
          break;
        }
      } while (true);
    }

    // Evacuate seed tree
    do {
      final BTree oldSeedTree = (BTree) Trunk.TREE.get(this.seedTrunk);
      final BTree newSeedTree = oldSeedTree.evacuated(post, version);
      if (oldSeedTree == newSeedTree) {
        break;
      } else if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, newSeedTree)) {
        final int newPost = newSeedTree.post();
        if (newPost == 0 || newPost >= post) {
          break;
        }
      }
    } while (true);

    // Commit seed tree
    BTree seedTree;
    do {
      final BTree oldSeedTree = (BTree) Trunk.TREE.get(this.seedTrunk);
      seedTree = oldSeedTree.committed(zone, step, version, time);
      if (oldSeedTree == seedTree) {
        break;
      } else if (Trunk.TREE.compareAndSet(this.seedTrunk, oldSeedTree, seedTree)) {
        seedTree.buildDiff(version, pageBuilder);
        step += seedTree.diffSize(version);
        break;
      }
    } while (true);

    // Evacuate meta tree
    do {
      final BTree oldMetaTree = (BTree) Trunk.TREE.get(this.seedTrunk);
      final BTree newMetaTree = oldMetaTree.evacuated(post, version);
      if (oldMetaTree == newMetaTree) {
        break;
      } else if (Trunk.TREE.compareAndSet(this.seedTrunk, oldMetaTree, newMetaTree)) {
        final int newPost = newMetaTree.post();
        if (newPost == 0 || newPost >= post) {
          break;
        }
      }
    } while (true);

    // Commit meta tree
    BTree metaTree;
    do {
      final BTree oldMetaTree = (BTree) Trunk.TREE.get(this.metaTrunk);
      metaTree = oldMetaTree.updated(Text.from("seed"), seedTree.rootRef().toValue(), version, post)
                            .updated(Text.from("stem"), Num.from(this.stem), version, post)
                            .updated(Text.from("time"), Num.from(time), version, post)
                            .committed(zone, step, version, time);
      if (oldMetaTree == metaTree) {
        break;
      } else if (Trunk.TREE.compareAndSet(this.metaTrunk, oldMetaTree, metaTree)) {
        metaTree.buildDiff(version, pageBuilder);
        step += metaTree.diffSize(version);
        break;
      }
    } while (true);

    final long size = step - base;
    final FingerTrieSeq trees = treeBuilder.bind();
    final FingerTrieSeq pages = pageBuilder.bind();

    final Germ germ = new Germ(this.stem, version, this.germ.created(),
                               time, seedTree.rootRef().toValue());
    this.germ = germ;
    return new Chunk(this, commit, post, zone, germ, size, trees, pages);
  }

  public void uncommit(long version) {
    final Cursor> seedCursor = ((BTree) Trunk.TREE.get(this.seedTrunk)).cursor();
    while (seedCursor.hasNext()) {
      final Value name = seedCursor.next().getKey();
      do {
        final long newVersion = this.version;
        final Trunk trunk = this.openTrunk(name, null, false, false);
        final Tree oldTree = Trunk.TREE.get(trunk);
        final Tree newTree = oldTree.uncommitted(version);
        if (oldTree != newTree) {
          if (trunk.updateTree(oldTree, newTree, newVersion)) {
            break;
          }
        } else {
          break;
        }
      } while (true);
    }
  }

  public Iterator trees() {
    return new DatabaseTreeIterator(((BTree) Trunk.TREE.get(this.seedTrunk)).cursor());
  }

  public Iterator leafs() {
    return new DatabaseLeafIterator(this, this.trees());
  }

  public void databaseDidCreateTrunk(Trunk trunk) {
    Database.TREE_SIZE.addAndGet(this, Trunk.TREE.get(trunk).treeSize());
  }

  public void databaseDidOpenTrunk(Trunk trunk) {
    this.store.treeDidOpen(this, Trunk.TREE.get(trunk));
  }

  Commit databaseWillCommit(Commit commit) {
    return this.store.databaseWillCommit(this, commit);
  }

  void databaseDidCommit(Chunk chunk) {
    this.store.databaseDidCommit(this, chunk);
    final DatabaseDelegate delegate = this.delegate;
    if (delegate != null) {
      delegate.databaseDidCommit(this, chunk);
    }
  }

  void databaseCommitDidFail(Throwable error) {
    this.store.databaseCommitDidFail(this, error);
  }

  void databaseWillCompact(int post) {
    this.store.databaseWillCompact(this, post);
  }

  void databaseDidCompact(int post) {
    this.store.databaseDidCompact(this, post);
    final DatabaseDelegate delegate = this.delegate;
    if (delegate != null) {
      delegate.databaseDidCompact(this, post);
    }
  }

  void databaseCompactDidFail(Throwable error) {
    this.store.databaseCompactDidFail(this, error);
  }

  @SuppressWarnings("unchecked")
  public void databaseDidUpdateTrunk(Trunk trunk, Tree newTree, Tree oldTree, long newVersion) {
    if (!newTree.isTransient()) {
      do {
        final HashTrieMap> oldSprouts = Database.SPROUTS.get(this);
        final HashTrieMap> newSprouts = oldSprouts.updated(trunk.name, (Trunk) trunk);
        if (Database.SPROUTS.compareAndSet(this, oldSprouts, newSprouts)) {
          break;
        }
      } while (true);
      final int newDiffSize = newTree.diffSize(newVersion);
      final int oldDiffSize = Trunk.DIFF_SIZE.getAndSet(trunk, newDiffSize);
      long deltaSize = (long) newDiffSize;
      if (!oldTree.isTransient()) {
        deltaSize -= (long) oldDiffSize;
      }
      Database.DIFF_SIZE.addAndGet(this, deltaSize);
    }
    Database.TREE_SIZE.addAndGet(this, newTree.treeSize() - oldTree.treeSize());
  }

  public void databaseDidCloseTrunk(Trunk trunk) {
    this.store.treeDidClose(this, Trunk.TREE.get(trunk));
  }

  static final int INITIAL_STATE = 0;
  static final int OPENING_STATE = 1;
  static final int OPENED_STATE = 2;
  static final int CLOSING_STATE = 3;
  static final int CLOSED_STATE = 4;

  static final int STATE_BITS = 3;
  static final int STATE_MASK = (1 << STATE_BITS) - 1;

  static final AtomicIntegerFieldUpdater STEM =
      AtomicIntegerFieldUpdater.newUpdater(Database.class, "stem");

  static final AtomicLongFieldUpdater VERSION =
      AtomicLongFieldUpdater.newUpdater(Database.class, "version");

  static final AtomicIntegerFieldUpdater POST =
      AtomicIntegerFieldUpdater.newUpdater(Database.class, "post");

  static final AtomicLongFieldUpdater DIFF_SIZE =
      AtomicLongFieldUpdater.newUpdater(Database.class, "diffSize");

  static final AtomicLongFieldUpdater TREE_SIZE =
      AtomicLongFieldUpdater.newUpdater(Database.class, "treeSize");

  static final AtomicIntegerFieldUpdater STATUS =
      AtomicIntegerFieldUpdater.newUpdater(Database.class, "status");

  @SuppressWarnings("unchecked")
  static final AtomicReferenceFieldUpdater>> TRUNKS =
      AtomicReferenceFieldUpdater.newUpdater(Database.class, (Class>>) (Class) HashTrieMap.class, "trunks");

  @SuppressWarnings("unchecked")
  static final AtomicReferenceFieldUpdater>> SPROUTS =
      AtomicReferenceFieldUpdater.newUpdater(Database.class, (Class>>) (Class) HashTrieMap.class, "sprouts");

}

final class DatabaseTreeIterator implements Iterator {

  final Cursor> seeds;

  DatabaseTreeIterator(Cursor> seeds) {
    this.seeds = seeds;
  }

  @Override
  public boolean hasNext() {
    return this.seeds.hasNext();
  }

  @Override
  public MetaTree next() {
    final Map.Entry slot = this.seeds.next();
    return MetaTree.fromValue(slot.getKey(), slot.getValue());
  }

  @Override
  public void remove() {
    throw new UnsupportedOperationException();
  }

}

final class DatabaseLeafIterator implements Iterator {

  final Database database;
  final Iterator trees;
  Trunk trunk;
  Cursor leafs;

  DatabaseLeafIterator(Database database, Iterator trees) {
    this.database = database;
    this.trees = trees;
  }

  @Override
  public boolean hasNext() {
    do {
      if (this.leafs != null) {
        if (this.leafs.hasNext()) {
          return true;
        } else {
          this.trunk = null;
          this.leafs = null;
        }
      }
      if (this.trees.hasNext()) {
        final MetaTree metaTree = this.trees.next();
        final Value name = metaTree.name;
        final TreeType type = metaTree.type;
        this.trunk = this.database.openTrunk(name, type, false, false);
        this.leafs = Trunk.TREE.get(this.trunk).cursor();
      } else {
        return false;
      }
    } while (true);
  }

  @Override
  public MetaLeaf next() {
    do {
      if (this.leafs != null) {
        if (this.leafs.hasNext()) {
          final Item leaf = (Item) this.leafs.next();
          final Value name = this.trunk.name;
          final TreeType type = Trunk.TREE.get(this.trunk).treeType();
          final Value key = leaf.key();
          final Value value = leaf.toValue();
          return new MetaLeaf(name, type, key, value);
        } else {
          this.trunk = null;
          this.leafs = null;
        }
      }
      if (this.trees.hasNext()) {
        final MetaTree metaTree = this.trees.next();
        final Value name = metaTree.name;
        final TreeType type = metaTree.type;
        this.trunk = this.database.openTrunk(name, type, false, false);
        this.leafs = Trunk.TREE.get(this.trunk).cursor();
      } else {
        throw new NoSuchElementException();
      }
    } while (true);
  }

  @Override
  public void remove() {
    throw new UnsupportedOperationException();
  }

}