optimize session using benc
This commit is contained in:
parent
941e810acb
commit
8c134774ee
|
@ -4,18 +4,19 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/deneonet/benc"
|
||||||
|
bstd "github.com/deneonet/benc/std"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Session stores data for a single user session
|
// Session stores data for a single user session
|
||||||
type Session struct {
|
type Session struct {
|
||||||
ID string `json:"id"`
|
ID string
|
||||||
Data map[string]any `json:"data"`
|
Data map[string]any
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time
|
||||||
LastUsed time.Time `json:"last_used"`
|
LastUsed time.Time
|
||||||
Expiry time.Time `json:"expiry"`
|
Expiry time.Time
|
||||||
dirty bool // Tracks if session has changes, not serialized
|
dirty bool // Tracks if session has changes, not serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session pool to reduce allocations
|
// Session pool to reduce allocations
|
||||||
|
@ -27,6 +28,9 @@ var sessionPool = sync.Pool{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufPool for reusing serialization buffers
|
||||||
|
var bufPool = benc.NewBufPool(benc.WithBufferSize(4096))
|
||||||
|
|
||||||
// GetFromPool retrieves a session from the pool
|
// GetFromPool retrieves a session from the pool
|
||||||
func GetFromPool() *Session {
|
func GetFromPool() *Session {
|
||||||
return sessionPool.Get().(*Session)
|
return sessionPool.Get().(*Session)
|
||||||
|
@ -130,18 +134,178 @@ func (s *Session) ResetDirty() {
|
||||||
s.dirty = false
|
s.dirty = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal serializes the session to JSON
|
// SizePlain calculates the size needed to marshal the session
|
||||||
func (s *Session) Marshal() ([]byte, error) {
|
func (s *Session) SizePlain() (size int) {
|
||||||
return json.Marshal(s)
|
// ID
|
||||||
|
size += bstd.SizeString(s.ID)
|
||||||
|
|
||||||
|
// Data (map of string to any)
|
||||||
|
// For simplicity, we store data as binary-encoded strings
|
||||||
|
// This is a simplification, in a real-world scenario you would handle
|
||||||
|
// different types differently
|
||||||
|
dataAsStrings := make(map[string]string)
|
||||||
|
for k, v := range s.Data {
|
||||||
|
dataAsStrings[k] = toString(v)
|
||||||
|
}
|
||||||
|
size += bstd.SizeMap(dataAsStrings, bstd.SizeString, bstd.SizeString)
|
||||||
|
|
||||||
|
// Time fields
|
||||||
|
size += bstd.SizeInt64() * 4 // Store Unix timestamps for all time fields
|
||||||
|
|
||||||
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal deserializes a session from JSON
|
// MarshalPlain serializes the session to binary
|
||||||
|
func (s *Session) MarshalPlain(n int, b []byte) (int, error) {
|
||||||
|
// ID
|
||||||
|
n = bstd.MarshalString(n, b, s.ID)
|
||||||
|
|
||||||
|
// Data
|
||||||
|
dataAsStrings := make(map[string]string)
|
||||||
|
for k, v := range s.Data {
|
||||||
|
dataAsStrings[k] = toString(v)
|
||||||
|
}
|
||||||
|
n = bstd.MarshalMap(n, b, dataAsStrings, bstd.MarshalString, bstd.MarshalString)
|
||||||
|
|
||||||
|
// Time fields as Unix timestamps
|
||||||
|
n = bstd.MarshalInt64(n, b, s.CreatedAt.Unix())
|
||||||
|
n = bstd.MarshalInt64(n, b, s.UpdatedAt.Unix())
|
||||||
|
n = bstd.MarshalInt64(n, b, s.LastUsed.Unix())
|
||||||
|
n = bstd.MarshalInt64(n, b, s.Expiry.Unix())
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalPlain deserializes the session from binary
|
||||||
|
func (s *Session) UnmarshalPlain(n int, b []byte) (int, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// ID
|
||||||
|
n, s.ID, err = bstd.UnmarshalString(n, b)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data
|
||||||
|
var dataAsStrings map[string]string
|
||||||
|
n, dataAsStrings, err = bstd.UnmarshalMap[string, string](n, b, bstd.UnmarshalString, bstd.UnmarshalString)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert string data back to original types
|
||||||
|
s.Data = make(map[string]any, len(dataAsStrings))
|
||||||
|
for k, v := range dataAsStrings {
|
||||||
|
s.Data[k] = fromString(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time fields
|
||||||
|
var timestamp int64
|
||||||
|
|
||||||
|
// CreatedAt
|
||||||
|
n, timestamp, err = bstd.UnmarshalInt64(n, b)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
s.CreatedAt = time.Unix(timestamp, 0)
|
||||||
|
|
||||||
|
// UpdatedAt
|
||||||
|
n, timestamp, err = bstd.UnmarshalInt64(n, b)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
s.UpdatedAt = time.Unix(timestamp, 0)
|
||||||
|
|
||||||
|
// LastUsed
|
||||||
|
n, timestamp, err = bstd.UnmarshalInt64(n, b)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
s.LastUsed = time.Unix(timestamp, 0)
|
||||||
|
|
||||||
|
// Expiry
|
||||||
|
n, timestamp, err = bstd.UnmarshalInt64(n, b)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
s.Expiry = time.Unix(timestamp, 0)
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal serializes the session using benc
|
||||||
|
func (s *Session) Marshal() ([]byte, error) {
|
||||||
|
size := s.SizePlain()
|
||||||
|
|
||||||
|
data, err := bufPool.Marshal(size, func(b []byte) (n int) {
|
||||||
|
n, _ = s.MarshalPlain(0, b)
|
||||||
|
return n
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal deserializes a session using benc
|
||||||
func Unmarshal(data []byte) (*Session, error) {
|
func Unmarshal(data []byte) (*Session, error) {
|
||||||
session := GetFromPool()
|
session := GetFromPool()
|
||||||
err := json.Unmarshal(data, session)
|
_, err := session.UnmarshalPlain(0, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ReturnToPool(session)
|
ReturnToPool(session)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper functions to convert between any and string
|
||||||
|
// In a production environment, you would use a more robust serialization method for the map values
|
||||||
|
func toString(v any) string {
|
||||||
|
if v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch t := v.(type) {
|
||||||
|
case string:
|
||||||
|
return t
|
||||||
|
case []byte:
|
||||||
|
return string(t)
|
||||||
|
case int:
|
||||||
|
return "i:" + string(rune(t))
|
||||||
|
case bool:
|
||||||
|
if t {
|
||||||
|
return "b:t"
|
||||||
|
}
|
||||||
|
return "b:f"
|
||||||
|
default:
|
||||||
|
return "u:" // unknown type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromString(s string) any {
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(s) < 2 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := s[:2]
|
||||||
|
switch prefix {
|
||||||
|
case "i:":
|
||||||
|
if len(s) > 2 {
|
||||||
|
return int(rune(s[2]))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
case "b:":
|
||||||
|
if len(s) > 2 && s[2] == 't' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case "u:":
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -5,7 +5,9 @@ go 1.24.1
|
||||||
require (
|
require (
|
||||||
git.sharkk.net/Sky/LuaJIT-to-Go v0.0.0
|
git.sharkk.net/Sky/LuaJIT-to-Go v0.0.0
|
||||||
github.com/VictoriaMetrics/fastcache v1.12.2
|
github.com/VictoriaMetrics/fastcache v1.12.2
|
||||||
|
github.com/deneonet/benc v1.1.7
|
||||||
github.com/goccy/go-json v0.10.5
|
github.com/goccy/go-json v0.10.5
|
||||||
|
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||||
github.com/valyala/bytebufferpool v1.0.0
|
github.com/valyala/bytebufferpool v1.0.0
|
||||||
github.com/valyala/fasthttp v1.60.0
|
github.com/valyala/fasthttp v1.60.0
|
||||||
)
|
)
|
||||||
|
@ -15,7 +17,7 @@ require (
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0 // indirect
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -7,7 +7,10 @@ github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOL
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/deneonet/benc v1.1.7 h1:0XPxTTVJZq/ulxXvMn2Mzjx5XquekVky3wX6eTgA0vA=
|
||||||
|
github.com/deneonet/benc v1.1.7/go.mod h1:UCfkM5Od0B2huwv/ZItvtUb7QnALFt9YXtX8NXX4Lts=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
|
@ -16,17 +19,22 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
|
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
|
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
|
||||||
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
|
||||||
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw=
|
github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw=
|
||||||
github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc=
|
github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||||
|
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
Loading…
Reference in New Issue
Block a user