github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/store/localstore/compactor.go (about) 1 // Copyright 2015 PingCAP, 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package localstore 15 16 import ( 17 "sync" 18 "time" 19 20 "github.com/insionng/yougam/libraries/juju/errors" 21 "github.com/insionng/yougam/libraries/ngaut/log" 22 "github.com/insionng/yougam/libraries/pingcap/tidb/kv" 23 "github.com/insionng/yougam/libraries/pingcap/tidb/store/localstore/engine" 24 "github.com/insionng/yougam/libraries/pingcap/tidb/terror" 25 "github.com/insionng/yougam/libraries/pingcap/tidb/util/bytes" 26 ) 27 28 const ( 29 deleteWorkerCnt = 3 30 ) 31 32 // compactPolicy defines gc policy of MVCC storage. 33 type compactPolicy struct { 34 // SafePoint specifies 35 SafePoint int 36 // TriggerInterval specifies how often should the compactor 37 // scans outdated data. 38 TriggerInterval time.Duration 39 // BatchDeleteCnt specifies the batch size for 40 // deleting outdated data transaction. 41 BatchDeleteCnt int 42 } 43 44 var localCompactDefaultPolicy = compactPolicy{ 45 SafePoint: 20 * 1000, // in ms 46 TriggerInterval: 10 * time.Second, 47 BatchDeleteCnt: 100, 48 } 49 50 type localstoreCompactor struct { 51 mu sync.Mutex 52 recentKeys map[string]struct{} 53 stopCh chan struct{} 54 delCh chan kv.EncodedKey 55 workerWaitGroup *sync.WaitGroup 56 ticker *time.Ticker 57 db engine.DB 58 policy compactPolicy 59 } 60 61 func (gc *localstoreCompactor) OnSet(k kv.Key) { 62 gc.mu.Lock() 63 defer gc.mu.Unlock() 64 gc.recentKeys[string(k)] = struct{}{} 65 } 66 67 func (gc *localstoreCompactor) OnDelete(k kv.Key) { 68 gc.mu.Lock() 69 defer gc.mu.Unlock() 70 gc.recentKeys[string(k)] = struct{}{} 71 } 72 73 func (gc *localstoreCompactor) getAllVersions(key kv.Key) ([]kv.EncodedKey, error) { 74 var keys []kv.EncodedKey 75 k := key 76 for ver := kv.MaxVersion; ver.Ver > 0; ver.Ver-- { 77 mvccK, _, err := gc.db.Seek(MvccEncodeVersionKey(key, ver)) 78 if terror.ErrorEqual(err, engine.ErrNotFound) { 79 break 80 } 81 if err != nil { 82 return nil, errors.Trace(err) 83 } 84 k, ver, err = MvccDecode(mvccK) 85 if k.Cmp(key) != 0 { 86 break 87 } 88 if err != nil { 89 return nil, errors.Trace(err) 90 } 91 keys = append(keys, bytes.CloneBytes(mvccK)) 92 } 93 return keys, nil 94 } 95 96 func (gc *localstoreCompactor) deleteWorker() { 97 defer gc.workerWaitGroup.Done() 98 cnt := 0 99 batch := gc.db.NewBatch() 100 for { 101 select { 102 case <-gc.stopCh: 103 return 104 case key := <-gc.delCh: 105 cnt++ 106 batch.Delete(key) 107 // Batch delete. 108 if cnt == gc.policy.BatchDeleteCnt { 109 log.Debugf("[kv] GC delete commit %d keys", batch.Len()) 110 err := gc.db.Commit(batch) 111 if err != nil { 112 log.Error(err) 113 } 114 batch = gc.db.NewBatch() 115 cnt = 0 116 } 117 } 118 } 119 } 120 121 func (gc *localstoreCompactor) checkExpiredKeysWorker() { 122 defer gc.workerWaitGroup.Done() 123 for { 124 select { 125 case <-gc.stopCh: 126 log.Debug("[kv] GC stopped") 127 return 128 case <-gc.ticker.C: 129 gc.mu.Lock() 130 m := gc.recentKeys 131 if len(m) == 0 { 132 gc.mu.Unlock() 133 continue 134 } 135 gc.recentKeys = make(map[string]struct{}) 136 gc.mu.Unlock() 137 for k := range m { 138 err := gc.Compact([]byte(k)) 139 if err != nil { 140 log.Error(err) 141 } 142 } 143 } 144 } 145 } 146 147 func (gc *localstoreCompactor) filterExpiredKeys(keys []kv.EncodedKey) []kv.EncodedKey { 148 var ret []kv.EncodedKey 149 first := true 150 currentTS := time.Now().UnixNano() / int64(time.Millisecond) 151 // keys are always in descending order. 152 for _, k := range keys { 153 _, ver, err := MvccDecode(k) 154 if err != nil { 155 // Should not happen. 156 panic(err) 157 } 158 ts := localVersionToTimestamp(ver) 159 // Check timeout keys. 160 if currentTS-int64(ts) >= int64(gc.policy.SafePoint) { 161 // Skip first version. 162 if first { 163 first = false 164 continue 165 } 166 ret = append(ret, k) 167 } 168 } 169 return ret 170 } 171 172 func (gc *localstoreCompactor) Compact(k kv.Key) error { 173 keys, err := gc.getAllVersions(k) 174 if err != nil { 175 return errors.Trace(err) 176 } 177 filteredKeys := gc.filterExpiredKeys(keys) 178 if len(filteredKeys) > 0 { 179 log.Debugf("[kv] GC send %d keys to delete worker", len(filteredKeys)) 180 } 181 for _, key := range filteredKeys { 182 gc.delCh <- key 183 } 184 return nil 185 } 186 187 func (gc *localstoreCompactor) Start() { 188 // Start workers. 189 gc.workerWaitGroup.Add(deleteWorkerCnt) 190 for i := 0; i < deleteWorkerCnt; i++ { 191 go gc.deleteWorker() 192 } 193 194 gc.workerWaitGroup.Add(1) 195 go gc.checkExpiredKeysWorker() 196 } 197 198 func (gc *localstoreCompactor) Stop() { 199 gc.ticker.Stop() 200 close(gc.stopCh) 201 // Wait for all workers to finish. 202 gc.workerWaitGroup.Wait() 203 } 204 205 func newLocalCompactor(policy compactPolicy, db engine.DB) *localstoreCompactor { 206 return &localstoreCompactor{ 207 recentKeys: make(map[string]struct{}), 208 stopCh: make(chan struct{}), 209 delCh: make(chan kv.EncodedKey, 100), 210 ticker: time.NewTicker(policy.TriggerInterval), 211 policy: policy, 212 db: db, 213 workerWaitGroup: &sync.WaitGroup{}, 214 } 215 }