Class FallbackKVDatabase

java.lang.Object
io.permazen.kv.raft.fallback.FallbackKVDatabase
All Implemented Interfaces:
KVDatabase

public class FallbackKVDatabase extends Object implements KVDatabase
A partition-tolerant KVDatabase that automatically migrates between a clustered RaftKVDatabase and a local, non-clustered "standalone mode" KVDatabase, based on availability of the Raft cluster.

A RaftKVDatabase requires that the local node be part of a cluster majority, otherwise, transactions cannot commit (even read-only ones) and application progress halts. This class adds partition tolerance to a RaftKVDatabase, by maintaining a separate private "standalone mode" KVDatabase that can be used in lieu of the normal RaftKVDatabase when the Raft cluster is unavailable.

Instances transparently and automatically switch over to standalone mode KVDatabase when they determine that the RaftKVDatabase is unavailable, and automatically switch back to using the RaftKVDatabase once it is available again. The rate of switching is limited by an enforced hysteresis; see FallbackTarget.getMinAvailableTime() and FallbackTarget.getMinUnavailableTime().

Of course, this sacrifices consistency. To address that, a configurable MergeStrategy is used to migrate the data when switching between normal mode and standalone mode. The MergeStrategy is given read-only access to the database being switched away from, and read-write access to the database being switched to; when switching away from the RaftKVDatabase, Consistency.EVENTUAL_COMMITTED is used to eliminate the requirement for communication with the rest of the cluster.

Although more exotic, instances support migrating between multiple RaftKVDatabases in a prioritized list. For example, the local node may be part of two independent RaftKVDatabase clusters: a higher priority one containing every node, and a lower priority one containing only nodes in the same data center as the local node. In any case, the "standalone mode" database (which is not a clustered database) always has the lowest priority.

Raft cluster availability is determined by FallbackTarget.checkAvailability(io.permazen.kv.raft.fallback.FallbackKVDatabase); subclasses may override the default implementation if desired.

  • Field Details

    • log

      protected final Logger log
  • Constructor Details

    • FallbackKVDatabase

      public FallbackKVDatabase()
  • Method Details

    • getStateFile

      public File getStateFile()
      Get this instance's persistent state file.
      Returns:
      file for persistent state
    • setStateFile

      public void setStateFile(File stateFile)
      Configure this instance's persistent state file.

      Required property.

      Parameters:
      stateFile - file for persistent state
      Throws:
      IllegalArgumentException - if stateFile is null
      IllegalStateException - if this instance is already started
    • getStandaloneTarget

      public KVDatabase getStandaloneTarget()
      Get the configured "standalone mode" KVDatabase to be used when all FallbackTargets are unavailable.
      Returns:
      "standalone mode" database
    • setStandaloneTarget

      public void setStandaloneTarget(KVDatabase standaloneKV)
      Configure the local "standalone mode" KVDatabase to be used when all FallbackTargets are unavailable.
      Parameters:
      standaloneKV - "standalone mode" database
      Throws:
      IllegalArgumentException - if standaloneKV is null
      IllegalStateException - if this instance is already started
    • getFallbackTarget

      public FallbackTarget getFallbackTarget()
      Get most preferred FallbackTarget.

      Targets will be sorted in order of increasing preference.

      Returns:
      top fallback target, or null if none are configured yet
    • getFallbackTargets

      public List<FallbackTarget> getFallbackTargets()
      Get the FallbackTarget(s).

      Targets will be sorted in order of increasing preference.

      Returns:
      list of one or more fallback targets; the returned list is a snapshot-in-time copy of each target
    • setFallbackTarget

      public void setFallbackTarget(FallbackTarget target)
      Configure a single FallbackTarget.
      Parameters:
      target - fallback target
      Throws:
      IllegalArgumentException - if target is null
      IllegalArgumentException - if any target does not have a RaftKVDatabase configured
      IllegalStateException - if this instance is already started
    • setFallbackTargets

      public void setFallbackTargets(List<? extends FallbackTarget> targets)
      Configure multiple FallbackTarget(s).

      Targets should be sorted in order of increasing preference.

      Parameters:
      targets - targets in order of increasing preference
      Throws:
      IllegalArgumentException - if targets is null
      IllegalArgumentException - if targets is empty
      IllegalArgumentException - if any target is null
      IllegalArgumentException - if any target does not have a RaftKVDatabase configured
      IllegalStateException - if this instance is already started
    • getInitialTargetIndex

      public int getInitialTargetIndex()
      Get the configured target index to use when starting up for the very first time.
      Returns:
      initial target index, with -1 meaning standalone mode
    • setInitialTargetIndex

      public void setInitialTargetIndex(int initialTargetIndex)
      Configure the index of the currently active database when starting up for the very first time. This value is only used on the initial startup; after that, the current fallback target is persisted across restarts.

      Default value is the most highly preferred target. Use -1 to change the default to standalone mode; this is appropriate when the Raft cluster is not yet configured on initial startup.

      Parameters:
      initialTargetIndex - initial target index, -1 meaning standalone mode; out of range values will be clipped
    • getCurrentTargetIndex

      public int getCurrentTargetIndex()
      Get the index of the currently active database.
      Returns:
      index into fallback target list, or -1 for standalone mode
    • setMaximumTargetIndex

      public void setMaximumTargetIndex(int maximumTargetIndex)
      Configure the maximum allowed target index. This is a dynamic control that can be changed at runtime to force this instance into a lower index target (or standalone mode) than it would otherwise be in.

      Default value is Integer.MAX_VALUE.

      Parameters:
      maximumTargetIndex - maximum target index, -1 meaning standalone mode; out of range values will be clipped
    • getMaximumTargetIndex

      public int getMaximumTargetIndex()
      Get the maximum allowed target index.
      Returns:
      maximum allowed target index; -1 for standalone mode
    • getLastStandaloneActiveTime

      public Date getLastStandaloneActiveTime()
      Get the last time the standalone database was active.
      Returns:
      last active time of the standalone database, or null if never active
    • setThreadPriority

      public void setThreadPriority(int threadPriority)
      Configure the priority of the internal service thread.

      Default is -1, which means do not change thread priority from its default.

      Parameters:
      threadPriority - internal service thread priority, or -1 to leave thread priority unchanged
      Throws:
      IllegalStateException - if this instance is already started
      IllegalArgumentException - if threadPriority is not -1 and not in the range Thread.MIN_PRIORITY to Thread.MAX_PRIORITY
    • getThreadPriority

      public int getThreadPriority()
      Get the configured internal service thread priority.
      Returns:
      internal service thread priority, or -1 if not configured
    • start

      @PostConstruct public void start()
      Description copied from interface: KVDatabase
      Start this instance. This method must be called prior to creating any transactions.

      This method is idempotent: if this instance is already started, nothing happens.

      Whether an instance that has been started and stopped can be restarted is implementation-dependent.

      Specified by:
      start in interface KVDatabase
    • stop

      @PreDestroy public void stop()
      Description copied from interface: KVDatabase
      Stop this instance.

      This method is idempotent: if this instance has not been started, or is already stopped, nothing happens.

      Specified by:
      stop in interface KVDatabase
    • createExecutorThread

      protected Thread createExecutorThread(Runnable action, int uniqueId)
      Create the service thread used by this instance.

      The implementation in FallbackKVDatabase simply instantiates a thread and sets the name. Subclasses may override to set priority, etc.

      Parameters:
      action - thread entry point
      uniqueId - unique ID for the thread which increments each time this instance is (re)started
      Returns:
      service thread for this instance
    • createTransaction

      public FallbackKVTransaction createTransaction()
      Description copied from interface: KVDatabase
      Create a new transaction.
      Specified by:
      createTransaction in interface KVDatabase
      Returns:
      newly created transaction
    • createTransaction

      public FallbackKVTransaction createTransaction(Map<String,?> options)
      Description copied from interface: KVDatabase
      Create a new transaction with the specified options.
      Specified by:
      createTransaction in interface KVDatabase
      Parameters:
      options - optional transaction options; may be null
      Returns:
      newly created transaction
    • isMigrationAllowed

      protected boolean isMigrationAllowed(int currTargetIndex, int nextTargetIndex)
      Subclass hook to veto an impending migration. This is invoked just prior to starting a migration. If this method returns false, the migration is deferred.

      The implementation in FallbackKVDatabase always returns true.

      Parameters:
      currTargetIndex - current fallback target list index (before migration), or -1 for standalone mode
      nextTargetIndex - next fallback target list index (after migration), or -1 for standalone mode
      Returns:
      true to allow migration to proceed, false to defer migration until later
    • migrationCompleted

      protected void migrationCompleted(int prevTargetIndex, int currTargetIndex)
      Subclass hook to be notified when a migration occurs. This method is invoked after each successful target change.

      The implementation in FallbackKVDatabase does nothing.

      Parameters:
      prevTargetIndex - previous fallback target list index, or -1 for standalone mode
      currTargetIndex - current fallback target list index, or -1 for standalone mode
    • toString

      public String toString()
      Overrides:
      toString in class Object
    • createSourceTransaction

      protected RaftKVTransaction createSourceTransaction(RaftKVDatabase kvdb)
      Create a Raft source transaction.

      The implementation in FallbackKVDatabase returns a new read-only transaction with consistency Consistency.EVENTUAL_COMMITTED. The combination of read-only and Consistency.EVENTUAL_COMMITTED is important, because this guarantees that the transaction will generate no network traffic (and not require any majority to exist) on commit().

      Parameters:
      kvdb - Raft database
      Returns:
      new transaction for availability check
    • createDestinationTransaction

      protected RaftKVTransaction createDestinationTransaction(RaftKVDatabase kvdb)
      Create a Raft destination transaction.

      The implementation in FallbackKVDatabase just delegates to RaftKVDatabase.createTransaction().

      Parameters:
      kvdb - Raft database
      Returns:
      new transaction for availability check
    • createAvailabilityCheckTransaction

      protected RaftKVTransaction createAvailabilityCheckTransaction(RaftKVDatabase kvdb)
      Create a Raft availability check transaction.

      The implementation in FallbackKVDatabase just delegates to RaftKVDatabase.createTransaction().

      Parameters:
      kvdb - Raft database
      Returns:
      new transaction for availability check