redis-oxide
redis-oxide copied to clipboard
Multi-threaded implementation of redis written in rust
#+AUTHOR: David Briggs #+STARTUP: SHOWALL
- Redis Oxide
[[https://github.com/dpbriggs/redis-oxide/actions][https://github.com/dpbriggs/redis-oxide/workflows/Redis%20Oxide%20Pipeline/badge.svg]]
A multi-threaded implementation of redis written in rust 🦀.
This project is intended to be a drop-in replacement for redis. It's under construction at the moment.
[[https://i.imgur.com/8Zb0gu5.png][https://i.imgur.com/8Zb0gu5.png]]
** Design
=redis-oxide= is a black-box multi-threaded re-implementation of redis, backed by [[https://tokio.rs/][tokio]]. It features data-structure key-space/lock granularity, written entirely in safe rust. It's currently protocol compatible with redis, so you should be able to test it out with your favourite tools.
The multi-threaded nature has advantages and disadvantages. On one hand, =KEYS *= isn't particularly crippling for the server as it'll just keep a thread busy. On the other hand, there's some lock-juggling overhead, especially for writes, which messes with tokio.
** Building / Running
There's currently no official release for the project. You can compile and install it yourself with the following command:
: cargo install --git https://github.com/dpbriggs/redis-oxide
Note: This project requires the rust nightly. You can use [[https://rustup.rs/][rustup]] to install it.
Once it compiles you should be able to run it with =~ redis-oxide=.
If you wish to download and run it yourself, you can do the following
#+begin_example ~ git clone https://github.com/dpbriggs/redis-oxide ~ cd redis-oxide ~ cargo run #+end_example
Then use your favorite redis client. Eg. =redis-cli=:
#+begin_example ~ redis-cli 127.0.0.1:6379> set foo bar OK 127.0.0.1:6379> get foo "bar" #+end_example
Or using the redis library for python:
#+begin_src python import redis from pprint import pprint
r = redis.Redis() r.set('foobar', 'foobar') pprint(r.get('foobar'))
for i in range(100): r.rpush('list', i)
list_res = r.lrange('list', 0, -1)
pprint(list_res[0:3]) pprint(sum(map(int, list_res)))
total = 0 for i in range(100): total += int(r.lpop('list')) pprint(total) #+end_src
Which will print:
#+begin_src python b'foobar' [b'0', b'1', b'2'] 4950 4950 #+end_src
** Things left to do
*** Basic Datastructures
- [X] Keys
- [X] Sets
- [X] Lists
- [X] Hashes
- [ ] HyperLogLog
- [ ] Geo
- [-] Sorted Sets
- [X] Basic Functionality
- [ ] Still need some operations
- [ ] Strings
We should solidify the above before working on the more complex bits, but contributions are welcome :)
*** Redis Compatibility
- [X] Resp / server
- [ ] Database compatibility
- [ ] Unsure if this is a good thing -- may be better to port existing dumps.
- [ ] Blocking / Concurrent Ops (ttl/save-on-x-ops)
- [ ] CLI / config compatibility
- [ ] Authentication
** Contribution Guide
Conduct: =Have fun, please don't be a jerk.=
Contact: Make an issue or PR against this repo, or send an email to [email protected]=. If you know of a better forum, please suggest it!
NOTE: DO NOT USE THE REDIS SOURCE CODE IN ANY WAY!
This project is under active development, so things are a little messy.
The general design of =redis-oxide= is:
- A Command (=set foo bar=) is read off the socket and passed to the translate function in =src/ops.rs=.
- The parser generates a =RedisValue=, which is the lingua franca of =redis-oxide=.
- This gets converted to an =Ops::XYZ(XYZOps::Foobar(..))= enum object, which is consumed by the =op_interact= function.
- A macro is used to provide automate this.
- This operation is executed against the global =State= object (using the =op_interact= function)
- This will return an =ReturnValue= type, which is a more convenient form of =RedisValue=.
- This =ReturnValue= is converted and sent back to the client.
Therefore, if you want to do something like implement =hashes=, you will need to:
- Add a new struct member in =State=.
- You first define the type: =type KeyHash = DashMap<Key, HashMap<Key, Value>>=
- Then add it to State: =pub hashes: KeyHash=
- Define a new file for your data type, =src/hashes.rs=.
- Keep your type definitions in =src/types.rs=!
- Create an enum to track your commands, =op_variants! { HashOps, HGet(Key, Key), HSet(Key, Key, Value) }=
- Implement parsing for your enum in =src/ops.rs=.
- You should be able to follow the existing parsing infrastructure. Should just be extra entries in =translate_array= in =src/ops.rs=.
- You will need to add your return type to the =ok!= macro. Just copy/paste an existing line.
- You should return something like =ok!(HashOps::HSet(x, y, z))=.
- A stretch goal is to automate parsing.
- Implement a =async *_interact= for your type; I would follow existing implementations (eg. =src/keys.rs=).
- I would keep the redis docs open, and play around with the commands in the web console (or wherever) to determine behavior.
- Add a new match entry in the =async op_interact= function in =src/ops.rs=.
- Test it! (follow existing testing bits; eg. =src/keys.rs=).
- Please add the commands to the list below.
- If you're using emacs, just fire up the server and evaluate the babel block below (see =README.org= source)
- Alternatively, copy the script into a terminal and copy/paste the output below. (see raw =README.org=)
** Implemented Commands
#+BEGIN_SRC python :results output raw :format org :exports results import redis
r = redis.StrictRedis(decode_responses=True)
all_commands = r.execute_command('printcmds')
for command in all_commands: command_name, ops = command[0], command[1:] print(f'*** {command_name}\n') for op in ops: print(f'- ={op}=') print('\n') #+END_SRC
#+RESULTS: *** KeyOps
- =Set (Key, Value)=
- =MSet (RVec<(Key, Value)>)=
- =Get (Key)=
- =MGet (RVec<Key>)=
- =Del (RVec<Key>)=
- =Rename (Key, Key)=
- =RenameNx (Key, Key)=
*** ListOps
- =LIndex (Key, Index)=
- =LLen (Key)=
- =LPop (Key)=
- =LPush (Key, RVec<Value>)=
- =LPushX (Key, Value)=
- =LRange (Key, Index, Index)=
- =LSet (Key, Index, Value)=
- =LTrim (Key, Index, Index)=
- =RPop (Key)=
- =RPush (Key, RVec<Value>)=
- =RPushX (Key, Value)=
- =RPopLPush (Key, Key)=
- =BLPop (Key, UTimeout)=
- =BRPop (Key, UTimeout)=
*** HashOps
- =HGet (Key, Key)=
- =HSet (Key, Key, Value)=
- =HExists (Key, Key)=
- =HGetAll (Key)=
- =HMGet (Key, RVec<Key>)=
- =HKeys (Key)=
- =HMSet (Key, RVec<(Key, Value)>)=
- =HIncrBy (Key, Key, Count)=
- =HLen (Key)=
- =HDel (Key, RVec<Key>)=
- =HVals (Key)=
- =HStrLen (Key, Key)=
- =HSetNX (Key, Key, Value)=
*** SetOps
- =SAdd (Key, RVec<Value>)=
- =SCard (Key)=
- =SDiff (RVec<Value>)=
- =SDiffStore (Key, RVec<Value>)=
- =SInter (RVec<Value>)=
- =SInterStore (Key, RVec<Value>)=
- =SIsMember (Key, Value)=
- =SMembers (Key)=
- =SMove (Key, Key, Value)=
- =SPop (Key, Option<Count>)=
- =SRandMembers (Key, Option<Count>)=
- =SRem (Key, RVec<Value>)=
- =SUnion (RVec<Value>)=
- =SUnionStore (Key, RVec<Value>)=
*** ZSetOps
- =ZAdd (Key, RVec<(Score, Key)>)=
- =ZRem (Key, RVec<Key>)=
- =ZRange (Key, Score, Score)=
- =ZCard (Key)=
- =ZScore (Key, Key)=
- =ZPopMax (Key, Count)=
- =ZPopMin (Key, Count)=
- =ZRank (Key, Key)=
*** BloomOps
- =BInsert (Key, Value)=
- =BContains (Key, Value)=
*** StackOps
- =STPush (Key, Value)=
- =STPop (Key)=
- =STPeek (Key)=
- =STSize (Key)=
*** HyperLogLogOps
- =PfAdd (Key, RVec<Value>)=
- =PfCount (RVec<Key>)=
- =PfMerge (Key, RVec<Key>)=
*** MiscOps
- =Keys ()=
- =Exists (Vec<Key>)=
- =Pong ()=
- =FlushAll ()=
- =FlushDB ()=
- =Echo (Value)=
- =PrintCmds ()=
- =Select (Index)=
- =Script (Value)=
- =EmbeddedScript (Value, Vec<RedisValueRef>)=
- =Info ()=