Skip to content

Commit 0ab7275

Browse files
committed
feat(conc): introduce conc operations with chans and mutex for queue
1 parent efa8865 commit 0ab7275

File tree

6 files changed

+198
-221
lines changed

6 files changed

+198
-221
lines changed

README.md

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
1-
#### memcache
1+
### memcache
22

33
in memory cache via http api
44

5+
#### endpoints
56

7+
`/save`
8+
9+
to save data in to cache
10+
data format
11+
12+
```json
13+
{
14+
"key":"some-key",
15+
"value":"some-value"
16+
}
17+
```
18+
19+
req type - **POST**
20+
21+
`/get`
22+
23+
to retrieve data from cache
24+
25+
data format
26+
27+
```json
28+
{
29+
"key":"some-key"
30+
}
31+
```
32+
33+
#### about
34+
35+
this is a concurrent implementation of cache via http.
36+
Can be used across any service layer as a stage or landing for some data
37+
which you require to consume / refer / lookup
38+
39+
**TODO**
40+
41+
- [ ] benchmark and test for disaster recovery
42+
- [ ] implement a leader process to have data sync between instances

datastructure/datastructure.go

+74-44
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,102 @@ package datastructure
22

33
// TODO: make it concurrent using channels
44
import (
5+
"strings"
56
"sync"
67
)
78

9+
type Data struct {
10+
Key any `json:"key"`
11+
Value any `json:"value"`
12+
}
13+
814
// inMemoryCache concurrent cache memory
915
var inMemoryCache sync.Map
16+
var result = make(chan any)
17+
var load = make(chan bool)
18+
var storeCh = make(chan *Data)
19+
20+
// var errCh = make(chan error)
21+
var done = make(chan bool)
22+
23+
func buildInp(k, v any) {
24+
go func() {
25+
d := &Data{
26+
Key: k, Value: v,
27+
}
28+
storeCh <- d
29+
}()
30+
}
1031

1132
// Add inserts new element in cache.when the element
1233
// is already present it replaces existing value with new
1334
// incoming value and returns new value and loaded flag as true
1435
// triggers update to queue , which adds provided data to end of
1536
// queue
16-
func Add(k, v any) (result any, loaded bool) {
17-
result, loaded = inMemoryCache.LoadOrStore(k, v)
18-
if loaded {
19-
result = v
20-
// update queue with new element
21-
inMemoryIdx.add(k)
22-
return
23-
}
24-
inMemoryIdx.add(k)
25-
return
37+
func Add(k, v any) (any, bool) {
38+
buildInp(k, v)
39+
go func() {
40+
data := <-storeCh
41+
// load data
42+
_, l := inMemoryCache.LoadOrStore(data.Key, data.Value)
43+
// return output
44+
result <- data.Value
45+
load <- l
46+
// update queue
47+
idx := <-inMemoryIdx.getIdx(k)
48+
// add when missing
49+
if idx < 0 {
50+
inMemoryIdx.add(k)
51+
done <- true
52+
return
53+
}
54+
// when present move to top
55+
inMemoryIdx.swap(idx)
56+
done <- true
57+
}()
58+
r := <-result
59+
l := <-load
60+
return r, l
2661
}
2762

2863
// Delete removes provided key from cache
64+
// remove the key from queue
2965
func Delete(k any) {
3066
inMemoryCache.Delete(k)
31-
}
32-
33-
// Get fetches matching key from cache
34-
// update queue in such manner the recently accessed
35-
// element from cache is moved to front of queue
36-
func Get(k any) (v any, ok bool) {
37-
v, ok = inMemoryCache.Load(k)
38-
// when found in cache
39-
if ok {
40-
updQueue(k)
67+
idx := <-inMemoryIdx.getIdx(k)
68+
if idx < 0 {
69+
return
4170
}
42-
return
71+
// remove element from queue
72+
inMemoryIdx.removeAt(idx)
4373
}
4474

45-
func updQueue(k any) {
46-
idx := inMemoryIdx.getIdx(k)
47-
// when element is missing from queue
48-
// upd queue and move it to first position
49-
if idx < 0 {
50-
lastPosition := len(inMemoryIdx.list) - 1
51-
inMemoryIdx.list[lastPosition] = k
52-
inMemoryIdx.swap(lastPosition)
53-
54-
}
75+
const NotFound = "not found"
5576

77+
func fetch(k any) chan any {
78+
out := make(chan any)
79+
go func(key any) {
80+
v, ok := inMemoryCache.Load(key)
81+
// when found in cache
82+
if ok {
83+
out <- v
84+
// upd queue
85+
idx := <-inMemoryIdx.getIdx(k)
86+
inMemoryIdx.swap(idx)
87+
return
88+
}
89+
out <- NotFound
90+
}(k)
91+
return out
5692
}
5793

58-
// func updQueue(k any) {
59-
// // get element position in queue
60-
// idx := inMemoryIdx.getIndex(k)
61-
// // not found in queue
62-
// if idx < 0 {
63-
// // add new key to queue
64-
// inMemoryIdx.add(k)
65-
// } else {
66-
// // move element to front of queue
67-
// inMemoryIdx.swap(idx)
68-
// }
69-
//
70-
// }
94+
func Get(k any) any {
95+
out := <-fetch(k)
96+
if strings.EqualFold(out.(string), NotFound) {
97+
return NotFound
98+
}
99+
return out
100+
}
71101

72102
// NewQueue creates new inmemory store and queue
73103
// each instance of `once` creates new store and queue

datastructure/queue.go

+29-56
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,65 @@
11
package datastructure
22

3-
// type inMemoryQueue []any
3+
import "sync"
44

55
type queue struct {
66
list []any
77
lastAdded int
8+
lock *sync.RWMutex
89
}
910

1011
func new(size int) *queue {
1112
return &queue{
1213
list: make([]any, size),
1314
lastAdded: 0,
15+
lock: &sync.RWMutex{},
1416
}
1517
}
1618

1719
func (q *queue) add(k any) {
20+
q.lock.Lock()
21+
defer q.lock.Unlock()
1822
q.list[q.lastAdded] = k
1923
q.lastAdded++
2024
}
2125

2226
func (q *queue) swap(idx int) {
27+
q.lock.Lock()
28+
defer q.lock.Unlock()
2329
q.list[0], q.list[idx] = q.list[idx], q.list[0]
2430
}
2531

26-
func (q *queue) getIdx(k any) int {
27-
totLen := len(q.list) - 1
28-
for i := 0; i < totLen; i++ {
29-
if q.list[i] == k {
30-
return i
31-
}
32+
func (q *queue) getIdx(k any) chan int {
33+
out := make(chan int)
34+
go func() {
35+
totLen := len(q.list) - 1
36+
for i := 0; i < totLen; i++ {
37+
if q.list[i] == k {
38+
out <- i
39+
}
3240

33-
}
34-
return -1
41+
}
42+
out <- -1
43+
}()
44+
return out
3545

3646
}
3747

3848
var inMemoryIdx *queue
3949

4050
func (q *queue) evict() {
51+
q.lock.Lock()
52+
defer q.lock.Unlock()
4153
lastElementPosition := len(q.list) - 1
4254
q.list[lastElementPosition] = nil
4355
}
4456

45-
// swap replaces first value in queue with
46-
// recently accessed index
47-
// func (idx *inMemoryQueue) swap(i int) {
48-
// tmp := (*idx)[0]
49-
// (*idx)[0] = (*idx)[i]
50-
// (*idx)[i] = tmp
51-
// }
52-
//
53-
// func (idx *inMemoryQueue) getIndex(k any) int {
54-
// totLen := len(*idx) - 1
55-
// for i := 0; i < totLen; i++ {
56-
// if (*idx)[i] == k {
57-
// return i
58-
// }
59-
// }
60-
// return -1
61-
// }
62-
63-
// add inserts the key to last empty position in queue
64-
// func (idx *inMemoryQueue) add(k any) {
65-
// index := idx.check()
66-
// (*idx)[index] = k
67-
// }
68-
69-
// func (idx *inMemoryQueue) check() int {
70-
// // when queue is empty return first position
71-
// if (*idx)[0] == nil {
72-
// return 0
73-
// }
74-
// // get len of queue
75-
// totLen := len(*idx) - 1
76-
// // return the first free space in queue
77-
// for i := totLen; i >= 0; i-- {
78-
// if (*idx)[i] == nil {
79-
// return i
80-
// }
81-
// }
82-
// return totLen
83-
//
84-
// }
57+
func (q *queue) removeAt(idx int) {
58+
q.lock.Lock()
59+
defer q.lock.Unlock()
60+
q.list = append(q.list[:idx], q.list[idx+1:]...)
61+
if q.lastAdded >= 1 {
62+
q.lastAdded = q.lastAdded - 1
63+
}
8564

86-
// evict follows last accessed/final element
87-
// in queue and remove it from queue and cache store
88-
// this is called only when queue is full and no space a vailable
89-
// func (idx *) evict() {
90-
// totLen := len(*idx) - 1
91-
// inMemoryIdx[totLen] = nil
92-
// }
65+
}

0 commit comments

Comments
 (0)