From 2028de04f3c43fd8c8df456b650108a891c45790 Mon Sep 17 00:00:00 2001 From: lucario <48748794+xgzlucario@users.noreply.github.com> Date: Sat, 13 Jul 2024 23:54:38 +0800 Subject: [PATCH] feat: add zipmap, zipset based on listpack (#40) * opt: refact dict data structure * opt: refact dict with swissmap * opt: init fes slice cap in aewait * feat: docker build support buildTime * perf: optimise listpack perf * perf: optimize listpack compress memalloc * feat: add zipmap, zipset based on listpack --------- Co-authored-by: guangzhixu --- .gitignore | 3 +- Dockerfile | 4 +- Makefile | 10 +- README.md | 10 +- ae.go | 1 + command.go | 67 ++++-- command_test.go | 15 +- go.mod | 6 +- go.sum | 12 +- internal/dict/bench_test.go | 71 +------ internal/dict/benchmark/main.go | 9 +- internal/dict/dict.go | 234 ++------------------- internal/dict/dict.png | Bin 139949 -> 0 bytes internal/dict/dict_test.go | 11 +- internal/dict/example/main.go | 106 ---------- internal/dict/index.go | 45 ---- internal/dict/utils.go | 49 ----- internal/hash/bench_test.go | 52 +++++ internal/hash/map.go | 19 +- internal/hash/map_test.go | 64 ++++++ internal/hash/set.go | 37 ++-- internal/hash/zipmap.go | 71 +++++++ internal/hash/zipset.go | 53 +++++ internal/list/bench_test.go | 61 +++--- internal/list/benchmark/main.go | 73 ------- internal/list/list.go | 165 +++++---------- internal/list/list_test.go | 304 +++++---------------------- internal/list/listpack.go | 294 ++++++++++++-------------- internal/list/listpack_test.go | 360 +++++++------------------------- internal/list/utils.go | 13 -- internal/pkg/allocator.go | 48 ----- main.go | 30 ++- rotom.go | 29 +-- 33 files changed, 767 insertions(+), 1559 deletions(-) delete mode 100644 internal/dict/dict.png delete mode 100644 internal/dict/example/main.go delete mode 100644 internal/dict/index.go delete mode 100644 internal/dict/utils.go create mode 100644 internal/hash/bench_test.go create mode 100644 internal/hash/map_test.go create mode 100644 internal/hash/zipmap.go create mode 100644 internal/hash/zipset.go delete mode 100644 internal/list/benchmark/main.go delete mode 100644 internal/pkg/allocator.go diff --git a/.gitignore b/.gitignore index a5c2373..b91399d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ coverage.* -*.aof \ No newline at end of file +*.aof +rotom \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0e4277b..47e3ed8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,13 @@ LABEL stage=gobuilder \ ENV CGO_ENABLED 0 ENV GOPROXY https://goproxy.cn,direct +ARG BUILD_TIME + WORKDIR /build COPY . . -RUN go build -ldflags="-s -w" -o rotom . +RUN go build -ldflags="-s -w -X main.buildTime=${BUILD_TIME}" -o rotom . FROM alpine:latest diff --git a/Makefile b/Makefile index 029b41c..698484f 100644 --- a/Makefile +++ b/Makefile @@ -16,10 +16,14 @@ pprof: heap: go tool pprof http://192.168.1.6:6060/debug/pprof/heap -build-docker: - docker build -t rotom . - bench: go test -bench . -benchmem +build: + go build -o rotom -ldflags "-s -w -X main.buildTime=$(shell date +%y%m%d_%H%M%S%z)" + +build-docker: + docker build --build-arg BUILD_TIME=$(shell date +%y%m%d_%H%M%S%z) -t rotom . + +# tmp command # rsync -av --exclude='.git' rotom/ 2:~/xgz/rotom \ No newline at end of file diff --git a/README.md b/README.md index 4465529..99a2c63 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,15 @@ ## 介绍 -这里是 rotom,一个使用 Go 编写的 tiny Redis Server。基于 IO 多路复用技术,还原了 Redis 中的 AeLoop 核心事件循环机制。 - -rotom 基于 [godis](https://github.com/archeryue/godis) 项目 +这里是 rotom,一个使用 Go 编写的 tiny Redis Server。基于 IO 多路复用还原了 Redis 中的 AeLoop 核心事件循环机制。 ### 实现特性 1. 基于 epoll 网络模型,还原了 Redis 中的 AeLoop 单线程事件循环 2. 兼容 Redis RESP 协议,你可以使用任何 redis 客户端连接 rotom -3. 实现了 dict, quicklist, hash, set, zset 数据结构 +3. 实现了 dict, quicklist(listpack), hash(map, zipmap), set(mapset, zipset), zset 数据结构 4. AOF 支持 -5. 支持 17 种常用命令 +5. 支持 18 种常用命令 ### 原理介绍 @@ -64,7 +62,7 @@ $ go run . ``` REPOSITORY TAG IMAGE ID CREATED SIZE -rotom latest 22f42ce9ae0e 8 seconds ago 18.6MB +rotom latest 22f42ce9ae0e 8 seconds ago 18.8MB ``` 然后启动容器: diff --git a/ae.go b/ae.go index 5bdf124..1083787 100644 --- a/ae.go +++ b/ae.go @@ -172,6 +172,7 @@ retry: } // collect file events + fes = make([]*AeFileEvent, 0, n) for _, ev := range events[:n] { if ev.Events&unix.EPOLLIN != 0 { fe := loop.FileEvents[int(ev.Fd)] diff --git a/command.go b/command.go index 3871bad..5dc3a8d 100644 --- a/command.go +++ b/command.go @@ -13,7 +13,7 @@ type Command struct { name string // handler is this command real database handler function. - handler func(respWriter *RESPWriter, args []RESP) + handler func(writer *RESPWriter, args []RESP) // arity represents the minimal number of arguments that command accepts. arity int @@ -37,6 +37,7 @@ var cmdTable []*Command = []*Command{ {"rpop", rpopCommand, 1, true}, {"lpop", lpopCommand, 1, true}, {"sadd", saddCommand, 2, true}, + {"srem", sremCommand, 2, true}, {"spop", spopCommand, 1, true}, {"zadd", zaddCommand, 3, true}, {"ping", pingCommand, 0, false}, @@ -60,8 +61,8 @@ func equalCommand(str, lowerText string) bool { return false } const s = 'a' - 'A' - for i, lo := range lowerText { - delta := lo - rune(str[i]) + for i, lt := range lowerText { + delta := lt - rune(str[i]) if delta != 0 && delta != s { return false } @@ -83,8 +84,8 @@ func pingCommand(writer *RESPWriter, _ []RESP) { func setCommand(writer *RESPWriter, args []RESP) { key := args[0].ToString() - value := args[1].ToBytes() - db.strs.Set(key, value) + value := args[1].Clone() + db.dict.Set(key, value) writer.WriteString("OK") } @@ -96,8 +97,8 @@ func msetCommand(writer *RESPWriter, args []RESP) { } for i := 0; i < len(args); i += 2 { key := args[i].ToString() - value := args[i+1].ToBytes() - db.strs.Set(key, value) + value := args[i+1].Clone() + db.dict.Set(key, value) } writer.WriteString("OK") } @@ -105,39 +106,44 @@ func msetCommand(writer *RESPWriter, args []RESP) { func incrCommand(writer *RESPWriter, args []RESP) { key := args[0].ToString() - val, ok := db.strs.Get(key) + val, ok := db.dict.Get(key) if !ok { - db.strs.Set(key, []byte("1")) + db.dict.Set(key, []byte("1")) writer.WriteInteger(1) return } - num, err := RESP(val).ToInt() + valBytes, ok := val.([]byte) + if !ok { + writer.WriteError(ErrWrongType) + return + } + + num, err := RESP(valBytes).ToInt() if err != nil { writer.WriteError(ErrParseInteger) return } num++ - db.strs.Set(key, []byte(strconv.Itoa(num))) + db.dict.Set(key, []byte(strconv.Itoa(num))) writer.WriteInteger(num) } func getCommand(writer *RESPWriter, args []RESP) { key := args[0].ToStringUnsafe() - value, ok := db.strs.Get(key) - if ok { - writer.WriteBulk(value) + val, ok := db.dict.Get(key) + if !ok { + writer.WriteNull() return } - // check extra maps - _, ok = db.extras.Get(key) + valBytes, ok := val.([]byte) if ok { - writer.WriteError(ErrWrongType) + writer.WriteBulk(valBytes) } else { - writer.WriteNull() + writer.WriteError(ErrWrongType) } } @@ -313,9 +319,8 @@ func lrangeCommand(writer *RESPWriter, args []RESP) { } writer.WriteArrayHead(size) - ls.Range(start, end, func(data []byte) (stop bool) { + ls.Range(start, end, func(data []byte) { writer.WriteBulk(data) - return false }) } @@ -338,6 +343,24 @@ func saddCommand(writer *RESPWriter, args []RESP) { writer.WriteInteger(newItems) } +func sremCommand(writer *RESPWriter, args []RESP) { + key := args[0].ToString() + + set, err := fetchSet(key) + if err != nil { + writer.WriteError(err) + return + } + + var count int + for _, arg := range args[1:] { + if set.Remove(arg.ToStringUnsafe()) { + count++ + } + } + writer.WriteInteger(count) +} + func spopCommand(writer *RESPWriter, args []RESP) { key := args[0].ToString() @@ -403,7 +426,7 @@ func fetchZSet(key string, setnx ...bool) (ZSet, error) { } func fetch[T any](key string, new func() T, setnx ...bool) (v T, err error) { - item, ok := db.extras.Get(key) + item, ok := db.dict.Get(key) if ok { v, ok := item.(T) if ok { @@ -413,7 +436,7 @@ func fetch[T any](key string, new func() T, setnx ...bool) (v T, err error) { } v = new() if len(setnx) > 0 && setnx[0] { - db.extras.Put(key, v) + db.dict.Set(key, v) } return v, nil } diff --git a/command_test.go b/command_test.go index 9c84c96..f9fec26 100644 --- a/command_test.go +++ b/command_test.go @@ -97,10 +97,8 @@ func TestCommand(t *testing.T) { assert.Equal(resm, map[string]string{"k1": "v1", "k2": "v2", "k3": "v3", "k4": "v4", "k5": "v5"}) // hdel - { - res, _ := rdb.HDel(ctx, "map", "k1", "k2", "k3", "k99").Result() - assert.Equal(res, int64(3)) - } + res, _ = rdb.HDel(ctx, "map", "k1", "k2", "k3", "k99").Result() + assert.Equal(res, int64(3)) // error _, err := rdb.HSet(ctx, "map").Result() @@ -123,6 +121,9 @@ func TestCommand(t *testing.T) { res, _ := rdb.LRange(ctx, "list", 0, -1).Result() assert.Equal(res, []string{"c", "b", "a", "d", "e", "f"}) + res, _ = rdb.LRange(ctx, "list", 1, 3).Result() + assert.Equal(res, []string{"b", "a"}) + // lpop val, _ := rdb.LPop(ctx, "list").Result() assert.Equal(val, "c") @@ -136,6 +137,7 @@ func TestCommand(t *testing.T) { n, _ := rdb.SAdd(ctx, "set", "k1", "k2", "k3").Result() assert.Equal(n, int64(3)) + // spop for i := 0; i < 3; i++ { val, _ := rdb.SPop(ctx, "set").Result() assert.NotEqual(val, "") @@ -143,6 +145,11 @@ func TestCommand(t *testing.T) { _, err := rdb.SPop(ctx, "set").Result() assert.Equal(err, redis.Nil) + + // srem + rdb.SAdd(ctx, "set", "k1", "k2", "k3").Result() + res, _ := rdb.SRem(ctx, "set", "k1", "k2", "k999").Result() + assert.Equal(res, int64(2)) }) t.Run("zset", func(t *testing.T) { diff --git a/go.mod b/go.mod index 68c74d4..703b2cf 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,14 @@ go 1.22 require ( github.com/cockroachdb/swiss v0.0.0-20240612210725-f4de07ae6964 + github.com/deckarep/golang-set/v2 v2.6.0 + github.com/klauspost/compress v1.17.9 github.com/redis/go-redis/v9 v9.5.2 github.com/rs/zerolog v1.33.0 github.com/sakeven/RbTree v1.1.1 github.com/stretchr/testify v1.9.0 github.com/tidwall/mmap v0.3.0 - golang.org/x/sys v0.21.0 + golang.org/x/sys v0.22.0 ) require ( @@ -22,7 +24,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect - golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 071875a..0040b6d 100644 --- a/go.sum +++ b/go.sum @@ -12,11 +12,15 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -47,14 +51,14 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/mmap v0.3.0 h1:XXt1YsiXCF5/UAu3pLbu6g7iulJ9jsbs6vt7UpiV0sY= github.com/tidwall/mmap v0.3.0/go.mod h1:2/dNzF5zA+te/JVHfrqNLcRkb8LjdH3c80vYHFQEZRk= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= diff --git a/internal/dict/bench_test.go b/internal/dict/bench_test.go index e406441..470f42a 100644 --- a/internal/dict/bench_test.go +++ b/internal/dict/bench_test.go @@ -7,80 +7,28 @@ import ( "github.com/cockroachdb/swiss" ) -const N = 100 * 10000 - -func getStdmap(n int) map[string]int { - m := make(map[string]int, n) - for i := 0; i < n; i++ { - k, _ := genKV(i) - m[k] = i - } - return m -} - -func getSwiss(n int) *swiss.Map[string, int] { - m := swiss.New[string, int](n) - for i := 0; i < n; i++ { - k, _ := genKV(i) - m.Put(k, i) - } - return m -} +const N = 10000 func BenchmarkSet(b *testing.B) { b.Run("stdmap", func(b *testing.B) { - m := make(map[string]int, 8) - for i := 0; i < b.N; i++ { - k, _ := genKV(i) - m[k] = i - } - }) - b.Run("swiss", func(b *testing.B) { - m := swiss.New[string, int](8) - for i := 0; i < b.N; i++ { - k, _ := genKV(i) - m.Put(k, i) - } - }) -} - -func BenchmarkGet(b *testing.B) { - b.Run("stdmap", func(b *testing.B) { - m := getStdmap(N) - b.ResetTimer() + m := make(map[string]any, 8) for i := 0; i < b.N; i++ { - k, _ := genKV(i) - _ = m[k] + k, v := genKV(i) + m[k] = v } }) b.Run("swiss", func(b *testing.B) { - m := getSwiss(N) - b.ResetTimer() + m := swiss.New[string, any](8) for i := 0; i < b.N; i++ { - k, _ := genKV(i) - m.Get(k) + k, v := genKV(i) + m.Put(k, v) } }) } func BenchmarkGC(b *testing.B) { - b.Run("swiss1", func(b *testing.B) { - m := swiss.New[string, int](N) - data := make([]byte, 0) - for i := 0; i < N; i++ { - k, v := genKV(i) - m.Put(k, i) - data = append(data, v...) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - runtime.GC() - } - m.Put("xgz", len(data)) - }) - - b.Run("swiss2", func(b *testing.B) { - m := swiss.New[string, []byte](N) + b.Run("swiss", func(b *testing.B) { + m := swiss.New[string, any](N) for i := 0; i < N; i++ { k, v := genKV(i) m.Put(k, v) @@ -89,6 +37,5 @@ func BenchmarkGC(b *testing.B) { for i := 0; i < b.N; i++ { runtime.GC() } - m.Put("xgz", []byte("hello")) }) } diff --git a/internal/dict/benchmark/main.go b/internal/dict/benchmark/main.go index 160c426..0dcfb75 100644 --- a/internal/dict/benchmark/main.go +++ b/internal/dict/benchmark/main.go @@ -32,18 +32,19 @@ func main() { c := "" entries := 0 flag.StringVar(&c, "cache", "dict", "map to bench.") - flag.IntVar(&entries, "entries", 1000*10000, "number of entries to test.") + flag.IntVar(&entries, "entries", 2000*10000, "number of entries to test.") flag.Parse() fmt.Println(c) fmt.Println("entries:", entries) + debug.SetGCPercent(10) start := time.Now() q := pkg.NewQuantile(entries) switch c { case "dict": - m := dict.New(dict.DefaultOptions) + m := dict.New() for i := 0; i < entries; i++ { k, v := genKV(i) start := time.Now() @@ -52,7 +53,7 @@ func main() { } case "stdmap": - m := make(map[string][]byte, 8) + m := make(map[string]any, 8) for i := 0; i < entries; i++ { k, v := genKV(i) start := time.Now() @@ -61,7 +62,7 @@ func main() { } case "swiss": - m := swiss.New[string, []byte](8) + m := swiss.New[string, any](8) for i := 0; i < entries; i++ { k, v := genKV(i) start := time.Now() diff --git a/internal/dict/dict.go b/internal/dict/dict.go index 50494b2..ab6b823 100644 --- a/internal/dict/dict.go +++ b/internal/dict/dict.go @@ -1,245 +1,57 @@ package dict import ( - "encoding/binary" - "math/rand/v2" "time" "github.com/cockroachdb/swiss" - "github.com/xgzlucario/rotom/internal/pkg" -) - -const ( - noTTL = 0 - KB = 1024 - - // maxFailed indicates that the eviction algorithm breaks - // when consecutive unexpired key-value pairs are detected. - maxFailed = 3 -) - -var ( - bufferpool = pkg.NewBufferPool() - dictAllocator = pkg.NewAllocator[string, Idx]() ) // Dict is the hashmap for Rotom. type Dict struct { - mask uint32 - shards []*shard + data *swiss.Map[string, any] + expire *swiss.Map[string, int64] } -func New(options Options) *Dict { - dict := &Dict{ - mask: options.ShardCount - 1, - shards: make([]*shard, options.ShardCount), - } - for i := range dict.shards { - dict.shards[i] = &shard{ - options: &options, - index: swiss.New(options.IndexSize, swiss.WithAllocator(dictAllocator)), - data: bufferpool.Get(options.BufferSize)[:0], - } +func New() *Dict { + return &Dict{ + data: swiss.New[string, any](64), + expire: swiss.New[string, int64](64), } - return dict -} - -func (dict *Dict) getShard(key string) *shard { - hash := MemHash(key) - return dict.shards[uint32(hash)&dict.mask] } -func (dict *Dict) Get(key string) ([]byte, bool) { - shard := dict.getShard(key) - idx, ok := shard.index.Get(key) - if !ok { - return nil, false - } - if idx.expired() { - shard.removeEntry(key, idx) - return nil, false - } - _, val := shard.findEntry(idx) - return val, ok +func (dict *Dict) Get(key string) (any, bool) { + return dict.data.Get(key) } -func (dict *Dict) SetTx(key string, val []byte, expiration int64) bool { - shard := dict.getShard(key) - idx, ok := shard.index.Get(key) - if ok { - entry, oldVal := shard.findEntry(idx) - // update value inplaced - if len(val) == len(oldVal) { - copy(oldVal, val) - shard.index.Put(key, idx.setTTL(expiration)) - return false - } - shard.unused += uint32(len(entry)) - } - - shard.index.Put(key, shard.appendEntry(val, expiration)) - return true -} - -func (dict *Dict) Set(kstr string, value []byte) bool { - return dict.SetTx(kstr, value, noTTL) -} - -func (dict *Dict) SetEx(kstr string, value []byte, duration time.Duration) bool { - return dict.SetTx(kstr, value, time.Now().Add(duration).UnixNano()) +func (dict *Dict) Set(key string, value any) { + dict.data.Put(key, value) } func (dict *Dict) Remove(key string) bool { - shard := dict.getShard(key) - idx, ok := shard.index.Get(key) - if !ok { - return false - } - shard.removeEntry(key, idx) - return !idx.expired() + _, ok := dict.data.Get(key) + dict.data.Delete(key) + dict.expire.Delete(key) + return ok } func (dict *Dict) SetTTL(key string, expiration int64) bool { - shard := dict.getShard(key) - idx, ok := shard.index.Get(key) + _, ok := dict.data.Get(key) if !ok { return false } - if idx.expired() { - shard.removeEntry(key, idx) - return false - } - shard.index.Put(key, idx.setTTL(expiration)) + dict.expire.Put(key, expiration) return true } -type Walker func(key string, value []byte, ttl int64) (next bool) - -func (dict *Dict) Scan(callback Walker) { - for _, shard := range dict.shards { - if !shard.scan(callback) { - return - } - } -} - func (dict *Dict) EvictExpired() { - id := rand.IntN(len(dict.shards)) - dict.shards[id].evictExpired() -} - -// Stats represents the runtime statistics of Dict. -type Stats struct { - Len int - Alloc uint64 - Unused uint64 - Migrates uint64 -} - -// GetStats returns the current runtime statistics of Dict. -func (c *Dict) GetStats() (stats Stats) { - for _, shard := range c.shards { - stats.Len += shard.index.Len() - stats.Alloc += uint64(len(shard.data)) - stats.Unused += uint64(shard.unused) - stats.Migrates += uint64(shard.migrations) - } - return -} - -// UnusedRate calculates the percentage of unused space in the dict. -func (s Stats) UnusedRate() float64 { - return float64(s.Unused) / float64(s.Alloc) * 100 -} - -// shard is the data container for Dict. -type shard struct { - options *Options - index *swiss.Map[string, Idx] - data []byte - unused uint32 - migrations uint32 -} - -func (s *shard) appendEntry(val []byte, ts int64) Idx { - idx := newIdx(len(s.data), ts) - s.data = binary.AppendUvarint(s.data, uint64(len(val))) - s.data = append(s.data, val...) - return idx -} - -func (s *shard) scan(walker Walker) (next bool) { - next = true - s.index.All(func(key string, idx Idx) bool { - if idx.expired() { - return true - } - _, val := s.findEntry(idx) - next = walker(key, val, idx.lo) - return next - }) - return -} - -func (s *shard) evictExpired() { - var failed int nanosec := time.Now().UnixNano() - - // probing - s.index.All(func(key string, idx Idx) bool { - failed++ - if idx.expiredWith(nanosec) { - s.removeEntry(key, idx) - failed = 0 + count := 0 + dict.expire.All(func(key string, value int64) bool { + if nanosec > value { + dict.expire.Delete(key) + dict.data.Delete(key) } - return failed <= maxFailed + count++ + return count <= 20 }) - - // check if migration is needed. - unusedRate := float64(s.unused) / float64(len(s.data)) - if unusedRate >= s.options.MigrateRatio { - s.migrate() - } -} - -// migrate transfers valid key-value pairs to a new container to save memory. -func (s *shard) migrate() { - newData := bufferpool.Get(len(s.data))[:0] - nanosec := time.Now().UnixNano() - newIndex := swiss.New(s.index.Len(), swiss.WithAllocator(dictAllocator)) - - s.index.All(func(key string, idx Idx) bool { - if idx.expiredWith(nanosec) { - return true - } - newIndex.Put(key, idx.setStart(len(newData))) - entry, _ := s.findEntry(idx) - newData = append(newData, entry...) - return true - }) - - s.index.Close() - bufferpool.Put(s.data) - s.index = newIndex - s.data = newData - s.unused = 0 - s.migrations++ -} - -func (s *shard) findEntry(idx Idx) (entry, val []byte) { - pos := idx.start() - // read value len - vlen, n := binary.Uvarint(s.data[pos:]) - pos += n - // read value - val = s.data[pos : pos+int(vlen)] - pos += int(vlen) - - return s.data[idx.start():pos], val -} - -func (s *shard) removeEntry(key string, idx Idx) { - entry, _ := s.findEntry(idx) - s.unused += uint32(len(entry)) - s.index.Delete(key) } diff --git a/internal/dict/dict.png b/internal/dict/dict.png deleted file mode 100644 index 65b7f34221fd91a3958802c69284d1fd2eb313b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 139949 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 X!7az;k+1 time.Minute { - break - } - } - - dict.SetEx(k, []byte(k), time.Second) - count++ - - if j%10 == 0 { - cost := float64(time.Since(now)) / float64(time.Nanosecond) - quant.Add(cost) - } - - // evict - if j%10 == 0 { - dict.EvictExpired() - } - } - - // Stat - stat := dict.GetStats() - - fmt.Printf("[Cache] %.0fs | %dw | len: %dw | alloc: %v (unused: %.1f%%)\n", - time.Since(start).Seconds(), - count/1e4, - stat.Len/1e4, - formatSize(stat.Alloc), - stat.UnusedRate(), - ) - - // mem stats - runtime.ReadMemStats(&memStats) - fmt.Printf("[Mem] mem: %.0fMB | sys: %.0fMB | gc: %d | gcpause: %.0f us\n", - float64(memStats.Alloc)/1024/1024, - float64(memStats.Sys)/1024/1024, - memStats.NumGC, - float64(memStats.PauseTotalNs)/float64(memStats.NumGC)/1000) - - // quant print - quant.Print() - - fmt.Println("-----------------------------------------------------") -} - -const ( - KB = 1024 - MB = 1024 * KB -) - -// formatSize -func formatSize[T float64 | uint64](size T) string { - switch { - case size < KB: - return fmt.Sprintf("%.0fB", float64(size)) - case size < MB: - return fmt.Sprintf("%.1fKB", float64(size)/KB) - default: - return fmt.Sprintf("%.1fMB", float64(size)/MB) - } -} diff --git a/internal/dict/index.go b/internal/dict/index.go deleted file mode 100644 index a9191f4..0000000 --- a/internal/dict/index.go +++ /dev/null @@ -1,45 +0,0 @@ -package dict - -import ( - "math" - "time" -) - -type Idx struct { - hi uint32 // hi is position of data. - lo int64 // lo is timestamp of key. -} - -func (i Idx) start() int { - return int(i.hi) -} - -func (i Idx) expired() bool { - return i.lo > noTTL && i.lo < time.Now().UnixNano() -} - -func (i Idx) expiredWith(nanosec int64) bool { - return i.lo > noTTL && i.lo < nanosec -} - -func (i Idx) setTTL(ts int64) Idx { - i.lo = ts - return i -} - -func (i Idx) setStart(start int) Idx { - check(start) - i.hi = uint32(start) - return i -} - -func check(x int) { - if x > math.MaxUint32 { - panic("x overflows the limit of uint32") - } -} - -func newIdx(start int, ttl int64) Idx { - check(start) - return Idx{hi: uint32(start), lo: ttl} -} diff --git a/internal/dict/utils.go b/internal/dict/utils.go deleted file mode 100644 index 53183cb..0000000 --- a/internal/dict/utils.go +++ /dev/null @@ -1,49 +0,0 @@ -package dict - -import ( - "math/bits" - "unsafe" -) - -type stringStruct struct { - str unsafe.Pointer - len int -} - -//go:noescape -//go:linkname memhash runtime.memhash -func memhash(p unsafe.Pointer, h, s uintptr) uintptr - -type HashFn func(string) uint64 - -// MemHash is the hash function used by go map, it utilizes available hardware instructions -// (behaves as aes hash if aes instruction is available). -// NOTE: The hash seed changes for every process. So, this cannot be used as a persistent hash. -func MemHash(str string) uint64 { - ss := (*stringStruct)(unsafe.Pointer(&str)) - return uint64(memhash(ss.str, 0, uintptr(ss.len))) -} - -// SizeUvarint -// See https://go-review.googlesource.com/c/go/+/572196/1/src/encoding/binary/varint.go#174 -func SizeUvarint(x uint64) int { - return int(9*uint32(bits.Len64(x))+64) / 64 -} - -type Options struct { - ShardCount uint32 - - // Default size of the bucket initial. - IndexSize int - BufferSize int - - // Migrate threshold for a bucket to trigger a migration. - MigrateRatio float64 -} - -var DefaultOptions = Options{ - ShardCount: 1024, - IndexSize: 1024, - BufferSize: 32 * KB, - MigrateRatio: 0.4, -} diff --git a/internal/hash/bench_test.go b/internal/hash/bench_test.go new file mode 100644 index 0000000..c45716a --- /dev/null +++ b/internal/hash/bench_test.go @@ -0,0 +1,52 @@ +package hash + +import ( + "fmt" + "testing" +) + +func genKey(i int) string { + return fmt.Sprintf("%08x", i) +} + +func BenchmarkMap(b *testing.B) { + benchMapI("map", func() MapI { return NewMap() }, b) + benchMapI("zipmap", func() MapI { return NewZipMap() }, b) +} + +func benchMapI(name string, newf func() MapI, b *testing.B) { + b.Run(name+"-set", func(b *testing.B) { + for i := 0; i < b.N; i++ { + m := newf() + for i := 0; i < 512; i++ { + k := genKey(i) + m.Set(k, []byte(k)) + } + } + }) + b.Run(name+"-update", func(b *testing.B) { + for i := 0; i < b.N; i++ { + m := newf() + for i := 0; i < 512; i++ { + k := genKey(0) + m.Set(k, []byte(k)) + } + } + }) +} + +func BenchmarkSet(b *testing.B) { + benchSetI("set", func() SetI { return NewSet() }, b) + benchSetI("zipset", func() SetI { return NewZipSet() }, b) +} + +func benchSetI(name string, newf func() SetI, b *testing.B) { + b.Run(name+"-add", func(b *testing.B) { + for i := 0; i < b.N; i++ { + m := newf() + for i := 0; i < 512; i++ { + m.Add(genKey(i)) + } + } + }) +} diff --git a/internal/hash/map.go b/internal/hash/map.go index 39c5789..35b775f 100644 --- a/internal/hash/map.go +++ b/internal/hash/map.go @@ -2,19 +2,24 @@ package hash import ( "github.com/cockroachdb/swiss" - "github.com/xgzlucario/rotom/internal/pkg" ) -var ( - mapAllocator = pkg.NewAllocator[string, []byte]() -) +type MapI interface { + Set(key string, val []byte) (ok bool) + Get(key string) (val []byte, ok bool) + Remove(key string) (ok bool) + Len() int + Scan(iterator func(key string, val []byte)) +} + +var _ MapI = (*Map)(nil) type Map struct { m *swiss.Map[string, []byte] } func NewMap() *Map { - return &Map{m: swiss.New(8, swiss.WithAllocator(mapAllocator))} + return &Map{m: swiss.New[string, []byte](8)} } func (m *Map) Get(key string) ([]byte, bool) { @@ -43,7 +48,3 @@ func (m *Map) Scan(fn func(key string, value []byte)) { return true }) } - -func (m *Map) Free() { - m.m.Close() -} diff --git a/internal/hash/map_test.go b/internal/hash/map_test.go new file mode 100644 index 0000000..e3e4998 --- /dev/null +++ b/internal/hash/map_test.go @@ -0,0 +1,64 @@ +package hash + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + testMapI(NewMap(), t) + testMapI(NewZipMap(), t) +} + +func testMapI(m MapI, t *testing.T) { + assert := assert.New(t) + + // set + assert.True(m.Set("key1", []byte("val1"))) + assert.True(m.Set("key2", []byte("val2"))) + assert.True(m.Set("key3", []byte("val3"))) + + // len + assert.Equal(m.Len(), 3) + + // get + val, ok := m.Get("key1") + assert.True(ok) + assert.Equal(string(val), "val1") + + val, ok = m.Get("key2") + assert.True(ok) + assert.Equal(string(val), "val2") + + val, ok = m.Get("key3") + assert.True(ok) + assert.Equal(string(val), "val3") + + // set(update) + assert.False(m.Set("key1", []byte("newval1"))) + assert.False(m.Set("key2", []byte("newval2"))) + assert.False(m.Set("key3", []byte("newval3"))) + + // get(update) + val, ok = m.Get("key1") + assert.True(ok) + assert.Equal(string(val), "newval1") + + val, ok = m.Get("key2") + assert.True(ok) + assert.Equal(string(val), "newval2") + + val, ok = m.Get("key3") + assert.True(ok) + assert.Equal(string(val), "newval3") + + // remove + assert.True(m.Remove("key1")) + assert.True(m.Remove("key2")) + assert.True(m.Remove("key3")) + assert.False(m.Remove("notexist")) + + // len + assert.Equal(m.Len(), 0) +} diff --git a/internal/hash/set.go b/internal/hash/set.go index e94e4c4..1647736 100644 --- a/internal/hash/set.go +++ b/internal/hash/set.go @@ -1,39 +1,34 @@ package hash import ( - "github.com/cockroachdb/swiss" - "github.com/xgzlucario/rotom/internal/pkg" + mapset "github.com/deckarep/golang-set/v2" ) -var ( - setAllocator = pkg.NewAllocator[string, struct{}]() -) +type SetI interface { + Add(key string) (ok bool) + Remove(key string) (ok bool) + Pop() (key string, ok bool) + Len() int +} + +var _ SetI = (*Set)(nil) type Set struct { - m *swiss.Map[string, struct{}] + mapset.Set[string] } func NewSet() *Set { - return &Set{m: swiss.New[string, struct{}](8, swiss.WithAllocator(setAllocator))} + return &Set{mapset.NewThreadUnsafeSet[string]()} } -func (s *Set) Add(key string) bool { - if _, ok := s.m.Get(key); ok { +func (s Set) Remove(key string) bool { + if !s.ContainsOne(key) { return false } - s.m.Put(key, struct{}{}) + s.Set.Remove(key) return true } -func (s *Set) Pop() (item string, ok bool) { - s.m.All(func(key string, _ struct{}) bool { - s.m.Delete(key) - item, ok = key, true - return false - }) - return -} - -func (s *Set) Free() { - s.m.Close() +func (s Set) Len() int { + return s.Cardinality() } diff --git a/internal/hash/zipmap.go b/internal/hash/zipmap.go new file mode 100644 index 0000000..e74c381 --- /dev/null +++ b/internal/hash/zipmap.go @@ -0,0 +1,71 @@ +package hash + +import ( + "unsafe" + + "github.com/xgzlucario/rotom/internal/list" +) + +var _ MapI = (*ZipMap)(nil) + +// ZipMap store datas as [key1, val1, key2, val2...] in listpack. +type ZipMap struct { + m *list.ListPack +} + +func NewZipMap() *ZipMap { + return &ZipMap{list.NewListPack()} +} + +func (zm *ZipMap) Set(key string, val []byte) (newField bool) { + it := zm.m.Iterator().SeekLast() + for !it.IsFirst() { + it.Prev() + keyBytes := it.Prev() + if key == b2s(keyBytes) { + // update val + it.Next() + it.ReplaceNext(b2s(val)) + return false + } + } + zm.m.RPush(key, b2s(val)) + return true +} + +func (zm *ZipMap) Get(key string) ([]byte, bool) { + it := zm.m.Iterator().SeekLast() + for !it.IsFirst() { + valBytes := it.Prev() + keyBytes := it.Prev() + if key == b2s(keyBytes) { + return valBytes, true + } + } + return nil, false +} + +func (zm *ZipMap) Remove(key string) bool { + it := zm.m.Iterator().SeekLast() + for !it.IsFirst() { + it.Prev() + keyBytes := it.Prev() + if key == b2s(keyBytes) { + it.RemoveNext() + it.RemoveNext() + return true + } + } + return false +} + +func (zm *ZipMap) Len() int { + return zm.m.Size() / 2 +} + +func (zm *ZipMap) Scan(fn func(string, []byte)) { +} + +func b2s(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} diff --git a/internal/hash/zipset.go b/internal/hash/zipset.go new file mode 100644 index 0000000..b063a62 --- /dev/null +++ b/internal/hash/zipset.go @@ -0,0 +1,53 @@ +package hash + +import ( + "github.com/xgzlucario/rotom/internal/list" +) + +var _ SetI = (*ZipSet)(nil) + +// ZipSet store datas as [key1, key2, key3...] in listpack. +type ZipSet struct { + m *list.ListPack +} + +func NewZipSet() *ZipSet { + return &ZipSet{list.NewListPack()} +} + +func (zs *ZipSet) Add(key string) (newField bool) { + if zs.Exist(key) { + return false + } + zs.m.RPush(key) + return true +} + +func (zs *ZipSet) Exist(key string) bool { + it := zs.m.Iterator().SeekLast() + for !it.IsFirst() { + if key == b2s(it.Prev()) { + return true + } + } + return false +} + +func (zs *ZipSet) Remove(key string) bool { + it := zs.m.Iterator().SeekLast() + for !it.IsFirst() { + if key == b2s(it.Prev()) { + it.RemoveNext() + return true + } + } + return false +} + +func (zs *ZipSet) Pop() (string, bool) { + return zs.m.RPop() +} + +func (zs *ZipSet) Len() int { + return zs.m.Size() +} diff --git a/internal/list/bench_test.go b/internal/list/bench_test.go index 7d81c49..442efc2 100644 --- a/internal/list/bench_test.go +++ b/internal/list/bench_test.go @@ -1,12 +1,10 @@ package list import ( - "fmt" "testing" ) func BenchmarkList(b *testing.B) { - const N = 10000 b.Run("lpush", func(b *testing.B) { ls := New() for i := 0; i < b.N; i++ { @@ -33,61 +31,68 @@ func BenchmarkList(b *testing.B) { ls.RPop() } }) - b.Run("index", func(b *testing.B) { - ls := genList(0, N) + b.Run("range_all", func(b *testing.B) { + ls := genList(0, 1000) b.ResetTimer() for i := 0; i < b.N; i++ { - ls.Index(i % N) + ls.Range(0, -1, func([]byte) {}) } }) - b.Run("set", func(b *testing.B) { - ls := genList(0, N) + b.Run("range_100", func(b *testing.B) { + ls := genList(0, 1000) b.ResetTimer() for i := 0; i < b.N; i++ { - ls.Set(i%N, genKey(N-i)) + ls.Range(0, 100, func([]byte) {}) } }) - b.Run("range", func(b *testing.B) { - ls := genList(0, N) + b.Run("revrange_all", func(b *testing.B) { + ls := genList(0, 1000) b.ResetTimer() for i := 0; i < b.N; i++ { - ls.Range(0, -1, func(s []byte) (stop bool) { - return false - }) + ls.RevRange(0, -1, func([]byte) {}) } }) - b.Run("revrange", func(b *testing.B) { - ls := genList(0, N) + b.Run("revrange_100", func(b *testing.B) { + ls := genList(0, 1000) b.ResetTimer() for i := 0; i < b.N; i++ { - ls.RevRange(0, -1, func(s []byte) (stop bool) { - return false - }) + ls.RevRange(0, 100, func([]byte) {}) } }) } func BenchmarkListPack(b *testing.B) { - const N = 1000 - b.Run("set/same-len", func(b *testing.B) { - ls := genListPack(0, N) + b.Run("compress", func(b *testing.B) { + lp := NewListPack() + for i := 0; i < 1000; i++ { + lp.RPush("rotom") + } b.ResetTimer() for i := 0; i < b.N; i++ { - ls.Set(i%N, fmt.Sprintf("%08x", i)) + lp.Compress() + lp.Decompress() } }) - b.Run("set/less-len", func(b *testing.B) { - ls := genListPack(0, N) + b.Run("replaceBegin", func(b *testing.B) { + lp := NewListPack() + for i := 0; i < 1000; i++ { + lp.RPush("rotom") + } b.ResetTimer() for i := 0; i < b.N; i++ { - ls.Set(i%N, fmt.Sprintf("%07x", i)) + it := lp.Iterator() + it.ReplaceNext("abcde") } }) - b.Run("set/great-len", func(b *testing.B) { - ls := genListPack(0, N) + b.Run("replaceEnd", func(b *testing.B) { + lp := NewListPack() + for i := 0; i < 1000; i++ { + lp.RPush("rotom") + } b.ResetTimer() for i := 0; i < b.N; i++ { - ls.Set(i%N, fmt.Sprintf("%09x", i)) + it := lp.Iterator().SeekLast() + it.ReplaceNext("abcde") } }) } diff --git a/internal/list/benchmark/main.go b/internal/list/benchmark/main.go deleted file mode 100644 index d3525c4..0000000 --- a/internal/list/benchmark/main.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "runtime" - "runtime/debug" - "time" - - "github.com/xgzlucario/rotom/internal/list" -) - -var previousPause time.Duration - -func gcPause() time.Duration { - runtime.GC() - var stats debug.GCStats - debug.ReadGCStats(&stats) - pause := stats.PauseTotal - previousPause - previousPause = stats.PauseTotal - return pause -} - -func genKey(id int) string { - return fmt.Sprintf("%08x", id) -} - -func main() { - c := "" - entries := 0 - flag.StringVar(&c, "list", "[]string", "list to bench.") - flag.IntVar(&entries, "entries", 2000*10000, "number of entries to test.") - flag.Parse() - - fmt.Println(c) - fmt.Println("entries:", entries) - - start := time.Now() - switch c { - case "[]string": - ls := make([]string, 0) - for i := 0; i < entries; i++ { - ls = append(ls, genKey(i)) - } - defer func() { - _ = len(ls) - }() - - case "quicklist": - ls := list.New() - for i := 0; i < entries; i++ { - ls.RPush(genKey(i)) - } - defer func() { - _ = ls.Size() - }() - } - cost := time.Since(start) - - var mem runtime.MemStats - var stat debug.GCStats - - runtime.ReadMemStats(&mem) - debug.ReadGCStats(&stat) - - fmt.Println("alloc:", mem.Alloc/1024/1024, "mb") - fmt.Println("gcsys:", mem.GCSys/1024/1024, "mb") - fmt.Println("heap inuse:", mem.HeapInuse/1024/1024, "mb") - fmt.Println("heap object:", mem.HeapObjects/1024, "k") - fmt.Println("gc:", stat.NumGC) - fmt.Println("pause:", gcPause()) - fmt.Println("cost:", cost) -} diff --git a/internal/list/list.go b/internal/list/list.go index a57cb11..6c6b317 100644 --- a/internal/list/list.go +++ b/internal/list/list.go @@ -1,8 +1,6 @@ package list -import ( - "math" -) +import "math" // +------------------------------ QuickList -----------------------------+ // | +-----------+ +-----------+ +-----------+ | @@ -20,10 +18,6 @@ type Node struct { prev, next *Node } -func SetMaxListPackSize(s int) { - maxListPackSize = s -} - // New create a quicklist instance. func New() *QuickList { n := newNode() @@ -42,7 +36,7 @@ func (ls *QuickList) LPush(key string) { ls.head.prev = n ls.head = n } - ls.head.Insert(0, key) + ls.head.LPush(key) } // RPush @@ -53,28 +47,25 @@ func (ls *QuickList) RPush(key string) { n.prev = ls.tail ls.tail = n } - ls.tail.Insert(-1, key) -} - -// Index -func (ls *QuickList) Index(i int) (val string, ok bool) { - ls.Range(i, i+1, func(key []byte) bool { - val, ok = string(key), true - return true - }) - return + ls.tail.RPush(key) } // LPop -func (ls *QuickList) LPop() (string, bool) { - return ls.Remove(0) +func (ls *QuickList) LPop() (key string, ok bool) { + for lp := ls.head; lp != nil; lp = lp.next { + if lp.size > 0 { + return lp.LPop() + } + ls.free(lp) + } + return } // RPop func (ls *QuickList) RPop() (key string, ok bool) { for lp := ls.tail; lp != nil; lp = lp.prev { if lp.size > 0 { - return lp.Remove(-1) + return lp.RPop() } ls.free(lp) } @@ -83,7 +74,7 @@ func (ls *QuickList) RPop() (key string, ok bool) { // free release empty list node. func (ls *QuickList) free(n *Node) { - if n.size == 0 && n.prev != nil && n.next != nil { + if n.prev != nil && n.next != nil { n.prev.next = n.next n.next.prev = n.prev bpool.Put(n.data) @@ -91,53 +82,6 @@ func (ls *QuickList) free(n *Node) { } } -// find quickly locates `listpack` and it `indexInternal` based on index. -func (ls *QuickList) find(index int) (*Node, int) { - var n *Node - for n = ls.head; n != nil && index >= n.Size(); n = n.next { - index -= n.Size() - } - return n, index -} - -// Set -func (ls *QuickList) Set(index int, key string) bool { - lp, indexInternal := ls.find(index) - if lp != nil { - return lp.Set(indexInternal, key) - } - return false -} - -// Remove -func (ls *QuickList) Remove(index int) (val string, ok bool) { - lp, indexInternal := ls.find(index) - if lp != nil { - val, ok = lp.Remove(indexInternal) - ls.free(lp) - } - return -} - -// RemoveFirst -func (ls *QuickList) RemoveFirst(key string) (res int, ok bool) { - for lp := ls.head; lp != nil; lp = lp.next { - if lp.size == 0 { - ls.free(lp) - - } else { - n, ok := lp.RemoveFirst(key) - if ok { - return res + n, true - } else { - res += lp.Size() - } - } - } - return 0, false -} - -// Size func (ls *QuickList) Size() (n int) { for lp := ls.head; lp != nil; lp = lp.next { n += lp.Size() @@ -145,67 +89,60 @@ func (ls *QuickList) Size() (n int) { return } -type lsIterator func(data []byte) (stop bool) - -func (ls *QuickList) iterFront(start, end int, f lsIterator) { - count := end - start +func (ls *QuickList) Range(start, end int, f func(data []byte)) { if end == -1 { - count = math.MaxInt + end = math.MaxInt } - if start < 0 || count < 0 { - return + count := end - start + + lp := ls.head + for lp != nil && start > lp.Size() { + start -= lp.Size() + lp = lp.next } - lp, indexInternal := ls.find(start) + it := lp.Iterator().SeekFirst() + for range start { + it.Next() + } - var stop bool - for !stop && count > 0 && lp != nil { - lp.Range(indexInternal, -1, func(data []byte, _ int) bool { - stop = f(data) - count-- - return stop || count == 0 - }) - lp = lp.next - indexInternal = 0 + for range count { + if it.IsLast() { + if lp.next == nil { + return + } + lp = lp.next + it = lp.Iterator().SeekFirst() + } + f(it.Next()) } } -func (ls *QuickList) iterBack(start, end int, f lsIterator) { - count := end - start +func (ls *QuickList) RevRange(start, end int, f func(data []byte)) { if end == -1 { - count = math.MaxInt - } - if start < 0 || count < 0 { - return + end = math.MaxInt } + count := end - start lp := ls.tail - for start > lp.Size() { + for lp != nil && start > lp.Size() { start -= lp.Size() lp = lp.prev - if lp == nil { - return - } } - var stop bool - for !stop && count > 0 && lp != nil { - lp.RevRange(start, -1, func(data []byte, _ int) bool { - stop = f(data) - count-- - return stop || count == 0 - }) - lp = lp.prev - start = 0 + it := lp.Iterator().SeekLast() + for range start { + it.Prev() } -} - -// Range -func (ls *QuickList) Range(start, end int, f lsIterator) { - ls.iterFront(start, end, f) -} -// RevRange -func (ls *QuickList) RevRange(start, end int, f lsIterator) { - ls.iterBack(start, end, f) + for range count { + if it.IsFirst() { + if lp.prev == nil { + return + } + lp = lp.prev + it = lp.Iterator().SeekLast() + } + f(it.Prev()) + } } diff --git a/internal/list/list_test.go b/internal/list/list_test.go index c6ee79d..34ffebd 100644 --- a/internal/list/list_test.go +++ b/internal/list/list_test.go @@ -1,9 +1,7 @@ package list import ( - "fmt" - "math/rand/v2" - "strconv" + "slices" "testing" "github.com/stretchr/testify/assert" @@ -17,43 +15,39 @@ func genList(start, end int) *QuickList { return lp } +func list2slice(ls *QuickList) (res []string) { + ls.Range(0, -1, func(data []byte) { + res = append(res, string(data)) + }) + return +} + func TestList(t *testing.T) { - const N = 1000 + const N = 10000 assert := assert.New(t) - SetMaxListPackSize(128) - t.Run("rpush", func(t *testing.T) { + t.Run("lpush", func(t *testing.T) { ls := New() + ls2 := make([]string, 0, N) for i := 0; i < N; i++ { - assert.Equal(ls.Size(), i) - ls.RPush(genKey(i)) - } - for i := 0; i < N; i++ { - v, ok := ls.Index(i) - assert.Equal(genKey(i), v) - assert.Equal(true, ok) - } - // check each node length - for cur := ls.head; cur != nil; cur = cur.next { - assert.LessOrEqual(len(cur.data), maxListPackSize) + key := genKey(i) + ls.LPush(key) + ls2 = slices.Insert(ls2, 0, key) } + assert.Equal(ls.Size(), len(ls2)) + assert.Equal(list2slice(ls), ls2) }) - t.Run("lpush", func(t *testing.T) { + t.Run("rpush", func(t *testing.T) { ls := New() + ls2 := make([]string, 0, N) for i := 0; i < N; i++ { - assert.Equal(ls.Size(), i) - ls.LPush(genKey(i)) - } - for i := 0; i < N; i++ { - v, ok := ls.Index(N - 1 - i) - assert.Equal(genKey(i), v) - assert.Equal(true, ok) - } - // check each node length - for cur := ls.head; cur != nil; cur = cur.next { - assert.LessOrEqual(len(cur.data), maxListPackSize) + key := genKey(i) + ls.RPush(key) + ls2 = append(ls2, key) } + assert.Equal(ls.Size(), len(ls2)) + assert.Equal(list2slice(ls), ls2) }) t.Run("lpop", func(t *testing.T) { @@ -84,237 +78,41 @@ func TestList(t *testing.T) { assert.Equal(false, ok) }) - t.Run("len", func(t *testing.T) { - ls := New() - for i := 0; i < N; i++ { - ls.RPush(genKey(i)) - assert.Equal(ls.Size(), i+1) - } - }) - - t.Run("set", func(t *testing.T) { + t.Run("range", func(t *testing.T) { ls := genList(0, N) - for i := 0; i < N; i++ { - newK := fmt.Sprintf("newkk-%x", i) - ok := ls.Set(i, newK) - assert.Equal(true, ok) - } - var count int - ls.Range(0, -1, func(b []byte) bool { - targetK := fmt.Sprintf("newkk-%x", count) - assert.Equal(string(b), targetK) - count++ - return false + i := 0 + ls.Range(0, -1, func(data []byte) { + assert.Equal(string(data), genKey(i)) + i++ }) - assert.Equal(N, count) - - ok := ls.Set(N+1, "new") - assert.Equal(false, ok) - }) - - t.Run("remove", func(t *testing.T) { - ls := genList(0, N) - for i := 0; i < N-1; i++ { - val, ok := ls.Remove(0) - assert.Equal(val, genKey(i)) - assert.Equal(true, ok) - - val, ok = ls.Index(0) - assert.Equal(val, genKey(i+1)) - assert.Equal(true, ok) - } - - assert.Equal(ls.head.Size(), 0) - // only has 2 nodes. - assert.Equal(ls.head.next, ls.tail) - assert.Equal(ls.tail.Size(), 1) - - val, ok := ls.tail.Remove(-1) - assert.Equal(val, genKey(N-1)) - assert.Equal(true, ok) - }) - - t.Run("removeFirst", func(t *testing.T) { - ls := genList(0, N) - - // remove not exist item. - index, ok := ls.RemoveFirst("none") - if index != 0 { - t.Error(index, 0) - } - if ok { - t.Error(ok) - } - - for i := 0; i < N-1; i++ { - // same as LPop - index, ok := ls.RemoveFirst(genKey(i)) - if index != 0 { - t.Error(index, i) - } - if !ok { - t.Error(ok) - } - - val, ok := ls.Index(0) - if val != genKey(i+1) { - t.Error(val, genKey(i+1)) - } - if !ok { - t.Error(ok) - } + assert.Equal(i, N) + + for _, start := range []int{100, 1000, 5000} { + i = 0 + ls.Range(start, start+100, func(data []byte) { + assert.Equal(string(data), genKey(start+i)) + i++ + }) + assert.Equal(i, 100) } - - assert.Equal(ls.head.Size(), 0) - - // only has 2 nodes. - assert.Equal(ls.head.next, ls.tail) - assert.Equal(ls.tail.Size(), 1) - - val, ok := ls.tail.Remove(-1) - assert.Equal(val, genKey(N-1)) - assert.Equal(true, ok) - }) - - t.Run("range", func(t *testing.T) { - ls := New() - ls.Range(1, 2, func(s []byte) bool { - panic("should not call") - }) - ls = genList(0, N) - - var count int - ls.Range(0, -1, func(s []byte) bool { - assert.Equal(string(s), genKey(count)) - count++ - return false - }) - assert.Equal(count, N) - - ls.Range(1, 1, func(s []byte) bool { - panic("should not call") - }) - ls.Range(-1, -1, func(s []byte) bool { - panic("should not call") - }) }) t.Run("revrange", func(t *testing.T) { - ls := New() - ls.RevRange(1, 2, func(s []byte) bool { - panic("should not call") - }) - ls = genList(0, N) - - var count int - ls.RevRange(0, -1, func(s []byte) bool { - assert.Equal(string(s), genKey(N-count-1)) - count++ - return false - }) - assert.Equal(count, N) - - ls.RevRange(1, 1, func(s []byte) bool { - panic("should not call") - }) - ls.RevRange(-1, -1, func(s []byte) bool { - panic("should not call") + ls := genList(0, N) + i := 0 + ls.RevRange(0, -1, func(data []byte) { + assert.Equal(string(data), genKey(N-i-1)) + i++ }) - }) -} - -func FuzzList(f *testing.F) { - ls := New() - vls := make([]string, 0, 4096) - - f.Fuzz(func(t *testing.T, key string) { - assert := assert.New(t) - - switch rand.IntN(15) { - // RPush - case 0, 1, 2: - k := strconv.Itoa(rand.Int()) - ls.RPush(k) - vls = append(vls, k) - - // LPush - case 3, 4, 5: - k := strconv.Itoa(rand.Int()) - ls.LPush(k) - vls = append([]string{k}, vls...) - - // LPop - case 6, 7: - val, ok := ls.LPop() - if len(vls) > 0 { - valVls := vls[0] - vls = vls[1:] - assert.Equal(val, valVls) - assert.Equal(true, ok) - } else { - assert.Equal(val, "") - assert.Equal(false, ok) - } - - // RPop - case 8, 9: - val, ok := ls.RPop() - if len(vls) > 0 { - valVls := vls[len(vls)-1] - vls = vls[:len(vls)-1] - assert.Equal(val, valVls) - assert.Equal(true, ok) - } else { - assert.Equal(val, "") - assert.Equal(false, ok) - } - - // Set - case 10: - if len(vls) > 0 { - index := rand.IntN(len(vls)) - randKey := fmt.Sprintf("%d", rand.Uint32()) - ok := ls.Set(index, randKey) - assert.Equal(true, ok) - vls[index] = randKey - } - - // Index - case 11: - if len(vls) > 0 { - index := rand.IntN(len(vls)) - val, ok := ls.Index(index) - vlsVal := vls[index] - assert.Equal(val, vlsVal) - assert.Equal(true, ok) - } - - // Remove - case 12: - if len(vls) > 0 { - index := rand.IntN(len(vls)) - val, ok := ls.Remove(index) - assert.Equal(val, vls[index]) - assert.Equal(true, ok) - vls = append(vls[:index], vls[index+1:]...) - } - - // Range - case 13: - if len(vls) > 0 { - end := rand.IntN(len(vls)) - if end == 0 { - return - } - start := rand.IntN(end) - - var count int - ls.Range(start, end, func(data []byte) bool { - assert.Equal(b2s(data), vls[start+count]) - count++ - return false - }) - } + assert.Equal(i, N) + + for _, start := range []int{100, 1000, 5000} { + i = 0 + ls.RevRange(start, start+100, func(data []byte) { + assert.Equal(string(data), genKey(N-start-i-1)) + i++ + }) + assert.Equal(i, 100) } }) } diff --git a/internal/list/listpack.go b/internal/list/listpack.go index 4bdefb2..28335ea 100644 --- a/internal/list/listpack.go +++ b/internal/list/listpack.go @@ -1,17 +1,21 @@ package list import ( - "bytes" "encoding/binary" "slices" + "github.com/klauspost/compress/zstd" "github.com/xgzlucario/rotom/internal/pkg" ) -var ( +const ( maxListPackSize = 8 * 1024 +) - bpool = pkg.NewBufferPool() +var ( + bpool = pkg.NewBufferPool() + encoder, _ = zstd.NewWriter(nil) + decoder, _ = zstd.NewReader(nil) ) // ListPack is a lists of strings serialization format on Redis. @@ -31,197 +35,173 @@ var ( Using this structure, it is fast to iterate from both sides. */ type ListPack struct { - size uint16 - data []byte + compress bool + size uint32 + data []byte } -func NewListPack() *ListPack { - return &ListPack{data: make([]byte, 0, 32)} +func NewListPack(val ...string) *ListPack { + return &ListPack{data: bpool.Get(32)[:0]} } func (lp *ListPack) Size() int { return int(lp.size) } -// lpIterator is listpack iterator. -type lpIterator func(data []byte, index int, startPos, endPos int) (stop bool) +func (lp *ListPack) LPush(data ...string) { + lp.Iterator().Insert(data...) +} -func (lp *ListPack) iterFront(start, end int, f lpIterator) { - if end == -1 { - end = lp.Size() - } - var index int - for i := 0; i < end && index < len(lp.data); i++ { - // - // index dataStartPos dataEndPos indexNext - // | | | | - // +------------+--------------+---------------------+-----+ - // --> | data_len | data | entry_len | ... | - // +------------+--------------+---------------------+-----+ - // |<--- n ---->|<- data_len ->|<-- size_entry_len ->| - // - dataLen, n := binary.Uvarint(lp.data[index:]) - indexNext := index + n + int(dataLen) + +SizeUvarint(dataLen+uint64(n)) - - if i >= start { - dataStartPos := index + n - dataEndPos := dataStartPos + int(dataLen) - - data := lp.data[dataStartPos:dataEndPos] - if f(data, i, index, indexNext) { - return - } - } - index = indexNext - } +func (lp *ListPack) RPush(data ...string) { + lp.Iterator().SeekLast().Insert(data...) } -func (lp *ListPack) iterBack(start, end int, f lpIterator) { - if end == -1 { - end = lp.Size() - } - var index = len(lp.data) - for i := 0; i < end && index > 0; i++ { - // - // indexNext dataStartPos dataEndPos index - // | | | | - // +-----+------------+--------------+---------------------+ - // | ... | data_len | data | entry_len | <-- - // +-----+------------+--------------+---------------------+ - // |<--- n ---->|<- data_len ->|<-- size_entry_len ->| - // |<------ entry_len -------->| - // - entryLen, sizeEntryLen := uvarintReverse(lp.data[:index]) - indexNext := index - int(entryLen) - sizeEntryLen - - if i >= start { - dataLen, n := binary.Uvarint(lp.data[indexNext:]) - dataStartPos := indexNext + n - dataEndPos := dataStartPos + int(dataLen) - - data := lp.data[dataStartPos:dataEndPos] - if f(data, i, indexNext, index) { - return - } - } - index = indexNext +func (lp *ListPack) LPop() (string, bool) { + return lp.Iterator().RemoveNext() +} + +func (lp *ListPack) RPop() (string, bool) { + it := lp.Iterator().SeekLast() + it.Prev() + return it.RemoveNext() +} + +func (lp *ListPack) Compress() { + if !lp.compress { + dst := encoder.EncodeAll(lp.data, bpool.Get(len(lp.data))[:0]) + bpool.Put(lp.data) + lp.data = slices.Clip(dst) + lp.compress = true } } -// find quickly locates the element based on index. -// When the target index is in the first half, use forward traversal; -// otherwise, use reverse traversal. -func (lp *ListPack) find(index int, fn func(old []byte, index int, startPos, endPos int)) { - if lp.size == 0 || index >= lp.Size() || index < 0 { - return +func (lp *ListPack) Decompress() { + if lp.compress { + lp.data, _ = decoder.DecodeAll(lp.data, bpool.Get(maxListPackSize)[:0]) + lp.compress = false } - if index <= lp.Size()/2 { - lp.iterFront(index, index+1, func(old []byte, index int, startPos, endPos int) bool { - fn(old, index, startPos, endPos) - return true - }) - } else { - index = lp.Size() - index - 1 - lp.iterBack(index, index+1, func(old []byte, index int, startPos, endPos int) bool { - fn(old, index, startPos, endPos) - return true - }) +} + +type lpIterator struct { + *ListPack + index int +} + +func (lp *ListPack) Iterator() *lpIterator { + return &lpIterator{ListPack: lp} +} + +func (it *lpIterator) SeekFirst() *lpIterator { + it.index = 0 + return it +} + +func (it *lpIterator) SeekLast() *lpIterator { + it.index = len(it.data) + return it +} + +func (it *lpIterator) IsFirst() bool { return it.index == 0 } + +func (it *lpIterator) IsLast() bool { return it.index == len(it.data) } + +func (it *lpIterator) Next() []byte { + if it.IsLast() { + return nil } + // + // index dataStartPos dataEndPos indexNext + // | | | | + // +------------+--------------+---------------------+-----+ + // --> | data_len | data | entry_len | ... | + // +------------+--------------+---------------------+-----+ + // |<--- n ---->|<- data_len ->|<-- size_entry_len ->| + // + dataLen, n := binary.Uvarint(it.data[it.index:]) + indexNext := it.index + n + int(dataLen) + SizeUvarint(dataLen+uint64(n)) + + dataStartPos := it.index + n + dataEndPos := dataStartPos + int(dataLen) + + data := it.data[dataStartPos:dataEndPos] + it.index = indexNext + + return data } -// Insert datas into listpack. -// index = 0: same as `LPush` -// index = -1: same as `RPush` -func (lp *ListPack) Insert(index int, datas ...string) { - if index == -1 { - index = lp.Size() +func (it *lpIterator) Prev() []byte { + if it.IsFirst() { + return nil } + // + // indexNext dataStartPos dataEndPos index + // | | | | + // +-----+------------+--------------+---------------------+ + // | ... | data_len | data | entry_len | <-- + // +-----+------------+--------------+---------------------+ + // |<--- n ---->|<- data_len ->|<-- size_entry_len ->| + // |<------ entry_len -------->| + // + entryLen, sizeEntryLen := uvarintReverse(it.data[:it.index]) + indexNext := it.index - int(entryLen) - sizeEntryLen + + dataLen, n := binary.Uvarint(it.data[indexNext:]) + dataStartPos := indexNext + n + dataEndPos := dataStartPos + int(dataLen) + + data := it.data[dataStartPos:dataEndPos] + it.index = indexNext + + return data +} - // rpush - if index == lp.Size() { +func (it *lpIterator) Insert(datas ...string) { + if it.IsLast() { for _, data := range datas { - lp.data = appendEntry(lp.data, data) - lp.size++ + it.data = appendEntry(it.data, data) + it.size++ } return } - - // insert - if index < lp.Size() { - var pos int - lp.find(index, func(_ []byte, _, startPos, _ int) { - pos = startPos - }) - - var alloc []byte - for _, data := range datas { - alloc = appendEntry(alloc, data) - lp.size++ - } - lp.data = slices.Insert(lp.data, pos, alloc...) - bpool.Put(alloc) + alloc := bpool.Get(maxListPackSize)[:0] + for _, data := range datas { + alloc = appendEntry(alloc, data) + it.size++ } + it.data = slices.Insert(it.data, it.index, alloc...) + bpool.Put(alloc) } -func (lp *ListPack) Set(index int, data string) (ok bool) { - if index == -1 { - index = lp.Size() - 1 +func (it *lpIterator) removeNext(copyNext bool) (data string, ok bool) { + if it.size == 0 { + return "", false } - lp.find(index, func(old []byte, _, startPos, endPos int) { - if len(data) == len(old) { - copy(old, data) - } else { - alloc := appendEntry(nil, data) - lp.data = slices.Replace(lp.data, startPos, endPos, alloc...) - bpool.Put(alloc) - } - ok = true - }) - return -} + before := it.index -func (lp *ListPack) Remove(index int) (val string, ok bool) { - if index == -1 { - index = lp.Size() - 1 + if copyNext { + data = string(it.Next()) + } else { + it.Next() } - lp.find(index, func(data []byte, _, startPos, endPos int) { - val = string(data) - lp.data = slices.Delete(lp.data, startPos, endPos) - lp.size-- - ok = true - }) - return -} - -func (lp *ListPack) RemoveFirst(data string) (res int, ok bool) { - lp.iterFront(0, -1, func(old []byte, index int, startPos, endPos int) bool { - if bytes.Equal(s2b(&data), old) { - lp.data = slices.Delete(lp.data, startPos, endPos) - lp.size-- - res, ok = index, true - } - return ok - }) - return + + after := it.index + it.data = slices.Delete(it.data, before, after) + it.index = before + it.size-- + + return data, true } -func (lp *ListPack) Range(start, end int, fn func(data []byte, index int) (stop bool)) { - lp.iterFront(start, end, func(data []byte, index int, _, _ int) bool { - return fn(data, index) - }) +func (it *lpIterator) RemoveNext() (string, bool) { + return it.removeNext(true) } -func (lp *ListPack) RevRange(start, end int, fn func(data []byte, index int) (stop bool)) { - lp.iterBack(start, end, func(data []byte, index int, _, _ int) bool { - return fn(data, index) - }) +func (it *lpIterator) ReplaceNext(key string) { + it.removeNext(false) + it.Insert(key) } -// encode data to [data_len, data, entry_len]. func appendEntry(dst []byte, data string) []byte { - if dst == nil { - dst = bpool.Get(maxListPackSize)[:0] - } before := len(dst) dst = appendUvarint(dst, len(data), false) dst = append(dst, data...) diff --git a/internal/list/listpack_test.go b/internal/list/listpack_test.go index 7f4e3ac..5cc4c40 100644 --- a/internal/list/listpack_test.go +++ b/internal/list/listpack_test.go @@ -2,330 +2,114 @@ package list import ( "fmt" - "math/rand/v2" "testing" "github.com/stretchr/testify/assert" ) -func genListPack(start, end int) *ListPack { - lp := NewListPack() - for i := start; i < end; i++ { - lp.Insert(-1, genKey(i)) - } - return lp +func genKey(i int) string { + return fmt.Sprintf("%06x", i) } -func genKey(i int) string { - return fmt.Sprintf("%08x", i) +func lp2list(lp *ListPack) (res []string) { + it := lp.Iterator() + for !it.IsLast() { + res = append(res, string(it.Next())) + } + return } -func TestListPack(t *testing.T) { +func TestListpack(t *testing.T) { assert := assert.New(t) - const N = 1000 - t.Run("rpush/lpop", func(t *testing.T) { + t.Run("rpush", func(t *testing.T) { lp := NewListPack() - for i := 0; i < N; i++ { - assert.Equal(lp.Size(), i) - lp.Insert(-1, genKey(i)) - } - for i := 0; i < N; i++ { - val, ok := lp.Remove(0) - assert.Equal(val, genKey(i)) - assert.Equal(true, ok) - } + lp.RPush("A") + lp.RPush("B", "C") + assert.Equal(lp.Size(), 3) + assert.Equal(lp2list(lp), []string{"A", "B", "C"}) }) - t.Run("lpush/rpop", func(t *testing.T) { + t.Run("rpush", func(t *testing.T) { lp := NewListPack() - for i := 0; i < N; i++ { - assert.Equal(lp.Size(), i) - lp.Insert(0, genKey(i)) - } - for i := 0; i < N; i++ { - val, ok := lp.Remove(-1) - assert.Equal(val, genKey(i)) - assert.Equal(true, ok) - } + lp.LPush("A") + lp.LPush("B", "C") + assert.Equal(lp.Size(), 3) + assert.Equal(lp2list(lp), []string{"B", "C", "A"}) }) - t.Run("iterFront", func(t *testing.T) { - lp := genListPack(0, N) - - // iter [0, -1] - var i int - lp.iterFront(0, -1, func(data []byte, _, _, _ int) bool { - assert.Equal(string(data), genKey(i)) - i++ - return false - }) - assert.Equal(i, N) - - // iter [0, N/2] - i = 0 - lp.iterFront(0, N/2, func(data []byte, _, _, _ int) bool { - assert.Equal(string(data), genKey(i)) - i++ - return false - }) - assert.Equal(i, N/2) - - // iter [N/2, -1] - i = 0 - lp.iterFront(N/2, -1, func(data []byte, _, _, _ int) bool { - assert.Equal(string(data), genKey(i+N/2)) - i++ - return false - }) - assert.Equal(i, N/2) - }) + t.Run("lpop", func(t *testing.T) { + lp := NewListPack() + lp.LPush("A", "B", "C") - t.Run("iterBack", func(t *testing.T) { - lp := genListPack(0, N) + it := lp.Iterator() - // iter [0, -1] - var i int - lp.iterBack(0, -1, func(data []byte, _, _, _ int) bool { - assert.Equal(string(data), genKey(N-1-i)) - i++ - return false - }) - assert.Equal(i, N) + val, ok := it.RemoveNext() + assert.Equal(val, "A") + assert.True(ok) - // iter [0, N/2] - i = 0 - lp.iterBack(0, N/2, func(data []byte, _, _, _ int) bool { - assert.Equal(string(data), genKey(N-1-i)) - i++ - return false - }) - assert.Equal(i, N/2) + val, ok = it.RemoveNext() + assert.Equal(val, "B") + assert.True(ok) - // iter [N/2, -1] - i = 0 - lp.iterBack(N/2, -1, func(data []byte, _, _, _ int) bool { - assert.Equal(string(data), genKey(N/2-i-1)) - i++ - return false - }) - assert.Equal(i, N/2) - }) + val, ok = it.RemoveNext() + assert.Equal(val, "C") + assert.True(ok) - t.Run("remove", func(t *testing.T) { - lp := genListPack(0, N) - - val, ok := lp.Remove(N) + // empty + val, ok = it.RemoveNext() assert.Equal(val, "") - assert.Equal(false, ok) - - val, ok = lp.Remove(0) - assert.Equal(val, genKey(0)) - assert.Equal(true, ok) - - res, ok := lp.Remove(0) - assert.Equal(res, genKey(1)) - assert.Equal(true, ok) + assert.False(ok) }) - t.Run("removeFirst", func(t *testing.T) { - lp := genListPack(0, N) - - index, ok := lp.RemoveFirst(genKey(N)) - if index != 0 || ok { - t.Error(ok, index, N) - } - - index, ok = lp.RemoveFirst(genKey(0)) - assert.Equal(index, 0) - assert.Equal(true, ok) - - res, ok := lp.Remove(0) - assert.Equal(res, genKey(1)) - assert.Equal(true, ok) - }) - - t.Run("set", func(t *testing.T) { - lp := genListPack(0, N) - - for i := 0; i < N; i++ { - newKey := fmt.Sprintf("newkey-%d", i) - ok := lp.Set(i, newKey) - if !ok { - t.Error(ok) - } - - var val string - lp.find(i, func(data []byte, index, _, _ int) { - val = string(data) - }) - if newKey != val { - t.Error(ok, val, i) - } - } - - // set -1 - ok := lp.Set(-1, "last") - if !ok { - t.Error(ok) - } - - var val string - lp.find(lp.Size()-1, func(data []byte, index, _, _ int) { - val = string(data) - }) - if string("last") != val { - t.Error(ok, val) - } + t.Run("rpop", func(t *testing.T) { + lp := NewListPack() + lp.LPush("A", "B", "C") - // out of bound - ok = lp.Set(N+1, "newKey") - assert.Equal(false, ok) - }) + it := lp.Iterator().SeekLast() - t.Run("insert", func(t *testing.T) { - lp := genListPack(0, 10) + it.Prev() + val, ok := it.RemoveNext() + assert.Equal(val, "C") + assert.True(ok) - // insert to head - lp.Insert(0, "test0") - val, ok := lp.Remove(0) - assert.Equal(val, "test0") - assert.Equal(ok, true) + it.Prev() + val, ok = it.RemoveNext() + assert.Equal(val, "B") + assert.True(ok) - // insert to mid - lp.Insert(5, "test1") - val, ok = lp.Remove(5) - assert.Equal(val, "test1") - assert.Equal(ok, true) + it.Prev() + val, ok = it.RemoveNext() + assert.Equal(val, "A") + assert.True(ok) - // insert to tail - lp.Insert(-1, "test2") - val, ok = lp.Remove(-1) - assert.Equal(val, "test2") - assert.Equal(ok, true) + // empty + it.Prev() + val, ok = it.RemoveNext() + assert.Equal(val, "") + assert.False(ok) }) - t.Run("range", func(t *testing.T) { - lp := genListPack(0, N) + t.Run("replaceNext", func(t *testing.T) { + lp := NewListPack() + lp.LPush("TEST1", "TEST2", "TEST3") - // range [0,-1] - count := 0 - lp.Range(0, -1, func(data []byte, index int) (stop bool) { - if string(data) != genKey(index) { - t.Error(string(data), genKey(index)) - } - count++ - return false - }) - if count != N { - t.Error(count, N) - } + it := lp.Iterator() + it.ReplaceNext("TEST4") + assert.Equal(lp2list(lp), []string{"TEST4", "TEST2", "TEST3"}) - // revrange [0,-1] - count = 0 - lp.RevRange(0, -1, func(data []byte, index int) (stop bool) { - if string(data) != genKey(N-index-1) { - t.Error(string(data), genKey(N-index-1)) - } - count++ - return false - }) - if count != N { - t.Error(count, N) - } + it.ReplaceNext("ABC") + assert.Equal(lp2list(lp), []string{"ABC", "TEST2", "TEST3"}) - // range [0,N/2] - count = 0 - lp.Range(0, N/2, func(data []byte, index int) (stop bool) { - if index > N/2 { - t.Error(index, N/2) - } - count++ - return false - }) - if count != N/2 { - t.Error(count, N) - } + it.ReplaceNext("TTTTTT") + assert.Equal(lp2list(lp), []string{"TTTTTT", "TEST2", "TEST3"}) }) -} - -func FuzzListPack(f *testing.F) { - ls := NewListPack() - vls := make([]string, 0, 4096) - f.Fuzz(func(t *testing.T, rkey string) { - assert := assert.New(t) - - switch rand.IntN(15) { - // RPush - case 0, 1, 2: - ls.Insert(-1, rkey) - vls = append(vls, rkey) - - // LPush - case 3, 4, 5: - ls.Insert(0, rkey) - vls = append([]string{rkey}, vls...) - - // LPop - case 6, 7: - val, ok := ls.Remove(0) - if len(vls) > 0 { - valVls := vls[0] - vls = vls[1:] - assert.Equal(val, valVls) - assert.Equal(true, ok) - } else { - assert.Equal(val, "") - assert.Equal(false, ok) - } - - // RPop - case 8, 9: - val, ok := ls.Remove(-1) - if len(vls) > 0 { - valVls := vls[len(vls)-1] - vls = vls[:len(vls)-1] - assert.Equal(val, valVls) - assert.Equal(true, ok) - } else { - assert.Equal(val, "") - assert.Equal(false, ok) - } - - // Set - case 10: - if len(vls) > 0 { - index := rand.IntN(len(vls)) - ok := ls.Set(index, rkey) - assert.Equal(true, ok) - vls[index] = rkey - } - - // Remove - case 12: - if len(vls) > 0 { - index := rand.IntN(len(vls)) - val, ok := ls.Remove(index) - assert.Equal(val, vls[index]) - assert.Equal(true, ok) - vls = append(vls[:index], vls[index+1:]...) - } - - // Range - case 13: - if len(vls) <= 1 { - break - } - end := rand.IntN(len(vls)-1) + 1 - start := rand.IntN(end) - - var count int - ls.Range(start, end, func(data []byte, index int) (stop bool) { - assert.Equal(b2s(data), vls[start+count]) - count++ - return false - }) - } + t.Run("compress", func(t *testing.T) { + lp := NewListPack() + lp.LPush("A", "B", "C", "D", "E") + lp.Compress() + lp.Decompress() + assert.Equal(lp2list(lp), []string{"A", "B", "C", "D", "E"}) }) } diff --git a/internal/list/utils.go b/internal/list/utils.go index 83a053c..c2d72f7 100644 --- a/internal/list/utils.go +++ b/internal/list/utils.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "math/bits" "slices" - "unsafe" ) func appendUvarint(b []byte, n int, reverse bool) []byte { @@ -46,15 +45,3 @@ func uvarintReverse(buf []byte) (uint64, int) { func SizeUvarint(x uint64) int { return int(9*uint32(bits.Len64(x))+64) / 64 } - -func s2b(str *string) []byte { - strHeader := (*[2]uintptr)(unsafe.Pointer(str)) - byteSliceHeader := [3]uintptr{ - strHeader[0], strHeader[1], strHeader[1], - } - return *(*[]byte)(unsafe.Pointer(&byteSliceHeader)) -} - -func b2s(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} diff --git a/internal/pkg/allocator.go b/internal/pkg/allocator.go deleted file mode 100644 index 9f87800..0000000 --- a/internal/pkg/allocator.go +++ /dev/null @@ -1,48 +0,0 @@ -package pkg - -import ( - "sync" - - "github.com/cockroachdb/swiss" -) - -// Allocator is a group pool for swissmap. -type Allocator[K comparable, V any] struct { - pool *sync.Pool - miss, hit uint64 -} - -func NewAllocator[K comparable, V any]() *Allocator[K, V] { - return &Allocator[K, V]{ - pool: &sync.Pool{ - New: func() interface{} { return new([]swiss.Group[K, V]) }, - }, - } -} - -func (p *Allocator[K, V]) Alloc(want int) []swiss.Group[K, V] { - buf := p.pool.Get().(*[]swiss.Group[K, V]) - - if cap(*buf) < want { - *buf = make([]swiss.Group[K, V], want) - p.miss++ - - } else { - *buf = (*buf)[:want] - p.hit++ - } - - return *buf -} - -func (p *Allocator[K, V]) Free(b []swiss.Group[K, V]) { - p.pool.Put(&b) -} - -func (p *Allocator[K, V]) Miss() uint64 { - return p.miss -} - -func (p *Allocator[K, V]) Hit() uint64 { - return p.hit -} diff --git a/main.go b/main.go index d0ba745..7bb96d3 100644 --- a/main.go +++ b/main.go @@ -10,17 +10,24 @@ import ( "github.com/rs/zerolog" ) -var log = zerolog. - New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.DateTime}). - Level(zerolog.TraceLevel). - With(). - Timestamp(). - Logger() +var ( + log = initLogger() + buildTime string +) func runDebug() { go http.ListenAndServe(":6060", nil) } +func initLogger() zerolog.Logger { + return zerolog. + New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.DateTime}). + Level(zerolog.TraceLevel). + With(). + Timestamp(). + Logger() +} + func main() { var path string var debug bool @@ -29,7 +36,8 @@ func main() { flag.BoolVar(&debug, "debug", false, "run with debug mode.") flag.Parse() - log.Debug().Str("config", path).Bool("debug", debug).Msg("read cmd arguments") + log.Info().Str("buildTime", buildTime).Msg("current version") + log.Info().Str("config", path).Bool("debug", debug).Msg("read cmd arguments") config, err := LoadConfig(path) if err != nil { @@ -45,14 +53,14 @@ func main() { runDebug() } - log.Debug().Int("port", config.Port).Msg("running on") - log.Debug().Msg("rotom server is ready to accept.") + log.Info().Int("port", config.Port).Msg("running on") + log.Info().Msg("rotom server is ready to accept.") // register main aeLoop event server.aeLoop.AddRead(server.fd, AcceptHandler, nil) - server.aeLoop.AddTimeEvent(AE_NORMAL, 100, ServerCronEvict, nil) + server.aeLoop.AddTimeEvent(AE_NORMAL, 100, EvictExpired, nil) if server.config.AppendOnly { - server.aeLoop.AddTimeEvent(AE_NORMAL, 1000, ServerCronFlush, nil) + server.aeLoop.AddTimeEvent(AE_NORMAL, 1000, SyncAOF, nil) } server.aeLoop.AeMain() } diff --git a/rotom.go b/rotom.go index bd4edb3..f0f8ba9 100644 --- a/rotom.go +++ b/rotom.go @@ -3,7 +3,6 @@ package main import ( "io" - "github.com/cockroachdb/swiss" "github.com/xgzlucario/rotom/internal/dict" "github.com/xgzlucario/rotom/internal/hash" "github.com/xgzlucario/rotom/internal/list" @@ -17,16 +16,15 @@ const ( ) type ( - Map = *hash.Map - Set = *hash.Set + Map = hash.MapI + Set = hash.SetI List = *list.QuickList ZSet = *zset.ZSet ) type DB struct { - strs *dict.Dict - extras *swiss.Map[string, any] - aof *Aof + dict *dict.Dict + aof *Aof } type Client struct { @@ -51,8 +49,7 @@ var ( // InitDB initializes database and redo appendonly files if nedded. func InitDB(config *Config) (err error) { - db.strs = dict.New(dict.DefaultOptions) - db.extras = swiss.New[string, any](64) + db.dict = dict.New() if config.AppendOnly { db.aof, err = NewAof(config.AppendFileName) @@ -158,7 +155,7 @@ func ProcessQueryBuf(client *Client) { } else { err := ErrUnknownCommand(command) client.replyWriter.WriteError(err) - log.Warn().Msgf("ERR %v", err) + log.Error().Msg(err.Error()) } } @@ -199,16 +196,12 @@ func initServer(config *Config) (err error) { return nil } -func ServerCronFlush(loop *AeLoop, id int, extra interface{}) { - if db.aof == nil { - return - } - err := db.aof.Flush() - if err != nil { - log.Error().Msgf("flush aof buffer error: %v", err) +func SyncAOF(loop *AeLoop, id int, extra interface{}) { + if err := db.aof.Flush(); err != nil { + log.Error().Msgf("sync aof error: %v", err) } } -func ServerCronEvict(loop *AeLoop, id int, extra interface{}) { - db.strs.EvictExpired() +func EvictExpired(loop *AeLoop, id int, extra interface{}) { + db.dict.EvictExpired() }