Skip to content

Commit 53bed30

Browse files
committed
Update lock strategy for faster writes
This commit changes the default lock strategy for general read and write operations, such as SET and INTERSECTS, to speed up write heavy workloads. In some cases showing a 35% performance increase for SET operations, while keeping read heavy workload performance the same as before.
1 parent 40f58b0 commit 53bed30

File tree

4 files changed

+109
-5
lines changed

4 files changed

+109
-5
lines changed

cmd/tile38-server/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ Advanced Options:
9090
--http-transport yes/no : HTTP transport (default: yes)
9191
--protected-mode yes/no : protected mode (default: yes)
9292
--nohup : do not exit on SIGHUP
93+
--spinlock : use a spinlock. For very write-heavy workloads
9394
9495
Developer Options:
9596
--dev : enable developer mode
@@ -144,6 +145,7 @@ Developer Options:
144145
nohup bool
145146
showEvioDisabled bool
146147
showThreadsDisabled bool
148+
spinlock bool
147149
)
148150

149151
var (
@@ -199,6 +201,9 @@ Developer Options:
199201
case "--nohup", "-nohup":
200202
nohup = true
201203
continue
204+
case "--spinlock", "-spinlock":
205+
spinlock = true
206+
continue
202207
case "--appendonly", "-appendonly":
203208
i++
204209
if i < len(os.Args) {
@@ -414,7 +419,7 @@ Developer Options:
414419
}
415420

416421
c := make(chan os.Signal, 1)
417-
shutdown := make (chan bool, 1)
422+
shutdown := make(chan bool, 1)
418423

419424
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
420425
go func() {
@@ -484,6 +489,7 @@ Developer Options:
484489
AppendFileName: appendFileName,
485490
QueueFileName: queueFileName,
486491
Shutdown: shutdown,
492+
Spinlock: spinlock,
487493
}
488494
if err := server.Serve(opts); err != nil {
489495
log.Fatal(err)

internal/server/expire.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const bgExpireDelay = time.Second / 10
1616
func (s *Server) backgroundExpiring(wg *sync.WaitGroup) {
1717
defer wg.Done()
1818
s.loopUntilServerStops(bgExpireDelay, func() {
19-
s.mu.Lock()
19+
s.mu.LockLowPriority()
2020
defer s.mu.Unlock()
2121
now := time.Now()
2222
s.backgroundExpireObjects(now)

internal/server/server.go

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,88 @@ type commandDetails struct {
6969
children []*commandDetails // for multi actions such as "PDEL"
7070
}
7171

72+
type rwlocker interface {
73+
LockLowPriority()
74+
Lock()
75+
Unlock()
76+
RLock()
77+
RUnlock()
78+
}
79+
80+
// rwspinlock is the same as a RWLock, but uses spinlocks instead of mutexes.
81+
type rwspinlock struct {
82+
state atomic.Int32
83+
}
84+
85+
func (l *rwspinlock) Lock() {
86+
for {
87+
state := l.state.Load()
88+
if state == 0 && l.state.CompareAndSwap(state, -1) {
89+
return
90+
}
91+
runtime.Gosched()
92+
}
93+
}
94+
func (l *rwspinlock) LockLowPriority() {
95+
// All write locks are low priority and unfair. First come first serve.
96+
l.Lock()
97+
}
98+
func (l *rwspinlock) Unlock() {
99+
if l.state.Add(1) > 0 {
100+
panic("Unlock of unlocked rwspinlock")
101+
}
102+
}
103+
func (l *rwspinlock) RLock() {
104+
for {
105+
state := l.state.Load()
106+
if state >= 0 && l.state.CompareAndSwap(state, state+1) {
107+
return
108+
}
109+
runtime.Gosched()
110+
}
111+
}
112+
func (l *rwspinlock) RUnlock() {
113+
if l.state.Add(-1) < 0 {
114+
panic("RUnlock of unlocked rwspinlock")
115+
}
116+
}
117+
118+
// rwmutex is the same as a RWLock but includes a LockLowPriority for
119+
// performing write locks that do not block the queue for readers.
120+
type rwmutex struct {
121+
mu sync.RWMutex
122+
}
123+
124+
func (l *rwmutex) Lock() {
125+
start := time.Now()
126+
for {
127+
for range 100 {
128+
if l.mu.TryLock() {
129+
return
130+
}
131+
runtime.Gosched()
132+
}
133+
if time.Since(start) > time.Millisecond*50 {
134+
break
135+
}
136+
}
137+
l.mu.Lock()
138+
}
139+
func (l *rwmutex) LockLowPriority() {
140+
for !l.mu.TryLock() {
141+
runtime.Gosched()
142+
}
143+
}
144+
func (l *rwmutex) Unlock() {
145+
l.mu.Unlock()
146+
}
147+
func (l *rwmutex) RLock() {
148+
l.mu.RLock()
149+
}
150+
func (l *rwmutex) RUnlock() {
151+
l.mu.RUnlock()
152+
}
153+
72154
// Server is a tile38 controller
73155
type Server struct {
74156
// user defined options
@@ -107,7 +189,7 @@ type Server struct {
107189
connsmu sync.RWMutex
108190
conns map[int]*Client
109191

110-
mu sync.RWMutex
192+
mu rwlocker // sync.RWMutex
111193

112194
// aof
113195
aof *os.File // active aof file
@@ -183,6 +265,9 @@ type Options struct {
183265

184266
// Shutdown allows for shutting down the server.
185267
Shutdown <-chan bool
268+
269+
// Spinlock uses a spinlock instead of a mutex
270+
Spinlock bool
186271
}
187272

188273
// Serve starts a new tile38 server
@@ -208,8 +293,17 @@ func Serve(opts Options) error {
208293
}
209294
}()
210295

296+
var lock rwlocker
297+
298+
if opts.Spinlock {
299+
lock = new(rwspinlock)
300+
} else {
301+
lock = new(rwmutex)
302+
}
303+
211304
// Initialize the s
212305
s := &Server{
306+
mu: lock,
213307
unix: opts.UnixSocketPath,
214308
host: opts.Host,
215309
port: opts.Port,
@@ -804,7 +898,7 @@ func (s *Server) watchLuaStatePool(wg *sync.WaitGroup) {
804898
func (s *Server) backgroundSyncAOF(wg *sync.WaitGroup) {
805899
defer wg.Done()
806900
s.loopUntilServerStops(time.Second, func() {
807-
s.mu.Lock()
901+
s.mu.LockLowPriority()
808902
defer s.mu.Unlock()
809903
s.flushAOF(true)
810904
})

scripts/build.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,9 @@ export CGO_ENABLED=0
3737
# go mod vendor
3838
# fi
3939

40+
if [[ "$GORACE" == "1" ]]; then
41+
goflags="$goflags -race"
42+
fi
43+
4044
# Build and store objects into original directory.
41-
go build -ldflags "$LDFLAGS" -o $1 cmd/$1/*.go
45+
go build -ldflags "$LDFLAGS" $goflags -o $1 cmd/$1/*.go

0 commit comments

Comments
 (0)