go.etcd.io/etcd@v3.3.27+incompatible/mvcc/backend/batch_tx.go (about) 1 // Copyright 2015 The etcd Authors 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 15 package backend 16 17 import ( 18 "bytes" 19 "math" 20 "sync" 21 "sync/atomic" 22 "time" 23 24 bolt "github.com/coreos/bbolt" 25 ) 26 27 type BatchTx interface { 28 ReadTx 29 UnsafeCreateBucket(name []byte) 30 UnsafePut(bucketName []byte, key []byte, value []byte) 31 UnsafeSeqPut(bucketName []byte, key []byte, value []byte) 32 UnsafeDelete(bucketName []byte, key []byte) 33 // Commit commits a previous tx and begins a new writable one. 34 Commit() 35 // CommitAndStop commits the previous tx and does not create a new one. 36 CommitAndStop() 37 } 38 39 type batchTx struct { 40 sync.Mutex 41 tx *bolt.Tx 42 backend *backend 43 44 pending int 45 } 46 47 func (t *batchTx) UnsafeCreateBucket(name []byte) { 48 _, err := t.tx.CreateBucket(name) 49 if err != nil && err != bolt.ErrBucketExists { 50 plog.Fatalf("cannot create bucket %s (%v)", name, err) 51 } 52 t.pending++ 53 } 54 55 // UnsafePut must be called holding the lock on the tx. 56 func (t *batchTx) UnsafePut(bucketName []byte, key []byte, value []byte) { 57 t.unsafePut(bucketName, key, value, false) 58 } 59 60 // UnsafeSeqPut must be called holding the lock on the tx. 61 func (t *batchTx) UnsafeSeqPut(bucketName []byte, key []byte, value []byte) { 62 t.unsafePut(bucketName, key, value, true) 63 } 64 65 func (t *batchTx) unsafePut(bucketName []byte, key []byte, value []byte, seq bool) { 66 bucket := t.tx.Bucket(bucketName) 67 if bucket == nil { 68 plog.Fatalf("bucket %s does not exist", bucketName) 69 } 70 if seq { 71 // it is useful to increase fill percent when the workloads are mostly append-only. 72 // this can delay the page split and reduce space usage. 73 bucket.FillPercent = 0.9 74 } 75 if err := bucket.Put(key, value); err != nil { 76 plog.Fatalf("cannot put key into bucket (%v)", err) 77 } 78 t.pending++ 79 } 80 81 // UnsafeRange must be called holding the lock on the tx. 82 func (t *batchTx) UnsafeRange(bucketName, key, endKey []byte, limit int64) ([][]byte, [][]byte) { 83 bucket := t.tx.Bucket(bucketName) 84 if bucket == nil { 85 plog.Fatalf("bucket %s does not exist", bucketName) 86 } 87 return unsafeRange(bucket.Cursor(), key, endKey, limit) 88 } 89 90 func unsafeRange(c *bolt.Cursor, key, endKey []byte, limit int64) (keys [][]byte, vs [][]byte) { 91 if limit <= 0 { 92 limit = math.MaxInt64 93 } 94 var isMatch func(b []byte) bool 95 if len(endKey) > 0 { 96 isMatch = func(b []byte) bool { return bytes.Compare(b, endKey) < 0 } 97 } else { 98 isMatch = func(b []byte) bool { return bytes.Equal(b, key) } 99 limit = 1 100 } 101 for ck, cv := c.Seek(key); ck != nil && isMatch(ck); ck, cv = c.Next() { 102 vs = append(vs, cv) 103 keys = append(keys, ck) 104 if limit == int64(len(keys)) { 105 break 106 } 107 } 108 return keys, vs 109 } 110 111 // UnsafeDelete must be called holding the lock on the tx. 112 func (t *batchTx) UnsafeDelete(bucketName []byte, key []byte) { 113 bucket := t.tx.Bucket(bucketName) 114 if bucket == nil { 115 plog.Fatalf("bucket %s does not exist", bucketName) 116 } 117 err := bucket.Delete(key) 118 if err != nil { 119 plog.Fatalf("cannot delete key from bucket (%v)", err) 120 } 121 t.pending++ 122 } 123 124 // UnsafeForEach must be called holding the lock on the tx. 125 func (t *batchTx) UnsafeForEach(bucketName []byte, visitor func(k, v []byte) error) error { 126 return unsafeForEach(t.tx, bucketName, visitor) 127 } 128 129 func unsafeForEach(tx *bolt.Tx, bucket []byte, visitor func(k, v []byte) error) error { 130 if b := tx.Bucket(bucket); b != nil { 131 return b.ForEach(visitor) 132 } 133 return nil 134 } 135 136 // Commit commits a previous tx and begins a new writable one. 137 func (t *batchTx) Commit() { 138 t.Lock() 139 t.commit(false) 140 t.Unlock() 141 } 142 143 // CommitAndStop commits the previous tx and does not create a new one. 144 func (t *batchTx) CommitAndStop() { 145 t.Lock() 146 t.commit(true) 147 t.Unlock() 148 } 149 150 func (t *batchTx) Unlock() { 151 if t.pending >= t.backend.batchLimit { 152 t.commit(false) 153 } 154 t.Mutex.Unlock() 155 } 156 157 func (t *batchTx) commit(stop bool) { 158 // commit the last tx 159 if t.tx != nil { 160 if t.pending == 0 && !stop { 161 return 162 } 163 164 start := time.Now() 165 166 // gofail: var beforeCommit struct{} 167 err := t.tx.Commit() 168 // gofail: var afterCommit struct{} 169 170 commitDurations.Observe(time.Since(start).Seconds()) 171 atomic.AddInt64(&t.backend.commits, 1) 172 173 t.pending = 0 174 if err != nil { 175 plog.Fatalf("cannot commit tx (%s)", err) 176 } 177 } 178 if !stop { 179 t.tx = t.backend.begin(true) 180 } 181 } 182 183 type batchTxBuffered struct { 184 batchTx 185 buf txWriteBuffer 186 } 187 188 func newBatchTxBuffered(backend *backend) *batchTxBuffered { 189 tx := &batchTxBuffered{ 190 batchTx: batchTx{backend: backend}, 191 buf: txWriteBuffer{ 192 txBuffer: txBuffer{make(map[string]*bucketBuffer)}, 193 seq: true, 194 }, 195 } 196 tx.Commit() 197 return tx 198 } 199 200 func (t *batchTxBuffered) Unlock() { 201 if t.pending != 0 { 202 t.backend.readTx.mu.Lock() 203 t.buf.writeback(&t.backend.readTx.buf) 204 t.backend.readTx.mu.Unlock() 205 if t.pending >= t.backend.batchLimit { 206 t.commit(false) 207 } 208 } 209 t.batchTx.Unlock() 210 } 211 212 func (t *batchTxBuffered) Commit() { 213 t.Lock() 214 t.commit(false) 215 t.Unlock() 216 } 217 218 func (t *batchTxBuffered) CommitAndStop() { 219 t.Lock() 220 t.commit(true) 221 t.Unlock() 222 } 223 224 func (t *batchTxBuffered) commit(stop bool) { 225 // all read txs must be closed to acquire boltdb commit rwlock 226 t.backend.readTx.mu.Lock() 227 t.unsafeCommit(stop) 228 t.backend.readTx.mu.Unlock() 229 } 230 231 func (t *batchTxBuffered) unsafeCommit(stop bool) { 232 if t.backend.readTx.tx != nil { 233 if err := t.backend.readTx.tx.Rollback(); err != nil { 234 plog.Fatalf("cannot rollback tx (%s)", err) 235 } 236 t.backend.readTx.reset() 237 } 238 239 t.batchTx.commit(stop) 240 241 if !stop { 242 t.backend.readTx.tx = t.backend.begin(false) 243 } 244 } 245 246 func (t *batchTxBuffered) UnsafePut(bucketName []byte, key []byte, value []byte) { 247 t.batchTx.UnsafePut(bucketName, key, value) 248 t.buf.put(bucketName, key, value) 249 } 250 251 func (t *batchTxBuffered) UnsafeSeqPut(bucketName []byte, key []byte, value []byte) { 252 t.batchTx.UnsafeSeqPut(bucketName, key, value) 253 t.buf.putSeq(bucketName, key, value) 254 }