Repository: dgraph-io/badger
Pull request #362 (2017-12-17 13:34:12)

Add Sequence.Release()
> Release the leased sequence to avoid wasted integers. This should be done right before closing the associated DB. However it is valid to use the sequence after it was released, causing a new lease with full bandwidth.

Changes:

- Add new method `Release() error` on `Sequence`
- Add defer example to readme
- Test release and use after release

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/dgraph-io/badger/362)
<!-- Reviewable:end -->


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/dgraph-io/badger?pullRequest=362) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/dgraph-io/badger?pullRequest=362) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/dgraph-io/badger?pullRequest=362) it.</sub>

Patch:
diff --git a/README.md b/README.md
index c462968..af84d95 100644
--- a/README.md
+++ b/README.md
@@ -247,9 +247,11 @@ bandwidth provided to `DB.GetSequence`. The frequency at which disk writes are
 done is determined by this lease bandwidth and the frequency of `Next`
 invocations. Setting a bandwith too low would do more disk writes, setting it
 too high would result in wasted integers if Badger is closed or crashes.
+To avoid wasted integers, call `Release` before closing Badger.
 
 ```go
 seq, err := db.GetSequence(key, 1000)
+defer seq.Release()
 for {
   num, err := seq.Next()
 }
diff --git a/db.go b/db.go
index eb49792..9eddae6 100644
--- a/db.go
+++ b/db.go
@@ -1106,6 +1106,24 @@ func (seq *Sequence) Next() (uint64, error) {
 	return val, nil
 }
 
+// Release the leased sequence to avoid wasted integers. This should be done right
+// before closing the associated DB. However it is valid to use the sequence after
+// it was released, causing a new lease with full bandwidth.
+func (seq *Sequence) Release() error {
+	seq.Lock()
+	defer seq.Unlock()
+	err := seq.db.Update(func(txn *Txn) error {
+		var buf [8]byte
+		binary.BigEndian.PutUint64(buf[:], seq.next)
+		return txn.Set(seq.key, buf[:])
+	})
+	if err != nil {
+		return err
+	}
+	seq.leased = seq.next
+	return nil
+}
+
 func (seq *Sequence) updateLease() error {
 	return seq.db.Update(func(txn *Txn) error {
 		item, err := txn.Get(seq.key)
diff --git a/level_handler.go b/level_handler.go
index 63bc255..d7295c4 100644
--- a/level_handler.go
+++ b/level_handler.go
@@ -239,6 +239,7 @@ func (s *levelHandler) get(key []byte) (y.ValueStruct, error) {
 	tables, decr := s.getTableForKey(key)
 	keyNoTs := y.ParseKey(key)
 
+	var maxVs y.ValueStruct
 	for _, th := range tables {
 		if th.DoesNotHave(keyNoTs) {
 			y.NumLSMBloomHits.Add(s.strLevel, 1)
@@ -254,12 +255,13 @@ func (s *levelHandler) get(key []byte) (y.ValueStruct, error) {
 			continue
 		}
 		if y.SameKey(key, it.Key()) {
-			vs := it.Value()
-			vs.Version = y.ParseTs(it.Key())
-			return vs, decr()
+			if version := y.ParseTs(it.Key()); maxVs.Version < version {
+				maxVs = it.Value()
+				maxVs.Version = version
+			}
 		}
 	}
-	return y.ValueStruct{}, decr()
+	return maxVs, decr()
 }
 
 // appendIterators appends iterators to an array of iterators, for merging.


Test patch:
diff --git a/db_test.go b/db_test.go
index 1ac261e..04827e0 100644
--- a/db_test.go
+++ b/db_test.go
@@ -1197,6 +1197,52 @@ func TestSequence(t *testing.T) {
 	})
 }
 
+func TestSequence_Release(t *testing.T) {
+	runBadgerTest(t, nil, func(t *testing.T, db *DB) {
+		// get sequence, use once and release
+		key := []byte("key")
+		seq, err := db.GetSequence(key, 1000)
+		require.NoError(t, err)
+		num, err := seq.Next()
+		require.NoError(t, err)
+		require.Equal(t, uint64(0), num)
+		require.NoError(t, seq.Release())
+
+		// we used up 0 and 1 should be stored now
+		err = db.View(func(txn *Txn) error {
+			item, err := txn.Get(key)
+			if err != nil {
+				return err
+			}
+			val, err := item.Value()
+			if err != nil {
+				return err
+			}
+			require.Equal(t, num+1, binary.BigEndian.Uint64(val))
+			return nil
+		})
+		require.NoError(t, err)
+
+		// using it again will lease 1+1000
+		num, err = seq.Next()
+		require.NoError(t, err)
+		require.Equal(t, uint64(1), num)
+		err = db.View(func(txn *Txn) error {
+			item, err := txn.Get(key)
+			if err != nil {
+				return err
+			}
+			val, err := item.Value()
+			if err != nil {
+				return err
+			}
+			require.Equal(t, uint64(1001), binary.BigEndian.Uint64(val))
+			return nil
+		})
+		require.NoError(t, err)
+	})
+}
+
 func ExampleOpen() {
 	dir, err := ioutil.TempDir("", "badger")
 	if err != nil {