Skip to content

Commit

Permalink
Implemented PERSIST, EXPIRE options
Browse files Browse the repository at this point in the history
Signed-off-by: Omkar Phansopkar <omkarphansopkar@gmail.com>
  • Loading branch information
OmkarPh committed Mar 26, 2024
1 parent 608a45c commit d29a483
Show file tree
Hide file tree
Showing 15 changed files with 185 additions and 42 deletions.
2 changes: 1 addition & 1 deletion .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ brews:
- tap:
owner: OmkarPh
name: homebrew-tap
name: redis-server-lite-test
name: redis-server-lite
homepage: "https://github.com/OmkarPh/redis-server-lite"
description: "A lightweight Redis server implementation in Go."
folder: Formula
Expand Down
55 changes: 40 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,64 @@

### Prerequisites

- Go 1.11 or later
- Go 1.22 or later
- redis-cli (for testing this implementation)

Installation guide - https://redis.io/docs/install/install-redis/

#### Setup
#### Try out

```bash
# Clone this repository
$ git clone https://github.com/OmkarPh/redis-lite.git
# Using brew
brew install omkarph/tap/redis-server-lite
redis-server-lite

# or

# Using release archvies
wget https://github.com/OmkarPh/test-redis-server-lite/releases/download/v0.0.1-test-actions-9/redis-server-lite_0.0.1-test-actions-9_darwin_amd64.tar.gz
mkdir redis-server-lite
tar -xf redis-server-lite_0.0.1-test-actions-9_darwin_arm64.tar.gz -C redis-server-lite
cd redis-server-lite
./redis-server-lite
```


# Go into the server directory
$ cd redis-lite
#### Local setup

```bash
# Clone this repository
git clone https://github.com/OmkarPh/redis-lite.git
cd redis-lite

# Run the server
$ go run .
go run .

# Build executible
$ go build -o build/redis-lite-server -v
# Build release executible
go build -o build/redis-lite-server -v
```

## Supported redis-cli commands
## Implemented redis-cli commands

| Command | Syntax | Example | |
|--------- |------------------- |------------------------------ |--- |
| SET | SET <key> <value> | redis-cli SET name Mark | |
| SET | SET <key> <value> | redis-cli SET name omkar | |
| GET | GET <key> | redis-cli GET name | |
| DEL | DEL key [key ...] | redis-cli DEL name<br/>redis-cli DEL name age | |
| INCR | INCR key | redis-cli INCR age | |
| DECR | DECR key | redis-cli DECR age | |
| EXISTS | EXISTS key [key ...] | redis-cli EXISTS name age | |
| EXPIRE | EXPIRE key seconds | redis-cli EXPIRE name 20 | |
| EXISTS | EXISTS key [key ...] | redis-cli EXISTS name<br/>redis-cli EXISTS name age | |
| EXPIRE | EXPIRE key seconds [NX / XX / GT / LT] | redis-cli EXPIRE name 20<br/>redis-cli EXPIRE name 20 NX | |
| PERSIST | PERSIST key | redis-cli PERSIST name | |
| TTL | TTL key | redis-cli TTL key | |
| DEL | DEL key [key ...] | redis-cli DEL name age | |
| TYPE | TYPE key | redis-cli TYPE name | |
| PING | PING | redis-cli PING | |
| ECHO | ECHO <message> | redis-cli ECHO "Hello world" | |

## redis-benchmarks
## Benchmarks

#### redis-server-lite
![redis-server-lite](assets/benchmarkLite.png)

#### Original redis-server
![redis-server](assets/benchmarkOg.png)
Binary file added assets/benchmarkLite.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/benchmarkOg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions core/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ var RedisActions = map[resp.ActionKey]Action{
resp.ACTION_EXISTS: &ExistsAction{},
resp.ACTION_TTL: &TtlAction{},
resp.ACTION_EXPIRE: &ExpireAction{},
resp.ACTION_PERSIST: &PersistAction{},
}
26 changes: 24 additions & 2 deletions core/actions/expire.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
type ExpireAction struct{}

func (action *ExpireAction) Execute(kvStore *store.KvStore, redisConfig *config.RedisConfig, args ...string) ([][]byte, error) {
if len(args) < 2 || len(args) > 3 {
if len(args) < 2 {
errString := "ERR wrong number of arguments for 'EXPIRE' command"
return [][]byte{resp.ResolveResponse(errString, resp.Response_ERRORS)}, errors.New(errString)
}
Expand All @@ -31,7 +31,29 @@ func (action *ExpireAction) Execute(kvStore *store.KvStore, redisConfig *config.
return [][]byte{resp.ResolveResponse(errString, resp.Response_ERRORS)}, errors.New(errString)
}

success, _ := (*kvStore).Expire(key, seconds)
expireOptions := store.ExpireOptions{
NX: false,
XX: false,
GT: false,
LT: false,
}

if len(args) > 2 {
for _, option := range args[2:] {
switch option {
case "NX":
expireOptions.NX = true
case "XX":
expireOptions.XX = true
case "GT":
expireOptions.GT = true
case "LT":
expireOptions.LT = true
}
}
}

success, _ := (*kvStore).Expire(key, seconds, expireOptions)

if success {
return [][]byte{resp.ResolveResponse(1, resp.Response_INTEGERS)}, nil
Expand Down
29 changes: 29 additions & 0 deletions core/actions/persist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package actions

import (
"errors"

"github.com/OmkarPh/redis-lite/config"
"github.com/OmkarPh/redis-lite/resp"
"github.com/OmkarPh/redis-lite/store"
"github.com/OmkarPh/redis-lite/utils"
)

type PersistAction struct{}

func (action *PersistAction) Execute(kvStore *store.KvStore, redisConfig *config.RedisConfig, args ...string) ([][]byte, error) {
if len(args) != 1 {
errString := "ERR wrong number of arguments for 'PERSIST' command"
return [][]byte{resp.ResolveResponse(errString, resp.Response_ERRORS)}, errors.New(errString)
}

key := utils.ResolvePossibleKeyDirectives(args[0])

success := (*kvStore).Persist(key)

if success {
return [][]byte{resp.ResolveResponse(1, resp.Response_INTEGERS)}, nil
} else {
return [][]byte{resp.ResolveResponse(0, resp.Response_INTEGERS)}, nil
}
}
12 changes: 11 additions & 1 deletion core/connHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,18 @@ func HandleConnection(conn net.Conn, redisConfig *config.RedisConfig, kvStore *s

// Reader for progressive command parser
reader := bufio.NewReader(bytes.NewBuffer(messageBuffer[:]))

identifierBytes, _ := reader.Peek(1)

// reader := bufio.NewReaderSize(conn, 1024)
// conn.SetReadDeadline(time.Now().Add(1 * time.Microsecond))
// identifierBytes, err := reader.Peek(1)
// conn.SetReadDeadline(time.Time{})
// read if exists
// if err != nil || len(identifierBytes) == 0 {
// continue
// }
// fmt.Printf("can read something in handler --%s--\n", string(identifierBytes))

requestType := ResolveRequestType(identifierBytes[0])
slog.Debug(fmt.Sprint("Serve request", requestType))

Expand Down
8 changes: 2 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ module github.com/OmkarPh/redis-lite

go 1.20

require github.com/google/uuid v1.6.0

require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
github.com/cespare/xxhash/v2 v2.2.0
github.com/google/uuid v1.6.0
)
13 changes: 0 additions & 13 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,17 +1,4 @@
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1 change: 1 addition & 0 deletions resp/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
ACTION_COMMAND ActionKey = "command"
ACTION_EXISTS ActionKey = "exists"
ACTION_EXPIRE ActionKey = "expire"
ACTION_PERSIST ActionKey = "persist"
ACTION_TTL ActionKey = "ttl"
)

Expand Down
4 changes: 4 additions & 0 deletions resp/respParser.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func resolveArrayRequest(reader bufio.Reader) []Command {
var commands []Command

for {
// conn.SetReadDeadline(time.Now().Add(1 * time.Microsecond))
// if _, err := reader.Peek(1); err != nil {
// break
// }
arrayIdentifier, err := reader.ReadByte()
if err != nil {
slog.Debug(fmt.Sprintf("Errr reading arrayidentifier: %b\n", arrayIdentifier))
Expand Down
9 changes: 8 additions & 1 deletion store/kvStore.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,20 @@ type StoredValue struct {
// Value interface{}
Expiry time.Time
}
type ExpireOptions struct {
NX bool
XX bool
GT bool
LT bool
}
type KvStore interface {
Get(key string) (string, bool)
Set(key string, value string)
Del(key string) bool
Incr(key string) (string, error)
Decr(key string) (string, error)
Expire(key string, seconds int64) (bool, error)
Expire(key string, seconds int64, options ExpireOptions) (bool, error)
Persist(key string) bool
Ttl(key string) int
}

Expand Down
35 changes: 33 additions & 2 deletions store/shardedKvStore.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (kvStore *ShardedKvStore) Del(key string) bool {
return existed
}

func (kvStore *ShardedKvStore) Expire(key string, seconds int64) (bool, error) {
func (kvStore *ShardedKvStore) Expire(key string, seconds int64, options ExpireOptions) (bool, error) {
shardIdx := kvStore.resolveShardIdx(key)
kvStore.mutex[shardIdx].Lock()
defer kvStore.mutex[shardIdx].Unlock()
Expand All @@ -88,6 +88,19 @@ func (kvStore *ShardedKvStore) Expire(key string, seconds int64) (bool, error) {
return false, errors.New("ERR key doesn't exist")
}

if options.NX && !existingData.Expiry.IsZero() {
return false, nil
}
if options.XX && existingData.Expiry.IsZero() {
return false, nil
}
if options.LT && !existingData.Expiry.IsZero() && newExpiry.After(existingData.Expiry) {
return false, nil
}
if options.GT && !existingData.Expiry.IsZero() && newExpiry.Before(existingData.Expiry) {
return false, nil
}

kvStore.kv_stores[shardIdx][key] = StoredValue{
Value: existingData.Value,
Expiry: newExpiry,
Expand All @@ -96,6 +109,24 @@ func (kvStore *ShardedKvStore) Expire(key string, seconds int64) (bool, error) {
return true, nil
}

func (kvStore *ShardedKvStore) Persist(key string) bool {
shardIdx := kvStore.resolveShardIdx(key)
kvStore.mutex[shardIdx].Lock()
defer kvStore.mutex[shardIdx].Unlock()

existingData, exists := kvStore.kv_stores[shardIdx][key]
if !exists || existingData.Expiry.IsZero() {
return false
}

kvStore.kv_stores[shardIdx][key] = StoredValue{
Value: existingData.Value,
Expiry: time.Time{},
}

return true
}

func (kvStore *ShardedKvStore) Ttl(key string) int {
shardIdx := kvStore.resolveShardIdx(key)

Expand All @@ -115,7 +146,7 @@ func (kvStore *ShardedKvStore) Ttl(key string) int {
if data.Expiry.IsZero() {
return -1
}
return int(data.Expiry.Sub(time.Now()).Abs().Seconds())
return int(time.Until(data.Expiry).Seconds())
}
return -2
}
Expand Down
32 changes: 31 additions & 1 deletion store/simpleKvStore.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (kvStore *SimpleKvStore) Del(key string) bool {
return existed
}

func (kvStore *SimpleKvStore) Expire(key string, seconds int64) (bool, error) {
func (kvStore *SimpleKvStore) Expire(key string, seconds int64, options ExpireOptions) (bool, error) {
kvStore.mutex.Lock()
defer kvStore.mutex.Unlock()

Expand All @@ -66,6 +66,19 @@ func (kvStore *SimpleKvStore) Expire(key string, seconds int64) (bool, error) {
return false, errors.New("ERR key doesn't exist")
}

if options.NX && !existingData.Expiry.IsZero() {
return false, nil
}
if options.XX && existingData.Expiry.IsZero() {
return false, nil
}
if options.LT && !existingData.Expiry.IsZero() && newExpiry.After(existingData.Expiry) {
return false, nil
}
if options.GT && !existingData.Expiry.IsZero() && newExpiry.Before(existingData.Expiry) {
return false, nil
}

kvStore.kv_store[key] = StoredValue{
Value: existingData.Value,
Expiry: newExpiry,
Expand All @@ -74,6 +87,23 @@ func (kvStore *SimpleKvStore) Expire(key string, seconds int64) (bool, error) {
return true, nil
}

func (kvStore *SimpleKvStore) Persist(key string) bool {
kvStore.mutex.Lock()
defer kvStore.mutex.Unlock()

existingData, exists := kvStore.kv_store[key]
if !exists {
return false
}

kvStore.kv_store[key] = StoredValue{
Value: existingData.Value,
Expiry: time.Time{},
}

return true
}

func (kvStore *SimpleKvStore) Ttl(key string) int {
/*
-2 => Key doesn't exist
Expand Down

0 comments on commit d29a483

Please sign in to comment.