PHPackages                             lucinda/db - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. lucinda/db

ActiveLibrary[Database &amp; ORM](/categories/database)

lucinda/db
==========

Lucinda DB: Pure PHP Tag-Based Key-Value Store

v4.0.1(4w ago)118.1k1MITPHPPHP ^8.1

Since Aug 30Pushed 4w ago1 watchersCompare

[ Source](https://github.com/aherne/lucinda_db)[ Packagist](https://packagist.org/packages/lucinda/db)[ RSS](/packages/lucinda-db/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (2)Versions (13)Used By (0)

Lucinda DB: Pure PHP Tag-Based Key-Value Store
==============================================

[](#lucinda-db-pure-php-tag-based-key-value-store)

Table of contents:

- [About](#about)
    - [DATA](#data)
    - [KEY](#keys)
    - [VALUE](#values)
    - [SCHEMA](#schema)
- [Installation](#installation)
- [Configuration](#configuration)
- [Querying](#querying)
    - [Querying Entries](#querying-entries)
    - [Querying Schemas](#querying-schemas)
- [Maintenance](#maintenance)
    - [By Cron Job](#by-cron-job)
    - [By Console Command](#by-console-command)
- [Advanced Guide](#advanced-guide)
    - [Specializing Keys](#specializing-keys)
    - [Handling Race Conditions](#handling-race-conditions)
    - [Checking Schemas Health](#checking-schemas-health)
    - [Plugging Schema In](#pluging-schema-in)
    - [Plugging Schema Out](#pluging-schema-out)
    - [Deleting Entries by Tag](#deleting-entries-by-tag)
    - [Deleting Entries by Time](#deleting-entries-by-time)
    - [Deleting Entries by Capacity](#deleting-entries-by-capacity)
    - [Avoiding API Disadvantages](#avoiding-api-disadvantages)
- [Usage Examples](#examples)

About
-----

[](#about)

Lucinda DB is serverless KEY-VALUE store originally designed to help developers cache results of resource-intensive SQL queries based to criteria (aka TAG) query depends on. It is different from other KV stores by having KEYs self-generate based on TAGs query result depended on and VALUEs saved as individual JSON files (instead of RAM) named by KEY in one/more SCHEMAs. This brings following advantages:

- **ability of working without a server**: operating system on host machine, already optimized to manipulate files, becomes the server
- **ability of being platform agnostic**: a database specification that can be implemented in any programming language on any operating system
- **key standardization**: value of a KEY is generated according to a predictable rule based on value of TAGs it depends on
- **no entry duplication**: combination of TAGs will always be unique
- **easy maintenance**: entries can be queried by TAG, something impossible in standard KV stores that purely rely on RAM hash tables indexed by cumulative key
- **portability**: to transfer/backup database, it is as easy as copying schema folder(s)
- **scalability**: ability of database to be distributed on multiple disks in real time

API relies on an interplay of following concepts:

- **[DATA](#data)**: this is value of data to cache (eg: result of your SQL query or query combinations)
- **[TAG](#tag)**: criteria based on whome DATA was generated (eg: "users", "roles")
- **[KEY](#key)**: the key in KV store, whose name was generated automatically based on TAGs it depends on (eg: "roles\_users")
- **[VALUE](#value)**: the value in KV store present as json-ed DATA saved on disk as a separate file in SCHEMA folder named by KEY
- **[SCHEMA](#schema)**: folders/disks in which KV entries are stored

### Data

[](#data)

This is result to cache as VALUE in KV store, convertible to JSON.

Example query:

```
SELECT t1.name AS user, t3.name AS role
FROM users AS t1
INNER JOIN users__roles AS t2 ON t1.user_id = t2.user_id
INNER JOIN roles AS t3 ON t2.role_id = t3.id
WHERE t1.active = 1
```

Processed into following PHP array structure:

```
$data = ["John Doe"=>["Administrator"], "Jane Doe"=>["Assistant Manager", "Team Leader"]];
```

### Tag

[](#tag)

A tag corresponds to name of criteria based on whom [DATA](#data) was generated (eg: "users" and "roles" for example above). A tag's value must obey following requirements:

- must be lowercase
- can only contain a-z0-9 characters
- **-** sign is allowed as separator of multi-word names

### Key

[](#key)

A key is unique identifier of [DATA](#data) in KV store named by combination of [TAG](#tag)s data depended on (eg: "roles\_users" above) . To make things easier for maintenance, each finite combination of [TAG](#tag)s results into a single KEY, regardless of how they were ordered by caller! The rules based on whom key name is calculated are:

- checks if [TAG](#tag)s obey above specifications
- sorts [TAG](#tag)s alphabetically
- joins all [TAG](#tag)s using **\_** sign

Key creation is encapsulated by **[Lucinda\\DB\\Key](https://github.com/aherne/lucinda_db/blob/master/src/Key.php)** class. Usage example:

```
$object = new Lucinda\DB\Key(["users", "roles"]);
$key = $object->getValue(); // key will be "roles_users"
```

### Value

[](#value)

A value is a JSON-ed representation of [DATA](#data) saved as **.json** file named by [KEY](#key) inside [SCHEMA](#schema) folder according to following rules:

- [DATA](#data) must be json encodable

Value operations are encapsulated by **[Lucinda\\DB\\Value](https://github.com/aherne/lucinda_db/blob/master/src/Value.php)** class. Usage example:

```
$key = new Lucinda\DB\Key(["users", "roles"]); // initializes KEY
$value = new Lucinda\DB\Value("/usr/local/share/db", $key->getValue()); // instances VALUE by KEY and SCHEMA
$value->set($data); // saves DATA by KEY, creating a KEY.json file within SCHEMA
```

#### Load Balancing

[](#load-balancing)

For performance, consistency or scalability reasons, users can opt for VALUEs to be load balanced across multiple [SCHEMA](#schema)s. This is done by using **[Lucinda\\DB\\ValueDriver](https://github.com/aherne/lucinda_db/blob/master/src/ValueDriver.php)** instead, which wraps **[Lucinda\\DB\\Value](https://github.com/aherne/lucinda_db/blob/master/src/Value.php)** in order to insure that:

- all writes (set/delete) are evenly distributed among replicas
- all reads are done from random replica
- all race condition operations (increment/decrement) will be done in first replica, then distributed to all others

Usage example:

```
$key = new Lucinda\DB\ValueDriver(["schema1", "schema2"], ["users", "roles"]); //instances VALUE by KEY and SCHEMAs
$value->set($data); // saves DATA by KEY, creating a KEY.json file within all SCHEMAs
```

### Schema

[](#schema)

A schema is simply the folder where [VALUE](#value)s are saved. A schema can be a single folder or an array of replicas that can be located on different disks from same server or even different servers (eg: via symlinks). Class **[Lucinda\\DB\\Schema](https://github.com/aherne/lucinda_db/blob/master/src/Schema.php)** encapsulates operations one can perform on a single schema. Usage example:

```
$key = new Lucinda\DB\Schema("schema1"); // initializes SCHEMA
$value->deleteAll(); // deletes all VALUEs in SCHEMA
```

#### Load Balancing

[](#load-balancing-1)

For performance, consistency or scalability reasons, users can opt for SCHEMAs to be load balanced and evenly distributed. This is done via **[Lucinda\\DB\\SchemaDriver](https://github.com/aherne/lucinda_db/blob/master/src/SchemaDriver.php)** class that insures all schema operations are automatically reflected in replicas. Usage example:

```
$key = new Lucinda\DB\SchemaDriver(["schema1", "schema2"]); // initializes SCHEMAs
$value->deleteAll(); // deletes all VALUEs in all SCHEMAs
```

Installation
------------

[](#installation)

First choose a folder, then write this command there using console:

```
composer require lucinda/db
```

Then create *at least one* [SCHEMA](#schema) and follow [configuration](#configuration) guide in order to set it/them in XML required to configure API. Once latter is done, you will be able to query database using **[Lucinda\\DB\\Wrapper](https://github.com/aherne/lucinda_db/blob/master/src/Wrapper.php)** object. Usage example:

```
require 'vendor/autoload.php';

$object = new Lucinda\DB\Wrapper(XML_CONFIGURATION_PATH, DEVELOPMENT_ENVIRONMENT);
$value = $wrapper->getEntryDriver(["users", "roles"]);
$value->set(["John Doe"=>["Administrator"], "Jane Doe"=>["Assistant Manager", "Team Leader"]]);
```

API uses composer autoload, requires PHP 8.1+ and has no external dependencies except SimpleXML, DOM and SPL extensions. All code inside is 100% unit tested and developed on simplicity and elegance principles!

Configuration
-------------

[](#configuration)

To configure this API you must have a XML with a **lucinda\_db** tag inside:

```

      {SCHEMA}
      ...

	...

```

Where:

- **lucinda\_db**: (mandatory) holds database configuration
    - {ENVIRONMENT}: name of development environment (to be replaced with "local", "dev", "live", etc)
        - **schemas**: (mandatory) stores list of schemas to be load balanced, each as **schema** tag
            - **schema**: (mandatory) holds path to a single schema that takes part of load balancing scheme

Example:

```

          C:\db

          /usr/local/share/db
          /mnt/remote/db

```

Querying
--------

[](#querying)

Now that XML is configured, you can query entries or schemas via **[Lucinda\\DB\\Wrapper](https://github.com/aherne/lucinda_db/blob/master/src/Wrapper.php)** class and its methods available:

MethodArgumentsReturnsDescription\_\_constructstring $xmlFilePath, string $developmentEnvironmentvoidSets location of [configuration](#configuration) file along with development environment for later queryinggetEntryDriverstring\[\] $tags[Lucinda\\DB\\ValueDriver](https://github.com/aherne/lucinda_db/blob/master/src/ValueDriver.php)Gets object to query load-balanced entry list of tags key depends ongetSchemaDrivervoid[Lucinda\\DB\\SchemaDriver](https://github.com/aherne/lucinda_db/blob/master/src/SchemaDriver.php)Gets object to query distributed schemas### Querying Entries

[](#querying-entries)

Usage example employing **[Lucinda\\DB\\ValueDriver](https://github.com/aherne/lucinda_db/blob/master/src/ValueDriver.php)**:

```
$object = new Lucinda\DB\Wrapper("/var/www/html/myapp/configuration.xml", "local");
$driver = $wrapper->getEntryDriver(["users", "roles"]);
$driver->set(["John Doe"=>["Administrator"], "Jane Doe"=>["Assistant Manager", "Team Leader"]]);
```

Using **[Lucinda\\DB\\Value](https://github.com/aherne/lucinda_db/blob/master/src/Value.php)** directly is useful only if your app requires no load balancing, lies in one development environment only and needs only primitive maintenance. Usage example:

```
$key = new Lucinda\DB\Key(["users", "roles"]);
$value = new Lucinda\DB\Value("/usr/local/share/db", $key->getValue());
$value->set(["John Doe"=>["Administrator"], "Jane Doe"=>["Assistant Manager", "Team Leader"]]);
```

Both **[Lucinda\\DB\\Value](https://github.com/aherne/lucinda_db/blob/master/src/Value.php)** and its **[Lucinda\\DB\\ValueDriver](https://github.com/aherne/lucinda_db/blob/master/src/ValueDriver.php)** wrapper abide to a single contract defining blueprints of [VALUE](#value) operations through interface **[Lucinda\\DB\\ValueOperations](https://github.com/aherne/lucinda_db/blob/master/src/ValueOperations.php)**, which comes with following prototype methods:

MethodArgumentsReturnsDescriptionsetmixed $valuevoidSets entry valuegetvoidmixedGets entry valueexistsvoidboolChecks if entry existsincrementint $step = 1intIncrements **existing** entry value using [locking](#locking). Throws [Lucinda\\DB\\LockException](https://github.com/aherne/lucinda_db/blob/master/src/LockException.php) on race condition!
See: [Handling Race Conditions](#handling-race-conditions)decrementint $step = 1intDecrements **existing** entry value using [locking](#locking). Throws [Lucinda\\DB\\LockException](https://github.com/aherne/lucinda_db/blob/master/src/LockException.php) on race condition!
See: [Handling Race Conditions](#handling-race-conditions)deletevoidvoidDeletes entryAs one can see above, in case developers opt using **[Lucinda\\DB\\Value](https://github.com/aherne/lucinda_db/blob/master/src/Value.php)** directly, class **[Lucinda\\DB\\Key](https://github.com/aherne/lucinda_db/blob/master/src/Key.php)** needs to be instanced manually. It encapsulates creation of keys based on tags and defines following public methods:

MethodArgumentsReturnsDescription\_\_constructstring\[\] tagsvoidCompiles key based on [TAG](#tag)s. Throws [Lucinda\\DB\\KeyException](https://github.com/aherne/lucinda_db/blob/master/src/KeyException.php) if [TAG](#tag)s break naming rulesgetValuestringGets entry key compiled above.### Querying Schemas

[](#querying-schemas)

Usage example employing **[Lucinda\\DB\\SchemaDriver](https://github.com/aherne/lucinda_db/blob/master/src/SchemaDriver.php)**:

```
$object = new Lucinda\DB\Wrapper("/var/www/html/myapp/configuration.xml", "local");
$driver = $wrapper->getSchemaDriver();
$driver->deleteAll();
```

Using **[Lucinda\\DB\\Schema](https://github.com/aherne/lucinda_db/blob/master/src/Schema.php)** directly is useful only if your app requires no load balancing, lies in one development environment only and needs only primitive maintenance. Usage example:

```
$object = new Lucinda\DB\Schema("/usr/local/share/db");
$object->deleteAll();
```

Both **[Lucinda\\DB\\Schema](https://github.com/aherne/lucinda_db/blob/master/src/Schema.php)** and **[Lucinda\\DB\\SchemaDriver](https://github.com/aherne/lucinda_db/blob/master/src/SchemaDriver.php)** abide to a single contract defining blueprints of [SCHEMA](#schema) operations through interface **[Lucinda\\DB\\SchemaOperations](https://github.com/aherne/lucinda_db/blob/master/src/SchemaOperations.php)**, which comes with following prototype methods:

MethodArgumentsReturnsDescriptioncreatevoidboolCreates schema(s)existsvoidboolChecks if schema(s) existgetAllvoidstring\[\]Gets all keys in schema(s)getByTagstring $tagstring\[\]Gets all keys in schema(s) matching taggetCapacityvoidintGets number of entries in schema(s)deleteAllvoidintDeletes all entries in schema(s)dropvoidboolDrops all schema(s), deleting all entries in the processMaintenance
-----------

[](#maintenance)

Modern operating systems allow up to 4,294,967,295 files in one folder but you shouldn't go anywhere near that value! Just like MySQL running out of disk space, LucindaDB may additionally run out of [VALUE](#value)s in [SCHEMA](#schema), something that usually happens only when [specialization](#specialization) is used at massive scale.

To fix such cases class **[Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)** class was created, whose purpose is to do automated maintenance through following public methods:

MethodArgumentsReturnsDescription\_\_constructstring $xmlFilePath, string $developmentEnvironmentvoidSets location of [configuration](#configuration) file along with development environment for later queryingcheckHealthfloat $maximumWriteDuration[Lucinda\\DB\\SchemaStatus](https://github.com/aherne/lucinda_db/blob/master/src/SchemaStatus.php)\[string\]Performs health checks of all load balanced schemas and returns results as status by schema.
See: [Checking Schemas Health](#checking-schemas-health)plugInstring $schemavoidPlugs in schema to load-balanced DB without down times
See: [Plugging Schema In](#pluging-schema-in)plugOutstring $schemavoidPlugs out schema from load-balanced DB without down times
See: [Plugging Schema Out](#pluging-schema-out)deleteByTagstring $tagintDeletes all DB entries whose key matches [TAG](#tag)
See: [Deleting Entries by Tag](#deleting-entries-by-tag)deleteUntilint $secondsBeforeNowintDeletes all DB entries whose last modified time is more than #seconds old
See: [Deleting Entries by Time](#deleting-entries-by-time)deleteByCapacityint $minCapacity, int $maxCapacityintDeletes all DB entries by keeping schema at fixed max capacity range based on entry last modified time.
See: [Deleting Entries by Capacity](#deleting-entries-by-capacity)### By Cron Job

[](#by-cron-job)

This class should be used via a cron job whose periodicity depends on the chance of your project to get filled! Example:

```
require 'vendor/autoload.php';

$maintenance = new Lucinda\DB\DatabaseMaintenance(XML_CONFIGURATION_PATH, DEVELOPMENT_ENVIRONMENT);
// checks schema health and plugs out unresponsive ones
$statuses  = $maintenance->checkHealth();
foreach ($statuses as $schema=>$status) {
  if (in_array($status, [DatabaseMaintenance::STATUS_OFFLINE, DatabaseMaintenance::STATUS_UNRESPONSIVE])) {
    $maintenance->plugOut($schema);
  }
}
// performs delete of all entries older than one day
$maintenance->deleteUntil(time()-(24*3600));
```

### By Console Command

[](#by-console-command)

If maintenance involves just one operation it can be done without programming by calling **client.php** file bundled in API root. Console syntax:

```
php PATH_TO_CLIENT_PHP OPERATION ARGUMENTS
```

Where:

- PATH\_TO\_CLIENT\_PHP: absolute location of **client.php** file (example: */var/www/html/mysite/vendor/lucinda/db/client.php*)
- OPERATION: name of [Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php) method (example: *deleteUntil*)
- ARGUMENTS: arguments to call method above, separated by space (example: *3600*)

The greatest advantage of this solution is that it allows non-programmers (devops) to perform maintenance from command line. Example:

```
php /var/www/html/mysite/vendor/lucinda/db/client.php plugIn /my/new/schema
```

Advanced Guide
--------------

[](#advanced-guide)

### Specializing Keys

[](#specializing-keys)

Sometimes, different [VALUE](#value)s need to be produced for same [TAG](#tag) combination. This requires us to have different keys, while abiding to principles described in [KEY](#key) section at the same time. Solution is to add an extra *specializer* tag (eg: MD5 checksum of query) when creating key:

```
$object = new Lucinda\DB\Key(["users", "roles", md5($query)]);
$key = $object->getValue(); // key will be "54ed347f362bb056e4d6db0477bf19c9_roles_users"
```

As a rule, specialization should be avoided as much as possible, since it enlarges database and has a duplication potential (for example a simple extra space in query above would generate another key)!

### Handling Race Conditions

[](#handling-race-conditions)

A complicated problem in all databases is managing *race conditions*. What happens when a increment or decrement operation is ran in parallel? Let's imagine [DATA](#data) was 8 and increment is attempted at same moment Z by users X and Y:

```
# user X increments value at moment Z
$value->increment(1);
# user Y increments value at same moment Z
$value->increment(1);
```

Will end result be 10, as expected? The answer is no, because both processes got 8 to increment at same time! This situation is called a "race condition" and the only solution is to stack writes on that entry instead of letting them run in parallel.

For increment/decrement [VALUE](#value) operations respective file is locked for writes and unlocked only when value update completes. If a concurrent process tries to write to a still locked file, a **[Lucinda\\DB\\LockException](https://github.com/aherne/lucinda_db/blob/master/src/LockException.php)** is thrown. Instead of letting exception bubble, developers can catch it and retry after delay:

```
try {
  $value->increment(1);
} catch(Lucinda\DB\LockException $e) {
  usleep(100);
  $value->increment(1);
}
```

### Checking Schemas Health

[](#checking-schemas-health)

Method *checkHealth* @ class **[Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)** is to be used in checking the state of each replica [SCHEMA](#schema) and produce a [Lucinda\\DB\\SchemaStatus](https://github.com/aherne/lucinda_db/blob/master/src/SchemaStatus.php). The algorithm by which statuses are produced is:

- if folder (schema) doesn't exist, status is OFFLINE
- otherwise, if files can't be written to schema, status is UNRESPONSIVE
- otherwise, if file writes take longer than maximumWriteDuration, status is OVERLOADED
- otherwise, status is ONLINE

The end result of all checks will be returned as an array where key is schema and value is status found.

### Plugging Schema In

[](#plugging-schema-in)

Method *plugIn* @ class **[Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)** is to be used for plugging a [SCHEMA](#schema) to replicas without database down times. The algorithm used is:

- if [SCHEMA](#schema) doesn't exist or it is already plugged, a [Lucinda\\DB\\ConfigurationException](https://github.com/aherne/lucinda_db/blob/master/src/ConfigurationException.php) is thrown
- copies all files from first replica to [SCHEMA](#schema)
- plugs in [SCHEMA](#schema) into XML
- copies all remaining files from first replica to [SCHEMA](#schema) that may have been inserted to former as initial copy process was running

### Plugging Schema Out

[](#plugging-schema-out)

Method *plugOut* @ class **[Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)** is to be used for plugging out a [SCHEMA](#schema) to replicas without database down times. The algorithm used is:

- if [SCHEMA](#schema) doesn't exist or it is not plugged, a [Lucinda\\DB\\ConfigurationException](https://github.com/aherne/lucinda_db/blob/master/src/ConfigurationException.php) is thrown
- [SCHEMA](#schema) is removed from XML, which insures no further writes will occur
- all files are deleted in [SCHEMA](#schema)

### Deleting Entries by Tag

[](#deleting-entries-by-tag)

Method *deleteByTag* @ class **[Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)** is to be used for removing all [VALUE](#value)s whose [KEY](#key) matches [TAG](#tag) from all replicas. The algorithm used is:

- iterates entries in random replica [SCHEMA](#schema)
- if entry [KEY](#key) matches [TAG](#tag)
    - for each [SCHEMA](#schema) replica
        - deletes entry ([VALUE](#value)) by [KEY](#key) above

### Deleting Entries by Time

[](#deleting-entries-by-time)

Method *deleteUntil* @ class **[Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)** is to be used for removing all [VALUE](#value)s whose last modified time is more than $secondsBeforeNow old. The algorithm used is:

- iterates entries in random [SCHEMA](#schema) replica
- if entry [VALUE](#value) last modified time is more than $secondsBeforeNow old
    - remembers entry [KEY](#key)
    - for each [SCHEMA](#schema) replica
        - deletes entry ([VALUE](#value)) by [KEY](#key) above

### Deleting Entries by Capacity

[](#deleting-entries-by-capacity)

Method *deleteByCapacity* @ class **[Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)** is to be used to insure number of entries in database doesn't exceed $maxCapacity and, if it does, shrink it to $minCapacity by having older entries removed. The algorithm used is:

- iterates entries in random [SCHEMA](#schema) replica
- records entry [KEY](#key) to a fixed capacity **SplMaxHeap** sorted by [VALUE](#value)'s last modified time
- if heap reached $maxCapacity, pops head and deletes entry until $minCapacity is reached

### Avoiding API Disadvantages

[](#avoiding-api-disadvantages)

This API, being disk based, comes with its own disadvantages compared to standard RAM-based KV stores:

- *slightly reduced speed*: hard drives will always be slower than RAM, but considering how fast SSDs are this won't be a problem unless your app has very high thoroughput
- *no expiration for entries*: all entries inside, being separate files, persist forever unless specifically deleted. This can be solved by using [Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)!
- *not suitable for temporary/volatile data*: if your app expects database entries to be volatile (changing randomly) and [specializing keys](#specializing-keys) is required at massive scale, standard RAM-based KV stores are highly recommended
- *requires daemonized maintenance*: in case volatility is expected, a program must periodically remove old files in order prevent disk(s) getting full. . This can be solved by using [Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)!

It is always up for developers to decide which KV store model fits their application the best! More often than not you will need to employ multiple stores (eg: LucindaDB and Redis) to cover all usage cases (one for persistent query-able data, the other for volatile data).

Usage Examples
--------------

[](#usage-examples)

To see usage examples, these unit tests should be enough:

- [ValueDriverTest](https://github.com/aherne/lucinda_db/blob/master/tests/ValueDriverTest.php): testing **[Lucinda\\DB\\ValueDriver](https://github.com/aherne/lucinda_db/blob/master/src/ValueDriver.php)** operations
- [SchemaDriverTest](https://github.com/aherne/lucinda_db/blob/master/tests/SchemaDriverTest.php): testing **[Lucinda\\DB\\SchemaDriver](https://github.com/aherne/lucinda_db/blob/master/src/SchemaDriver.php)** operations
- [DatabaseMaintenanceTest](https://github.com/aherne/lucinda_db/blob/master/tests/DatabaseMaintenanceTest.php): testing **[Lucinda\\DB\\DatabaseMaintenance](https://github.com/aherne/lucinda_db/blob/master/src/DatabaseMaintenance.php)** operations

###  Health Score

53

—

FairBetter than 96% of packages

Maintenance94

Actively maintained with recent releases

Popularity22

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity71

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~191 days

Recently: every ~402 days

Total

12

Last Release

28d ago

Major Versions

v2.0.4 → v3.0.02022-01-01

v1.0.x-dev → v2.0.52022-01-08

v2.0.5 → v3.0.12022-06-18

v3.0.1 → v4.0.02026-06-05

v2.0.x-dev → v4.0.12026-06-05

PHP version history (3 changes)v2.0.0PHP ^7.1

v3.0.0PHP ^8.1

v1.0.x-devPHP ^7.1|^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/3382770?v=4)[Lucian Gabriel Popescu](/maintainers/aherne)[@aherne](https://github.com/aherne)

---

Top Contributors

[![aherne](https://avatars.githubusercontent.com/u/3382770?v=4)](https://github.com/aherne "aherne (21 commits)")

---

Tags

databasenosqlkey value store

### Embed Badge

![Health badge](/badges/lucinda-db/health.svg)

```
[![Health](https://phpackages.com/badges/lucinda-db/health.svg)](https://phpackages.com/packages/lucinda-db)
```

###  Alternatives

[datastax/php-driver

DataStax PHP Driver for Apache Cassandra

439529.4k19](/packages/datastax-php-driver)[triagens/arangodb

ArangoDB PHP client

186196.6k19](/packages/triagens-arangodb)[basho/riak

Official Riak client for PHP

158248.3k7](/packages/basho-riak)[tbolier/php-rethink-ql

A clean and solid RethinkDB driver for PHP.

5211.7k](/packages/tbolier-php-rethink-ql)[cubettech/lacassa

Cassandra based query builder for laravel.

358.6k](/packages/cubettech-lacassa)[mroosz/php-cassandra

A pure-PHP client for Apache Cassandra and ScyllaDB with support for CQL binary protocol v3, v4 and v5 (Cassandra 2.1+ incl. 3.x-5.x; ScyllaDB 6.2 and 2025.x), synchronous and asynchronous APIs, prepared statements, batches, result iterators, object mapping, SSL/TLS, and LZ4 compression.

217.4k3](/packages/mroosz-php-cassandra)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
