Repository: genjidb/genji
Pull request #409 (2021-06-05 18:03:27)

Small performance improvements
The codec interface has been modified to allow the reuse of buffers when decoding
documents one by one from a table.

Memory consumption has been reduced drastically (-60%) and iteration speed has been improved by 20%

```
name                          old time/op    new time/op    delta
PreparedSelectWhere/00001-12    8.28µs ± 0%    7.24µs ± 0%  -12.51%  (p=1.000 n=1+1)
PreparedSelectWhere/00010-12    14.3µs ± 0%    12.7µs ± 0%  -11.16%  (p=1.000 n=1+1)
PreparedSelectWhere/00100-12    91.7µs ± 0%    77.3µs ± 0%  -15.65%  (p=1.000 n=1+1)
PreparedSelectWhere/01000-12     862µs ± 0%     728µs ± 0%  -15.55%  (p=1.000 n=1+1)
PreparedSelectWhere/10000-12    9.08ms ± 0%    7.34ms ± 0%  -19.13%  (p=1.000 n=1+1)

name                          old alloc/op   new alloc/op   delta
PreparedSelectWhere/00001-12    1.64kB ± 0%    1.69kB ± 0%   +3.43%  (p=1.000 n=1+1)
PreparedSelectWhere/00010-12    2.93kB ± 0%    1.83kB ± 0%  -37.47%  (p=1.000 n=1+1)
PreparedSelectWhere/00100-12    15.9kB ± 0%     3.3kB ± 0%  -79.41%  (p=1.000 n=1+1)
PreparedSelectWhere/01000-12     146kB ± 0%      18kB ± 0%  -87.85%  (p=1.000 n=1+1)
PreparedSelectWhere/10000-12    1.45MB ± 0%    0.16MB ± 0%  -88.76%  (p=1.000 n=1+1)

name                          old allocs/op  new allocs/op  delta
PreparedSelectWhere/00001-12      32.0 ± 0%      30.0 ± 0%   -6.25%  (p=1.000 n=1+1)
PreparedSelectWhere/00010-12      86.0 ± 0%      48.0 ± 0%  -44.19%  (p=1.000 n=1+1)
PreparedSelectWhere/00100-12       626 ± 0%       228 ± 0%  -63.58%  (p=1.000 n=1+1)
PreparedSelectWhere/01000-12     6.03k ± 0%     2.03k ± 0%  -66.35%  (p=1.000 n=1+1)
PreparedSelectWhere/10000-12     60.0k ± 0%     20.0k ± 0%  -66.64%  (p=1.000 n=1+1)
```

codecov-commenter: # [Codecov](https://codecov.io/gh/genjidb/genji/pull/409?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb) Report
> Merging [#409](https://codecov.io/gh/genjidb/genji/pull/409?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb) (20943a1) into [main](https://codecov.io/gh/genjidb/genji/commit/1640f734a1215bec12a4ca443ba99b018b1d0179?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb) (1640f73) will **increase** coverage by `0.97%`.
> The diff coverage is `79.59%`.

[![Impacted file tree graph](https://codecov.io/gh/genjidb/genji/pull/409/graphs/tree.svg?width=650&height=150&src=pr&token=nwR1GpvtyH&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb)](https://codecov.io/gh/genjidb/genji/pull/409?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb)

```diff
@@            Coverage Diff             @@
##             main     #409      +/-   ##
==========================================
+ Coverage   68.15%   69.12%   +0.97%     
==========================================
  Files          82       83       +1     
  Lines        7760     8079     +319     
==========================================
+ Hits         5289     5585     +296     
- Misses       1809     1820      +11     
- Partials      662      674      +12     
```


| [Impacted Files](https://codecov.io/gh/genjidb/genji/pull/409?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb) | Coverage Δ | |
|---|---|---|
| [document/encoding/custom/format.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-ZG9jdW1lbnQvZW5jb2RpbmcvY3VzdG9tL2Zvcm1hdC5nbw==) | `66.31% <ø> (-0.36%)` | :arrow_down: |
| [document/encoding/custom/codec.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-ZG9jdW1lbnQvZW5jb2RpbmcvY3VzdG9tL2NvZGVjLmdv) | `70.12% <55.55%> (-0.44%)` | :arrow_down: |
| [document/encoding/msgpack/encoding.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-ZG9jdW1lbnQvZW5jb2RpbmcvbXNncGFjay9lbmNvZGluZy5nbw==) | `54.65% <70.00%> (+1.94%)` | :arrow_up: |
| [document/encoding/msgpack/codec.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-ZG9jdW1lbnQvZW5jb2RpbmcvbXNncGFjay9jb2RlYy5nbw==) | `70.99% <100.00%> (ø)` | |
| [internal/database/index.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-aW50ZXJuYWwvZGF0YWJhc2UvaW5kZXguZ28=) | `66.00% <100.00%> (ø)` | |
| [internal/database/table.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-aW50ZXJuYWwvZGF0YWJhc2UvdGFibGUuZ28=) | `56.30% <100.00%> (+2.06%)` | :arrow_up: |
| [internal/stream/concat.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-aW50ZXJuYWwvc3RyZWFtL2NvbmNhdC5nbw==) | `50.00% <0.00%> (ø)` | |
| [internal/query/statement/select.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-aW50ZXJuYWwvcXVlcnkvc3RhdGVtZW50L3NlbGVjdC5nbw==) | `93.10% <0.00%> (+3.49%)` | :arrow_up: |
| [internal/sql/scanner/token.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-aW50ZXJuYWwvc3FsL3NjYW5uZXIvdG9rZW4uZ28=) | `15.00% <0.00%> (+4.28%)` | :arrow_up: |
| [internal/sql/parser/select.go](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb#diff-aW50ZXJuYWwvc3FsL3BhcnNlci9zZWxlY3QuZ28=) | `64.48% <0.00%> (+5.33%)` | :arrow_up: |
| ... and [2 more](https://codecov.io/gh/genjidb/genji/pull/409/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/genjidb/genji/pull/409?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/genjidb/genji/pull/409?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb). Last update [1640f73...20943a1](https://codecov.io/gh/genjidb/genji/pull/409?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=genjidb).


Patch:
diff --git a/document/encoding/codec.go b/document/encoding/codec.go
index 2a507e0e..a81b9290 100644
--- a/document/encoding/codec.go
+++ b/document/encoding/codec.go
@@ -16,7 +16,7 @@ type Codec interface {
 	// NewDocument returns a document without decoding its given binary representation.
 	// The returned document should ideally support random-access, i.e. decoding one path
 	// without decoding the entire document. If not, the document must be lazily decoded.
-	NewDocument([]byte) document.Document
+	NewDecoder([]byte) Decoder
 }
 
 // An Encoder encodes one document to the underlying writer.
@@ -25,3 +25,12 @@ type Encoder interface {
 	// Close the encoder to release any resource.
 	Close()
 }
+
+// A Decoder represents an encoded document that can
+// be used as if it was decoded.
+// Decoders can be reused to read different documents.
+type Decoder interface {
+	document.Document
+
+	Reset([]byte)
+}
diff --git a/document/encoding/custom/codec.go b/document/encoding/custom/codec.go
index ab9f9d8c..8c0bb325 100644
--- a/document/encoding/custom/codec.go
+++ b/document/encoding/custom/codec.go
@@ -25,8 +25,8 @@ func (c Codec) NewEncoder(w io.Writer) encoding.Encoder {
 }
 
 // NewDocument implements the encoding.Codec interface.
-func (c Codec) NewDocument(data []byte) document.Document {
-	return EncodedDocument(data)
+func (c Codec) NewDecoder(data []byte) encoding.Decoder {
+	return &EncodedDocument{data}
 }
 
 // Encoder encodes Genji documents and values
@@ -54,14 +54,12 @@ func (e *Encoder) EncodeDocument(d document.Document) error {
 }
 
 // Close does nothing.
-func (e *Encoder) Close() {
-	return
-}
+func (e *Encoder) Close() {}
 
 // EncodeDocument takes a document and encodes it using the encoding.Format type.
 func EncodeDocument(d document.Document) ([]byte, error) {
-	if ec, ok := d.(EncodedDocument); ok {
-		return ec, nil
+	if ec, ok := d.(*EncodedDocument); ok {
+		return ec.data, nil
 	}
 
 	var format Format
@@ -112,7 +110,7 @@ func EncodeDocument(d document.Document) ([]byte, error) {
 // DecodeDocument takes a byte slice and returns a lazily decoded document.
 // If buf is malformed, an error will be returned when calling one of the document method.
 func DecodeDocument(buf []byte) document.Document {
-	return EncodedDocument(buf)
+	return &EncodedDocument{buf}
 }
 
 // EncodeValue encodes any value to a binary representation.
@@ -148,18 +146,22 @@ func encodeInt64(x int64) []byte {
 // An EncodedDocument implements the document.Document interface on top of an encoded representation of a
 // document.
 // It is useful to avoid decoding the entire document when only a few fields are needed.
-type EncodedDocument []byte
+type EncodedDocument struct {
+	data []byte
+}
 
 // GetByField decodes the selected field.
-func (e EncodedDocument) GetByField(field string) (document.Value, error) {
-	return decodeValueFromDocument(e, field)
+func (e *EncodedDocument) GetByField(field string) (document.Value, error) {
+	return decodeValueFromDocument(e.data, field)
 }
 
+func (e *EncodedDocument) Reset(data []byte) { e.data = data }
+
 // Iterate decodes each fields one by one and passes them to fn until the end of the document
 // or until fn returns an error.
-func (e EncodedDocument) Iterate(fn func(field string, value document.Value) error) error {
+func (e *EncodedDocument) Iterate(fn func(field string, value document.Value) error) error {
 	var format Format
-	err := format.Decode(e)
+	err := format.Decode(e.data)
 	if err != nil {
 		return err
 	}
@@ -180,7 +182,7 @@ func (e EncodedDocument) Iterate(fn func(field string, value document.Value) err
 }
 
 // MarshalJSON implements the json.Marshaler interface.
-func (e EncodedDocument) MarshalJSON() ([]byte, error) {
+func (e *EncodedDocument) MarshalJSON() ([]byte, error) {
 	return document.MarshalJSON(e)
 }
 
@@ -320,7 +322,7 @@ func DecodeArray(buf []byte) document.Array {
 func DecodeValue(t document.ValueType, data []byte) (document.Value, error) {
 	switch t {
 	case document.DocumentValue:
-		return document.NewDocumentValue(EncodedDocument(data)), nil
+		return document.NewDocumentValue(&EncodedDocument{data}), nil
 	case document.ArrayValue:
 		return document.NewArrayValue(EncodedArray(data)), nil
 	case document.BlobValue:
diff --git a/document/encoding/custom/format.go b/document/encoding/custom/format.go
index 419c6dde..41283efd 100644
--- a/document/encoding/custom/format.go
+++ b/document/encoding/custom/format.go
@@ -168,7 +168,6 @@ func (f *FieldHeader) Decode(data []byte) (int, error) {
 	if n <= 0 {
 		return 0, errors.New("cannot decode data")
 	}
-	data = data[n:]
 	read += n
 
 	return read, nil
diff --git a/document/encoding/msgpack/codec.go b/document/encoding/msgpack/codec.go
index 3a011e40..7a85c8b9 100644
--- a/document/encoding/msgpack/codec.go
+++ b/document/encoding/msgpack/codec.go
@@ -24,8 +24,8 @@ func (c Codec) NewEncoder(w io.Writer) encoding.Encoder {
 }
 
 // NewDocument implements the encoding.Codec interface.
-func (c Codec) NewDocument(data []byte) document.Document {
-	return EncodedDocument(data)
+func (c Codec) NewDecoder(data []byte) encoding.Decoder {
+	return NewEncodedDocument(data)
 }
 
 // Encoder encodes Genji documents and values
@@ -258,7 +258,7 @@ func (d *Decoder) DecodeDocument() (document.Document, error) {
 		return nil, err
 	}
 
-	return EncodedDocument(r), nil
+	return NewEncodedDocument(r), nil
 }
 
 // DecodeArray decodes one array from the reader.
diff --git a/document/encoding/msgpack/encoding.go b/document/encoding/msgpack/encoding.go
index 80385014..6fc1bcc7 100644
--- a/document/encoding/msgpack/encoding.go
+++ b/document/encoding/msgpack/encoding.go
@@ -2,6 +2,7 @@ package msgpack
 
 import (
 	"bytes"
+	"io"
 
 	"github.com/genjidb/genji/document"
 	"github.com/genjidb/genji/internal/stringutil"
@@ -14,7 +15,19 @@ import (
 // document.
 // It is useful for avoiding decoding the entire document when
 // only a few fields are needed.
-type EncodedDocument []byte
+type EncodedDocument struct {
+	encoded []byte
+	buf     []byte
+
+	reader bytes.Reader
+}
+
+func NewEncodedDocument(data []byte) *EncodedDocument {
+	var e EncodedDocument
+
+	e.Reset(data)
+	return &e
+}
 
 // bytesLen determines the size of the next string in the decoder
 // based on c.
@@ -32,11 +45,26 @@ func bytesLen(c byte, dec *msgpack.Decoder) (int, error) {
 	return 0, stringutil.Errorf("msgpack: invalid code=%x decoding bytes length", c)
 }
 
+func (e *EncodedDocument) Reset(data []byte) {
+	e.encoded = data
+
+	e.reader.Reset(data)
+}
+
 // GetByField decodes the selected field from the buffer.
-func (e EncodedDocument) GetByField(field string) (v document.Value, err error) {
-	dec := NewDecoder(bytes.NewReader(e))
+func (e *EncodedDocument) GetByField(field string) (v document.Value, err error) {
+	_, err = e.reader.Seek(0, io.SeekStart)
+	if err != nil {
+		return
+	}
+
+	dec := NewDecoder(&e.reader)
 	defer dec.Close()
 
+	if len(e.buf) == 0 {
+		e.buf = make([]byte, 32)
+	}
+
 	l, err := dec.dec.DecodeMapLen()
 	if err != nil {
 		return
@@ -44,8 +72,6 @@ func (e EncodedDocument) GetByField(field string) (v document.Value, err error)
 
 	bf := []byte(field)
 
-	buf := make([]byte, 32)
-
 	var c byte
 	var n int
 	for i := 0; i < l; i++ {
@@ -68,7 +94,7 @@ func (e EncodedDocument) GetByField(field string) (v document.Value, err error)
 		}
 
 		// Move the cursor by one to skip the type code
-		err = dec.dec.ReadFull(buf[:1])
+		err = dec.dec.ReadFull(e.buf[:1])
 		if err != nil {
 			return
 		}
@@ -80,19 +106,19 @@ func (e EncodedDocument) GetByField(field string) (v document.Value, err error)
 		}
 
 		// ensure the buffer is big enough to hold the string
-		if len(buf) < n {
-			buf = make([]byte, n)
+		if len(e.buf) < n {
+			e.buf = make([]byte, n)
 		}
 
 		// copy the field name into the buffer
-		err = dec.dec.ReadFull(buf[:n])
+		err = dec.dec.ReadFull(e.buf[:n])
 		if err != nil {
 			return
 		}
 
 		// if the field name is the one we are
 		// looking for, decode the next value
-		if bytes.Equal(buf[:n], bf) {
+		if bytes.Equal(e.buf[:n], bf) {
 			return dec.DecodeValue()
 		}
 
@@ -109,8 +135,13 @@ func (e EncodedDocument) GetByField(field string) (v document.Value, err error)
 
 // Iterate decodes each fields one by one and passes them to fn
 // until the end of the document or until fn returns an error.
-func (e EncodedDocument) Iterate(fn func(field string, value document.Value) error) error {
-	dec := NewDecoder(bytes.NewReader(e))
+func (e *EncodedDocument) Iterate(fn func(field string, value document.Value) error) error {
+	_, err := e.reader.Seek(0, io.SeekStart)
+	if err != nil {
+		return err
+	}
+
+	dec := NewDecoder(&e.reader)
 	defer dec.Close()
 
 	l, err := dec.dec.DecodeMapLen()
diff --git a/internal/database/index.go b/internal/database/index.go
index 1f264ceb..594a5672 100644
--- a/internal/database/index.go
+++ b/internal/database/index.go
@@ -244,7 +244,7 @@ func (idx *Index) Delete(vs []document.Value, k []byte) error {
 	var toDelete []byte
 	var buf []byte
 	err = idx.iterate(st, vs, false, func(item engine.Item) error {
-		buf, err = item.ValueCopy(buf[:0])
+		buf, err = item.ValueCopy(buf)
 		if err != nil {
 			return err
 		}
@@ -375,7 +375,7 @@ func (idx *Index) iterateOnStore(pivot Pivot, reverse bool, fn func(val, key []b
 			k = k[:len(k)-int(n)-1]
 		}
 
-		buf, err = item.ValueCopy(buf[:0])
+		buf, err = item.ValueCopy(buf)
 		if err != nil {
 			return err
 		}
diff --git a/internal/database/table.go b/internal/database/table.go
index 66cea346..5b3c16be 100644
--- a/internal/database/table.go
+++ b/internal/database/table.go
@@ -297,32 +297,48 @@ func (e documentWithKey) Key() (document.Value, error) {
 // from store on documents that don't need to be
 // decoded.
 type lazilyDecodedDocument struct {
-	item  engine.Item
-	buf   []byte
-	codec encoding.Codec
-	pk    *FieldConstraint
+	item    engine.Item
+	buf     []byte
+	codec   encoding.Codec
+	decoder encoding.Decoder
+	pk      *FieldConstraint
+	dirty   bool
 }
 
 func (d *lazilyDecodedDocument) GetByField(field string) (v document.Value, err error) {
-	if len(d.buf) == 0 {
+	if d.dirty {
+		d.dirty = false
 		err = d.copyFromItem()
 		if err != nil {
 			return
 		}
+
+		if d.decoder == nil {
+			d.decoder = d.codec.NewDecoder(d.buf)
+		} else {
+			d.decoder.Reset(d.buf)
+		}
 	}
 
-	return d.codec.NewDocument(d.buf).GetByField(field)
+	return d.decoder.GetByField(field)
 }
 
 func (d *lazilyDecodedDocument) Iterate(fn func(field string, value document.Value) error) error {
-	if len(d.buf) == 0 {
+	if d.dirty {
+		d.dirty = false
 		err := d.copyFromItem()
 		if err != nil {
 			return err
 		}
+
+		if d.decoder == nil {
+			d.decoder = d.codec.NewDecoder(d.buf)
+		} else {
+			d.decoder.Reset(d.buf)
+		}
 	}
 
-	return d.codec.NewDocument(d.buf).Iterate(fn)
+	return d.decoder.Iterate(fn)
 }
 
 func (d *lazilyDecodedDocument) RawKey() []byte {
@@ -340,7 +356,7 @@ func (d *lazilyDecodedDocument) Key() (document.Value, error) {
 }
 
 func (d *lazilyDecodedDocument) Reset() {
-	d.buf = d.buf[:0]
+	d.dirty = true
 	d.item = nil
 }
 
@@ -485,7 +501,7 @@ func (t *Table) GetDocument(key []byte) (document.Document, error) {
 	}
 
 	var d documentWithKey
-	d.Document = t.Tx.DB.Codec.NewDocument(v)
+	d.Document = t.Tx.DB.Codec.NewDecoder(v)
 	d.key = key
 	d.pk = t.Info.GetPrimaryKey()
 	return &d, err


Test patch:
diff --git a/document/encoding/encodingtest/benchmark.go b/document/encoding/encodingtest/benchmark.go
index 53daab90..96388cff 100644
--- a/document/encoding/encodingtest/benchmark.go
+++ b/document/encoding/encodingtest/benchmark.go
@@ -64,7 +64,7 @@ func benchmarkDocumentGetByField(b *testing.B, codecBuilder func() encoding.Code
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		codec.NewDocument(buf.Bytes()).GetByField("name-99")
+		codec.NewDecoder(buf.Bytes()).GetByField("name-99")
 	}
 }
 
@@ -80,7 +80,7 @@ func benchmarkDocumentIterate(b *testing.B, codecBuilder func() encoding.Codec)
 	err := codec.NewEncoder(&buf).EncodeDocument(&fb)
 	require.NoError(b, err)
 
-	doc := codec.NewDocument(buf.Bytes())
+	doc := codec.NewDecoder(buf.Bytes())
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
@@ -102,7 +102,7 @@ func benchmarkDecodeDocument(b *testing.B, codecBuilder func() encoding.Codec) {
 	err := codec.NewEncoder(&buf).EncodeDocument(&fb)
 	require.NoError(b, err)
 
-	doc := codec.NewDocument(buf.Bytes())
+	doc := codec.NewDecoder(buf.Bytes())
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
diff --git a/document/encoding/encodingtest/testing.go b/document/encoding/encodingtest/testing.go
index 37423203..27b474c9 100644
--- a/document/encoding/encodingtest/testing.go
+++ b/document/encoding/encodingtest/testing.go
@@ -87,10 +87,10 @@ func testEncodeDecode(t *testing.T, codecBuilder func() encoding.Codec) {
 			codec := codecBuilder()
 			err := codec.NewEncoder(&buf).EncodeDocument(test.d)
 			require.NoError(t, err)
-			ok, err := document.NewDocumentValue(test.d).IsEqual(document.NewDocumentValue(codec.NewDocument(buf.Bytes())))
+			ok, err := document.NewDocumentValue(test.d).IsEqual(document.NewDocumentValue(codec.NewDecoder(buf.Bytes())))
 			require.NoError(t, err)
 			require.True(t, ok)
-			data, err := document.MarshalJSON(codec.NewDocument(buf.Bytes()))
+			data, err := document.MarshalJSON(codec.NewDecoder(buf.Bytes()))
 			require.NoError(t, err)
 			require.JSONEq(t, test.expected, string(data))
 		})
@@ -110,7 +110,7 @@ func testDocumentGetByField(t *testing.T, codecBuilder func() encoding.Codec) {
 	err := codec.NewEncoder(&buf).EncodeDocument(fb)
 	require.NoError(t, err)
 
-	d := codec.NewDocument(buf.Bytes())
+	d := codec.NewDecoder(buf.Bytes())
 
 	v, err := d.GetByField("a")
 	require.NoError(t, err)
@@ -142,7 +142,7 @@ func testArrayGetByIndex(t *testing.T, codecBuilder func() encoding.Codec) {
 	err := codec.NewEncoder(&buf).EncodeDocument(document.NewFieldBuffer().Add("a", document.NewArrayValue(arr)))
 	require.NoError(t, err)
 
-	d := codec.NewDocument(buf.Bytes())
+	d := codec.NewDecoder(buf.Bytes())
 	v, err := d.GetByField("a")
 	require.NoError(t, err)
 
@@ -187,7 +187,7 @@ func testDecodeDocument(t *testing.T, codecBuilder func() encoding.Codec) {
 	err = enc.EncodeDocument(doc)
 	require.NoError(t, err)
 
-	ec := codec.NewDocument(buf.Bytes())
+	ec := codec.NewDecoder(buf.Bytes())
 	v, err := ec.GetByField("age")
 	require.NoError(t, err)
 	require.Equal(t, document.NewIntegerValue(10), v)
diff --git a/document/encoding/msgpack/encoding_test.go b/document/encoding/msgpack/encoding_test.go
index a63f6704..dfeddd00 100644
--- a/document/encoding/msgpack/encoding_test.go
+++ b/document/encoding/msgpack/encoding_test.go
@@ -35,7 +35,8 @@ func TestCompactedIntDecoding(t *testing.T) {
 	err := codec.NewEncoder(&buf).EncodeDocument(d)
 	require.NoError(t, err)
 
-	data, err := document.MarshalJSON(codec.NewDocument(buf.Bytes()))
+	doc := codec.NewDecoder(buf.Bytes())
+	data, err := document.MarshalJSON(doc)
 	require.NoError(t, err)
 	require.JSONEq(t, expected, string(data))
 }
diff --git a/document/scan_test.go b/document/scan_test.go
index 184c1af5..274812d0 100644
--- a/document/scan_test.go
+++ b/document/scan_test.go
@@ -75,7 +75,7 @@ func TestScan(t *testing.T) {
 		)).
 		Add("o", document.NewNullValue()).
 		Add("p", document.NewTextValue(now.Format(time.RFC3339Nano))).
-		Add("r", document.NewDocumentValue(codec.NewDocument(buf.Bytes()))).
+		Add("r", document.NewDocumentValue(codec.NewDecoder(buf.Bytes()))).
 		Add("s", document.NewArrayValue(document.NewValueBuffer(document.NewBoolValue(true), document.NewBoolValue(false)))).
 		Add("u", document.NewArrayValue(document.NewValueBuffer(
 			document.NewDocumentValue(