Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit 1e9e85b

Browse files
Merge pull request #1203 from DiceDB/last-min-2
WAL implementation
2 parents 5067809 + 1009593 commit 1e9e85b

File tree

23 files changed

+745
-42
lines changed

23 files changed

+745
-42
lines changed

build_protos.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
protoc --go_out=. ./internal/wal/wal.proto

config/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ var (
5555
EnableProfiling = false
5656

5757
EnableWatch = true
58+
LogDir = ""
59+
60+
EnableWAL = true
61+
RestoreFromWAL = false
62+
WALEngine = "sqlite"
5863
)
5964

6065
type Config struct {

docs/src/content/docs/commands/GETDEL.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ Setting a key `mylist` as a list and then trying to use `GETDEL`, which is incom
8888
127.0.0.1:7379> LPUSH mylist "item1"
8989
(integer) 1
9090
127.0.0.1:7379> GETDEL mylist
91-
<<<<<<< HEAD
9291
ERROR WRONGTYPE Operation against a key holding the wrong kind of value
9392
```
9493

@@ -97,7 +96,3 @@ ERROR WRONGTYPE Operation against a key holding the wrong kind of value
9796
- The key `mylist` is a list, not a string.
9897
- # The `GETDEL` command raises a `WRONGTYPE` error because it expects the key to be a string.
9998
(error) WRONGTYPE Operation against a key holding the wrong kind of value
100-
101-
```
102-
>>>>>>> d43577926873d0df0c8f189cdde6afa65c515ccb
103-
```

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ require (
4747
github.com/google/go-cmp v0.6.0
4848
github.com/google/uuid v1.6.0
4949
github.com/gorilla/websocket v1.5.3
50+
github.com/mattn/go-sqlite3 v1.14.24
5051
github.com/mmcloughlin/geohash v0.10.0
5152
github.com/ohler55/ojg v1.25.0
5253
github.com/pelletier/go-toml/v2 v2.2.3
@@ -58,4 +59,5 @@ require (
5859
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
5960
golang.org/x/crypto v0.28.0
6061
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
62+
google.golang.org/protobuf v1.35.1
6163
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
6565
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
6666
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
6767
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
68+
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
69+
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
6870
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
6971
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
7072
github.com/mmcloughlin/geohash v0.10.0 h1:9w1HchfDfdeLc+jFEf/04D27KP7E2QmpDu52wPbJWRE=
@@ -131,6 +133,8 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
131133
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
132134
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
133135
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
136+
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
137+
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
134138
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
135139
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
136140
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

integration_tests/commands/async/setup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func RunTestServer(ctx context.Context, wg *sync.WaitGroup, opt TestServerOption
123123
gec := make(chan error)
124124
shardManager := shard.NewShardManager(1, watchChan, nil, gec)
125125
// Initialize the AsyncServer
126-
testServer := server.NewAsyncServer(shardManager, watchChan)
126+
testServer := server.NewAsyncServer(shardManager, watchChan, nil)
127127

128128
// Try to bind to a port with a maximum of `totalRetries` retries.
129129
for i := 0; i < totalRetries; i++ {

integration_tests/commands/http/setup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func RunHTTPServer(ctx context.Context, wg *sync.WaitGroup, opt TestServerOption
108108
queryWatcherLocal := querymanager.NewQueryManager()
109109
config.HTTPPort = opt.Port
110110
// Initialize the HTTPServer
111-
testServer := server.NewHTTPServer(shardManager)
111+
testServer := server.NewHTTPServer(shardManager, nil)
112112
// Inform the user that the server is starting
113113
fmt.Println("Starting the test server on port", config.HTTPPort)
114114
shardManagerCtx, cancelShardManager := context.WithCancel(ctx)

integration_tests/commands/resp/setup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func RunTestServer(wg *sync.WaitGroup, opt TestServerOptions) {
128128
shardManager := shard.NewShardManager(1, queryWatchChan, cmdWatchChan, gec)
129129
workerManager := worker.NewWorkerManager(20000, shardManager)
130130
// Initialize the RESP Server
131-
testServer := resp.NewServer(shardManager, workerManager, cmdWatchSubscriptionChan, cmdWatchChan, gec)
131+
testServer := resp.NewServer(shardManager, workerManager, cmdWatchSubscriptionChan, cmdWatchChan, gec, nil)
132132

133133
ctx, cancel := context.WithCancel(context.Background())
134134
fmt.Println("Starting the test server on port", config.DiceConfig.AsyncServer.Port)

integration_tests/commands/websocket/setup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func RunWebsocketServer(ctx context.Context, wg *sync.WaitGroup, opt TestServerO
109109
shardManager := shard.NewShardManager(1, watchChan, nil, globalErrChannel)
110110
queryWatcherLocal := querymanager.NewQueryManager()
111111
config.WebsocketPort = opt.Port
112-
testServer := server.NewWebSocketServer(shardManager, testPort1)
112+
testServer := server.NewWebSocketServer(shardManager, testPort1, nil)
113113
shardManagerCtx, cancelShardManager := context.WithCancel(ctx)
114114

115115
// run shard manager

internal/cmd/cmds.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@ type RedisCmds struct {
1717
RequestID uint32
1818
}
1919

20+
// Repr returns a string representation of the command.
21+
func (cmd *DiceDBCmd) Repr() string {
22+
return fmt.Sprintf("%s %s", cmd.Cmd, strings.Join(cmd.Args, " "))
23+
}
24+
2025
// GetFingerprint returns a 32-bit fingerprint of the command and its arguments.
2126
func (cmd *DiceDBCmd) GetFingerprint() uint32 {
22-
return farm.Fingerprint32([]byte(fmt.Sprintf("%s-%s", cmd.Cmd, strings.Join(cmd.Args, " "))))
27+
return farm.Fingerprint32([]byte(cmd.Repr()))
2328
}
2429

2530
// GetKey Returns the key which the command operates on.

internal/server/httpServer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313
"time"
1414

1515
"github.com/dicedb/dice/internal/eval"
16-
1716
"github.com/dicedb/dice/internal/server/abstractserver"
17+
"github.com/dicedb/dice/internal/wal"
1818

1919
"github.com/dicedb/dice/config"
2020
"github.com/dicedb/dice/internal/clientio"
@@ -61,7 +61,7 @@ func (cim *CaseInsensitiveMux) ServeHTTP(w http.ResponseWriter, r *http.Request)
6161
cim.mux.ServeHTTP(w, r)
6262
}
6363

64-
func NewHTTPServer(shardManager *shard.ShardManager) *HTTPServer {
64+
func NewHTTPServer(shardManager *shard.ShardManager, wl wal.AbstractWAL) *HTTPServer {
6565
mux := http.NewServeMux()
6666
caseInsensitiveMux := &CaseInsensitiveMux{mux: mux}
6767
srv := &http.Server{

internal/server/resp/server.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"time"
1313

1414
"github.com/dicedb/dice/internal/server/abstractserver"
15+
"github.com/dicedb/dice/internal/wal"
1516

1617
dstore "github.com/dicedb/dice/internal/store"
1718
"github.com/dicedb/dice/internal/watchmanager"
@@ -49,20 +50,21 @@ type Server struct {
4950
cmdWatchSubscriptionChan chan watchmanager.WatchSubscription
5051
cmdWatchChan chan dstore.CmdWatchEvent
5152
globalErrorChan chan error
53+
wl wal.AbstractWAL
5254
}
5355

54-
func NewServer(shardManager *shard.ShardManager, workerManager *worker.WorkerManager, cmdWatchSubscriptionChan chan watchmanager.WatchSubscription,
55-
cmdWatchChan chan dstore.CmdWatchEvent, globalErrChan chan error) *Server {
56+
func NewServer(shardManager *shard.ShardManager, workerManager *worker.WorkerManager,
57+
cmdWatchSubscriptionChan chan watchmanager.WatchSubscription, cmdWatchChan chan dstore.CmdWatchEvent, globalErrChan chan error, wl wal.AbstractWAL) *Server {
5658
return &Server{
57-
Host: config.DiceConfig.AsyncServer.Addr,
58-
Port: config.DiceConfig.AsyncServer.Port,
59-
connBacklogSize: DefaultConnBacklogSize,
60-
workerManager: workerManager,
61-
shardManager: shardManager,
62-
watchManager: watchmanager.NewManager(cmdWatchSubscriptionChan),
63-
cmdWatchChan: cmdWatchChan,
64-
cmdWatchSubscriptionChan: cmdWatchSubscriptionChan,
65-
globalErrorChan: globalErrChan,
59+
Host: config.DiceConfig.AsyncServer.Addr,
60+
Port: config.DiceConfig.AsyncServer.Port,
61+
connBacklogSize: DefaultConnBacklogSize,
62+
workerManager: workerManager,
63+
shardManager: shardManager,
64+
watchManager: watchmanager.NewManager(cmdWatchSubscriptionChan),
65+
cmdWatchChan: cmdWatchChan,
66+
globalErrorChan: globalErrChan,
67+
wl: wl,
6668
}
6769
}
6870

@@ -198,7 +200,7 @@ func (s *Server) AcceptConnectionRequests(ctx context.Context, wg *sync.WaitGrou
198200
preprocessingChan := make(chan *ops.StoreResponse) // preprocessingChan is specifically for handling responses from shards for commands that require preprocessing
199201

200202
wID := GenerateUniqueWorkerID()
201-
w := worker.NewWorker(wID, responseChan, preprocessingChan, s.cmdWatchSubscriptionChan, ioHandler, parser, s.shardManager, s.globalErrorChan)
203+
w := worker.NewWorker(wID, responseChan, preprocessingChan, s.cmdWatchSubscriptionChan, ioHandler, parser, s.shardManager, s.globalErrorChan, s.wl)
202204

203205
// Register the worker with the worker manager
204206
err = s.workerManager.RegisterWorker(w)

internal/server/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/dicedb/dice/internal/server/abstractserver"
17+
"github.com/dicedb/dice/internal/wal"
1718

1819
"github.com/dicedb/dice/config"
1920
"github.com/dicedb/dice/internal/auth"
@@ -44,7 +45,7 @@ type AsyncServer struct {
4445
}
4546

4647
// NewAsyncServer initializes a new AsyncServer
47-
func NewAsyncServer(shardManager *shard.ShardManager, queryWatchChan chan dstore.QueryWatchEvent) *AsyncServer {
48+
func NewAsyncServer(shardManager *shard.ShardManager, queryWatchChan chan dstore.QueryWatchEvent, wl wal.AbstractWAL) *AsyncServer {
4849
return &AsyncServer{
4950
maxClients: config.DiceConfig.Performance.MaxClients,
5051
connectedClients: make(map[int]*comm.Client),

internal/server/websocketServer.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"time"
1616

1717
"github.com/dicedb/dice/internal/server/abstractserver"
18+
"github.com/dicedb/dice/internal/wal"
1819

1920
"github.com/dicedb/dice/config"
2021
"github.com/dicedb/dice/internal/clientio"
@@ -46,7 +47,7 @@ type WebsocketServer struct {
4647
shutdownChan chan struct{}
4748
}
4849

49-
func NewWebSocketServer(shardManager *shard.ShardManager, port int) *WebsocketServer {
50+
func NewWebSocketServer(shardManager *shard.ShardManager, port int, wl wal.AbstractWAL) *WebsocketServer {
5051
mux := http.NewServeMux()
5152
srv := &http.Server{
5253
Addr: fmt.Sprintf(":%d", port),

internal/wal/wal.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package wal
2+
3+
import (
4+
"fmt"
5+
"log/slog"
6+
sync "sync"
7+
"time"
8+
9+
"github.com/dicedb/dice/internal/cmd"
10+
)
11+
12+
type AbstractWAL interface {
13+
LogCommand(c *cmd.DiceDBCmd)
14+
Close() error
15+
Init(t time.Time) error
16+
ForEachCommand(f func(c cmd.DiceDBCmd) error) error
17+
}
18+
19+
var (
20+
ticker *time.Ticker
21+
stopCh chan struct{}
22+
mu sync.Mutex
23+
)
24+
25+
func init() {
26+
ticker = time.NewTicker(1 * time.Minute)
27+
stopCh = make(chan struct{})
28+
}
29+
30+
func rotateWAL(wl AbstractWAL) {
31+
mu.Lock()
32+
defer mu.Unlock()
33+
34+
if err := wl.Close(); err != nil {
35+
slog.Warn("error closing the WAL", slog.Any("error", err))
36+
}
37+
38+
if err := wl.Init(time.Now()); err != nil {
39+
slog.Warn("error creating a new WAL", slog.Any("error", err))
40+
}
41+
}
42+
43+
func periodicRotate(wl AbstractWAL) {
44+
for {
45+
select {
46+
case <-ticker.C:
47+
rotateWAL(wl)
48+
case <-stopCh:
49+
return
50+
}
51+
}
52+
}
53+
54+
func InitBG(wl AbstractWAL) {
55+
go periodicRotate(wl)
56+
}
57+
58+
func ShutdownBG() {
59+
close(stopCh)
60+
ticker.Stop()
61+
}
62+
63+
func ReplayWAL(wl AbstractWAL) {
64+
err := wl.ForEachCommand(func(c cmd.DiceDBCmd) error {
65+
fmt.Println("replaying", c.Cmd, c.Args)
66+
return nil
67+
})
68+
69+
if err != nil {
70+
slog.Warn("error replaying WAL", slog.Any("error", err))
71+
}
72+
}

0 commit comments

Comments
 (0)