-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 026ea53
Showing
7 changed files
with
246 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# This workflow will build a golang project | ||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go | ||
|
||
name: Go | ||
|
||
on: | ||
push: | ||
branches: ["main"] | ||
pull_request: | ||
branches: ["main"] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: "1.21.6" | ||
|
||
- name: Build | ||
run: go build -v ./... | ||
|
||
- name: Test | ||
run: go test -v ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
data | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
A key-value store inspired by [BitCask](https://github.com/basho/bitcask) | ||
|
||
``` | ||
go run . | ||
go test -bench=. # for benchmarks | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"log" | ||
"os" | ||
"strings" | ||
) | ||
|
||
func main() { | ||
kv, err := NewKV() | ||
if err != nil { | ||
log.Fatalln("Error initializing KV store", err) | ||
} | ||
defer func(kv *KV) { | ||
err := kv.Close() | ||
if err != nil { | ||
log.Fatalln("Failed to close") | ||
} | ||
}(kv) | ||
scanner := bufio.NewScanner(os.Stdin) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
spaceIdx := strings.Index(line, " ") | ||
command := line | ||
if spaceIdx != -1 { | ||
command = line[0:spaceIdx] | ||
} | ||
|
||
switch command { | ||
case "GET": | ||
value, err := kv.Get(line[spaceIdx+1:]) | ||
if err != nil { | ||
fmt.Println(err) | ||
} else { | ||
fmt.Println(value) | ||
} | ||
case "SET": | ||
args := line[spaceIdx+1:] | ||
spaceIdx := strings.Index(args, " ") | ||
if spaceIdx == -1 { | ||
fmt.Println("Syntax SET <key> <value>") | ||
continue | ||
} | ||
key := args[0:spaceIdx] | ||
value := args[spaceIdx+1:] | ||
err := kv.Set(key, value) | ||
if err != nil { | ||
fmt.Println("Error while setting", err) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module sattar.dev/kv | ||
|
||
go 1.21.6 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/binary" | ||
"io" | ||
"os" | ||
) | ||
|
||
type KV struct { | ||
file *os.File | ||
index map[string]KeyDir | ||
} | ||
|
||
type KeyDir struct { | ||
valueSize int64 | ||
valueOffset int64 | ||
} | ||
|
||
func NewKV() (*KV, error) { | ||
file, err := os.OpenFile("data", os.O_RDWR|os.O_CREATE, 0644) | ||
if err != nil { | ||
return nil, err | ||
} | ||
index := make(map[string]KeyDir) | ||
kv := KV{file: file, index: index} | ||
err = buildIndex(&kv) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &kv, nil | ||
} | ||
|
||
func buildIndex(kv *KV) error { | ||
_, err := kv.file.Seek(0, io.SeekStart) | ||
if err != nil { | ||
return err | ||
} | ||
offset := int64(0) | ||
for { | ||
keyValueSizeBytes := make([]byte, 8+8) | ||
_, err = kv.file.Read(keyValueSizeBytes) | ||
if err != nil { | ||
if err.Error() == "EOF" { | ||
return nil | ||
} | ||
return err | ||
} | ||
keySize := int64(binary.BigEndian.Uint64(keyValueSizeBytes[0:8])) | ||
valueSize := int64(binary.BigEndian.Uint64(keyValueSizeBytes[8:])) | ||
|
||
offset += 8 + 8 | ||
|
||
keyValueBytes := make([]byte, keySize+valueSize) | ||
_, err = kv.file.Read(keyValueBytes) | ||
if err != nil { | ||
return err | ||
} | ||
key := string(keyValueBytes[:keySize]) | ||
|
||
kv.index[key] = KeyDir{valueSize: valueSize, valueOffset: offset + keySize} | ||
|
||
offset += keySize + valueSize | ||
} | ||
} | ||
|
||
func (kv *KV) Close() error { | ||
return kv.file.Close() | ||
} | ||
|
||
func (kv *KV) Get(key string) (string, error) { | ||
keyDir, exists := kv.index[key] | ||
if !exists { | ||
err := buildIndex(kv) | ||
if err != nil { | ||
return "", err | ||
} | ||
} | ||
return readAt(kv.file, keyDir.valueOffset, keyDir.valueSize) | ||
} | ||
|
||
func readAt(file *os.File, offset int64, size int64) (string, error) { | ||
_, err := file.Seek(offset, io.SeekStart) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
contentBytes := make([]byte, size) | ||
_, err = file.Read(contentBytes) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return string(contentBytes), nil | ||
} | ||
|
||
func (kv *KV) Set(key string, value string) error { | ||
keyBytes := []byte(key) | ||
valueBytes := []byte(value) | ||
keySizeBytes := intToBuffer(uint64(len(keyBytes))) | ||
valueSizeBytes := intToBuffer(uint64(len(valueBytes))) | ||
|
||
offset, err := kv.file.Seek(0, io.SeekCurrent) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
order := [][]byte{ | ||
keySizeBytes, valueSizeBytes, keyBytes, valueBytes, | ||
} | ||
var bytesToWrite []byte | ||
for _, r := range order { | ||
bytesToWrite = append(bytesToWrite, r...) | ||
} | ||
|
||
if _, err := kv.file.Write(bytesToWrite); err != nil { | ||
return err | ||
} | ||
kv.index[key] = KeyDir{valueSize: int64(len(valueBytes)), valueOffset: offset + int64(8+8) + int64(len(keyBytes))} | ||
return nil | ||
} | ||
|
||
func intToBuffer(number uint64) []byte { | ||
buf := make([]byte, 8) | ||
binary.BigEndian.PutUint64(buf, number) | ||
return buf | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func BenchmarkWrites(b *testing.B) { | ||
kv, err := NewKV() | ||
defer func(kv *KV) { | ||
err := kv.Close() | ||
if err != nil { | ||
fmt.Println(err) | ||
panic(err) | ||
} | ||
}(kv) | ||
if err != nil { | ||
fmt.Println(err) | ||
panic(err) | ||
} | ||
for i := 0; i < b.N; i++ { | ||
err := kv.Set(fmt.Sprintf("key%d", i), "value") | ||
if err != nil { | ||
fmt.Println(err) | ||
panic(err) | ||
} | ||
} | ||
} |