github.com/uber/kraken@v0.1.4/lib/store/cleanup.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package store 15 16 import ( 17 "fmt" 18 "os" 19 "sync" 20 "time" 21 22 "github.com/uber/kraken/lib/store/base" 23 "github.com/uber/kraken/lib/store/metadata" 24 "github.com/uber/kraken/utils/log" 25 26 "github.com/andres-erbsen/clock" 27 "github.com/uber-go/tally" 28 ) 29 30 // CleanupConfig defines configuration for periodically cleaning up idle files. 31 type CleanupConfig struct { 32 Disabled bool `yaml:"disabled"` 33 Interval time.Duration `yaml:"interval"` // How often cleanup runs. 34 TTI time.Duration `yaml:"tti"` // Time to idle based on last access time. 35 TTL time.Duration `yaml:"ttl"` // Time to live regardless of access. If 0, disables TTL. 36 } 37 38 func (c CleanupConfig) applyDefaults() CleanupConfig { 39 if c.Interval == 0 { 40 c.Interval = 30 * time.Minute 41 } 42 if c.TTI == 0 { 43 c.TTI = 6 * time.Hour 44 } 45 return c 46 } 47 48 type cleanupManager struct { 49 clk clock.Clock 50 stats tally.Scope 51 stopOnce sync.Once 52 stopc chan struct{} 53 } 54 55 func newCleanupManager(clk clock.Clock, stats tally.Scope) (*cleanupManager, error) { 56 hostname, err := os.Hostname() 57 if err != nil { 58 return nil, fmt.Errorf("look up hostname: %s", err) 59 } 60 stats = stats.Tagged(map[string]string{ 61 "module": "storecleanup", 62 "hostname": hostname, 63 }) 64 return &cleanupManager{ 65 clk: clk, 66 stats: stats, 67 stopc: make(chan struct{}), 68 }, nil 69 } 70 71 // addJob starts a background cleanup task which removes idle files from op based 72 // on the settings in config. op must set the desired states to clean before addJob 73 // is called. 74 func (m *cleanupManager) addJob(tag string, config CleanupConfig, op base.FileOp) { 75 config = config.applyDefaults() 76 if config.Disabled { 77 log.Warnf("Cleanup disabled for %s", op) 78 return 79 } 80 if config.TTL == 0 { 81 log.Warnf("TTL disabled for %s", op) 82 } 83 84 ticker := m.clk.Ticker(config.Interval) 85 86 usageGauge := m.stats.Tagged(map[string]string{"job": tag}).Gauge("disk_usage") 87 88 go func() { 89 for { 90 select { 91 case <-ticker.C: 92 log.Debugf("Performing cleanup of %s", op) 93 usage, err := m.scan(op, config.TTI, config.TTL) 94 if err != nil { 95 log.Errorf("Error scanning %s: %s", op, err) 96 } 97 usageGauge.Update(float64(usage)) 98 case <-m.stopc: 99 ticker.Stop() 100 return 101 } 102 } 103 }() 104 } 105 106 func (m *cleanupManager) stop() { 107 m.stopOnce.Do(func() { close(m.stopc) }) 108 } 109 110 // scan scans the op for idle or expired files. Also returns the total disk usage 111 // of op. 112 func (m *cleanupManager) scan( 113 op base.FileOp, tti time.Duration, ttl time.Duration) (usage int64, err error) { 114 115 names, err := op.ListNames() 116 if err != nil { 117 return 0, fmt.Errorf("list names: %s", err) 118 } 119 for _, name := range names { 120 info, err := op.GetFileStat(name) 121 if err != nil { 122 log.With("name", name).Errorf("Error getting file stat: %s", err) 123 continue 124 } 125 if ready, err := m.readyForDeletion(op, name, info, tti, ttl); err != nil { 126 log.With("name", name).Errorf("Error checking if file expired: %s", err) 127 } else if ready { 128 if err := op.DeleteFile(name); err != nil && err != base.ErrFilePersisted { 129 log.With("name", name).Errorf("Error deleting expired file: %s", err) 130 } 131 } 132 usage += info.Size() 133 } 134 return usage, nil 135 } 136 137 func (m *cleanupManager) readyForDeletion( 138 op base.FileOp, 139 name string, 140 info os.FileInfo, 141 tti time.Duration, 142 ttl time.Duration) (bool, error) { 143 144 if ttl > 0 && m.clk.Now().Sub(info.ModTime()) > ttl { 145 return true, nil 146 } 147 148 var lat metadata.LastAccessTime 149 if err := op.GetFileMetadata(name, &lat); os.IsNotExist(err) { 150 return false, nil 151 } else if err != nil { 152 return false, fmt.Errorf("get file lat: %s", err) 153 } 154 return m.clk.Now().Sub(lat.Time) > tti, nil 155 }