JavaScript In Memory Store
JavaScript memory store for key/value with indexed lookups based on hash.
Install
First, install with npm.
npm install in-memory-store
Then include in your project either with ES6 imports
;
or require
const InMemoryStore = ;
or can also directly reference the compiled version from 'dist/in-memory-tree.js'.
Usage
InMemoryStore constructor takes 3 parameters keyFn[, items]
.
keyFn
: each item in the store requires a unique key, specify the function to get it from the item.
Creating and populating a store ...
// Create an empty storelet store = objkey;// and populate itstore;// Note: Populate can only be called on an empty store. // or add individual items one by onestore;// or add multiple items laterlet items = item1 item2 item3;store;
Removing and updating items ...
// remove an item from storestore;// remove by item keystore;// remove many itemslet items = item1 item2 item3;store; // update an itemstore;// update many itemslet items = item1 item2 item3;store;
All store queries require an index. You can use either a BinaryIndex or HashIndex. BinaryIndex keeps data sorted at all times and provide fast range searches (e.g. $lt, $gt). HashIndex is significantly faster to query for single items.
// Build a binary index (binary indexes are sorted at all times)let index = "firstLetter" objkey kittenbreed;// Build a hash index (hash indexes are very fast, but can't do range searches)let index = "firstLetter" kittenbreed;// Keys are case senstive, so normalise if neededlet index = "firstLetter" objkey kittenbreed;// Add an index to the store (populates it with items in the store if not already populated)store;
Most simple way to query the store is using the get and getOne functions.
// Get all entries with specified breedlet britishShorthair = store;// Get based on array of breedslet allSorts = store;// Get join of 2 indexeslet mixed = store;let old = store;let oldOrMixed = ...old ...mixed;
You can also use the find()
function to execute CouchDB style Mango queries. Only $and, $or are currently supported combinators. $lt, $gt, $gte, $lte, $eq, $in
operators are all supported.
// Using implicit $and with implicit $inlet oldMixed = store;// Using $or with implicit $inlet oldOrBreed = store;// Using $or with explicit $in and $eqlet oldOrBreed = store;// Get all kittens that are 3 months old (most simple form)let allSorts = store;// Get all kittens age more than 2 monthslet allSorts = store;// Get all kittens with age less than or equal to 3let allSorts = store;
Rebuilding and cleaning up a store ...
// Rebuild the store with new items keeping the same indexes and keyFnstore; // Destroy the store completely (removes all indexes and keyFn)store;
Running the example project
The example project is a very simple address book. It shows building an index of people by first letter of their last name and using the index to filter.
git clone https://github.com/badbod99/in-memory-storecd in-memory-storenpm installnpm run buildnpm run example
Then open a web browser to http://localhost:8080
Using AVLIndex instead of BinaryIndex
HashIndex, BinaryIndex and AVLIndex are packaged with in-memory-store. The AVL dependency is external. Include AVL globally with a CDN or using Webpack/Browserify/Rollup in your project to use.
AVL provides similar read, poor range search, but much faster insert performance for large datasets. It's based on an AVL Tree and has to perform in order traversal for range searches.
Performance
Performance testing is included with Benchmark.
Test definition:
- Define object as
{id: X, rnd1: Y, rnd2: Z}
where X=1 to 10,000 Y=rnd(1 to 500) and Z=rnd(1 to 500). - Create index (of each type) on Y (so items grouped by Y).
- Create 10,000 objects as above in each index
- Find each grouped Y (all 500 of them), 20 times over (10,000 find operations)
- Find all values where key > Y (all 500 of them), 20 times over (10,000 find operations) **
- Find all values where key <=> Y (all 500 of them), 20 times over (10,000 find operations) **
- Remove all items (1-10,000) from each index
** RBIndex omitted and library does not contain functions suitable for range searches. ** Unfortunately AVL does not include lt/gte. It does provide range, but calls a function for each entry, so very slow. AVL could potentially be entended to include native range return.
Different index types tested
- HashIndex - Backed by a javascript Map object. Insert and Read is
very fast. - BinaryIndex is an array backed binary search index. Performance is not far off that of the Tree structures, but it allows simple, fast range results to be returned as sorted arrays.
- RBIndex - Index built on Red Black Tree from bintrees.
- AVLIndex - Index built on AVLIndex from AVL.
Results
Insert RBIndex x 740 ops/sec ±1.11% AVLIndex x 1,258 ops/sec ±0.72% BinaryIndex x 689 ops/sec ±2.95% HashIndex x 2,025 ops/sec ±2.89% - Fastest is HashIndex Random read RBIndex x 1,013 ops/sec ±2.53% AVLIndex x 2,223 ops/sec ±1.01% BinaryIndex x 2,094 ops/sec ±1.23% HashIndex x 175,833 ops/sec ±2.20% - Fastest is HashIndex gt read AVLIndex x 9.74 ops/sec ±2.92% BinaryIndex x 87.02 ops/sec ±3.25% HashIndex x 25.50 ops/sec ±1.37% - Fastest is BinaryIndex lte read AVLIndex x 18.77 ops/sec ±1.36% BinaryIndex x 87.51 ops/sec ±1.75% HashIndex x 24.28 ops/sec ±3.22% - Fastest is BinaryIndex Remove RBIndex x 15,446 ops/sec ±1.81% AVLIndex x 11,354 ops/sec ±1.46% BinaryIndex x 9,379 ops/sec ±0.79% HashIndex x 7,641 ops/sec ±2.08% - Fastest is RBIndex