Repository: prometheus/tsdb
Pull request #542 (2019-03-05 12:25:15)

Add Head.compactable method

Extract whether head has a compactable range  into a method (`Head.compactable` ).




krasi-georgiev: LGTM

ping @codesome 	

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11b8413..51858f3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## master / unreleased
+ - [REMOVED] `chunks.NewReader` is removed as it wasn't used anywhere.
  - [REMOVED] `FromData` is considered unused so was removed.
+ - [FEATURE] Added option WALSegmentSize -1 to disable the WAL.
 
 ## 0.6.1
   - [BUGFIX] Update `last` after appending a non-overlapping chunk in `chunks.MergeOverlappingChunks`. [#539](https://github.com/prometheus/tsdb/pull/539)
@@ -14,7 +16,7 @@
    - Added `MergeOverlappingChunks` function in `chunks/chunks.go` to merge multiple time-overlapping Chunk Metas.
    - Added `MinTime` and `MaxTime` method for `BlockReader`.
  - [FEATURE] New `dump` command to tsdb tool to dump all samples.
- - [FEATURE] New `encoding` package for common binary encoding/decoding helpers. 
+ - [FEATURE] New `encoding` package for common binary encoding/decoding helpers.
     - Added to remove some code duplication.
  - [ENHANCEMENT] When closing the db any running compaction will be cancelled so it doesn't block.
    - `NewLeveledCompactor` takes a context.
@@ -40,7 +42,7 @@
  - [CHANGE] `LastCheckpoint()` used to return just the segment name and now it returns the full relative path.
  - [CHANGE] `NewSegmentsRangeReader()` can now read over miltiple wal ranges by using the new `SegmentRange{}` struct.
  - [CHANGE] `CorruptionErr{}` now also exposes the Segment `Dir` which is added when displaying any errors.
- - [CHANGE] `Head.Init()` is changed to `Head.Init(minValidTime int64)`  
+ - [CHANGE] `Head.Init()` is changed to `Head.Init(minValidTime int64)`
  - [CHANGE] `SymbolTable()` renamed to `SymbolTableSize()` to make the name consistent with the  `Block{ symbolTableSize uint64 }` field.
  - [CHANGE] `wal.Reader{}` now exposes `Segment()` for the current segment being read  and `Offset()` for the current offset.
  - [FEATURE] tsdbutil analyze subcomand to find churn, high cardinality, etc.
diff --git a/Makefile b/Makefile
index 3458e7f..7c35f8d 100644
--- a/Makefile
+++ b/Makefile
@@ -20,11 +20,6 @@ TSDB_BENCHMARK_OUTPUT_DIR ?= "$(TSDB_CLI_DIR)/benchout"
 
 include Makefile.common
 
-.PHONY: deps
-deps:
-	@echo ">> getting dependencies"
-	GO111MODULE=$(GO111MODULE) $(GO) get $(GOOPTS) -t ./...
-
 build:
 	GO111MODULE=$(GO111MODULE) $(GO) build -o $(TSDB_BIN) $(TSDB_CLI_DIR)
 
diff --git a/Makefile.common b/Makefile.common
index 7105cff..108e494 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -116,6 +116,15 @@ common-check_license:
                exit 1; \
        fi
 
+.PHONY: common-deps
+common-deps:
+	@echo ">> getting dependencies"
+ifdef GO111MODULE
+	GO111MODULE=$(GO111MODULE) $(GO) mod download
+else
+	$(GO) get $(GOOPTS) -t ./...
+endif
+
 .PHONY: common-test-short
 common-test-short:
 	@echo ">> running short tests"
@@ -221,7 +230,6 @@ precheck::
 define PRECHECK_COMMAND_template =
 precheck:: $(1)_precheck
 
-
 PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1)))
 .PHONY: $(1)_precheck
 $(1)_precheck:
diff --git a/README.md b/README.md
index 4393d51..c62d616 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
-# TSDB [![Build Status](https://travis-ci.org/prometheus/tsdb.svg?branch=master)](https://travis-ci.org/prometheus/tsdb)
+# TSDB 
 
+[![Build Status](https://travis-ci.org/prometheus/tsdb.svg?branch=master)](https://travis-ci.org/prometheus/tsdb)
 [![GoDoc](https://godoc.org/github.com/prometheus/tsdb?status.svg)](https://godoc.org/github.com/prometheus/tsdb)
 [![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/tsdb)](https://goreportcard.com/report/github.com/prometheus/tsdb)
 
diff --git a/block.go b/block.go
index d9570bd..2d9c81d 100644
--- a/block.go
+++ b/block.go
@@ -28,6 +28,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/prometheus/tsdb/chunkenc"
 	"github.com/prometheus/tsdb/chunks"
+	tsdb_errors "github.com/prometheus/tsdb/errors"
 	"github.com/prometheus/tsdb/index"
 	"github.com/prometheus/tsdb/labels"
 )
@@ -244,7 +245,7 @@ func writeMetaFile(dir string, meta *BlockMeta) error {
 	enc := json.NewEncoder(f)
 	enc.SetIndent("", "\t")
 
-	var merr MultiError
+	var merr tsdb_errors.MultiError
 
 	if merr.Add(enc.Encode(meta)); merr.Err() != nil {
 		merr.Add(f.Close())
@@ -283,7 +284,7 @@ func OpenBlock(logger log.Logger, dir string, pool chunkenc.Pool) (pb *Block, er
 	var closers []io.Closer
 	defer func() {
 		if err != nil {
-			var merr MultiError
+			var merr tsdb_errors.MultiError
 			merr.Add(err)
 			merr.Add(closeAll(closers))
 			err = merr.Err()
@@ -350,7 +351,7 @@ func (pb *Block) Close() error {
 
 	pb.pendingReaders.Wait()
 
-	var merr MultiError
+	var merr tsdb_errors.MultiError
 
 	merr.Add(pb.chunkr.Close())
 	merr.Add(pb.indexr.Close())
diff --git a/checkpoint.go b/checkpoint.go
index 1c62392..d8dee28 100644
--- a/checkpoint.go
+++ b/checkpoint.go
@@ -25,6 +25,7 @@ import (
 	"strings"
 
 	"github.com/pkg/errors"
+	tsdb_errors "github.com/prometheus/tsdb/errors"
 	"github.com/prometheus/tsdb/fileutil"
 	"github.com/prometheus/tsdb/wal"
 )
@@ -67,7 +68,7 @@ func LastCheckpoint(dir string) (string, int, error) {
 
 // DeleteCheckpoints deletes all checkpoints in a directory below a given index.
 func DeleteCheckpoints(dir string, maxIndex int) error {
-	var errs MultiError
+	var errs tsdb_errors.MultiError
 
 	files, err := ioutil.ReadDir(dir)
 	if err != nil {
diff --git a/chunks/chunks.go b/chunks/chunks.go
index dd5a5f8..9e75407 100644
--- a/chunks/chunks.go
+++ b/chunks/chunks.go
@@ -27,12 +27,20 @@ import (
 
 	"github.com/pkg/errors"
 	"github.com/prometheus/tsdb/chunkenc"
+	tsdb_errors "github.com/prometheus/tsdb/errors"
 	"github.com/prometheus/tsdb/fileutil"
 )
 
 const (
 	// MagicChunks is 4 bytes at the head of a series file.
 	MagicChunks = 0x85BD40DD
+	// MagicChunksSize is the size in bytes of MagicChunks.
+	MagicChunksSize = 4
+
+	chunksFormatV1          = 1
+	ChunksFormatVersionSize = 1
+
+	chunkHeaderSize = MagicChunksSize + ChunksFormatVersionSize
 )
 
 // Meta holds information about a chunk of data.
@@ -57,7 +65,7 @@ func (cm *Meta) writeHash(h hash.Hash) error {
 	return nil
 }
 
-// Returns true if the chunk overlaps [mint, maxt].
+// OverlapsClosedInterval Returns true if the chunk overlaps [mint, maxt].
 func (cm *Meta) OverlapsClosedInterval(mint, maxt int64) bool {
 	// The chunk itself is a closed interval [cm.MinTime, cm.MaxTime].
 	return cm.MinTime <= maxt && mint <= cm.MaxTime
@@ -93,8 +101,6 @@ type Writer struct {
 
 const (
 	defaultChunkSegmentSize = 512 * 1024 * 1024
-
-	chunksFormatV1 = 1
 )
 
 // NewWriter returns a new writer against the given directory.
@@ -170,9 +176,8 @@ func (w *Writer) cut() error {
 	}
 
 	// Write header metadata for new file.
-
 	metab := make([]byte, 8)
-	binary.BigEndian.PutUint32(metab[:4], MagicChunks)
+	binary.BigEndian.PutUint32(metab[:MagicChunksSize], MagicChunks)
 	metab[4] = chunksFormatV1
 
 	if _, err := f.Write(metab); err != nil {
@@ -373,27 +378,24 @@ func newReader(bs []ByteSlice, cs []io.Closer, pool chunkenc.Pool) (*Reader, err
 	var totalSize int64
 
 	for i, b := range cr.bs {
-		if b.Len() < 4 {
-			return nil, errors.Wrapf(errInvalidSize, "validate magic in segment %d", i)
+		if b.Len() < chunkHeaderSize {
+			return nil, errors.Wrapf(errInvalidSize, "invalid chunk header in segment %d", i)
 		}
 		// Verify magic number.
-		if m := binary.BigEndian.Uint32(b.Range(0, 4)); m != MagicChunks {
+		if m := binary.BigEndian.Uint32(b.Range(0, MagicChunksSize)); m != MagicChunks {
 			return nil, errors.Errorf("invalid magic number %x", m)
 		}
+
+		// Verify chunk format version.
+		if v := int(b.Range(MagicChunksSize, MagicChunksSize+ChunksFormatVersionSize)[0]); v != chunksFormatV1 {
+			return nil, errors.Errorf("invalid chunk format version %d", v)
+		}
 		totalSize += int64(b.Len())
 	}
 	cr.size = totalSize
 	return &cr, nil
 }
 
-// NewReader returns a new chunk reader against the given byte slices.
-func NewReader(bs []ByteSlice, pool chunkenc.Pool) (*Reader, error) {
-	if pool == nil {
-		pool = chunkenc.NewPool()
-	}
-	return newReader(bs, nil, pool)
-}
-
 // NewDirReader returns a new Reader against sequentially numbered files in the
 // given directory.
 func NewDirReader(dir string, pool chunkenc.Pool) (*Reader, error) {
@@ -417,7 +419,15 @@ func NewDirReader(dir string, pool chunkenc.Pool) (*Reader, error) {
 		cs = append(cs, f)
 		bs = append(bs, realByteSlice(f.Bytes()))
 	}
-	return newReader(bs, cs, pool)
+
+	reader, err := newReader(bs, cs, pool)
+	if err != nil {
+		var merr tsdb_errors.MultiError
+		merr.Add(err)
+		merr.Add(closeAll(cs))
+		return nil, merr
+	}
+	return reader, nil
 }
 
 func (s *Reader) Close() error {
diff --git a/cmd/tsdb/main.go b/cmd/tsdb/main.go
index a0845f2..8c04945 100644
--- a/cmd/tsdb/main.go
+++ b/cmd/tsdb/main.go
@@ -48,7 +48,7 @@ func main() {
 		benchWriteCmd        = benchCmd.Command("write", "run a write performance benchmark")
 		benchWriteOutPath    = benchWriteCmd.Flag("out", "set the output path").Default("benchout").String()
 		benchWriteNumMetrics = benchWriteCmd.Flag("metrics", "number of metrics to read").Default("10000").Int()
-		benchSamplesFile     = benchWriteCmd.Arg("file", "input file with samples data, default is ("+filepath.Join("..", "testdata", "20kseries.json")+")").Default(filepath.Join("..", "testdata", "20kseries.json")).String()
+		benchSamplesFile     = benchWriteCmd.Arg("file", "input file with samples data, default is ("+filepath.Join("..", "..", "testdata", "20kseries.json")+")").Default(filepath.Join("..", "..", "testdata", "20kseries.json")).String()
 		listCmd              = cli.Command("ls", "list db blocks")
 		listCmdHumanReadable = listCmd.Flag("human-readable", "print human readable values").Short('h').Bool()
 		listPath             = listCmd.Arg("db path", "database path (default is "+defaultDBPath+")").Default(defaultDBPath).String()
diff --git a/compact.go b/compact.go
index a1a7aa7..c8d293f 100644
--- a/compact.go
+++ b/compact.go
@@ -31,6 +31,7 @@ import (
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/tsdb/chunkenc"
 	"github.com/prometheus/tsdb/chunks"
+	tsdb_errors "github.com/prometheus/tsdb/errors"
 	"github.com/prometheus/tsdb/fileutil"
 	"github.com/prometheus/tsdb/index"
 	"github.com/prometheus/tsdb/labels"
@@ -309,7 +310,7 @@ func splitByRange(ds []dirMeta, tr int64) [][]dirMeta {
 		}
 		// Skip blocks that don't fall into the range. This can happen via mis-alignment or
 		// by being the multiple of the intended range.
-		if ds[i].meta.MinTime < t0 || ds[i].meta.MaxTime > t0+tr {
+		if m.MaxTime > t0+tr {
 			i++
 			continue
 		}
@@ -317,7 +318,7 @@ func splitByRange(ds []dirMeta, tr int64) [][]dirMeta {
 		// Add all dirs to the current group that are within [t0, t0+tr].
 		for ; i < len(ds); i++ {
 			// Either the block falls into the next range or doesn't fit at all (checked above).
-			if ds[i].meta.MinTime < t0 || ds[i].meta.MaxTime > t0+tr {
+			if ds[i].meta.MaxTime > t0+tr {
 				break
 			}
 			group = append(group, ds[i])
@@ -451,7 +452,7 @@ func (c *LeveledCompactor) Compact(dest string, dirs []string, open []*Block) (u
 		return uid, nil
 	}
 
-	var merr MultiError
+	var merr tsdb_errors.MultiError
 	merr.Add(err)
 	if err != context.Canceled {
 		for _, b := range bs {
@@ -529,7 +530,7 @@ func (c *LeveledCompactor) write(dest string, meta *BlockMeta, blocks ...BlockRe
 	tmp := dir + ".tmp"
 	var closers []io.Closer
 	defer func(t time.Time) {
-		var merr MultiError
+		var merr tsdb_errors.MultiError
 		merr.Add(err)
 		merr.Add(closeAll(closers))
 		err = merr.Err()
@@ -592,7 +593,7 @@ func (c *LeveledCompactor) write(dest string, meta *BlockMeta, blocks ...BlockRe
 	// though these are covered under defer. This is because in Windows,
 	// you cannot delete these unless they are closed and the defer is to
 	// make sure they are closed if the function exits due to an error above.
-	var merr MultiError
+	var merr tsdb_errors.MultiError
 	for _, w := range closers {
 		merr.Add(w.Close())
 	}
@@ -658,7 +659,7 @@ func (c *LeveledCompactor) populateBlock(blocks []BlockReader, meta *BlockMeta,
 		overlapping bool
 	)
 	defer func() {
-		var merr MultiError
+		var merr tsdb_errors.MultiError
 		merr.Add(err)
 		merr.Add(closeAll(closers))
 		err = merr.Err()
diff --git a/db.go b/db.go
index 7b03a20..2ed31ea 100644
--- a/db.go
+++ b/db.go
@@ -15,7 +15,6 @@
 package tsdb
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"io"
@@ -36,6 +35,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/tsdb/chunkenc"
+	tsdb_errors "github.com/prometheus/tsdb/errors"
 	"github.com/prometheus/tsdb/fileutil"
 	"github.com/prometheus/tsdb/labels"
 	"github.com/prometheus/tsdb/wal"
@@ -54,7 +54,10 @@ var DefaultOptions = &Options{
 
 // Options of the DB storage.
 type Options struct {
-	// Segments (wal files) max size
+	// Segments (wal files) max size.
+	// WALSegmentSize = 0, segment size is default size.
+	// WALSegmentSize > 0, segment size is WALSegmentSize.
+	// WALSegmentSize < 0, wal is disabled.
 	WALSegmentSize int
 
 	// Duration of persisted data to keep.
@@ -92,8 +95,8 @@ type Appender interface {
 	// If the reference is 0 it must not be used for caching.
 	Add(l labels.Labels, t int64, v float64) (uint64, error)
 
-	// Add adds a sample pair for the referenced series. It is generally faster
-	// than adding a sample by providing its full label set.
+	// AddFast adds a sample pair for the referenced series. It is generally
+	// faster than adding a sample by providing its full label set.
 	AddFast(ref uint64, t int64, v float64) error
 
 	// Commit submits the collected samples and purges the batch.
@@ -288,14 +291,20 @@ func Open(dir string, l log.Logger, r prometheus.Registerer, opts *Options) (db
 	}
 	db.compactCancel = cancel
 
+	var wlog *wal.WAL
 	segmentSize := wal.DefaultSegmentSize
-	if opts.WALSegmentSize > 0 {
-		segmentSize = opts.WALSegmentSize
-	}
-	wlog, err := wal.NewSize(l, r, filepath.Join(dir, "wal"), segmentSize)
-	if err != nil {
-		return nil, err
+	// Wal is enabled.
+	if opts.WALSegmentSize >= 0 {
+		// Wal is set to a custom size.
+		if opts.WALSegmentSize > 0 {
+			segmentSize = opts.WALSegmentSize
+		}
+		wlog, err = wal.NewSize(l, r, filepath.Join(dir, "wal"), segmentSize)
+		if err != nil {
+			return nil, err
+		}
 	}
+
 	db.head, err = NewHead(r, l, wlog, opts.BlockRanges[0])
 	if err != nil {
 		return nil, err
@@ -382,7 +391,7 @@ func (a dbAppender) Commit() error {
 
 	// We could just run this check every few minutes practically. But for benchmarks
 	// and high frequency use cases this is the safer way.
-	if a.db.head.MaxTime()-a.db.head.MinTime() > a.db.head.chunkRange/2*3 {
+	if a.db.head.compactable() {
 		select {
 		case a.db.compactc <- struct{}{}:
 		default:
@@ -409,13 +418,11 @@ func (db *DB) compact() (err error) {
 			return nil
 		default:
 		}
-		// The head has a compactable range if 1.5 level 0 ranges are between the oldest
-		// and newest timestamp. The 0.5 acts as a buffer of the appendable window.
-		if db.head.MaxTime()-db.head.MinTime() <= db.opts.BlockRanges[0]/2*3 {
+		if !db.head.compactable() {
 			break
 		}
 		mint := db.head.MinTime()
-		maxt := rangeForTimestamp(mint, db.opts.BlockRanges[0])
+		maxt := rangeForTimestamp(mint, db.head.chunkRange)
 
 		// Wrap head into a range that bounds all reads to it.
 		head := &rangeHead{
@@ -861,7 +868,7 @@ func (db *DB) Close() error {
 		g.Go(pb.Close)
 	}
 
-	var merr MultiError
+	var merr tsdb_errors.MultiError
 
 	merr.Add(g.Wait())
 
@@ -896,7 +903,7 @@ func (db *DB) Snapshot(dir string, withHead bool) error {
 	if dir == db.dir {
 		return errors.Errorf("cannot snapshot into base directory")
 	}
-	if _, err := ulid.Parse(dir); err == nil {
+	if _, err := ulid.ParseStrict(dir); err == nil {
 		return errors.Errorf("dir must not be a valid ULID")
 	}
 
@@ -1037,7 +1044,7 @@ func isBlockDir(fi os.FileInfo) bool {
 	if !fi.IsDir() {
 		return false
 	}
-	_, err := ulid.Parse(fi.Name())
+	_, err := ulid.ParseStrict(fi.Name())
 	return err == nil
 }
 
@@ -1089,50 +1096,8 @@ func nextSequenceFile(dir string) (string, int, error) {
 	return filepath.Join(dir, fmt.Sprintf("%0.6d", i+1)), int(i + 1), nil
 }
 
-// The MultiError type implements the error interface, and contains the
-// Errors used to construct it.
-type MultiError []error
-
-// Returns a concatenated string of the contained errors
-func (es MultiError) Error() string {
-	var buf bytes.Buffer
-
-	if len(es) > 1 {
-		fmt.Fprintf(&buf, "%d errors: ", len(es))
-	}
-
-	for i, err := range es {
-		if i != 0 {
-			buf.WriteString("; ")
-		}
-		buf.WriteString(err.Error())
-	}
-
-	return buf.String()
-}
-
-// Add adds the error to the error list if it is not nil.
-func (es *MultiError) Add(err error) {
-	if err == nil {
-		return
-	}
-	if merr, ok := err.(MultiError); ok {
-		*es = append(*es, merr...)
-	} else {
-		*es = append(*es, err)
-	}
-}
-
-// Err returns the error list as an error or nil if it is empty.
-func (es MultiError) Err() error {
-	if len(es) == 0 {
-		return nil
-	}
-	return es
-}
-
 func closeAll(cs []io.Closer) error {
-	var merr MultiError
+	var merr tsdb_errors.MultiError
 
 	for _, c := range cs {
 		merr.Add(c.Close())
diff --git a/docs/format/chunks.md b/docs/format/chunks.md
index df38867..1ef8603 100644
--- a/docs/format/chunks.md
+++ b/docs/format/chunks.md
@@ -1,15 +1,31 @@
 # Chunks Disk Format
 
-The following describes the format of a single chunks file, which is created in the `chunks/` directory of a block. The maximum size per segment file is 512MiB.
+The following describes the format of a chunks file,
+which is created in the `chunks/` directory of a block.
+The maximum size per segment file is 512MiB.
 
-Chunks in the files are referenced from the index by uint64 composed of in-file offset (lower 4 bytes) and segment sequence number (upper 4 bytes).
+Chunks in the files are referenced from the index by uint64 composed of
+in-file offset (lower 4 bytes) and segment sequence number (upper 4 bytes).
 
 ```
-┌────────────────────────────────────────┬──────────────────────┐
-│ magic(0x85BD40DD) <4 byte>             │ version(1) <1 byte>  │
-├────────────────────────────────────────┴──────────────────────┤
-│ ┌───────────────┬───────────────────┬──────┬────────────────┐ │
-│ │ len <uvarint> │ encoding <1 byte> │ data │ CRC32 <4 byte> │ │
-│ └───────────────┴───────────────────┴──────┴────────────────┘ │
-└───────────────────────────────────────────────────────────────┘
+┌────────────────────────────┬─────────────────────┐
+│ magic(0x85BD40DD) <4 byte> │ version(1) <1 byte> │
+├────────────────────────────┴─────────────────────┤
+│ ┌──────────────────────────────────────────────┐ │
+│ │                   Chunk 1                    │ │
+│ ├──────────────────────────────────────────────┤ │
+│ │                     ...                      │ │
+│ ├──────────────────────────────────────────────┤ │
+│ │                   Chunk N                    │ │
+│ └──────────────────────────────────────────────┘ │
+└──────────────────────────────────────────────────┘
+```
+
+
+# Chunk
+
+```
+┌───────────────┬───────────────────┬──────────────┬────────────────┐
+│ len <uvarint> │ encoding <1 byte> │ data <bytes> │ CRC32 <4 byte> │
+└───────────────┴───────────────────┴──────────────┴────────────────┘
 ```
diff --git a/docs/format/wal.md b/docs/format/wal.md
index 7195e0b..af2ec61 100644
--- a/docs/format/wal.md
+++ b/docs/format/wal.md
@@ -54,14 +54,16 @@ Series records encode the labels that identifies a series and its unique ID.
 
 Sample records encode samples as a list of triples `(series_id, timestamp, value)`.
 Series reference and timestamp are encoded as deltas w.r.t the first sample.
+The first row stores the starting id and the starting timestamp.
+The first sample record begins at the second row.
 
 ```
 ┌──────────────────────────────────────────────────────────────────┐
 │ type = 2 <1b>                                                    │
 ├──────────────────────────────────────────────────────────────────┤
-│ ┌────────────────────┬───────────────────────────┬─────────────┐ │
-│ │ id <8b>            │ timestamp <8b>            │ value <8b>  │ │
-│ └────────────────────┴───────────────────────────┴─────────────┘ │
+│ ┌────────────────────┬───────────────────────────┐               │
+│ │ id <8b>            │ timestamp <8b>            │               │
+│ └────────────────────┴───────────────────────────┘               │
 │ ┌────────────────────┬───────────────────────────┬─────────────┐ │
 │ │ id_delta <uvarint> │ timestamp_delta <uvarint> │ value <8b>  │ │
 │ └────────────────────┴───────────────────────────┴─────────────┘ │
diff --git a/encoding/encoding.go b/encoding/encoding.go
index 395e11c..a732a60 100644
--- a/encoding/encoding.go
+++ b/encoding/encoding.go
@@ -27,7 +27,7 @@ var (
 	ErrInvalidChecksum = errors.New("invalid checksum")
 )
 
-// enbuf is a helper type to populate a byte slice with various types.
+// Encbuf is a helper type to populate a byte slice with various types.
 type Encbuf struct {
 	B []byte
 	C [binary.MaxVarintLen64]byte
@@ -65,14 +65,14 @@ func (e *Encbuf) PutVarint64(x int64) {
 	e.B = append(e.B, e.C[:n]...)
 }
 
-// putVarintStr writes a string to the buffer prefixed by its varint length (in bytes!).
+// PutUvarintStr writes a string to the buffer prefixed by its varint length (in bytes!).
 func (e *Encbuf) PutUvarintStr(s string) {
 	b := *(*[]byte)(unsafe.Pointer(&s))
 	e.PutUvarint(len(b))
 	e.PutString(s)
 }
 
-// putHash appends a hash over the buffers current contents to the buffer.
+// PutHash appends a hash over the buffers current contents to the buffer.
 func (e *Encbuf) PutHash(h hash.Hash) {
 	h.Reset()
 	_, err := h.Write(e.B)
@@ -82,7 +82,7 @@ func (e *Encbuf) PutHash(h hash.Hash) {
 	e.B = h.Sum(e.B)
 }
 
-// decbuf provides safe methods to extract data from a byte slice. It does all
+// Decbuf provides safe methods to extract data from a byte slice. It does all
 // necessary bounds checking and advancing of the byte slice.
 // Several datums can be extracted without checking for errors. However, before using
 // any datum, the err() method must be checked.
diff --git a/errors/errors.go b/errors/errors.go
new file mode 100644
index 0000000..69d3662
--- /dev/null
+++ b/errors/errors.go
@@ -0,0 +1,62 @@
+// Copyright 2016 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package errors
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// The MultiError type implements the error interface, and contains the
+// Errors used to construct it.
+type MultiError []error
+
+// Returns a concatenated string of the contained errors
+func (es MultiError) Error() string {
+	var buf bytes.Buffer
+
+	if len(es) > 1 {
+		fmt.Fprintf(&buf, "%d errors: ", len(es))
+	}
+
+	for i, err := range es {
+		if i != 0 {
+			buf.WriteString("; ")
+		}
+		buf.WriteString(err.Error())
+	}
+
+	return buf.String()
+}
+
+// Add adds the error to the error list if it is not nil.
+func (es *MultiError) Add(err error) {
+	if err == nil {
+		return
+	}
+	if merr, ok := err.(MultiError); ok {
+		*es = append(*es, merr...)
+	} else {
+		*es = append(*es, err)
+	}
+}
+
+// Err returns the error list as an error or nil if it is empty.
+func (es MultiError) Err() error {
+	if len(es) == 0 {
+		return nil
+	}
+	return es
+}
diff --git a/head.go b/head.go
index fe20897..26a42fb 100644
--- a/head.go
+++ b/head.go
@@ -39,7 +39,7 @@ var (
 	ErrNotFound = errors.Errorf("not found")
 
 	// ErrOutOfOrderSample is returned if an appended sample has a
-	// timestamp larger than the most recent sample.
+	// timestamp smaller than the most recent sample.
 	ErrOutOfOrderSample = errors.New("out of order sample")
 
 	// ErrAmendSample is returned if an appended sample has the same timestamp
@@ -891,8 +891,8 @@ func (h *Head) Delete(mint, maxt int64, ms ...labels.Matcher) error {
 	var enc RecordEncoder
 	if h.wal != nil {
 		// Although we don't store the stones in the head
-		// we need to write them  to the WAL to mark these as deleted
-		// after a restart while loeading the WAL.
+		// we need to write them to the WAL to mark these as deleted
+		// after a restart while loading the WAL.
 		if err := h.wal.Log(enc.Tombstones(stones, nil)); err != nil {
 			return err
 		}
@@ -954,7 +954,7 @@ func (h *Head) gc() {
 	h.postings.Delete(deleted)
 
 	// Rebuild symbols and label value indices from what is left in the postings terms.
-	symbols := make(map[string]struct{})
+	symbols := make(map[string]struct{}, len(h.symbols))
 	values := make(map[string]stringset, len(h.values))
 
 	if err := h.postings.Iter(func(t labels.Label, _ index.Postings) error {
@@ -1020,6 +1020,13 @@ func (h *Head) MaxTime() int64 {
 	return atomic.LoadInt64(&h.maxTime)
 }
 
+// compactable returns whether the head has a compactable range.
+// The head has a compactable range when the head time range is 1.5 times the chunk range.
+// The 0.5 acts as a buffer of the appendable window.
+func (h *Head) compactable() bool {
+	return h.MaxTime()-h.MinTime() > h.chunkRange/2*3
+}
+
 // Close flushes the WAL and closes the head.
 func (h *Head) Close() error {
 	if h.wal == nil {
diff --git a/index/postings.go b/index/postings.go
index cbad5b7..9270c68 100644
--- a/index/postings.go
+++ b/index/postings.go
@@ -92,7 +92,7 @@ func (p *MemPostings) Get(name, value string) Postings {
 	if lp == nil {
 		return EmptyPostings()
 	}
-	return newListPostings(lp)
+	return newListPostings(lp...)
 }
 
 // All returns a postings list over all documents ever added.
@@ -202,7 +202,7 @@ func (p *MemPostings) Iter(f func(labels.Label, Postings) error) error {
 
 	for n, e := range p.m {
 		for v, p := range e {
-			if err := f(labels.Label{Name: n, Value: v}, newListPostings(p)); err != nil {
+			if err := f(labels.Label{Name: n, Value: v}, newListPostings(p...)); err != nil {
 				return err
 			}
 		}
@@ -283,6 +283,8 @@ func (e errPostings) Err() error       { return e.err }
 var emptyPostings = errPostings{}
 
 // EmptyPostings returns a postings list that's always empty.
+// NOTE: Returning EmptyPostings sentinel when index.Postings struct has no postings is recommended.
+// It triggers optimized flow in other functions like Intersect, Without etc.
 func EmptyPostings() Postings {
 	return emptyPostings
 }
@@ -296,13 +298,20 @@ func ErrPostings(err error) Postings {
 // input postings.
 func Intersect(its ...Postings) Postings {
 	if len(its) == 0 {
-		return emptyPostings
+		return EmptyPostings()
 	}
 	if len(its) == 1 {
 		return its[0]
 	}
+
 	l := len(its) / 2
-	return newIntersectPostings(Intersect(its[:l]...), Intersect(its[l:]...))
+	a := Intersect(its[:l]...)
+	b := Intersect(its[l:]...)
+
+	if a == EmptyPostings() || b == EmptyPostings() {
+		return EmptyPostings()
+	}
+	return newIntersectPostings(a, b)
 }
 
 type intersectPostings struct {
@@ -366,7 +375,12 @@ func Merge(its ...Postings) Postings {
 	if len(its) == 1 {
 		return its[0]
 	}
-	return newMergedPostings(its)
+
+	p, ok := newMergedPostings(its)
+	if !ok {
+		return EmptyPostings()
+	}
+	return p
 }
 
 type postingsHeap []Postings
@@ -395,18 +409,24 @@ type mergedPostings struct {
 	err        error
 }
 
-func newMergedPostings(p []Postings) *mergedPostings {
+func newMergedPostings(p []Postings) (m *mergedPostings, nonEmpty bool) {
 	ph := make(postingsHeap, 0, len(p))
+
 	for _, it := range p {
+		// NOTE: mergedPostings struct requires the user to issue an initial Next.
 		if it.Next() {
 			ph = append(ph, it)
 		} else {
 			if it.Err() != nil {
-				return &mergedPostings{err: it.Err()}
+				return &mergedPostings{err: it.Err()}, true
 			}
 		}
 	}
-	return &mergedPostings{h: ph}
+
+	if len(ph) == 0 {
+		return nil, false
+	}
+	return &mergedPostings{h: ph}, true
 }
 
 func (it *mergedPostings) Next() bool {
@@ -495,8 +515,15 @@ func (it mergedPostings) Err() error {
 }
 
 // Without returns a new postings list that contains all elements from the full list that
-// are not in the drop list
+// are not in the drop list.
 func Without(full, drop Postings) Postings {
+	if full == EmptyPostings() {
+		return EmptyPostings()
+	}
+
+	if drop == EmptyPostings() {
+		return full
+	}
 	return newRemovedPostings(full, drop)
 }
 
@@ -580,10 +607,10 @@ type listPostings struct {
 }
 
 func NewListPostings(list []uint64) Postings {
-	return newListPostings(list)
+	return newListPostings(list...)
 }
 
-func newListPostings(list []uint64) *listPostings {
+func newListPostings(list ...uint64) *listPostings {
 	return &listPostings{list: list}
 }
 
diff --git a/labels/selector.go b/labels/selector.go
index c0c74ed..bcc5b51 100644
--- a/labels/selector.go
+++ b/labels/selector.go
@@ -75,7 +75,7 @@ func NewRegexpMatcher(name, pattern string) (Matcher, error) {
 	return &regexpMatcher{name: name, re: re}, nil
 }
 
-// NewRegexpMatcher returns a new matcher verifying that a value matches
+// NewMustRegexpMatcher returns a new matcher verifying that a value matches
 // the regular expression pattern. Will panic if the pattern is not a valid
 // regular expression.
 func NewMustRegexpMatcher(name, pattern string) Matcher {
diff --git a/querier.go b/querier.go
index 3e8cd77..71910f1 100644
--- a/querier.go
+++ b/querier.go
@@ -21,6 +21,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/prometheus/tsdb/chunkenc"
 	"github.com/prometheus/tsdb/chunks"
+	tsdb_errors "github.com/prometheus/tsdb/errors"
 	"github.com/prometheus/tsdb/index"
 	"github.com/prometheus/tsdb/labels"
 )
@@ -134,7 +135,7 @@ func (q *querier) sel(qs []Querier, ms []labels.Matcher) (SeriesSet, error) {
 }
 
 func (q *querier) Close() error {
-	var merr MultiError
+	var merr tsdb_errors.MultiError
 
 	for _, bq := range q.blocks {
 		merr.Add(bq.Close())
@@ -251,7 +252,7 @@ func (q *blockQuerier) LabelValuesFor(string, labels.Label) ([]string, error) {
 }
 
 func (q *blockQuerier) Close() error {
-	var merr MultiError
+	var merr tsdb_errors.MultiError
 
 	merr.Add(q.index.Close())
 	merr.Add(q.chunks.Close())
@@ -263,6 +264,7 @@ func (q *blockQuerier) Close() error {
 // PostingsForMatchers assembles a single postings iterator against the index reader
 // based on the given matchers. It returns a list of label names that must be manually
 // checked to not exist in series the postings list points to.
+// It returns EmptyPostings() if it can be determined beforehand that no results will be found.
 func PostingsForMatchers(ix IndexReader, ms ...labels.Matcher) (index.Postings, error) {
 	var its []index.Postings
 


Test patch:
diff --git a/block_test.go b/block_test.go
index dd0972d..c6c3951 100644
--- a/block_test.go
+++ b/block_test.go
@@ -15,6 +15,8 @@ package tsdb
 
 import (
 	"context"
+	"encoding/binary"
+	"errors"
 	"io/ioutil"
 	"math/rand"
 	"os"
@@ -22,6 +24,7 @@ import (
 	"testing"
 
 	"github.com/go-kit/kit/log"
+	"github.com/prometheus/tsdb/chunks"
 	"github.com/prometheus/tsdb/testutil"
 	"github.com/prometheus/tsdb/tsdbutil"
 )
@@ -32,7 +35,9 @@ import (
 func TestBlockMetaMustNeverBeVersion2(t *testing.T) {
 	dir, err := ioutil.TempDir("", "metaversion")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	testutil.Ok(t, writeMetaFile(dir, &BlockMeta{}))
 
@@ -44,7 +49,9 @@ func TestBlockMetaMustNeverBeVersion2(t *testing.T) {
 func TestSetCompactionFailed(t *testing.T) {
 	tmpdir, err := ioutil.TempDir("", "test")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(tmpdir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(tmpdir))
+	}()
 
 	blockDir := createBlock(t, tmpdir, genSeries(1, 1, 0, 0))
 	b, err := OpenBlock(nil, blockDir, nil)
@@ -60,6 +67,87 @@ func TestSetCompactionFailed(t *testing.T) {
 	testutil.Ok(t, b.Close())
 }
 
+func TestCreateBlock(t *testing.T) {
+	tmpdir, err := ioutil.TempDir("", "test")
+	testutil.Ok(t, err)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(tmpdir))
+	}()
+	b, err := OpenBlock(nil, createBlock(t, tmpdir, genSeries(1, 1, 0, 10)), nil)
+	if err == nil {
+		testutil.Ok(t, b.Close())
+	}
+	testutil.Ok(t, err)
+}
+
+func TestCorruptedChunk(t *testing.T) {
+	for name, test := range map[string]struct {
+		corrFunc func(f *os.File) // Func that applies the corruption.
+		expErr   error
+	}{
+		"invalid header size": {
+			func(f *os.File) {
+				err := f.Truncate(1)
+				testutil.Ok(t, err)
+			},
+			errors.New("invalid chunk header in segment 0: invalid size"),
+		},
+		"invalid magic number": {
+			func(f *os.File) {
+				magicChunksOffset := int64(0)
+				_, err := f.Seek(magicChunksOffset, 0)
+				testutil.Ok(t, err)
+
+				// Set invalid magic number.
+				b := make([]byte, chunks.MagicChunksSize)
+				binary.BigEndian.PutUint32(b[:chunks.MagicChunksSize], 0x00000000)
+				n, err := f.Write(b)
+				testutil.Ok(t, err)
+				testutil.Equals(t, chunks.MagicChunksSize, n)
+			},
+			errors.New("invalid magic number 0"),
+		},
+		"invalid chunk format version": {
+			func(f *os.File) {
+				chunksFormatVersionOffset := int64(4)
+				_, err := f.Seek(chunksFormatVersionOffset, 0)
+				testutil.Ok(t, err)
+
+				// Set invalid chunk format version.
+				b := make([]byte, chunks.ChunksFormatVersionSize)
+				b[0] = 0
+				n, err := f.Write(b)
+				testutil.Ok(t, err)
+				testutil.Equals(t, chunks.ChunksFormatVersionSize, n)
+			},
+			errors.New("invalid chunk format version 0"),
+		},
+	} {
+		t.Run(name, func(t *testing.T) {
+			tmpdir, err := ioutil.TempDir("", "test_open_block_chunk_corrupted")
+			testutil.Ok(t, err)
+			defer func() {
+				testutil.Ok(t, os.RemoveAll(tmpdir))
+			}()
+
+			blockDir := createBlock(t, tmpdir, genSeries(1, 1, 0, 0))
+			files, err := sequenceFiles(chunkDir(blockDir))
+			testutil.Ok(t, err)
+			testutil.Assert(t, len(files) > 0, "No chunk created.")
+
+			f, err := os.OpenFile(files[0], os.O_RDWR, 0666)
+			testutil.Ok(t, err)
+
+			// Apply corruption function.
+			test.corrFunc(f)
+			testutil.Ok(t, f.Close())
+
+			_, err = OpenBlock(nil, blockDir, nil)
+			testutil.Equals(t, test.expErr.Error(), err.Error())
+		})
+	}
+}
+
 // createBlock creates a block with given set of series and returns its dir.
 func createBlock(tb testing.TB, dir string, series []Series) string {
 	head, err := NewHead(nil, nil, nil, 2*60*60*1000)
diff --git a/checkpoint_test.go b/checkpoint_test.go
index 8b13c15..fe8ee4e 100644
--- a/checkpoint_test.go
+++ b/checkpoint_test.go
@@ -32,7 +32,9 @@ import (
 func TestLastCheckpoint(t *testing.T) {
 	dir, err := ioutil.TempDir("", "test_checkpoint")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	_, _, err = LastCheckpoint(dir)
 	testutil.Equals(t, ErrNotFound, err)
@@ -65,7 +67,9 @@ func TestLastCheckpoint(t *testing.T) {
 func TestDeleteCheckpoints(t *testing.T) {
 	dir, err := ioutil.TempDir("", "test_checkpoint")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	testutil.Ok(t, DeleteCheckpoints(dir, 0))
 
@@ -84,7 +88,9 @@ func TestDeleteCheckpoints(t *testing.T) {
 func TestCheckpoint(t *testing.T) {
 	dir, err := ioutil.TempDir("", "test_checkpoint")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	var enc RecordEncoder
 	// Create a dummy segment to bump the initial number.
@@ -188,7 +194,9 @@ func TestCheckpointNoTmpFolderAfterError(t *testing.T) {
 	// Create a new wal with an invalid records.
 	dir, err := ioutil.TempDir("", "test_checkpoint")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 	w, err := wal.NewSize(nil, nil, dir, 64*1024)
 	testutil.Ok(t, err)
 	testutil.Ok(t, w.Log([]byte{99}))
diff --git a/compact_test.go b/compact_test.go
index 1e06b39..1e6a962 100644
--- a/compact_test.go
+++ b/compact_test.go
@@ -421,7 +421,9 @@ func TestCompactionFailWillCleanUpTempDir(t *testing.T) {
 
 	tmpdir, err := ioutil.TempDir("", "test")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(tmpdir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(tmpdir))
+	}()
 
 	testutil.NotOk(t, compactor.write(tmpdir, &BlockMeta{}, erringBReader{}))
 	_, err = os.Stat(filepath.Join(tmpdir, BlockMeta{}.ULID.String()) + ".tmp")
@@ -907,7 +909,9 @@ func TestDisableAutoCompactions(t *testing.T) {
 func TestCancelCompactions(t *testing.T) {
 	tmpdir, err := ioutil.TempDir("", "testCancelCompaction")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(tmpdir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(tmpdir))
+	}()
 
 	// Create some blocks to fall within the compaction range.
 	createBlock(t, tmpdir, genSeries(10, 10000, 0, 1000))
@@ -918,7 +922,9 @@ func TestCancelCompactions(t *testing.T) {
 	tmpdirCopy := tmpdir + "Copy"
 	err = fileutil.CopyDirs(tmpdir, tmpdirCopy)
 	testutil.Ok(t, err)
-	defer os.RemoveAll(tmpdirCopy)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(tmpdirCopy))
+	}()
 
 	// Measure the compaction time without interupting it.
 	var timeCompactionUninterrupted time.Duration
diff --git a/db_test.go b/db_test.go
index eda807f..3d6aa6a 100644
--- a/db_test.go
+++ b/db_test.go
@@ -437,7 +437,9 @@ func TestDB_Snapshot(t *testing.T) {
 	snap, err := ioutil.TempDir("", "snap")
 	testutil.Ok(t, err)
 
-	defer os.RemoveAll(snap)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(snap))
+	}()
 	testutil.Ok(t, db.Snapshot(snap, true))
 	testutil.Ok(t, db.Close())
 
@@ -504,7 +506,9 @@ Outer:
 		snap, err := ioutil.TempDir("", "snap")
 		testutil.Ok(t, err)
 
-		defer os.RemoveAll(snap)
+		defer func() {
+			testutil.Ok(t, os.RemoveAll(snap))
+		}()
 		testutil.Ok(t, db.Snapshot(snap, true))
 		testutil.Ok(t, db.Close())
 
@@ -742,29 +746,53 @@ func TestWALFlushedOnDBClose(t *testing.T) {
 	testutil.Equals(t, []string{"labelvalue"}, values)
 }
 
-func TestWALSegmentSizeOption(t *testing.T) {
-	options := *DefaultOptions
-	options.WALSegmentSize = 2 * 32 * 1024
-	db, delete := openTestDB(t, &options)
-	defer delete()
-	app := db.Appender()
-	for i := int64(0); i < 155; i++ {
-		_, err := app.Add(labels.Labels{labels.Label{Name: "wal", Value: "size"}}, i, rand.Float64())
-		testutil.Ok(t, err)
-		testutil.Ok(t, app.Commit())
+func TestWALSegmentSizeOptions(t *testing.T) {
+	tests := map[int]func(dbdir string, segmentSize int){
+		// Default Wal Size.
+		0: func(dbDir string, segmentSize int) {
+			files, err := ioutil.ReadDir(filepath.Join(dbDir, "wal"))
+			testutil.Ok(t, err)
+			for _, f := range files[:len(files)-1] {
+				testutil.Equals(t, int64(DefaultOptions.WALSegmentSize), f.Size(), "WAL file size doesn't match WALSegmentSize option, filename: %v", f.Name())
+			}
+			lastFile := files[len(files)-1]
+			testutil.Assert(t, int64(DefaultOptions.WALSegmentSize) > lastFile.Size(), "last WAL file size is not smaller than the WALSegmentSize option, filename: %v", lastFile.Name())
+		},
+		// Custom Wal Size.
+		2 * 32 * 1024: func(dbDir string, segmentSize int) {
+			files, err := ioutil.ReadDir(filepath.Join(dbDir, "wal"))
+			testutil.Assert(t, len(files) > 1, "current WALSegmentSize should result in more than a single WAL file.")
+			testutil.Ok(t, err)
+			for _, f := range files[:len(files)-1] {
+				testutil.Equals(t, int64(segmentSize), f.Size(), "WAL file size doesn't match WALSegmentSize option, filename: %v", f.Name())
+			}
+			lastFile := files[len(files)-1]
+			testutil.Assert(t, int64(segmentSize) > lastFile.Size(), "last WAL file size is not smaller than the WALSegmentSize option, filename: %v", lastFile.Name())
+		},
+		// Wal disabled.
+		-1: func(dbDir string, segmentSize int) {
+			if _, err := os.Stat(filepath.Join(dbDir, "wal")); !os.IsNotExist(err) {
+				t.Fatal("wal directory is present when the wal is disabled")
+			}
+		},
 	}
+	for segmentSize, testFunc := range tests {
+		t.Run(fmt.Sprintf("WALSegmentSize %d test", segmentSize), func(t *testing.T) {
+			options := *DefaultOptions
+			options.WALSegmentSize = segmentSize
+			db, delete := openTestDB(t, &options)
+			defer delete()
+			app := db.Appender()
+			for i := int64(0); i < 155; i++ {
+				_, err := app.Add(labels.Labels{labels.Label{Name: "wal", Value: "size"}}, i, rand.Float64())
+				testutil.Ok(t, err)
+				testutil.Ok(t, app.Commit())
+			}
 
-	dbDir := db.Dir()
-	db.Close()
-	files, err := ioutil.ReadDir(filepath.Join(dbDir, "wal"))
-	testutil.Assert(t, len(files) > 1, "current WALSegmentSize should result in more than a single WAL file.")
-	testutil.Ok(t, err)
-	for i, f := range files {
-		if len(files)-1 != i {
-			testutil.Equals(t, int64(options.WALSegmentSize), f.Size(), "WAL file size doesn't match WALSegmentSize option, filename: %v", f.Name())
-			continue
-		}
-		testutil.Assert(t, int64(options.WALSegmentSize) > f.Size(), "last WAL file size is not smaller than the WALSegmentSize option, filename: %v", f.Name())
+			dbDir := db.Dir()
+			db.Close()
+			testFunc(dbDir, options.WALSegmentSize)
+		})
 	}
 }
 
@@ -800,7 +828,9 @@ func TestTombstoneClean(t *testing.T) {
 		snap, err := ioutil.TempDir("", "snap")
 		testutil.Ok(t, err)
 
-		defer os.RemoveAll(snap)
+		defer func() {
+			testutil.Ok(t, os.RemoveAll(snap))
+		}()
 		testutil.Ok(t, db.Snapshot(snap, true))
 		testutil.Ok(t, db.Close())
 
@@ -1323,10 +1353,13 @@ func TestInitializeHeadTimestamp(t *testing.T) {
 	t.Run("clean", func(t *testing.T) {
 		dir, err := ioutil.TempDir("", "test_head_init")
 		testutil.Ok(t, err)
-		defer os.RemoveAll(dir)
+		defer func() {
+			testutil.Ok(t, os.RemoveAll(dir))
+		}()
 
 		db, err := Open(dir, nil, nil, nil)
 		testutil.Ok(t, err)
+		defer db.Close()
 
 		// Should be set to init values if no WAL or blocks exist so far.
 		testutil.Equals(t, int64(math.MaxInt64), db.head.MinTime())
@@ -1343,7 +1376,9 @@ func TestInitializeHeadTimestamp(t *testing.T) {
 	t.Run("wal-only", func(t *testing.T) {
 		dir, err := ioutil.TempDir("", "test_head_init")
 		testutil.Ok(t, err)
-		defer os.RemoveAll(dir)
+		defer func() {
+			testutil.Ok(t, os.RemoveAll(dir))
+		}()
 
 		testutil.Ok(t, os.MkdirAll(path.Join(dir, "wal"), 0777))
 		w, err := wal.New(nil, nil, path.Join(dir, "wal"))
@@ -1365,6 +1400,7 @@ func TestInitializeHeadTimestamp(t *testing.T) {
 
 		db, err := Open(dir, nil, nil, nil)
 		testutil.Ok(t, err)
+		defer db.Close()
 
 		testutil.Equals(t, int64(5000), db.head.MinTime())
 		testutil.Equals(t, int64(15000), db.head.MaxTime())
@@ -1372,12 +1408,15 @@ func TestInitializeHeadTimestamp(t *testing.T) {
 	t.Run("existing-block", func(t *testing.T) {
 		dir, err := ioutil.TempDir("", "test_head_init")
 		testutil.Ok(t, err)
-		defer os.RemoveAll(dir)
+		defer func() {
+			testutil.Ok(t, os.RemoveAll(dir))
+		}()
 
 		createBlock(t, dir, genSeries(1, 1, 1000, 2000))
 
 		db, err := Open(dir, nil, nil, nil)
 		testutil.Ok(t, err)
+		defer db.Close()
 
 		testutil.Equals(t, int64(2000), db.head.MinTime())
 		testutil.Equals(t, int64(2000), db.head.MaxTime())
@@ -1385,7 +1424,9 @@ func TestInitializeHeadTimestamp(t *testing.T) {
 	t.Run("existing-block-and-wal", func(t *testing.T) {
 		dir, err := ioutil.TempDir("", "test_head_init")
 		testutil.Ok(t, err)
-		defer os.RemoveAll(dir)
+		defer func() {
+			testutil.Ok(t, os.RemoveAll(dir))
+		}()
 
 		createBlock(t, dir, genSeries(1, 1, 1000, 6000))
 
@@ -1411,6 +1452,7 @@ func TestInitializeHeadTimestamp(t *testing.T) {
 
 		db, err := Open(dir, nil, r, nil)
 		testutil.Ok(t, err)
+		defer db.Close()
 
 		testutil.Equals(t, int64(6000), db.head.MinTime())
 		testutil.Equals(t, int64(15000), db.head.MaxTime())
@@ -1482,7 +1524,7 @@ func TestNoEmptyBlocks(t *testing.T) {
 		testutil.Assert(t, len(actBlocks) == 1, "No blocks created when compacting with >0 samples")
 	})
 
-	t.Run(`When no new block is created from head, and there are some blocks on disk 
+	t.Run(`When no new block is created from head, and there are some blocks on disk
 	compaction should not run into infinite loop (was seen during development).`, func(t *testing.T) {
 		oldBlocks := db.Blocks()
 		app := db.Appender()
diff --git a/head_test.go b/head_test.go
index 679e8c8..7e8730b 100644
--- a/head_test.go
+++ b/head_test.go
@@ -117,7 +117,9 @@ func TestHead_ReadWAL(t *testing.T) {
 	}
 	dir, err := ioutil.TempDir("", "test_read_wal")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	w, err := wal.New(nil, nil, dir)
 	testutil.Ok(t, err)
@@ -281,7 +283,9 @@ func TestHeadDeleteSeriesWithoutSamples(t *testing.T) {
 	}
 	dir, err := ioutil.TempDir("", "test_delete_series")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	w, err := wal.New(nil, nil, dir)
 	testutil.Ok(t, err)
@@ -337,13 +341,17 @@ Outer:
 	for _, c := range cases {
 		dir, err := ioutil.TempDir("", "test_wal_reload")
 		testutil.Ok(t, err)
-		defer os.RemoveAll(dir)
+		defer func() {
+			testutil.Ok(t, os.RemoveAll(dir))
+		}()
 
 		w, err := wal.New(nil, nil, path.Join(dir, "wal"))
 		testutil.Ok(t, err)
+		defer w.Close()
 
 		head, err := NewHead(nil, nil, w, 1000)
 		testutil.Ok(t, err)
+		defer head.Close()
 
 		app := head.Appender()
 		for _, smpl := range smplsAll {
@@ -361,8 +369,10 @@ Outer:
 		// Compare the samples for both heads - before and after the reload.
 		reloadedW, err := wal.New(nil, nil, w.Dir()) // Use a new wal to ensure deleted samples are gone even after a reload.
 		testutil.Ok(t, err)
+		defer reloadedW.Close()
 		reloadedHead, err := NewHead(nil, nil, reloadedW, 1000)
 		testutil.Ok(t, err)
+		defer reloadedHead.Close()
 		testutil.Ok(t, reloadedHead.Init(0))
 		for _, h := range []*Head{head, reloadedHead} {
 			indexr, err := h.Index()
@@ -543,7 +553,9 @@ func TestDelete_e2e(t *testing.T) {
 		seriesMap[labels.New(l...).String()] = []tsdbutil.Sample{}
 	}
 	dir, _ := ioutil.TempDir("", "test")
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 	hb, err := NewHead(nil, nil, nil, 100000)
 	testutil.Ok(t, err)
 	defer hb.Close()
@@ -907,7 +919,9 @@ func TestRemoveSeriesAfterRollbackAndTruncate(t *testing.T) {
 func TestHead_LogRollback(t *testing.T) {
 	dir, err := ioutil.TempDir("", "wal_rollback")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	w, err := wal.New(nil, nil, dir)
 	testutil.Ok(t, err)
@@ -974,7 +988,9 @@ func TestWalRepair(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			dir, err := ioutil.TempDir("", "wal_head_repair")
 			testutil.Ok(t, err)
-			defer os.RemoveAll(dir)
+			defer func() {
+				testutil.Ok(t, os.RemoveAll(dir))
+			}()
 
 			w, err := wal.New(nil, nil, dir)
 			testutil.Ok(t, err)
diff --git a/index/index_test.go b/index/index_test.go
index 1395a34..70aa097 100644
--- a/index/index_test.go
+++ b/index/index_test.go
@@ -150,7 +150,9 @@ func (m mockIndex) LabelIndices() ([][]string, error) {
 func TestIndexRW_Create_Open(t *testing.T) {
 	dir, err := ioutil.TempDir("", "test_index_create")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	fn := filepath.Join(dir, indexFilename)
 
@@ -168,6 +170,7 @@ func TestIndexRW_Create_Open(t *testing.T) {
 	testutil.Ok(t, err)
 	_, err = f.WriteAt([]byte{0, 0}, 0)
 	testutil.Ok(t, err)
+	f.Close()
 
 	_, err = NewFileReader(dir)
 	testutil.NotOk(t, err)
@@ -176,7 +179,9 @@ func TestIndexRW_Create_Open(t *testing.T) {
 func TestIndexRW_Postings(t *testing.T) {
 	dir, err := ioutil.TempDir("", "test_index_postings")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	fn := filepath.Join(dir, indexFilename)
 
@@ -207,7 +212,7 @@ func TestIndexRW_Postings(t *testing.T) {
 	testutil.Ok(t, iw.AddSeries(3, series[2]))
 	testutil.Ok(t, iw.AddSeries(4, series[3]))
 
-	err = iw.WritePostings("a", "1", newListPostings([]uint64{1, 2, 3, 4}))
+	err = iw.WritePostings("a", "1", newListPostings(1, 2, 3, 4))
 	testutil.Ok(t, err)
 
 	testutil.Ok(t, iw.Close())
@@ -236,7 +241,9 @@ func TestIndexRW_Postings(t *testing.T) {
 func TestPersistence_index_e2e(t *testing.T) {
 	dir, err := ioutil.TempDir("", "test_persistence_e2e")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	lbls, err := labels.ReadLabels(filepath.Join("..", "testdata", "20kseries.json"), 20000)
 	testutil.Ok(t, err)
@@ -299,7 +306,6 @@ func TestPersistence_index_e2e(t *testing.T) {
 			valset[l.Value] = struct{}{}
 		}
 		postings.Add(uint64(i), s.labels)
-		i++
 	}
 
 	for k, v := range values {
@@ -317,9 +323,9 @@ func TestPersistence_index_e2e(t *testing.T) {
 	for i := range all {
 		all[i] = uint64(i)
 	}
-	err = iw.WritePostings("", "", newListPostings(all))
+	err = iw.WritePostings("", "", newListPostings(all...))
 	testutil.Ok(t, err)
-	mi.WritePostings("", "", newListPostings(all))
+	mi.WritePostings("", "", newListPostings(all...))
 
 	for n, e := range postings.m {
 		for v := range e {
diff --git a/index/postings_test.go b/index/postings_test.go
index 668ec36..b8f7603 100644
--- a/index/postings_test.go
+++ b/index/postings_test.go
@@ -62,38 +62,120 @@ func TestMemPostings_ensureOrder(t *testing.T) {
 }
 
 func TestIntersect(t *testing.T) {
+	a := newListPostings(1, 2, 3)
+	b := newListPostings(2, 3, 4)
+
 	var cases = []struct {
-		a, b []uint64
-		res  []uint64
+		in []Postings
+
+		res Postings
 	}{
 		{
-			a:   []uint64{1, 2, 3, 4, 5},
-			b:   []uint64{6, 7, 8, 9, 10},
-			res: nil,
+			in:  []Postings{},
+			res: EmptyPostings(),
 		},
 		{
-			a:   []uint64{1, 2, 3, 4, 5},
-			b:   []uint64{4, 5, 6, 7, 8},
-			res: []uint64{4, 5},
+			in:  []Postings{a, b, EmptyPostings()},
+			res: EmptyPostings(),
 		},
 		{
-			a:   []uint64{1, 2, 3, 4, 9, 10},
-			b:   []uint64{1, 4, 5, 6, 7, 8, 10, 11},
-			res: []uint64{1, 4, 10},
-		}, {
-			a:   []uint64{1},
-			b:   []uint64{0, 1},
-			res: []uint64{1},
+			in:  []Postings{b, a, EmptyPostings()},
+			res: EmptyPostings(),
+		},
+		{
+			in:  []Postings{EmptyPostings(), b, a},
+			res: EmptyPostings(),
+		},
+		{
+			in:  []Postings{EmptyPostings(), a, b},
+			res: EmptyPostings(),
+		},
+		{
+			in:  []Postings{a, EmptyPostings(), b},
+			res: EmptyPostings(),
+		},
+		{
+			in:  []Postings{b, EmptyPostings(), a},
+			res: EmptyPostings(),
+		},
+		{
+			in:  []Postings{b, EmptyPostings(), a, a, b, a, a, a},
+			res: EmptyPostings(),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2, 3, 4, 5),
+				newListPostings(6, 7, 8, 9, 10),
+			},
+			res: newListPostings(),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2, 3, 4, 5),
+				newListPostings(4, 5, 6, 7, 8),
+			},
+			res: newListPostings(4, 5),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2, 3, 4, 9, 10),
+				newListPostings(1, 4, 5, 6, 7, 8, 10, 11),
+			},
+			res: newListPostings(1, 4, 10),
+		},
+		{
+			in: []Postings{
+				newListPostings(1),
+				newListPostings(0, 1),
+			},
+			res: newListPostings(1),
+		},
+		{
+			in: []Postings{
+				newListPostings(1),
+			},
+			res: newListPostings(1),
+		},
+		{
+			in: []Postings{
+				newListPostings(1),
+				newListPostings(),
+			},
+			res: newListPostings(),
+		},
+		{
+			in: []Postings{
+				newListPostings(),
+				newListPostings(),
+			},
+			res: newListPostings(),
 		},
 	}
 
 	for _, c := range cases {
-		a := newListPostings(c.a)
-		b := newListPostings(c.b)
+		t.Run("", func(t *testing.T) {
+			if c.res == nil {
+				t.Fatal("intersect result expectancy cannot be nil")
+			}
 
-		res, err := ExpandPostings(Intersect(a, b))
-		testutil.Ok(t, err)
-		testutil.Equals(t, c.res, res)
+			expected, err := ExpandPostings(c.res)
+			testutil.Ok(t, err)
+
+			i := Intersect(c.in...)
+
+			if c.res == EmptyPostings() {
+				testutil.Equals(t, EmptyPostings(), i)
+				return
+			}
+
+			if i == EmptyPostings() {
+				t.Fatal("intersect unexpected result: EmptyPostings sentinel")
+			}
+
+			res, err := ExpandPostings(i)
+			testutil.Ok(t, err)
+			testutil.Equals(t, expected, res)
+		})
 	}
 }
 
@@ -128,7 +210,7 @@ func TestMultiIntersect(t *testing.T) {
 	for _, c := range cases {
 		ps := make([]Postings, 0, len(c.p))
 		for _, postings := range c.p {
-			ps = append(ps, newListPostings(postings))
+			ps = append(ps, newListPostings(postings...))
 		}
 
 		res, err := ExpandPostings(Intersect(ps...))
@@ -157,10 +239,10 @@ func BenchmarkIntersect(t *testing.B) {
 		d = append(d, uint64(i))
 	}
 
-	i1 := newListPostings(a)
-	i2 := newListPostings(b)
-	i3 := newListPostings(c)
-	i4 := newListPostings(d)
+	i1 := newListPostings(a...)
+	i2 := newListPostings(b...)
+	i3 := newListPostings(c...)
+	i4 := newListPostings(d...)
 
 	t.ResetTimer()
 
@@ -172,60 +254,117 @@ func BenchmarkIntersect(t *testing.B) {
 }
 
 func TestMultiMerge(t *testing.T) {
-	var cases = []struct {
-		a, b, c []uint64
-		res     []uint64
-	}{
-		{
-			a:   []uint64{1, 2, 3, 4, 5, 6, 1000, 1001},
-			b:   []uint64{2, 4, 5, 6, 7, 8, 999, 1001},
-			c:   []uint64{1, 2, 5, 6, 7, 8, 1001, 1200},
-			res: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 999, 1000, 1001, 1200},
-		},
-	}
+	i1 := newListPostings(1, 2, 3, 4, 5, 6, 1000, 1001)
+	i2 := newListPostings(2, 4, 5, 6, 7, 8, 999, 1001)
+	i3 := newListPostings(1, 2, 5, 6, 7, 8, 1001, 1200)
 
-	for _, c := range cases {
-		i1 := newListPostings(c.a)
-		i2 := newListPostings(c.b)
-		i3 := newListPostings(c.c)
-
-		res, err := ExpandPostings(Merge(i1, i2, i3))
-		testutil.Ok(t, err)
-		testutil.Equals(t, c.res, res)
-	}
+	res, err := ExpandPostings(Merge(i1, i2, i3))
+	testutil.Ok(t, err)
+	testutil.Equals(t, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 999, 1000, 1001, 1200}, res)
 }
 
 func TestMergedPostings(t *testing.T) {
 	var cases = []struct {
-		a, b []uint64
-		res  []uint64
+		in []Postings
+
+		res Postings
 	}{
 		{
-			a:   []uint64{1, 2, 3, 4, 5},
-			b:   []uint64{6, 7, 8, 9, 10},
-			res: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
+			in:  []Postings{},
+			res: EmptyPostings(),
 		},
 		{
-			a:   []uint64{1, 2, 3, 4, 5},
-			b:   []uint64{4, 5, 6, 7, 8},
-			res: []uint64{1, 2, 3, 4, 5, 6, 7, 8},
+			in: []Postings{
+				newListPostings(),
+				newListPostings(),
+			},
+			res: EmptyPostings(),
 		},
 		{
-			a:   []uint64{1, 2, 3, 4, 9, 10},
-			b:   []uint64{1, 4, 5, 6, 7, 8, 10, 11},
-			res: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
+			in: []Postings{
+				newListPostings(),
+			},
+			res: newListPostings(),
+		},
+		{
+			in: []Postings{
+				EmptyPostings(),
+				EmptyPostings(),
+				EmptyPostings(),
+				EmptyPostings(),
+			},
+			res: EmptyPostings(),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2, 3, 4, 5),
+				newListPostings(6, 7, 8, 9, 10),
+			},
+			res: newListPostings(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2, 3, 4, 5),
+				newListPostings(4, 5, 6, 7, 8),
+			},
+			res: newListPostings(1, 2, 3, 4, 5, 6, 7, 8),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2, 3, 4, 9, 10),
+				newListPostings(1, 4, 5, 6, 7, 8, 10, 11),
+			},
+			res: newListPostings(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2, 3, 4, 9, 10),
+				EmptyPostings(),
+				newListPostings(1, 4, 5, 6, 7, 8, 10, 11),
+			},
+			res: newListPostings(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2),
+				newListPostings(),
+			},
+			res: newListPostings(1, 2),
+		},
+		{
+			in: []Postings{
+				newListPostings(1, 2),
+				EmptyPostings(),
+			},
+			res: newListPostings(1, 2),
 		},
 	}
 
 	for _, c := range cases {
-		a := newListPostings(c.a)
-		b := newListPostings(c.b)
+		t.Run("", func(t *testing.T) {
+			if c.res == nil {
+				t.Fatal("merge result expectancy cannot be nil")
+			}
 
-		res, err := ExpandPostings(Merge(a, b))
-		testutil.Ok(t, err)
-		testutil.Equals(t, c.res, res)
-	}
+			expected, err := ExpandPostings(c.res)
+			testutil.Ok(t, err)
+
+			m := Merge(c.in...)
 
+			if c.res == EmptyPostings() {
+				testutil.Equals(t, EmptyPostings(), m)
+				return
+			}
+
+			if m == EmptyPostings() {
+				t.Fatal("merge unexpected result: EmptyPostings sentinel")
+			}
+
+			res, err := ExpandPostings(m)
+			testutil.Ok(t, err)
+			testutil.Equals(t, expected, res)
+		})
+	}
 }
 
 func TestMergedPostingsSeek(t *testing.T) {
@@ -271,8 +410,8 @@ func TestMergedPostingsSeek(t *testing.T) {
 	}
 
 	for _, c := range cases {
-		a := newListPostings(c.a)
-		b := newListPostings(c.b)
+		a := newListPostings(c.a...)
+		b := newListPostings(c.b...)
 
 		p := Merge(a, b)
 
@@ -333,8 +472,8 @@ func TestRemovedPostings(t *testing.T) {
 	}
 
 	for _, c := range cases {
-		a := newListPostings(c.a)
-		b := newListPostings(c.b)
+		a := newListPostings(c.a...)
+		b := newListPostings(c.b...)
 
 		res, err := ExpandPostings(newRemovedPostings(a, b))
 		testutil.Ok(t, err)
@@ -353,8 +492,8 @@ func TestRemovedNextStackoverflow(t *testing.T) {
 		remove = append(remove, i)
 	}
 
-	flp := newListPostings(full)
-	rlp := newListPostings(remove)
+	flp := newListPostings(full...)
+	rlp := newListPostings(remove...)
 	rp := newRemovedPostings(flp, rlp)
 	gotElem := false
 	for rp.Next() {
@@ -432,8 +571,8 @@ func TestRemovedPostingsSeek(t *testing.T) {
 	}
 
 	for _, c := range cases {
-		a := newListPostings(c.a)
-		b := newListPostings(c.b)
+		a := newListPostings(c.a...)
+		b := newListPostings(c.b...)
 
 		p := newRemovedPostings(a, b)
 
@@ -526,13 +665,13 @@ func TestBigEndian(t *testing.T) {
 }
 
 func TestIntersectWithMerge(t *testing.T) {
-	// One of the reproduceable cases for:
+	// One of the reproducible cases for:
 	// https://github.com/prometheus/prometheus/issues/2616
-	a := newListPostings([]uint64{21, 22, 23, 24, 25, 30})
+	a := newListPostings(21, 22, 23, 24, 25, 30)
 
 	b := Merge(
-		newListPostings([]uint64{10, 20, 30}),
-		newListPostings([]uint64{15, 26, 30}),
+		newListPostings(10, 20, 30),
+		newListPostings(15, 26, 30),
 	)
 
 	p := Intersect(a, b)
@@ -541,3 +680,81 @@ func TestIntersectWithMerge(t *testing.T) {
 	testutil.Ok(t, err)
 	testutil.Equals(t, []uint64{30}, res)
 }
+
+func TestWithoutPostings(t *testing.T) {
+	var cases = []struct {
+		base Postings
+		drop Postings
+
+		res Postings
+	}{
+		{
+			base: EmptyPostings(),
+			drop: EmptyPostings(),
+
+			res: EmptyPostings(),
+		},
+		{
+			base: EmptyPostings(),
+			drop: newListPostings(1, 2),
+
+			res: EmptyPostings(),
+		},
+		{
+			base: newListPostings(1, 2),
+			drop: EmptyPostings(),
+
+			res: newListPostings(1, 2),
+		},
+		{
+			base: newListPostings(),
+			drop: newListPostings(),
+
+			res: newListPostings(),
+		},
+		{
+			base: newListPostings(1, 2, 3),
+			drop: newListPostings(),
+
+			res: newListPostings(1, 2, 3),
+		},
+		{
+			base: newListPostings(1, 2, 3),
+			drop: newListPostings(4, 5, 6),
+
+			res: newListPostings(1, 2, 3),
+		},
+		{
+			base: newListPostings(1, 2, 3),
+			drop: newListPostings(3, 4, 5),
+
+			res: newListPostings(1, 2),
+		},
+	}
+
+	for _, c := range cases {
+		t.Run("", func(t *testing.T) {
+			if c.res == nil {
+				t.Fatal("without result expectancy cannot be nil")
+			}
+
+			expected, err := ExpandPostings(c.res)
+			testutil.Ok(t, err)
+
+			w := Without(c.base, c.drop)
+
+			if c.res == EmptyPostings() {
+				testutil.Equals(t, EmptyPostings(), w)
+				return
+			}
+
+			if w == EmptyPostings() {
+				t.Fatal("without unexpected result: EmptyPostings sentinel")
+			}
+
+			res, err := ExpandPostings(w)
+			testutil.Ok(t, err)
+			testutil.Equals(t, expected, res)
+		})
+	}
+}
diff --git a/querier_test.go b/querier_test.go
index ee426d0..0ce114d 100644
--- a/querier_test.go
+++ b/querier_test.go
@@ -1232,7 +1232,9 @@ func BenchmarkPersistedQueries(b *testing.B) {
 			b.Run(fmt.Sprintf("series=%d,samplesPerSeries=%d", nSeries, nSamples), func(b *testing.B) {
 				dir, err := ioutil.TempDir("", "bench_persisted")
 				testutil.Ok(b, err)
-				defer os.RemoveAll(dir)
+				defer func() {
+					testutil.Ok(b, os.RemoveAll(dir))
+				}()
 
 				block, err := OpenBlock(nil, createBlock(b, dir, genSeries(nSeries, 10, 1, int64(nSamples))), nil)
 				testutil.Ok(b, err)
diff --git a/repair_test.go b/repair_test.go
index cbe2169..19b663f 100644
--- a/repair_test.go
+++ b/repair_test.go
@@ -70,7 +70,9 @@ func TestRepairBadIndexVersion(t *testing.T) {
 
 	// Touch chunks dir in block.
 	os.MkdirAll(filepath.Join(dbDir, "chunks"), 0777)
-	defer os.RemoveAll(filepath.Join(dbDir, "chunks"))
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(filepath.Join(dbDir, "chunks")))
+	}()
 
 	r, err := index.NewFileReader(filepath.Join(dbDir, indexFilename))
 	testutil.Ok(t, err)
@@ -89,7 +91,9 @@ func TestRepairBadIndexVersion(t *testing.T) {
 	if err = fileutil.CopyDirs(dbDir, tmpDbDir); err != nil {
 		t.Fatal(err)
 	}
-	defer os.RemoveAll(tmpDir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(tmpDir))
+	}()
 	// On DB opening all blocks in the base dir should be repaired.
 	db, err := Open(tmpDir, nil, nil, nil)
 	testutil.Ok(t, err)
@@ -97,6 +101,7 @@ func TestRepairBadIndexVersion(t *testing.T) {
 
 	r, err = index.NewFileReader(filepath.Join(tmpDbDir, indexFilename))
 	testutil.Ok(t, err)
+	defer r.Close()
 	p, err = r.Postings("b", "1")
 	testutil.Ok(t, err)
 	res := []labels.Labels{}
diff --git a/tombstones_test.go b/tombstones_test.go
index 95ef42f..80d5ef2 100644
--- a/tombstones_test.go
+++ b/tombstones_test.go
@@ -26,7 +26,9 @@ import (
 
 func TestWriteAndReadbackTombStones(t *testing.T) {
 	tmpdir, _ := ioutil.TempDir("", "test")
-	defer os.RemoveAll(tmpdir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(tmpdir))
+	}()
 
 	ref := uint64(0)
 
diff --git a/wal/reader_test.go b/wal/reader_test.go
index d782ae8..1178aa5 100644
--- a/wal/reader_test.go
+++ b/wal/reader_test.go
@@ -30,6 +30,7 @@ import (
 	"time"
 
 	"github.com/go-kit/kit/log"
+	tsdb_errors "github.com/prometheus/tsdb/errors"
 	"github.com/prometheus/tsdb/testutil"
 )
 
@@ -268,21 +269,43 @@ func generateRandomEntries(w *WAL, records chan []byte) error {
 	return w.Log(recs...)
 }
 
-func allSegments(dir string) (io.Reader, error) {
+type multiReadCloser struct {
+	reader  io.Reader
+	closers []io.Closer
+}
+
+func (m *multiReadCloser) Read(p []byte) (n int, err error) {
+	return m.reader.Read(p)
+}
+func (m *multiReadCloser) Close() error {
+	var merr tsdb_errors.MultiError
+	for _, closer := range m.closers {
+		merr.Add(closer.Close())
+	}
+	return merr.Err()
+}
+
+func allSegments(dir string) (io.ReadCloser, error) {
 	seg, err := listSegments(dir)
 	if err != nil {
 		return nil, err
 	}
 
 	var readers []io.Reader
+	var closers []io.Closer
 	for _, r := range seg {
 		f, err := os.Open(filepath.Join(dir, r.name))
 		if err != nil {
 			return nil, err
 		}
 		readers = append(readers, f)
+		closers = append(closers, f)
 	}
-	return io.MultiReader(readers...), nil
+
+	return &multiReadCloser{
+		reader:  io.MultiReader(readers...),
+		closers: closers,
+	}, nil
 }
 
 func TestReaderFuzz(t *testing.T) {
@@ -290,7 +313,9 @@ func TestReaderFuzz(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			dir, err := ioutil.TempDir("", "wal_fuzz_live")
 			testutil.Ok(t, err)
-			defer os.RemoveAll(dir)
+			defer func() {
+				testutil.Ok(t, os.RemoveAll(dir))
+			}()
 
 			w, err := NewSize(nil, nil, dir, 128*pageSize)
 			testutil.Ok(t, err)
@@ -306,6 +331,7 @@ func TestReaderFuzz(t *testing.T) {
 
 			sr, err := allSegments(w.Dir())
 			testutil.Ok(t, err)
+			defer sr.Close()
 
 			reader := fn(sr)
 			for expected := range input {
@@ -321,10 +347,13 @@ func TestReaderFuzz_Live(t *testing.T) {
 	logger := testutil.NewLogger(t)
 	dir, err := ioutil.TempDir("", "wal_fuzz_live")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	w, err := NewSize(nil, nil, dir, 128*pageSize)
 	testutil.Ok(t, err)
+	defer w.Close()
 
 	// In the background, generate a stream of random records and write them
 	// to the WAL.
@@ -343,6 +372,7 @@ func TestReaderFuzz_Live(t *testing.T) {
 
 	seg, err := OpenReadSegment(SegmentName(dir, m))
 	testutil.Ok(t, err)
+	defer seg.Close()
 
 	r := NewLiveReader(logger, seg)
 	segmentTicker := time.NewTicker(100 * time.Millisecond)
@@ -379,6 +409,7 @@ outer:
 
 			seg, err = OpenReadSegment(SegmentName(dir, seg.i+1))
 			testutil.Ok(t, err)
+			defer seg.Close()
 			r = NewLiveReader(logger, seg)
 
 		case <-readTicker.C:
@@ -399,7 +430,9 @@ func TestLiveReaderCorrupt_ShortFile(t *testing.T) {
 	logger := testutil.NewLogger(t)
 	dir, err := ioutil.TempDir("", "wal_live_corrupt")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	w, err := NewSize(nil, nil, dir, pageSize)
 	testutil.Ok(t, err)
@@ -429,6 +462,7 @@ func TestLiveReaderCorrupt_ShortFile(t *testing.T) {
 
 	seg, err := OpenReadSegment(SegmentName(dir, m))
 	testutil.Ok(t, err)
+	defer seg.Close()
 
 	r := NewLiveReader(logger, seg)
 	testutil.Assert(t, r.Next() == false, "expected no records")
@@ -440,7 +474,9 @@ func TestLiveReaderCorrupt_RecordTooLongAndShort(t *testing.T) {
 	logger := testutil.NewLogger(t)
 	dir, err := ioutil.TempDir("", "wal_live_corrupt")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	w, err := NewSize(nil, nil, dir, pageSize*2)
 	testutil.Ok(t, err)
@@ -474,6 +510,7 @@ func TestLiveReaderCorrupt_RecordTooLongAndShort(t *testing.T) {
 
 	seg, err := OpenReadSegment(SegmentName(dir, m))
 	testutil.Ok(t, err)
+	defer seg.Close()
 
 	r := NewLiveReader(logger, seg)
 	testutil.Assert(t, r.Next() == false, "expected no records")
diff --git a/wal/wal_test.go b/wal/wal_test.go
index 16d2775..e7b893a 100644
--- a/wal/wal_test.go
+++ b/wal/wal_test.go
@@ -109,7 +109,9 @@ func TestWAL_Repair(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			dir, err := ioutil.TempDir("", "wal_repair")
 			testutil.Ok(t, err)
-			defer os.RemoveAll(dir)
+			defer func() {
+				testutil.Ok(t, os.RemoveAll(dir))
+			}()
 
 			// We create 3 segments with 3 records each and
 			// then corrupt a given record in a given segment.
@@ -138,6 +140,7 @@ func TestWAL_Repair(t *testing.T) {
 
 			w, err = NewSize(nil, nil, dir, segSize)
 			testutil.Ok(t, err)
+			defer w.Close()
 
 			sr, err := NewSegmentsReader(dir)
 			testutil.Ok(t, err)
@@ -151,6 +154,7 @@ func TestWAL_Repair(t *testing.T) {
 			testutil.Ok(t, w.Repair(r.Err()))
 			sr, err = NewSegmentsReader(dir)
 			testutil.Ok(t, err)
+			defer sr.Close()
 			r = NewReader(sr)
 
 			var result [][]byte
@@ -181,7 +185,9 @@ func TestWAL_Repair(t *testing.T) {
 func TestCorruptAndCarryOn(t *testing.T) {
 	dir, err := ioutil.TempDir("", "wal_repair")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	var (
 		logger      = testutil.NewLogger(t)
@@ -295,13 +301,16 @@ func TestCorruptAndCarryOn(t *testing.T) {
 		testutil.Equals(t, 9, i, "wrong number of records")
 		testutil.Assert(t, !reader.Next(), "unexpected record")
 		testutil.Equals(t, nil, reader.Err())
+		sr.Close()
 	}
 }
 
 func BenchmarkWAL_LogBatched(b *testing.B) {
 	dir, err := ioutil.TempDir("", "bench_logbatch")
 	testutil.Ok(b, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(b, os.RemoveAll(dir))
+	}()
 
 	w, err := New(nil, nil, "testdir")
 	testutil.Ok(b, err)
@@ -329,7 +338,9 @@ func BenchmarkWAL_LogBatched(b *testing.B) {
 func BenchmarkWAL_Log(b *testing.B) {
 	dir, err := ioutil.TempDir("", "bench_logsingle")
 	testutil.Ok(b, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(b, os.RemoveAll(dir))
+	}()
 
 	w, err := New(nil, nil, "testdir")
 	testutil.Ok(b, err)
diff --git a/wal_test.go b/wal_test.go
index fcda65b..7b9f965 100644
--- a/wal_test.go
+++ b/wal_test.go
@@ -36,7 +36,9 @@ import (
 func TestSegmentWAL_cut(t *testing.T) {
 	tmpdir, err := ioutil.TempDir("", "test_wal_cut")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(tmpdir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(tmpdir))
+	}()
 
 	// This calls cut() implicitly the first time without a previous tail.
 	w, err := OpenSegmentWAL(tmpdir, nil, 0, nil)
@@ -84,7 +86,9 @@ func TestSegmentWAL_Truncate(t *testing.T) {
 
 	dir, err := ioutil.TempDir("", "test_wal_log_truncate")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	w, err := OpenSegmentWAL(dir, nil, 0, nil)
 	testutil.Ok(t, err)
@@ -163,7 +167,9 @@ func TestSegmentWAL_Log_Restore(t *testing.T) {
 
 	dir, err := ioutil.TempDir("", "test_wal_log_restore")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	var (
 		recordedSeries  [][]RefSeries
@@ -270,7 +276,9 @@ func TestSegmentWAL_Log_Restore(t *testing.T) {
 func TestWALRestoreCorrupted_invalidSegment(t *testing.T) {
 	dir, err := ioutil.TempDir("", "test_wal_log_restore")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	wal, err := OpenSegmentWAL(dir, nil, 0, nil)
 	testutil.Ok(t, err)
@@ -367,7 +375,9 @@ func TestWALRestoreCorrupted(t *testing.T) {
 			// for the purpose of this test.
 			dir, err := ioutil.TempDir("", "test_corrupted")
 			testutil.Ok(t, err)
-			defer os.RemoveAll(dir)
+			defer func() {
+				testutil.Ok(t, os.RemoveAll(dir))
+			}()
 
 			w, err := OpenSegmentWAL(dir, nil, 0, nil)
 			testutil.Ok(t, err)
@@ -442,7 +452,9 @@ func TestMigrateWAL_Empty(t *testing.T) {
 	// which is valid in the new format.
 	dir, err := ioutil.TempDir("", "walmigrate")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	wdir := path.Join(dir, "wal")
 
@@ -457,7 +469,9 @@ func TestMigrateWAL_Empty(t *testing.T) {
 func TestMigrateWAL_Fuzz(t *testing.T) {
 	dir, err := ioutil.TempDir("", "walmigrate")
 	testutil.Ok(t, err)
-	defer os.RemoveAll(dir)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
 	wdir := path.Join(dir, "wal")