mysql-redis
Version:
caching layer for mysql
217 lines (150 loc) • 6.43 kB
Markdown
# mysql-redis :rocket:
Transform your mysql server with `Redis` caching layer for [mysql](https://www.npmjs.com/package/mysql)/[mysql2](https://www.npmjs.com/package/mysql2).
- MysqlRedis checks if there is a cached result for the query in redis
- if not found in cache, it will retrieve data from mysql and on successful result cache it in redis for future queries
- if redis is unavailable or errors, query will be served by mysql
- zero external dependency (farmhash is now option after v0.7)
## Use-case
Use _along_ with mysql and redis. This is not a replacement of either. Use it with queries/stored procedures that only perform `select` and will return same result set every time.
- **No brainer** for retrieving static data, eg, `select * from countries`
- Data that will not be updated once created, typically timeseries data like chat messages, logs
- Use with caution where data may get updated in mysql as redis cache may be stale
### Hashing
The above is achieved by creating a unique hash for every query
"select 1+2" => #️⃣
In redis, the hash and query results are stored as key-value pair
#️⃣ => [{'1+2':3}]
#### Currently supported hash types are:
**md5hash** ⚡🗜️
MD5hash is the new default to prevent non mandatory dependency on farmhash
MD5 hash is generated without any external lib
**farmhash32** ⚡🗜️
Example redis key: _prefix._`2jNDCJ`
Fast!!! Over ~5 million hashes/s on reference machine. Most compact key. (if you have millions of possible queries, these hashes can collide :collision:)
**farmhash64**
Example redis key: _prefix._`DiHlF3yv0V$`
fast (~2 million hashes/sec on reference machine )
_farmhash32/64_ use Google's farmhash, non-crypto algorithm (if you have millions of possible queries, these hashes can collide :collision:)
**blake2b512** 🛡️
Example redis key: _prefix._`4KbMOx3xJi+7mJNy0tDbju6NY9uHqOroDsG4rYjpHK1mEwXJokls5Ofdjs7iDsn3cAtibgUkT8RDdpCE2phhiQ==`
Crypto safe, ~500k hashes/sec on reference machine.
Use it for caching millions of different queries (eg. chats, logs)
Note that the key is longer than farmhash32/64.
**full**
Matches full query string. Use this if you are paranoid or if your queries are smaller than blake2b512 hashes
Or you can **provide your own hash _per_ query**, eg, _prefix._`p.123` to represent `select * from person p where id = 123`
## Getting Started
### Pre-Requisites
mysql ([mysql](https://www.npmjs.com/package/mysql)/[mysql2](https://www.npmjs.com/package/mysql2)), and redis ([redis](https://www.npmjs.com/package/redis)/[ioredis](https://www.npmjs.com/package/ioredis)). Internally MysqlRedis relies on mysql/mysql2's `query` function and redis's `get` and `set` functions
For async/await api, you can use mysql2's promise api and [async-redis](https://www.npmjs.com/package/async-redis)
### Installing
`npm i mysql-redis --save`
### Usage
```
const { MysqlRedis, HashTypes, Caching } = require("mysql-redis");
// or if you use async await api
const { MysqlRedisAsync, HashTypes, Caching } = require("mysql-redis");
```
#### Creating an instance of MysqlRedis requires
- a mysql connection or pool (mysqlRedis will call it's query method when no cache found)
- redis connection (mysqlRedis will call its set and get methods)
- cache options (optional)
#### Creating an instance of MysqlRedisAsync requires
- a mysql connection or pool promise
`// Example from mysql2 docs: const poolPromise = mysql.createPool({ host:'localhost', user: 'root', database: 'test' }) .promise();`
- async redis
````
eg:
const asyncRedis = require("async-redis");
const redis = asyncRedis.createClient(redisOptions);
```
````
- cache options (optional)
import `HashTypes` and `Caching` from mysql-redis
```
const cacheOptions = {
expire: 2629746,// seconds, defaults to 30 days
keyPrefix: "sql.", // default
hashType: HashTypes.md5 //default
caching: Caching.CACHE //default
};
```
`hashType` can be
- `HashTypes.farmhash32`
- `HashTypes.farmhash64`
- `HashTypes.blake2b512`
- `HashTypes.full`
`caching` can be
- `Caching.CACHE` to get data from Redis if available, else mysql
- `Caching.SKIP` to get data from mysql, do not save to redis
- `Caching.REFRESH` to get data from mysql and save to redis
Options can be specified when creating mysqlRedis instance or overriden at the time of query.
At query time, you can also provide a custom hash as _cacheOptions.hash_ (ignores hashType if specified)
```
const mysqlRedis = new MysqlRedis(
mysqlConnection,
redisConnection,
cacheOptions
);
```
Now if you wish to get something from cache, just use mysqlRedis.query instead of your mysql connection's query. (Use your mysql connection normally to bypass cache)
```
mysqlRedis.query('select * from logs where id =?",["some-log-id"], (err,data,fields)=>{
console.log(data)
// if served by Redis, fields value is something like [ { cacheHit: 'sql.Dh9VSNbN5V$' } ]
// else mysql fields
});
```
or if you like promises, then:
```
const mysqlRedis = new MysqlRedisAsync(
mysqlConnection,
redisConnection,
cacheOptions
);
... in an async function ...
try{
[result,fields]=await mysqlRedis.query("select 1+?+?",[2,3]);
}catch(err){
// handle err
}
```
You can override cache options per query as below:
```
mysqlRedis.query('select * from logs where id =?",["some-log-id"],
{ //cache option
keyPrefix:'sql-abc-',
expire:3600,
hashType: HashTypes.farmhash64
//or hash: myHash <- provide your own
// caching: Caching.SKIP or Caching.REFRESH or Caching.CACHE
},
(err,data,fields)=>{
console.log(data)
// if served by Redis, fields value is something like [ { cacheHit: 'sql.Dh9VSNbN5V$' } ]
// else mysql fields
});
// promise api
[result,fields]=await mysqlRedis.query("select 1+?+?",[2,3],
{ //cache option
keyPrefix:'sql-abc-',
expire:3600,
hashType: HashTypes.farmhash64
//or hash: myHash <- provide your own
// caching: Caching.SKIP or Caching.REFRESH or Caching.CACHE
});
```
## Testing
- Start mysql and redis servers on your local (host 127.0.0.1, default ports)
- create user `test_user` in mysql, no need to give any grants
```
CREATE USER 'test_user'@'%' IDENTIFIED BY PASSWORD 'User@123';
```
- then run
`npm run test`
## Contributing
Feel free to fork/send PR
## Authors
- **Gi Singh**
## License
This project is licensed under the [MIT](./LICENSE).