public class FallbackKVDatabase extends Object implements KVDatabase
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 RaftKVDatabase
s 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.
Constructor and Description |
---|
FallbackKVDatabase() |
Modifier and Type | Method and Description |
---|---|
protected RaftKVTransaction |
createAvailabilityCheckTransaction(RaftKVDatabase kvdb)
Create a Raft availability check transaction.
|
protected RaftKVTransaction |
createDestinationTransaction(RaftKVDatabase kvdb)
Create a Raft destination transaction.
|
protected Thread |
createExecutorThread(Runnable action,
int uniqueId)
Create the service thread used by this instance.
|
protected RaftKVTransaction |
createSourceTransaction(RaftKVDatabase kvdb)
Create a Raft source transaction.
|
FallbackKVTransaction |
createTransaction()
Create a new transaction.
|
FallbackKVTransaction |
createTransaction(Map<String,?> options)
Create a new transaction with the specified options.
|
int |
getCurrentTargetIndex()
Get the index of the currently active database.
|
FallbackTarget |
getFallbackTarget()
Get most preferred
FallbackTarget . |
List<FallbackTarget> |
getFallbackTargets()
Get the
FallbackTarget (s). |
int |
getInitialTargetIndex()
Get the configured target index to use when starting up for the very first time.
|
Date |
getLastStandaloneActiveTime()
Get the last time the standalone database was active.
|
int |
getMaximumTargetIndex()
Get the maximum allowed target index.
|
KVDatabase |
getStandaloneTarget()
Get the configured "standalone mode"
KVDatabase to be used when all FallbackTarget s are unavailable. |
File |
getStateFile()
Get this instance's persistent state file.
|
int |
getThreadPriority()
Get the configured internal service thread priority.
|
protected boolean |
isMigrationAllowed(int currTargetIndex,
int nextTargetIndex)
Subclass hook to veto an impending migration.
|
protected void |
migrationCompleted(int prevTargetIndex,
int currTargetIndex)
Subclass hook to be notified when a migration occurs.
|
void |
setFallbackTarget(FallbackTarget target)
Configure a single
FallbackTarget . |
void |
setFallbackTargets(List<? extends FallbackTarget> targets)
Configure multiple
FallbackTarget (s). |
void |
setInitialTargetIndex(int initialTargetIndex)
Configure the index of the currently active database when starting up for the very first time.
|
void |
setMaximumTargetIndex(int maximumTargetIndex)
Configure the maximum allowed target index.
|
void |
setStandaloneTarget(KVDatabase standaloneKV)
Configure the local "standalone mode"
KVDatabase to be used when all FallbackTarget s are unavailable. |
void |
setStateFile(File stateFile)
Configure this instance's persistent state file.
|
void |
setThreadPriority(int threadPriority)
Configure the priority of the internal service thread.
|
void |
start()
Start this instance.
|
void |
stop()
Stop this instance.
|
String |
toString() |
protected final Logger log
public File getStateFile()
public void setStateFile(File stateFile)
Required property.
stateFile
- file for persistent stateIllegalArgumentException
- if stateFile
is nullIllegalStateException
- if this instance is already startedpublic KVDatabase getStandaloneTarget()
KVDatabase
to be used when all FallbackTarget
s are unavailable.public void setStandaloneTarget(KVDatabase standaloneKV)
KVDatabase
to be used when all FallbackTarget
s are unavailable.standaloneKV
- "standalone mode" databaseIllegalArgumentException
- if standaloneKV
is nullIllegalStateException
- if this instance is already startedpublic FallbackTarget getFallbackTarget()
FallbackTarget
.
Targets will be sorted in order of increasing preference.
public List<FallbackTarget> getFallbackTargets()
FallbackTarget
(s).
Targets will be sorted in order of increasing preference.
public void setFallbackTarget(FallbackTarget target)
FallbackTarget
.target
- fallback targetIllegalArgumentException
- if target
is nullIllegalArgumentException
- if any target does not have a RaftKVDatabase
configuredIllegalStateException
- if this instance is already startedpublic void setFallbackTargets(List<? extends FallbackTarget> targets)
FallbackTarget
(s).
Targets should be sorted in order of increasing preference.
targets
- targets in order of increasing preferenceIllegalArgumentException
- if targets
is nullIllegalArgumentException
- if targets
is emptyIllegalArgumentException
- if any target is nullIllegalArgumentException
- if any target does not have a RaftKVDatabase
configuredIllegalStateException
- if this instance is already startedpublic int getInitialTargetIndex()
public void setInitialTargetIndex(int initialTargetIndex)
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.
initialTargetIndex
- initial target index, -1 meaning standalone mode; out of range values will be clippedpublic int getCurrentTargetIndex()
public void setMaximumTargetIndex(int maximumTargetIndex)
Default value is Integer.MAX_VALUE
.
maximumTargetIndex
- maximum target index, -1 meaning standalone mode; out of range values will be clippedpublic int getMaximumTargetIndex()
public Date getLastStandaloneActiveTime()
public void setThreadPriority(int threadPriority)
Default is -1, which means do not change thread priority from its default.
threadPriority
- internal service thread priority, or -1 to leave thread priority unchangedIllegalStateException
- if this instance is already startedIllegalArgumentException
- if threadPriority
is not -1 and not in the range
Thread.MIN_PRIORITY
to Thread.MAX_PRIORITY
public int getThreadPriority()
@PostConstruct public void start()
KVDatabase
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.
start
in interface KVDatabase
@PreDestroy public void stop()
KVDatabase
This method is idempotent: if this instance has not been started, or is already stopped, nothing happens.
stop
in interface KVDatabase
protected Thread createExecutorThread(Runnable action, int uniqueId)
The implementation in FallbackKVDatabase
simply instantiates a thread and sets the name.
Subclasses may override to set priority, etc.
action
- thread entry pointuniqueId
- unique ID for the thread which increments each time this instance is (re)startedpublic FallbackKVTransaction createTransaction()
KVDatabase
createTransaction
in interface KVDatabase
public FallbackKVTransaction createTransaction(Map<String,?> options)
KVDatabase
createTransaction
in interface KVDatabase
options
- optional transaction options; may be nullprotected boolean isMigrationAllowed(int currTargetIndex, int nextTargetIndex)
The implementation in FallbackKVDatabase
always returns true.
currTargetIndex
- current fallback target list index (before migration), or -1 for standalone modenextTargetIndex
- next fallback target list index (after migration), or -1 for standalone modeprotected void migrationCompleted(int prevTargetIndex, int currTargetIndex)
The implementation in FallbackKVDatabase
does nothing.
prevTargetIndex
- previous fallback target list index, or -1 for standalone modecurrTargetIndex
- current fallback target list index, or -1 for standalone modeprotected RaftKVTransaction createSourceTransaction(RaftKVDatabase kvdb)
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()
.
kvdb
- Raft databaseprotected RaftKVTransaction createDestinationTransaction(RaftKVDatabase kvdb)
The implementation in FallbackKVDatabase
just delegates to RaftKVDatabase.createTransaction()
.
kvdb
- Raft databaseprotected RaftKVTransaction createAvailabilityCheckTransaction(RaftKVDatabase kvdb)
The implementation in FallbackKVDatabase
just delegates to RaftKVDatabase.createTransaction()
.
kvdb
- Raft databaseCopyright © 2022. All rights reserved.