From d4835339a4281972bbb5a39b253fe99a2c8ac5b1 Mon Sep 17 00:00:00 2001 From: chenyanchen Date: Tue, 10 Dec 2024 13:08:26 +0800 Subject: [PATCH] feat: add example implementations for caching and telemetry --- examples/cache/main.go | 65 ++++++++++++++++++++ examples/go.mod | 17 ++++- examples/go.sum | 26 +++++++- examples/telemetry/main.go | 123 +++++++++++++++++++++++++++++++++++++ 4 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 examples/cache/main.go create mode 100644 examples/telemetry/main.go diff --git a/examples/cache/main.go b/examples/cache/main.go new file mode 100644 index 0000000..a4a192c --- /dev/null +++ b/examples/cache/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "context" + "fmt" + + "github.com/chenyanchen/db/cachekv" + "github.com/chenyanchen/db/layerkv" +) + +func main() { + userDatabaseKV := &databaseKV{} + userLRUKV, err := cachekv.NewLRU[int, *User](1<<10, nil, 0) + if err != nil { + panic(err) + } + + userKV, err := layerkv.New(userLRUKV, userDatabaseKV) + if err != nil { + panic(err) + } + + ctx := context.Background() + + // 1st get user from database + user, err := userKV.Get(ctx, 1) + if err != nil { + panic(err) + } + + // 2nd get user from cache + user, err = userKV.Get(ctx, 1) + if err != nil { + panic(err) + } + + fmt.Printf("user: %+v\n", user) +} + +type User struct { + ID int + Name string +} + +// Database implementation + +type databaseKV struct { + // uncomment the following line to use the database + // db *sql.DB +} + +func (s *databaseKV) Get(ctx context.Context, id int) (*User, error) { + return &User{ + ID: id, + Name: "Mock Name", + }, nil +} + +func (s *databaseKV) Set(ctx context.Context, id int, user *User) error { + return nil +} + +func (s *databaseKV) Del(ctx context.Context, id int) error { + return nil +} diff --git a/examples/go.mod b/examples/go.mod index 9b30553..0fad9d2 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -2,8 +2,21 @@ module github.com/chenyanchen/db/examples go 1.23 -require github.com/chenyanchen/db v0.0.0-00010101000000-000000000000 +require ( + github.com/chenyanchen/db v0.0.0-00010101000000-000000000000 + github.com/prometheus/client_golang v1.20.5 +) replace github.com/chenyanchen/db => ../ -require github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + golang.org/x/sys v0.27.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/examples/go.sum b/examples/go.sum index eaff70d..58e0861 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,10 +1,32 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/telemetry/main.go b/examples/telemetry/main.go new file mode 100644 index 0000000..9cd0b01 --- /dev/null +++ b/examples/telemetry/main.go @@ -0,0 +1,123 @@ +package main + +import ( + "context" + "fmt" + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/chenyanchen/db" + "github.com/chenyanchen/db/cachekv" + "github.com/chenyanchen/db/layerkv" +) + +func main() { + userDatabaseKV := &databaseKV{} + userLRUKV, err := cachekv.NewLRU[int, *User](1<<10, nil, 0) + if err != nil { + panic(err) + } + + // For example cache hit ratio: + // rate(example_kv_user_kv_operation_duration_seconds_count{name="user_lru_kv", operation="Get", success="true"}[$__rate_interval])) / + // (rate(example_kv_user_kv_operation_duration_seconds_count{name="user_lru_kv", operation="Get", success="true"}[$__rate_interval]) + rate(example_kv_user_kv_operation_duration_seconds_count{name="user_database_kv", operation="Get", success="true"}[$__rate_interval])) + userKV, err := layerkv.New( + NewTelemetry(userLRUKV, NewRecorder("user_lru_kv")), + NewTelemetry(userDatabaseKV, NewRecorder("user_database_kv")), + ) + if err != nil { + panic(err) + } + + ctx := context.Background() + + // 1st get user from database + user, err := userKV.Get(ctx, 1) + if err != nil { + panic(err) + } + + // 2nd get user from cache + user, err = userKV.Get(ctx, 1) + if err != nil { + panic(err) + } + + fmt.Printf("user: %+v\n", user) +} + +type User struct { + ID int + Name string +} + +// Database implementation + +type databaseKV struct { + // uncomment the following line to use the database + // db *sql.DB +} + +func (s *databaseKV) Get(ctx context.Context, id int) (*User, error) { + return &User{ + ID: id, + Name: "Mock Name", + }, nil +} + +func (s *databaseKV) Set(ctx context.Context, id int, user *User) error { + return nil +} + +func (s *databaseKV) Del(ctx context.Context, id int) error { + return nil +} + +// Telemetry implementation + +type recordFunc func(operation string, success bool, duration time.Duration) + +type telemetry[K comparable, V any] struct { + next db.KV[K, V] + record recordFunc +} + +func NewTelemetry[K comparable, V any](next db.KV[K, V], record recordFunc) telemetry[K, V] { + return telemetry[K, V]{next: next, record: record} +} + +func (t telemetry[K, V]) Get(ctx context.Context, k K) (V, error) { + now := time.Now() + v, err := t.next.Get(ctx, k) + t.record("Get", err == nil, time.Since(now)) + return v, err +} + +func (t telemetry[K, V]) Set(ctx context.Context, k K, v V) error { + now := time.Now() + err := t.next.Set(ctx, k, v) + t.record("Set", err == nil, time.Since(now)) + return err +} + +func (t telemetry[K, V]) Del(ctx context.Context, k K) error { + now := time.Now() + err := t.next.Del(ctx, k) + t.record("Del", err == nil, time.Since(now)) + return err +} + +var histogram = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Namespace: "example", + Subsystem: "kv", + Name: "user_kv_operation_duration_seconds", +}, []string{"name", "operation", "success"}) + +func NewRecorder(name string) recordFunc { + return func(operation string, success bool, duration time.Duration) { + histogram.WithLabelValues(name, operation, strconv.FormatBool(success)).Observe(duration.Seconds()) + } +}