- Type Parameters:
 T- the type of data we'll be persisting (must extend fromDbData)
Engine 2 is a database engine that improves on the performance from the first database provided by Minum. It does this by using different strategies for disk persistence.
The mental model of the previous Minum database has been an in-memory data structure in which every change is eventually written to its own file on disk for persistence. Data changes affect just their relevant files. The benefit of this approach is extreme simplicity. It requires very little code, relying as it does on the operating system's file capabilities.
However, there are two performance problems with this approach. First is when the data changes are arriving at a high rate. In that situation, the in-memory portion keeps up to date, but the disk portion may lag by minutes. The second problem is start-up time. When the database starts, it reads files into memory. The database can read about 6,000 files a second in the best case. If there are a million data items, it would take about 160 seconds to load it into memory, which is far too long.
The new approach to disk persistence is to append each change to a file. Append-only file changes can be very fast. These append files are eventually consolidated into files partitioned by their index - data with indexes between 1 and 1000 go into one file, between 1001 and 2000 go into another, and so on.
Startup is magnitudes faster by this approach. What took the previous database 160 seconds to load requires only 2 seconds. Writes to disk are also faster. What would have taken several minutes to write should only take a few seconds now.
This new approach uses a different file structure than the previous. If it is desired to use the new engine on existing data, it is possible to convert the old data format to the new. Construct an instance of the new engine, pointing at the same name as the previous, and it will convert the data. If the previous call looked like this:
  Db photoDb = context.getDb("photos", Photograph.EMPTY);
   
  Then converting to the new database is just replacing it with the following line. Please, backup your database before this change.
 
     DbEngine2
  
Once the new engine starts up, it will notice the old file structure and convert it over. The methods and behaviors are mostly the same between the old and new engines, so the update should be straightforward.
(By the way, it *is* possible to convert back to the old file structure, by starting the database the old way again. Just be aware that each time the files are converted, it takes longer than normal to start the database)
However, something to note is that using the old database is still fine in many cases, particularly for prototypes or systems which do not contain large amounts of data. If your system is working fine, there is no need to change things.
- 
Field Summary
Fields inherited from class com.renomad.minum.database.AbstractDb
context, data, dbDirectory, emptyInstance, fileUtils, index, logger, partitioningMap, registeredIndexes - 
Constructor Summary
Constructors - 
Method Summary
Modifier and TypeMethodDescriptionvoidDelete datavoidflush()This command callsDatabaseAppender.flush(), which will force any in-memory-buffered data to be written to disk.getIndexedData(String indexName, String key) Given the name of a registered index (seeAbstractDb.registerIndex(String, Function)), use the key to find the collection of data that matches it.voidloadData()This is what loads the data from disk the first time someone needs it.booleanregisterIndex(String indexName, Function<T, String> keyObtainingFunction) Register an index in the database for higher performance data access.voidstop()This is here to match the contract ofDbbut all it does is tell the interior file writer to write its data to disk.voidstop(int count, int sleepTime) values()This method provides read capability for the values of a database.Write data to the database.Methods inherited from class com.renomad.minum.database.AbstractDb
addToIndexes, deleteFromMemory, findExactlyOne, findExactlyOne, getSetOfIndexes, processDataIndex, writeToMemory 
- 
Constructor Details
- 
DbEngine2
Constructs an in-memory disk-persisted database. Loading of data from disk happens at the first invocation of any command changing or requesting data, such aswrite(DbData),delete(DbData), orvalues(). See the private method loadData() for details.- Parameters:
 dbDirectory- this uniquely names your database, and also sets the directory name for this data. The expected use case is to name this after the data in question. For example, "users", or "accounts".context- used to provide important state data to several componentsinstance- an instance of theDbDataobject relevant for use in this database. Note that each database (that is, each instance of this class), focuses on just one data, which must be an implementation ofDbData.
 
 - 
 - 
Method Details
- 
write
Write data to the database. Use an index of 0 to store new data, and a positive non-zero value to update data.Example of adding new data to the database:
final var newSalt = StringUtils.generateSecureRandomString(10); final var hashedPassword = CryptoUtils.createPasswordHash(newPassword, newSalt); final var newUser = new User(0L, newUsername, hashedPassword, newSalt); userDb.write(newUser);Example of updating data:
// write the updated salted password to the database final var updatedUser = new User( user().getIndex(), user().getUsername(), hashedPassword, newSalt); userDb.write(updatedUser);- Specified by:
 writein classAbstractDb<T extends DbData<?>>- Parameters:
 newData- the data we are writing- Returns:
 - the data with its new index assigned.
 - Throws:
 DbException- if there is a failure to write
 - 
delete
Delete dataExample:
userDb.delete(user);- Specified by:
 deletein classAbstractDb<T extends DbData<?>>- Parameters:
 dataToDelete- the data we are serializing and writing- Throws:
 DbException- if there is a failure to delete
 - 
loadData
public void loadData()This is what loads the data from disk the first time someone needs it. Because it is locked, only one thread can enter at a time. The first one in will load the data, and the second will encounter a branch which skips loading.- Specified by:
 loadDatain classAbstractDb<T extends DbData<?>>
 - 
values
This method provides read capability for the values of a database.
The returned collection is a read-only view over the data, throughCollections.unmodifiableCollection(Collection)Example:
boolean doesUserAlreadyExist(String username) { return userDb.values().stream().anyMatch(x -> x.getUsername().equals(username)); }- Specified by:
 valuesin classAbstractDb<T extends DbData<?>>
 - 
registerIndex
Description copied from class:AbstractDbRegister an index in the database for higher performance data access.This command should be run immediately after database declaration, or more specifically, before any data is loaded from disk. Otherwise, it would be possible to skip indexing that data.
Example:final var myDatabase = context.getDb("photos", Photograph.EMPTY); myDatabase.registerIndex("url", photo -> photo.getUrl());- Overrides:
 registerIndexin classAbstractDb<T extends DbData<?>>- Parameters:
 indexName- a string used to distinguish this index. This string will be used again when requesting data in a method likeAbstractDb.getIndexedData(java.lang.String, java.lang.String)orAbstractDb.findExactlyOne(java.lang.String, java.lang.String)keyObtainingFunction- a function which obtains data from the data in this database, used to partition the data into groups (potentially up to a 1-to-1 correspondence between id and object)- Returns:
 - true if the registration succeeded
 
 - 
getIndexedData
Description copied from class:AbstractDbGiven the name of a registered index (seeAbstractDb.registerIndex(String, Function)), use the key to find the collection of data that matches it.- Overrides:
 getIndexedDatain classAbstractDb<T extends DbData<?>>- Parameters:
 indexName- the name of an indexkey- a string value that matches a partition calculated from the partition function provided toAbstractDb.registerIndex(String, Function)- Returns:
 - a collection of data, an empty collection if nothing found
 
 - 
flush
public void flush()This command callsDatabaseAppender.flush(), which will force any in-memory-buffered data to be written to disk. This is not commonly necessary to call for business purposes, but tests may require it if you want to be absolutely sure the data is written to disk at a particular moment. - 
stop
public void stop()This is here to match the contract ofDbbut all it does is tell the interior file writer to write its data to disk.- Specified by:
 stopin classAbstractDb<T extends DbData<?>>
 - 
stop
public void stop(int count, int sleepTime) - Specified by:
 stopin classAbstractDb<T extends DbData<?>>- Parameters:
 count- number of loops before we are done waiting for a clean close and instead crash the instance closed.sleepTime- how long to wait, in milliseconds, for each iteration of the waiting loop.
 
 -