-
Notifications
You must be signed in to change notification settings - Fork 5
Feature/tiered storage lru policy #960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/tiered_storage
Are you sure you want to change the base?
Changes from 13 commits
9cef74d
d51bfe8
d805f91
432d54d
628feaa
2ccb80f
63b276c
ca744a6
8e31b0c
ed56450
ea8b42a
3ea1864
4b15841
449e766
3f3a1b8
74cce48
adef71e
e9dcb70
13c608c
8ac166b
d6c9a30
7a58ff6
776d9b3
5a99492
37507f9
ed6d5ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,265 @@ | ||
| package tiered_storage | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "sync" | ||
| "time" | ||
|
|
||
| "github.com/Seagate/cloudfuse/common" | ||
| "github.com/Seagate/cloudfuse/common/log" | ||
| ) | ||
|
|
||
| type lruNode struct { | ||
| prev *lruNode | ||
| next *lruNode | ||
| name string | ||
| } | ||
|
|
||
| //upload 50 files and then check | ||
|
|
||
| type lruQueue struct { | ||
| mu sync.Mutex | ||
|
|
||
| nodeMap sync.Map | ||
|
|
||
| wg sync.WaitGroup | ||
| numWorkers int | ||
|
|
||
| head *lruNode | ||
| tail *lruNode | ||
|
|
||
| uploadChan chan string | ||
| doneChan chan struct{} | ||
|
|
||
| cachePath string | ||
| maxCacheSize float64 | ||
|
|
||
| threshold float64 | ||
| targetRatio float64 | ||
|
|
||
| tickerUnit time.Duration | ||
|
|
||
| //Functions to wire later into tiered_storage package | ||
| //upload function from tiered storage WIRE THIS LATER in tiered storage because we are using a function from there | ||
| upload func(name string) error | ||
|
|
||
| FileHasOpenFileHandle func(name string) bool | ||
|
|
||
| //policy.isFileInUse = func(name string) bool { | ||
| // return c.fileLocks.Get(name).Count() > 0 | ||
| //} | ||
|
|
||
| //we also need the filLock map to use | ||
|
|
||
| } | ||
|
|
||
| func (q *lruQueue) StartPolicy() error { | ||
| if q.upload == nil { | ||
| return fmt.Errorf("lruQueue: upload function not set") | ||
| } | ||
| if q.numWorkers <= 0 { | ||
| return fmt.Errorf("lruQueue: numWorkers must be > 0") | ||
| } | ||
| //initialize queue | ||
| q.head = nil | ||
| q.tail = nil | ||
| //channels | ||
| q.uploadChan = make(chan string, 1000) | ||
| q.doneChan = make(chan struct{}) | ||
| //timer | ||
| //go routines | ||
| q.wg.Add(1) | ||
| go q.capacityChecker() | ||
|
|
||
| q.wg.Add(q.numWorkers) | ||
| for i := 0; i < q.numWorkers; i++ { | ||
| go q.worker() | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (q *lruQueue) StopPolicy() error { | ||
| close(q.doneChan) | ||
| q.wg.Wait() | ||
| return nil | ||
| } | ||
|
|
||
| func (q *lruQueue) Touch(name string) { | ||
| q.Enqueue(name) | ||
| } | ||
|
|
||
| func (q *lruQueue) Enqueue(name string) { | ||
| //Maybe have a duplicate , that touches essentially | ||
|
|
||
| //lock earlier | ||
| q.mu.Lock() | ||
| defer q.mu.Unlock() | ||
|
|
||
| //create node | ||
| newNode := &lruNode{name: name} | ||
| val, found := q.nodeMap.LoadOrStore(name, newNode) | ||
| node := val.(*lruNode) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, dereferencing / type asserting |
||
|
|
||
| if found { | ||
| // touch | ||
| q.extractNode(node) | ||
| } else { | ||
| // brand new node — update tail if list was empty | ||
| if q.tail == nil { | ||
| q.tail = node | ||
| } | ||
| } | ||
| q.setHead(node) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This creates a new node before searching for an existing one. It still does what we expect in the end, but it smells off. |
||
| } | ||
|
|
||
| func (q *lruQueue) Dequeue(name string) { | ||
| log.Trace("lruPolicy::removeNode : %s", name) | ||
|
|
||
| q.mu.Lock() | ||
| defer q.mu.Unlock() | ||
|
|
||
| val, found := q.nodeMap.LoadAndDelete(name) | ||
| if !found || val == nil { | ||
| return | ||
| } | ||
|
|
||
| node := val.(*lruNode) | ||
|
|
||
| q.extractNode(node) | ||
| } | ||
|
|
||
| func (q *lruQueue) setHead(node *lruNode) { | ||
| // insert node at the head | ||
| node.prev = nil | ||
| node.next = q.head | ||
| if q.head != nil { | ||
| q.head.prev = node | ||
| } | ||
| q.head = node | ||
| } | ||
|
|
||
| func (q *lruQueue) extractNode(node *lruNode) { | ||
| // remove the node from its position in the list | ||
|
|
||
| // head case | ||
| if node == q.head { | ||
| q.head = node.next | ||
| } | ||
| //tail case | ||
| if node == q.tail { | ||
| q.tail = node.prev | ||
| } | ||
|
|
||
| if node.next != nil { | ||
| node.next.prev = node.prev | ||
| } | ||
| if node.prev != nil { | ||
| node.prev.next = node.next | ||
| } | ||
| node.prev = nil | ||
| node.next = nil | ||
| } | ||
|
|
||
| func (q *lruQueue) capacityChecker() { | ||
| defer q.wg.Done() | ||
| defer close(q.uploadChan) | ||
|
|
||
| ticker := time.NewTicker(q.tickerUnit) | ||
| defer ticker.Stop() | ||
|
|
||
| for { | ||
| select { | ||
| case <-ticker.C: | ||
| // eviction | ||
|
|
||
| //check du , do stat file before, based on difference between DU and | ||
|
|
||
| //1. check if we need eviction | ||
| curSize, err := common.GetUsage(q.cachePath) | ||
| if err != nil { | ||
| log.Err("lruPolicy::capacityChecker : failed to get usage: %v", err) | ||
| continue | ||
| } | ||
| if curSize/q.maxCacheSize <= q.threshold { | ||
| break | ||
| } | ||
|
|
||
| //targetRatio should always be less than thresholdRatio | ||
|
|
||
| //find difference to evict down to 60% | ||
| difference := curSize - q.maxCacheSize*q.targetRatio | ||
| curEvictedSpace := 0 | ||
| for curEvictedSpace < int(difference) { | ||
| nodeSize, evicted := q.eviction() | ||
| if !evicted { | ||
| break | ||
| } | ||
| curEvictedSpace += int(nodeSize) | ||
| } | ||
|
Comment on lines
+182
to
+256
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks good! |
||
|
|
||
| case <-q.doneChan: | ||
| return | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func (q *lruQueue) eviction() (int64, bool) { | ||
| q.mu.Lock() | ||
| nodeToEvict := q.tail | ||
| if nodeToEvict == nil { | ||
| q.mu.Unlock() | ||
| return 0, false | ||
| } | ||
|
|
||
| //find the first applicable node | ||
| for nodeToEvict != nil && q.FileHasOpenFileHandle(nodeToEvict.name) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there could be a race condition here. After this check, it is possible that a new request comes in that opens the file before it gets uploaded. |
||
| prevNode := nodeToEvict.prev | ||
| q.extractNode(nodeToEvict) | ||
| q.setHead(nodeToEvict) | ||
| nodeToEvict = prevNode | ||
| } | ||
| //means all files are in use, what if all files are in use so what do we evict then?????time?????? | ||
| if nodeToEvict == nil { | ||
| q.mu.Unlock() | ||
| return 0, false | ||
| } | ||
|
|
||
| //right here is where we lock the file, where do we unlock it | ||
|
|
||
| //Get the node size that we evict | ||
| localPath := filepath.Join(q.cachePath, nodeToEvict.name) | ||
|
|
||
| fileInfo, err := os.Stat(localPath) | ||
| if err != nil { | ||
| log.Err("lruPolicy::capacityChecker : failed to stat file: %v", err) | ||
| q.mu.Unlock() | ||
| return 0, false | ||
| } | ||
| nodeSize := fileInfo.Size() | ||
|
|
||
| //remove node from queue and map | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the eviction code here, you delete from the LRU, but the file will still remain in the local cache? Do we want the file to stay in the cache or should we delete it when we evict? |
||
| q.extractNode(nodeToEvict) | ||
| q.nodeMap.Delete(nodeToEvict.name) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a rule we want to follow, for what node membership in the map and the linked list mean? In other words, should we update our records and then execute the action (upload & delete), or visa versa? Which is better for error handling? |
||
| q.mu.Unlock() | ||
|
|
||
| //send node to channel | ||
| select { | ||
| case q.uploadChan <- nodeToEvict.name: | ||
| case <-q.doneChan: | ||
| return 0, false | ||
| } | ||
|
|
||
| return nodeSize, true | ||
| } | ||
|
|
||
| func (q *lruQueue) worker() { | ||
| defer q.wg.Done() | ||
| for fileName := range q.uploadChan { | ||
| err := q.upload(fileName) | ||
| if err != nil { | ||
| log.Err("lruPolicy::worker : failed to upload file %s: %v", fileName, err) | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.