Repository: prometheus/prometheus
Pull request #7225 (2020-05-07 16:49:46)

[Merge without Squash] Merge release-2.18 back to master for 2.18.1 fixes.


Patch:
diff --git a/.github/workflows/funcbench.yml b/.github/workflows/funcbench.yml
deleted file mode 100644
index ea0eac365..000000000
--- a/.github/workflows/funcbench.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-on: repository_dispatch
-name: Funcbench Workflow
-jobs:
-  run_funcbench:
-    name: Running funcbench
-    if: github.event.action == 'funcbench_start'
-    runs-on: ubuntu-latest
-    env:
-      AUTH_FILE: ${{ secrets.TEST_INFRA_GKE_AUTH }}
-      BRANCH: ${{ github.event.client_payload.BRANCH }}
-      BENCH_FUNC_REGEX: ${{ github.event.client_payload.BENCH_FUNC_REGEX }}
-      CLUSTER_NAME: test-infra
-      GITHUB_TOKEN: ${{ secrets.PROMBOT_TOKEN }}
-      GITHUB_ORG: prometheus
-      GITHUB_REPO: prometheus
-      GITHUB_STATUS_TARGET_URL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
-      LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
-      PROJECT_ID: macro-mile-203600
-      PR_NUMBER: ${{ github.event.client_payload.PR_NUMBER }}
-      ZONE: europe-west3-a
-    steps:
-    - name: Update status to pending
-      run: >-
-        curl -i -X POST
-        -H "Authorization: Bearer $GITHUB_TOKEN"
-        -H "Content-Type: application/json"
-        --data '{"state":"pending","context":"funcbench-status","target_url":"'$GITHUB_STATUS_TARGET_URL'"}'
-        "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
-    - name: Prepare nodepool
-      uses: docker://prominfra/prombench:master
-      with:
-        args: make funcbench_nodepool_create
-    - name: Run funcbench
-      uses: docker://prominfra/prombench:master
-      with:
-        args: make funcbench_resource_apply
-    - name: Delete all resources
-      if: always()
-      uses: docker://prominfra/prombench:master
-      with:
-        args: make funcbench_resource_delete; make funcbench_nodepool_delete
-    - name: Update status to failure
-      if: failure()
-      run: >-
-        curl -i -X POST
-        -H "Authorization: Bearer $GITHUB_TOKEN"
-        -H "Content-Type: application/json"
-        --data '{"state":"failure","context":"funcbench-status","target_url":"'$GITHUB_STATUS_TARGET_URL'"}'
-        "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
-    - name: Update status to success
-      if: success()
-      run: >-
-        curl -i -X POST
-        -H "Authorization: Bearer $GITHUB_TOKEN"
-        -H "Content-Type: application/json"
-        --data '{"state":"success","context":"funcbench-status","target_url":"'$GITHUB_STATUS_TARGET_URL'"}'
-        "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
diff --git a/.github/workflows/prombench.yml b/.github/workflows/prombench.yml
index ec7f38051..0bbda79bd 100644
--- a/.github/workflows/prombench.yml
+++ b/.github/workflows/prombench.yml
@@ -1,19 +1,5 @@
 on: repository_dispatch
 name: Prombench Workflow
-env:
-  AUTH_FILE: ${{ secrets.TEST_INFRA_GKE_AUTH }}
-  CLUSTER_NAME: test-infra
-  DOMAIN_NAME: prombench.prometheus.io
-  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-  GITHUB_ORG: prometheus
-  GITHUB_REPO: prometheus
-  GITHUB_STATUS_TARGET_URL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
-  LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
-  PROJECT_ID: macro-mile-203600
-  PR_NUMBER: ${{ github.event.client_payload.PR_NUMBER }}
-  RELEASE: ${{ github.event.client_payload.RELEASE }}
-  TEST_INFRA_REPO: https://github.com/prometheus/test-infra.git
-  ZONE: europe-west3-a
 jobs:
   benchmark_start:
     name: Benchmark Start
@@ -21,85 +7,141 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Update status to pending
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"pending",  "context": "prombench-status-update-start", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
+        --data '{"state":"pending",  "context": "prombench-status-update-start", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
     - name: Run make deploy to start test
       id: make_deploy
       uses: docker://prominfra/prombench:master
+      env:
+        AUTH_FILE: ${{ secrets.PROMBENCH_GKE_AUTH }}
+        PROJECT_ID: macro-mile-203600
+        CLUSTER_NAME: prombench
+        ZONE: europe-west3-a
+        DOMAIN_NAME: prombench.prometheus.io
+        TEST_INFRA_REPO: https://github.com/prometheus/test-infra.git
+        GITHUB_ORG: prometheus
+        GITHUB_REPO: prometheus
+        PR_NUMBER: ${{ github.event.client_payload.PR_NUMBER }}
+        RELEASE: ${{ github.event.client_payload.RELEASE }}
       with:
         args: >-
           until make all_nodepools_deleted; do echo "waiting for nodepools to be deleted"; sleep 10; done;
           make deploy;
     - name: Update status to failure
       if: failure()
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"failure",  "context": "prombench-status-update-start", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
+        --data '{"state":"failure",  "context": "prombench-status-update-start", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
     - name: Update status to success
       if: success()
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"success",  "context": "prombench-status-update-start", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
+        --data '{"state":"success",  "context": "prombench-status-update-start", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
+  #############################
+  # Jobs for stopping benchmark
+  #############################
   benchmark_cancel:
     name: Benchmark Cancel
     if: github.event.action == 'prombench_stop'
     runs-on: ubuntu-latest
     steps:
     - name: Update status to pending
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"pending",  "context": "prombench-status-update-cancel", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
+        --data '{"state":"pending",  "context": "prombench-status-update-cancel", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
     - name: Run make clean to stop test
       id: make_clean
       uses: docker://prominfra/prombench:master
+      env:
+        AUTH_FILE: ${{ secrets.PROMBENCH_GKE_AUTH }}
+        PROJECT_ID: macro-mile-203600
+        CLUSTER_NAME: prombench
+        ZONE: europe-west3-a
+        TEST_INFRA_REPO: https://github.com/prometheus/test-infra.git
+        PR_NUMBER: ${{ github.event.client_payload.PR_NUMBER }}
       with:
         args: >-
           until make all_nodepools_running; do echo "waiting for nodepools to be created"; sleep 10; done;
           make clean;
     - name: Update status to failure
       if: failure()
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"failure",  "context": "prombench-status-update-cancel", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
+        --data '{"state":"failure",  "context": "prombench-status-update-cancel", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
     - name: Update status to success
       if: success()
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"success",  "context": "prombench-status-update-cancel", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
+        --data '{"state":"success",  "context": "prombench-status-update-cancel", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
+  ###############################
+  # Jobs for restarting benchmark
+  ###############################
   benchmark_restart:
     name: Benchmark Restart
     if: github.event.action == 'prombench_restart'
     runs-on: ubuntu-latest
     steps:
     - name: Update status to pending
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"pending",  "context": "prombench-status-update-restart", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
+        --data '{"state":"pending",  "context": "prombench-status-update-restart", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
     - name: Run make clean then make deploy to restart test
       id: make_restart
       uses: docker://prominfra/prombench:master
+      env:
+        AUTH_FILE: ${{ secrets.PROMBENCH_GKE_AUTH }}
+        PROJECT_ID: macro-mile-203600
+        CLUSTER_NAME: prombench
+        ZONE: europe-west3-a
+        DOMAIN_NAME: prombench.prometheus.io
+        TEST_INFRA_REPO: https://github.com/prometheus/test-infra.git
+        GITHUB_ORG: prometheus
+        GITHUB_REPO: prometheus
+        PR_NUMBER: ${{ github.event.client_payload.PR_NUMBER }}
+        RELEASE: ${{ github.event.client_payload.RELEASE }}
       with:
         args: >-
           until make all_nodepools_running; do echo "waiting for nodepools to be created"; sleep 10; done;
@@ -108,17 +150,23 @@ jobs:
           make deploy;
     - name: Update status to failure
       if: failure()
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"failure",  "context": "prombench-status-update-restart", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
+        --data '{"state":"failure",  "context": "prombench-status-update-restart", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
         "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
     - name: Update status to success
       if: success()
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        LAST_COMMIT_SHA: ${{ github.event.client_payload.LAST_COMMIT_SHA }}
       run: >-
         curl -i -X POST
         -H "Authorization: Bearer $GITHUB_TOKEN"
         -H "Content-Type: application/json"
-        --data '{"state":"success",  "context": "prombench-status-update-restart", "target_url": "'$GITHUB_STATUS_TARGET_URL'"}'
-        "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
+        --data '{"state":"success",  "context": "prombench-status-update-restart", "target_url": "https://github.com/'$GITHUB_REPOSITORY'/actions/runs/'$GITHUB_RUN_ID'"}'
+        "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/$LAST_COMMIT_SHA"
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dfd1ad13c..aec464e7f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.18.1 / 2020-05-07
+
+* [BUGFIX] TSDB: Fixed snapshot API. #7217
+
 ## 2.18.0 / 2020-05-05
 
 * [CHANGE] Federation: Only use local TSDB for federation (ignore remote read). #7077
diff --git a/VERSION b/VERSION
index cf8690732..a36e9b090 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-2.18.0
+2.18.1
diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go
index 2b7038138..a6b5c0ec0 100644
--- a/cmd/prometheus/main.go
+++ b/cmd/prometheus/main.go
@@ -389,6 +389,7 @@ func main() {
 	cfg.web.Context = ctxWeb
 	cfg.web.TSDBRetentionDuration = cfg.tsdb.RetentionDuration
 	cfg.web.TSDBMaxBytes = cfg.tsdb.MaxBytes
+	cfg.web.TSDBDir = cfg.localStoragePath
 	cfg.web.LocalStorage = localStorage
 	cfg.web.Storage = fanoutStorage
 	cfg.web.QueryEngine = queryEngine
diff --git a/cmd/promtool/main.go b/cmd/promtool/main.go
index 41166c962..6c1fbf025 100644
--- a/cmd/promtool/main.go
+++ b/cmd/promtool/main.go
@@ -33,7 +33,6 @@ import (
 	"github.com/prometheus/client_golang/api"
 	v1 "github.com/prometheus/client_golang/api/prometheus/v1"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
-	"github.com/prometheus/client_golang/prometheus/testutil/promlint"
 	config_util "github.com/prometheus/common/config"
 	"github.com/prometheus/common/model"
 	"github.com/prometheus/common/version"
@@ -41,6 +40,7 @@ import (
 
 	"github.com/prometheus/prometheus/config"
 	"github.com/prometheus/prometheus/pkg/rulefmt"
+	"github.com/prometheus/prometheus/util/promlint"
 )
 
 func main() {
diff --git a/discovery/azure/azure.go b/discovery/azure/azure.go
index 2b8635dc2..4666bfca5 100644
--- a/discovery/azure/azure.go
+++ b/discovery/azure/azure.go
@@ -302,9 +302,11 @@ func (d *Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
 				labels[azureLabelMachineScaleSet] = model.LabelValue(vm.ScaleSet)
 			}
 
-			for k, v := range vm.Tags {
-				name := strutil.SanitizeLabelName(k)
-				labels[azureLabelMachineTag+model.LabelName(name)] = model.LabelValue(*v)
+			if vm.Tags != nil {
+				for k, v := range vm.Tags {
+					name := strutil.SanitizeLabelName(k)
+					labels[azureLabelMachineTag+model.LabelName(name)] = model.LabelValue(*v)
+				}
 			}
 
 			// Get the IP address information via separate call to the network provider.
diff --git a/docs/configuration/recording_rules.md b/docs/configuration/recording_rules.md
index 4416988d4..4cf801b97 100644
--- a/docs/configuration/recording_rules.md
+++ b/docs/configuration/recording_rules.md
@@ -62,7 +62,7 @@ groups:
   - name: example
     rules:
     - record: job:http_inprogress_requests:sum
-      expr: sum by (job) (http_inprogress_requests)
+      expr: sum(http_inprogress_requests) by (job)
 ```
 
 ### `<rule_group>`
diff --git a/docs/getting_started.md b/docs/getting_started.md
index babb242c9..cfc77b50b 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -118,7 +118,7 @@ For more about the expression language, see the
 To graph expressions, navigate to http://localhost:9090/graph and use the "Graph"
 tab.
 
-For example, enter the following expression to graph the per-second rate of chunks
+For example, enter the following expression to graph the per-second rate of chunks 
 being created in the self-scraped Prometheus:
 
 ```
@@ -132,26 +132,36 @@ Experiment with the graph range parameters and other settings.
 Let us make this more interesting and start some example targets for Prometheus
 to scrape.
 
-The Node Exporter is used as an example target, for more information on using it
-[see these instructions.](https://prometheus.io/docs/guides/node-exporter/)
+The Go client library includes an example which exports fictional RPC latencies
+for three services with different latency distributions.
+
+Ensure you have the [Go compiler installed](https://golang.org/doc/install) and
+have a [working Go build environment](https://golang.org/doc/code.html) (with
+correct `GOPATH`) set up.
+
+Download the Go client library for Prometheus and run three of these example
+processes:
 
 ```bash
-tar -xzvf node_exporter-*.*.tar.gz
-cd node_exporter-*.*
+# Fetch the client library code and compile example.
+git clone https://github.com/prometheus/client_golang.git
+cd client_golang/examples/random
+go get -d
+go build
 
 # Start 3 example targets in separate terminals:
-./node_exporter --web.listen-address 127.0.0.1:8080
-./node_exporter --web.listen-address 127.0.0.1:8081
-./node_exporter --web.listen-address 127.0.0.1:8082
+./random -listen-address=:8080
+./random -listen-address=:8081
+./random -listen-address=:8082
 ```
 
 You should now have example targets listening on http://localhost:8080/metrics,
 http://localhost:8081/metrics, and http://localhost:8082/metrics.
 
-## Configure Prometheus to monitor the sample targets
+## Configuring Prometheus to monitor the sample targets
 
 Now we will configure Prometheus to scrape these new targets. Let's group all
-three endpoints into one job called `node`. However, imagine that the
+three endpoints into one job called `example-random`. However, imagine that the
 first two endpoints are production targets, while the third one represents a
 canary instance. To model this in Prometheus, we can add several groups of
 endpoints to a single job, adding extra labels to each group of targets. In
@@ -163,7 +173,7 @@ section in your `prometheus.yml` and restart your Prometheus instance:
 
 ```yaml
 scrape_configs:
-  - job_name:       'node'
+  - job_name:       'example-random'
 
     # Override the global default and scrape targets from this job every 5 seconds.
     scrape_interval: 5s
@@ -179,7 +189,8 @@ scrape_configs:
 ```
 
 Go to the expression browser and verify that Prometheus now has information
-about time series that these example endpoints expose, such as `node_cpu_seconds_total`.
+about time series that these example endpoints expose, such as the
+`rpc_durations_seconds` metric.
 
 ## Configure rules for aggregating scraped data into new time series
 
@@ -187,26 +198,27 @@ Though not a problem in our example, queries that aggregate over thousands of
 time series can get slow when computed ad-hoc. To make this more efficient,
 Prometheus allows you to prerecord expressions into completely new persisted
 time series via configured recording rules. Let's say we are interested in
-recording the per-second rate of cpu time (`node_cpu_seconds_total`) averaged
-over all cpus per instance (but preserving the `job`, `instance` and `mode`
-dimensions) as measured over a window of 5 minutes. We could write this as:
+recording the per-second rate of example RPCs
+(`rpc_durations_seconds_count`) averaged over all instances (but
+preserving the `job` and `service` dimensions) as measured over a window of 5
+minutes. We could write this as:
 
 ```
-avg by (job, instance, mode) (rate(node_cpu_seconds_total[5m]))
+avg(rate(rpc_durations_seconds_count[5m])) by (job, service)
 ```
 
 Try graphing this expression.
 
 To record the time series resulting from this expression into a new metric
-called `job_instance_mode:node_cpu_seconds:avg_rate5m`, create a file
+called `job_service:rpc_durations_seconds_count:avg_rate5m`, create a file
 with the following recording rule and save it as `prometheus.rules.yml`:
 
 ```
 groups:
-- name: cpu-node
+- name: example
   rules:
-  - record: job_instance_mode:node_cpu_seconds:avg_rate5m
-    expr: avg by (job, instance, mode) (rate(node_cpu_seconds_total[5m]))
+  - record: job_service:rpc_durations_seconds_count:avg_rate5m
+    expr: avg(rate(rpc_durations_seconds_count[5m])) by (job, service)
 ```
 
 To make Prometheus pick up this new rule, add a `rule_files` statement in your `prometheus.yml`. The config should now
@@ -233,7 +245,7 @@ scrape_configs:
     static_configs:
       - targets: ['localhost:9090']
 
-  - job_name:       'node'
+  - job_name:       'example-random'
 
     # Override the global default and scrape targets from this job every 5 seconds.
     scrape_interval: 5s
@@ -249,5 +261,5 @@ scrape_configs:
 ```
 
 Restart Prometheus with the new configuration and verify that a new time series
-with the metric name `job_instance_mode:node_cpu_seconds:avg_rate5m`
+with the metric name `job_service:rpc_durations_seconds_count:avg_rate5m`
 is now available by querying it through the expression browser or graphing it.
diff --git a/docs/migration.md b/docs/migration.md
index 3bc18ddd5..2c1a8848b 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -88,7 +88,7 @@ An example of a recording rule and alert in the old format:
 
 ```
 job:request_duration_seconds:histogram_quantile99 =
-  histogram_quantile(0.99, sum by (le, job) (rate(request_duration_seconds_bucket[1m])))
+  histogram_quantile(0.99, sum(rate(request_duration_seconds_bucket[1m])) by (le, job))
 
 ALERT FrontendRequestLatency
   IF job:request_duration_seconds:histogram_quantile99{job="frontend"} > 0.1
@@ -105,7 +105,8 @@ groups:
 - name: example.rules
   rules:
   - record: job:request_duration_seconds:histogram_quantile99
-    expr: histogram_quantile(0.99, sum by (le, job) (rate(request_duration_seconds_bucket[1m])))
+    expr: histogram_quantile(0.99, sum(rate(request_duration_seconds_bucket[1m]))
+      BY (le, job))
   - alert: FrontendRequestLatency
     expr: job:request_duration_seconds:histogram_quantile99{job="frontend"} > 0.1
     for: 5m
diff --git a/docs/querying/api.md b/docs/querying/api.md
index bd1c67987..60827aa7a 100644
--- a/docs/querying/api.md
+++ b/docs/querying/api.md
@@ -523,7 +523,7 @@ $ curl http://localhost:9090/api/v1/rules
                     {
                         "health": "ok",
                         "name": "job:http_inprogress_requests:sum",
-                        "query": "sum by (job) (http_inprogress_requests)",
+                        "query": "sum(http_inprogress_requests) by (job)",
                         "type": "recording"
                     }
                 ],
diff --git a/docs/querying/functions.md b/docs/querying/functions.md
index 419823430..7481e71ed 100644
--- a/docs/querying/functions.md
+++ b/docs/querying/functions.md
@@ -166,11 +166,11 @@ around the `rate()` function. Since the `le` label is required by
 `histogram_quantile()`, it has to be included in the `by` clause. The following
 expression aggregates the 90th percentile by `job`:
 
-    histogram_quantile(0.9, sum by (job, le) (rate(http_request_duration_seconds_bucket[10m])))
+    histogram_quantile(0.9, sum(rate(http_request_duration_seconds_bucket[10m])) by (job, le))
 
 To aggregate everything, specify only the `le` label:
 
-    histogram_quantile(0.9, sum by (le) (rate(http_request_duration_seconds_bucket[10m])))
+    histogram_quantile(0.9, sum(rate(http_request_duration_seconds_bucket[10m])) by (le))
 
 The `histogram_quantile()` function interpolates quantile values by
 assuming a linear distribution within a bucket. The highest bucket
diff --git a/go.mod b/go.mod
index 251115b92..0b5a585db 100644
--- a/go.mod
+++ b/go.mod
@@ -26,6 +26,7 @@ require (
 	github.com/go-openapi/swag v0.19.9 // indirect
 	github.com/go-openapi/validate v0.19.8 // indirect
 	github.com/gogo/protobuf v1.3.1
+	github.com/golang/protobuf v1.4.0 // indirect
 	github.com/golang/snappy v0.0.1
 	github.com/google/pprof v0.0.0-20200417002340-c6e0a841f49a
 	github.com/googleapis/gnostic v0.4.0 // indirect
@@ -51,9 +52,10 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/pmezard/go-difflib v1.0.0
 	github.com/prometheus/alertmanager v0.20.0
-	github.com/prometheus/client_golang v1.6.0
+	github.com/prometheus/client_golang v1.5.1
 	github.com/prometheus/client_model v0.2.0
 	github.com/prometheus/common v0.9.1
+	github.com/prometheus/procfs v0.0.11 // indirect
 	github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da
 	github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
 	github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
diff --git a/go.sum b/go.sum
index 1c02f205f..7941bbe62 100644
--- a/go.sum
+++ b/go.sum
@@ -653,8 +653,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
 github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
-github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A=
-github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
+github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA=
+github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
diff --git a/storage/remote/client.go b/storage/remote/client.go
index 55ceb2341..16f67484d 100644
--- a/storage/remote/client.go
+++ b/storage/remote/client.go
@@ -27,12 +27,12 @@ import (
 	"github.com/gogo/protobuf/proto"
 	"github.com/golang/snappy"
 	"github.com/opentracing/opentracing-go"
+	"github.com/opentracing/opentracing-go/ext"
 	"github.com/pkg/errors"
 	config_util "github.com/prometheus/common/config"
 	"github.com/prometheus/common/model"
 	"github.com/prometheus/common/version"
 
-	"github.com/opentracing-contrib/go-stdlib/nethttp"
 	"github.com/prometheus/prometheus/prompb"
 )
 
@@ -62,11 +62,6 @@ func NewClient(remoteName string, conf *ClientConfig) (*Client, error) {
 		return nil, err
 	}
 
-	t := httpClient.Transport
-	httpClient.Transport = &nethttp.Transport{
-		RoundTripper: t,
-	}
-
 	return &Client{
 		remoteName: remoteName,
 		url:        conf.URL,
@@ -156,23 +151,21 @@ func (c *Client) Read(ctx context.Context, query *prompb.Query) (*prompb.QueryRe
 	httpReq.Header.Set("User-Agent", userAgent)
 	httpReq.Header.Set("X-Prometheus-Remote-Read-Version", "0.1.0")
 
-	ctx, cancel := context.WithTimeout(ctx, c.timeout)
-	defer cancel()
-
-	httpReq = httpReq.WithContext(ctx)
-
-	if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil {
-		var ht *nethttp.Tracer
-		httpReq, ht = nethttp.TraceRequest(
-			parentSpan.Tracer(),
-			httpReq,
-			nethttp.OperationName("Remote Read"),
-			nethttp.ClientTrace(false),
+	if span := opentracing.SpanFromContext(ctx); span != nil {
+		ext.SpanKindRPCClient.Set(span)
+		ext.HTTPUrl.Set(span, httpReq.URL.String())
+		ext.HTTPMethod.Set(span, httpReq.Method)
+		_ = span.Tracer().Inject(
+			span.Context(),
+			opentracing.HTTPHeaders,
+			opentracing.HTTPHeadersCarrier(httpReq.Header),
 		)
-		defer ht.Finish()
 	}
 
-	httpResp, err := c.client.Do(httpReq)
+	ctx, cancel := context.WithTimeout(ctx, c.timeout)
+	defer cancel()
+
+	httpResp, err := c.client.Do(httpReq.WithContext(ctx))
 	if err != nil {
 		return nil, errors.Wrap(err, "error sending request")
 	}
diff --git a/storage/remote/queue_manager.go b/storage/remote/queue_manager.go
index fbdd28d8d..16bc0da14 100644
--- a/storage/remote/queue_manager.go
+++ b/storage/remote/queue_manager.go
@@ -46,134 +46,152 @@ const (
 )
 
 type queueManagerMetrics struct {
-	reg prometheus.Registerer
-
-	succeededSamplesTotal prometheus.Counter
-	failedSamplesTotal    prometheus.Counter
-	retriedSamplesTotal   prometheus.Counter
-	droppedSamplesTotal   prometheus.Counter
-	enqueueRetriesTotal   prometheus.Counter
-	sentBatchDuration     prometheus.Histogram
-	highestSentTimestamp  *maxGauge
-	pendingSamples        prometheus.Gauge
-	shardCapacity         prometheus.Gauge
-	numShards             prometheus.Gauge
-	maxNumShards          prometheus.Gauge
-	minNumShards          prometheus.Gauge
-	desiredNumShards      prometheus.Gauge
-	bytesSent             prometheus.Counter
+	succeededSamplesTotal     *prometheus.CounterVec
+	failedSamplesTotal        *prometheus.CounterVec
+	retriedSamplesTotal       *prometheus.CounterVec
+	droppedSamplesTotal       *prometheus.CounterVec
+	enqueueRetriesTotal       *prometheus.CounterVec
+	sentBatchDuration         *prometheus.HistogramVec
+	queueHighestSentTimestamp *prometheus.GaugeVec
+	queuePendingSamples       *prometheus.GaugeVec
+	shardCapacity             *prometheus.GaugeVec
+	numShards                 *prometheus.GaugeVec
+	maxNumShards              *prometheus.GaugeVec
+	minNumShards              *prometheus.GaugeVec
+	desiredNumShards          *prometheus.GaugeVec
+	bytesSent                 *prometheus.CounterVec
 }
 
-func newQueueManagerMetrics(r prometheus.Registerer, rn, e string) *queueManagerMetrics {
-	m := &queueManagerMetrics{
-		reg: r,
-	}
-	constLabels := prometheus.Labels{
-		remoteName: rn,
-		endpoint:   e,
-	}
-
-	m.succeededSamplesTotal = prometheus.NewCounter(prometheus.CounterOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "succeeded_samples_total",
-		Help:        "Total number of samples successfully sent to remote storage.",
-		ConstLabels: constLabels,
-	})
-	m.failedSamplesTotal = prometheus.NewCounter(prometheus.CounterOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "failed_samples_total",
-		Help:        "Total number of samples which failed on send to remote storage, non-recoverable errors.",
-		ConstLabels: constLabels,
-	})
-	m.retriedSamplesTotal = prometheus.NewCounter(prometheus.CounterOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "retried_samples_total",
-		Help:        "Total number of samples which failed on send to remote storage but were retried because the send error was recoverable.",
-		ConstLabels: constLabels,
-	})
-	m.droppedSamplesTotal = prometheus.NewCounter(prometheus.CounterOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "dropped_samples_total",
-		Help:        "Total number of samples which were dropped after being read from the WAL before being sent via remote write.",
-		ConstLabels: constLabels,
-	})
-	m.enqueueRetriesTotal = prometheus.NewCounter(prometheus.CounterOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "enqueue_retries_total",
-		Help:        "Total number of times enqueue has failed because a shards queue was full.",
-		ConstLabels: constLabels,
-	})
-	m.sentBatchDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "sent_batch_duration_seconds",
-		Help:        "Duration of sample batch send calls to the remote storage.",
-		Buckets:     prometheus.DefBuckets,
-		ConstLabels: constLabels,
-	})
-	m.highestSentTimestamp = &maxGauge{
-		Gauge: prometheus.NewGauge(prometheus.GaugeOpts{
-			Namespace:   namespace,
-			Subsystem:   subsystem,
-			Name:        "queue_highest_sent_timestamp_seconds",
-			Help:        "Timestamp from a WAL sample, the highest timestamp successfully sent by this queue, in seconds since epoch.",
-			ConstLabels: constLabels,
-		}),
-	}
-	m.pendingSamples = prometheus.NewGauge(prometheus.GaugeOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "pending_samples",
-		Help:        "The number of samples pending in the queues shards to be sent to the remote storage.",
-		ConstLabels: constLabels,
-	})
-	m.shardCapacity = prometheus.NewGauge(prometheus.GaugeOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "shard_capacity",
-		Help:        "The capacity of each shard of the queue used for parallel sending to the remote storage.",
-		ConstLabels: constLabels,
-	})
-	m.numShards = prometheus.NewGauge(prometheus.GaugeOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "shards",
-		Help:        "The number of shards used for parallel sending to the remote storage.",
-		ConstLabels: constLabels,
-	})
-	m.maxNumShards = prometheus.NewGauge(prometheus.GaugeOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "shards_max",
-		Help:        "The maximum number of shards that the queue is allowed to run.",
-		ConstLabels: constLabels,
-	})
-	m.minNumShards = prometheus.NewGauge(prometheus.GaugeOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "shards_min",
-		Help:        "The minimum number of shards that the queue is allowed to run.",
-		ConstLabels: constLabels,
-	})
-	m.desiredNumShards = prometheus.NewGauge(prometheus.GaugeOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "shards_desired",
-		Help:        "The number of shards that the queues shard calculation wants to run based on the rate of samples in vs. samples out.",
-		ConstLabels: constLabels,
-	})
-	m.bytesSent = prometheus.NewCounter(prometheus.CounterOpts{
-		Namespace:   namespace,
-		Subsystem:   subsystem,
-		Name:        "sent_bytes_total",
-		Help:        "The total number of bytes sent by the queue.",
-		ConstLabels: constLabels,
-	})
+func newQueueManagerMetrics(r prometheus.Registerer) *queueManagerMetrics {
+	m := &queueManagerMetrics{}
+
+	m.succeededSamplesTotal = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "succeeded_samples_total",
+			Help:      "Total number of samples successfully sent to remote storage.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.failedSamplesTotal = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "failed_samples_total",
+			Help:      "Total number of samples which failed on send to remote storage, non-recoverable errors.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.retriedSamplesTotal = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "retried_samples_total",
+			Help:      "Total number of samples which failed on send to remote storage but were retried because the send error was recoverable.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.droppedSamplesTotal = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "dropped_samples_total",
+			Help:      "Total number of samples which were dropped after being read from the WAL before being sent via remote write.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.enqueueRetriesTotal = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "enqueue_retries_total",
+			Help:      "Total number of times enqueue has failed because a shards queue was full.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.sentBatchDuration = prometheus.NewHistogramVec(
+		prometheus.HistogramOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "sent_batch_duration_seconds",
+			Help:      "Duration of sample batch send calls to the remote storage.",
+			Buckets:   prometheus.DefBuckets,
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.queueHighestSentTimestamp = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "queue_highest_sent_timestamp_seconds",
+			Help:      "Timestamp from a WAL sample, the highest timestamp successfully sent by this queue, in seconds since epoch.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.queuePendingSamples = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "pending_samples",
+			Help:      "The number of samples pending in the queues shards to be sent to the remote storage.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.shardCapacity = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "shard_capacity",
+			Help:      "The capacity of each shard of the queue used for parallel sending to the remote storage.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.numShards = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "shards",
+			Help:      "The number of shards used for parallel sending to the remote storage.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.maxNumShards = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "shards_max",
+			Help:      "The maximum number of shards that the queue is allowed to run.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.minNumShards = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "shards_min",
+			Help:      "The minimum number of shards that the queue is allowed to run.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.desiredNumShards = prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "shards_desired",
+			Help:      "The number of shards that the queues shard calculation wants to run based on the rate of samples in vs. samples out.",
+		},
+		[]string{remoteName, endpoint},
+	)
+	m.bytesSent = prometheus.NewCounterVec(
+		prometheus.CounterOpts{
+			Namespace: namespace,
+			Subsystem: subsystem,
+			Name:      "sent_bytes_total",
+			Help:      "The total number of bytes sent by the queue.",
+		},
+		[]string{remoteName, endpoint},
+	)
 
 	if r != nil {
 		r.MustRegister(
@@ -183,8 +201,8 @@ func newQueueManagerMetrics(r prometheus.Registerer, rn, e string) *queueManager
 			m.droppedSamplesTotal,
 			m.enqueueRetriesTotal,
 			m.sentBatchDuration,
-			m.highestSentTimestamp,
-			m.pendingSamples,
+			m.queueHighestSentTimestamp,
+			m.queuePendingSamples,
 			m.shardCapacity,
 			m.numShards,
 			m.maxNumShards,
@@ -196,25 +214,6 @@ func newQueueManagerMetrics(r prometheus.Registerer, rn, e string) *queueManager
 	return m
 }
 
-func (m *queueManagerMetrics) unregister() {
-	if m.reg != nil {
-		m.reg.Unregister(m.succeededSamplesTotal)
-		m.reg.Unregister(m.failedSamplesTotal)
-		m.reg.Unregister(m.retriedSamplesTotal)
-		m.reg.Unregister(m.droppedSamplesTotal)
-		m.reg.Unregister(m.enqueueRetriesTotal)
-		m.reg.Unregister(m.sentBatchDuration)
-		m.reg.Unregister(m.highestSentTimestamp)
-		m.reg.Unregister(m.pendingSamples)
-		m.reg.Unregister(m.shardCapacity)
-		m.reg.Unregister(m.numShards)
-		m.reg.Unregister(m.maxNumShards)
-		m.reg.Unregister(m.minNumShards)
-		m.reg.Unregister(m.desiredNumShards)
-		m.reg.Unregister(m.bytesSent)
-	}
-}
-
 // StorageClient defines an interface for sending a batch of samples to an
 // external timeseries database.
 type StorageClient interface {
@@ -256,23 +255,25 @@ type QueueManager struct {
 
 	samplesIn, samplesDropped, samplesOut, samplesOutDuration *ewmaRate
 
-	metrics *queueManagerMetrics
+	metrics                    *queueManagerMetrics
+	highestSentTimestampMetric *maxGauge
+	pendingSamplesMetric       prometheus.Gauge
+	enqueueRetriesMetric       prometheus.Counter
+	droppedSamplesTotal        prometheus.Counter
+	numShardsMetric            prometheus.Gauge
+	failedSamplesTotal         prometheus.Counter
+	sentBatchDuration          prometheus.Observer
+	succeededSamplesTotal      prometheus.Counter
+	retriedSamplesTotal        prometheus.Counter
+	shardCapacity              prometheus.Gauge
+	maxNumShards               prometheus.Gauge
+	minNumShards               prometheus.Gauge
+	desiredNumShards           prometheus.Gauge
+	bytesSent                  prometheus.Counter
 }
 
 // NewQueueManager builds a new QueueManager.
-func NewQueueManager(
-	metrics *queueManagerMetrics,
-	watcherMetrics *wal.WatcherMetrics,
-	readerMetrics *wal.LiveReaderMetrics,
-	logger log.Logger,
-	walDir string,
-	samplesIn *ewmaRate,
-	cfg config.QueueConfig,
-	externalLabels labels.Labels,
-	relabelConfigs []*relabel.Config,
-	client StorageClient,
-	flushDeadline time.Duration,
-) *QueueManager {
+func NewQueueManager(metrics *queueManagerMetrics, watcherMetrics *wal.WatcherMetrics, readerMetrics *wal.LiveReaderMetrics, logger log.Logger, walDir string, samplesIn *ewmaRate, cfg config.QueueConfig, externalLabels labels.Labels, relabelConfigs []*relabel.Config, client StorageClient, flushDeadline time.Duration) *QueueManager {
 	if logger == nil {
 		logger = log.NewNopLogger()
 	}
@@ -316,7 +317,7 @@ outer:
 		t.seriesMtx.Lock()
 		lbls, ok := t.seriesLabels[s.Ref]
 		if !ok {
-			t.metrics.droppedSamplesTotal.Inc()
+			t.droppedSamplesTotal.Inc()
 			t.samplesDropped.incr(1)
 			if _, ok := t.droppedSeries[s.Ref]; !ok {
 				level.Info(t.logger).Log("msg", "Dropped sample for series that was not explicitly dropped via relabelling", "ref", s.Ref)
@@ -342,7 +343,7 @@ outer:
 				continue outer
 			}
 
-			t.metrics.enqueueRetriesTotal.Inc()
+			t.enqueueRetriesMetric.Inc()
 			time.Sleep(time.Duration(backoff))
 			backoff = backoff * 2
 			if backoff > t.cfg.MaxBackoff {
@@ -356,12 +357,34 @@ outer:
 // Start the queue manager sending samples to the remote storage.
 // Does not block.
 func (t *QueueManager) Start() {
+	// Setup the QueueManagers metrics. We do this here rather than in the
+	// constructor because of the ordering of creating Queue Managers's, stopping them,
+	// and then starting new ones in storage/remote/storage.go ApplyConfig.
+	name := t.client().Name()
+	ep := t.client().Endpoint()
+	t.highestSentTimestampMetric = &maxGauge{
+		Gauge: t.metrics.queueHighestSentTimestamp.WithLabelValues(name, ep),
+	}
+	t.pendingSamplesMetric = t.metrics.queuePendingSamples.WithLabelValues(name, ep)
+	t.enqueueRetriesMetric = t.metrics.enqueueRetriesTotal.WithLabelValues(name, ep)
+	t.droppedSamplesTotal = t.metrics.droppedSamplesTotal.WithLabelValues(name, ep)
+	t.numShardsMetric = t.metrics.numShards.WithLabelValues(name, ep)
+	t.failedSamplesTotal = t.metrics.failedSamplesTotal.WithLabelValues(name, ep)
+	t.sentBatchDuration = t.metrics.sentBatchDuration.WithLabelValues(name, ep)
+	t.succeededSamplesTotal = t.metrics.succeededSamplesTotal.WithLabelValues(name, ep)
+	t.retriedSamplesTotal = t.metrics.retriedSamplesTotal.WithLabelValues(name, ep)
+	t.shardCapacity = t.metrics.shardCapacity.WithLabelValues(name, ep)
+	t.maxNumShards = t.metrics.maxNumShards.WithLabelValues(name, ep)
+	t.minNumShards = t.metrics.minNumShards.WithLabelValues(name, ep)
+	t.desiredNumShards = t.metrics.desiredNumShards.WithLabelValues(name, ep)
+	t.bytesSent = t.metrics.bytesSent.WithLabelValues(name, ep)
+
 	// Initialise some metrics.
-	t.metrics.shardCapacity.Set(float64(t.cfg.Capacity))
-	t.metrics.pendingSamples.Set(0)
-	t.metrics.maxNumShards.Set(float64(t.cfg.MaxShards))
-	t.metrics.minNumShards.Set(float64(t.cfg.MinShards))
-	t.metrics.desiredNumShards.Set(float64(t.cfg.MinShards))
+	t.shardCapacity.Set(float64(t.cfg.Capacity))
+	t.pendingSamplesMetric.Set(0)
+	t.maxNumShards.Set(float64(t.cfg.MaxShards))
+	t.minNumShards.Set(float64(t.cfg.MinShards))
+	t.desiredNumShards.Set(float64(t.cfg.MinShards))
 
 	t.shards.start(t.numShards)
 	t.watcher.Start()
@@ -391,7 +414,22 @@ func (t *QueueManager) Stop() {
 		releaseLabels(labels)
 	}
 	t.seriesMtx.Unlock()
-	t.metrics.unregister()
+	// Delete metrics so we don't have alerts for queues that are gone.
+	name := t.client().Name()
+	ep := t.client().Endpoint()
+	t.metrics.queueHighestSentTimestamp.DeleteLabelValues(name, ep)
+	t.metrics.queuePendingSamples.DeleteLabelValues(name, ep)
+	t.metrics.enqueueRetriesTotal.DeleteLabelValues(name, ep)
+	t.metrics.droppedSamplesTotal.DeleteLabelValues(name, ep)
+	t.metrics.numShards.DeleteLabelValues(name, ep)
+	t.metrics.failedSamplesTotal.DeleteLabelValues(name, ep)
+	t.metrics.sentBatchDuration.DeleteLabelValues(name, ep)
+	t.metrics.succeededSamplesTotal.DeleteLabelValues(name, ep)
+	t.metrics.retriedSamplesTotal.DeleteLabelValues(name, ep)
+	t.metrics.shardCapacity.DeleteLabelValues(name, ep)
+	t.metrics.maxNumShards.DeleteLabelValues(name, ep)
+	t.metrics.minNumShards.DeleteLabelValues(name, ep)
+	t.metrics.desiredNumShards.DeleteLabelValues(name, ep)
 }
 
 // StoreSeries keeps track of which series we know about for lookups when sending samples to remote.
@@ -559,7 +597,7 @@ func (t *QueueManager) calculateDesiredShards() int {
 		samplesKeptRatio   = samplesOutRate / (t.samplesDropped.rate() + samplesOutRate)
 		samplesOutDuration = t.samplesOutDuration.rate() / float64(time.Second)
 		samplesPendingRate = samplesInRate*samplesKeptRatio - samplesOutRate
-		highestSent        = t.metrics.highestSentTimestamp.Get()
+		highestSent        = t.highestSentTimestampMetric.Get()
 		highestRecv        = highestTimestamp.Get()
 		delay              = highestRecv - highestSent
 		samplesPending     = delay * samplesInRate * samplesKeptRatio
@@ -578,7 +616,7 @@ func (t *QueueManager) calculateDesiredShards() int {
 		timePerSample = samplesOutDuration / samplesOutRate
 		desiredShards = timePerSample * (samplesInRate*samplesKeptRatio + integralGain*samplesPending)
 	)
-	t.metrics.desiredNumShards.Set(desiredShards)
+	t.desiredNumShards.Set(desiredShards)
 	level.Debug(t.logger).Log("msg", "QueueManager.calculateDesiredShards",
 		"samplesInRate", samplesInRate,
 		"samplesOutRate", samplesOutRate,
@@ -688,7 +726,7 @@ func (s *shards) start(n int) {
 	for i := 0; i < n; i++ {
 		go s.runShard(hardShutdownCtx, i, newQueues[i])
 	}
-	s.qm.metrics.numShards.Set(float64(n))
+	s.qm.numShardsMetric.Set(float64(n))
 }
 
 // stop the shards; subsequent call to enqueue will return false.
@@ -782,7 +820,7 @@ func (s *shards) runShard(ctx context.Context, shardID int, queue chan sample) {
 				if nPending > 0 {
 					level.Debug(s.qm.logger).Log("msg", "Flushing samples to remote storage...", "count", nPending)
 					s.sendSamples(ctx, pendingSamples[:nPending], &buf)
-					s.qm.metrics.pendingSamples.Sub(float64(nPending))
+					s.qm.pendingSamplesMetric.Sub(float64(nPending))
 					level.Debug(s.qm.logger).Log("msg", "Done flushing.")
 				}
 				return
@@ -795,12 +833,12 @@ func (s *shards) runShard(ctx context.Context, shardID int, queue chan sample) {
 			pendingSamples[nPending].Samples[0].Timestamp = sample.t
 			pendingSamples[nPending].Samples[0].Value = sample.v
 			nPending++
-			s.qm.metrics.pendingSamples.Inc()
+			s.qm.pendingSamplesMetric.Inc()
 
 			if nPending >= max {
 				s.sendSamples(ctx, pendingSamples, &buf)
 				nPending = 0
-				s.qm.metrics.pendingSamples.Sub(float64(max))
+				s.qm.pendingSamplesMetric.Sub(float64(max))
 
 				stop()
 				timer.Reset(time.Duration(s.qm.cfg.BatchSendDeadline))
@@ -810,7 +848,7 @@ func (s *shards) runShard(ctx context.Context, shardID int, queue chan sample) {
 			if nPending > 0 {
 				level.Debug(s.qm.logger).Log("msg", "runShard timer ticked, sending samples", "samples", nPending, "shard", shardNum)
 				s.sendSamples(ctx, pendingSamples[:nPending], &buf)
-				s.qm.metrics.pendingSamples.Sub(float64(nPending))
+				s.qm.pendingSamplesMetric.Sub(float64(nPending))
 				nPending = 0
 			}
 			timer.Reset(time.Duration(s.qm.cfg.BatchSendDeadline))
@@ -823,7 +861,7 @@ func (s *shards) sendSamples(ctx context.Context, samples []prompb.TimeSeries, b
 	err := s.sendSamplesWithBackoff(ctx, samples, buf)
 	if err != nil {
 		level.Error(s.qm.logger).Log("msg", "non-recoverable error", "count", len(samples), "err", err)
-		s.qm.metrics.failedSamplesTotal.Add(float64(len(samples)))
+		s.qm.failedSamplesTotal.Add(float64(len(samples)))
 	}
 
 	// These counters are used to calculate the dynamic sharding, and as such
@@ -853,20 +891,20 @@ func (s *shards) sendSamplesWithBackoff(ctx context.Context, samples []prompb.Ti
 		begin := time.Now()
 		err := s.qm.client().Store(ctx, req)
 
-		s.qm.metrics.sentBatchDuration.Observe(time.Since(begin).Seconds())
+		s.qm.sentBatchDuration.Observe(time.Since(begin).Seconds())
 
 		if err == nil {
-			s.qm.metrics.succeededSamplesTotal.Add(float64(len(samples)))
-			s.qm.metrics.bytesSent.Add(float64(len(req)))
-			s.qm.metrics.highestSentTimestamp.Set(float64(highest / 1000))
+			s.qm.succeededSamplesTotal.Add(float64(len(samples)))
+			s.qm.bytesSent.Add(float64(len(req)))
+			s.qm.highestSentTimestampMetric.Set(float64(highest / 1000))
 			return nil
 		}
 
 		if _, ok := err.(recoverableError); !ok {
 			return err
 		}
-		s.qm.metrics.retriedSamplesTotal.Add(float64(len(samples)))
-		level.Warn(s.qm.logger).Log("msg", "Failed to send batch, retrying", "err", err)
+		s.qm.retriedSamplesTotal.Add(float64(len(samples)))
+		level.Debug(s.qm.logger).Log("msg", "Failed to send batch, retrying", "err", err)
 
 		time.Sleep(time.Duration(backoff))
 		backoff = backoff * 2
diff --git a/storage/remote/write.go b/storage/remote/write.go
index c71239736..57df73bbb 100644
--- a/storage/remote/write.go
+++ b/storage/remote/write.go
@@ -47,9 +47,9 @@ var (
 // WriteStorage represents all the remote write storage.
 type WriteStorage struct {
 	logger log.Logger
-	reg    prometheus.Registerer
 	mtx    sync.Mutex
 
+	queueMetrics      *queueManagerMetrics
 	watcherMetrics    *wal.WatcherMetrics
 	liveReaderMetrics *wal.LiveReaderMetrics
 	externalLabels    labels.Labels
@@ -66,10 +66,10 @@ func NewWriteStorage(logger log.Logger, reg prometheus.Registerer, walDir string
 	}
 	rws := &WriteStorage{
 		queues:            make(map[string]*QueueManager),
+		queueMetrics:      newQueueManagerMetrics(reg),
 		watcherMetrics:    wal.NewWatcherMetrics(reg),
 		liveReaderMetrics: wal.NewLiveReaderMetrics(reg),
 		logger:            logger,
-		reg:               reg,
 		flushDeadline:     flushDeadline,
 		samplesIn:         newEWMARate(ewmaWeight, shardUpdateDuration),
 		walDir:            walDir,
@@ -136,9 +136,8 @@ func (rws *WriteStorage) ApplyConfig(conf *config.Config) error {
 			continue
 		}
 
-		endpoint := rwConf.URL.String()
 		newQueues[hash] = NewQueueManager(
-			newQueueManagerMetrics(rws.reg, name, endpoint),
+			rws.queueMetrics,
 			rws.watcherMetrics,
 			rws.liveReaderMetrics,
 			rws.logger,
diff --git a/tsdb/chunks/head_chunks.go b/tsdb/chunks/head_chunks.go
index 52e20f866..fd111c2df 100644
--- a/tsdb/chunks/head_chunks.go
+++ b/tsdb/chunks/head_chunks.go
@@ -72,13 +72,13 @@ const (
 )
 
 // corruptionErr is an error that's returned when corruption is encountered.
-type CorruptionErr struct {
+type corruptionErr struct {
 	Dir       string
 	FileIndex int
 	Err       error
 }
 
-func (e *CorruptionErr) Error() string {
+func (e *corruptionErr) Error() string {
 	return errors.Wrapf(e.Err, "corruption in head chunk file %s", segmentFile(e.Dir, e.FileIndex)).Error()
 }
 
@@ -512,7 +512,7 @@ func (cdm *ChunkDiskMapper) Chunk(ref uint64) (chunkenc.Chunk, error) {
 // and runs the provided function on each chunk. It returns on the first error encountered.
 // NOTE: This method needs to be called at least once after creating ChunkDiskMapper
 // to set the maxt of all the file.
-func (cdm *ChunkDiskMapper) IterateAllChunks(f func(seriesRef, chunkRef uint64, mint, maxt int64, numSamples uint16) error) (err error) {
+func (cdm *ChunkDiskMapper) IterateAllChunks(f func(seriesRef, chunkRef uint64, mint, maxt int64) error) (err error) {
 	cdm.writePathMtx.Lock()
 	defer cdm.writePathMtx.Unlock()
 
@@ -550,7 +550,7 @@ func (cdm *ChunkDiskMapper) IterateAllChunks(f func(seriesRef, chunkRef uint64,
 				if allZeros {
 					break
 				}
-				return &CorruptionErr{
+				return &corruptionErr{
 					Dir:       cdm.dir.Name(),
 					FileIndex: segID,
 					Err:       errors.Errorf("head chunk file doesn't include enough bytes to read the chunk header - required:%v, available:%v, file:%d", idx+MaxHeadChunkMetaSize, fileEnd, segID),
@@ -577,15 +577,12 @@ func (cdm *ChunkDiskMapper) IterateAllChunks(f func(seriesRef, chunkRef uint64,
 
 			idx += ChunkEncodingSize // Skip encoding.
 			dataLen, n := binary.Uvarint(mmapFile.byteSlice.Range(idx, idx+MaxChunkLengthFieldSize))
-			idx += n
-
-			numSamples := binary.BigEndian.Uint16(mmapFile.byteSlice.Range(idx, idx+2))
-			idx += int(dataLen) // Skip the data.
+			idx += n + int(dataLen) // Skip the data.
 
 			// In the beginning we only checked for the chunk meta size.
 			// Now that we have added the chunk data length, we check for sufficient bytes again.
 			if idx+CRCSize > fileEnd {
-				return &CorruptionErr{
+				return &corruptionErr{
 					Dir:       cdm.dir.Name(),
 					FileIndex: segID,
 					Err:       errors.Errorf("head chunk file doesn't include enough bytes to read the chunk header - required:%v, available:%v, file:%d", idx+CRCSize, fileEnd, segID),
@@ -598,7 +595,7 @@ func (cdm *ChunkDiskMapper) IterateAllChunks(f func(seriesRef, chunkRef uint64,
 				return err
 			}
 			if act := chkCRC32.Sum(nil); !bytes.Equal(act, sum) {
-				return &CorruptionErr{
+				return &corruptionErr{
 					Dir:       cdm.dir.Name(),
 					FileIndex: segID,
 					Err:       errors.Errorf("checksum mismatch expected:%x, actual:%x", sum, act),
@@ -610,14 +607,14 @@ func (cdm *ChunkDiskMapper) IterateAllChunks(f func(seriesRef, chunkRef uint64,
 				mmapFile.maxt = maxt
 			}
 
-			if err := f(seriesRef, chunkRef, mint, maxt, numSamples); err != nil {
+			if err := f(seriesRef, chunkRef, mint, maxt); err != nil {
 				return err
 			}
 		}
 
 		if idx > fileEnd {
 			// It should be equal to the slice length.
-			return &CorruptionErr{
+			return &corruptionErr{
 				Dir:       cdm.dir.Name(),
 				FileIndex: segID,
 				Err:       errors.Errorf("head chunk file doesn't include enough bytes to read the last chunk data - required:%v, available:%v, file:%d", idx, fileEnd, segID),
@@ -681,11 +678,11 @@ func (cdm *ChunkDiskMapper) deleteFiles(removedFiles []int) error {
 	return nil
 }
 
-// DeleteCorrupted deletes all the head chunk files after the one which had the corruption
+// Repair deletes all the head chunk files after the one which had the corruption
 // (including the corrupt file).
-func (cdm *ChunkDiskMapper) DeleteCorrupted(originalErr error) error {
+func (cdm *ChunkDiskMapper) Repair(originalErr error) error {
 	err := errors.Cause(originalErr) // So that we can pick up errors even if wrapped.
-	cerr, ok := err.(*CorruptionErr)
+	cerr, ok := err.(*corruptionErr)
 	if !ok {
 		return errors.Wrap(originalErr, "cannot handle error")
 	}
diff --git a/tsdb/db.go b/tsdb/db.go
index 17a69b01a..e97499c04 100644
--- a/tsdb/db.go
+++ b/tsdb/db.go
@@ -309,7 +309,7 @@ func (db *DBReadOnly) FlushWAL(dir string) (returnErr error) {
 	if err != nil {
 		return err
 	}
-	head, err := NewHead(nil, db.logger, w, 1, db.dir, nil, DefaultStripeSize)
+	head, err := NewHead(nil, db.logger, w, 1, DefaultStripeSize)
 	if err != nil {
 		return err
 	}
@@ -368,7 +368,7 @@ func (db *DBReadOnly) Querier(ctx context.Context, mint, maxt int64) (storage.Qu
 		blocks[i] = b
 	}
 
-	head, err := NewHead(nil, db.logger, nil, 1, db.dir, nil, DefaultStripeSize)
+	head, err := NewHead(nil, db.logger, nil, 1, DefaultStripeSize)
 	if err != nil {
 		return nil, err
 	}
@@ -379,14 +379,11 @@ func (db *DBReadOnly) Querier(ctx context.Context, mint, maxt int64) (storage.Qu
 
 	// Also add the WAL if the current blocks don't cover the requests time range.
 	if maxBlockTime <= maxt {
-		if err := head.Close(); err != nil {
-			return nil, err
-		}
 		w, err := wal.Open(db.logger, filepath.Join(db.dir, "wal"))
 		if err != nil {
 			return nil, err
 		}
-		head, err = NewHead(nil, db.logger, w, 1, db.dir, nil, DefaultStripeSize)
+		head, err = NewHead(nil, db.logger, w, 1, DefaultStripeSize)
 		if err != nil {
 			return nil, err
 		}
@@ -398,9 +395,9 @@ func (db *DBReadOnly) Querier(ctx context.Context, mint, maxt int64) (storage.Qu
 		// Set the wal to nil to disable all wal operations.
 		// This is mainly to avoid blocking when closing the head.
 		head.wal = nil
-	}
 
-	db.closers = append(db.closers, head)
+		db.closers = append(db.closers, head)
+	}
 
 	// TODO: Refactor so that it is possible to obtain a Querier without initializing a writable DB instance.
 	// Option 1: refactor DB to have the Querier implementation using the DBReadOnly.Querier implementation not the opposite.
@@ -586,21 +583,19 @@ func open(dir string, l log.Logger, r prometheus.Registerer, opts *Options, rngs
 
 	var wlog *wal.WAL
 	segmentSize := wal.DefaultSegmentSize
-	walDir := filepath.Join(dir, "wal")
 	// 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, walDir, segmentSize, opts.WALCompression)
+		wlog, err = wal.NewSize(l, r, filepath.Join(dir, "wal"), segmentSize, opts.WALCompression)
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	db.head, err = NewHead(r, l, wlog, rngs[0], dir, db.chunkPool, opts.StripeSize)
-
+	db.head, err = NewHead(r, l, wlog, rngs[0], opts.StripeSize)
 	if err != nil {
 		return nil, err
 	}
@@ -1023,10 +1018,9 @@ func (db *DB) beyondSizeRetention(blocks []*Block) (deletable map[ulid.ULID]*Blo
 	deletable = make(map[ulid.ULID]*Block)
 
 	walSize, _ := db.Head().wal.Size()
-	headChunksSize := db.Head().chunkDiskMapper.Size()
-	// Initializing size counter with WAL size and Head chunks
-	// written to disk, as that is part of the retention strategy.
-	blocksSize := walSize + headChunksSize
+	// Initializing size counter with WAL size,
+	// as that is part of the retention strategy.
+	blocksSize := walSize
 	for i, block := range blocks {
 		blocksSize += block.Size()
 		if blocksSize > int64(db.opts.MaxBytes) {
diff --git a/tsdb/docs/format/head_chunks.md b/tsdb/docs/format/head_chunks.md
index 5a320fa76..32657e804 100644
--- a/tsdb/docs/format/head_chunks.md
+++ b/tsdb/docs/format/head_chunks.md
@@ -33,4 +33,4 @@ Unlike chunks in the on-disk blocks, here we additionally store series reference
 ┌─────────────────────┬───────────────────────┬───────────────────────┬───────────────────┬───────────────┬──────────────┬────────────────┐
 | series ref <8 byte> | mint <8 byte, uint64> | maxt <8 byte, uint64> | encoding <1 byte> | len <uvarint> | data <bytes> │ CRC32 <4 byte> │
 └─────────────────────┴───────────────────────┴───────────────────────┴───────────────────┴───────────────┴──────────────┴────────────────┘
-```
\ No newline at end of file
+```
diff --git a/tsdb/head.go b/tsdb/head.go
index d56f167e3..4b374fd5d 100644
--- a/tsdb/head.go
+++ b/tsdb/head.go
@@ -16,7 +16,6 @@ package tsdb
 import (
 	"fmt"
 	"math"
-	"path/filepath"
 	"runtime"
 	"sort"
 	"strings"
@@ -33,7 +32,6 @@ import (
 	"github.com/prometheus/prometheus/storage"
 	"github.com/prometheus/prometheus/tsdb/chunkenc"
 	"github.com/prometheus/prometheus/tsdb/chunks"
-	tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
 	"github.com/prometheus/prometheus/tsdb/index"
 	"github.com/prometheus/prometheus/tsdb/record"
 	"github.com/prometheus/prometheus/tsdb/tombstones"
@@ -56,13 +54,12 @@ type Head struct {
 	minValidTime     int64 // Mint allowed to be added to the head. It shouldn't be lower than the maxt of the last persisted block.
 	lastSeriesID     uint64
 
-	metrics      *headMetrics
-	wal          *wal.WAL
-	logger       log.Logger
-	appendPool   sync.Pool
-	seriesPool   sync.Pool
-	bytesPool    sync.Pool
-	memChunkPool sync.Pool
+	metrics    *headMetrics
+	wal        *wal.WAL
+	logger     log.Logger
+	appendPool sync.Pool
+	seriesPool sync.Pool
+	bytesPool  sync.Pool
 
 	// All series addressable by their ID or hash.
 	series *stripeSeries
@@ -83,35 +80,27 @@ type Head struct {
 	cardinalityMutex      sync.Mutex
 	cardinalityCache      *index.PostingsStats // Posting stats cache which will expire after 30sec.
 	lastPostingsStatsCall time.Duration        // Last posting stats call (PostingsCardinalityStats()) time for caching.
-
-	// chunkDiskMapper is used to write and read Head chunks to/from disk.
-	chunkDiskMapper *chunks.ChunkDiskMapper
-	// chunkDirRoot is the parent directory of the chunks directory.
-	chunkDirRoot string
 }
 
 type headMetrics struct {
-	activeAppenders          prometheus.Gauge
-	series                   prometheus.GaugeFunc
-	seriesCreated            prometheus.Counter
-	seriesRemoved            prometheus.Counter
-	seriesNotFound           prometheus.Counter
-	chunks                   prometheus.Gauge
-	chunksCreated            prometheus.Counter
-	chunksRemoved            prometheus.Counter
-	gcDuration               prometheus.Summary
-	samplesAppended          prometheus.Counter
-	outOfBoundSamples        prometheus.Counter
-	outOfOrderSamples        prometheus.Counter
-	walTruncateDuration      prometheus.Summary
-	walCorruptionsTotal      prometheus.Counter
-	headTruncateFail         prometheus.Counter
-	headTruncateTotal        prometheus.Counter
-	checkpointDeleteFail     prometheus.Counter
-	checkpointDeleteTotal    prometheus.Counter
-	checkpointCreationFail   prometheus.Counter
-	checkpointCreationTotal  prometheus.Counter
-	mmapChunkCorruptionTotal prometheus.Counter
+	activeAppenders         prometheus.Gauge
+	series                  prometheus.GaugeFunc
+	seriesCreated           prometheus.Counter
+	seriesRemoved           prometheus.Counter
+	seriesNotFound          prometheus.Counter
+	chunks                  prometheus.Gauge
+	chunksCreated           prometheus.Counter
+	chunksRemoved           prometheus.Counter
+	gcDuration              prometheus.Summary
+	samplesAppended         prometheus.Counter
+	walTruncateDuration     prometheus.Summary
+	walCorruptionsTotal     prometheus.Counter
+	headTruncateFail        prometheus.Counter
+	headTruncateTotal       prometheus.Counter
+	checkpointDeleteFail    prometheus.Counter
+	checkpointDeleteTotal   prometheus.Counter
+	checkpointCreationFail  prometheus.Counter
+	checkpointCreationTotal prometheus.Counter
 }
 
 func newHeadMetrics(h *Head, r prometheus.Registerer) *headMetrics {
@@ -166,14 +155,6 @@ func newHeadMetrics(h *Head, r prometheus.Registerer) *headMetrics {
 			Name: "prometheus_tsdb_head_samples_appended_total",
 			Help: "Total number of appended samples.",
 		}),
-		outOfBoundSamples: prometheus.NewCounter(prometheus.CounterOpts{
-			Name: "prometheus_tsdb_out_of_bound_samples_total",
-			Help: "Total number of out of bound samples ingestion failed attempts.",
-		}),
-		outOfOrderSamples: prometheus.NewCounter(prometheus.CounterOpts{
-			Name: "prometheus_tsdb_out_of_order_samples_total",
-			Help: "Total number of out of order samples ingestion failed attempts.",
-		}),
 		headTruncateFail: prometheus.NewCounter(prometheus.CounterOpts{
 			Name: "prometheus_tsdb_head_truncations_failed_total",
 			Help: "Total number of head truncations that failed.",
@@ -198,10 +179,6 @@ func newHeadMetrics(h *Head, r prometheus.Registerer) *headMetrics {
 			Name: "prometheus_tsdb_checkpoint_creations_total",
 			Help: "Total number of checkpoint creations attempted.",
 		}),
-		mmapChunkCorruptionTotal: prometheus.NewCounter(prometheus.CounterOpts{
-			Name: "prometheus_tsdb_mmap_chunk_corruptions_total",
-			Help: "Total number of memory-mapped chunk corruptions.",
-		}),
 	}
 
 	if r != nil {
@@ -218,15 +195,12 @@ func newHeadMetrics(h *Head, r prometheus.Registerer) *headMetrics {
 			m.walTruncateDuration,
 			m.walCorruptionsTotal,
 			m.samplesAppended,
-			m.outOfBoundSamples,
-			m.outOfOrderSamples,
 			m.headTruncateFail,
 			m.headTruncateTotal,
 			m.checkpointDeleteFail,
 			m.checkpointDeleteTotal,
 			m.checkpointCreationFail,
 			m.checkpointCreationTotal,
-			m.mmapChunkCorruptionTotal,
 			// Metrics bound to functions and not needed in tests
 			// can be created and registered on the spot.
 			prometheus.NewGaugeFunc(prometheus.GaugeOpts{
@@ -284,7 +258,7 @@ func (h *Head) PostingsCardinalityStats(statsByLabelName string) *index.Postings
 // stripeSize sets the number of entries in the hash map, it must be a power of 2.
 // A larger stripeSize will allocate more memory up-front, but will increase performance when handling a large number of series.
 // A smaller stripeSize reduces the memory allocated, but can decrease performance with large number of series.
-func NewHead(r prometheus.Registerer, l log.Logger, wal *wal.WAL, chunkRange int64, chkDirRoot string, pool chunkenc.Pool, stripeSize int) (*Head, error) {
+func NewHead(r prometheus.Registerer, l log.Logger, wal *wal.WAL, chunkRange int64, stripeSize int) (*Head, error) {
 	if l == nil {
 		l = log.NewNopLogger()
 	}
@@ -304,30 +278,12 @@ func NewHead(r prometheus.Registerer, l log.Logger, wal *wal.WAL, chunkRange int
 		tombstones: tombstones.NewMemTombstones(),
 		iso:        newIsolation(),
 		deleted:    map[uint64]int{},
-		memChunkPool: sync.Pool{
-			New: func() interface{} {
-				return &memChunk{}
-			},
-		},
-		chunkDirRoot: chkDirRoot,
 	}
 	h.metrics = newHeadMetrics(h, r)
 
-	if pool == nil {
-		pool = chunkenc.NewPool()
-	}
-
-	var err error
-	h.chunkDiskMapper, err = chunks.NewChunkDiskMapper(mmappedChunksDir(chkDirRoot), pool)
-	if err != nil {
-		return nil, err
-	}
-
 	return h, nil
 }
 
-func mmappedChunksDir(dir string) string { return filepath.Join(dir, "chunks_head") }
-
 // processWALSamples adds a partition of samples it receives to the head and passes
 // them on to other workers.
 // Samples before the mint timestamp are discarded.
@@ -356,7 +312,7 @@ func (h *Head) processWALSamples(
 				}
 				refSeries[s.Ref] = ms
 			}
-			if _, chunkCreated := ms.append(s.T, s.V, 0, h.chunkDiskMapper); chunkCreated {
+			if _, chunkCreated := ms.append(s.T, s.V, 0); chunkCreated {
 				h.metrics.chunksCreated.Inc()
 				h.metrics.chunks.Inc()
 			}
@@ -395,7 +351,7 @@ func (h *Head) updateMinMaxTime(mint, maxt int64) {
 	}
 }
 
-func (h *Head) loadWAL(r *wal.Reader, multiRef map[uint64]uint64, mmappedChunks map[uint64][]*mmappedChunk) (err error) {
+func (h *Head) loadWAL(r *wal.Reader, multiRef map[uint64]uint64) (err error) {
 	// Track number of samples that referenced a series we don't know about
 	// for error reporting.
 	var unknownRefs uint64
@@ -516,20 +472,7 @@ func (h *Head) loadWAL(r *wal.Reader, multiRef map[uint64]uint64, mmappedChunks
 			for _, s := range v {
 				series, created := h.getOrCreateWithID(s.Ref, s.Labels.Hash(), s.Labels)
 
-				if created {
-					// If this series gets a duplicate record, we don't restore its mmapped chunks,
-					// and instead restore everything from WAL records.
-					series.mmappedChunks = mmappedChunks[series.ref]
-
-					h.metrics.chunks.Add(float64(len(series.mmappedChunks)))
-					h.metrics.chunksCreated.Add(float64(len(series.mmappedChunks)))
-
-					if len(series.mmappedChunks) > 0 {
-						h.updateMinMaxTime(series.minTime(), series.maxTime())
-					}
-				} else {
-					// TODO(codesome) Discard old samples and mmapped chunks and use mmap chunks for the new series ID.
-
+				if !created {
 					// There's already a different ref for this series.
 					multiRef[s.Ref] = series.ref
 				}
@@ -629,20 +572,8 @@ func (h *Head) Init(minValidTime int64) error {
 		return nil
 	}
 
-	level.Info(h.logger).Log("msg", "Replaying WAL and on-disk memory mappable chunks if any, this may take a while")
+	level.Info(h.logger).Log("msg", "Replaying WAL, this may take awhile")
 	start := time.Now()
-
-	mmappedChunks, err := h.loadMmappedChunks()
-	if err != nil {
-		level.Error(h.logger).Log("msg", "Loading on-disk chunks failed", "err", err)
-		if _, ok := errors.Cause(err).(*chunks.CorruptionErr); ok {
-			h.metrics.mmapChunkCorruptionTotal.Inc()
-		}
-		// If this fails, data will be recovered from WAL.
-		// Hence we wont lose any data (given WAL is not corrupt).
-		h.removeCorruptedMmappedChunks(err)
-	}
-
 	// Backfill the checkpoint first if it exists.
 	dir, startFrom, err := wal.LastCheckpoint(h.wal.Dir())
 	if err != nil && err != record.ErrNotFound {
@@ -662,7 +593,7 @@ func (h *Head) Init(minValidTime int64) error {
 
 		// A corrupted checkpoint is a hard error for now and requires user
 		// intervention. There's likely little data that can be recovered anyway.
-		if err := h.loadWAL(wal.NewReader(sr), multiRef, mmappedChunks); err != nil {
+		if err := h.loadWAL(wal.NewReader(sr), multiRef); err != nil {
 			return errors.Wrap(err, "backfill checkpoint")
 		}
 		startFrom++
@@ -683,7 +614,7 @@ func (h *Head) Init(minValidTime int64) error {
 		}
 
 		sr := wal.NewSegmentBufReader(s)
-		err = h.loadWAL(wal.NewReader(sr), multiRef, mmappedChunks)
+		err = h.loadWAL(wal.NewReader(sr), multiRef)
 		if err := sr.Close(); err != nil {
 			level.Warn(h.logger).Log("msg", "Error while closing the wal segments reader", "err", err)
 		}
@@ -698,54 +629,6 @@ func (h *Head) Init(minValidTime int64) error {
 	return nil
 }
 
-func (h *Head) loadMmappedChunks() (map[uint64][]*mmappedChunk, error) {
-	mmappedChunks := map[uint64][]*mmappedChunk{}
-	if err := h.chunkDiskMapper.IterateAllChunks(func(seriesRef, chunkRef uint64, mint, maxt int64, numSamples uint16) error {
-		if maxt < h.minValidTime {
-			return nil
-		}
-
-		slice := mmappedChunks[seriesRef]
-		if len(slice) > 0 {
-			if slice[len(slice)-1].maxTime >= mint {
-				return errors.Errorf("out of sequence m-mapped chunk for series ref %d", seriesRef)
-			}
-		}
-
-		slice = append(slice, &mmappedChunk{
-			ref:        chunkRef,
-			minTime:    mint,
-			maxTime:    maxt,
-			numSamples: numSamples,
-		})
-		mmappedChunks[seriesRef] = slice
-		return nil
-	}); err != nil {
-		return nil, errors.Wrap(err, "iterate on on-disk chunks")
-	}
-	return mmappedChunks, nil
-}
-
-// removeCorruptedMmappedChunks attempts to delete the corrupted mmapped chunks and if it fails, it clears all the previously
-// loaded mmapped chunks.
-func (h *Head) removeCorruptedMmappedChunks(err error) map[uint64][]*mmappedChunk {
-	level.Info(h.logger).Log("msg", "Deleting mmapped chunk files")
-
-	if err := h.chunkDiskMapper.DeleteCorrupted(err); err != nil {
-		level.Info(h.logger).Log("msg", "Deletion of mmap chunk files failed, discarding chunk files completely", "err", err)
-		return map[uint64][]*mmappedChunk{}
-	}
-
-	level.Info(h.logger).Log("msg", "Deletion of mmap chunk files successful, reattempting m-mapping the on-disk chunks")
-	mmappedChunks, err := h.loadMmappedChunks()
-	if err != nil {
-		level.Error(h.logger).Log("msg", "Loading on-disk chunks failed, discarding chunk files completely", "err", err)
-		mmappedChunks = map[uint64][]*mmappedChunk{}
-	}
-
-	return mmappedChunks
-}
-
 // Truncate removes old data before mint from the head.
 func (h *Head) Truncate(mint int64) (err error) {
 	defer func() {
@@ -779,11 +662,6 @@ func (h *Head) Truncate(mint int64) (err error) {
 	level.Info(h.logger).Log("msg", "Head GC completed", "duration", time.Since(start))
 	h.metrics.gcDuration.Observe(time.Since(start).Seconds())
 
-	// Truncate the chunk m-mapper.
-	if err := h.chunkDiskMapper.Truncate(mint); err != nil {
-		return errors.Wrap(err, "truncate chunks.HeadReadWriter")
-	}
-
 	if h.wal == nil {
 		return nil
 	}
@@ -1069,7 +947,6 @@ type headAppender struct {
 
 func (a *headAppender) Add(lset labels.Labels, t int64, v float64) (uint64, error) {
 	if t < a.minValidTime {
-		a.head.metrics.outOfBoundSamples.Inc()
 		return 0, storage.ErrOutOfBounds
 	}
 
@@ -1096,7 +973,6 @@ func (a *headAppender) Add(lset labels.Labels, t int64, v float64) (uint64, erro
 
 func (a *headAppender) AddFast(ref uint64, t int64, v float64) error {
 	if t < a.minValidTime {
-		a.head.metrics.outOfBoundSamples.Inc()
 		return storage.ErrOutOfBounds
 	}
 
@@ -1107,9 +983,6 @@ func (a *headAppender) AddFast(ref uint64, t int64, v float64) error {
 	s.Lock()
 	if err := s.appendable(t, v); err != nil {
 		s.Unlock()
-		if err == storage.ErrOutOfOrderSample {
-			a.head.metrics.outOfOrderSamples.Inc()
-		}
 		return err
 	}
 	s.pendingCommit = true
@@ -1178,14 +1051,13 @@ func (a *headAppender) Commit() error {
 	for i, s := range a.samples {
 		series = a.sampleSeries[i]
 		series.Lock()
-		ok, chunkCreated := series.append(s.T, s.V, a.appendID, a.head.chunkDiskMapper)
+		ok, chunkCreated := series.append(s.T, s.V, a.appendID)
 		series.cleanupAppendIDsBelow(a.cleanupAppendIDsBelow)
 		series.pendingCommit = false
 		series.Unlock()
 
 		if !ok {
 			total--
-			a.head.metrics.outOfOrderSamples.Inc()
 		}
 		if chunkCreated {
 			a.head.metrics.chunks.Inc()
@@ -1353,11 +1225,10 @@ func (h *Head) chunksRange(mint, maxt int64, is *isolationState) *headChunkReade
 		mint = hmin
 	}
 	return &headChunkReader{
-		head:         h,
-		mint:         mint,
-		maxt:         maxt,
-		isoState:     is,
-		memChunkPool: &h.memChunkPool,
+		head:     h,
+		mint:     mint,
+		maxt:     maxt,
+		isoState: is,
 	}
 }
 
@@ -1400,19 +1271,16 @@ func (h *Head) compactable() bool {
 
 // Close flushes the WAL and closes the head.
 func (h *Head) Close() error {
-	var merr tsdb_errors.MultiError
-	merr.Add(h.chunkDiskMapper.Close())
-	if h.wal != nil {
-		merr.Add(h.wal.Close())
+	if h.wal == nil {
+		return nil
 	}
-	return merr.Err()
+	return h.wal.Close()
 }
 
 type headChunkReader struct {
-	head         *Head
-	mint, maxt   int64
-	isoState     *isolationState
-	memChunkPool *sync.Pool
+	head       *Head
+	mint, maxt int64
+	isoState   *isolationState
 }
 
 func (h *headChunkReader) Close() error {
@@ -1447,17 +1315,10 @@ func (h *headChunkReader) Chunk(ref uint64) (chunkenc.Chunk, error) {
 	}
 
 	s.Lock()
-	c, garbageCollect := s.chunk(int(cid), h.head.chunkDiskMapper)
-	defer func() {
-		if garbageCollect {
-			// Set this to nil so that Go GC can collect it after it has been used.
-			c.chunk = nil
-			h.memChunkPool.Put(c)
-		}
-	}()
+	c := s.chunk(int(cid))
 
-	// This means that the chunk has been garbage collected (or) is outside
-	// the specified range (or) Head is closing.
+	// This means that the chunk has been garbage collected or is outside
+	// the specified range.
 	if c == nil || !c.OverlapsClosedInterval(h.mint, h.maxt) {
 		s.Unlock()
 		return nil, storage.ErrNotFound
@@ -1465,25 +1326,23 @@ func (h *headChunkReader) Chunk(ref uint64) (chunkenc.Chunk, error) {
 	s.Unlock()
 
 	return &safeChunk{
-		Chunk:           c.chunk,
-		s:               s,
-		cid:             int(cid),
-		isoState:        h.isoState,
-		chunkDiskMapper: h.head.chunkDiskMapper,
+		Chunk:    c.chunk,
+		s:        s,
+		cid:      int(cid),
+		isoState: h.isoState,
 	}, nil
 }
 
 type safeChunk struct {
 	chunkenc.Chunk
-	s               *memSeries
-	cid             int
-	isoState        *isolationState
-	chunkDiskMapper *chunks.ChunkDiskMapper
+	s        *memSeries
+	cid      int
+	isoState *isolationState
 }
 
 func (c *safeChunk) Iterator(reuseIter chunkenc.Iterator) chunkenc.Iterator {
 	c.s.Lock()
-	it := c.s.iterator(c.cid, c.isoState, c.chunkDiskMapper, reuseIter)
+	it := c.s.iterator(c.cid, c.isoState, reuseIter)
 	c.s.Unlock()
 	return it
 }
@@ -1589,24 +1448,23 @@ func (h *headIndexReader) Series(ref uint64, lbls *labels.Labels, chks *[]chunks
 
 	*chks = (*chks)[:0]
 
-	for i, c := range s.mmappedChunks {
+	for i, c := range s.chunks {
 		// Do not expose chunks that are outside of the specified range.
 		if !c.OverlapsClosedInterval(h.mint, h.maxt) {
 			continue
 		}
+		// Set the head chunks as open (being appended to).
+		maxTime := c.maxTime
+		if s.headChunk == c {
+			maxTime = math.MaxInt64
+		}
+
 		*chks = append(*chks, chunks.Meta{
 			MinTime: c.minTime,
-			MaxTime: c.maxTime,
+			MaxTime: maxTime,
 			Ref:     packChunkID(s.ref, uint64(s.chunkID(i))),
 		})
 	}
-	if s.headChunk != nil && s.headChunk.OverlapsClosedInterval(h.mint, h.maxt) {
-		*chks = append(*chks, chunks.Meta{
-			MinTime: s.headChunk.minTime,
-			MaxTime: math.MaxInt64, // Set the head chunks as open (being appended to).
-			Ref:     packChunkID(s.ref, uint64(s.chunkID(len(s.mmappedChunks)))),
-		})
-	}
 
 	return nil
 }
@@ -1627,7 +1485,7 @@ func (h *Head) getOrCreate(hash uint64, lset labels.Labels) (*memSeries, bool) {
 }
 
 func (h *Head) getOrCreateWithID(id, hash uint64, lset labels.Labels) (*memSeries, bool) {
-	s := newMemSeries(lset, id, h.chunkRange, &h.memChunkPool)
+	s := newMemSeries(lset, id, h.chunkRange)
 
 	s, created := h.series.getOrSet(hash, s)
 	if !created {
@@ -1753,7 +1611,7 @@ func (s *stripeSeries) gc(mint int64) (map[uint64]struct{}, int) {
 				series.Lock()
 				rmChunks += series.truncateChunksBefore(mint)
 
-				if len(series.mmappedChunks) > 0 || series.headChunk != nil || series.pendingCommit {
+				if len(series.chunks) > 0 || series.pendingCommit {
 					series.Unlock()
 					continue
 				}
@@ -1846,12 +1704,12 @@ func (s sample) V() float64 {
 type memSeries struct {
 	sync.RWMutex
 
-	ref           uint64
-	lset          labels.Labels
-	mmappedChunks []*mmappedChunk
-	headChunk     *memChunk
-	chunkRange    int64
-	firstChunkID  int
+	ref          uint64
+	lset         labels.Labels
+	chunks       []*memChunk
+	headChunk    *memChunk
+	chunkRange   int64
+	firstChunkID int
 
 	nextAt        int64 // Timestamp at which to cut the next chunk.
 	sampleBuf     [4]sample
@@ -1859,31 +1717,25 @@ type memSeries struct {
 
 	app chunkenc.Appender // Current appender for the chunk.
 
-	memChunkPool *sync.Pool
-
 	txs *txRing
 }
 
-func newMemSeries(lset labels.Labels, id uint64, chunkRange int64, memChunkPool *sync.Pool) *memSeries {
+func newMemSeries(lset labels.Labels, id uint64, chunkRange int64) *memSeries {
 	s := &memSeries{
-		lset:         lset,
-		ref:          id,
-		chunkRange:   chunkRange,
-		nextAt:       math.MinInt64,
-		txs:          newTxRing(4),
-		memChunkPool: memChunkPool,
+		lset:       lset,
+		ref:        id,
+		chunkRange: chunkRange,
+		nextAt:     math.MinInt64,
+		txs:        newTxRing(4),
 	}
 	return s
 }
 
 func (s *memSeries) minTime() int64 {
-	if len(s.mmappedChunks) > 0 {
-		return s.mmappedChunks[0].minTime
-	}
-	if s.headChunk != nil {
-		return s.headChunk.minTime
+	if len(s.chunks) == 0 {
+		return math.MinInt64
 	}
-	return math.MinInt64
+	return s.chunks[0].minTime
 }
 
 func (s *memSeries) maxTime() int64 {
@@ -1894,45 +1746,30 @@ func (s *memSeries) maxTime() int64 {
 	return c.maxTime
 }
 
-func (s *memSeries) cutNewHeadChunk(mint int64, chunkDiskMapper *chunks.ChunkDiskMapper) *memChunk {
-	s.mmapCurrentHeadChunk(chunkDiskMapper)
-
-	s.headChunk = &memChunk{
+func (s *memSeries) cut(mint int64) *memChunk {
+	c := &memChunk{
 		chunk:   chunkenc.NewXORChunk(),
 		minTime: mint,
 		maxTime: math.MinInt64,
 	}
+	s.chunks = append(s.chunks, c)
+	s.headChunk = c
+
+	// Remove exceeding capacity from the previous chunk byte slice to save memory.
+	if l := len(s.chunks); l > 1 {
+		s.chunks[l-2].chunk.Compact()
+	}
 
 	// Set upper bound on when the next chunk must be started. An earlier timestamp
 	// may be chosen dynamically at a later point.
 	s.nextAt = rangeForTimestamp(mint, s.chunkRange)
 
-	app, err := s.headChunk.chunk.Appender()
+	app, err := c.chunk.Appender()
 	if err != nil {
 		panic(err)
 	}
 	s.app = app
-	return s.headChunk
-}
-
-func (s *memSeries) mmapCurrentHeadChunk(chunkDiskMapper *chunks.ChunkDiskMapper) {
-	if s.headChunk == nil {
-		// There is no head chunk, so nothing to m-map here.
-		return
-	}
-
-	chunkRef, err := chunkDiskMapper.WriteChunk(s.ref, s.headChunk.minTime, s.headChunk.maxTime, s.headChunk.chunk)
-	if err != nil {
-		if err != chunks.ErrChunkDiskMapperClosed {
-			panic(err)
-		}
-	}
-	s.mmappedChunks = append(s.mmappedChunks, &mmappedChunk{
-		ref:        chunkRef,
-		numSamples: uint16(s.headChunk.chunk.NumSamples()),
-		minTime:    s.headChunk.minTime,
-		maxTime:    s.headChunk.maxTime,
-	})
+	return c
 }
 
 // appendable checks whether the given sample is valid for appending to the series.
@@ -1956,34 +1793,12 @@ func (s *memSeries) appendable(t int64, v float64) error {
 	return nil
 }
 
-// chunk returns the chunk for the chunk id from memory or by m-mapping it from the disk.
-// If garbageCollect is true, it means that the returned *memChunk
-// (and not the chunkenc.Chunk inside it) can be garbage collected after it's usage.
-func (s *memSeries) chunk(id int, chunkDiskMapper *chunks.ChunkDiskMapper) (chunk *memChunk, garbageCollect bool) {
-	// ix represents the index of chunk in the s.mmappedChunks slice. The chunk id's are
-	// incremented by 1 when new chunk is created, hence (id - firstChunkID) gives the slice index.
-	// The max index for the s.mmappedChunks slice can be len(s.mmappedChunks)-1, hence if the ix
-	// is len(s.mmappedChunks), it represents the next chunk, which is the head chunk.
+func (s *memSeries) chunk(id int) *memChunk {
 	ix := id - s.firstChunkID
-	if ix < 0 || ix > len(s.mmappedChunks) {
-		return nil, false
-	}
-	if ix == len(s.mmappedChunks) {
-		return s.headChunk, false
-	}
-	chk, err := chunkDiskMapper.Chunk(s.mmappedChunks[ix].ref)
-	if err != nil {
-		if err == chunks.ErrChunkDiskMapperClosed {
-			return nil, false
-		}
-		// TODO(codesome): Find a better way to handle this error instead of a panic.
-		panic(err)
+	if ix < 0 || ix >= len(s.chunks) {
+		return nil
 	}
-	mc := s.memChunkPool.Get().(*memChunk)
-	mc.chunk = chk
-	mc.minTime = s.mmappedChunks[ix].minTime
-	mc.maxTime = s.mmappedChunks[ix].maxTime
-	return mc, true
+	return s.chunks[ix]
 }
 
 func (s *memSeries) chunkID(pos int) int {
@@ -1994,32 +1809,27 @@ func (s *memSeries) chunkID(pos int) int {
 // at or after mint. Chunk IDs remain unchanged.
 func (s *memSeries) truncateChunksBefore(mint int64) (removed int) {
 	var k int
-	if s.headChunk != nil && s.headChunk.maxTime < mint {
-		// If head chunk is truncated, we can truncate all mmapped chunks.
-		k = 1 + len(s.mmappedChunks)
-		s.firstChunkID += k
-		s.headChunk = nil
-		s.mmappedChunks = nil
-		return k
-	}
-	if len(s.mmappedChunks) > 0 {
-		for i, c := range s.mmappedChunks {
-			if c.maxTime >= mint {
-				break
-			}
-			k = i + 1
+	for i, c := range s.chunks {
+		if c.maxTime >= mint {
+			break
 		}
-		s.mmappedChunks = append(s.mmappedChunks[:0], s.mmappedChunks[k:]...)
-		s.firstChunkID += k
+		k = i + 1
 	}
+	s.chunks = append(s.chunks[:0], s.chunks[k:]...)
+	s.firstChunkID += k
+	if len(s.chunks) == 0 {
+		s.headChunk = nil
+	} else {
+		s.headChunk = s.chunks[len(s.chunks)-1]
+	}
+
 	return k
 }
 
 // append adds the sample (t, v) to the series. The caller also has to provide
 // the appendID for isolation. (The appendID can be zero, which results in no
 // isolation for this append.)
-// It is unsafe to call this concurrently with s.iterator(...) without holding the series lock.
-func (s *memSeries) append(t int64, v float64, appendID uint64, chunkDiskMapper *chunks.ChunkDiskMapper) (sampleInOrder, chunkCreated bool) {
+func (s *memSeries) append(t int64, v float64, appendID uint64) (success, chunkCreated bool) {
 	// Based on Gorilla white papers this offers near-optimal compression ratio
 	// so anything bigger that this has diminishing returns and increases
 	// the time range within which we have to decompress all samples.
@@ -2028,12 +1838,7 @@ func (s *memSeries) append(t int64, v float64, appendID uint64, chunkDiskMapper
 	c := s.head()
 
 	if c == nil {
-		if len(s.mmappedChunks) > 0 && s.mmappedChunks[len(s.mmappedChunks)-1].maxTime >= t {
-			// Out of order sample. Sample timestamp is already in the mmaped chunks, so ignore it.
-			return false, false
-		}
-		// There is no chunk in this series yet, create the first chunk for the sample.
-		c = s.cutNewHeadChunk(t, chunkDiskMapper)
+		c = s.cut(t)
 		chunkCreated = true
 	}
 	numSamples := c.chunk.NumSamples()
@@ -2049,7 +1854,7 @@ func (s *memSeries) append(t int64, v float64, appendID uint64, chunkDiskMapper
 		s.nextAt = computeChunkEndTime(c.minTime, c.maxTime, s.nextAt)
 	}
 	if t >= s.nextAt {
-		c = s.cutNewHeadChunk(t, chunkDiskMapper)
+		c = s.cut(t)
 		chunkCreated = true
 	}
 	s.app.Append(t, v)
@@ -2085,19 +1890,8 @@ func computeChunkEndTime(start, cur, max int64) int64 {
 	return start + (max-start)/a
 }
 
-// iterator returns a chunk iterator.
-// It is unsafe to call this concurrently with s.append(...) without holding the series lock.
-func (s *memSeries) iterator(id int, isoState *isolationState, chunkDiskMapper *chunks.ChunkDiskMapper, it chunkenc.Iterator) chunkenc.Iterator {
-	c, garbageCollect := s.chunk(id, chunkDiskMapper)
-	defer func() {
-		if garbageCollect {
-			// Set this to nil so that Go GC can collect it after it has been used.
-			// This should be done always at the end.
-			c.chunk = nil
-			s.memChunkPool.Put(c)
-		}
-	}()
-
+func (s *memSeries) iterator(id int, isoState *isolationState, it chunkenc.Iterator) chunkenc.Iterator {
+	c := s.chunk(id)
 	// TODO(fabxc): Work around! A querier may have retrieved a pointer to a
 	// series's chunk, which got then garbage collected before it got
 	// accessed.  We must ensure to not garbage collect as long as any
@@ -2115,18 +1909,12 @@ func (s *memSeries) iterator(id int, isoState *isolationState, chunkDiskMapper *
 		totalSamples := 0    // Total samples in this series.
 		previousSamples := 0 // Samples before this chunk.
 
-		for j, d := range s.mmappedChunks {
-			totalSamples += int(d.numSamples)
+		for j, d := range s.chunks {
+			totalSamples += d.chunk.NumSamples()
 			if j < ix {
-				previousSamples += int(d.numSamples)
+				previousSamples += d.chunk.NumSamples()
 			}
 		}
-		// mmappedChunks does not contain the last chunk. Hence check it separately.
-		if len(s.mmappedChunks) < ix {
-			previousSamples += s.headChunk.chunk.NumSamples()
-		} else {
-			totalSamples += s.headChunk.chunk.NumSamples()
-		}
 
 		// Removing the extra transactionIDs that are relevant for samples that
 		// come after this chunk, from the total transactionIDs.
@@ -2155,7 +1943,7 @@ func (s *memSeries) iterator(id int, isoState *isolationState, chunkDiskMapper *
 		return chunkenc.NewNopIterator()
 	}
 
-	if id-s.firstChunkID < len(s.mmappedChunks) {
+	if id-s.firstChunkID < len(s.chunks)-1 {
 		if stopAfter == numSamples {
 			return c.chunk.Iterator(it)
 		}
@@ -2201,7 +1989,7 @@ type memChunk struct {
 	minTime, maxTime int64
 }
 
-// OverlapsClosedInterval returns true if the chunk overlaps [mint, maxt].
+// Returns true if the chunk overlaps [mint, maxt].
 func (mc *memChunk) OverlapsClosedInterval(mint, maxt int64) bool {
 	return mc.minTime <= maxt && mint <= mc.maxTime
 }
@@ -2264,14 +2052,3 @@ func (ss stringset) slice() []string {
 	sort.Strings(slice)
 	return slice
 }
-
-type mmappedChunk struct {
-	ref              uint64
-	numSamples       uint16
-	minTime, maxTime int64
-}
-
-// Returns true if the chunk overlaps [mint, maxt].
-func (mc *mmappedChunk) OverlapsClosedInterval(mint, maxt int64) bool {
-	return mc.minTime <= maxt && mint <= mc.maxTime
-}
diff --git a/tsdb/index/postings.go b/tsdb/index/postings.go
index c2f042975..ddd7ad601 100644
--- a/tsdb/index/postings.go
+++ b/tsdb/index/postings.go
@@ -170,7 +170,7 @@ func (p *MemPostings) EnsureOrder() {
 	for i := 0; i < n; i++ {
 		go func() {
 			for l := range workc {
-				sort.Slice(l, func(a, b int) bool { return l[a] < l[b] })
+				sort.Slice(l, func(i, j int) bool { return l[i] < l[j] })
 			}
 			wg.Done()
 		}()
diff --git a/tsdb/tsdbblockutil.go b/tsdb/tsdbblockutil.go
index 8a26e91e7..5246edd64 100644
--- a/tsdb/tsdbblockutil.go
+++ b/tsdb/tsdbblockutil.go
@@ -32,9 +32,8 @@ type MetricSample struct {
 }
 
 // CreateHead creates a TSDB writer head to write the sample data to.
-func CreateHead(samples []*MetricSample, chunkRange int64, chunkDir string, logger log.Logger) (*Head, error) {
-	head, err := NewHead(nil, logger, nil, chunkRange, chunkDir, nil, DefaultStripeSize)
-
+func CreateHead(samples []*MetricSample, chunkRange int64, logger log.Logger) (*Head, error) {
+	head, err := NewHead(nil, logger, nil, chunkRange, DefaultStripeSize)
 	if err != nil {
 		return nil, err
 	}
@@ -61,15 +60,10 @@ func CreateBlock(samples []*MetricSample, dir string, mint, maxt int64, logger l
 	if chunkRange < 0 {
 		return "", ErrInvalidTimes
 	}
-	chunkDir := filepath.Join(dir, "chunks_tmp")
-	defer func() {
-		os.RemoveAll(chunkDir)
-	}()
-	head, err := CreateHead(samples, chunkRange, chunkDir, logger)
+	head, err := CreateHead(samples, chunkRange, logger)
 	if err != nil {
 		return "", err
 	}
-	defer head.Close()
 
 	compactor, err := NewLeveledCompactor(context.Background(), nil, logger, ExponentialBlockRanges(DefaultBlockDuration, 3, 5), nil)
 	if err != nil {
diff --git a/vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go b/vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
index fa35dddee..61f7fb4cb 100644
--- a/vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
+++ b/vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
@@ -905,14 +905,14 @@ func (h *apiClientImpl) Do(ctx context.Context, req *http.Request) (*http.Respon
 		}
 	}
 
-	if apiError(code) && result.Status == "success" {
+	if apiError(code) != (result.Status == "error") {
 		err = &Error{
 			Type: ErrBadResponse,
 			Msg:  "inconsistent body for response code",
 		}
 	}
 
-	if result.Status == "error" {
+	if apiError(code) && result.Status == "error" {
 		err = &Error{
 			Type: result.ErrorType,
 			Msg:  result.Error,
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/counter.go b/vendor/github.com/prometheus/client_golang/prometheus/counter.go
index 0e1b48c03..df72fcf36 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/counter.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/counter.go
@@ -309,8 +309,6 @@ type CounterFunc interface {
 // provided function must be concurrency-safe. The function should also honor
 // the contract for a Counter (values only go up, not down), but compliance will
 // not be checked.
-//
-// Check out the ExampleGaugeFunc examples for the similar GaugeFunc.
 func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc {
 	return newValueFunc(NewDesc(
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go
index f973398df..e0b935d1f 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go
@@ -33,22 +33,18 @@ var (
 )
 
 type processMemoryCounters struct {
-	// System interface description
-	// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-process_memory_counters_ex
-
-	// Refer to the Golang internal implementation
-	// https://golang.org/src/internal/syscall/windows/psapi_windows.go
+	// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-_process_memory_counters_ex
 	_                          uint32
 	PageFaultCount             uint32
-	PeakWorkingSetSize         uintptr
-	WorkingSetSize             uintptr
-	QuotaPeakPagedPoolUsage    uintptr
-	QuotaPagedPoolUsage        uintptr
-	QuotaPeakNonPagedPoolUsage uintptr
-	QuotaNonPagedPoolUsage     uintptr
-	PagefileUsage              uintptr
-	PeakPagefileUsage          uintptr
-	PrivateUsage               uintptr
+	PeakWorkingSetSize         uint64
+	WorkingSetSize             uint64
+	QuotaPeakPagedPoolUsage    uint64
+	QuotaPagedPoolUsage        uint64
+	QuotaPeakNonPagedPoolUsage uint64
+	QuotaNonPagedPoolUsage     uint64
+	PagefileUsage              uint64
+	PeakPagefileUsage          uint64
+	PrivateUsage               uint64
 }
 
 func getProcessMemoryInfo(handle windows.Handle) (processMemoryCounters, error) {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f1be63896..3c1b80e30 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -275,7 +275,7 @@ github.com/pkg/errors
 github.com/pmezard/go-difflib/difflib
 # github.com/prometheus/alertmanager v0.20.0
 github.com/prometheus/alertmanager/api/v2/models
-# github.com/prometheus/client_golang v1.6.0
+# github.com/prometheus/client_golang v1.5.1
 github.com/prometheus/client_golang/api
 github.com/prometheus/client_golang/api/prometheus/v1
 github.com/prometheus/client_golang/prometheus
@@ -283,7 +283,6 @@ github.com/prometheus/client_golang/prometheus/internal
 github.com/prometheus/client_golang/prometheus/promauto
 github.com/prometheus/client_golang/prometheus/promhttp
 github.com/prometheus/client_golang/prometheus/testutil
-github.com/prometheus/client_golang/prometheus/testutil/promlint
 # github.com/prometheus/client_model v0.2.0
 github.com/prometheus/client_model/go
 # github.com/prometheus/common v0.9.1


Test patch:
diff --git a/cmd/prometheus/query_log_test.go b/cmd/prometheus/query_log_test.go
index eac6f6ec2..12b2f930f 100644
--- a/cmd/prometheus/query_log_test.go
+++ b/cmd/prometheus/query_log_test.go
@@ -246,19 +246,7 @@ func (p *queryLogTest) run(t *testing.T) {
 		p.setQueryLog(t, "")
 	}
 
-	dir, err := ioutil.TempDir("", "query_log_test")
-	testutil.Ok(t, err)
-	defer func() {
-		testutil.Ok(t, os.RemoveAll(dir))
-	}()
-
-	params := append([]string{
-		"-test.main",
-		"--config.file=" + p.configFile.Name(),
-		"--web.enable-lifecycle",
-		fmt.Sprintf("--web.listen-address=%s:%d", p.host, p.port),
-		"--storage.tsdb.path=" + dir,
-	}, p.params()...)
+	params := append([]string{"-test.main", "--config.file=" + p.configFile.Name(), "--web.enable-lifecycle", fmt.Sprintf("--web.listen-address=%s:%d", p.host, p.port)}, p.params()...)
 
 	prom := exec.Command(promPath, params...)
 
diff --git a/storage/remote/queue_manager_test.go b/storage/remote/queue_manager_test.go
index 323ee7e5e..78c7fa3be 100644
--- a/storage/remote/queue_manager_test.go
+++ b/storage/remote/queue_manager_test.go
@@ -22,7 +22,6 @@ import (
 	"reflect"
 	"sort"
 	"strconv"
-	"strings"
 	"sync"
 	"sync/atomic"
 	"testing"
@@ -32,7 +31,6 @@ import (
 	"github.com/gogo/protobuf/proto"
 	"github.com/golang/snappy"
 
-	"github.com/prometheus/client_golang/prometheus"
 	client_testutil "github.com/prometheus/client_golang/prometheus/testutil"
 	"github.com/prometheus/common/model"
 	"github.com/prometheus/prometheus/config"
@@ -62,7 +60,7 @@ func TestSampleDelivery(t *testing.T) {
 	testutil.Ok(t, err)
 	defer os.RemoveAll(dir)
 
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 	m := NewQueueManager(metrics, nil, nil, nil, dir, newEWMARate(ewmaWeight, shardUpdateDuration), cfg, nil, nil, c, defaultFlushDeadline)
 	m.StoreSeries(series, 0)
 
@@ -91,7 +89,7 @@ func TestSampleDeliveryTimeout(t *testing.T) {
 	testutil.Ok(t, err)
 	defer os.RemoveAll(dir)
 
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 	m := NewQueueManager(metrics, nil, nil, nil, dir, newEWMARate(ewmaWeight, shardUpdateDuration), cfg, nil, nil, c, defaultFlushDeadline)
 	m.StoreSeries(series, 0)
 	m.Start()
@@ -132,7 +130,7 @@ func TestSampleDeliveryOrder(t *testing.T) {
 	testutil.Ok(t, err)
 	defer os.RemoveAll(dir)
 
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 	m := NewQueueManager(metrics, nil, nil, nil, dir, newEWMARate(ewmaWeight, shardUpdateDuration), config.DefaultQueueConfig, nil, nil, c, defaultFlushDeadline)
 	m.StoreSeries(series, 0)
 
@@ -151,7 +149,7 @@ func TestShutdown(t *testing.T) {
 	testutil.Ok(t, err)
 	defer os.RemoveAll(dir)
 
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 
 	m := NewQueueManager(metrics, nil, nil, nil, dir, newEWMARate(ewmaWeight, shardUpdateDuration), config.DefaultQueueConfig, nil, nil, c, deadline)
 	n := 2 * config.DefaultQueueConfig.MaxSamplesPerSend
@@ -190,7 +188,7 @@ func TestSeriesReset(t *testing.T) {
 	testutil.Ok(t, err)
 	defer os.RemoveAll(dir)
 
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 	m := NewQueueManager(metrics, nil, nil, nil, dir, newEWMARate(ewmaWeight, shardUpdateDuration), config.DefaultQueueConfig, nil, nil, c, deadline)
 	for i := 0; i < numSegments; i++ {
 		series := []record.RefSeries{}
@@ -220,7 +218,7 @@ func TestReshard(t *testing.T) {
 	testutil.Ok(t, err)
 	defer os.RemoveAll(dir)
 
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 	m := NewQueueManager(metrics, nil, nil, nil, dir, newEWMARate(ewmaWeight, shardUpdateDuration), cfg, nil, nil, c, defaultFlushDeadline)
 	m.StoreSeries(series, 0)
 
@@ -253,7 +251,7 @@ func TestReshardRaceWithStop(t *testing.T) {
 
 	go func() {
 		for {
-			metrics := newQueueManagerMetrics(nil, "", "")
+			metrics := newQueueManagerMetrics(nil)
 			m = NewQueueManager(metrics, nil, nil, nil, "", newEWMARate(ewmaWeight, shardUpdateDuration), config.DefaultQueueConfig, nil, nil, c, defaultFlushDeadline)
 			m.Start()
 			h.Unlock()
@@ -270,7 +268,7 @@ func TestReshardRaceWithStop(t *testing.T) {
 }
 
 func TestReleaseNoninternedString(t *testing.T) {
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 	c := NewTestStorageClient()
 	m := NewQueueManager(metrics, nil, nil, nil, "", newEWMARate(ewmaWeight, shardUpdateDuration), config.DefaultQueueConfig, nil, nil, c, defaultFlushDeadline)
 	m.Start()
@@ -318,7 +316,7 @@ func TestShouldReshard(t *testing.T) {
 		},
 	}
 	for _, c := range cases {
-		metrics := newQueueManagerMetrics(nil, "", "")
+		metrics := newQueueManagerMetrics(nil)
 		client := NewTestStorageClient()
 		m := NewQueueManager(metrics, nil, nil, nil, "", newEWMARate(ewmaWeight, shardUpdateDuration), config.DefaultQueueConfig, nil, nil, client, defaultFlushDeadline)
 		m.numShards = c.startingShards
@@ -526,7 +524,7 @@ func BenchmarkSampleDelivery(b *testing.B) {
 	testutil.Ok(b, err)
 	defer os.RemoveAll(dir)
 
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 	m := NewQueueManager(metrics, nil, nil, nil, dir, newEWMARate(ewmaWeight, shardUpdateDuration), cfg, nil, nil, c, defaultFlushDeadline)
 	m.StoreSeries(series, 0)
 
@@ -567,7 +565,7 @@ func BenchmarkStartup(b *testing.B) {
 	logger = log.With(logger, "caller", log.DefaultCaller)
 
 	for n := 0; n < b.N; n++ {
-		metrics := newQueueManagerMetrics(nil, "", "")
+		metrics := newQueueManagerMetrics(nil)
 		c := NewTestBlockedStorageClient()
 		m := NewQueueManager(metrics, nil, nil, logger, dir,
 			newEWMARate(ewmaWeight, shardUpdateDuration),
@@ -618,7 +616,7 @@ func TestCalculateDesiredShards(t *testing.T) {
 	testutil.Ok(t, err)
 	defer os.RemoveAll(dir)
 
-	metrics := newQueueManagerMetrics(nil, "", "")
+	metrics := newQueueManagerMetrics(nil)
 	samplesIn := newEWMARate(ewmaWeight, shardUpdateDuration)
 	m := NewQueueManager(metrics, nil, nil, nil, dir, samplesIn, cfg, nil, nil, c, defaultFlushDeadline)
 
@@ -651,7 +649,7 @@ func TestCalculateDesiredShards(t *testing.T) {
 
 		// highest sent is how far back pending samples would be at our input rate.
 		highestSent := startedAt.Add(ts - time.Duration(pendingSamples/inputRate)*time.Second)
-		m.metrics.highestSentTimestamp.Set(float64(highestSent.Unix()))
+		m.highestSentTimestampMetric.Set(float64(highestSent.Unix()))
 
 		atomic.StoreInt64(&m.lastSendTimestamp, time.Now().Unix())
 	}
@@ -687,19 +685,3 @@ func TestCalculateDesiredShards(t *testing.T) {
 	}
 	testutil.Assert(t, pendingSamples == 0, "Remote write never caught up, there are still %d pending samples.", pendingSamples)
 }
-
-func TestQueueManagerMetrics(t *testing.T) {
-	reg := prometheus.NewPedanticRegistry()
-	metrics := newQueueManagerMetrics(reg, "name", "http://localhost:1234")
-
-	// Make sure metrics pass linting.
-	problems, err := client_testutil.GatherAndLint(reg)
-	testutil.Ok(t, err)
-	testutil.Equals(t, 0, len(problems), "Metric linting problems detected: %v", problems)
-
-	// Make sure all metrics were unregistered. A failure here means you need
-	// unregister a metric in `queueManagerMetrics.unregister()`.
-	metrics.unregister()
-	err = client_testutil.GatherAndCompare(reg, strings.NewReader(""))
-	testutil.Ok(t, err)
-}
diff --git a/tsdb/block_test.go b/tsdb/block_test.go
index 529ed771a..df18a08ac 100644
--- a/tsdb/block_test.go
+++ b/tsdb/block_test.go
@@ -305,12 +305,7 @@ func TestReadIndexFormatV1(t *testing.T) {
 
 // createBlock creates a block with given set of series and returns its dir.
 func createBlock(tb testing.TB, dir string, series []storage.Series) string {
-	chunkDir, err := ioutil.TempDir("", "chunk_dir")
-	testutil.Ok(tb, err)
-	defer func() { testutil.Ok(tb, os.RemoveAll(chunkDir)) }()
-	head := createHead(tb, series, chunkDir)
-	defer func() { testutil.Ok(tb, head.Close()) }()
-	return createBlockFromHead(tb, dir, head)
+	return createBlockFromHead(tb, dir, createHead(tb, series))
 }
 
 func createBlockFromHead(tb testing.TB, dir string, head *Head) string {
@@ -326,9 +321,10 @@ func createBlockFromHead(tb testing.TB, dir string, head *Head) string {
 	return filepath.Join(dir, ulid.String())
 }
 
-func createHead(tb testing.TB, series []storage.Series, chunkDir string) *Head {
-	head, err := NewHead(nil, nil, nil, 2*60*60*1000, chunkDir, nil, DefaultStripeSize)
+func createHead(tb testing.TB, series []storage.Series) *Head {
+	head, err := NewHead(nil, nil, nil, 2*60*60*1000, DefaultStripeSize)
 	testutil.Ok(tb, err)
+	defer head.Close()
 
 	app := head.Appender()
 	for _, s := range series {
diff --git a/tsdb/chunks/head_chunks_test.go b/tsdb/chunks/head_chunks_test.go
index 073a16955..fbea37aee 100644
--- a/tsdb/chunks/head_chunks_test.go
+++ b/tsdb/chunks/head_chunks_test.go
@@ -39,7 +39,6 @@ func TestHeadReadWriter_WriteChunk_Chunk_IterateChunks(t *testing.T) {
 	type expectedDataType struct {
 		seriesRef, chunkRef uint64
 		mint, maxt          int64
-		numSamples          uint16
 		chunk               chunkenc.Chunk
 	}
 	expectedData := []expectedDataType{}
@@ -52,12 +51,11 @@ func TestHeadReadWriter_WriteChunk_Chunk_IterateChunks(t *testing.T) {
 			seriesRef, chkRef, mint, maxt, chunk := createChunk(t, totalChunks, hrw)
 			totalChunks++
 			expectedData = append(expectedData, expectedDataType{
-				seriesRef:  seriesRef,
-				mint:       mint,
-				maxt:       maxt,
-				chunkRef:   chkRef,
-				chunk:      chunk,
-				numSamples: uint16(chunk.NumSamples()),
+				seriesRef: seriesRef,
+				mint:      mint,
+				maxt:      maxt,
+				chunkRef:  chkRef,
+				chunk:     chunk,
 			})
 
 			if hrw.curFileSequence != 1 {
@@ -130,7 +128,7 @@ func TestHeadReadWriter_WriteChunk_Chunk_IterateChunks(t *testing.T) {
 	testutil.Ok(t, err)
 
 	idx := 0
-	err = hrw.IterateAllChunks(func(seriesRef, chunkRef uint64, mint, maxt int64, numSamples uint16) error {
+	err = hrw.IterateAllChunks(func(seriesRef, chunkRef uint64, mint, maxt int64) error {
 		t.Helper()
 
 		expData := expectedData[idx]
@@ -138,7 +136,6 @@ func TestHeadReadWriter_WriteChunk_Chunk_IterateChunks(t *testing.T) {
 		testutil.Equals(t, expData.chunkRef, chunkRef)
 		testutil.Equals(t, expData.maxt, maxt)
 		testutil.Equals(t, expData.maxt, maxt)
-		testutil.Equals(t, expData.numSamples, numSamples)
 
 		actChunk, err := hrw.Chunk(expData.chunkRef)
 		testutil.Ok(t, err)
@@ -160,7 +157,7 @@ func TestHeadReadWriter_Truncate(t *testing.T) {
 	}()
 
 	testutil.Assert(t, !hrw.fileMaxtSet, "")
-	testutil.Ok(t, hrw.IterateAllChunks(func(_, _ uint64, _, _ int64, _ uint16) error { return nil }))
+	testutil.Ok(t, hrw.IterateAllChunks(func(_, _ uint64, _, _ int64) error { return nil }))
 	testutil.Assert(t, hrw.fileMaxtSet, "")
 
 	timeRange := 0
@@ -230,7 +227,7 @@ func TestHeadReadWriter_Truncate(t *testing.T) {
 	testutil.Ok(t, err)
 
 	testutil.Assert(t, !hrw.fileMaxtSet, "")
-	testutil.Ok(t, hrw.IterateAllChunks(func(_, _ uint64, _, _ int64, _ uint16) error { return nil }))
+	testutil.Ok(t, hrw.IterateAllChunks(func(_, _ uint64, _, _ int64) error { return nil }))
 	testutil.Assert(t, hrw.fileMaxtSet, "")
 
 	// Truncating files after restart.
diff --git a/tsdb/compact_test.go b/tsdb/compact_test.go
index a733c1e99..3b72fe02c 100644
--- a/tsdb/compact_test.go
+++ b/tsdb/compact_test.go
@@ -870,12 +870,7 @@ func BenchmarkCompactionFromHead(b *testing.B) {
 	for labelNames := 1; labelNames < totalSeries; labelNames *= 10 {
 		labelValues := totalSeries / labelNames
 		b.Run(fmt.Sprintf("labelnames=%d,labelvalues=%d", labelNames, labelValues), func(b *testing.B) {
-			chunkDir, err := ioutil.TempDir("", "chunk_dir")
-			testutil.Ok(b, err)
-			defer func() {
-				testutil.Ok(b, os.RemoveAll(chunkDir))
-			}()
-			h, err := NewHead(nil, nil, nil, 1000, chunkDir, nil, DefaultStripeSize)
+			h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
 			testutil.Ok(b, err)
 			for ln := 0; ln < labelNames; ln++ {
 				app := h.Appender()
diff --git a/tsdb/db_test.go b/tsdb/db_test.go
index 6c12147a9..3ce6a3c2a 100644
--- a/tsdb/db_test.go
+++ b/tsdb/db_test.go
@@ -849,15 +849,8 @@ func TestWALSegmentSizeOptions(t *testing.T) {
 	tests := map[int]func(dbdir string, segmentSize int){
 		// Default Wal Size.
 		0: func(dbDir string, segmentSize int) {
-			filesAndDir, err := ioutil.ReadDir(filepath.Join(dbDir, "wal"))
+			files, err := ioutil.ReadDir(filepath.Join(dbDir, "wal"))
 			testutil.Ok(t, err)
-			files := []os.FileInfo{}
-			for _, f := range filesAndDir {
-				if !f.IsDir() {
-					files = append(files, f)
-				}
-			}
-			// All the full segment files (all but the last) should match the segment size option.
 			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())
 			}
@@ -866,16 +859,9 @@ func TestWALSegmentSizeOptions(t *testing.T) {
 		},
 		// Custom Wal Size.
 		2 * 32 * 1024: func(dbDir string, segmentSize int) {
-			filesAndDir, err := ioutil.ReadDir(filepath.Join(dbDir, "wal"))
-			testutil.Ok(t, err)
-			files := []os.FileInfo{}
-			for _, f := range filesAndDir {
-				if !f.IsDir() {
-					files = append(files, f)
-				}
-			}
+			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.")
-			// All the full segment files (all but the last) should match the segment size option.
+			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())
 			}
@@ -884,12 +870,9 @@ func TestWALSegmentSizeOptions(t *testing.T) {
 		},
 		// Wal disabled.
 		-1: func(dbDir string, segmentSize int) {
-			// Check that WAL dir is not there.
-			_, err := os.Stat(filepath.Join(dbDir, "wal"))
-			testutil.NotOk(t, err)
-			// Check that there is chunks dir.
-			_, err = os.Stat(mmappedChunksDir(dbDir))
-			testutil.Ok(t, err)
+			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 {
diff --git a/tsdb/head_bench_test.go b/tsdb/head_bench_test.go
index bd0f24826..c1ada3417 100644
--- a/tsdb/head_bench_test.go
+++ b/tsdb/head_bench_test.go
@@ -14,8 +14,6 @@
 package tsdb
 
 import (
-	"io/ioutil"
-	"os"
 	"strconv"
 	"sync/atomic"
 	"testing"
@@ -25,13 +23,8 @@ import (
 )
 
 func BenchmarkHeadStripeSeriesCreate(b *testing.B) {
-	chunkDir, err := ioutil.TempDir("", "chunk_dir")
-	testutil.Ok(b, err)
-	defer func() {
-		testutil.Ok(b, os.RemoveAll(chunkDir))
-	}()
 	// Put a series, select it. GC it and then access it.
-	h, err := NewHead(nil, nil, nil, 1000, chunkDir, nil, DefaultStripeSize)
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
 	testutil.Ok(b, err)
 	defer h.Close()
 
@@ -41,13 +34,8 @@ func BenchmarkHeadStripeSeriesCreate(b *testing.B) {
 }
 
 func BenchmarkHeadStripeSeriesCreateParallel(b *testing.B) {
-	chunkDir, err := ioutil.TempDir("", "chunk_dir")
-	testutil.Ok(b, err)
-	defer func() {
-		testutil.Ok(b, os.RemoveAll(chunkDir))
-	}()
 	// Put a series, select it. GC it and then access it.
-	h, err := NewHead(nil, nil, nil, 1000, chunkDir, nil, DefaultStripeSize)
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
 	testutil.Ok(b, err)
 	defer h.Close()
 
diff --git a/tsdb/head_test.go b/tsdb/head_test.go
index 7ac9b599b..ed4a4e51d 100644
--- a/tsdb/head_test.go
+++ b/tsdb/head_test.go
@@ -19,6 +19,7 @@ import (
 	"math"
 	"math/rand"
 	"os"
+	"path"
 	"path/filepath"
 	"sort"
 	"strconv"
@@ -41,11 +42,10 @@ import (
 
 func BenchmarkCreateSeries(b *testing.B) {
 	series := genSeries(b.N, 10, 0, 0)
-	h, _, closer := newTestHead(b, 10000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(b, h.Close())
-	}()
+
+	h, err := NewHead(nil, nil, nil, 10000, DefaultStripeSize)
+	testutil.Ok(b, err)
+	defer h.Close()
 
 	b.ReportAllocs()
 	b.ResetTimer()
@@ -173,7 +173,7 @@ func BenchmarkLoadWAL(b *testing.B) {
 
 				// Load the WAL.
 				for i := 0; i < b.N; i++ {
-					h, err := NewHead(nil, nil, w, 1000, w.Dir(), nil, DefaultStripeSize)
+					h, err := NewHead(nil, nil, w, 10000, DefaultStripeSize)
 					testutil.Ok(b, err)
 					h.Init(0)
 				}
@@ -209,15 +209,20 @@ func TestHead_ReadWAL(t *testing.T) {
 					{Ref: 0, Intervals: []tombstones.Interval{{Mint: 99, Maxt: 101}}},
 				},
 			}
-
-			head, w, closer := newTestHead(t, 1000, compress)
-			defer closer()
+			dir, err := ioutil.TempDir("", "test_read_wal")
+			testutil.Ok(t, err)
 			defer func() {
-				testutil.Ok(t, head.Close())
+				testutil.Ok(t, os.RemoveAll(dir))
 			}()
 
+			w, err := wal.New(nil, nil, dir, compress)
+			testutil.Ok(t, err)
+			defer w.Close()
 			populateTestWAL(t, w, entries)
 
+			head, err := NewHead(nil, nil, w, 10000, DefaultStripeSize)
+			testutil.Ok(t, err)
+
 			testutil.Ok(t, head.Init(math.MinInt64))
 			testutil.Equals(t, uint64(101), head.lastSeriesID)
 
@@ -239,78 +244,72 @@ func TestHead_ReadWAL(t *testing.T) {
 				testutil.Ok(t, c.Err())
 				return x
 			}
-			testutil.Equals(t, []sample{{100, 2}, {101, 5}}, expandChunk(s10.iterator(0, nil, head.chunkDiskMapper, nil)))
-			testutil.Equals(t, []sample{{101, 6}}, expandChunk(s50.iterator(0, nil, head.chunkDiskMapper, nil)))
-			testutil.Equals(t, []sample{{100, 3}, {101, 7}}, expandChunk(s100.iterator(0, nil, head.chunkDiskMapper, nil)))
+			testutil.Equals(t, []sample{{100, 2}, {101, 5}}, expandChunk(s10.iterator(0, nil, nil)))
+			testutil.Equals(t, []sample{{101, 6}}, expandChunk(s50.iterator(0, nil, nil)))
+			testutil.Equals(t, []sample{{100, 3}, {101, 7}}, expandChunk(s100.iterator(0, nil, nil)))
 		})
 	}
 }
 
 func TestHead_WALMultiRef(t *testing.T) {
-	head, w, closer := newTestHead(t, 1000, false)
-	defer closer()
-
-	testutil.Ok(t, head.Init(0))
-
-	app := head.Appender()
-	ref1, err := app.Add(labels.FromStrings("foo", "bar"), 100, 1)
+	dir, err := ioutil.TempDir("", "test_wal_multi_ref")
 	testutil.Ok(t, err)
-	testutil.Ok(t, app.Commit())
-	testutil.Equals(t, 1.0, prom_testutil.ToFloat64(head.metrics.chunksCreated))
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
 
-	// Add another sample outside chunk range to mmap a chunk.
-	app = head.Appender()
-	_, err = app.Add(labels.FromStrings("foo", "bar"), 1500, 2)
+	w, err := wal.New(nil, nil, dir, false)
 	testutil.Ok(t, err)
-	testutil.Ok(t, app.Commit())
-	testutil.Equals(t, 2.0, prom_testutil.ToFloat64(head.metrics.chunksCreated))
 
-	testutil.Ok(t, head.Truncate(1600))
+	head, err := NewHead(nil, nil, w, 10000, DefaultStripeSize)
+	testutil.Ok(t, err)
 
-	app = head.Appender()
-	ref2, err := app.Add(labels.FromStrings("foo", "bar"), 1700, 3)
+	testutil.Ok(t, head.Init(0))
+	app := head.Appender()
+	ref1, err := app.Add(labels.FromStrings("foo", "bar"), 100, 1)
 	testutil.Ok(t, err)
 	testutil.Ok(t, app.Commit())
-	testutil.Equals(t, 3.0, prom_testutil.ToFloat64(head.metrics.chunksCreated))
 
-	// Add another sample outside chunk range to mmap a chunk.
+	testutil.Ok(t, head.Truncate(200))
+
 	app = head.Appender()
-	_, err = app.Add(labels.FromStrings("foo", "bar"), 2000, 4)
+	ref2, err := app.Add(labels.FromStrings("foo", "bar"), 300, 2)
 	testutil.Ok(t, err)
 	testutil.Ok(t, app.Commit())
-	testutil.Equals(t, 4.0, prom_testutil.ToFloat64(head.metrics.chunksCreated))
 
-	testutil.Assert(t, ref1 != ref2, "Refs are the same")
+	if ref1 == ref2 {
+		t.Fatal("Refs are the same")
+	}
 	testutil.Ok(t, head.Close())
 
-	w, err = wal.New(nil, nil, w.Dir(), false)
+	w, err = wal.New(nil, nil, dir, false)
 	testutil.Ok(t, err)
 
-	head, err = NewHead(nil, nil, w, 1000, w.Dir(), nil, DefaultStripeSize)
+	head, err = NewHead(nil, nil, w, 10000, DefaultStripeSize)
 	testutil.Ok(t, err)
 	testutil.Ok(t, head.Init(0))
-	defer func() {
-		testutil.Ok(t, head.Close())
-	}()
+	defer head.Close()
 
-	q, err := NewBlockQuerier(head, 0, 2100)
+	q, err := NewBlockQuerier(head, 0, 300)
 	testutil.Ok(t, err)
 	series := query(t, q, labels.MustNewMatcher(labels.MatchEqual, "foo", "bar"))
-	testutil.Equals(t, map[string][]tsdbutil.Sample{`{foo="bar"}`: {
-		sample{100, 1},
-		sample{1500, 2},
-		sample{1700, 3},
-		sample{2000, 4},
-	}}, series)
+	testutil.Equals(t, map[string][]tsdbutil.Sample{`{foo="bar"}`: {sample{100, 1}, sample{300, 2}}}, series)
 }
 
 func TestHead_Truncate(t *testing.T) {
-	h, _, closer := newTestHead(t, 1000, false)
-	defer closer()
+	dir, err := ioutil.TempDir("", "test_truncate")
+	testutil.Ok(t, err)
 	defer func() {
-		testutil.Ok(t, h.Close())
+		testutil.Ok(t, os.RemoveAll(dir))
 	}()
 
+	w, err := wal.New(nil, nil, dir, false)
+	testutil.Ok(t, err)
+
+	h, err := NewHead(nil, nil, w, 10000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
+
 	h.initTime(0)
 
 	s1, _ := h.getOrCreate(1, labels.FromStrings("a", "1", "b", "1"))
@@ -318,35 +317,35 @@ func TestHead_Truncate(t *testing.T) {
 	s3, _ := h.getOrCreate(3, labels.FromStrings("a", "1", "b", "2"))
 	s4, _ := h.getOrCreate(4, labels.FromStrings("a", "2", "b", "2", "c", "1"))
 
-	s1.mmappedChunks = []*mmappedChunk{
-		{minTime: 0, maxTime: 999},
-		{minTime: 1000, maxTime: 1999},
-		{minTime: 2000, maxTime: 2999},
+	s1.chunks = []*memChunk{
+		{minTime: 0, maxTime: 999, chunk: chunkenc.NewXORChunk()},
+		{minTime: 1000, maxTime: 1999, chunk: chunkenc.NewXORChunk()},
+		{minTime: 2000, maxTime: 2999, chunk: chunkenc.NewXORChunk()},
 	}
-	s2.mmappedChunks = []*mmappedChunk{
-		{minTime: 1000, maxTime: 1999},
-		{minTime: 2000, maxTime: 2999},
-		{minTime: 3000, maxTime: 3999},
+	s2.chunks = []*memChunk{
+		{minTime: 1000, maxTime: 1999, chunk: chunkenc.NewXORChunk()},
+		{minTime: 2000, maxTime: 2999, chunk: chunkenc.NewXORChunk()},
+		{minTime: 3000, maxTime: 3999, chunk: chunkenc.NewXORChunk()},
 	}
-	s3.mmappedChunks = []*mmappedChunk{
-		{minTime: 0, maxTime: 999},
-		{minTime: 1000, maxTime: 1999},
+	s3.chunks = []*memChunk{
+		{minTime: 0, maxTime: 999, chunk: chunkenc.NewXORChunk()},
+		{minTime: 1000, maxTime: 1999, chunk: chunkenc.NewXORChunk()},
 	}
-	s4.mmappedChunks = []*mmappedChunk{}
+	s4.chunks = []*memChunk{}
 
 	// Truncation need not be aligned.
 	testutil.Ok(t, h.Truncate(1))
 
 	testutil.Ok(t, h.Truncate(2000))
 
-	testutil.Equals(t, []*mmappedChunk{
-		{minTime: 2000, maxTime: 2999},
-	}, h.series.getByID(s1.ref).mmappedChunks)
+	testutil.Equals(t, []*memChunk{
+		{minTime: 2000, maxTime: 2999, chunk: chunkenc.NewXORChunk()},
+	}, h.series.getByID(s1.ref).chunks)
 
-	testutil.Equals(t, []*mmappedChunk{
-		{minTime: 2000, maxTime: 2999},
-		{minTime: 3000, maxTime: 3999},
-	}, h.series.getByID(s2.ref).mmappedChunks)
+	testutil.Equals(t, []*memChunk{
+		{minTime: 2000, maxTime: 2999, chunk: chunkenc.NewXORChunk()},
+		{minTime: 3000, maxTime: 3999, chunk: chunkenc.NewXORChunk()},
+	}, h.series.getByID(s2.ref).chunks)
 
 	testutil.Assert(t, h.series.getByID(s3.ref) == nil, "")
 	testutil.Assert(t, h.series.getByID(s4.ref) == nil, "")
@@ -383,57 +382,36 @@ func TestHead_Truncate(t *testing.T) {
 // Validate various behaviors brought on by firstChunkID accounting for
 // garbage collected chunks.
 func TestMemSeries_truncateChunks(t *testing.T) {
-	dir, err := ioutil.TempDir("", "truncate_chunks")
-	testutil.Ok(t, err)
-	defer func() {
-		testutil.Ok(t, os.RemoveAll(dir))
-	}()
-	// This is usually taken from the Head, but passing manually here.
-	chunkDiskMapper, err := chunks.NewChunkDiskMapper(dir, chunkenc.NewPool())
-	testutil.Ok(t, err)
-	defer func() {
-		testutil.Ok(t, chunkDiskMapper.Close())
-	}()
-
-	memChunkPool := sync.Pool{
-		New: func() interface{} {
-			return &memChunk{}
-		},
-	}
-
-	s := newMemSeries(labels.FromStrings("a", "b"), 1, 2000, &memChunkPool)
+	s := newMemSeries(labels.FromStrings("a", "b"), 1, 2000)
 
 	for i := 0; i < 4000; i += 5 {
-		ok, _ := s.append(int64(i), float64(i), 0, chunkDiskMapper)
+		ok, _ := s.append(int64(i), float64(i), 0)
 		testutil.Assert(t, ok == true, "sample append failed")
 	}
 
 	// Check that truncate removes half of the chunks and afterwards
 	// that the ID of the last chunk still gives us the same chunk afterwards.
-	countBefore := len(s.mmappedChunks) + 1 // +1 for the head chunk.
+	countBefore := len(s.chunks)
 	lastID := s.chunkID(countBefore - 1)
-	lastChunk, _ := s.chunk(lastID, chunkDiskMapper)
+	lastChunk := s.chunk(lastID)
 
-	chk, _ := s.chunk(0, chunkDiskMapper)
-	testutil.Assert(t, chk != nil, "")
+	testutil.Assert(t, s.chunk(0) != nil, "")
 	testutil.Assert(t, lastChunk != nil, "")
 
 	s.truncateChunksBefore(2000)
 
-	testutil.Equals(t, int64(2000), s.mmappedChunks[0].minTime)
-	chk, _ = s.chunk(0, chunkDiskMapper)
-	testutil.Assert(t, chk == nil, "first chunks not gone")
-	testutil.Equals(t, countBefore/2, len(s.mmappedChunks)+1) // +1 for the head chunk.
-	chk, _ = s.chunk(lastID, chunkDiskMapper)
-	testutil.Equals(t, lastChunk, chk)
+	testutil.Equals(t, int64(2000), s.chunks[0].minTime)
+	testutil.Assert(t, s.chunk(0) == nil, "first chunks not gone")
+	testutil.Equals(t, countBefore/2, len(s.chunks))
+	testutil.Equals(t, lastChunk, s.chunk(lastID))
 
 	// Validate that the series' sample buffer is applied correctly to the last chunk
 	// after truncation.
-	it1 := s.iterator(s.chunkID(len(s.mmappedChunks)), nil, chunkDiskMapper, nil)
+	it1 := s.iterator(s.chunkID(len(s.chunks)-1), nil, nil)
 	_, ok := it1.(*memSafeIterator)
 	testutil.Assert(t, ok == true, "")
 
-	it2 := s.iterator(s.chunkID(len(s.mmappedChunks)-1), nil, chunkDiskMapper, nil)
+	it2 := s.iterator(s.chunkID(len(s.chunks)-2), nil, nil)
 	_, ok = it2.(*memSafeIterator)
 	testutil.Assert(t, ok == false, "non-last chunk incorrectly wrapped with sample buffer")
 }
@@ -454,14 +432,20 @@ func TestHeadDeleteSeriesWithoutSamples(t *testing.T) {
 					{Ref: 50, T: 90, V: 1},
 				},
 			}
-			head, w, closer := newTestHead(t, 1000, compress)
-			defer closer()
+			dir, err := ioutil.TempDir("", "test_delete_series")
+			testutil.Ok(t, err)
 			defer func() {
-				testutil.Ok(t, head.Close())
+				testutil.Ok(t, os.RemoveAll(dir))
 			}()
 
+			w, err := wal.New(nil, nil, dir, compress)
+			testutil.Ok(t, err)
+			defer w.Close()
 			populateTestWAL(t, w, entries)
 
+			head, err := NewHead(nil, nil, w, 1000, DefaultStripeSize)
+			testutil.Ok(t, err)
+
 			testutil.Ok(t, head.Init(math.MinInt64))
 
 			testutil.Ok(t, head.Delete(0, 100, labels.MustNewMatcher(labels.MatchEqual, "a", "1")))
@@ -522,12 +506,23 @@ func TestHeadDeleteSimple(t *testing.T) {
 	for _, compress := range []bool{false, true} {
 		t.Run(fmt.Sprintf("compress=%t", compress), func(t *testing.T) {
 			for _, c := range cases {
-				head, w, closer := newTestHead(t, 1000, compress)
-				defer closer()
+				dir, err := ioutil.TempDir("", "test_wal_reload")
+				testutil.Ok(t, err)
+				defer func() {
+					testutil.Ok(t, os.RemoveAll(dir))
+				}()
+
+				w, err := wal.New(nil, nil, path.Join(dir, "wal"), compress)
+				testutil.Ok(t, err)
+				defer w.Close()
+
+				head, err := NewHead(nil, nil, w, 1000, DefaultStripeSize)
+				testutil.Ok(t, err)
+				defer head.Close()
 
 				app := head.Appender()
 				for _, smpl := range smplsAll {
-					_, err := app.Add(labels.Labels{lblDefault}, smpl.t, smpl.v)
+					_, err = app.Add(labels.Labels{lblDefault}, smpl.t, smpl.v)
 					testutil.Ok(t, err)
 
 				}
@@ -541,7 +536,7 @@ func TestHeadDeleteSimple(t *testing.T) {
 				// Add more samples.
 				app = head.Appender()
 				for _, smpl := range c.addSamples {
-					_, err := app.Add(labels.Labels{lblDefault}, smpl.t, smpl.v)
+					_, err = app.Add(labels.Labels{lblDefault}, smpl.t, smpl.v)
 					testutil.Ok(t, err)
 
 				}
@@ -550,8 +545,10 @@ func TestHeadDeleteSimple(t *testing.T) {
 				// Compare the samples for both heads - before and after the reload.
 				reloadedW, err := wal.New(nil, nil, w.Dir(), compress) // Use a new wal to ensure deleted samples are gone even after a reload.
 				testutil.Ok(t, err)
-				reloadedHead, err := NewHead(nil, nil, reloadedW, 1000, reloadedW.Dir(), nil, DefaultStripeSize)
+				defer reloadedW.Close()
+				reloadedHead, err := NewHead(nil, nil, reloadedW, 1000, DefaultStripeSize)
 				testutil.Ok(t, err)
+				defer reloadedHead.Close()
 				testutil.Ok(t, reloadedHead.Init(0))
 
 				// Compare the query results for both heads - before and after the reload.
@@ -562,7 +559,6 @@ func TestHeadDeleteSimple(t *testing.T) {
 					actSeriesSet, ws, err := q.Select(false, nil, labels.MustNewMatcher(labels.MatchEqual, lblDefault.Name, lblDefault.Value))
 					testutil.Ok(t, err)
 					testutil.Equals(t, 0, len(ws))
-					testutil.Ok(t, q.Close())
 					expSeriesSet := newMockSeriesSet([]storage.Series{
 						newSeries(map[string]string{lblDefault.Name: lblDefault.Value}, func() []tsdbutil.Sample {
 							ss := make([]tsdbutil.Sample, 0, len(c.smplsExp))
@@ -600,13 +596,10 @@ func TestHeadDeleteSimple(t *testing.T) {
 }
 
 func TestDeleteUntilCurMax(t *testing.T) {
-	hb, _, closer := newTestHead(t, 1000000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, hb.Close())
-	}()
-
 	numSamples := int64(10)
+	hb, err := NewHead(nil, nil, nil, 1000000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer hb.Close()
 	app := hb.Appender()
 	smpls := make([]float64, numSamples)
 	for i := int64(0); i < numSamples; i++ {
@@ -647,12 +640,19 @@ func TestDeleteUntilCurMax(t *testing.T) {
 }
 
 func TestDeletedSamplesAndSeriesStillInWALAfterCheckpoint(t *testing.T) {
-	numSamples := 10000
+	dir, err := ioutil.TempDir("", "test_delete_wal")
+	testutil.Ok(t, err)
+	defer func() {
+		testutil.Ok(t, os.RemoveAll(dir))
+	}()
+	wlog, err := wal.NewSize(nil, nil, dir, 32768, false)
+	testutil.Ok(t, err)
 
 	// Enough samples to cause a checkpoint.
-	hb, w, closer := newTestHead(t, int64(numSamples)*10, false)
-	defer closer()
-
+	numSamples := 10000
+	hb, err := NewHead(nil, nil, wlog, int64(numSamples)*10, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer hb.Close()
 	for i := 0; i < numSamples; i++ {
 		app := hb.Appender()
 		_, err := app.Add(labels.Labels{{Name: "a", Value: "b"}}, int64(i), 0)
@@ -664,11 +664,11 @@ func TestDeletedSamplesAndSeriesStillInWALAfterCheckpoint(t *testing.T) {
 	testutil.Ok(t, hb.Close())
 
 	// Confirm there's been a checkpoint.
-	cdir, _, err := wal.LastCheckpoint(w.Dir())
+	cdir, _, err := wal.LastCheckpoint(dir)
 	testutil.Ok(t, err)
 	// Read in checkpoint and WAL.
 	recs := readTestWAL(t, cdir)
-	recs = append(recs, readTestWAL(t, w.Dir())...)
+	recs = append(recs, readTestWAL(t, dir)...)
 
 	var series, samples, stones int
 	for _, rec := range recs {
@@ -740,13 +740,13 @@ func TestDelete_e2e(t *testing.T) {
 	for _, l := range lbls {
 		seriesMap[labels.New(l...).String()] = []tsdbutil.Sample{}
 	}
-
-	hb, _, closer := newTestHead(t, 100000, false)
-	defer closer()
+	dir, _ := ioutil.TempDir("", "test")
 	defer func() {
-		testutil.Ok(t, hb.Close())
+		testutil.Ok(t, os.RemoveAll(dir))
 	}()
-
+	hb, err := NewHead(nil, nil, nil, 100000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer hb.Close()
 	app := hb.Appender()
 	for _, l := range lbls {
 		ls := labels.New(l...)
@@ -926,85 +926,68 @@ func TestComputeChunkEndTime(t *testing.T) {
 }
 
 func TestMemSeries_append(t *testing.T) {
-	dir, err := ioutil.TempDir("", "append")
-	testutil.Ok(t, err)
-	defer func() {
-		testutil.Ok(t, os.RemoveAll(dir))
-	}()
-	// This is usually taken from the Head, but passing manually here.
-	chunkDiskMapper, err := chunks.NewChunkDiskMapper(dir, chunkenc.NewPool())
-	testutil.Ok(t, err)
-	defer func() {
-		testutil.Ok(t, chunkDiskMapper.Close())
-	}()
-
-	s := newMemSeries(labels.Labels{}, 1, 500, nil)
+	s := newMemSeries(labels.Labels{}, 1, 500)
 
 	// Add first two samples at the very end of a chunk range and the next two
 	// on and after it.
 	// New chunk must correctly be cut at 1000.
-	ok, chunkCreated := s.append(998, 1, 0, chunkDiskMapper)
+	ok, chunkCreated := s.append(998, 1, 0)
 	testutil.Assert(t, ok, "append failed")
 	testutil.Assert(t, chunkCreated, "first sample created chunk")
 
-	ok, chunkCreated = s.append(999, 2, 0, chunkDiskMapper)
+	ok, chunkCreated = s.append(999, 2, 0)
 	testutil.Assert(t, ok, "append failed")
 	testutil.Assert(t, !chunkCreated, "second sample should use same chunk")
 
-	ok, chunkCreated = s.append(1000, 3, 0, chunkDiskMapper)
+	ok, chunkCreated = s.append(1000, 3, 0)
 	testutil.Assert(t, ok, "append failed")
 	testutil.Assert(t, chunkCreated, "expected new chunk on boundary")
 
-	ok, chunkCreated = s.append(1001, 4, 0, chunkDiskMapper)
+	ok, chunkCreated = s.append(1001, 4, 0)
 	testutil.Assert(t, ok, "append failed")
 	testutil.Assert(t, !chunkCreated, "second sample should use same chunk")
 
-	testutil.Assert(t, len(s.mmappedChunks) == 1, "there should be only 1 mmapped chunk")
-	testutil.Assert(t, s.mmappedChunks[0].minTime == 998 && s.mmappedChunks[0].maxTime == 999, "wrong chunk range")
-	testutil.Assert(t, s.headChunk.minTime == 1000 && s.headChunk.maxTime == 1001, "wrong chunk range")
+	testutil.Assert(t, s.chunks[0].minTime == 998 && s.chunks[0].maxTime == 999, "wrong chunk range")
+	testutil.Assert(t, s.chunks[1].minTime == 1000 && s.chunks[1].maxTime == 1001, "wrong chunk range")
 
 	// Fill the range [1000,2000) with many samples. Intermediate chunks should be cut
 	// at approximately 120 samples per chunk.
 	for i := 1; i < 1000; i++ {
-		ok, _ := s.append(1001+int64(i), float64(i), 0, chunkDiskMapper)
+		ok, _ := s.append(1001+int64(i), float64(i), 0)
 		testutil.Assert(t, ok, "append failed")
 	}
 
-	testutil.Assert(t, len(s.mmappedChunks)+1 > 7, "expected intermediate chunks")
+	testutil.Assert(t, len(s.chunks) > 7, "expected intermediate chunks")
 
 	// All chunks but the first and last should now be moderately full.
-	for i, c := range s.mmappedChunks[1:] {
-		chk, err := chunkDiskMapper.Chunk(c.ref)
-		testutil.Ok(t, err)
-		testutil.Assert(t, chk.NumSamples() > 100, "unexpected small chunk %d of length %d", i, chk.NumSamples())
+	for i, c := range s.chunks[1 : len(s.chunks)-1] {
+		testutil.Assert(t, c.chunk.NumSamples() > 100, "unexpected small chunk %d of length %d", i, c.chunk.NumSamples())
 	}
 }
 
 func TestGCChunkAccess(t *testing.T) {
 	// Put a chunk, select it. GC it and then access it.
-	h, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, h.Close())
-	}()
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
 
 	h.initTime(0)
 
 	s, _ := h.getOrCreate(1, labels.FromStrings("a", "1"))
 
 	// Appending 2 samples for the first chunk.
-	ok, chunkCreated := s.append(0, 0, 0, h.chunkDiskMapper)
+	ok, chunkCreated := s.append(0, 0, 0)
 	testutil.Assert(t, ok, "series append failed")
 	testutil.Assert(t, chunkCreated, "chunks was not created")
-	ok, chunkCreated = s.append(999, 999, 0, h.chunkDiskMapper)
+	ok, chunkCreated = s.append(999, 999, 0)
 	testutil.Assert(t, ok, "series append failed")
 	testutil.Assert(t, !chunkCreated, "chunks was created")
 
 	// A new chunks should be created here as it's beyond the chunk range.
-	ok, chunkCreated = s.append(1000, 1000, 0, h.chunkDiskMapper)
+	ok, chunkCreated = s.append(1000, 1000, 0)
 	testutil.Assert(t, ok, "series append failed")
 	testutil.Assert(t, chunkCreated, "chunks was not created")
-	ok, chunkCreated = s.append(1999, 1999, 0, h.chunkDiskMapper)
+	ok, chunkCreated = s.append(1999, 1999, 0)
 	testutil.Assert(t, ok, "series append failed")
 	testutil.Assert(t, !chunkCreated, "chunks was created")
 
@@ -1021,7 +1004,7 @@ func TestGCChunkAccess(t *testing.T) {
 	testutil.Equals(t, 2, len(chunks))
 
 	cr := h.chunksRange(0, 1500, nil)
-	_, err := cr.Chunk(chunks[0].Ref)
+	_, err = cr.Chunk(chunks[0].Ref)
 	testutil.Ok(t, err)
 	_, err = cr.Chunk(chunks[1].Ref)
 	testutil.Ok(t, err)
@@ -1036,29 +1019,27 @@ func TestGCChunkAccess(t *testing.T) {
 
 func TestGCSeriesAccess(t *testing.T) {
 	// Put a series, select it. GC it and then access it.
-	h, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, h.Close())
-	}()
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
 
 	h.initTime(0)
 
 	s, _ := h.getOrCreate(1, labels.FromStrings("a", "1"))
 
 	// Appending 2 samples for the first chunk.
-	ok, chunkCreated := s.append(0, 0, 0, h.chunkDiskMapper)
+	ok, chunkCreated := s.append(0, 0, 0)
 	testutil.Assert(t, ok, "series append failed")
 	testutil.Assert(t, chunkCreated, "chunks was not created")
-	ok, chunkCreated = s.append(999, 999, 0, h.chunkDiskMapper)
+	ok, chunkCreated = s.append(999, 999, 0)
 	testutil.Assert(t, ok, "series append failed")
 	testutil.Assert(t, !chunkCreated, "chunks was created")
 
 	// A new chunks should be created here as it's beyond the chunk range.
-	ok, chunkCreated = s.append(1000, 1000, 0, h.chunkDiskMapper)
+	ok, chunkCreated = s.append(1000, 1000, 0)
 	testutil.Assert(t, ok, "series append failed")
 	testutil.Assert(t, chunkCreated, "chunks was not created")
-	ok, chunkCreated = s.append(1999, 1999, 0, h.chunkDiskMapper)
+	ok, chunkCreated = s.append(1999, 1999, 0)
 	testutil.Assert(t, ok, "series append failed")
 	testutil.Assert(t, !chunkCreated, "chunks was created")
 
@@ -1075,7 +1056,7 @@ func TestGCSeriesAccess(t *testing.T) {
 	testutil.Equals(t, 2, len(chunks))
 
 	cr := h.chunksRange(0, 2000, nil)
-	_, err := cr.Chunk(chunks[0].Ref)
+	_, err = cr.Chunk(chunks[0].Ref)
 	testutil.Ok(t, err)
 	_, err = cr.Chunk(chunks[1].Ref)
 	testutil.Ok(t, err)
@@ -1091,17 +1072,15 @@ func TestGCSeriesAccess(t *testing.T) {
 }
 
 func TestUncommittedSamplesNotLostOnTruncate(t *testing.T) {
-	h, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, h.Close())
-	}()
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
 
 	h.initTime(0)
 
 	app := h.appender()
 	lset := labels.FromStrings("a", "1")
-	_, err := app.Add(lset, 2100, 1)
+	_, err = app.Add(lset, 2100, 1)
 	testutil.Ok(t, err)
 
 	testutil.Ok(t, h.Truncate(2000))
@@ -1121,17 +1100,15 @@ func TestUncommittedSamplesNotLostOnTruncate(t *testing.T) {
 }
 
 func TestRemoveSeriesAfterRollbackAndTruncate(t *testing.T) {
-	h, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, h.Close())
-	}()
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
 
 	h.initTime(0)
 
 	app := h.appender()
 	lset := labels.FromStrings("a", "1")
-	_, err := app.Add(lset, 2100, 1)
+	_, err = app.Add(lset, 2100, 1)
 	testutil.Ok(t, err)
 
 	testutil.Ok(t, h.Truncate(2000))
@@ -1157,14 +1134,20 @@ func TestRemoveSeriesAfterRollbackAndTruncate(t *testing.T) {
 func TestHead_LogRollback(t *testing.T) {
 	for _, compress := range []bool{false, true} {
 		t.Run(fmt.Sprintf("compress=%t", compress), func(t *testing.T) {
-			h, w, closer := newTestHead(t, 1000, compress)
-			defer closer()
+			dir, err := ioutil.TempDir("", "wal_rollback")
+			testutil.Ok(t, err)
 			defer func() {
-				testutil.Ok(t, h.Close())
+				testutil.Ok(t, os.RemoveAll(dir))
 			}()
 
+			w, err := wal.New(nil, nil, dir, compress)
+			testutil.Ok(t, err)
+			defer w.Close()
+			h, err := NewHead(nil, nil, w, 1000, DefaultStripeSize)
+			testutil.Ok(t, err)
+
 			app := h.Appender()
-			_, err := app.Add(labels.FromStrings("a", "b"), 1, 2)
+			_, err = app.Add(labels.FromStrings("a", "b"), 1, 2)
 			testutil.Ok(t, err)
 
 			testutil.Ok(t, app.Rollback())
@@ -1248,7 +1231,7 @@ func TestWalRepair_DecodingError(t *testing.T) {
 						testutil.Ok(t, w.Log(test.rec))
 					}
 
-					h, err := NewHead(nil, nil, w, 1, w.Dir(), nil, DefaultStripeSize)
+					h, err := NewHead(nil, nil, w, 1, DefaultStripeSize)
 					testutil.Ok(t, err)
 					testutil.Equals(t, 0.0, prom_testutil.ToFloat64(h.metrics.walCorruptionsTotal))
 					initErr := h.Init(math.MinInt64)
@@ -1288,78 +1271,18 @@ func TestWalRepair_DecodingError(t *testing.T) {
 	}
 }
 
-func TestHeadReadWriterRepair(t *testing.T) {
-	dir, err := ioutil.TempDir("", "head_read_writer_repair")
+func TestNewWalSegmentOnTruncate(t *testing.T) {
+	dir, err := ioutil.TempDir("", "test_wal_segments")
 	testutil.Ok(t, err)
 	defer func() {
 		testutil.Ok(t, os.RemoveAll(dir))
 	}()
+	wlog, err := wal.NewSize(nil, nil, dir, 32768, false)
+	testutil.Ok(t, err)
 
-	const chunkRange = chunks.DefaultHeadChunkFileMaxTimeRange // to hold 4 chunks per segment.
-
-	walDir := filepath.Join(dir, "wal")
-	// Fill the chunk segments and corrupt it.
-	{
-		w, err := wal.New(nil, nil, walDir, false)
-		testutil.Ok(t, err)
-
-		h, err := NewHead(nil, nil, w, chunkRange, dir, nil, DefaultStripeSize)
-		testutil.Ok(t, err)
-		testutil.Equals(t, 0.0, prom_testutil.ToFloat64(h.metrics.mmapChunkCorruptionTotal))
-		testutil.Ok(t, h.Init(math.MinInt64))
-
-		s, created := h.getOrCreate(1, labels.FromStrings("a", "1"))
-		testutil.Assert(t, created, "series was not created")
-
-		for i := 0; i < 7; i++ {
-			ok, chunkCreated := s.append(int64(i*int(chunkRange)), float64(i*int(chunkRange)), 0, h.chunkDiskMapper)
-			testutil.Assert(t, ok, "series append failed")
-			testutil.Assert(t, chunkCreated, "chunk was not created")
-			ok, chunkCreated = s.append(int64(i*int(chunkRange))+chunkRange-1, float64(i*int(chunkRange)), 0, h.chunkDiskMapper)
-			testutil.Assert(t, ok, "series append failed")
-			testutil.Assert(t, !chunkCreated, "chunk was created")
-		}
-		testutil.Ok(t, h.Close())
-
-		// Verify that there are 6 segment files.
-		files, err := ioutil.ReadDir(mmappedChunksDir(dir))
-		testutil.Ok(t, err)
-		testutil.Equals(t, 6, len(files))
-
-		// Corrupt the 4th file by writing a random byte to series ref.
-		f, err := os.OpenFile(filepath.Join(mmappedChunksDir(dir), files[3].Name()), os.O_WRONLY, 0666)
-		testutil.Ok(t, err)
-		n, err := f.WriteAt([]byte{67, 88}, chunks.HeadChunkFileHeaderSize+2)
-		testutil.Ok(t, err)
-		testutil.Equals(t, 2, n)
-		testutil.Ok(t, f.Close())
-	}
-
-	// Open the db to trigger a repair.
-	{
-		db, err := Open(dir, nil, nil, DefaultOptions())
-		testutil.Ok(t, err)
-		defer func() {
-			testutil.Ok(t, db.Close())
-		}()
-		testutil.Equals(t, 1.0, prom_testutil.ToFloat64(db.head.metrics.mmapChunkCorruptionTotal))
-	}
-
-	// Verify that there are 3 segment files after the repair.
-	// The segments from the corrupt segment should be removed.
-	{
-		files, err := ioutil.ReadDir(mmappedChunksDir(dir))
-		testutil.Ok(t, err)
-		testutil.Equals(t, 3, len(files))
-	}
-}
-
-func TestNewWalSegmentOnTruncate(t *testing.T) {
-	h, wlog, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, h.Close())
-	}()
+	h, err := NewHead(nil, nil, wlog, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
 	add := func(ts int64) {
 		app := h.Appender()
 		_, err := app.Add(labels.Labels{{Name: "a", Value: "b"}}, ts, 0)
@@ -1386,15 +1309,21 @@ func TestNewWalSegmentOnTruncate(t *testing.T) {
 }
 
 func TestAddDuplicateLabelName(t *testing.T) {
-	h, _, closer := newTestHead(t, 1000, false)
-	defer closer()
+	dir, err := ioutil.TempDir("", "test_duplicate_label_name")
+	testutil.Ok(t, err)
 	defer func() {
-		testutil.Ok(t, h.Close())
+		testutil.Ok(t, os.RemoveAll(dir))
 	}()
+	wlog, err := wal.NewSize(nil, nil, dir, 32768, false)
+	testutil.Ok(t, err)
+
+	h, err := NewHead(nil, nil, wlog, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
 
 	add := func(labels labels.Labels, labelName string) {
 		app := h.Appender()
-		_, err := app.Add(labels, 0, 0)
+		_, err = app.Add(labels, 0, 0)
 		testutil.NotOk(t, err)
 		testutil.Equals(t, fmt.Sprintf(`label name "%s" is not unique: invalid sample`, labelName), err.Error())
 	}
@@ -1406,20 +1335,22 @@ func TestAddDuplicateLabelName(t *testing.T) {
 
 func TestMemSeriesIsolation(t *testing.T) {
 	// Put a series, select it. GC it and then access it.
+	hb, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer hb.Close()
 
-	lastValue := func(h *Head, maxAppendID uint64) int {
-		idx, err := h.Index()
-
+	lastValue := func(maxAppendID uint64) int {
+		idx, err := hb.Index()
 		testutil.Ok(t, err)
 
-		iso := h.iso.State()
+		iso := hb.iso.State()
 		iso.maxAppendID = maxAppendID
 
 		querier := &blockQuerier{
 			mint:       0,
 			maxt:       10000,
 			index:      idx,
-			chunks:     h.chunksRange(math.MinInt64, math.MaxInt64, iso),
+			chunks:     hb.chunksRange(math.MinInt64, math.MaxInt64, iso),
 			tombstones: tombstones.NewMemTombstones(),
 		}
 
@@ -1437,61 +1368,50 @@ func TestMemSeriesIsolation(t *testing.T) {
 		return -1
 	}
 
-	addSamples := func(h *Head) int {
-		i := 1
-		for ; i <= 1000; i++ {
-			var app storage.Appender
-			// To initialize bounds.
-			if h.MinTime() == math.MaxInt64 {
-				app = &initAppender{head: h}
-			} else {
-				a := h.appender()
-				a.cleanupAppendIDsBelow = 0
-				app = a
-			}
-
-			_, err := app.Add(labels.FromStrings("foo", "bar"), int64(i), float64(i))
-			testutil.Ok(t, err)
-			testutil.Ok(t, app.Commit())
+	i := 1
+	for ; i <= 1000; i++ {
+		var app storage.Appender
+		// To initialize bounds.
+		if hb.MinTime() == math.MaxInt64 {
+			app = &initAppender{head: hb}
+		} else {
+			a := hb.appender()
+			a.cleanupAppendIDsBelow = 0
+			app = a
 		}
-		return i
-	}
 
-	testIsolation := func(h *Head, i int) {
+		_, err := app.Add(labels.FromStrings("foo", "bar"), int64(i), float64(i))
+		testutil.Ok(t, err)
+		testutil.Ok(t, app.Commit())
 	}
 
-	// Test isolation without restart of Head.
-	hb, _, closer := newTestHead(t, 1000, false)
-	i := addSamples(hb)
-	testIsolation(hb, i)
-
 	// Test simple cases in different chunks when no appendID cleanup has been performed.
-	testutil.Equals(t, 10, lastValue(hb, 10))
-	testutil.Equals(t, 130, lastValue(hb, 130))
-	testutil.Equals(t, 160, lastValue(hb, 160))
-	testutil.Equals(t, 240, lastValue(hb, 240))
-	testutil.Equals(t, 500, lastValue(hb, 500))
-	testutil.Equals(t, 750, lastValue(hb, 750))
-	testutil.Equals(t, 995, lastValue(hb, 995))
-	testutil.Equals(t, 999, lastValue(hb, 999))
+	testutil.Equals(t, 10, lastValue(10))
+	testutil.Equals(t, 130, lastValue(130))
+	testutil.Equals(t, 160, lastValue(160))
+	testutil.Equals(t, 240, lastValue(240))
+	testutil.Equals(t, 500, lastValue(500))
+	testutil.Equals(t, 750, lastValue(750))
+	testutil.Equals(t, 995, lastValue(995))
+	testutil.Equals(t, 999, lastValue(999))
 
 	// Cleanup appendIDs below 500.
 	app := hb.appender()
 	app.cleanupAppendIDsBelow = 500
-	_, err := app.Add(labels.FromStrings("foo", "bar"), int64(i), float64(i))
+	_, err = app.Add(labels.FromStrings("foo", "bar"), int64(i), float64(i))
 	testutil.Ok(t, err)
 	testutil.Ok(t, app.Commit())
 	i++
 
 	// We should not get queries with a maxAppendID below 500 after the cleanup,
 	// but they only take the remaining appendIDs into account.
-	testutil.Equals(t, 499, lastValue(hb, 10))
-	testutil.Equals(t, 499, lastValue(hb, 130))
-	testutil.Equals(t, 499, lastValue(hb, 160))
-	testutil.Equals(t, 499, lastValue(hb, 240))
-	testutil.Equals(t, 500, lastValue(hb, 500))
-	testutil.Equals(t, 995, lastValue(hb, 995))
-	testutil.Equals(t, 999, lastValue(hb, 999))
+	testutil.Equals(t, 499, lastValue(10))
+	testutil.Equals(t, 499, lastValue(130))
+	testutil.Equals(t, 499, lastValue(160))
+	testutil.Equals(t, 499, lastValue(240))
+	testutil.Equals(t, 500, lastValue(500))
+	testutil.Equals(t, 995, lastValue(995))
+	testutil.Equals(t, 999, lastValue(999))
 
 	// Cleanup appendIDs below 1000, which means the sample buffer is
 	// the only thing with appendIDs.
@@ -1500,12 +1420,12 @@ func TestMemSeriesIsolation(t *testing.T) {
 	_, err = app.Add(labels.FromStrings("foo", "bar"), int64(i), float64(i))
 	testutil.Ok(t, err)
 	testutil.Ok(t, app.Commit())
-	testutil.Equals(t, 999, lastValue(hb, 998))
-	testutil.Equals(t, 999, lastValue(hb, 999))
-	testutil.Equals(t, 1000, lastValue(hb, 1000))
-	testutil.Equals(t, 1001, lastValue(hb, 1001))
-	testutil.Equals(t, 1002, lastValue(hb, 1002))
-	testutil.Equals(t, 1002, lastValue(hb, 1003))
+	testutil.Equals(t, 999, lastValue(998))
+	testutil.Equals(t, 999, lastValue(999))
+	testutil.Equals(t, 1000, lastValue(1000))
+	testutil.Equals(t, 1001, lastValue(1001))
+	testutil.Equals(t, 1002, lastValue(1002))
+	testutil.Equals(t, 1002, lastValue(1003))
 
 	i++
 	// Cleanup appendIDs below 1001, but with a rollback.
@@ -1514,71 +1434,21 @@ func TestMemSeriesIsolation(t *testing.T) {
 	_, err = app.Add(labels.FromStrings("foo", "bar"), int64(i), float64(i))
 	testutil.Ok(t, err)
 	testutil.Ok(t, app.Rollback())
-	testutil.Equals(t, 1000, lastValue(hb, 999))
-	testutil.Equals(t, 1000, lastValue(hb, 1000))
-	testutil.Equals(t, 1001, lastValue(hb, 1001))
-	testutil.Equals(t, 1002, lastValue(hb, 1002))
-	testutil.Equals(t, 1002, lastValue(hb, 1003))
-
-	testutil.Ok(t, hb.Close())
-	closer()
-
-	// Test isolation with restart of Head. This is to verify the num samples of chunks after m-map chunk replay.
-	hb, w, closer := newTestHead(t, 1000, false)
-	defer closer()
-	i = addSamples(hb)
-	testutil.Ok(t, hb.Close())
-
-	wlog, err := wal.NewSize(nil, nil, w.Dir(), 32768, false)
-	testutil.Ok(t, err)
-	hb, err = NewHead(nil, nil, wlog, 1000, wlog.Dir(), nil, DefaultStripeSize)
-	defer func() { testutil.Ok(t, hb.Close()) }()
-	testutil.Ok(t, err)
-	testutil.Ok(t, hb.Init(0))
-
-	// No appends after restarting. Hence all should return the last value.
-	testutil.Equals(t, 1000, lastValue(hb, 10))
-	testutil.Equals(t, 1000, lastValue(hb, 130))
-	testutil.Equals(t, 1000, lastValue(hb, 160))
-	testutil.Equals(t, 1000, lastValue(hb, 240))
-	testutil.Equals(t, 1000, lastValue(hb, 500))
-
-	// Cleanup appendIDs below 1000, which means the sample buffer is
-	// the only thing with appendIDs.
-	app = hb.appender()
-	_, err = app.Add(labels.FromStrings("foo", "bar"), int64(i), float64(i))
-	i++
-	testutil.Ok(t, err)
-	testutil.Ok(t, app.Commit())
-	testutil.Equals(t, 1001, lastValue(hb, 998))
-	testutil.Equals(t, 1001, lastValue(hb, 999))
-	testutil.Equals(t, 1001, lastValue(hb, 1000))
-	testutil.Equals(t, 1001, lastValue(hb, 1001))
-	testutil.Equals(t, 1001, lastValue(hb, 1002))
-	testutil.Equals(t, 1001, lastValue(hb, 1003))
-
-	// Cleanup appendIDs below 1002, but with a rollback.
-	app = hb.appender()
-	_, err = app.Add(labels.FromStrings("foo", "bar"), int64(i), float64(i))
-	testutil.Ok(t, err)
-	testutil.Ok(t, app.Rollback())
-	testutil.Equals(t, 1001, lastValue(hb, 999))
-	testutil.Equals(t, 1001, lastValue(hb, 1000))
-	testutil.Equals(t, 1001, lastValue(hb, 1001))
-	testutil.Equals(t, 1001, lastValue(hb, 1002))
-	testutil.Equals(t, 1001, lastValue(hb, 1003))
+	testutil.Equals(t, 1000, lastValue(999))
+	testutil.Equals(t, 1000, lastValue(1000))
+	testutil.Equals(t, 1001, lastValue(1001))
+	testutil.Equals(t, 1002, lastValue(1002))
+	testutil.Equals(t, 1002, lastValue(1003))
 }
 
 func TestIsolationRollback(t *testing.T) {
 	// Rollback after a failed append and test if the low watermark has progressed anyway.
-	hb, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, hb.Close())
-	}()
+	hb, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer hb.Close()
 
 	app := hb.Appender()
-	_, err := app.Add(labels.FromStrings("foo", "bar"), 0, 0)
+	_, err = app.Add(labels.FromStrings("foo", "bar"), 0, 0)
 	testutil.Ok(t, err)
 	testutil.Ok(t, app.Commit())
 	testutil.Equals(t, uint64(1), hb.iso.lowWatermark())
@@ -1599,14 +1469,12 @@ func TestIsolationRollback(t *testing.T) {
 }
 
 func TestIsolationLowWatermarkMonotonous(t *testing.T) {
-	hb, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, hb.Close())
-	}()
+	hb, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer hb.Close()
 
 	app1 := hb.Appender()
-	_, err := app1.Add(labels.FromStrings("foo", "bar"), 0, 0)
+	_, err = app1.Add(labels.FromStrings("foo", "bar"), 0, 0)
 	testutil.Ok(t, err)
 	testutil.Ok(t, app1.Commit())
 	testutil.Equals(t, uint64(1), hb.iso.lowWatermark(), "Low watermark should by 1 after 1st append.")
@@ -1633,17 +1501,15 @@ func TestIsolationLowWatermarkMonotonous(t *testing.T) {
 }
 
 func TestIsolationAppendIDZeroIsNoop(t *testing.T) {
-	h, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, h.Close())
-	}()
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
 
 	h.initTime(0)
 
 	s, _ := h.getOrCreate(1, labels.FromStrings("a", "1"))
 
-	ok, _ := s.append(0, 0, 0, h.chunkDiskMapper)
+	ok, _ := s.append(0, 0, 0)
 	testutil.Assert(t, ok, "Series append failed.")
 	testutil.Equals(t, 0, s.txs.txIDCount, "Series should not have an appendID after append with appendID=0.")
 }
@@ -1655,110 +1521,25 @@ func TestHeadSeriesChunkRace(t *testing.T) {
 }
 
 func TestIsolationWithoutAdd(t *testing.T) {
-	hb, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, hb.Close())
-	}()
+	hb, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer hb.Close()
 
 	app := hb.Appender()
 	testutil.Ok(t, app.Commit())
 
 	app = hb.Appender()
-	_, err := app.Add(labels.FromStrings("foo", "baz"), 1, 1)
+	_, err = app.Add(labels.FromStrings("foo", "baz"), 1, 1)
 	testutil.Ok(t, err)
 	testutil.Ok(t, app.Commit())
 
 	testutil.Equals(t, hb.iso.lastAppendID, hb.iso.lowWatermark(), "High watermark should be equal to the low watermark")
 }
 
-func TestOutOfOrderSamplesMetric(t *testing.T) {
-	dir, err := ioutil.TempDir("", "test")
-	testutil.Ok(t, err)
-	defer func() {
-		testutil.Ok(t, os.RemoveAll(dir))
-	}()
-
-	db, err := Open(dir, nil, nil, DefaultOptions())
-	testutil.Ok(t, err)
-	defer func() {
-		testutil.Ok(t, db.Close())
-	}()
-	db.DisableCompactions()
-
-	app := db.Appender()
-	for i := 1; i <= 5; i++ {
-		_, err = app.Add(labels.FromStrings("a", "b"), int64(i), 99)
-		testutil.Ok(t, err)
-	}
-	testutil.Ok(t, app.Commit())
-
-	// Test out of order metric.
-	testutil.Equals(t, 0.0, prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamples))
-	app = db.Appender()
-	_, err = app.Add(labels.FromStrings("a", "b"), 2, 99)
-	testutil.Equals(t, storage.ErrOutOfOrderSample, err)
-	testutil.Equals(t, 1.0, prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamples))
-
-	_, err = app.Add(labels.FromStrings("a", "b"), 3, 99)
-	testutil.Equals(t, storage.ErrOutOfOrderSample, err)
-	testutil.Equals(t, 2.0, prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamples))
-
-	_, err = app.Add(labels.FromStrings("a", "b"), 4, 99)
-	testutil.Equals(t, storage.ErrOutOfOrderSample, err)
-	testutil.Equals(t, 3.0, prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamples))
-	testutil.Ok(t, app.Commit())
-
-	// Compact Head to test out of bound metric.
-	app = db.Appender()
-	_, err = app.Add(labels.FromStrings("a", "b"), DefaultBlockDuration*2, 99)
-	testutil.Ok(t, err)
-	testutil.Ok(t, app.Commit())
-
-	testutil.Equals(t, int64(math.MinInt64), db.head.minValidTime)
-	testutil.Ok(t, db.Compact())
-	testutil.Assert(t, db.head.minValidTime > 0, "")
-
-	app = db.Appender()
-	_, err = app.Add(labels.FromStrings("a", "b"), db.head.minValidTime-2, 99)
-	testutil.Equals(t, storage.ErrOutOfBounds, err)
-	testutil.Equals(t, 1.0, prom_testutil.ToFloat64(db.head.metrics.outOfBoundSamples))
-
-	_, err = app.Add(labels.FromStrings("a", "b"), db.head.minValidTime-1, 99)
-	testutil.Equals(t, storage.ErrOutOfBounds, err)
-	testutil.Equals(t, 2.0, prom_testutil.ToFloat64(db.head.metrics.outOfBoundSamples))
-	testutil.Ok(t, app.Commit())
-
-	// Some more valid samples for out of order.
-	app = db.Appender()
-	for i := 1; i <= 5; i++ {
-		_, err = app.Add(labels.FromStrings("a", "b"), db.head.minValidTime+DefaultBlockDuration+int64(i), 99)
-		testutil.Ok(t, err)
-	}
-	testutil.Ok(t, app.Commit())
-
-	// Test out of order metric.
-	app = db.Appender()
-	_, err = app.Add(labels.FromStrings("a", "b"), db.head.minValidTime+DefaultBlockDuration+2, 99)
-	testutil.Equals(t, storage.ErrOutOfOrderSample, err)
-	testutil.Equals(t, 4.0, prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamples))
-
-	_, err = app.Add(labels.FromStrings("a", "b"), db.head.minValidTime+DefaultBlockDuration+3, 99)
-	testutil.Equals(t, storage.ErrOutOfOrderSample, err)
-	testutil.Equals(t, 5.0, prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamples))
-
-	_, err = app.Add(labels.FromStrings("a", "b"), db.head.minValidTime+DefaultBlockDuration+4, 99)
-	testutil.Equals(t, storage.ErrOutOfOrderSample, err)
-	testutil.Equals(t, 6.0, prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamples))
-	testutil.Ok(t, app.Commit())
-}
-
 func testHeadSeriesChunkRace(t *testing.T) {
-	h, _, closer := newTestHead(t, 1000, false)
-	defer closer()
-	defer func() {
-		testutil.Ok(t, h.Close())
-	}()
+	h, err := NewHead(nil, nil, nil, 30, DefaultStripeSize)
+	testutil.Ok(t, err)
+	defer h.Close()
 	testutil.Ok(t, h.Init(0))
 	app := h.Appender()
 
@@ -1787,19 +1568,3 @@ func testHeadSeriesChunkRace(t *testing.T) {
 	testutil.Ok(t, ss.Err())
 	wg.Wait()
 }
-
-func newTestHead(t testing.TB, chunkRange int64, compressWAL bool) (*Head, *wal.WAL, func()) {
-	dir, err := ioutil.TempDir("", "test")
-	testutil.Ok(t, err)
-	wlog, err := wal.NewSize(nil, nil, filepath.Join(dir, "wal"), 32768, compressWAL)
-	testutil.Ok(t, err)
-
-	h, err := NewHead(nil, nil, wlog, chunkRange, dir, nil, DefaultStripeSize)
-	testutil.Ok(t, err)
-
-	testutil.Ok(t, h.chunkDiskMapper.IterateAllChunks(func(_, _ uint64, _, _ int64, _ uint16) error { return nil }))
-
-	return h, wlog, func() {
-		testutil.Ok(t, os.RemoveAll(dir))
-	}
-}
diff --git a/tsdb/querier_bench_test.go b/tsdb/querier_bench_test.go
index 2a03852f2..3d0a3fbcf 100644
--- a/tsdb/querier_bench_test.go
+++ b/tsdb/querier_bench_test.go
@@ -30,12 +30,7 @@ const (
 )
 
 func BenchmarkPostingsForMatchers(b *testing.B) {
-	chunkDir, err := ioutil.TempDir("", "chunk_dir")
-	testutil.Ok(b, err)
-	defer func() {
-		testutil.Ok(b, os.RemoveAll(chunkDir))
-	}()
-	h, err := NewHead(nil, nil, nil, 1000, chunkDir, nil, DefaultStripeSize)
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
 	testutil.Ok(b, err)
 	defer func() {
 		testutil.Ok(b, h.Close())
@@ -131,12 +126,7 @@ func benchmarkPostingsForMatchers(b *testing.B, ir IndexReader) {
 }
 
 func BenchmarkQuerierSelect(b *testing.B) {
-	chunkDir, err := ioutil.TempDir("", "chunk_dir")
-	testutil.Ok(b, err)
-	defer func() {
-		testutil.Ok(b, os.RemoveAll(chunkDir))
-	}()
-	h, err := NewHead(nil, nil, nil, 1000, chunkDir, nil, DefaultStripeSize)
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
 	testutil.Ok(b, err)
 	defer h.Close()
 	app := h.Appender()
diff --git a/tsdb/querier_test.go b/tsdb/querier_test.go
index 8cf13249a..d6f8d10aa 100644
--- a/tsdb/querier_test.go
+++ b/tsdb/querier_test.go
@@ -1857,12 +1857,7 @@ func TestFindSetMatches(t *testing.T) {
 }
 
 func TestPostingsForMatchers(t *testing.T) {
-	chunkDir, err := ioutil.TempDir("", "chunk_dir")
-	testutil.Ok(t, err)
-	defer func() {
-		testutil.Ok(t, os.RemoveAll(chunkDir))
-	}()
-	h, err := NewHead(nil, nil, nil, 1000, chunkDir, nil, DefaultStripeSize)
+	h, err := NewHead(nil, nil, nil, 1000, DefaultStripeSize)
 	testutil.Ok(t, err)
 	defer func() {
 		testutil.Ok(t, h.Close())
@@ -2225,12 +2220,7 @@ func BenchmarkQueries(b *testing.B) {
 				queryTypes["_3-Blocks"] = &querier{blocks: qs[0:3]}
 				queryTypes["_10-Blocks"] = &querier{blocks: qs}
 
-				chunkDir, err := ioutil.TempDir("", "chunk_dir")
-				testutil.Ok(b, err)
-				defer func() {
-					testutil.Ok(b, os.RemoveAll(chunkDir))
-				}()
-				head := createHead(b, series, chunkDir)
+				head := createHead(b, series)
 				qHead, err := NewBlockQuerier(head, 1, int64(nSamples))
 				testutil.Ok(b, err)
 				queryTypes["_Head"] = qHead
@@ -2242,7 +2232,6 @@ func BenchmarkQueries(b *testing.B) {
 						benchQuery(b, expExpansions, querier, selectors)
 					})
 				}
-				testutil.Ok(b, head.Close())
 			}
 		}
 	}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/testutil/promlint/promlint.go b/util/promlint/promlint.go
similarity index 67%
rename from vendor/github.com/prometheus/client_golang/prometheus/testutil/promlint/promlint.go
rename to util/promlint/promlint.go
index e48e4d453..4570c6060 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/testutil/promlint/promlint.go
+++ b/util/promlint/promlint.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The Prometheus Authors
+// Copyright 2017 The Prometheus 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
@@ -21,21 +21,14 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/prometheus/common/expfmt"
-
 	dto "github.com/prometheus/client_model/go"
+	"github.com/prometheus/common/expfmt"
 )
 
 // A Linter is a Prometheus metrics linter.  It identifies issues with metric
 // names, types, and metadata, and reports them to the caller.
 type Linter struct {
-	// The linter will read metrics in the Prometheus text format from r and
-	// then lint it, _and_ it will lint the metrics provided directly as
-	// MetricFamily proto messages in mfs. Note, however, that the current
-	// constructor functions New and NewWithMetricFamilies only ever set one
-	// of them.
-	r   io.Reader
-	mfs []*dto.MetricFamily
+	r io.Reader
 }
 
 // A Problem is an issue detected by a Linter.
@@ -47,53 +40,46 @@ type Problem struct {
 	Text string
 }
 
-// newProblem is helper function to create a Problem.
-func newProblem(mf *dto.MetricFamily, text string) Problem {
-	return Problem{
+// problems is a slice of Problems with a helper method to easily append
+// additional Problems to the slice.
+type problems []Problem
+
+// Add appends a new Problem to the slice for the specified metric, with
+// the specified issue text.
+func (p *problems) Add(mf dto.MetricFamily, text string) {
+	*p = append(*p, Problem{
 		Metric: mf.GetName(),
 		Text:   text,
-	}
+	})
 }
 
-// New creates a new Linter that reads an input stream of Prometheus metrics in
-// the Prometheus text exposition format.
+// New creates a new Linter that reads an input stream of Prometheus metrics.
+// Only the text exposition format is supported.
 func New(r io.Reader) *Linter {
 	return &Linter{
 		r: r,
 	}
 }
 
-// NewWithMetricFamilies creates a new Linter that reads from a slice of
-// MetricFamily protobuf messages.
-func NewWithMetricFamilies(mfs []*dto.MetricFamily) *Linter {
-	return &Linter{
-		mfs: mfs,
-	}
-}
-
 // Lint performs a linting pass, returning a slice of Problems indicating any
-// issues found in the metrics stream. The slice is sorted by metric name
+// issues found in the metrics stream.  The slice is sorted by metric name
 // and issue description.
 func (l *Linter) Lint() ([]Problem, error) {
-	var problems []Problem
-
-	if l.r != nil {
-		d := expfmt.NewDecoder(l.r, expfmt.FmtText)
+	// TODO(mdlayher): support for protobuf exposition format?
+	d := expfmt.NewDecoder(l.r, expfmt.FmtText)
 
-		mf := &dto.MetricFamily{}
-		for {
-			if err := d.Decode(mf); err != nil {
-				if err == io.EOF {
-					break
-				}
+	var problems []Problem
 
-				return nil, err
+	var mf dto.MetricFamily
+	for {
+		if err := d.Decode(&mf); err != nil {
+			if err == io.EOF {
+				break
 			}
 
-			problems = append(problems, lint(mf)...)
+			return nil, err
 		}
-	}
-	for _, mf := range l.mfs {
+
 		problems = append(problems, lint(mf)...)
 	}
 
@@ -109,8 +95,8 @@ func (l *Linter) Lint() ([]Problem, error) {
 }
 
 // lint is the entry point for linting a single metric.
-func lint(mf *dto.MetricFamily) []Problem {
-	fns := []func(mf *dto.MetricFamily) []Problem{
+func lint(mf dto.MetricFamily) []Problem {
+	fns := []func(mf dto.MetricFamily) []Problem{
 		lintHelp,
 		lintMetricUnits,
 		lintCounter,
@@ -131,20 +117,20 @@ func lint(mf *dto.MetricFamily) []Problem {
 }
 
 // lintHelp detects issues related to the help text for a metric.
-func lintHelp(mf *dto.MetricFamily) []Problem {
-	var problems []Problem
+func lintHelp(mf dto.MetricFamily) []Problem {
+	var problems problems
 
 	// Expect all metrics to have help text available.
 	if mf.Help == nil {
-		problems = append(problems, newProblem(mf, "no help text"))
+		problems.Add(mf, "no help text")
 	}
 
 	return problems
 }
 
 // lintMetricUnits detects issues with metric unit names.
-func lintMetricUnits(mf *dto.MetricFamily) []Problem {
-	var problems []Problem
+func lintMetricUnits(mf dto.MetricFamily) []Problem {
+	var problems problems
 
 	unit, base, ok := metricUnits(*mf.Name)
 	if !ok {
@@ -157,15 +143,15 @@ func lintMetricUnits(mf *dto.MetricFamily) []Problem {
 		return nil
 	}
 
-	problems = append(problems, newProblem(mf, fmt.Sprintf("use base unit %q instead of %q", base, unit)))
+	problems.Add(mf, fmt.Sprintf("use base unit %q instead of %q", base, unit))
 
 	return problems
 }
 
 // lintCounter detects issues specific to counters, as well as patterns that should
 // only be used with counters.
-func lintCounter(mf *dto.MetricFamily) []Problem {
-	var problems []Problem
+func lintCounter(mf dto.MetricFamily) []Problem {
+	var problems problems
 
 	isCounter := mf.GetType() == dto.MetricType_COUNTER
 	isUntyped := mf.GetType() == dto.MetricType_UNTYPED
@@ -173,9 +159,9 @@ func lintCounter(mf *dto.MetricFamily) []Problem {
 
 	switch {
 	case isCounter && !hasTotalSuffix:
-		problems = append(problems, newProblem(mf, `counter metrics should have "_total" suffix`))
+		problems.Add(mf, `counter metrics should have "_total" suffix`)
 	case !isUntyped && !isCounter && hasTotalSuffix:
-		problems = append(problems, newProblem(mf, `non-counter metrics should not have "_total" suffix`))
+		problems.Add(mf, `non-counter metrics should not have "_total" suffix`)
 	}
 
 	return problems
@@ -183,14 +169,14 @@ func lintCounter(mf *dto.MetricFamily) []Problem {
 
 // lintHistogramSummaryReserved detects when other types of metrics use names or labels
 // reserved for use by histograms and/or summaries.
-func lintHistogramSummaryReserved(mf *dto.MetricFamily) []Problem {
+func lintHistogramSummaryReserved(mf dto.MetricFamily) []Problem {
 	// These rules do not apply to untyped metrics.
 	t := mf.GetType()
 	if t == dto.MetricType_UNTYPED {
 		return nil
 	}
 
-	var problems []Problem
+	var problems problems
 
 	isHistogram := t == dto.MetricType_HISTOGRAM
 	isSummary := t == dto.MetricType_SUMMARY
@@ -198,13 +184,13 @@ func lintHistogramSummaryReserved(mf *dto.MetricFamily) []Problem {
 	n := mf.GetName()
 
 	if !isHistogram && strings.HasSuffix(n, "_bucket") {
-		problems = append(problems, newProblem(mf, `non-histogram metrics should not have "_bucket" suffix`))
+		problems.Add(mf, `non-histogram metrics should not have "_bucket" suffix`)
 	}
 	if !isHistogram && !isSummary && strings.HasSuffix(n, "_count") {
-		problems = append(problems, newProblem(mf, `non-histogram and non-summary metrics should not have "_count" suffix`))
+		problems.Add(mf, `non-histogram and non-summary metrics should not have "_count" suffix`)
 	}
 	if !isHistogram && !isSummary && strings.HasSuffix(n, "_sum") {
-		problems = append(problems, newProblem(mf, `non-histogram and non-summary metrics should not have "_sum" suffix`))
+		problems.Add(mf, `non-histogram and non-summary metrics should not have "_sum" suffix`)
 	}
 
 	for _, m := range mf.GetMetric() {
@@ -212,10 +198,10 @@ func lintHistogramSummaryReserved(mf *dto.MetricFamily) []Problem {
 			ln := l.GetName()
 
 			if !isHistogram && ln == "le" {
-				problems = append(problems, newProblem(mf, `non-histogram metrics should not have "le" label`))
+				problems.Add(mf, `non-histogram metrics should not have "le" label`)
 			}
 			if !isSummary && ln == "quantile" {
-				problems = append(problems, newProblem(mf, `non-summary metrics should not have "quantile" label`))
+				problems.Add(mf, `non-summary metrics should not have "quantile" label`)
 			}
 		}
 	}
@@ -224,8 +210,8 @@ func lintHistogramSummaryReserved(mf *dto.MetricFamily) []Problem {
 }
 
 // lintMetricTypeInName detects when metric types are included in the metric name.
-func lintMetricTypeInName(mf *dto.MetricFamily) []Problem {
-	var problems []Problem
+func lintMetricTypeInName(mf dto.MetricFamily) []Problem {
+	var problems problems
 	n := strings.ToLower(mf.GetName())
 
 	for i, t := range dto.MetricType_name {
@@ -235,17 +221,17 @@ func lintMetricTypeInName(mf *dto.MetricFamily) []Problem {
 
 		typename := strings.ToLower(t)
 		if strings.Contains(n, "_"+typename+"_") || strings.HasSuffix(n, "_"+typename) {
-			problems = append(problems, newProblem(mf, fmt.Sprintf(`metric name should not include type '%s'`, typename)))
+			problems.Add(mf, fmt.Sprintf(`metric name should not include type '%s'`, typename))
 		}
 	}
 	return problems
 }
 
 // lintReservedChars detects colons in metric names.
-func lintReservedChars(mf *dto.MetricFamily) []Problem {
-	var problems []Problem
+func lintReservedChars(mf dto.MetricFamily) []Problem {
+	var problems problems
 	if strings.Contains(mf.GetName(), ":") {
-		problems = append(problems, newProblem(mf, "metric names should not contain ':'"))
+		problems.Add(mf, "metric names should not contain ':'")
 	}
 	return problems
 }
@@ -253,16 +239,16 @@ func lintReservedChars(mf *dto.MetricFamily) []Problem {
 var camelCase = regexp.MustCompile(`[a-z][A-Z]`)
 
 // lintCamelCase detects metric names and label names written in camelCase.
-func lintCamelCase(mf *dto.MetricFamily) []Problem {
-	var problems []Problem
+func lintCamelCase(mf dto.MetricFamily) []Problem {
+	var problems problems
 	if camelCase.FindString(mf.GetName()) != "" {
-		problems = append(problems, newProblem(mf, "metric names should be written in 'snake_case' not 'camelCase'"))
+		problems.Add(mf, "metric names should be written in 'snake_case' not 'camelCase'")
 	}
 
 	for _, m := range mf.GetMetric() {
 		for _, l := range m.GetLabel() {
 			if camelCase.FindString(l.GetName()) != "" {
-				problems = append(problems, newProblem(mf, "label names should be written in 'snake_case' not 'camelCase'"))
+				problems.Add(mf, "label names should be written in 'snake_case' not 'camelCase'")
 			}
 		}
 	}
@@ -270,12 +256,12 @@ func lintCamelCase(mf *dto.MetricFamily) []Problem {
 }
 
 // lintUnitAbbreviations detects abbreviated units in the metric name.
-func lintUnitAbbreviations(mf *dto.MetricFamily) []Problem {
-	var problems []Problem
+func lintUnitAbbreviations(mf dto.MetricFamily) []Problem {
+	var problems problems
 	n := strings.ToLower(mf.GetName())
 	for _, s := range unitAbbreviations {
 		if strings.Contains(n, "_"+s+"_") || strings.HasSuffix(n, "_"+s) {
-			problems = append(problems, newProblem(mf, "metric names should not contain abbreviated units"))
+			problems.Add(mf, "metric names should not contain abbreviated units")
 		}
 	}
 	return problems
diff --git a/util/promlint/promlint_test.go b/util/promlint/promlint_test.go
new file mode 100644
index 000000000..e9039c981
--- /dev/null
+++ b/util/promlint/promlint_test.go
@@ -0,0 +1,788 @@
+// Copyright 2017 The Prometheus 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 promlint_test
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/prometheus/prometheus/util/promlint"
+)
+
+type test struct {
+	name     string
+	in       string
+	problems []promlint.Problem
+}
+
+func TestLintNoHelpText(t *testing.T) {
+	const msg = "no help text"
+
+	tests := []test{
+		{
+			name: "no help",
+			in: `
+# TYPE go_goroutines gauge
+go_goroutines 24
+`,
+			problems: []promlint.Problem{{
+				Metric: "go_goroutines",
+				Text:   msg,
+			}},
+		},
+		{
+			name: "empty help",
+			in: `
+# HELP go_goroutines
+# TYPE go_goroutines gauge
+go_goroutines 24
+`,
+			problems: []promlint.Problem{{
+				Metric: "go_goroutines",
+				Text:   msg,
+			}},
+		},
+		{
+			name: "no help and empty help",
+			in: `
+# HELP go_goroutines
+# TYPE go_goroutines gauge
+go_goroutines 24
+# TYPE go_threads gauge
+go_threads 10
+`,
+			problems: []promlint.Problem{
+				{
+					Metric: "go_goroutines",
+					Text:   msg,
+				},
+				{
+					Metric: "go_threads",
+					Text:   msg,
+				},
+			},
+		},
+		{
+			name: "OK",
+			in: `
+# HELP go_goroutines Number of goroutines that currently exist.
+# TYPE go_goroutines gauge
+go_goroutines 24
+`,
+		},
+	}
+	runTests(t, tests)
+}
+
+func TestLintMetricUnits(t *testing.T) {
+	tests := []struct {
+		name     string
+		in       string
+		problems []promlint.Problem
+	}{
+		// good cases.
+		{
+			name: "amperes",
+			in: `
+# HELP x_amperes Test metric.
+# TYPE x_amperes untyped
+x_amperes 10
+`,
+		},
+		{
+			name: "bytes",
+			in: `
+# HELP x_bytes Test metric.
+# TYPE x_bytes untyped
+x_bytes 10
+`,
+		},
+		{
+			name: "grams",
+			in: `
+# HELP x_grams Test metric.
+# TYPE x_grams untyped
+x_grams 10
+`,
+		},
+		{
+			name: "celsius",
+			in: `
+# HELP x_celsius Test metric.
+# TYPE x_celsius untyped
+x_celsius 10
+`,
+		},
+		{
+			name: "meters",
+			in: `
+# HELP x_meters Test metric.
+# TYPE x_meters untyped
+x_meters 10
+`,
+		},
+		{
+			name: "metres",
+			in: `
+# HELP x_metres Test metric.
+# TYPE x_metres untyped
+x_metres 10
+`,
+		},
+		{
+			name: "moles",
+			in: `
+# HELP x_moles Test metric.
+# TYPE x_moles untyped
+x_moles 10
+`,
+		},
+		{
+			name: "seconds",
+			in: `
+# HELP x_seconds Test metric.
+# TYPE x_seconds untyped
+x_seconds 10
+`,
+		},
+		{
+			name: "joules",
+			in: `
+# HELP x_joules Test metric.
+# TYPE x_joules untyped
+x_joules 10
+`,
+		},
+		// bad cases.
+		{
+			name: "milliamperes",
+			in: `
+# HELP x_milliamperes Test metric.
+# TYPE x_milliamperes untyped
+x_milliamperes 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_milliamperes",
+				Text:   `use base unit "amperes" instead of "milliamperes"`,
+			}},
+		},
+		{
+			name: "gigabytes",
+			in: `
+# HELP x_gigabytes Test metric.
+# TYPE x_gigabytes untyped
+x_gigabytes 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_gigabytes",
+				Text:   `use base unit "bytes" instead of "gigabytes"`,
+			}},
+		},
+		{
+			name: "kilograms",
+			in: `
+# HELP x_kilograms Test metric.
+# TYPE x_kilograms untyped
+x_kilograms 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_kilograms",
+				Text:   `use base unit "grams" instead of "kilograms"`,
+			}},
+		},
+		{
+			name: "nanocelsius",
+			in: `
+# HELP x_nanocelsius Test metric.
+# TYPE x_nanocelsius untyped
+x_nanocelsius 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_nanocelsius",
+				Text:   `use base unit "celsius" instead of "nanocelsius"`,
+			}},
+		},
+		{
+			name: "kilometers",
+			in: `
+# HELP x_kilometers Test metric.
+# TYPE x_kilometers untyped
+x_kilometers 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_kilometers",
+				Text:   `use base unit "meters" instead of "kilometers"`,
+			}},
+		},
+		{
+			name: "picometers",
+			in: `
+# HELP x_picometers Test metric.
+# TYPE x_picometers untyped
+x_picometers 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_picometers",
+				Text:   `use base unit "meters" instead of "picometers"`,
+			}},
+		},
+		{
+			name: "microseconds",
+			in: `
+# HELP x_microseconds Test metric.
+# TYPE x_microseconds untyped
+x_microseconds 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_microseconds",
+				Text:   `use base unit "seconds" instead of "microseconds"`,
+			}},
+		},
+		{
+			name: "minutes",
+			in: `
+# HELP x_minutes Test metric.
+# TYPE x_minutes untyped
+x_minutes 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_minutes",
+				Text:   `use base unit "seconds" instead of "minutes"`,
+			}},
+		},
+		{
+			name: "hours",
+			in: `
+# HELP x_hours Test metric.
+# TYPE x_hours untyped
+x_hours 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_hours",
+				Text:   `use base unit "seconds" instead of "hours"`,
+			}},
+		},
+		{
+			name: "days",
+			in: `
+# HELP x_days Test metric.
+# TYPE x_days untyped
+x_days 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_days",
+				Text:   `use base unit "seconds" instead of "days"`,
+			}},
+		},
+		{
+			name: "kelvin",
+			in: `
+# HELP x_kelvin Test metric.
+# TYPE x_kelvin untyped
+x_kelvin 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_kelvin",
+				Text:   `use base unit "celsius" instead of "kelvin"`,
+			}},
+		},
+		{
+			name: "kelvins",
+			in: `
+# HELP x_kelvins Test metric.
+# TYPE x_kelvins untyped
+x_kelvins 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_kelvins",
+				Text:   `use base unit "celsius" instead of "kelvins"`,
+			}},
+		},
+		{
+			name: "fahrenheit",
+			in: `
+# HELP thermometers_fahrenheit Test metric.
+# TYPE thermometers_fahrenheit untyped
+thermometers_fahrenheit 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "thermometers_fahrenheit",
+				Text:   `use base unit "celsius" instead of "fahrenheit"`,
+			}},
+		},
+		{
+			name: "rankine",
+			in: `
+# HELP thermometers_rankine Test metric.
+# TYPE thermometers_rankine untyped
+thermometers_rankine 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "thermometers_rankine",
+				Text:   `use base unit "celsius" instead of "rankine"`,
+			}},
+		}, {
+			name: "inches",
+			in: `
+# HELP x_inches Test metric.
+# TYPE x_inches untyped
+x_inches 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_inches",
+				Text:   `use base unit "meters" instead of "inches"`,
+			}},
+		}, {
+			name: "yards",
+			in: `
+# HELP x_yards Test metric.
+# TYPE x_yards untyped
+x_yards 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_yards",
+				Text:   `use base unit "meters" instead of "yards"`,
+			}},
+		}, {
+			name: "miles",
+			in: `
+# HELP x_miles Test metric.
+# TYPE x_miles untyped
+x_miles 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_miles",
+				Text:   `use base unit "meters" instead of "miles"`,
+			}},
+		}, {
+			name: "bits",
+			in: `
+# HELP x_bits Test metric.
+# TYPE x_bits untyped
+x_bits 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_bits",
+				Text:   `use base unit "bytes" instead of "bits"`,
+			}},
+		},
+		{
+			name: "calories",
+			in: `
+# HELP x_calories Test metric.
+# TYPE x_calories untyped
+x_calories 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_calories",
+				Text:   `use base unit "joules" instead of "calories"`,
+			}},
+		},
+		{
+			name: "pounds",
+			in: `
+# HELP x_pounds Test metric.
+# TYPE x_pounds untyped
+x_pounds 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_pounds",
+				Text:   `use base unit "grams" instead of "pounds"`,
+			}},
+		},
+		{
+			name: "ounces",
+			in: `
+# HELP x_ounces Test metric.
+# TYPE x_ounces untyped
+x_ounces 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_ounces",
+				Text:   `use base unit "grams" instead of "ounces"`,
+			}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			l := promlint.New(strings.NewReader(tt.in))
+
+			problems, err := l.Lint()
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+
+			if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected problems:\n- want: %v\n-  got: %v",
+					want, got)
+			}
+		})
+	}
+}
+
+func TestLintCounter(t *testing.T) {
+	tests := []test{
+		{
+			name: "counter without _total suffix",
+			in: `
+# HELP x_bytes Test metric.
+# TYPE x_bytes counter
+x_bytes 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_bytes",
+				Text:   `counter metrics should have "_total" suffix`,
+			}},
+		},
+		{
+			name: "gauge with _total suffix",
+			in: `
+# HELP x_bytes_total Test metric.
+# TYPE x_bytes_total gauge
+x_bytes_total 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_bytes_total",
+				Text:   `non-counter metrics should not have "_total" suffix`,
+			}},
+		},
+		{
+			name: "counter with _total suffix",
+			in: `
+# HELP x_bytes_total Test metric.
+# TYPE x_bytes_total counter
+x_bytes_total 10
+`,
+		},
+		{
+			name: "gauge without _total suffix",
+			in: `
+# HELP x_bytes Test metric.
+# TYPE x_bytes gauge
+x_bytes 10
+`,
+		},
+		{
+			name: "untyped with _total suffix",
+			in: `
+# HELP x_bytes_total Test metric.
+# TYPE x_bytes_total untyped
+x_bytes_total 10
+`,
+		},
+		{
+			name: "untyped without _total suffix",
+			in: `
+# HELP x_bytes Test metric.
+# TYPE x_bytes untyped
+x_bytes 10
+`,
+		},
+	}
+
+	runTests(t, tests)
+}
+
+func TestLintHistogramSummaryReserved(t *testing.T) {
+	tests := []test{
+		{
+			name: "gauge with _bucket suffix",
+			in: `
+# HELP x_bytes_bucket Test metric.
+# TYPE x_bytes_bucket gauge
+x_bytes_bucket 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_bytes_bucket",
+				Text:   `non-histogram metrics should not have "_bucket" suffix`,
+			}},
+		},
+		{
+			name: "gauge with _count suffix",
+			in: `
+# HELP x_bytes_count Test metric.
+# TYPE x_bytes_count gauge
+x_bytes_count 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_bytes_count",
+				Text:   `non-histogram and non-summary metrics should not have "_count" suffix`,
+			}},
+		},
+		{
+			name: "gauge with _sum suffix",
+			in: `
+# HELP x_bytes_sum Test metric.
+# TYPE x_bytes_sum gauge
+x_bytes_sum 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_bytes_sum",
+				Text:   `non-histogram and non-summary metrics should not have "_sum" suffix`,
+			}},
+		},
+		{
+			name: "gauge with le label",
+			in: `
+# HELP x_bytes Test metric.
+# TYPE x_bytes gauge
+x_bytes{le="1"} 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_bytes",
+				Text:   `non-histogram metrics should not have "le" label`,
+			}},
+		},
+		{
+			name: "gauge with quantile label",
+			in: `
+# HELP x_bytes Test metric.
+# TYPE x_bytes gauge
+x_bytes{quantile="1"} 10
+`,
+			problems: []promlint.Problem{{
+				Metric: "x_bytes",
+				Text:   `non-summary metrics should not have "quantile" label`,
+			}},
+		},
+		{
+			name: "histogram with quantile label",
+			in: `
+# HELP tsdb_compaction_duration Duration of compaction runs.
+# TYPE tsdb_compaction_duration histogram
+tsdb_compaction_duration_bucket{le="0.005",quantile="0.01"} 0
+tsdb_compaction_duration_bucket{le="0.01",quantile="0.01"} 0
+tsdb_compaction_duration_bucket{le="0.025",quantile="0.01"} 0
+tsdb_compaction_duration_bucket{le="0.05",quantile="0.01"} 0
+tsdb_compaction_duration_bucket{le="0.1",quantile="0.01"} 0
+tsdb_compaction_duration_bucket{le="0.25",quantile="0.01"} 0
+tsdb_compaction_duration_bucket{le="0.5",quantile="0.01"} 57
+tsdb_compaction_duration_bucket{le="1",quantile="0.01"} 68
+tsdb_compaction_duration_bucket{le="2.5",quantile="0.01"} 69
+tsdb_compaction_duration_bucket{le="5",quantile="0.01"} 69
+tsdb_compaction_duration_bucket{le="10",quantile="0.01"} 69
+tsdb_compaction_duration_bucket{le="+Inf",quantile="0.01"} 69
+tsdb_compaction_duration_sum 28.740810936000006
+tsdb_compaction_duration_count 69
+`,
+			problems: []promlint.Problem{{
+				Metric: "tsdb_compaction_duration",
+				Text:   `non-summary metrics should not have "quantile" label`,
+			}},
+		},
+		{
+			name: "summary with le label",
+			in: `
+# HELP go_gc_duration_seconds A summary of the GC invocation durations.
+# TYPE go_gc_duration_seconds summary
+go_gc_duration_seconds{quantile="0",le="0.01"} 4.2365e-05
+go_gc_duration_seconds{quantile="0.25",le="0.01"} 8.1492e-05
+go_gc_duration_seconds{quantile="0.5",le="0.01"} 0.000100656
+go_gc_duration_seconds{quantile="0.75",le="0.01"} 0.000113913
+go_gc_duration_seconds{quantile="1",le="0.01"} 0.021754305
+go_gc_duration_seconds_sum 1.769429004
+go_gc_duration_seconds_count 5962
+`,
+			problems: []promlint.Problem{{
+				Metric: "go_gc_duration_seconds",
+				Text:   `non-histogram metrics should not have "le" label`,
+			}},
+		},
+		{
+			name: "histogram OK",
+			in: `
+# HELP tsdb_compaction_duration Duration of compaction runs.
+# TYPE tsdb_compaction_duration histogram
+tsdb_compaction_duration_bucket{le="0.005"} 0
+tsdb_compaction_duration_bucket{le="0.01"} 0
+tsdb_compaction_duration_bucket{le="0.025"} 0
+tsdb_compaction_duration_bucket{le="0.05"} 0
+tsdb_compaction_duration_bucket{le="0.1"} 0
+tsdb_compaction_duration_bucket{le="0.25"} 0
+tsdb_compaction_duration_bucket{le="0.5"} 57
+tsdb_compaction_duration_bucket{le="1"} 68
+tsdb_compaction_duration_bucket{le="2.5"} 69
+tsdb_compaction_duration_bucket{le="5"} 69
+tsdb_compaction_duration_bucket{le="10"} 69
+tsdb_compaction_duration_bucket{le="+Inf"} 69
+tsdb_compaction_duration_sum 28.740810936000006
+tsdb_compaction_duration_count 69
+`,
+		},
+		{
+			name: "summary OK",
+			in: `
+# HELP go_gc_duration_seconds A summary of the GC invocation durations.
+# TYPE go_gc_duration_seconds summary
+go_gc_duration_seconds{quantile="0"} 4.2365e-05
+go_gc_duration_seconds{quantile="0.25"} 8.1492e-05
+go_gc_duration_seconds{quantile="0.5"} 0.000100656
+go_gc_duration_seconds{quantile="0.75"} 0.000113913
+go_gc_duration_seconds{quantile="1"} 0.021754305
+go_gc_duration_seconds_sum 1.769429004
+go_gc_duration_seconds_count 5962
+`,
+		},
+	}
+	runTests(t, tests)
+}
+
+func TestLintMetricTypeInName(t *testing.T) {
+	genTest := func(n, t, err string, problems ...promlint.Problem) test {
+		return test{
+			name: fmt.Sprintf("%s with _%s suffix", t, t),
+			in: fmt.Sprintf(`
+# HELP %s Test metric.
+# TYPE %s %s
+%s 10
+`, n, n, t, n),
+			problems: append(problems, promlint.Problem{
+				Metric: n,
+				Text:   fmt.Sprintf(`metric name should not include type '%s'`, err),
+			}),
+		}
+	}
+
+	twoProbTest := genTest("http_requests_counter", "counter", "counter", promlint.Problem{
+		Metric: "http_requests_counter",
+		Text:   `counter metrics should have "_total" suffix`,
+	})
+
+	tests := []test{
+		twoProbTest,
+		genTest("instance_memory_limit_bytes_gauge", "gauge", "gauge"),
+		genTest("request_duration_seconds_summary", "summary", "summary"),
+		genTest("request_duration_seconds_summary", "histogram", "summary"),
+		genTest("request_duration_seconds_histogram", "histogram", "histogram"),
+		genTest("request_duration_seconds_HISTOGRAM", "histogram", "histogram"),
+
+		genTest("instance_memory_limit_gauge_bytes", "gauge", "gauge"),
+	}
+	runTests(t, tests)
+}
+
+func TestLintReservedChars(t *testing.T) {
+	tests := []test{
+		{
+			name: "request_duration::_seconds",
+			in: `
+# HELP request_duration::_seconds Test metric.
+# TYPE request_duration::_seconds histogram
+request_duration::_seconds 10
+`,
+			problems: []promlint.Problem{
+				{
+					Metric: "request_duration::_seconds",
+					Text:   "metric names should not contain ':'",
+				},
+			},
+		},
+	}
+	runTests(t, tests)
+}
+
+func TestLintCamelCase(t *testing.T) {
+	tests := []test{
+		{
+			name: "requestDuration_seconds",
+			in: `
+# HELP requestDuration_seconds Test metric.
+# TYPE requestDuration_seconds histogram
+requestDuration_seconds 10
+`,
+			problems: []promlint.Problem{
+				{
+					Metric: "requestDuration_seconds",
+					Text:   "metric names should be written in 'snake_case' not 'camelCase'",
+				},
+			},
+		},
+		{
+			name: "request_duration_seconds",
+			in: `
+# HELP request_duration_seconds Test metric.
+# TYPE request_duration_seconds histogram
+request_duration_seconds{httpService="foo"} 10
+`,
+			problems: []promlint.Problem{
+				{
+					Metric: "request_duration_seconds",
+					Text:   "label names should be written in 'snake_case' not 'camelCase'",
+				},
+			},
+		},
+	}
+	runTests(t, tests)
+}
+
+func TestLintUnitAbbreviations(t *testing.T) {
+	genTest := func(n string) test {
+		return test{
+			name: fmt.Sprintf("%s with abbreviated unit", n),
+			in: fmt.Sprintf(`
+# HELP %s Test metric.
+# TYPE %s gauge
+%s 10
+`, n, n, n),
+			problems: []promlint.Problem{
+				{
+					Metric: n,
+					Text:   "metric names should not contain abbreviated units",
+				},
+			},
+		}
+	}
+	tests := []test{
+		genTest("instance_memory_limit_b"),
+		genTest("instance_memory_limit_kb"),
+		genTest("instance_memory_limit_mb"),
+		genTest("instance_memory_limit_MB"),
+		genTest("instance_memory_limit_gb"),
+		genTest("instance_memory_limit_tb"),
+		genTest("instance_memory_limit_pb"),
+
+		genTest("request_duration_s"),
+		genTest("request_duration_ms"),
+		genTest("request_duration_us"),
+		genTest("request_duration_ns"),
+		genTest("request_duration_sec"),
+		genTest("request_sec_duration"),
+		genTest("request_duration_m"),
+		genTest("request_duration_h"),
+		genTest("request_duration_d"),
+	}
+	runTests(t, tests)
+}
+
+func runTests(t *testing.T, tests []test) {
+	t.Helper()
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			l := promlint.New(strings.NewReader(tt.in))
+
+			problems, err := l.Lint()
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+
+			if want, got := tt.problems, problems; !reflect.DeepEqual(want, got) {
+				t.Fatalf("unexpected problems:\n- want: %v\n-  got: %v",
+					want, got)
+			}
+		})
+	}
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/testutil/lint.go b/vendor/github.com/prometheus/client_golang/prometheus/testutil/lint.go
deleted file mode 100644
index 7681877a8..000000000
--- a/vendor/github.com/prometheus/client_golang/prometheus/testutil/lint.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2020 The Prometheus 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 testutil
-
-import (
-	"fmt"
-
-	"github.com/prometheus/client_golang/prometheus"
-	"github.com/prometheus/client_golang/prometheus/testutil/promlint"
-)
-
-// CollectAndLint registers the provided Collector with a newly created pedantic
-// Registry. It then calls GatherAndLint with that Registry and with the
-// provided metricNames.
-func CollectAndLint(c prometheus.Collector, metricNames ...string) ([]promlint.Problem, error) {
-	reg := prometheus.NewPedanticRegistry()
-	if err := reg.Register(c); err != nil {
-		return nil, fmt.Errorf("registering collector failed: %s", err)
-	}
-	return GatherAndLint(reg, metricNames...)
-}
-
-// GatherAndLint gathers all metrics from the provided Gatherer and checks them
-// with the linter in the promlint package. If any metricNames are provided,
-// only metrics with those names are checked.
-func GatherAndLint(g prometheus.Gatherer, metricNames ...string) ([]promlint.Problem, error) {
-	got, err := g.Gather()
-	if err != nil {
-		return nil, fmt.Errorf("gathering metrics failed: %s", err)
-	}
-	if metricNames != nil {
-		got = filterMetrics(got, metricNames)
-	}
-	return promlint.NewWithMetricFamilies(got).Lint()
-}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil.go b/vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil.go
index c47373c0b..cb098398f 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/testutil/testutil.go
@@ -31,10 +31,6 @@
 // testing custom prometheus.Collector implementations and in particular whole
 // exporters, i.e. programs that retrieve telemetry data from a 3rd party source
 // and convert it into Prometheus metrics.
-//
-// In a similar pattern, CollectAndLint and GatherAndLint can be used to detect
-// metrics that have issues with their name, type, or metadata without being
-// necessarily invalid, e.g. a counter with a name missing the “_total” suffix.
 package testutil
 
 import (
@@ -140,8 +136,8 @@ func CollectAndCount(c prometheus.Collector) int {
 }
 
 // CollectAndCompare registers the provided Collector with a newly created
-// pedantic Registry. It then calls GatherAndCompare with that Registry and with
-// the provided metricNames.
+// pedantic Registry. It then does the same as GatherAndCompare, gathering the
+// metrics from the pedantic Registry.
 func CollectAndCompare(c prometheus.Collector, expected io.Reader, metricNames ...string) error {
 	reg := prometheus.NewPedanticRegistry()
 	if err := reg.Register(c); err != nil {
diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go
index d693feb4e..e0c7c74f9 100644
--- a/web/api/v1/api_test.go
+++ b/web/api/v1/api_test.go
@@ -1898,18 +1898,8 @@ type fakeDB struct {
 func (f *fakeDB) CleanTombstones() error                               { return f.err }
 func (f *fakeDB) Delete(mint, maxt int64, ms ...*labels.Matcher) error { return f.err }
 func (f *fakeDB) Snapshot(dir string, withHead bool) error             { return f.err }
-func (f *fakeDB) Stats(statsByLabelName string) (_ *tsdb.Stats, retErr error) {
-	dbDir, err := ioutil.TempDir("", "tsdb-api-ready")
-	if err != nil {
-		return nil, err
-	}
-	defer func() {
-		err := os.RemoveAll(dbDir)
-		if retErr != nil {
-			retErr = err
-		}
-	}()
-	h, _ := tsdb.NewHead(nil, nil, nil, 1000, "", nil, tsdb.DefaultStripeSize)
+func (f *fakeDB) Stats(statsByLabelName string) (*tsdb.Stats, error) {
+	h, _ := tsdb.NewHead(nil, nil, nil, 1000, tsdb.DefaultStripeSize)
 	return h.Stats(statsByLabelName), nil
 }
 
diff --git a/web/web_test.go b/web/web_test.go
index 96c4c5c4f..23fbf3e49 100644
--- a/web/web_test.go
+++ b/web/web_test.go
@@ -100,7 +100,6 @@ func (a *dbAdapter) Stats(statsByLabelName string) (*tsdb.Stats, error) {
 
 func TestReadyAndHealthy(t *testing.T) {
 	t.Parallel()
-
 	dbDir, err := ioutil.TempDir("", "tsdb-ready")
 	testutil.Ok(t, err)
 	defer testutil.Ok(t, os.RemoveAll(dbDir))
@@ -427,6 +426,7 @@ func TestDebugHandler(t *testing.T) {
 
 func TestHTTPMetrics(t *testing.T) {
 	t.Parallel()
+
 	handler := New(nil, &Options{
 		RoutePrefix:   "/",
 		ListenAddress: "somehost:9090",