diff --git a/README.md b/README.md index 6f271be..f39188c 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ Redix isn't Redix is ========== - Simple `key => value` storage that speaks redis protocol. -- A database, you can store any size of data till your postgres db be down, or till your disk free size is about to be zero. +- A database which means you can store any size of data till your storage free size is about to be zero. - Ready to be abused. - Nested Large Hash-table. -- +- `Async` (all writes happen in the background), or `Sync` it won't respond to the client before writing to the internal datastore Core Commands ============= diff --git a/config.master.env b/config.master.env deleted file mode 100644 index 06c8a14..0000000 --- a/config.master.env +++ /dev/null @@ -1,14 +0,0 @@ -# the instance (node) role -INSTANCE_ROLE="master" - -# the instance (node) listen addr for RESP protocol -# RESP protocol is shorthand ffor REdis Serialization Protocol -INSTANCE_RESP_LISTEN_ADDR=":4000" - -# the instance (node) listen addr for HTTP protocol -INSTANCE_HTTP_LISTEN_ADDR=":8000" - -# the directory used to store redix data/states -DATA_DIR="${HOME}/playground/alash3al/redix/redixdata/master" - -WAL_MAX_SIZE="1kb" \ No newline at end of file diff --git a/config.replica.env b/config.replica.env deleted file mode 100644 index 1867fcf..0000000 --- a/config.replica.env +++ /dev/null @@ -1,17 +0,0 @@ -# the node role -INSTANCE_ROLE="replica" - -# the node listen addr -INSTANCE_RESP_LISTEN_ADDR=":4001" - -# the master resp connection string -MASTER_RESP_DSN="redis://localhost:4000" - -# the master http base url -MASTER_HTTP_BASE_URL="http://localhost:8000" - -# the instance (node) listen addr for HTTP protocol -INSTANCE_HTTP_LISTEN_ADDR=":8001" - -# the directory used to store redix data/states -DATA_DIR="${HOME}/playground/alash3al/redix/redixdata/replicas/${REPLICA_NAME}" diff --git a/internals/config/config.go b/internals/config/config.go index 11cf0c4..b57c993 100644 --- a/internals/config/config.go +++ b/internals/config/config.go @@ -11,8 +11,9 @@ import ( type Config struct { Server struct { Redis struct { - ListenAddr string `hcl:"listen"` - MaxConns int64 `hcl:"max_connections"` + ListenAddr string `hcl:"listen"` + AsyncWrites bool `hcl:"async"` + MaxConns int64 `hcl:"max_connections"` } `hcl:"redis,block"` } `hcl:"server,block"` diff --git a/internals/datastore/contract/engine.go b/internals/datastore/contract/engine.go index 16a667a..b73aedd 100644 --- a/internals/datastore/contract/engine.go +++ b/internals/datastore/contract/engine.go @@ -10,6 +10,7 @@ type Engine interface { Open(string) error Close() error Write(*WriteInput) (*WriteOutput, error) + // BatchWrite([]*WriteInput) error Read(*ReadInput) (*ReadOutput, error) Iterate(*IteratorOpts) error } @@ -44,6 +45,7 @@ type ReadOutput struct { TTL time.Duration } +// IteratorOpts represents the itrator options type IteratorOpts struct { Prefix []byte Callback func(*ReadOutput) error diff --git a/internals/redis/commands/context.go b/internals/redis/commands/context.go new file mode 100644 index 0000000..2fa7946 --- /dev/null +++ b/internals/redis/commands/context.go @@ -0,0 +1,55 @@ +package commands + +import ( + "bytes" + "strings" + "sync" + + "github.com/alash3al/redix/internals/config" + "github.com/alash3al/redix/internals/datastore/contract" + "github.com/tidwall/redcon" +) + +// Context represents the command context +type Context struct { + Conn redcon.Conn + Engine contract.Engine + Cfg *config.Config + Argv [][]byte + Argc int + + sync.RWMutex +} + +// Session fetches the current session map +func (c *Context) Session() map[string]interface{} { + c.RLock() + m := c.Conn.Context().(map[string]interface{}) + c.RUnlock() + + return m +} + +// SessionSet set a k-v into the current session +func (c *Context) SessionSet(k string, v interface{}) { + c.Lock() + + m := c.Conn.Context().(map[string]interface{}) + m[k] = v + c.Conn.SetContext(m) + + c.Unlock() +} + +// SessionGet fetches a value from the current session +func (c *Context) SessionGet(k string) (interface{}, bool) { + val, ok := c.Session()[k] + + return val, ok +} + +// AbsoluteKeyPath returns the full key path relative to the namespace the namespace +func (c *Context) AbsoluteKeyPath(k ...[]byte) []byte { + ns, _ := c.SessionGet("namespace") + return []byte(ns.(string) + strings.TrimLeft(string(bytes.Join(k, []byte("/"))), "/")) +} diff --git a/internals/redis/commands/core.go b/internals/redis/commands/core_commands.go similarity index 82% rename from internals/redis/commands/core.go rename to internals/redis/commands/core_commands.go index 81e159e..bdfcfa8 100644 --- a/internals/redis/commands/core.go +++ b/internals/redis/commands/core_commands.go @@ -3,6 +3,7 @@ package commands import ( "bytes" "fmt" + "log" "strconv" "strings" "time" @@ -96,9 +97,17 @@ func init() { } } - if _, err := c.Engine.Write(&writeOpts); err != nil { - c.Conn.WriteError("Err " + err.Error()) - return + if c.Cfg.Server.Redis.AsyncWrites { + go (func() { + if _, err := c.Engine.Write(&writeOpts); err != nil { + log.Println("[FATAL]", err.Error()) + } + })() + } else { + if _, err := c.Engine.Write(&writeOpts); err != nil { + c.Conn.WriteError("Err " + err.Error()) + return + } } c.Conn.WriteString("OK") @@ -145,6 +154,21 @@ func init() { delta = c.Argv[1] } + if c.Cfg.Server.Redis.AsyncWrites { + go (func() { + if _, err := c.Engine.Write(&contract.WriteInput{ + Key: c.AbsoluteKeyPath(c.Argv[0]), + Value: delta, + Increment: true, + }); err != nil { + log.Println("[FATAL]", err.Error()) + } + })() + + c.Conn.WriteNull() + return + } + ret, err := c.Engine.Write(&contract.WriteInput{ Key: c.AbsoluteKeyPath(c.Argv[0]), Value: delta, @@ -166,6 +190,25 @@ func init() { return } + if c.Cfg.Server.Redis.AsyncWrites { + go (func() { + for i := range c.Argv { + _, err := c.Engine.Write(&contract.WriteInput{ + Key: c.AbsoluteKeyPath(c.Argv[i]), + Value: nil, + }) + + if err != nil { + log.Println("[FATAL]", err.Error()) + return + } + } + })() + + c.Conn.WriteString("OK") + return + } + for i := range c.Argv { _, err := c.Engine.Write(&contract.WriteInput{ Key: c.AbsoluteKeyPath(c.Argv[i]), diff --git a/internals/redis/commands/registry.go b/internals/redis/commands/registry.go index 579fd78..a843fd1 100644 --- a/internals/redis/commands/registry.go +++ b/internals/redis/commands/registry.go @@ -1,60 +1,11 @@ package commands import ( - "bytes" "fmt" "strings" "sync" - - "github.com/alash3al/redix/internals/config" - "github.com/alash3al/redix/internals/datastore/contract" - "github.com/tidwall/redcon" ) -// Context represents the command context -type Context struct { - Conn redcon.Conn - Engine contract.Engine - Cfg *config.Config - Argv [][]byte - Argc int - - sync.RWMutex -} - -// Session fetches the current session map -func (c *Context) Session() map[string]interface{} { - c.RLock() - m := c.Conn.Context().(map[string]interface{}) - c.RUnlock() - - return m -} - -// SessionSet set a k-v into the current session -func (c *Context) SessionSet(k string, v interface{}) { - c.Lock() - - m := c.Conn.Context().(map[string]interface{}) - m[k] = v - c.Conn.SetContext(m) - - c.Unlock() -} - -// SessionGet fetches a value from the current session -func (c *Context) SessionGet(k string) (interface{}, bool) { - val, ok := c.Session()[k] - - return val, ok -} - -// AbsoluteKeyPath returns the full key path relative to the namespace the namespace -func (c *Context) AbsoluteKeyPath(k ...[]byte) []byte { - ns, _ := c.SessionGet("namespace") - return []byte(ns.(string) + string(bytes.Join(k, []byte("/")))) -} - // Handler a command handler func type Handler func(*Context) diff --git a/redix.hcl b/redix.hcl index 204968d..5c041a0 100644 --- a/redix.hcl +++ b/redix.hcl @@ -2,6 +2,7 @@ server { redis { listen = ":6380" max_connections = 100 + async = true } }