Skip to content

Commit

Permalink
Layered structure and some improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
i-sevostyanov committed Sep 16, 2020
1 parent 8019fa7 commit 8c5aefc
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 143 deletions.
13 changes: 8 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
.PHONY: build lint test vendor
.PHONY: build lint test vendor run

default: build

build:
go build -mod=vendor -o build/probe -ldflags "-s -w" cmd/main.go
@go build -mod=vendor -o build/cached -ldflags "-s -w" cmd/main.go

lint:
golangci-lint run
@golangci-lint run

test:
go test ./...
@go test ./...

vendor:
go mod tidy && go mod vendor
@go mod tidy && go mod vendor

run:
@go run cmd/main.go
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
## Probe
Is in-memory cache server with simple text-based protocol.
## Cached
Is an in-memory cache server with a simple text-based protocol.

### Features
* TTL per key
* TTL per key (based on BTree index)
* Dump and Restore from disk
* Thread-safety
* Commands:
* `set <key> <value> <ttl>`
* `get <key>`
* `del <key>`
* `stats`
* `quit`

### Why?
Expand Down Expand Up @@ -39,10 +40,14 @@ del mykey
OK
```

Show stats:
```bash
stats
Hit: 865, Miss: 24, Size: 853
```

### ToDo
* Tests and benchmarks
* Metrics
* Dump and restore without reflection
* Build in GitHub Actions


30 changes: 19 additions & 11 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"flag"
"io"
"log"
"os"
"os/signal"
Expand All @@ -12,8 +13,9 @@ import (

"golang.org/x/sync/errgroup"

"probe/internal/cache"
"probe/internal/server"
"github.com/i-sevostyanov/chached/internal/cache"
"github.com/i-sevostyanov/chached/internal/protocol"
"github.com/i-sevostyanov/chached/internal/server"
)

var errCanceled = errors.New("canceled")
Expand All @@ -30,13 +32,9 @@ func main() {
if err != nil {
logger.Fatalf("Failed to open file: %v", err)
}
defer func() {
if err = file.Close(); err != nil {
logger.Printf("Failed to close file: %v", err)
}
}()

inMemCache := cache.New()
cacheProtocol := protocol.New(inMemCache)

if err = inMemCache.Restore(file); err != nil {
logger.Printf("Warning. Failed to restore cache: %v", err)
Expand All @@ -45,7 +43,7 @@ func main() {
gr, ctx := errgroup.WithContext(context.Background())

gr.Go(func() error {
return server.New(*address, inMemCache, logger).Listen(ctx)
return server.New(*address, cacheProtocol, logger).Listen(ctx)
})

gr.Go(func() error {
Expand All @@ -56,6 +54,8 @@ func main() {
gr.Go(func() error {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(signals)

for {
select {
case <-ctx.Done():
Expand All @@ -67,15 +67,23 @@ func main() {
}
})

if err = gr.Wait(); err != nil && err != errCanceled {
if err = gr.Wait(); err != nil && !errors.Is(err, errCanceled) {
logger.Fatalf("Failed to start: %v", err)
}

if err := file.Truncate(0); err != nil {
if _, err = file.Seek(0, io.SeekStart); err != nil {
logger.Fatalf("Failed to set offset: %v", err)
}

if err = file.Truncate(0); err != nil {
logger.Fatalf("Failed to truncate file: %v", err)
}

if err := inMemCache.Dump(file); err != nil {
if err = inMemCache.Dump(file); err != nil {
logger.Fatalf("Failed to dump cache: %v", err)
}

if err = file.Close(); err != nil {
logger.Fatalf("Failed to close file: %v", err)
}
}
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module probe
module github.com/i-sevostyanov/chached

go 1.13
go 1.15

require golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
require golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
21 changes: 12 additions & 9 deletions internal/cache/btree.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (b *btree) removeNodesLessThan(k int64) map[string]struct{} {
}

fakeParent := &node{Right: b.Root}
rm := b.Root.removeNodesOlderThan(k, fakeParent)
rm := b.Root.removeNodesLessThan(k, fakeParent)
b.Root = fakeParent.Right

var nodes []*node
Expand All @@ -51,6 +51,7 @@ func (b *btree) traverse(n *node, f func(*node)) {
if n == nil {
return
}

b.traverse(n.Left, f)
f(n)
b.traverse(n.Right, f)
Expand Down Expand Up @@ -79,6 +80,7 @@ func (n *node) insert(newNode *node) {
n.Left = newNode
return
}

n.Left.insert(newNode)
return
}
Expand All @@ -88,6 +90,7 @@ func (n *node) insert(newNode *node) {
n.Right = newNode
return
}

n.Right.insert(newNode)
return
}
Expand All @@ -99,29 +102,29 @@ func (n *node) insert(newNode *node) {
}
}

func (n *node) removeNodesOlderThan(k int64, parent *node) map[string]struct{} {
func (n *node) removeNodesLessThan(k int64, parent *node) map[string]struct{} {
if n == nil {
return nil
}

if k < n.Key {
return n.Left.removeNodesOlderThan(k, n)
return n.Left.removeNodesLessThan(k, n)
}

keys := make(map[string]struct{})
for k := range n.Values {
keys[k] = struct{}{}
for key := range n.Values {
keys[key] = struct{}{}
}

if n.Left != nil {
for k := range n.Left.removeNodesOlderThan(k, n) {
keys[k] = struct{}{}
for key := range n.Left.removeNodesLessThan(k, n) {
keys[key] = struct{}{}
}
}

if n.Right != nil {
for k := range n.Right.removeNodesOlderThan(k, n) {
keys[k] = struct{}{}
for key := range n.Right.removeNodesLessThan(k, n) {
keys[key] = struct{}{}
}
}

Expand Down
38 changes: 31 additions & 7 deletions internal/cache/inmem.go → internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"io"
"sync"
"sync/atomic"
"time"
)

Expand All @@ -14,23 +15,33 @@ var ErrNotFound = errors.New("key not found")

// InMem in-memory cache
type InMem struct {
mu *sync.RWMutex
data *data
mu sync.RWMutex
data data
stats stats
}

type data struct {
Index *btree
Index btree
KV map[string]string
}

type stats struct {
miss int64
hit int64
}

// New returns new in-memory cache
func New() *InMem {
return &InMem{
mu: new(sync.RWMutex),
data: &data{
Index: new(btree),
mu: sync.RWMutex{},
data: data{
Index: btree{},
KV: make(map[string]string),
},
stats: stats{
miss: 0,
hit: 0,
},
}
}

Expand All @@ -40,9 +51,11 @@ func (m *InMem) Get(key string) (string, error) {
defer m.mu.RUnlock()

if v, ok := m.data.KV[key]; ok {
atomic.AddInt64(&m.stats.hit, 1)
return v, nil
}

atomic.AddInt64(&m.stats.miss, 1)
return "", ErrNotFound
}

Expand All @@ -64,6 +77,17 @@ func (m *InMem) Delete(key string) {
m.delete(key)
}

// Stats returns hits, misses and cache size
func (m *InMem) Stats() (int64, int64, int64) {
m.mu.RLock()
hit := m.stats.hit
miss := m.stats.miss
size := int64(len(m.data.KV))
m.mu.RUnlock()

return hit, miss, size
}

// Eviction starts the process of removing keys whose life has come to an end
func (m *InMem) Eviction(ctx context.Context, interval time.Duration) {
ticker := time.NewTicker(interval)
Expand Down Expand Up @@ -98,7 +122,7 @@ func (m *InMem) Restore(r io.Reader) error {
m.mu.Lock()
defer m.mu.Unlock()

return gob.NewDecoder(r).Decode(m.data)
return gob.NewDecoder(r).Decode(&m.data)
}

func (m *InMem) delete(key string) {
Expand Down
Loading

0 comments on commit 8c5aefc

Please sign in to comment.