Repository: prometheus/prometheus
Pull request #12618 (2023-07-31 07:00:52)

Merge release 2.46 into main
<!--
    Don't forget!

    - Please sign CNCF's Developer Certificate of Origin and sign-off your commits by adding the -s / --signoff flag to `git commit`. See https://github.com/apps/dco for more information.

    - If the PR adds or changes a behaviour or fixes a bug of an exported API it would need a unit/e2e test.

    - Where possible use only exported APIs for tests to simplify the review and make it as close as possible to an actual library usage.

    - No tests are needed for internal implementation changes.

    - Performance improvements would need a benchmark test to prove it.

    - All exposed objects should have a comment.

    - All comments should start with a capital letter and end with a full stop.
 -->



Patch:
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b291cf2bc..b75a7896e 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,7 +1,6 @@
 /web/ui @juliusv
 /web/ui/module @juliusv @nexucis
 /storage/remote @csmarchbanks @cstyan @bwplotka @tomwilkie
-/storage/remote/otlptranslator @gouthamve @jesusvazquez
 /discovery/kubernetes @brancz
 /tsdb @jesusvazquez
 /promql @roidelapluie
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 14b788c0f..e8b61bcad 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -172,7 +172,7 @@ jobs:
           quay_io_login: ${{ secrets.quay_io_login }}
           quay_io_password: ${{ secrets.quay_io_password }}
   publish_release:
-    name: Publish release artefacts
+    name: Publish release arfefacts
     runs-on: ubuntu-latest
     needs: [test_ui, test_go, test_windows, golangci, codeql, build_all]
     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v2.')
diff --git a/.golangci.yml b/.golangci.yml
index 5aa01e48a..4a6daae59 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -3,9 +3,6 @@ run:
   skip-files:
     # Skip autogenerated files.
     - ^.*\.(pb|y)\.go$
-  skip-dirs:
-    # Copied it from a different source
-    - storage/remote/otlptranslator/prometheusremotewrite
 
 output:
   sort-results: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d316e84d3..057342ded 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
 # Changelog
 
+## 2.46.0 / 2023-07-25
+
+* [FEATURE] Promtool: Add PromQL format and label matcher set/delete commands to promtool. #11411
+* [FEATURE] Promtool: Add push metrics command. #12299
+* [ENHANCEMENT] Promtool: Read from stdin if no filenames are provided in check rules. #12225
+* [ENHANCEMENT] Hetzner SD: Support larger ID's that will be used by Hetzner in September. #12569
+* [ENHANCEMENT] Kubernetes SD: Add more labels for endpointslice and endpoints role. #10914
+* [ENHANCEMENT] Kubernetes SD: Do not add pods to target group if the PodIP status is not set. #11642
+* [ENHANCEMENT] OpenStack SD: Include instance image ID in labels. #12502
+* [ENHANCEMENT] Remote Write receiver: Validate the metric names and labels. #11688
+* [ENHANCEMENT] Web: Initialize `prometheus_http_requests_total` metrics with `code` label set to `200`. #12472
+* [ENHANCEMENT] TSDB: Add Zstandard compression option for wlog. #11666
+* [ENHANCEMENT] TSDB: Support native histograms in snapshot on shutdown. #12258
+* [ENHANCEMENT] Labels: Avoid compiling regexes that are literal. #12434
+* [BUGFIX] Histograms: Fix parsing of float histograms without zero bucket. #12577
+* [BUGFIX] Histograms: Fix scraping native and classic histograms missing some histograms. #12554
+* [BUGFIX] Histograms: Enable ingestion of multiple exemplars per sample. 12557
+* [BUGFIX] File SD: Fix path handling in File-SD watcher to allow directory monitoring on Windows. #12488
+* [BUGFIX] Linode SD: Cast `InstanceSpec` values to `int64` to avoid overflows on 386 architecture. #12568
+* [BUGFIX] PromQL Engine: Include query parsing in active-query tracking. #12418
+* [BUGFIX] TSDB: Handle TOC parsing failures. #10623
 
 ## 2.45.0 / 2023-06-23
 
diff --git a/VERSION b/VERSION
index e599014ea..cc8758303 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-2.45.0
+2.46.0
diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go
index d48898b94..debc0d3f9 100644
--- a/cmd/prometheus/main.go
+++ b/cmd/prometheus/main.go
@@ -170,9 +170,6 @@ func (c *flagConfig) setFeatureListOptions(logger log.Logger) error {
 			case "remote-write-receiver":
 				c.web.EnableRemoteWriteReceiver = true
 				level.Warn(logger).Log("msg", "Remote write receiver enabled via feature flag remote-write-receiver. This is DEPRECATED. Use --web.enable-remote-write-receiver.")
-			case "otlp-write-receiver":
-				c.web.EnableOTLPWriteReceiver = true
-				level.Info(logger).Log("msg", "Experimental OTLP write receiver enabled")
 			case "expand-external-labels":
 				c.enableExpandExternalLabels = true
 				level.Info(logger).Log("msg", "Experimental expand-external-labels enabled")
@@ -423,7 +420,7 @@ func main() {
 	a.Flag("scrape.discovery-reload-interval", "Interval used by scrape manager to throttle target groups updates.").
 		Hidden().Default("5s").SetValue(&cfg.scrape.DiscoveryReloadInterval)
 
-	a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
+	a.Flag("enable-feature", "Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details.").
 		Default("").StringsVar(&cfg.featureList)
 
 	promlogflag.AddFlags(a, &cfg.promlogConfig)
diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md
index 78ec205f2..46f286e00 100644
--- a/docs/command-line/prometheus.md
+++ b/docs/command-line/prometheus.md
@@ -52,7 +52,7 @@ The Prometheus monitoring server
 | <code class="text-nowrap">--query.timeout</code> | Maximum time a query may take before being aborted. Use with server mode only. | `2m` |
 | <code class="text-nowrap">--query.max-concurrency</code> | Maximum number of queries executed concurrently. Use with server mode only. | `20` |
 | <code class="text-nowrap">--query.max-samples</code> | Maximum number of samples a single query can load into memory. Note that queries will fail if they try to load more samples than this into memory, so this also limits the number of samples a query can return. Use with server mode only. | `50000000` |
-| <code class="text-nowrap">--enable-feature</code> | Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms, otlp-write-receiver. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. |  |
+| <code class="text-nowrap">--enable-feature</code> | Comma separated feature names to enable. Valid options: agent, exemplar-storage, expand-external-labels, memory-snapshot-on-shutdown, promql-at-modifier, promql-negative-offset, promql-per-step-stats, remote-write-receiver (DEPRECATED), extra-scrape-metrics, new-service-discovery-manager, auto-gomaxprocs, no-default-scrape-port, native-histograms. See https://prometheus.io/docs/prometheus/latest/feature_flags/ for more details. |  |
 | <code class="text-nowrap">--log.level</code> | Only log messages with the given severity or above. One of: [debug, info, warn, error] | `info` |
 | <code class="text-nowrap">--log.format</code> | Output format of log messages. One of: [logfmt, json] | `logfmt` |
 
diff --git a/docs/installation.md b/docs/installation.md
index 28f64c0f9..05df14a46 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -55,23 +55,6 @@ docker run \
     prom/prometheus
 ```
 
-### Save your Prometheus data
-
-Prometheus data is stored in `/prometheus` dir inside the container, so the data is cleared every time the container gets restarted. To save your data, you need to set up persistent storage (or bind mounts) for your container.
-
-Run Prometheus container with persistent storage:
-
-```bash
-# Create persistent volume for your data
-docker volume create prometheus-data
-# Start Prometheus container
-docker run \
-    -p 9090:9090 \
-    -v /path/to/prometheus.yml:/etc/prometheus/prometheus.yml \
-    -v prometheus-data:/prometheus \
-    prom/prometheus
-```
-
 ### Custom image
 
 To avoid managing a file on the host and bind-mount it, the
diff --git a/docs/querying/functions.md b/docs/querying/functions.md
index bec87a69d..55ed92ecc 100644
--- a/docs/querying/functions.md
+++ b/docs/querying/functions.md
@@ -419,10 +419,11 @@ label_join(up{job="api-server",src1="a",src2="b",src3="c"}, "foo", ",", "src1",
 ## `label_replace()`
 
 For each timeseries in `v`, `label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)`
-matches the [regular expression](https://github.com/google/re2/wiki/Syntax) `regex` against the value of the label `src_label`. If it
+matches the regular expression `regex` against the value of the label `src_label`. If it
 matches, the value of the label `dst_label` in the returned timeseries will be the expansion
 of `replacement`, together with the original labels in the input. Capturing groups in the
-regular expression can be referenced with `$1`, `$2`, etc. Named capturing groups in the regular expression can be referenced with `$name` (where `name` is the  capturing group name). If the regular expression doesn't match then the timeseries is returned unchanged.
+regular expression can be referenced with `$1`, `$2`, etc. If the regular expression doesn't
+match then the timeseries is returned unchanged.
 
 `label_replace` acts on float and histogram samples in the same way.
 
@@ -432,11 +433,6 @@ This example will return timeseries with the values `a:c` at label `service` and
 label_replace(up{job="api-server",service="a:c"}, "foo", "$1", "service", "(.*):.*")
 ```
 
-This second example has the same effect than the first example, and illustrates use of named capturing groups:
-```
-label_replace(up{job="api-server",service="a:c"}, "foo", "$name", "service", "(?P<name>.*):(?P<version>.*)")
-```
-
 ## `ln()`
 
 `ln(v instant-vector)` calculates the natural logarithm for all elements in `v`.
diff --git a/go.mod b/go.mod
index 1ddf77184..82e53f185 100644
--- a/go.mod
+++ b/go.mod
@@ -54,8 +54,6 @@ require (
 	github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c
 	github.com/stretchr/testify v1.8.4
 	github.com/vultr/govultr/v2 v2.17.2
-	go.opentelemetry.io/collector/pdata v0.66.0
-	go.opentelemetry.io/collector/semconv v0.81.0
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0
 	go.opentelemetry.io/otel v1.16.0
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0
@@ -66,7 +64,6 @@ require (
 	go.uber.org/atomic v1.11.0
 	go.uber.org/automaxprocs v1.5.2
 	go.uber.org/goleak v1.2.1
-	go.uber.org/multierr v1.8.0
 	golang.org/x/net v0.12.0
 	golang.org/x/oauth2 v0.10.0
 	golang.org/x/sync v0.3.0
diff --git a/go.sum b/go.sum
index 63c60005a..4a5496485 100644
--- a/go.sum
+++ b/go.sum
@@ -448,7 +448,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@@ -797,10 +797,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.opentelemetry.io/collector/pdata v0.66.0 h1:UdE5U6MsDNzuiWaXdjGx2lC3ElVqWmN/hiUE8vyvSuM=
-go.opentelemetry.io/collector/pdata v0.66.0/go.mod h1:pqyaznLzk21m+1KL6fwOsRryRELL+zNM0qiVSn0MbVc=
-go.opentelemetry.io/collector/semconv v0.81.0 h1:lCYNNo3powDvFIaTPP2jDKIrBiV1T92NK4QgL/aHYXw=
-go.opentelemetry.io/collector/semconv v0.81.0/go.mod h1:TlYPtzvsXyHOgr5eATi43qEMqwSmIziivJB2uctKswo=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8=
 go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
@@ -824,7 +820,6 @@ go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lI
 go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
 go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
@@ -833,8 +828,6 @@ go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
 go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
-go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
-go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
diff --git a/model/histogram/float_histogram.go b/model/histogram/float_histogram.go
index d3f013935..782b07df6 100644
--- a/model/histogram/float_histogram.go
+++ b/model/histogram/float_histogram.go
@@ -93,8 +93,26 @@ func (h *FloatHistogram) CopyToSchema(targetSchema int32) *FloatHistogram {
 		Sum:           h.Sum,
 	}
 
-	c.PositiveSpans, c.PositiveBuckets = mergeToSchema(h.PositiveSpans, h.PositiveBuckets, h.Schema, targetSchema)
-	c.NegativeSpans, c.NegativeBuckets = mergeToSchema(h.NegativeSpans, h.NegativeBuckets, h.Schema, targetSchema)
+	// TODO(beorn7): This is a straight-forward implementation using merging
+	// iterators for the original buckets and then adding one merged bucket
+	// after another to the newly created FloatHistogram. It's well possible
+	// that a more involved implementation performs much better, which we
+	// could do if this code path turns out to be performance-critical.
+	var iInSpan, index int32
+	for iSpan, iBucket, it := -1, -1, h.floatBucketIterator(true, 0, targetSchema); it.Next(); {
+		b := it.At()
+		c.PositiveSpans, c.PositiveBuckets, iSpan, iBucket, iInSpan = addBucket(
+			b, c.PositiveSpans, c.PositiveBuckets, iSpan, iBucket, iInSpan, index,
+		)
+		index = b.Index
+	}
+	for iSpan, iBucket, it := -1, -1, h.floatBucketIterator(false, 0, targetSchema); it.Next(); {
+		b := it.At()
+		c.NegativeSpans, c.NegativeBuckets, iSpan, iBucket, iInSpan = addBucket(
+			b, c.NegativeSpans, c.NegativeBuckets, iSpan, iBucket, iInSpan, index,
+		)
+		index = b.Index
+	}
 
 	return &c
 }
@@ -800,11 +818,6 @@ type floatBucketIterator struct {
 	absoluteStartValue float64 // Never return buckets with an upper bound ≤ this value.
 }
 
-func (i *floatBucketIterator) At() Bucket[float64] {
-	// Need to use i.targetSchema rather than i.baseBucketIterator.schema.
-	return i.baseBucketIterator.at(i.targetSchema)
-}
-
 func (i *floatBucketIterator) Next() bool {
 	if i.spansIdx >= len(i.spans) {
 		return false
@@ -964,72 +977,3 @@ func (i *allFloatBucketIterator) Next() bool {
 func (i *allFloatBucketIterator) At() Bucket[float64] {
 	return i.currBucket
 }
-
-// targetIdx returns the bucket index in the target schema for the given bucket
-// index idx in the original schema.
-func targetIdx(idx, originSchema, targetSchema int32) int32 {
-	return ((idx - 1) >> (originSchema - targetSchema)) + 1
-}
-
-// mergeToSchema is used to merge a FloatHistogram's Spans and Buckets (no matter if
-// positive or negative) from the original schema to the target schema.
-// The target schema must be smaller than the original schema.
-func mergeToSchema(originSpans []Span, originBuckets []float64, originSchema, targetSchema int32) ([]Span, []float64) {
-	var (
-		targetSpans         []Span    // The spans in the target schema.
-		targetBuckets       []float64 // The buckets in the target schema.
-		bucketIdx           int32     // The index of bucket in the origin schema.
-		lastTargetBucketIdx int32     // The index of the last added target bucket.
-		origBucketIdx       int       // The position of a bucket in originBuckets slice.
-	)
-
-	for _, span := range originSpans {
-		// Determine the index of the first bucket in this span.
-		bucketIdx += span.Offset
-		for j := 0; j < int(span.Length); j++ {
-			// Determine the index of the bucket in the target schema from the index in the original schema.
-			targetBucketIdx := targetIdx(bucketIdx, originSchema, targetSchema)
-
-			switch {
-			case len(targetSpans) == 0:
-				// This is the first span in the targetSpans.
-				span := Span{
-					Offset: targetBucketIdx,
-					Length: 1,
-				}
-				targetSpans = append(targetSpans, span)
-				targetBuckets = append(targetBuckets, originBuckets[0])
-				lastTargetBucketIdx = targetBucketIdx
-
-			case lastTargetBucketIdx == targetBucketIdx:
-				// The current bucket has to be merged into the same target bucket as the previous bucket.
-				targetBuckets[len(targetBuckets)-1] += originBuckets[origBucketIdx]
-
-			case (lastTargetBucketIdx + 1) == targetBucketIdx:
-				// The current bucket has to go into a new target bucket,
-				// and that bucket is next to the previous target bucket,
-				// so we add it to the current target span.
-				targetSpans[len(targetSpans)-1].Length++
-				targetBuckets = append(targetBuckets, originBuckets[origBucketIdx])
-				lastTargetBucketIdx++
-
-			case (lastTargetBucketIdx + 1) < targetBucketIdx:
-				// The current bucket has to go into a new target bucket,
-				// and that bucket is separated by a gap from the previous target bucket,
-				// so we need to add a new target span.
-				span := Span{
-					Offset: targetBucketIdx - lastTargetBucketIdx - 1,
-					Length: 1,
-				}
-				targetSpans = append(targetSpans, span)
-				targetBuckets = append(targetBuckets, originBuckets[origBucketIdx])
-				lastTargetBucketIdx = targetBucketIdx
-			}
-
-			bucketIdx++
-			origBucketIdx++
-		}
-	}
-
-	return targetSpans, targetBuckets
-}
diff --git a/model/histogram/generic.go b/model/histogram/generic.go
index dad54cb06..e1de5ffb5 100644
--- a/model/histogram/generic.go
+++ b/model/histogram/generic.go
@@ -102,22 +102,16 @@ type baseBucketIterator[BC BucketCount, IBC InternalBucketCount] struct {
 }
 
 func (b baseBucketIterator[BC, IBC]) At() Bucket[BC] {
-	return b.at(b.schema)
-}
-
-// at is an internal version of the exported At to enable using a different
-// schema.
-func (b baseBucketIterator[BC, IBC]) at(schema int32) Bucket[BC] {
 	bucket := Bucket[BC]{
 		Count: BC(b.currCount),
 		Index: b.currIdx,
 	}
 	if b.positive {
-		bucket.Upper = getBound(b.currIdx, schema)
-		bucket.Lower = getBound(b.currIdx-1, schema)
+		bucket.Upper = getBound(b.currIdx, b.schema)
+		bucket.Lower = getBound(b.currIdx-1, b.schema)
 	} else {
-		bucket.Lower = -getBound(b.currIdx, schema)
-		bucket.Upper = -getBound(b.currIdx-1, schema)
+		bucket.Lower = -getBound(b.currIdx, b.schema)
+		bucket.Upper = -getBound(b.currIdx-1, b.schema)
 	}
 	bucket.LowerInclusive = bucket.Lower < 0
 	bucket.UpperInclusive = bucket.Upper > 0
diff --git a/model/textparse/protobufparse.go b/model/textparse/protobufparse.go
index c111bb065..29ccdb84d 100644
--- a/model/textparse/protobufparse.go
+++ b/model/textparse/protobufparse.go
@@ -554,17 +554,20 @@ func formatOpenMetricsFloat(f float64) string {
 	return s + ".0"
 }
 
-// isNativeHistogram returns false iff the provided histograms has no spans at
-// all (neither positive nor negative) and a zero threshold of 0 and a zero
-// count of 0. In principle, this could still be meant to be a native histogram
-// with a zero threshold of 0 and no observations yet. In that case,
-// instrumentation libraries should add a "no-op" span (e.g. length zero, offset
-// zero) to signal that the histogram is meant to be parsed as a native
-// histogram. Failing to do so will cause Prometheus to parse it as a classic
-// histogram as long as no observations have happened.
+// isNativeHistogram returns false iff the provided histograms has no sparse
+// buckets and a zero threshold of 0 and a zero count of 0. In principle, this
+// could still be meant to be a native histogram (with a zero threshold of 0 and
+// no observations yet), but for now, we'll treat this case as a conventional
+// histogram.
+//
+// TODO(beorn7): In the final format, there should be an unambiguous way of
+// deciding if a histogram should be ingested as a conventional one or a native
+// one.
 func isNativeHistogram(h *dto.Histogram) bool {
-	return len(h.GetPositiveSpan()) > 0 ||
-		len(h.GetNegativeSpan()) > 0 ||
-		h.GetZeroThreshold() > 0 ||
-		h.GetZeroCount() > 0
+	return h.GetZeroThreshold() > 0 ||
+		h.GetZeroCount() > 0 ||
+		len(h.GetNegativeDelta()) > 0 ||
+		len(h.GetPositiveDelta()) > 0 ||
+		len(h.GetNegativeCount()) > 0 ||
+		len(h.GetPositiveCount()) > 0
 }
diff --git a/prompb/io/prometheus/client/metrics.pb.go b/prompb/io/prometheus/client/metrics.pb.go
index 83a7da779..3e4bc7df8 100644
--- a/prompb/io/prometheus/client/metrics.pb.go
+++ b/prompb/io/prometheus/client/metrics.pb.go
@@ -414,9 +414,6 @@ type Histogram struct {
 	NegativeDelta []int64   `protobuf:"zigzag64,10,rep,packed,name=negative_delta,json=negativeDelta,proto3" json:"negative_delta,omitempty"`
 	NegativeCount []float64 `protobuf:"fixed64,11,rep,packed,name=negative_count,json=negativeCount,proto3" json:"negative_count,omitempty"`
 	// Positive buckets for the native histogram.
-	// Use a no-op span (offset 0, length 0) for a native histogram without any
-	// observations yet and with a zero_threshold of 0. Otherwise, it would be
-	// indistinguishable from a classic histogram.
 	PositiveSpan []BucketSpan `protobuf:"bytes,12,rep,name=positive_span,json=positiveSpan,proto3" json:"positive_span"`
 	// Use either "positive_delta" or "positive_count", the former for
 	// regular histograms with integer counts, the latter for float
diff --git a/prompb/io/prometheus/client/metrics.proto b/prompb/io/prometheus/client/metrics.proto
index 3fef2b6d0..6bbea622f 100644
--- a/prompb/io/prometheus/client/metrics.proto
+++ b/prompb/io/prometheus/client/metrics.proto
@@ -97,9 +97,6 @@ message Histogram {
   repeated double negative_count = 11; // Absolute count of each bucket.
 
   // Positive buckets for the native histogram.
-  // Use a no-op span (offset 0, length 0) for a native histogram without any
-  // observations yet and with a zero_threshold of 0. Otherwise, it would be
-  // indistinguishable from a classic histogram.
   repeated BucketSpan positive_span = 12 [(gogoproto.nullable) = false];
   // Use either "positive_delta" or "positive_count", the former for
   // regular histograms with integer counts, the latter for float
diff --git a/promql/engine.go b/promql/engine.go
index e2092a800..83bbdeff8 100644
--- a/promql/engine.go
+++ b/promql/engine.go
@@ -2874,7 +2874,7 @@ func btos(b bool) float64 {
 // result of the op operation.
 func shouldDropMetricName(op parser.ItemType) bool {
 	switch op {
-	case parser.ADD, parser.SUB, parser.DIV, parser.MUL, parser.POW, parser.MOD, parser.ATAN2:
+	case parser.ADD, parser.SUB, parser.DIV, parser.MUL, parser.POW, parser.MOD:
 		return true
 	default:
 		return false
diff --git a/storage/remote/codec.go b/storage/remote/codec.go
index 4927c16fd..3f426204e 100644
--- a/storage/remote/codec.go
+++ b/storage/remote/codec.go
@@ -14,7 +14,6 @@
 package remote
 
 import (
-	"compress/gzip"
 	"errors"
 	"fmt"
 	"io"
@@ -27,7 +26,6 @@ import (
 	"github.com/gogo/protobuf/proto"
 	"github.com/golang/snappy"
 	"github.com/prometheus/common/model"
-	"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
 	"golang.org/x/exp/slices"
 
 	"github.com/prometheus/prometheus/model/exemplar"
@@ -40,13 +38,8 @@ import (
 	"github.com/prometheus/prometheus/tsdb/chunks"
 )
 
-const (
-	// decodeReadLimit is the maximum size of a read request body in bytes.
-	decodeReadLimit = 32 * 1024 * 1024
-
-	pbContentType   = "application/x-protobuf"
-	jsonContentType = "application/json"
-)
+// decodeReadLimit is the maximum size of a read request body in bytes.
+const decodeReadLimit = 32 * 1024 * 1024
 
 type HTTPError struct {
 	msg    string
@@ -813,56 +806,3 @@ func DecodeWriteRequest(r io.Reader) (*prompb.WriteRequest, error) {
 
 	return &req, nil
 }
-
-func DecodeOTLPWriteRequest(r *http.Request) (pmetricotlp.ExportRequest, error) {
-	contentType := r.Header.Get("Content-Type")
-	var decoderFunc func(buf []byte) (pmetricotlp.ExportRequest, error)
-	switch contentType {
-	case pbContentType:
-		decoderFunc = func(buf []byte) (pmetricotlp.ExportRequest, error) {
-			req := pmetricotlp.NewExportRequest()
-			return req, req.UnmarshalProto(buf)
-		}
-
-	case jsonContentType:
-		decoderFunc = func(buf []byte) (pmetricotlp.ExportRequest, error) {
-			req := pmetricotlp.NewExportRequest()
-			return req, req.UnmarshalJSON(buf)
-		}
-
-	default:
-		return pmetricotlp.NewExportRequest(), fmt.Errorf("unsupported content type: %s, supported: [%s, %s]", contentType, jsonContentType, pbContentType)
-	}
-
-	reader := r.Body
-	// Handle compression.
-	switch r.Header.Get("Content-Encoding") {
-	case "gzip":
-		gr, err := gzip.NewReader(reader)
-		if err != nil {
-			return pmetricotlp.NewExportRequest(), err
-		}
-		reader = gr
-
-	case "":
-		// No compression.
-
-	default:
-		return pmetricotlp.NewExportRequest(), fmt.Errorf("unsupported compression: %s. Only \"gzip\" or no compression supported", r.Header.Get("Content-Encoding"))
-	}
-
-	body, err := io.ReadAll(reader)
-	if err != nil {
-		r.Body.Close()
-		return pmetricotlp.NewExportRequest(), err
-	}
-	if err = r.Body.Close(); err != nil {
-		return pmetricotlp.NewExportRequest(), err
-	}
-	otlpReq, err := decoderFunc(body)
-	if err != nil {
-		return pmetricotlp.NewExportRequest(), err
-	}
-
-	return otlpReq, nil
-}
diff --git a/storage/remote/otlptranslator/README.md b/storage/remote/otlptranslator/README.md
deleted file mode 100644
index c2b04e5af..000000000
--- a/storage/remote/otlptranslator/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-## Copying from opentelemetry/opentelemetry-collector-contrib
-
-This files in the `prometheus/` and `prometheusremotewrite/` are copied from the OpenTelemetry Project[^1].
-
-This is done instead of adding a go.mod dependency because OpenTelemetry depends on `prometheus/prometheus` and a cyclic dependency will be created. This is just a temporary solution and the long-term solution is to move the required packages from OpenTelemetry into `prometheus/prometheus`. 
-We don't copy in `./prometheus` through this script because that package imports a collector specific featuregate package we don't want to import. The featuregate package is being removed now, and in the future we will copy this folder too.
-
-To update the dependency is a multi-step process:
-1. Vendor the latest `prometheus/prometheus`@`main` into [`opentelemetry/opentelemetry-collector-contrib`](https://github.com/open-telemetry/opentelemetry-collector-contrib)
-1. Update the VERSION in `update-copy.sh`.
-1. Run `./update-copy.sh`.
-
-### Why copy?
-
-This is because the packages we copy depend on the [`prompb`](https://github.com/prometheus/prometheus/blob/main/prompb) package. While the package is relatively stable, there are still changes. For example, https://github.com/prometheus/prometheus/pull/11935 changed the types.
-This means if we depend on the upstream packages directly, we will never able to make the changes like above. Hence we're copying the code for now.
-
-### I need to manually change these files
-
-When we do want to make changes to the types in `prompb`, we might need to edit the files directly. That is OK, please let @gouthamve or @jesusvazquez know so they can take care of updating the upstream code (by vendoring in `prometheus/prometheus` upstream and resolving conflicts) and then will run the copy
-script again to keep things updated.
-
-[^1]: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/translator/prometheus and https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/translator/prometheusremotewrite
\ No newline at end of file
diff --git a/storage/remote/otlptranslator/prometheus/normalize_label.go b/storage/remote/otlptranslator/prometheus/normalize_label.go
deleted file mode 100644
index cfcb5652e..000000000
--- a/storage/remote/otlptranslator/prometheus/normalize_label.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package normalize
-
-import (
-	"strings"
-	"unicode"
-)
-
-// Normalizes the specified label to follow Prometheus label names standard
-//
-// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
-//
-// Labels that start with non-letter rune will be prefixed with "key_"
-//
-// Exception is made for double-underscores which are allowed
-func NormalizeLabel(label string) string {
-	// Trivial case
-	if len(label) == 0 {
-		return label
-	}
-
-	// Replace all non-alphanumeric runes with underscores
-	label = strings.Map(sanitizeRune, label)
-
-	// If label starts with a number, prepend with "key_"
-	if unicode.IsDigit(rune(label[0])) {
-		label = "key_" + label
-	}
-
-	return label
-}
-
-// Return '_' for anything non-alphanumeric
-func sanitizeRune(r rune) rune {
-	if unicode.IsLetter(r) || unicode.IsDigit(r) {
-		return r
-	}
-	return '_'
-}
diff --git a/storage/remote/otlptranslator/prometheus/normalize_name.go b/storage/remote/otlptranslator/prometheus/normalize_name.go
deleted file mode 100644
index 0b62a28f2..000000000
--- a/storage/remote/otlptranslator/prometheus/normalize_name.go
+++ /dev/null
@@ -1,251 +0,0 @@
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package normalize
-
-import (
-	"strings"
-	"unicode"
-
-	"go.opentelemetry.io/collector/pdata/pmetric"
-)
-
-// The map to translate OTLP units to Prometheus units
-// OTLP metrics use the c/s notation as specified at https://ucum.org/ucum.html
-// (See also https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/README.md#instrument-units)
-// Prometheus best practices for units: https://prometheus.io/docs/practices/naming/#base-units
-// OpenMetrics specification for units: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#units-and-base-units
-var unitMap = map[string]string{
-	// Time
-	"d":   "days",
-	"h":   "hours",
-	"min": "minutes",
-	"s":   "seconds",
-	"ms":  "milliseconds",
-	"us":  "microseconds",
-	"ns":  "nanoseconds",
-
-	// Bytes
-	"By":   "bytes",
-	"KiBy": "kibibytes",
-	"MiBy": "mebibytes",
-	"GiBy": "gibibytes",
-	"TiBy": "tibibytes",
-	"KBy":  "kilobytes",
-	"MBy":  "megabytes",
-	"GBy":  "gigabytes",
-	"TBy":  "terabytes",
-	"B":    "bytes",
-	"KB":   "kilobytes",
-	"MB":   "megabytes",
-	"GB":   "gigabytes",
-	"TB":   "terabytes",
-
-	// SI
-	"m": "meters",
-	"V": "volts",
-	"A": "amperes",
-	"J": "joules",
-	"W": "watts",
-	"g": "grams",
-
-	// Misc
-	"Cel": "celsius",
-	"Hz":  "hertz",
-	"1":   "",
-	"%":   "percent",
-	"$":   "dollars",
-}
-
-// The map that translates the "per" unit
-// Example: s => per second (singular)
-var perUnitMap = map[string]string{
-	"s":  "second",
-	"m":  "minute",
-	"h":  "hour",
-	"d":  "day",
-	"w":  "week",
-	"mo": "month",
-	"y":  "year",
-}
-
-// Build a Prometheus-compliant metric name for the specified metric
-//
-// Metric name is prefixed with specified namespace and underscore (if any).
-// Namespace is not cleaned up. Make sure specified namespace follows Prometheus
-// naming convention.
-//
-// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
-// and https://prometheus.io/docs/practices/naming/#metric-and-label-naming
-func BuildPromCompliantName(metric pmetric.Metric, namespace string) string {
-	// Split metric name in "tokens" (remove all non-alphanumeric)
-	nameTokens := strings.FieldsFunc(
-		metric.Name(),
-		func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) },
-	)
-
-	// Split unit at the '/' if any
-	unitTokens := strings.SplitN(metric.Unit(), "/", 2)
-
-	// Main unit
-	// Append if not blank, doesn't contain '{}', and is not present in metric name already
-	if len(unitTokens) > 0 {
-		mainUnitOtel := strings.TrimSpace(unitTokens[0])
-		if mainUnitOtel != "" && !strings.ContainsAny(mainUnitOtel, "{}") {
-			mainUnitProm := CleanUpString(unitMapGetOrDefault(mainUnitOtel))
-			if mainUnitProm != "" && !contains(nameTokens, mainUnitProm) {
-				nameTokens = append(nameTokens, mainUnitProm)
-			}
-		}
-
-		// Per unit
-		// Append if not blank, doesn't contain '{}', and is not present in metric name already
-		if len(unitTokens) > 1 && unitTokens[1] != "" {
-			perUnitOtel := strings.TrimSpace(unitTokens[1])
-			if perUnitOtel != "" && !strings.ContainsAny(perUnitOtel, "{}") {
-				perUnitProm := CleanUpString(perUnitMapGetOrDefault(perUnitOtel))
-				if perUnitProm != "" && !contains(nameTokens, perUnitProm) {
-					nameTokens = append(append(nameTokens, "per"), perUnitProm)
-				}
-			}
-		}
-
-	}
-
-	// Append _total for Counters
-	if metric.Type() == pmetric.MetricTypeSum && metric.Sum().IsMonotonic() {
-		nameTokens = append(removeItem(nameTokens, "total"), "total")
-	}
-
-	// Append _ratio for metrics with unit "1"
-	// Some Otel receivers improperly use unit "1" for counters of objects
-	// See https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+some+metric+units+don%27t+follow+otel+semantic+conventions
-	// Until these issues have been fixed, we're appending `_ratio` for gauges ONLY
-	// Theoretically, counters could be ratios as well, but it's absurd (for mathematical reasons)
-	if metric.Unit() == "1" && metric.Type() == pmetric.MetricTypeGauge {
-		nameTokens = append(removeItem(nameTokens, "ratio"), "ratio")
-	}
-
-	// Namespace?
-	if namespace != "" {
-		nameTokens = append([]string{namespace}, nameTokens...)
-	}
-
-	// Build the string from the tokens, separated with underscores
-	normalizedName := strings.Join(nameTokens, "_")
-
-	// Metric name cannot start with a digit, so prefix it with "_" in this case
-	if normalizedName != "" && unicode.IsDigit(rune(normalizedName[0])) {
-		normalizedName = "_" + normalizedName
-	}
-
-	return normalizedName
-}
-
-// TrimPromSuffixes trims type and unit prometheus suffixes from a metric name.
-// Following the [OpenTelemetry specs] for converting Prometheus Metric points to OTLP.
-//
-// [OpenTelemetry specs]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#metric-metadata
-func TrimPromSuffixes(promName string, metricType pmetric.MetricType, unit string) string {
-	nameTokens := strings.Split(promName, "_")
-	if len(nameTokens) == 1 {
-		return promName
-	}
-
-	nameTokens = removeTypeSuffixes(nameTokens, metricType)
-	nameTokens = removeUnitSuffixes(nameTokens, unit)
-
-	return strings.Join(nameTokens, "_")
-}
-
-func removeTypeSuffixes(tokens []string, metricType pmetric.MetricType) []string {
-	switch metricType {
-	case pmetric.MetricTypeSum:
-		// Only counters are expected to have a type suffix at this point.
-		// for other types, suffixes are removed during scrape.
-		return removeSuffix(tokens, "total")
-	default:
-		return tokens
-	}
-}
-
-func removeUnitSuffixes(nameTokens []string, unit string) []string {
-	l := len(nameTokens)
-	unitTokens := strings.Split(unit, "_")
-	lu := len(unitTokens)
-
-	if lu == 0 || l <= lu {
-		return nameTokens
-	}
-
-	suffixed := true
-	for i := range unitTokens {
-		if nameTokens[l-i-1] != unitTokens[lu-i-1] {
-			suffixed = false
-			break
-		}
-	}
-
-	if suffixed {
-		return nameTokens[:l-lu]
-	}
-
-	return nameTokens
-}
-
-func removeSuffix(tokens []string, suffix string) []string {
-	l := len(tokens)
-	if tokens[l-1] == suffix {
-		return tokens[:l-1]
-	}
-
-	return tokens
-}
-
-// Clean up specified string so it's Prometheus compliant
-func CleanUpString(s string) string {
-	return strings.Join(strings.FieldsFunc(s, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) }), "_")
-}
-
-func RemovePromForbiddenRunes(s string) string {
-	return strings.Join(strings.FieldsFunc(s, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' && r != ':' }), "_")
-}
-
-// Retrieve the Prometheus "basic" unit corresponding to the specified "basic" unit
-// Returns the specified unit if not found in unitMap
-func unitMapGetOrDefault(unit string) string {
-	if promUnit, ok := unitMap[unit]; ok {
-		return promUnit
-	}
-	return unit
-}
-
-// Retrieve the Prometheus "per" unit corresponding to the specified "per" unit
-// Returns the specified unit if not found in perUnitMap
-func perUnitMapGetOrDefault(perUnit string) string {
-	if promPerUnit, ok := perUnitMap[perUnit]; ok {
-		return promPerUnit
-	}
-	return perUnit
-}
-
-// Returns whether the slice contains the specified value
-func contains(slice []string, value string) bool {
-	for _, sliceEntry := range slice {
-		if sliceEntry == value {
-			return true
-		}
-	}
-	return false
-}
-
-// Remove the specified value from the slice
-func removeItem(slice []string, value string) []string {
-	newSlice := make([]string, 0, len(slice))
-	for _, sliceEntry := range slice {
-		if sliceEntry != value {
-			newSlice = append(newSlice, sliceEntry)
-		}
-	}
-	return newSlice
-}
diff --git a/storage/remote/otlptranslator/prometheusremotewrite/helper.go b/storage/remote/otlptranslator/prometheusremotewrite/helper.go
deleted file mode 100644
index beae5c80e..000000000
--- a/storage/remote/otlptranslator/prometheusremotewrite/helper.go
+++ /dev/null
@@ -1,559 +0,0 @@
-// DO NOT EDIT. COPIED AS-IS. SEE README.md
-
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package prometheusremotewrite // import "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
-
-import (
-	"encoding/hex"
-	"fmt"
-	"log"
-	"math"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-	"unicode/utf8"
-
-	"github.com/prometheus/common/model"
-	"github.com/prometheus/prometheus/model/timestamp"
-	"github.com/prometheus/prometheus/model/value"
-	"github.com/prometheus/prometheus/prompb"
-	"go.opentelemetry.io/collector/pdata/pcommon"
-	"go.opentelemetry.io/collector/pdata/pmetric"
-	conventions "go.opentelemetry.io/collector/semconv/v1.6.1"
-
-	prometheustranslator "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus"
-)
-
-const (
-	nameStr       = "__name__"
-	sumStr        = "_sum"
-	countStr      = "_count"
-	bucketStr     = "_bucket"
-	leStr         = "le"
-	quantileStr   = "quantile"
-	pInfStr       = "+Inf"
-	createdSuffix = "_created"
-	// maxExemplarRunes is the maximum number of UTF-8 exemplar characters
-	// according to the prometheus specification
-	// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#exemplars
-	maxExemplarRunes = 128
-	// Trace and Span id keys are defined as part of the spec:
-	// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification%2Fmetrics%2Fdatamodel.md#exemplars-2
-	traceIDKey       = "trace_id"
-	spanIDKey        = "span_id"
-	infoType         = "info"
-	targetMetricName = "target_info"
-)
-
-type bucketBoundsData struct {
-	sig   string
-	bound float64
-}
-
-// byBucketBoundsData enables the usage of sort.Sort() with a slice of bucket bounds
-type byBucketBoundsData []bucketBoundsData
-
-func (m byBucketBoundsData) Len() int           { return len(m) }
-func (m byBucketBoundsData) Less(i, j int) bool { return m[i].bound < m[j].bound }
-func (m byBucketBoundsData) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
-
-// ByLabelName enables the usage of sort.Sort() with a slice of labels
-type ByLabelName []prompb.Label
-
-func (a ByLabelName) Len() int           { return len(a) }
-func (a ByLabelName) Less(i, j int) bool { return a[i].Name < a[j].Name }
-func (a ByLabelName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-
-// addSample finds a TimeSeries in tsMap that corresponds to the label set labels, and add sample to the TimeSeries; it
-// creates a new TimeSeries in the map if not found and returns the time series signature.
-// tsMap will be unmodified if either labels or sample is nil, but can still be modified if the exemplar is nil.
-func addSample(tsMap map[string]*prompb.TimeSeries, sample *prompb.Sample, labels []prompb.Label,
-	datatype string) string {
-
-	if sample == nil || labels == nil || tsMap == nil {
-		return ""
-	}
-
-	sig := timeSeriesSignature(datatype, &labels)
-	ts, ok := tsMap[sig]
-
-	if ok {
-		ts.Samples = append(ts.Samples, *sample)
-	} else {
-		newTs := &prompb.TimeSeries{
-			Labels:  labels,
-			Samples: []prompb.Sample{*sample},
-		}
-		tsMap[sig] = newTs
-	}
-
-	return sig
-}
-
-// addExemplars finds a bucket bound that corresponds to the exemplars value and add the exemplar to the specific sig;
-// we only add exemplars if samples are presents
-// tsMap is unmodified if either of its parameters is nil and samples are nil.
-func addExemplars(tsMap map[string]*prompb.TimeSeries, exemplars []prompb.Exemplar, bucketBoundsData []bucketBoundsData) {
-	if tsMap == nil || bucketBoundsData == nil || exemplars == nil {
-		return
-	}
-
-	sort.Sort(byBucketBoundsData(bucketBoundsData))
-
-	for _, exemplar := range exemplars {
-		addExemplar(tsMap, bucketBoundsData, exemplar)
-	}
-}
-
-func addExemplar(tsMap map[string]*prompb.TimeSeries, bucketBounds []bucketBoundsData, exemplar prompb.Exemplar) {
-	for _, bucketBound := range bucketBounds {
-		sig := bucketBound.sig
-		bound := bucketBound.bound
-
-		_, ok := tsMap[sig]
-		if ok {
-			if tsMap[sig].Samples != nil {
-				if exemplar.Value <= bound {
-					tsMap[sig].Exemplars = append(tsMap[sig].Exemplars, exemplar)
-					return
-				}
-			}
-		}
-	}
-}
-
-// timeSeries return a string signature in the form of:
-//
-//	TYPE-label1-value1- ...  -labelN-valueN
-//
-// the label slice should not contain duplicate label names; this method sorts the slice by label name before creating
-// the signature.
-func timeSeriesSignature(datatype string, labels *[]prompb.Label) string {
-	b := strings.Builder{}
-	b.WriteString(datatype)
-
-	sort.Sort(ByLabelName(*labels))
-
-	for _, lb := range *labels {
-		b.WriteString("-")
-		b.WriteString(lb.GetName())
-		b.WriteString("-")
-		b.WriteString(lb.GetValue())
-	}
-
-	return b.String()
-}
-
-// createAttributes creates a slice of Cortex Label with OTLP attributes and pairs of string values.
-// Unpaired string value is ignored. String pairs overwrites OTLP labels if collision happens, and the overwrite is
-// logged. Resultant label names are sanitized.
-func createAttributes(resource pcommon.Resource, attributes pcommon.Map, externalLabels map[string]string, extras ...string) []prompb.Label {
-	// map ensures no duplicate label name
-	l := map[string]prompb.Label{}
-
-	// Ensure attributes are sorted by key for consistent merging of keys which
-	// collide when sanitized.
-	labels := make([]prompb.Label, 0, attributes.Len())
-	attributes.Range(func(key string, value pcommon.Value) bool {
-		labels = append(labels, prompb.Label{Name: key, Value: value.AsString()})
-		return true
-	})
-	sort.Stable(ByLabelName(labels))
-
-	for _, label := range labels {
-		var finalKey = prometheustranslator.NormalizeLabel(label.Name)
-		if existingLabel, alreadyExists := l[finalKey]; alreadyExists {
-			existingLabel.Value = existingLabel.Value + ";" + label.Value
-			l[finalKey] = existingLabel
-		} else {
-			l[finalKey] = prompb.Label{
-				Name:  finalKey,
-				Value: label.Value,
-			}
-		}
-	}
-
-	// Map service.name + service.namespace to job
-	if serviceName, ok := resource.Attributes().Get(conventions.AttributeServiceName); ok {
-		val := serviceName.AsString()
-		if serviceNamespace, ok := resource.Attributes().Get(conventions.AttributeServiceNamespace); ok {
-			val = fmt.Sprintf("%s/%s", serviceNamespace.AsString(), val)
-		}
-		l[model.JobLabel] = prompb.Label{
-			Name:  model.JobLabel,
-			Value: val,
-		}
-	}
-	// Map service.instance.id to instance
-	if instance, ok := resource.Attributes().Get(conventions.AttributeServiceInstanceID); ok {
-		l[model.InstanceLabel] = prompb.Label{
-			Name:  model.InstanceLabel,
-			Value: instance.AsString(),
-		}
-	}
-	for key, value := range externalLabels {
-		// External labels have already been sanitized
-		if _, alreadyExists := l[key]; alreadyExists {
-			// Skip external labels if they are overridden by metric attributes
-			continue
-		}
-		l[key] = prompb.Label{
-			Name:  key,
-			Value: value,
-		}
-	}
-
-	for i := 0; i < len(extras); i += 2 {
-		if i+1 >= len(extras) {
-			break
-		}
-		_, found := l[extras[i]]
-		if found {
-			log.Println("label " + extras[i] + " is overwritten. Check if Prometheus reserved labels are used.")
-		}
-		// internal labels should be maintained
-		name := extras[i]
-		if !(len(name) > 4 && name[:2] == "__" && name[len(name)-2:] == "__") {
-			name = prometheustranslator.NormalizeLabel(name)
-		}
-		l[name] = prompb.Label{
-			Name:  name,
-			Value: extras[i+1],
-		}
-	}
-
-	s := make([]prompb.Label, 0, len(l))
-	for _, lb := range l {
-		s = append(s, lb)
-	}
-
-	return s
-}
-
-// isValidAggregationTemporality checks whether an OTel metric has a valid
-// aggregation temporality for conversion to a Prometheus metric.
-func isValidAggregationTemporality(metric pmetric.Metric) bool {
-	switch metric.Type() {
-	case pmetric.MetricTypeGauge, pmetric.MetricTypeSummary:
-		return true
-	case pmetric.MetricTypeSum:
-		return metric.Sum().AggregationTemporality() == pmetric.AggregationTemporalityCumulative
-	case pmetric.MetricTypeHistogram:
-		return metric.Histogram().AggregationTemporality() == pmetric.AggregationTemporalityCumulative
-	case pmetric.MetricTypeExponentialHistogram:
-		return metric.ExponentialHistogram().AggregationTemporality() == pmetric.AggregationTemporalityCumulative
-	}
-	return false
-}
-
-// addSingleHistogramDataPoint converts pt to 2 + min(len(ExplicitBounds), len(BucketCount)) + 1 samples. It
-// ignore extra buckets if len(ExplicitBounds) > len(BucketCounts)
-func addSingleHistogramDataPoint(pt pmetric.HistogramDataPoint, resource pcommon.Resource, metric pmetric.Metric, settings Settings, tsMap map[string]*prompb.TimeSeries) {
-	timestamp := convertTimeStamp(pt.Timestamp())
-	// sum, count, and buckets of the histogram should append suffix to baseName
-	baseName := prometheustranslator.BuildPromCompliantName(metric, settings.Namespace)
-
-	// If the sum is unset, it indicates the _sum metric point should be
-	// omitted
-	if pt.HasSum() {
-		// treat sum as a sample in an individual TimeSeries
-		sum := &prompb.Sample{
-			Value:     pt.Sum(),
-			Timestamp: timestamp,
-		}
-		if pt.Flags().NoRecordedValue() {
-			sum.Value = math.Float64frombits(value.StaleNaN)
-		}
-
-		sumlabels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels, nameStr, baseName+sumStr)
-		addSample(tsMap, sum, sumlabels, metric.Type().String())
-
-	}
-
-	// treat count as a sample in an individual TimeSeries
-	count := &prompb.Sample{
-		Value:     float64(pt.Count()),
-		Timestamp: timestamp,
-	}
-	if pt.Flags().NoRecordedValue() {
-		count.Value = math.Float64frombits(value.StaleNaN)
-	}
-
-	countlabels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels, nameStr, baseName+countStr)
-	addSample(tsMap, count, countlabels, metric.Type().String())
-
-	// cumulative count for conversion to cumulative histogram
-	var cumulativeCount uint64
-
-	promExemplars := getPromExemplars[pmetric.HistogramDataPoint](pt)
-
-	var bucketBounds []bucketBoundsData
-
-	// process each bound, based on histograms proto definition, # of buckets = # of explicit bounds + 1
-	for i := 0; i < pt.ExplicitBounds().Len() && i < pt.BucketCounts().Len(); i++ {
-		bound := pt.ExplicitBounds().At(i)
-		cumulativeCount += pt.BucketCounts().At(i)
-		bucket := &prompb.Sample{
-			Value:     float64(cumulativeCount),
-			Timestamp: timestamp,
-		}
-		if pt.Flags().NoRecordedValue() {
-			bucket.Value = math.Float64frombits(value.StaleNaN)
-		}
-		boundStr := strconv.FormatFloat(bound, 'f', -1, 64)
-		labels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels, nameStr, baseName+bucketStr, leStr, boundStr)
-		sig := addSample(tsMap, bucket, labels, metric.Type().String())
-
-		bucketBounds = append(bucketBounds, bucketBoundsData{sig: sig, bound: bound})
-	}
-	// add le=+Inf bucket
-	infBucket := &prompb.Sample{
-		Timestamp: timestamp,
-	}
-	if pt.Flags().NoRecordedValue() {
-		infBucket.Value = math.Float64frombits(value.StaleNaN)
-	} else {
-		infBucket.Value = float64(pt.Count())
-	}
-	infLabels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels, nameStr, baseName+bucketStr, leStr, pInfStr)
-	sig := addSample(tsMap, infBucket, infLabels, metric.Type().String())
-
-	bucketBounds = append(bucketBounds, bucketBoundsData{sig: sig, bound: math.Inf(1)})
-	addExemplars(tsMap, promExemplars, bucketBounds)
-
-	// add _created time series if needed
-	startTimestamp := pt.StartTimestamp()
-	if settings.ExportCreatedMetric && startTimestamp != 0 {
-		createdLabels := createAttributes(
-			resource,
-			pt.Attributes(),
-			settings.ExternalLabels,
-			nameStr,
-			baseName+createdSuffix,
-		)
-		addCreatedTimeSeriesIfNeeded(tsMap, createdLabels, startTimestamp, metric.Type().String())
-	}
-}
-
-type exemplarType interface {
-	pmetric.ExponentialHistogramDataPoint | pmetric.HistogramDataPoint | pmetric.NumberDataPoint
-	Exemplars() pmetric.ExemplarSlice
-}
-
-func getPromExemplars[T exemplarType](pt T) []prompb.Exemplar {
-	var promExemplars []prompb.Exemplar
-
-	for i := 0; i < pt.Exemplars().Len(); i++ {
-		exemplar := pt.Exemplars().At(i)
-		exemplarRunes := 0
-
-		promExemplar := &prompb.Exemplar{
-			Value:     exemplar.DoubleValue(),
-			Timestamp: timestamp.FromTime(exemplar.Timestamp().AsTime()),
-		}
-		if traceID := exemplar.TraceID(); !traceID.IsEmpty() {
-			val := hex.EncodeToString(traceID[:])
-			exemplarRunes += utf8.RuneCountInString(traceIDKey) + utf8.RuneCountInString(val)
-			promLabel := prompb.Label{
-				Name:  traceIDKey,
-				Value: val,
-			}
-			promExemplar.Labels = append(promExemplar.Labels, promLabel)
-		}
-		if spanID := exemplar.SpanID(); !spanID.IsEmpty() {
-			val := hex.EncodeToString(spanID[:])
-			exemplarRunes += utf8.RuneCountInString(spanIDKey) + utf8.RuneCountInString(val)
-			promLabel := prompb.Label{
-				Name:  spanIDKey,
-				Value: val,
-			}
-			promExemplar.Labels = append(promExemplar.Labels, promLabel)
-		}
-		var labelsFromAttributes []prompb.Label
-
-		exemplar.FilteredAttributes().Range(func(key string, value pcommon.Value) bool {
-			val := value.AsString()
-			exemplarRunes += utf8.RuneCountInString(key) + utf8.RuneCountInString(val)
-			promLabel := prompb.Label{
-				Name:  key,
-				Value: val,
-			}
-
-			labelsFromAttributes = append(labelsFromAttributes, promLabel)
-
-			return true
-		})
-		if exemplarRunes <= maxExemplarRunes {
-			// only append filtered attributes if it does not cause exemplar
-			// labels to exceed the max number of runes
-			promExemplar.Labels = append(promExemplar.Labels, labelsFromAttributes...)
-		}
-
-		promExemplars = append(promExemplars, *promExemplar)
-	}
-
-	return promExemplars
-}
-
-// mostRecentTimestampInMetric returns the latest timestamp in a batch of metrics
-func mostRecentTimestampInMetric(metric pmetric.Metric) pcommon.Timestamp {
-	var ts pcommon.Timestamp
-	// handle individual metric based on type
-	switch metric.Type() {
-	case pmetric.MetricTypeGauge:
-		dataPoints := metric.Gauge().DataPoints()
-		for x := 0; x < dataPoints.Len(); x++ {
-			ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
-		}
-	case pmetric.MetricTypeSum:
-		dataPoints := metric.Sum().DataPoints()
-		for x := 0; x < dataPoints.Len(); x++ {
-			ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
-		}
-	case pmetric.MetricTypeHistogram:
-		dataPoints := metric.Histogram().DataPoints()
-		for x := 0; x < dataPoints.Len(); x++ {
-			ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
-		}
-	case pmetric.MetricTypeExponentialHistogram:
-		dataPoints := metric.ExponentialHistogram().DataPoints()
-		for x := 0; x < dataPoints.Len(); x++ {
-			ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
-		}
-	case pmetric.MetricTypeSummary:
-		dataPoints := metric.Summary().DataPoints()
-		for x := 0; x < dataPoints.Len(); x++ {
-			ts = maxTimestamp(ts, dataPoints.At(x).Timestamp())
-		}
-	}
-	return ts
-}
-
-func maxTimestamp(a, b pcommon.Timestamp) pcommon.Timestamp {
-	if a > b {
-		return a
-	}
-	return b
-}
-
-// addSingleSummaryDataPoint converts pt to len(QuantileValues) + 2 samples.
-func addSingleSummaryDataPoint(pt pmetric.SummaryDataPoint, resource pcommon.Resource, metric pmetric.Metric, settings Settings,
-	tsMap map[string]*prompb.TimeSeries) {
-	timestamp := convertTimeStamp(pt.Timestamp())
-	// sum and count of the summary should append suffix to baseName
-	baseName := prometheustranslator.BuildPromCompliantName(metric, settings.Namespace)
-	// treat sum as a sample in an individual TimeSeries
-	sum := &prompb.Sample{
-		Value:     pt.Sum(),
-		Timestamp: timestamp,
-	}
-	if pt.Flags().NoRecordedValue() {
-		sum.Value = math.Float64frombits(value.StaleNaN)
-	}
-	sumlabels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels, nameStr, baseName+sumStr)
-	addSample(tsMap, sum, sumlabels, metric.Type().String())
-
-	// treat count as a sample in an individual TimeSeries
-	count := &prompb.Sample{
-		Value:     float64(pt.Count()),
-		Timestamp: timestamp,
-	}
-	if pt.Flags().NoRecordedValue() {
-		count.Value = math.Float64frombits(value.StaleNaN)
-	}
-	countlabels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels, nameStr, baseName+countStr)
-	addSample(tsMap, count, countlabels, metric.Type().String())
-
-	// process each percentile/quantile
-	for i := 0; i < pt.QuantileValues().Len(); i++ {
-		qt := pt.QuantileValues().At(i)
-		quantile := &prompb.Sample{
-			Value:     qt.Value(),
-			Timestamp: timestamp,
-		}
-		if pt.Flags().NoRecordedValue() {
-			quantile.Value = math.Float64frombits(value.StaleNaN)
-		}
-		percentileStr := strconv.FormatFloat(qt.Quantile(), 'f', -1, 64)
-		qtlabels := createAttributes(resource, pt.Attributes(), settings.ExternalLabels, nameStr, baseName, quantileStr, percentileStr)
-		addSample(tsMap, quantile, qtlabels, metric.Type().String())
-	}
-
-	// add _created time series if needed
-	startTimestamp := pt.StartTimestamp()
-	if settings.ExportCreatedMetric && startTimestamp != 0 {
-		createdLabels := createAttributes(
-			resource,
-			pt.Attributes(),
-			settings.ExternalLabels,
-			nameStr,
-			baseName+createdSuffix,
-		)
-		addCreatedTimeSeriesIfNeeded(tsMap, createdLabels, startTimestamp, metric.Type().String())
-	}
-}
-
-// addCreatedTimeSeriesIfNeeded adds {name}_created time series with a single
-// sample. If the series exists, then new samples won't be added.
-func addCreatedTimeSeriesIfNeeded(
-	series map[string]*prompb.TimeSeries,
-	labels []prompb.Label,
-	startTimestamp pcommon.Timestamp,
-	metricType string,
-) {
-	sig := timeSeriesSignature(metricType, &labels)
-	if _, ok := series[sig]; !ok {
-		series[sig] = &prompb.TimeSeries{
-			Labels: labels,
-			Samples: []prompb.Sample{
-				{ // convert ns to ms
-					Value: float64(convertTimeStamp(startTimestamp)),
-				},
-			},
-		}
-	}
-}
-
-// addResourceTargetInfo converts the resource to the target info metric
-func addResourceTargetInfo(resource pcommon.Resource, settings Settings, timestamp pcommon.Timestamp, tsMap map[string]*prompb.TimeSeries) {
-	if settings.DisableTargetInfo {
-		return
-	}
-	// Use resource attributes (other than those used for job+instance) as the
-	// metric labels for the target info metric
-	attributes := pcommon.NewMap()
-	resource.Attributes().CopyTo(attributes)
-	attributes.RemoveIf(func(k string, _ pcommon.Value) bool {
-		switch k {
-		case conventions.AttributeServiceName, conventions.AttributeServiceNamespace, conventions.AttributeServiceInstanceID:
-			// Remove resource attributes used for job + instance
-			return true
-		default:
-			return false
-		}
-	})
-	if attributes.Len() == 0 {
-		// If we only have job + instance, then target_info isn't useful, so don't add it.
-		return
-	}
-	// create parameters for addSample
-	name := targetMetricName
-	if len(settings.Namespace) > 0 {
-		name = settings.Namespace + "_" + name
-	}
-	labels := createAttributes(resource, attributes, settings.ExternalLabels, nameStr, name)
-	sample := &prompb.Sample{
-		Value: float64(1),
-		// convert ns to ms
-		Timestamp: convertTimeStamp(timestamp),
-	}
-	addSample(tsMap, sample, labels, infoType)
-}
-
-// convertTimeStamp converts OTLP timestamp in ns to timestamp in ms
-func convertTimeStamp(timestamp pcommon.Timestamp) int64 {
-	return timestamp.AsTime().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
-}
diff --git a/storage/remote/otlptranslator/prometheusremotewrite/histograms.go b/storage/remote/otlptranslator/prometheusremotewrite/histograms.go
deleted file mode 100644
index 9a4ec6e11..000000000
--- a/storage/remote/otlptranslator/prometheusremotewrite/histograms.go
+++ /dev/null
@@ -1,158 +0,0 @@
-// DO NOT EDIT. COPIED AS-IS. SEE README.md
-
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package prometheusremotewrite // import "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
-
-import (
-	"fmt"
-	"math"
-
-	"github.com/prometheus/common/model"
-	"github.com/prometheus/prometheus/model/value"
-	"github.com/prometheus/prometheus/prompb"
-	"go.opentelemetry.io/collector/pdata/pcommon"
-	"go.opentelemetry.io/collector/pdata/pmetric"
-)
-
-const defaultZeroThreshold = 1e-128
-
-func addSingleExponentialHistogramDataPoint(
-	metric string,
-	pt pmetric.ExponentialHistogramDataPoint,
-	resource pcommon.Resource,
-	settings Settings,
-	series map[string]*prompb.TimeSeries,
-) error {
-	labels := createAttributes(
-		resource,
-		pt.Attributes(),
-		settings.ExternalLabels,
-		model.MetricNameLabel, metric,
-	)
-
-	sig := timeSeriesSignature(
-		pmetric.MetricTypeExponentialHistogram.String(),
-		&labels,
-	)
-	ts, ok := series[sig]
-	if !ok {
-		ts = &prompb.TimeSeries{
-			Labels: labels,
-		}
-		series[sig] = ts
-	}
-
-	histogram, err := exponentialToNativeHistogram(pt)
-	if err != nil {
-		return err
-	}
-	ts.Histograms = append(ts.Histograms, histogram)
-
-	exemplars := getPromExemplars[pmetric.ExponentialHistogramDataPoint](pt)
-	ts.Exemplars = append(ts.Exemplars, exemplars...)
-
-	return nil
-}
-
-// exponentialToNativeHistogram  translates OTel Exponential Histogram data point
-// to Prometheus Native Histogram.
-func exponentialToNativeHistogram(p pmetric.ExponentialHistogramDataPoint) (prompb.Histogram, error) {
-	scale := p.Scale()
-	if scale < -4 || scale > 8 {
-		return prompb.Histogram{},
-			fmt.Errorf("cannot convert exponential to native histogram."+
-				" Scale must be <= 8 and >= -4, was %d", scale)
-		// TODO: downscale to 8 if scale > 8
-	}
-
-	pSpans, pDeltas := convertBucketsLayout(p.Positive())
-	nSpans, nDeltas := convertBucketsLayout(p.Negative())
-
-	h := prompb.Histogram{
-		Schema: scale,
-
-		ZeroCount: &prompb.Histogram_ZeroCountInt{ZeroCountInt: p.ZeroCount()},
-		// TODO use zero_threshold, if set, see
-		// https://github.com/open-telemetry/opentelemetry-proto/pull/441
-		ZeroThreshold: defaultZeroThreshold,
-
-		PositiveSpans:  pSpans,
-		PositiveDeltas: pDeltas,
-		NegativeSpans:  nSpans,
-		NegativeDeltas: nDeltas,
-
-		Timestamp: convertTimeStamp(p.Timestamp()),
-	}
-
-	if p.Flags().NoRecordedValue() {
-		h.Sum = math.Float64frombits(value.StaleNaN)
-		h.Count = &prompb.Histogram_CountInt{CountInt: value.StaleNaN}
-	} else {
-		if p.HasSum() {
-			h.Sum = p.Sum()
-		}
-		h.Count = &prompb.Histogram_CountInt{CountInt: p.Count()}
-	}
-	return h, nil
-}
-
-// convertBucketsLayout translates OTel Exponential Histogram dense buckets
-// representation to Prometheus Native Histogram sparse bucket representation.
-//
-// The translation logic is taken from the client_golang `histogram.go#makeBuckets`
-// function, see `makeBuckets` https://github.com/prometheus/client_golang/blob/main/prometheus/histogram.go
-// The bucket indexes conversion was adjusted, since OTel exp. histogram bucket
-// index 0 corresponds to the range (1, base] while Prometheus bucket index 0
-// to the range (base 1].
-func convertBucketsLayout(buckets pmetric.ExponentialHistogramDataPointBuckets) ([]prompb.BucketSpan, []int64) {
-	bucketCounts := buckets.BucketCounts()
-	if bucketCounts.Len() == 0 {
-		return nil, nil
-	}
-
-	var (
-		spans         []prompb.BucketSpan
-		deltas        []int64
-		prevCount     int64
-		nextBucketIdx int32
-	)
-
-	appendDelta := func(count int64) {
-		spans[len(spans)-1].Length++
-		deltas = append(deltas, count-prevCount)
-		prevCount = count
-	}
-
-	for i := 0; i < bucketCounts.Len(); i++ {
-		count := int64(bucketCounts.At(i))
-		if count == 0 {
-			continue
-		}
-
-		// The offset is adjusted by 1 as described above.
-		bucketIdx := int32(i) + buckets.Offset() + 1
-		delta := bucketIdx - nextBucketIdx
-		if i == 0 || delta > 2 {
-			// We have to create a new span, either because we are
-			// at the very beginning, or because we have found a gap
-			// of more than two buckets. The constant 2 is copied from the logic in
-			// https://github.com/prometheus/client_golang/blob/27f0506d6ebbb117b6b697d0552ee5be2502c5f2/prometheus/histogram.go#L1296
-			spans = append(spans, prompb.BucketSpan{
-				Offset: delta,
-				Length: 0,
-			})
-		} else {
-			// We have found a small gap (or no gap at all).
-			// Insert empty buckets as needed.
-			for j := int32(0); j < delta; j++ {
-				appendDelta(0)
-			}
-		}
-		appendDelta(count)
-		nextBucketIdx = bucketIdx + 1
-	}
-
-	return spans, deltas
-}
diff --git a/storage/remote/otlptranslator/prometheusremotewrite/metrics_to_prw.go b/storage/remote/otlptranslator/prometheusremotewrite/metrics_to_prw.go
deleted file mode 100644
index 34ee762dd..000000000
--- a/storage/remote/otlptranslator/prometheusremotewrite/metrics_to_prw.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// DO NOT EDIT. COPIED AS-IS. SEE README.md
-
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package prometheusremotewrite // import "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
-
-import (
-	"errors"
-	"fmt"
-
-	"github.com/prometheus/prometheus/prompb"
-	"go.opentelemetry.io/collector/pdata/pcommon"
-	"go.opentelemetry.io/collector/pdata/pmetric"
-	"go.uber.org/multierr"
-
-	prometheustranslator "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus"
-)
-
-type Settings struct {
-	Namespace           string
-	ExternalLabels      map[string]string
-	DisableTargetInfo   bool
-	ExportCreatedMetric bool
-}
-
-// FromMetrics converts pmetric.Metrics to prometheus remote write format.
-func FromMetrics(md pmetric.Metrics, settings Settings) (tsMap map[string]*prompb.TimeSeries, errs error) {
-	tsMap = make(map[string]*prompb.TimeSeries)
-
-	resourceMetricsSlice := md.ResourceMetrics()
-	for i := 0; i < resourceMetricsSlice.Len(); i++ {
-		resourceMetrics := resourceMetricsSlice.At(i)
-		resource := resourceMetrics.Resource()
-		scopeMetricsSlice := resourceMetrics.ScopeMetrics()
-		// keep track of the most recent timestamp in the ResourceMetrics for
-		// use with the "target" info metric
-		var mostRecentTimestamp pcommon.Timestamp
-		for j := 0; j < scopeMetricsSlice.Len(); j++ {
-			scopeMetrics := scopeMetricsSlice.At(j)
-			metricSlice := scopeMetrics.Metrics()
-
-			// TODO: decide if instrumentation library information should be exported as labels
-			for k := 0; k < metricSlice.Len(); k++ {
-				metric := metricSlice.At(k)
-				mostRecentTimestamp = maxTimestamp(mostRecentTimestamp, mostRecentTimestampInMetric(metric))
-
-				if !isValidAggregationTemporality(metric) {
-					errs = multierr.Append(errs, fmt.Errorf("invalid temporality and type combination for metric %q", metric.Name()))
-					continue
-				}
-
-				// handle individual metric based on type
-				switch metric.Type() {
-				case pmetric.MetricTypeGauge:
-					dataPoints := metric.Gauge().DataPoints()
-					if dataPoints.Len() == 0 {
-						errs = multierr.Append(errs, fmt.Errorf("empty data points. %s is dropped", metric.Name()))
-					}
-					for x := 0; x < dataPoints.Len(); x++ {
-						addSingleGaugeNumberDataPoint(dataPoints.At(x), resource, metric, settings, tsMap)
-					}
-				case pmetric.MetricTypeSum:
-					dataPoints := metric.Sum().DataPoints()
-					if dataPoints.Len() == 0 {
-						errs = multierr.Append(errs, fmt.Errorf("empty data points. %s is dropped", metric.Name()))
-					}
-					for x := 0; x < dataPoints.Len(); x++ {
-						addSingleSumNumberDataPoint(dataPoints.At(x), resource, metric, settings, tsMap)
-					}
-				case pmetric.MetricTypeHistogram:
-					dataPoints := metric.Histogram().DataPoints()
-					if dataPoints.Len() == 0 {
-						errs = multierr.Append(errs, fmt.Errorf("empty data points. %s is dropped", metric.Name()))
-					}
-					for x := 0; x < dataPoints.Len(); x++ {
-						addSingleHistogramDataPoint(dataPoints.At(x), resource, metric, settings, tsMap)
-					}
-				case pmetric.MetricTypeExponentialHistogram:
-					dataPoints := metric.ExponentialHistogram().DataPoints()
-					if dataPoints.Len() == 0 {
-						errs = multierr.Append(errs, fmt.Errorf("empty data points. %s is dropped", metric.Name()))
-					}
-					name := prometheustranslator.BuildPromCompliantName(metric, settings.Namespace)
-					for x := 0; x < dataPoints.Len(); x++ {
-						errs = multierr.Append(
-							errs,
-							addSingleExponentialHistogramDataPoint(
-								name,
-								dataPoints.At(x),
-								resource,
-								settings,
-								tsMap,
-							),
-						)
-					}
-				case pmetric.MetricTypeSummary:
-					dataPoints := metric.Summary().DataPoints()
-					if dataPoints.Len() == 0 {
-						errs = multierr.Append(errs, fmt.Errorf("empty data points. %s is dropped", metric.Name()))
-					}
-					for x := 0; x < dataPoints.Len(); x++ {
-						addSingleSummaryDataPoint(dataPoints.At(x), resource, metric, settings, tsMap)
-					}
-				default:
-					errs = multierr.Append(errs, errors.New("unsupported metric type"))
-				}
-			}
-		}
-		addResourceTargetInfo(resource, settings, mostRecentTimestamp, tsMap)
-	}
-
-	return
-}
diff --git a/storage/remote/otlptranslator/prometheusremotewrite/number_data_points.go b/storage/remote/otlptranslator/prometheusremotewrite/number_data_points.go
deleted file mode 100644
index 3a5d201dd..000000000
--- a/storage/remote/otlptranslator/prometheusremotewrite/number_data_points.go
+++ /dev/null
@@ -1,104 +0,0 @@
-// DO NOT EDIT. COPIED AS-IS. SEE README.md
-
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package prometheusremotewrite // import "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
-
-import (
-	"math"
-
-	"github.com/prometheus/common/model"
-	"github.com/prometheus/prometheus/model/value"
-	"github.com/prometheus/prometheus/prompb"
-	"go.opentelemetry.io/collector/pdata/pcommon"
-	"go.opentelemetry.io/collector/pdata/pmetric"
-
-	prometheustranslator "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus"
-)
-
-// addSingleSumNumberDataPoint converts the Gauge metric data point to a
-// Prometheus time series with samples and labels. The result is stored in the
-// series map.
-func addSingleGaugeNumberDataPoint(
-	pt pmetric.NumberDataPoint,
-	resource pcommon.Resource,
-	metric pmetric.Metric,
-	settings Settings,
-	series map[string]*prompb.TimeSeries,
-) {
-	name := prometheustranslator.BuildPromCompliantName(metric, settings.Namespace)
-	labels := createAttributes(
-		resource,
-		pt.Attributes(),
-		settings.ExternalLabels,
-		model.MetricNameLabel, name,
-	)
-	sample := &prompb.Sample{
-		// convert ns to ms
-		Timestamp: convertTimeStamp(pt.Timestamp()),
-	}
-	switch pt.ValueType() {
-	case pmetric.NumberDataPointValueTypeInt:
-		sample.Value = float64(pt.IntValue())
-	case pmetric.NumberDataPointValueTypeDouble:
-		sample.Value = pt.DoubleValue()
-	}
-	if pt.Flags().NoRecordedValue() {
-		sample.Value = math.Float64frombits(value.StaleNaN)
-	}
-	addSample(series, sample, labels, metric.Type().String())
-}
-
-// addSingleSumNumberDataPoint converts the Sum metric data point to a Prometheus
-// time series with samples, labels and exemplars. The result is stored in the
-// series map.
-func addSingleSumNumberDataPoint(
-	pt pmetric.NumberDataPoint,
-	resource pcommon.Resource,
-	metric pmetric.Metric,
-	settings Settings,
-	series map[string]*prompb.TimeSeries,
-) {
-	name := prometheustranslator.BuildPromCompliantName(metric, settings.Namespace)
-	labels := createAttributes(
-		resource,
-		pt.Attributes(),
-		settings.ExternalLabels,
-		model.MetricNameLabel, name,
-	)
-	sample := &prompb.Sample{
-		// convert ns to ms
-		Timestamp: convertTimeStamp(pt.Timestamp()),
-	}
-	switch pt.ValueType() {
-	case pmetric.NumberDataPointValueTypeInt:
-		sample.Value = float64(pt.IntValue())
-	case pmetric.NumberDataPointValueTypeDouble:
-		sample.Value = pt.DoubleValue()
-	}
-	if pt.Flags().NoRecordedValue() {
-		sample.Value = math.Float64frombits(value.StaleNaN)
-	}
-	sig := addSample(series, sample, labels, metric.Type().String())
-
-	if ts, ok := series[sig]; sig != "" && ok {
-		exemplars := getPromExemplars[pmetric.NumberDataPoint](pt)
-		ts.Exemplars = append(ts.Exemplars, exemplars...)
-	}
-
-	// add _created time series if needed
-	if settings.ExportCreatedMetric && metric.Sum().IsMonotonic() {
-		startTimestamp := pt.StartTimestamp()
-		if startTimestamp != 0 {
-			createdLabels := createAttributes(
-				resource,
-				pt.Attributes(),
-				settings.ExternalLabels,
-				nameStr,
-				name+createdSuffix,
-			)
-			addCreatedTimeSeriesIfNeeded(series, createdLabels, startTimestamp, metric.Type().String())
-		}
-	}
-}
diff --git a/storage/remote/otlptranslator/update-copy.sh b/storage/remote/otlptranslator/update-copy.sh
deleted file mode 100755
index 13a2a7a2e..000000000
--- a/storage/remote/otlptranslator/update-copy.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-
-OTEL_VERSION=v0.81.0
-
-git clone https://github.com/open-telemetry/opentelemetry-collector-contrib ./tmp
-cd ./tmp
-git checkout $OTEL_VERSION
-cd ..
-rm -rf ./prometheusremotewrite/*
-cp -r ./tmp/pkg/translator/prometheusremotewrite/*.go ./prometheusremotewrite
-rm -rf ./prometheusremotewrite/*_test.go
-rm -rf ./tmp
-
-sed -i '' 's#github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus#github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheus#g' ./prometheusremotewrite/*.go
-sed -i '' '1s#^#// DO NOT EDIT. COPIED AS-IS. SEE README.md\n\n#g' ./prometheusremotewrite/*.go
diff --git a/storage/remote/write_handler.go b/storage/remote/write_handler.go
index 6c0cd8a29..ac4caa619 100644
--- a/storage/remote/write_handler.go
+++ b/storage/remote/write_handler.go
@@ -27,7 +27,6 @@ import (
 	"github.com/prometheus/prometheus/model/exemplar"
 	"github.com/prometheus/prometheus/prompb"
 	"github.com/prometheus/prometheus/storage"
-	otlptranslator "github.com/prometheus/prometheus/storage/remote/otlptranslator/prometheusremotewrite"
 )
 
 type writeHandler struct {
@@ -120,9 +119,8 @@ func (h *writeHandler) write(ctx context.Context, req *prompb.WriteRequest) (err
 			samplesWithInvalidLabels++
 			continue
 		}
-		var ref storage.SeriesRef
 		for _, s := range ts.Samples {
-			ref, err = app.Append(ref, labels, s.Timestamp, s.Value)
+			_, err = app.Append(0, labels, s.Timestamp, s.Value)
 			if err != nil {
 				unwrappedErr := errors.Unwrap(err)
 				if unwrappedErr == nil {
@@ -179,60 +177,3 @@ func (h *writeHandler) write(ctx context.Context, req *prompb.WriteRequest) (err
 
 	return nil
 }
-
-// NewOTLPWriteHandler creates a http.Handler that accepts OTLP write requests and
-// writes them to the provided appendable.
-func NewOTLPWriteHandler(logger log.Logger, appendable storage.Appendable) http.Handler {
-	rwHandler := &writeHandler{
-		logger:     logger,
-		appendable: appendable,
-	}
-
-	return &otlpWriteHandler{
-		logger:    logger,
-		rwHandler: rwHandler,
-	}
-}
-
-type otlpWriteHandler struct {
-	logger    log.Logger
-	rwHandler *writeHandler
-}
-
-func (h *otlpWriteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	req, err := DecodeOTLPWriteRequest(r)
-	if err != nil {
-		level.Error(h.logger).Log("msg", "Error decoding remote write request", "err", err.Error())
-		http.Error(w, err.Error(), http.StatusBadRequest)
-		return
-	}
-
-	prwMetricsMap, errs := otlptranslator.FromMetrics(req.Metrics(), otlptranslator.Settings{})
-	if errs != nil {
-		level.Warn(h.logger).Log("msg", "Error translating OTLP metrics to Prometheus write request", "err", errs)
-	}
-
-	prwMetrics := make([]prompb.TimeSeries, 0, len(prwMetricsMap))
-
-	for _, ts := range prwMetricsMap {
-		prwMetrics = append(prwMetrics, *ts)
-	}
-
-	err = h.rwHandler.write(r.Context(), &prompb.WriteRequest{
-		Timeseries: prwMetrics,
-	})
-
-	switch err {
-	case nil:
-	case storage.ErrOutOfOrderSample, storage.ErrOutOfBounds, storage.ErrDuplicateSampleForTimestamp:
-		// Indicated an out of order sample is a bad request to prevent retries.
-		http.Error(w, err.Error(), http.StatusBadRequest)
-		return
-	default:
-		level.Error(h.logger).Log("msg", "Error appending remote write", "err", err.Error())
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		return
-	}
-
-	w.WriteHeader(http.StatusOK)
-}
diff --git a/storage/series.go b/storage/series.go
index e88964d88..b73f1e35c 100644
--- a/storage/series.go
+++ b/storage/series.go
@@ -58,21 +58,7 @@ func NewListSeries(lset labels.Labels, s []tsdbutil.Sample) *SeriesEntry {
 
 // NewListChunkSeriesFromSamples returns chunk series entry that allows to iterate over provided samples.
 // NOTE: It uses inefficient chunks encoding implementation, not caring about chunk size.
-// Use only for testing.
 func NewListChunkSeriesFromSamples(lset labels.Labels, samples ...[]tsdbutil.Sample) *ChunkSeriesEntry {
-	chksFromSamples := make([]chunks.Meta, 0, len(samples))
-	for _, s := range samples {
-		cfs, err := tsdbutil.ChunkFromSamples(s)
-		if err != nil {
-			return &ChunkSeriesEntry{
-				Lset: lset,
-				ChunkIteratorFn: func(it chunks.Iterator) chunks.Iterator {
-					return errChunksIterator{err: err}
-				},
-			}
-		}
-		chksFromSamples = append(chksFromSamples, cfs)
-	}
 	return &ChunkSeriesEntry{
 		Lset: lset,
 		ChunkIteratorFn: func(it chunks.Iterator) chunks.Iterator {
@@ -83,7 +69,9 @@ func NewListChunkSeriesFromSamples(lset labels.Labels, samples ...[]tsdbutil.Sam
 			} else {
 				chks = make([]chunks.Meta, 0, len(samples))
 			}
-			chks = append(chks, chksFromSamples...)
+			for _, s := range samples {
+				chks = append(chks, tsdbutil.ChunkFromSamples(s))
+			}
 			if existing {
 				lcsi.Reset(chks...)
 				return lcsi
@@ -292,10 +280,9 @@ func NewSeriesToChunkEncoder(series Series) ChunkSeries {
 
 func (s *seriesToChunkEncoder) Iterator(it chunks.Iterator) chunks.Iterator {
 	var (
-		chk, newChk chunkenc.Chunk
-		app         chunkenc.Appender
-		err         error
-		recoded     bool
+		chk chunkenc.Chunk
+		app *RecodingAppender
+		err error
 	)
 	mint := int64(math.MaxInt64)
 	maxt := int64(math.MinInt64)
@@ -310,17 +297,20 @@ func (s *seriesToChunkEncoder) Iterator(it chunks.Iterator) chunks.Iterator {
 	seriesIter := s.Series.Iterator(nil)
 	lastType := chunkenc.ValNone
 	for typ := seriesIter.Next(); typ != chunkenc.ValNone; typ = seriesIter.Next() {
+		chunkCreated := false
 		if typ != lastType || i >= seriesToChunkEncoderSplit {
 			// Create a new chunk if the sample type changed or too many samples in the current one.
 			chks = appendChunk(chks, mint, maxt, chk)
+			chunkCreated = true
 			chk, err = chunkenc.NewEmptyChunk(typ.ChunkEncoding())
 			if err != nil {
 				return errChunksIterator{err: err}
 			}
-			app, err = chk.Appender()
+			chkAppender, err := chk.Appender()
 			if err != nil {
 				return errChunksIterator{err: err}
 			}
+			app = NewRecodingAppender(&chk, chkAppender)
 			mint = int64(math.MaxInt64)
 			// maxt is immediately overwritten below which is why setting it here won't make a difference.
 			i = 0
@@ -339,33 +329,52 @@ func (s *seriesToChunkEncoder) Iterator(it chunks.Iterator) chunks.Iterator {
 			app.Append(t, v)
 		case chunkenc.ValHistogram:
 			t, h = seriesIter.AtHistogram()
-			newChk, recoded, app, err = app.AppendHistogram(nil, t, h, false)
-			if err != nil {
-				return errChunksIterator{err: err}
-			}
-			if newChk != nil {
-				if !recoded {
-					chks = appendChunk(chks, mint, maxt, chk)
-					mint = int64(math.MaxInt64)
-					// maxt is immediately overwritten below which is why setting it here won't make a difference.
-					i = 0
+			if ok, counterReset := app.AppendHistogram(t, h); !ok {
+				chks = appendChunk(chks, mint, maxt, chk)
+				histChunk := chunkenc.NewHistogramChunk()
+				chunkCreated = true
+				if counterReset {
+					histChunk.SetCounterResetHeader(chunkenc.CounterReset)
+				}
+				chk = histChunk
+
+				chkAppender, err := chk.Appender()
+				if err != nil {
+					return errChunksIterator{err: err}
+				}
+				mint = int64(math.MaxInt64)
+				i = 0
+				app = NewRecodingAppender(&chk, chkAppender)
+				if ok, _ := app.AppendHistogram(t, h); !ok {
+					panic("unexpected error while appending histogram")
 				}
-				chk = newChk
+			}
+			if chunkCreated && h.CounterResetHint == histogram.GaugeType {
+				chk.(*chunkenc.HistogramChunk).SetCounterResetHeader(chunkenc.GaugeType)
 			}
 		case chunkenc.ValFloatHistogram:
 			t, fh = seriesIter.AtFloatHistogram()
-			newChk, recoded, app, err = app.AppendFloatHistogram(nil, t, fh, false)
-			if err != nil {
-				return errChunksIterator{err: err}
-			}
-			if newChk != nil {
-				if !recoded {
-					chks = appendChunk(chks, mint, maxt, chk)
-					mint = int64(math.MaxInt64)
-					// maxt is immediately overwritten below which is why setting it here won't make a difference.
-					i = 0
+			if ok, counterReset := app.AppendFloatHistogram(t, fh); !ok {
+				chks = appendChunk(chks, mint, maxt, chk)
+				floatHistChunk := chunkenc.NewFloatHistogramChunk()
+				chunkCreated = true
+				if counterReset {
+					floatHistChunk.SetCounterResetHeader(chunkenc.CounterReset)
+				}
+				chk = floatHistChunk
+				chkAppender, err := chk.Appender()
+				if err != nil {
+					return errChunksIterator{err: err}
+				}
+				mint = int64(math.MaxInt64)
+				i = 0
+				app = NewRecodingAppender(&chk, chkAppender)
+				if ok, _ := app.AppendFloatHistogram(t, fh); !ok {
+					panic("unexpected error while float appending histogram")
 				}
-				chk = newChk
+			}
+			if chunkCreated && fh.CounterResetHint == histogram.GaugeType {
+				chk.(*chunkenc.FloatHistogramChunk).SetCounterResetHeader(chunkenc.GaugeType)
 			}
 		default:
 			return errChunksIterator{err: fmt.Errorf("unknown sample type %s", typ.String())}
@@ -456,3 +465,126 @@ func ExpandChunks(iter chunks.Iterator) ([]chunks.Meta, error) {
 	}
 	return result, iter.Err()
 }
+
+// RecodingAppender is a tsdb.Appender that recodes histogram samples if needed during appends.
+// It takes an existing appender and a chunk to which samples are appended.
+type RecodingAppender struct {
+	chk *chunkenc.Chunk
+	app chunkenc.Appender
+}
+
+func NewRecodingAppender(chk *chunkenc.Chunk, app chunkenc.Appender) *RecodingAppender {
+	return &RecodingAppender{
+		chk: chk,
+		app: app,
+	}
+}
+
+// Append appends a float sample to the appender.
+func (a *RecodingAppender) Append(t int64, v float64) {
+	a.app.Append(t, v)
+}
+
+// AppendHistogram appends a histogram sample to the underlying chunk.
+// The method returns false if the sample cannot be appended and a boolean value set to true
+// when it is not appendable because of a counter reset.
+// If counterReset is true, okToAppend is always false.
+func (a *RecodingAppender) AppendHistogram(t int64, h *histogram.Histogram) (okToAppend, counterReset bool) {
+	app, ok := a.app.(*chunkenc.HistogramAppender)
+	if !ok {
+		return false, false
+	}
+
+	if app.NumSamples() == 0 {
+		a.app.AppendHistogram(t, h)
+		return true, false
+	}
+
+	var (
+		pForwardInserts, nForwardInserts   []chunkenc.Insert
+		pBackwardInserts, nBackwardInserts []chunkenc.Insert
+		pMergedSpans, nMergedSpans         []histogram.Span
+	)
+	switch h.CounterResetHint {
+	case histogram.GaugeType:
+		pForwardInserts, nForwardInserts,
+			pBackwardInserts, nBackwardInserts,
+			pMergedSpans, nMergedSpans,
+			okToAppend = app.AppendableGauge(h)
+	default:
+		pForwardInserts, nForwardInserts, okToAppend, counterReset = app.Appendable(h)
+	}
+	if !okToAppend || counterReset {
+		return false, counterReset
+	}
+
+	if len(pBackwardInserts)+len(nBackwardInserts) > 0 {
+		h.PositiveSpans = pMergedSpans
+		h.NegativeSpans = nMergedSpans
+		app.RecodeHistogram(h, pBackwardInserts, nBackwardInserts)
+	}
+	if len(pForwardInserts) > 0 || len(nForwardInserts) > 0 {
+		chk, app := app.Recode(
+			pForwardInserts, nForwardInserts,
+			h.PositiveSpans, h.NegativeSpans,
+		)
+		*a.chk = chk
+		a.app = app
+	}
+
+	a.app.AppendHistogram(t, h)
+	return true, counterReset
+}
+
+// AppendFloatHistogram appends a float histogram sample to the underlying chunk.
+// The method returns false if the sample cannot be appended and a boolean value set to true
+// when it is not appendable because of a counter reset.
+// If counterReset is true, okToAppend is always false.
+func (a *RecodingAppender) AppendFloatHistogram(t int64, fh *histogram.FloatHistogram) (okToAppend, counterReset bool) {
+	app, ok := a.app.(*chunkenc.FloatHistogramAppender)
+	if !ok {
+		return false, false
+	}
+
+	if app.NumSamples() == 0 {
+		a.app.AppendFloatHistogram(t, fh)
+		return true, false
+	}
+
+	var (
+		pForwardInserts, nForwardInserts   []chunkenc.Insert
+		pBackwardInserts, nBackwardInserts []chunkenc.Insert
+		pMergedSpans, nMergedSpans         []histogram.Span
+	)
+	switch fh.CounterResetHint {
+	case histogram.GaugeType:
+		pForwardInserts, nForwardInserts,
+			pBackwardInserts, nBackwardInserts,
+			pMergedSpans, nMergedSpans,
+			okToAppend = app.AppendableGauge(fh)
+	default:
+		pForwardInserts, nForwardInserts, okToAppend, counterReset = app.Appendable(fh)
+	}
+
+	if !okToAppend || counterReset {
+		return false, counterReset
+	}
+
+	if len(pBackwardInserts)+len(nBackwardInserts) > 0 {
+		fh.PositiveSpans = pMergedSpans
+		fh.NegativeSpans = nMergedSpans
+		app.RecodeHistogramm(fh, pBackwardInserts, nBackwardInserts)
+	}
+
+	if len(pForwardInserts) > 0 || len(nForwardInserts) > 0 {
+		chunk, app := app.Recode(
+			pForwardInserts, nForwardInserts,
+			fh.PositiveSpans, fh.NegativeSpans,
+		)
+		*a.chk = chunk
+		a.app = app
+	}
+
+	a.app.AppendFloatHistogram(t, fh)
+	return true, counterReset
+}
diff --git a/tsdb/agent/db.go b/tsdb/agent/db.go
index 2ed51e4b8..d47095a23 100644
--- a/tsdb/agent/db.go
+++ b/tsdb/agent/db.go
@@ -985,25 +985,11 @@ func (a *appender) UpdateMetadata(storage.SeriesRef, labels.Labels, metadata.Met
 
 // Commit submits the collected samples and purges the batch.
 func (a *appender) Commit() error {
-	if err := a.log(); err != nil {
-		return err
-	}
-
-	a.clearData()
-	a.appenderPool.Put(a)
-	return nil
-}
-
-// log logs all pending data to the WAL.
-func (a *appender) log() error {
 	a.mtx.RLock()
 	defer a.mtx.RUnlock()
 
 	var encoder record.Encoder
 	buf := a.bufPool.Get().([]byte)
-	defer func() {
-		a.bufPool.Put(buf) //nolint:staticcheck
-	}()
 
 	if len(a.pendingSeries) > 0 {
 		buf = encoder.Series(a.pendingSeries, buf)
@@ -1065,11 +1051,12 @@ func (a *appender) log() error {
 		}
 	}
 
-	return nil
+	//nolint:staticcheck
+	a.bufPool.Put(buf)
+	return a.Rollback()
 }
 
-// clearData clears all pending data.
-func (a *appender) clearData() {
+func (a *appender) Rollback() error {
 	a.pendingSeries = a.pendingSeries[:0]
 	a.pendingSamples = a.pendingSamples[:0]
 	a.pendingHistograms = a.pendingHistograms[:0]
@@ -1078,39 +1065,6 @@ func (a *appender) clearData() {
 	a.sampleSeries = a.sampleSeries[:0]
 	a.histogramSeries = a.histogramSeries[:0]
 	a.floatHistogramSeries = a.floatHistogramSeries[:0]
-}
-
-func (a *appender) Rollback() error {
-	// Series are created in-memory regardless of rollback. This means we must
-	// log them to the WAL, otherwise subsequent commits may reference a series
-	// which was never written to the WAL.
-	if err := a.logSeries(); err != nil {
-		return err
-	}
-
-	a.clearData()
 	a.appenderPool.Put(a)
 	return nil
 }
-
-// logSeries logs only pending series records to the WAL.
-func (a *appender) logSeries() error {
-	a.mtx.RLock()
-	defer a.mtx.RUnlock()
-
-	if len(a.pendingSeries) > 0 {
-		buf := a.bufPool.Get().([]byte)
-		defer func() {
-			a.bufPool.Put(buf) //nolint:staticcheck
-		}()
-
-		var encoder record.Encoder
-		buf = encoder.Series(a.pendingSeries, buf)
-		if err := a.wal.Log(buf); err != nil {
-			return err
-		}
-		buf = buf[:0]
-	}
-
-	return nil
-}
diff --git a/tsdb/chunkenc/chunk.go b/tsdb/chunkenc/chunk.go
index 397c09ed8..1ebef3eb1 100644
--- a/tsdb/chunkenc/chunk.go
+++ b/tsdb/chunkenc/chunk.go
@@ -82,20 +82,8 @@ type Chunk interface {
 // Appender adds sample pairs to a chunk.
 type Appender interface {
 	Append(int64, float64)
-
-	// AppendHistogram and AppendFloatHistogram append a histogram sample to a histogram or float histogram chunk.
-	// Appending a histogram may require creating a completely new chunk or recoding (changing) the current chunk.
-	// The Appender prev is used to determine if there is a counter reset between the previous Appender and the current Appender.
-	// The Appender prev is optional and only taken into account when the first sample is being appended.
-	// The bool appendOnly governs what happens when a sample cannot be appended to the current chunk. If appendOnly is true, then
-	// in such case an error is returned without modifying the chunk. If appendOnly is false, then a new chunk is created or the
-	// current chunk is recoded to accommodate the sample.
-	// The returned Chunk c is nil if sample could be appended to the current Chunk, otherwise c is the new Chunk.
-	// The returned bool isRecoded can be used to distinguish between the new Chunk c being a completely new Chunk
-	// or the current Chunk recoded to a new Chunk.
-	// The Appender app that can be used for the next append is always returned.
-	AppendHistogram(prev *HistogramAppender, t int64, h *histogram.Histogram, appendOny bool) (c Chunk, isRecoded bool, app Appender, err error)
-	AppendFloatHistogram(prev *FloatHistogramAppender, t int64, h *histogram.FloatHistogram, appendOnly bool) (c Chunk, isRecoded bool, app Appender, err error)
+	AppendHistogram(t int64, h *histogram.Histogram)
+	AppendFloatHistogram(t int64, h *histogram.FloatHistogram)
 }
 
 // Iterator is a simple iterator that can only get the next value.
diff --git a/tsdb/chunkenc/float_histogram.go b/tsdb/chunkenc/float_histogram.go
index 2b9cc11a3..d49885a1c 100644
--- a/tsdb/chunkenc/float_histogram.go
+++ b/tsdb/chunkenc/float_histogram.go
@@ -15,7 +15,6 @@ package chunkenc
 
 import (
 	"encoding/binary"
-	"fmt"
 	"math"
 
 	"github.com/prometheus/prometheus/model/histogram"
@@ -81,10 +80,15 @@ func (c *FloatHistogramChunk) Layout() (
 	return readHistogramChunkLayout(&b)
 }
 
+// SetCounterResetHeader sets the counter reset header.
+func (c *FloatHistogramChunk) SetCounterResetHeader(h CounterResetHeader) {
+	setCounterResetHeader(h, c.Bytes())
+}
+
 // GetCounterResetHeader returns the info about the first 2 bits of the chunk
 // header.
 func (c *FloatHistogramChunk) GetCounterResetHeader() CounterResetHeader {
-	return CounterResetHeader(c.Bytes()[2] & CounterResetHeaderMask)
+	return CounterResetHeader(c.Bytes()[2] & 0b11000000)
 }
 
 // Compact implements the Chunk interface.
@@ -170,7 +174,7 @@ func newFloatHistogramIterator(b []byte) *floatHistogramIterator {
 	// The first 3 bytes contain chunk headers.
 	// We skip that for actual samples.
 	_, _ = it.br.readBits(24)
-	it.counterResetHeader = CounterResetHeader(b[2] & CounterResetHeaderMask)
+	it.counterResetHeader = CounterResetHeader(b[2] & 0b11000000)
 	return it
 }
 
@@ -194,11 +198,7 @@ type FloatHistogramAppender struct {
 }
 
 func (a *FloatHistogramAppender) GetCounterResetHeader() CounterResetHeader {
-	return CounterResetHeader(a.b.bytes()[2] & CounterResetHeaderMask)
-}
-
-func (a *FloatHistogramAppender) setCounterResetHeader(cr CounterResetHeader) {
-	a.b.bytes()[2] = (a.b.bytes()[2] & (^CounterResetHeaderMask)) | (byte(cr) & CounterResetHeaderMask)
+	return CounterResetHeader(a.b.bytes()[2] & 0b11000000)
 }
 
 func (a *FloatHistogramAppender) NumSamples() int {
@@ -211,7 +211,13 @@ func (a *FloatHistogramAppender) Append(int64, float64) {
 	panic("appended a float sample to a histogram chunk")
 }
 
-// appendable returns whether the chunk can be appended to, and if so whether
+// AppendHistogram implements Appender. This implementation panics because integer
+// histogram samples must never be appended to a float histogram chunk.
+func (a *FloatHistogramAppender) AppendHistogram(int64, *histogram.Histogram) {
+	panic("appended an integer histogram to a float histogram chunk")
+}
+
+// Appendable returns whether the chunk can be appended to, and if so whether
 // any recoding needs to happen using the provided inserts (in case of any new
 // buckets, positive or negative range, respectively). If the sample is a gauge
 // histogram, AppendableGauge must be used instead.
@@ -226,7 +232,7 @@ func (a *FloatHistogramAppender) Append(int64, float64) {
 // The method returns an additional boolean set to true if it is not appendable
 // because of a counter reset. If the given sample is stale, it is always ok to
 // append. If counterReset is true, okToAppend is always false.
-func (a *FloatHistogramAppender) appendable(h *histogram.FloatHistogram) (
+func (a *FloatHistogramAppender) Appendable(h *histogram.FloatHistogram) (
 	positiveInserts, negativeInserts []Insert,
 	okToAppend, counterReset bool,
 ) {
@@ -282,7 +288,7 @@ func (a *FloatHistogramAppender) appendable(h *histogram.FloatHistogram) (
 	return
 }
 
-// appendableGauge returns whether the chunk can be appended to, and if so
+// AppendableGauge returns whether the chunk can be appended to, and if so
 // whether:
 //  1. Any recoding needs to happen to the chunk using the provided inserts
 //     (in case of any new buckets, positive or negative range, respectively).
@@ -296,7 +302,7 @@ func (a *FloatHistogramAppender) appendable(h *histogram.FloatHistogram) (
 //   - The schema has changed.
 //   - The threshold for the zero bucket has changed.
 //   - The last sample in the chunk was stale while the current sample is not stale.
-func (a *FloatHistogramAppender) appendableGauge(h *histogram.FloatHistogram) (
+func (a *FloatHistogramAppender) AppendableGauge(h *histogram.FloatHistogram) (
 	positiveInserts, negativeInserts []Insert,
 	backwardPositiveInserts, backwardNegativeInserts []Insert,
 	positiveSpans, negativeSpans []histogram.Span,
@@ -393,11 +399,11 @@ func counterResetInAnyFloatBucket(oldBuckets []xorValue, newBuckets []float64, o
 	return false
 }
 
-// appendFloatHistogram appends a float histogram to the chunk. The caller must ensure that
+// AppendFloatHistogram appends a float histogram to the chunk. The caller must ensure that
 // the histogram is properly structured, e.g. the number of buckets used
 // corresponds to the number conveyed by the span structures. First call
 // Appendable() and act accordingly!
-func (a *FloatHistogramAppender) appendFloatHistogram(t int64, h *histogram.FloatHistogram) {
+func (a *FloatHistogramAppender) AppendFloatHistogram(t int64, h *histogram.FloatHistogram) {
 	var tDelta int64
 	num := binary.BigEndian.Uint16(a.b.bytes())
 
@@ -495,12 +501,12 @@ func (a *FloatHistogramAppender) writeXorValue(old *xorValue, v float64) {
 	old.value = v
 }
 
-// recode converts the current chunk to accommodate an expansion of the set of
+// Recode converts the current chunk to accommodate an expansion of the set of
 // (positive and/or negative) buckets used, according to the provided inserts,
 // resulting in the honoring of the provided new positive and negative spans. To
 // continue appending, use the returned Appender rather than the receiver of
 // this method.
-func (a *FloatHistogramAppender) recode(
+func (a *FloatHistogramAppender) Recode(
 	positiveInserts, negativeInserts []Insert,
 	positiveSpans, negativeSpans []histogram.Span,
 ) (Chunk, Appender) {
@@ -513,9 +519,8 @@ func (a *FloatHistogramAppender) recode(
 	hc := NewFloatHistogramChunk()
 	app, err := hc.Appender()
 	if err != nil {
-		panic(err) // This should never happen for an empty float histogram chunk.
+		panic(err)
 	}
-	happ := app.(*FloatHistogramAppender)
 	numPositiveBuckets, numNegativeBuckets := countSpans(positiveSpans), countSpans(negativeSpans)
 
 	for it.Next() == ValFloatHistogram {
@@ -541,16 +546,16 @@ func (a *FloatHistogramAppender) recode(
 		if len(negativeInserts) > 0 {
 			hOld.NegativeBuckets = insert(hOld.NegativeBuckets, negativeBuckets, negativeInserts, false)
 		}
-		happ.appendFloatHistogram(tOld, hOld)
+		app.AppendFloatHistogram(tOld, hOld)
 	}
 
-	happ.setCounterResetHeader(CounterResetHeader(byts[2] & CounterResetHeaderMask))
+	hc.SetCounterResetHeader(CounterResetHeader(byts[2] & 0b11000000))
 	return hc, app
 }
 
-// recodeHistogram converts the current histogram (in-place) to accommodate an expansion of the set of
+// RecodeHistogramm converts the current histogram (in-place) to accommodate an expansion of the set of
 // (positive and/or negative) buckets used.
-func (a *FloatHistogramAppender) recodeHistogram(
+func (a *FloatHistogramAppender) RecodeHistogramm(
 	fh *histogram.FloatHistogram,
 	pBackwardInter, nBackwardInter []Insert,
 ) {
@@ -564,111 +569,6 @@ func (a *FloatHistogramAppender) recodeHistogram(
 	}
 }
 
-func (a *FloatHistogramAppender) AppendHistogram(*HistogramAppender, int64, *histogram.Histogram, bool) (Chunk, bool, Appender, error) {
-	panic("appended a histogram sample to a float histogram chunk")
-}
-
-func (a *FloatHistogramAppender) AppendFloatHistogram(prev *FloatHistogramAppender, t int64, h *histogram.FloatHistogram, appendOnly bool) (Chunk, bool, Appender, error) {
-	if a.NumSamples() == 0 {
-		a.appendFloatHistogram(t, h)
-		if h.CounterResetHint == histogram.GaugeType {
-			a.setCounterResetHeader(GaugeType)
-			return nil, false, a, nil
-		}
-
-		if prev != nil && h.CounterResetHint != histogram.CounterReset {
-			// This is a new chunk, but continued from a previous one. We need to calculate the reset header unless already set.
-			_, _, _, counterReset := prev.appendable(h)
-			if counterReset {
-				a.setCounterResetHeader(CounterReset)
-			} else {
-				a.setCounterResetHeader(NotCounterReset)
-			}
-		} else {
-			// Honor the explicit counter reset hint.
-			a.setCounterResetHeader(CounterResetHeader(h.CounterResetHint))
-		}
-		return nil, false, a, nil
-	}
-
-	// Adding counter-like histogram.
-	if h.CounterResetHint != histogram.GaugeType {
-		pForwardInserts, nForwardInserts, okToAppend, counterReset := a.appendable(h)
-		if !okToAppend || counterReset {
-			if appendOnly {
-				if !okToAppend {
-					return nil, false, a, fmt.Errorf("float histogram schema change")
-				}
-				return nil, false, a, fmt.Errorf("float histogram counter reset")
-			}
-			newChunk := NewFloatHistogramChunk()
-			app, err := newChunk.Appender()
-			if err != nil {
-				panic(err) // This should never happen for an empty float histogram chunk.
-			}
-			happ := app.(*FloatHistogramAppender)
-			if counterReset {
-				happ.setCounterResetHeader(CounterReset)
-			}
-			happ.appendFloatHistogram(t, h)
-			return newChunk, false, app, nil
-		}
-		if len(pForwardInserts) > 0 || len(nForwardInserts) > 0 {
-			if appendOnly {
-				return nil, false, a, fmt.Errorf("float histogram layout change with %d positive and %d negative forwards inserts", len(pForwardInserts), len(nForwardInserts))
-			}
-			chk, app := a.recode(
-				pForwardInserts, nForwardInserts,
-				h.PositiveSpans, h.NegativeSpans,
-			)
-			app.(*FloatHistogramAppender).appendFloatHistogram(t, h)
-			return chk, true, app, nil
-		}
-		a.appendFloatHistogram(t, h)
-		return nil, false, a, nil
-	}
-	// Adding gauge histogram.
-	pForwardInserts, nForwardInserts, pBackwardInserts, nBackwardInserts, pMergedSpans, nMergedSpans, okToAppend := a.appendableGauge(h)
-	if !okToAppend {
-		if appendOnly {
-			return nil, false, a, fmt.Errorf("float gauge histogram schema change")
-		}
-		newChunk := NewFloatHistogramChunk()
-		app, err := newChunk.Appender()
-		if err != nil {
-			panic(err) // This should never happen for an empty float histogram chunk.
-		}
-		happ := app.(*FloatHistogramAppender)
-		happ.setCounterResetHeader(GaugeType)
-		happ.appendFloatHistogram(t, h)
-		return newChunk, false, app, nil
-	}
-
-	if len(pBackwardInserts)+len(nBackwardInserts) > 0 {
-		if appendOnly {
-			return nil, false, a, fmt.Errorf("float gauge histogram layout change with %d positive and %d negative backwards inserts", len(pBackwardInserts), len(nBackwardInserts))
-		}
-		h.PositiveSpans = pMergedSpans
-		h.NegativeSpans = nMergedSpans
-		a.recodeHistogram(h, pBackwardInserts, nBackwardInserts)
-	}
-
-	if len(pForwardInserts) > 0 || len(nForwardInserts) > 0 {
-		if appendOnly {
-			return nil, false, a, fmt.Errorf("float gauge histogram layout change with %d positive and %d negative forwards inserts", len(pForwardInserts), len(nForwardInserts))
-		}
-		chk, app := a.recode(
-			pForwardInserts, nForwardInserts,
-			h.PositiveSpans, h.NegativeSpans,
-		)
-		app.(*FloatHistogramAppender).appendFloatHistogram(t, h)
-		return chk, true, app, nil
-	}
-
-	a.appendFloatHistogram(t, h)
-	return nil, false, a, nil
-}
-
 type floatHistogramIterator struct {
 	br       bstreamReader
 	numTotal uint16
@@ -757,7 +657,7 @@ func (it *floatHistogramIterator) Reset(b []byte) {
 	it.numTotal = binary.BigEndian.Uint16(b)
 	it.numRead = 0
 
-	it.counterResetHeader = CounterResetHeader(b[2] & CounterResetHeaderMask)
+	it.counterResetHeader = CounterResetHeader(b[2] & 0b11000000)
 
 	it.t, it.tDelta = 0, 0
 	it.cnt, it.zCnt, it.sum = xorValue{}, xorValue{}, xorValue{}
diff --git a/tsdb/chunkenc/histogram.go b/tsdb/chunkenc/histogram.go
index 4b021d426..2350b2af2 100644
--- a/tsdb/chunkenc/histogram.go
+++ b/tsdb/chunkenc/histogram.go
@@ -15,7 +15,6 @@ package chunkenc
 
 import (
 	"encoding/binary"
-	"fmt"
 	"math"
 
 	"github.com/prometheus/prometheus/model/histogram"
@@ -89,13 +88,26 @@ const (
 	UnknownCounterReset CounterResetHeader = 0b00000000
 )
 
-// CounterResetHeaderMask is the mask to get the counter reset header bits.
-const CounterResetHeaderMask byte = 0b11000000
+// setCounterResetHeader sets the counter reset header of the chunk
+// The third byte of the chunk is the counter reset header.
+func setCounterResetHeader(h CounterResetHeader, bytes []byte) {
+	switch h {
+	case CounterReset, NotCounterReset, GaugeType, UnknownCounterReset:
+		bytes[2] = (bytes[2] & 0b00111111) | byte(h)
+	default:
+		panic("invalid CounterResetHeader type")
+	}
+}
+
+// SetCounterResetHeader sets the counter reset header.
+func (c *HistogramChunk) SetCounterResetHeader(h CounterResetHeader) {
+	setCounterResetHeader(h, c.Bytes())
+}
 
 // GetCounterResetHeader returns the info about the first 2 bits of the chunk
 // header.
 func (c *HistogramChunk) GetCounterResetHeader() CounterResetHeader {
-	return CounterResetHeader(c.Bytes()[2] & CounterResetHeaderMask)
+	return CounterResetHeader(c.Bytes()[2] & 0b11000000)
 }
 
 // Compact implements the Chunk interface.
@@ -165,7 +177,7 @@ func newHistogramIterator(b []byte) *histogramIterator {
 	// The first 3 bytes contain chunk headers.
 	// We skip that for actual samples.
 	_, _ = it.br.readBits(24)
-	it.counterResetHeader = CounterResetHeader(b[2] & CounterResetHeaderMask)
+	it.counterResetHeader = CounterResetHeader(b[2] & 0b11000000)
 	return it
 }
 
@@ -212,11 +224,7 @@ type HistogramAppender struct {
 }
 
 func (a *HistogramAppender) GetCounterResetHeader() CounterResetHeader {
-	return CounterResetHeader(a.b.bytes()[2] & CounterResetHeaderMask)
-}
-
-func (a *HistogramAppender) setCounterResetHeader(cr CounterResetHeader) {
-	a.b.bytes()[2] = (a.b.bytes()[2] & (^CounterResetHeaderMask)) | (byte(cr) & CounterResetHeaderMask)
+	return CounterResetHeader(a.b.bytes()[2] & 0b11000000)
 }
 
 func (a *HistogramAppender) NumSamples() int {
@@ -229,7 +237,13 @@ func (a *HistogramAppender) Append(int64, float64) {
 	panic("appended a float sample to a histogram chunk")
 }
 
-// appendable returns whether the chunk can be appended to, and if so whether
+// AppendFloatHistogram implements Appender. This implementation panics because float
+// histogram samples must never be appended to a histogram chunk.
+func (a *HistogramAppender) AppendFloatHistogram(int64, *histogram.FloatHistogram) {
+	panic("appended a float histogram to a histogram chunk")
+}
+
+// Appendable returns whether the chunk can be appended to, and if so whether
 // any recoding needs to happen using the provided inserts (in case of any new
 // buckets, positive or negative range, respectively).  If the sample is a gauge
 // histogram, AppendableGauge must be used instead.
@@ -246,7 +260,7 @@ func (a *HistogramAppender) Append(int64, float64) {
 // The method returns an additional boolean set to true if it is not appendable
 // because of a counter reset. If the given sample is stale, it is always ok to
 // append. If counterReset is true, okToAppend is always false.
-func (a *HistogramAppender) appendable(h *histogram.Histogram) (
+func (a *HistogramAppender) Appendable(h *histogram.Histogram) (
 	positiveInserts, negativeInserts []Insert,
 	okToAppend, counterReset bool,
 ) {
@@ -302,7 +316,7 @@ func (a *HistogramAppender) appendable(h *histogram.Histogram) (
 	return
 }
 
-// appendableGauge returns whether the chunk can be appended to, and if so
+// AppendableGauge returns whether the chunk can be appended to, and if so
 // whether:
 //  1. Any recoding needs to happen to the chunk using the provided inserts
 //     (in case of any new buckets, positive or negative range, respectively).
@@ -316,7 +330,7 @@ func (a *HistogramAppender) appendable(h *histogram.Histogram) (
 //   - The schema has changed.
 //   - The threshold for the zero bucket has changed.
 //   - The last sample in the chunk was stale while the current sample is not stale.
-func (a *HistogramAppender) appendableGauge(h *histogram.Histogram) (
+func (a *HistogramAppender) AppendableGauge(h *histogram.Histogram) (
 	positiveInserts, negativeInserts []Insert,
 	backwardPositiveInserts, backwardNegativeInserts []Insert,
 	positiveSpans, negativeSpans []histogram.Span,
@@ -413,11 +427,11 @@ func counterResetInAnyBucket(oldBuckets, newBuckets []int64, oldSpans, newSpans
 	return false
 }
 
-// appendHistogram appends a histogram to the chunk. The caller must ensure that
+// AppendHistogram appends a histogram to the chunk. The caller must ensure that
 // the histogram is properly structured, e.g. the number of buckets used
 // corresponds to the number conveyed by the span structures. First call
 // Appendable() and act accordingly!
-func (a *HistogramAppender) appendHistogram(t int64, h *histogram.Histogram) {
+func (a *HistogramAppender) AppendHistogram(t int64, h *histogram.Histogram) {
 	var tDelta, cntDelta, zCntDelta int64
 	num := binary.BigEndian.Uint16(a.b.bytes())
 
@@ -526,12 +540,12 @@ func (a *HistogramAppender) appendHistogram(t int64, h *histogram.Histogram) {
 	a.sum = h.Sum
 }
 
-// recode converts the current chunk to accommodate an expansion of the set of
+// Recode converts the current chunk to accommodate an expansion of the set of
 // (positive and/or negative) buckets used, according to the provided inserts,
 // resulting in the honoring of the provided new positive and negative spans. To
 // continue appending, use the returned Appender rather than the receiver of
 // this method.
-func (a *HistogramAppender) recode(
+func (a *HistogramAppender) Recode(
 	positiveInserts, negativeInserts []Insert,
 	positiveSpans, negativeSpans []histogram.Span,
 ) (Chunk, Appender) {
@@ -544,9 +558,8 @@ func (a *HistogramAppender) recode(
 	hc := NewHistogramChunk()
 	app, err := hc.Appender()
 	if err != nil {
-		panic(err) // This should never happen for an empty histogram chunk.
+		panic(err)
 	}
-	happ := app.(*HistogramAppender)
 	numPositiveBuckets, numNegativeBuckets := countSpans(positiveSpans), countSpans(negativeSpans)
 
 	for it.Next() == ValHistogram {
@@ -572,16 +585,16 @@ func (a *HistogramAppender) recode(
 		if len(negativeInserts) > 0 {
 			hOld.NegativeBuckets = insert(hOld.NegativeBuckets, negativeBuckets, negativeInserts, true)
 		}
-		happ.appendHistogram(tOld, hOld)
+		app.AppendHistogram(tOld, hOld)
 	}
 
-	happ.setCounterResetHeader(CounterResetHeader(byts[2] & CounterResetHeaderMask))
+	hc.SetCounterResetHeader(CounterResetHeader(byts[2] & 0b11000000))
 	return hc, app
 }
 
-// recodeHistogram converts the current histogram (in-place) to accommodate an
+// RecodeHistogram converts the current histogram (in-place) to accommodate an
 // expansion of the set of (positive and/or negative) buckets used.
-func (a *HistogramAppender) recodeHistogram(
+func (a *HistogramAppender) RecodeHistogram(
 	h *histogram.Histogram,
 	pBackwardInserts, nBackwardInserts []Insert,
 ) {
@@ -599,124 +612,6 @@ func (a *HistogramAppender) writeSumDelta(v float64) {
 	xorWrite(a.b, v, a.sum, &a.leading, &a.trailing)
 }
 
-func (a *HistogramAppender) AppendFloatHistogram(*FloatHistogramAppender, int64, *histogram.FloatHistogram, bool) (Chunk, bool, Appender, error) {
-	panic("appended a float histogram sample to a histogram chunk")
-}
-
-func (a *HistogramAppender) AppendHistogram(prev *HistogramAppender, t int64, h *histogram.Histogram, appendOnly bool) (Chunk, bool, Appender, error) {
-	if a.NumSamples() == 0 {
-		a.appendHistogram(t, h)
-		if h.CounterResetHint == histogram.GaugeType {
-			a.setCounterResetHeader(GaugeType)
-			return nil, false, a, nil
-		}
-
-		if prev != nil && h.CounterResetHint != histogram.CounterReset {
-			// This is a new chunk, but continued from a previous one. We need to calculate the reset header unless already set.
-			_, _, _, counterReset := prev.appendable(h)
-			if counterReset {
-				a.setCounterResetHeader(CounterReset)
-			} else {
-				a.setCounterResetHeader(NotCounterReset)
-			}
-		} else {
-			// Honor the explicit counter reset hint.
-			a.setCounterResetHeader(CounterResetHeader(h.CounterResetHint))
-		}
-		return nil, false, a, nil
-	}
-
-	// Adding counter-like histogram.
-	if h.CounterResetHint != histogram.GaugeType {
-		pForwardInserts, nForwardInserts, okToAppend, counterReset := a.appendable(h)
-		if !okToAppend || counterReset {
-			if appendOnly {
-				if !okToAppend {
-					return nil, false, a, fmt.Errorf("histogram schema change")
-				}
-				return nil, false, a, fmt.Errorf("histogram counter reset")
-			}
-			newChunk := NewHistogramChunk()
-			app, err := newChunk.Appender()
-			if err != nil {
-				panic(err) // This should never happen for an empty histogram chunk.
-			}
-			happ := app.(*HistogramAppender)
-			if counterReset {
-				happ.setCounterResetHeader(CounterReset)
-			}
-			happ.appendHistogram(t, h)
-			return newChunk, false, app, nil
-		}
-		if len(pForwardInserts) > 0 || len(nForwardInserts) > 0 {
-			if appendOnly {
-				return nil, false, a, fmt.Errorf("histogram layout change with %d positive and %d negative forwards inserts", len(pForwardInserts), len(nForwardInserts))
-			}
-			chk, app := a.recode(
-				pForwardInserts, nForwardInserts,
-				h.PositiveSpans, h.NegativeSpans,
-			)
-			app.(*HistogramAppender).appendHistogram(t, h)
-			return chk, true, app, nil
-		}
-		a.appendHistogram(t, h)
-		return nil, false, a, nil
-	}
-	// Adding gauge histogram.
-	pForwardInserts, nForwardInserts, pBackwardInserts, nBackwardInserts, pMergedSpans, nMergedSpans, okToAppend := a.appendableGauge(h)
-	if !okToAppend {
-		if appendOnly {
-			return nil, false, a, fmt.Errorf("gauge histogram schema change")
-		}
-		newChunk := NewHistogramChunk()
-		app, err := newChunk.Appender()
-		if err != nil {
-			panic(err) // This should never happen for an empty histogram chunk.
-		}
-		happ := app.(*HistogramAppender)
-		happ.setCounterResetHeader(GaugeType)
-		happ.appendHistogram(t, h)
-		return newChunk, false, app, nil
-	}
-
-	if len(pBackwardInserts)+len(nBackwardInserts) > 0 {
-		if appendOnly {
-			return nil, false, a, fmt.Errorf("gauge histogram layout change with %d positive and %d negative backwards inserts", len(pBackwardInserts), len(nBackwardInserts))
-		}
-		h.PositiveSpans = pMergedSpans
-		h.NegativeSpans = nMergedSpans
-		a.recodeHistogram(h, pBackwardInserts, nBackwardInserts)
-	}
-
-	if len(pForwardInserts) > 0 || len(nForwardInserts) > 0 {
-		if appendOnly {
-			return nil, false, a, fmt.Errorf("gauge histogram layout change with %d positive and %d negative forwards inserts", len(pForwardInserts), len(nForwardInserts))
-		}
-		chk, app := a.recode(
-			pForwardInserts, nForwardInserts,
-			h.PositiveSpans, h.NegativeSpans,
-		)
-		app.(*HistogramAppender).appendHistogram(t, h)
-		return chk, true, app, nil
-	}
-
-	a.appendHistogram(t, h)
-	return nil, false, a, nil
-}
-
-func CounterResetHintToHeader(hint histogram.CounterResetHint) CounterResetHeader {
-	switch hint {
-	case histogram.CounterReset:
-		return CounterReset
-	case histogram.NotCounterReset:
-		return NotCounterReset
-	case histogram.GaugeType:
-		return GaugeType
-	default:
-		return UnknownCounterReset
-	}
-}
-
 type histogramIterator struct {
 	br       bstreamReader
 	numTotal uint16
@@ -820,7 +715,7 @@ func (it *histogramIterator) Reset(b []byte) {
 	it.numTotal = binary.BigEndian.Uint16(b)
 	it.numRead = 0
 
-	it.counterResetHeader = CounterResetHeader(b[2] & CounterResetHeaderMask)
+	it.counterResetHeader = CounterResetHeader(b[2] & 0b11000000)
 
 	it.t, it.cnt, it.zCnt = 0, 0, 0
 	it.tDelta, it.cntDelta, it.zCntDelta = 0, 0, 0
diff --git a/tsdb/chunkenc/xor.go b/tsdb/chunkenc/xor.go
index 089a51a64..aa6b689a7 100644
--- a/tsdb/chunkenc/xor.go
+++ b/tsdb/chunkenc/xor.go
@@ -152,6 +152,14 @@ type xorAppender struct {
 	trailing uint8
 }
 
+func (a *xorAppender) AppendHistogram(int64, *histogram.Histogram) {
+	panic("appended a histogram to an xor chunk")
+}
+
+func (a *xorAppender) AppendFloatHistogram(int64, *histogram.FloatHistogram) {
+	panic("appended a float histogram to an xor chunk")
+}
+
 func (a *xorAppender) Append(t int64, v float64) {
 	var tDelta uint64
 	num := binary.BigEndian.Uint16(a.b.bytes())
@@ -220,14 +228,6 @@ func (a *xorAppender) writeVDelta(v float64) {
 	xorWrite(a.b, v, a.v, &a.leading, &a.trailing)
 }
 
-func (a *xorAppender) AppendHistogram(*HistogramAppender, int64, *histogram.Histogram, bool) (Chunk, bool, Appender, error) {
-	panic("appended a histogram sample to a float chunk")
-}
-
-func (a *xorAppender) AppendFloatHistogram(*FloatHistogramAppender, int64, *histogram.FloatHistogram, bool) (Chunk, bool, Appender, error) {
-	panic("appended a float histogram sample to a float chunk")
-}
-
 type xorIterator struct {
 	br       bstreamReader
 	numTotal uint16
diff --git a/tsdb/head_append.go b/tsdb/head_append.go
index cbbb60f03..3d828d066 100644
--- a/tsdb/head_append.go
+++ b/tsdb/head_append.go
@@ -1155,32 +1155,87 @@ func (s *memSeries) append(t int64, v float64, appendID uint64, o chunkOpts) (sa
 
 // appendHistogram adds the histogram.
 // It is unsafe to call this concurrently with s.iterator(...) without holding the series lock.
-// In case of recoding the existing chunk, a new chunk is allocated and the old chunk is dropped.
-// To keep the meaning of prometheus_tsdb_head_chunks and prometheus_tsdb_head_chunks_created_total
-// consistent, we return chunkCreated=false in this case.
 func (s *memSeries) appendHistogram(t int64, h *histogram.Histogram, appendID uint64, o chunkOpts) (sampleInOrder, chunkCreated bool) {
 	// Head controls the execution of recoding, so that we own the proper
-	// chunk reference afterwards and mmap used up chunks.
-
-	// Ignoring ok is ok, since we don't want to compare to the wrong previous appender anyway.
-	prevApp, _ := s.app.(*chunkenc.HistogramAppender)
-
+	// chunk reference afterwards. We check for Appendable from appender before
+	// appendPreprocessor because in case it ends up creating a new chunk,
+	// we need to know if there was also a counter reset or not to set the
+	// meta properly.
+	app, _ := s.app.(*chunkenc.HistogramAppender)
+	var (
+		pForwardInserts, nForwardInserts   []chunkenc.Insert
+		pBackwardInserts, nBackwardInserts []chunkenc.Insert
+		pMergedSpans, nMergedSpans         []histogram.Span
+		okToAppend, counterReset, gauge    bool
+	)
 	c, sampleInOrder, chunkCreated := s.appendPreprocessor(t, chunkenc.EncHistogram, o)
 	if !sampleInOrder {
 		return sampleInOrder, chunkCreated
 	}
-
-	var (
-		newChunk chunkenc.Chunk
-		recoded  bool
-	)
+	switch h.CounterResetHint {
+	case histogram.GaugeType:
+		gauge = true
+		if app != nil {
+			pForwardInserts, nForwardInserts,
+				pBackwardInserts, nBackwardInserts,
+				pMergedSpans, nMergedSpans,
+				okToAppend = app.AppendableGauge(h)
+		}
+	case histogram.CounterReset:
+		// The caller tells us this is a counter reset, even if it
+		// doesn't look like one.
+		counterReset = true
+	default:
+		if app != nil {
+			pForwardInserts, nForwardInserts, okToAppend, counterReset = app.Appendable(h)
+		}
+	}
 
 	if !chunkCreated {
-		// Ignore the previous appender if we continue the current chunk.
-		prevApp = nil
+		if len(pBackwardInserts)+len(nBackwardInserts) > 0 {
+			h.PositiveSpans = pMergedSpans
+			h.NegativeSpans = nMergedSpans
+			app.RecodeHistogram(h, pBackwardInserts, nBackwardInserts)
+		}
+		// We have 3 cases here
+		// - !okToAppend or counterReset -> We need to cut a new chunk.
+		// - okToAppend but we have inserts → Existing chunk needs
+		//   recoding before we can append our histogram.
+		// - okToAppend and no inserts → Chunk is ready to support our histogram.
+		switch {
+		case !okToAppend || counterReset:
+			c = s.cutNewHeadChunk(t, chunkenc.EncHistogram, o.chunkDiskMapper, o.chunkRange)
+			chunkCreated = true
+		case len(pForwardInserts) > 0 || len(nForwardInserts) > 0:
+			// New buckets have appeared. We need to recode all
+			// prior histogram samples within the chunk before we
+			// can process this one.
+			chunk, app := app.Recode(
+				pForwardInserts, nForwardInserts,
+				h.PositiveSpans, h.NegativeSpans,
+			)
+			c.chunk = chunk
+			s.app = app
+		}
 	}
 
-	newChunk, recoded, s.app, _ = s.app.AppendHistogram(prevApp, t, h, false) // false=request a new chunk if needed
+	if chunkCreated {
+		hc := s.headChunk.chunk.(*chunkenc.HistogramChunk)
+		header := chunkenc.UnknownCounterReset
+		switch {
+		case gauge:
+			header = chunkenc.GaugeType
+		case counterReset:
+			header = chunkenc.CounterReset
+		case okToAppend:
+			header = chunkenc.NotCounterReset
+		}
+		hc.SetCounterResetHeader(header)
+	}
+
+	s.app.AppendHistogram(t, h)
+
+	c.maxTime = t
 
 	s.lastHistogramValue = h
 	s.lastFloatHistogramValue = nil
@@ -1189,84 +1244,101 @@ func (s *memSeries) appendHistogram(t int64, h *histogram.Histogram, appendID ui
 		s.txs.add(appendID)
 	}
 
-	if newChunk == nil { // Sample was appended to existing chunk or is the first sample in a new chunk.
-		c.maxTime = t
-		return true, chunkCreated
-	}
-
-	if recoded { // The appender needed to recode the chunk.
-		c.maxTime = t
-		c.chunk = newChunk
-		return true, false
-	}
-
-	// This is a brand new chunk, switch out the head chunk (based on cutNewHeadChunk).
-	s.mmapCurrentHeadChunk(o.chunkDiskMapper)
-	s.headChunk = &memChunk{
-		chunk:   newChunk,
-		minTime: t,
-		maxTime: t,
-	}
-	s.nextAt = rangeForTimestamp(t, o.chunkRange)
-	return true, true
+	return true, chunkCreated
 }
 
 // appendFloatHistogram adds the float histogram.
 // It is unsafe to call this concurrently with s.iterator(...) without holding the series lock.
-// In case of recoding the existing chunk, a new chunk is allocated and the old chunk is dropped.
-// To keep the meaning of prometheus_tsdb_head_chunks and prometheus_tsdb_head_chunks_created_total
-// consistent, we return chunkCreated=false in this case.
 func (s *memSeries) appendFloatHistogram(t int64, fh *histogram.FloatHistogram, appendID uint64, o chunkOpts) (sampleInOrder, chunkCreated bool) {
 	// Head controls the execution of recoding, so that we own the proper
-	// chunk reference afterwards and mmap used up chunks.
-
-	// Ignoring ok is ok, since we don't want to compare to the wrong previous appender anyway.
-	prevApp, _ := s.app.(*chunkenc.FloatHistogramAppender)
-
+	// chunk reference afterwards.  We check for Appendable from appender before
+	// appendPreprocessor because in case it ends up creating a new chunk,
+	// we need to know if there was also a counter reset or not to set the
+	// meta properly.
+	app, _ := s.app.(*chunkenc.FloatHistogramAppender)
+	var (
+		pForwardInserts, nForwardInserts   []chunkenc.Insert
+		pBackwardInserts, nBackwardInserts []chunkenc.Insert
+		pMergedSpans, nMergedSpans         []histogram.Span
+		okToAppend, counterReset, gauge    bool
+	)
 	c, sampleInOrder, chunkCreated := s.appendPreprocessor(t, chunkenc.EncFloatHistogram, o)
 	if !sampleInOrder {
 		return sampleInOrder, chunkCreated
 	}
-
-	var (
-		newChunk chunkenc.Chunk
-		recoded  bool
-	)
+	switch fh.CounterResetHint {
+	case histogram.GaugeType:
+		gauge = true
+		if app != nil {
+			pForwardInserts, nForwardInserts,
+				pBackwardInserts, nBackwardInserts,
+				pMergedSpans, nMergedSpans,
+				okToAppend = app.AppendableGauge(fh)
+		}
+	case histogram.CounterReset:
+		// The caller tells us this is a counter reset, even if it
+		// doesn't look like one.
+		counterReset = true
+	default:
+		if app != nil {
+			pForwardInserts, nForwardInserts, okToAppend, counterReset = app.Appendable(fh)
+		}
+	}
 
 	if !chunkCreated {
-		// Ignore the previous appender if we continue the current chunk.
-		prevApp = nil
+		if len(pBackwardInserts)+len(nBackwardInserts) > 0 {
+			fh.PositiveSpans = pMergedSpans
+			fh.NegativeSpans = nMergedSpans
+			app.RecodeHistogramm(fh, pBackwardInserts, nBackwardInserts)
+		}
+		// We have 3 cases here
+		// - !okToAppend or counterReset -> We need to cut a new chunk.
+		// - okToAppend but we have inserts → Existing chunk needs
+		//   recoding before we can append our histogram.
+		// - okToAppend and no inserts → Chunk is ready to support our histogram.
+		switch {
+		case !okToAppend || counterReset:
+			c = s.cutNewHeadChunk(t, chunkenc.EncFloatHistogram, o.chunkDiskMapper, o.chunkRange)
+			chunkCreated = true
+		case len(pForwardInserts) > 0 || len(nForwardInserts) > 0:
+			// New buckets have appeared. We need to recode all
+			// prior histogram samples within the chunk before we
+			// can process this one.
+			chunk, app := app.Recode(
+				pForwardInserts, nForwardInserts,
+				fh.PositiveSpans, fh.NegativeSpans,
+			)
+			c.chunk = chunk
+			s.app = app
+		}
+	}
+
+	if chunkCreated {
+		hc := s.headChunk.chunk.(*chunkenc.FloatHistogramChunk)
+		header := chunkenc.UnknownCounterReset
+		switch {
+		case gauge:
+			header = chunkenc.GaugeType
+		case counterReset:
+			header = chunkenc.CounterReset
+		case okToAppend:
+			header = chunkenc.NotCounterReset
+		}
+		hc.SetCounterResetHeader(header)
 	}
 
-	newChunk, recoded, s.app, _ = s.app.AppendFloatHistogram(prevApp, t, fh, false) // False means request a new chunk if needed.
+	s.app.AppendFloatHistogram(t, fh)
+
+	c.maxTime = t
 
-	s.lastHistogramValue = nil
 	s.lastFloatHistogramValue = fh
+	s.lastHistogramValue = nil
 
 	if appendID > 0 {
 		s.txs.add(appendID)
 	}
 
-	if newChunk == nil { // Sample was appended to existing chunk or is the first sample in a new chunk.
-		c.maxTime = t
-		return true, chunkCreated
-	}
-
-	if recoded { // The appender needed to recode the chunk.
-		c.maxTime = t
-		c.chunk = newChunk
-		return true, false
-	}
-
-	// This is a brand new chunk, switch out the head chunk (based on cutNewHeadChunk).
-	s.mmapCurrentHeadChunk(o.chunkDiskMapper)
-	s.headChunk = &memChunk{
-		chunk:   newChunk,
-		minTime: t,
-		maxTime: t,
-	}
-	s.nextAt = rangeForTimestamp(t, o.chunkRange)
-	return true, true
+	return true, chunkCreated
 }
 
 // appendPreprocessor takes care of cutting new chunks and m-mapping old chunks.
diff --git a/tsdb/querier.go b/tsdb/querier.go
index 965707547..98ee34e50 100644
--- a/tsdb/querier.go
+++ b/tsdb/querier.go
@@ -821,17 +821,74 @@ func (p *populateWithDelChunkSeriesIterator) Next() bool {
 			break
 		}
 
-		for vt := valueType; vt != chunkenc.ValNone; vt = p.currDelIter.Next() {
+		switch hc := p.currChkMeta.Chunk.(type) {
+		case *chunkenc.HistogramChunk:
+			newChunk.(*chunkenc.HistogramChunk).SetCounterResetHeader(hc.GetCounterResetHeader())
+		case *safeHeadChunk:
+			if unwrapped, ok := hc.Chunk.(*chunkenc.HistogramChunk); ok {
+				newChunk.(*chunkenc.HistogramChunk).SetCounterResetHeader(unwrapped.GetCounterResetHeader())
+			} else {
+				err = fmt.Errorf("internal error, could not unwrap safeHeadChunk to histogram chunk: %T", hc.Chunk)
+			}
+		default:
+			err = fmt.Errorf("internal error, unknown chunk type %T when expecting histogram", p.currChkMeta.Chunk)
+		}
+		if err != nil {
+			break
+		}
+
+		var h *histogram.Histogram
+		t, h = p.currDelIter.AtHistogram()
+		p.curr.MinTime = t
+
+		// Detect missing gauge reset hint.
+		if h.CounterResetHint == histogram.GaugeType && newChunk.(*chunkenc.HistogramChunk).GetCounterResetHeader() != chunkenc.GaugeType {
+			err = fmt.Errorf("found gauge histogram in non gauge chunk")
+			break
+		}
+
+		app.AppendHistogram(t, h)
+
+		for vt := p.currDelIter.Next(); vt != chunkenc.ValNone; vt = p.currDelIter.Next() {
 			if vt != chunkenc.ValHistogram {
 				err = fmt.Errorf("found value type %v in histogram chunk", vt)
 				break
 			}
-			var h *histogram.Histogram
 			t, h = p.currDelIter.AtHistogram()
-			_, _, app, err = app.AppendHistogram(nil, t, h, true)
-			if err != nil {
-				break
+
+			// Defend against corrupted chunks.
+			if h.CounterResetHint == histogram.GaugeType {
+				pI, nI, bpI, bnI, _, _, okToAppend := app.(*chunkenc.HistogramAppender).AppendableGauge(h)
+				if !okToAppend {
+					err = errors.New("unable to append histogram due to unexpected schema change")
+					break
+				}
+				if len(pI)+len(nI)+len(bpI)+len(bnI) > 0 {
+					err = fmt.Errorf(
+						"bucket layout has changed unexpectedly: forward %d positive, %d negative, backward %d positive %d negative bucket interjections required",
+						len(pI), len(nI), len(bpI), len(bnI),
+					)
+					break
+				}
+			} else {
+				pI, nI, okToAppend, counterReset := app.(*chunkenc.HistogramAppender).Appendable(h)
+				if len(pI)+len(nI) > 0 {
+					err = fmt.Errorf(
+						"bucket layout has changed unexpectedly: %d positive and %d negative bucket interjections required",
+						len(pI), len(nI),
+					)
+					break
+				}
+				if counterReset {
+					err = errors.New("detected unexpected counter reset in histogram")
+					break
+				}
+				if !okToAppend {
+					err = errors.New("unable to append histogram due to unexpected schema change")
+					break
+				}
 			}
+			app.AppendHistogram(t, h)
 		}
 	case chunkenc.ValFloat:
 		newChunk = chunkenc.NewXORChunk()
@@ -856,17 +913,75 @@ func (p *populateWithDelChunkSeriesIterator) Next() bool {
 			break
 		}
 
-		for vt := valueType; vt != chunkenc.ValNone; vt = p.currDelIter.Next() {
+		switch hc := p.currChkMeta.Chunk.(type) {
+		case *chunkenc.FloatHistogramChunk:
+			newChunk.(*chunkenc.FloatHistogramChunk).SetCounterResetHeader(hc.GetCounterResetHeader())
+		case *safeHeadChunk:
+			if unwrapped, ok := hc.Chunk.(*chunkenc.FloatHistogramChunk); ok {
+				newChunk.(*chunkenc.FloatHistogramChunk).SetCounterResetHeader(unwrapped.GetCounterResetHeader())
+			} else {
+				err = fmt.Errorf("internal error, could not unwrap safeHeadChunk to float histogram chunk: %T", hc.Chunk)
+			}
+		default:
+			err = fmt.Errorf("internal error, unknown chunk type %T when expecting float histogram", p.currChkMeta.Chunk)
+		}
+		if err != nil {
+			break
+		}
+
+		var h *histogram.FloatHistogram
+		t, h = p.currDelIter.AtFloatHistogram()
+		p.curr.MinTime = t
+
+		// Detect missing gauge reset hint.
+		if h.CounterResetHint == histogram.GaugeType && newChunk.(*chunkenc.FloatHistogramChunk).GetCounterResetHeader() != chunkenc.GaugeType {
+			err = fmt.Errorf("found float gauge histogram in non gauge chunk")
+			break
+		}
+
+		app.AppendFloatHistogram(t, h)
+
+		for vt := p.currDelIter.Next(); vt != chunkenc.ValNone; vt = p.currDelIter.Next() {
 			if vt != chunkenc.ValFloatHistogram {
 				err = fmt.Errorf("found value type %v in histogram chunk", vt)
 				break
 			}
-			var h *histogram.FloatHistogram
 			t, h = p.currDelIter.AtFloatHistogram()
-			_, _, app, err = app.AppendFloatHistogram(nil, t, h, true)
-			if err != nil {
-				break
+
+			// Defend against corrupted chunks.
+			if h.CounterResetHint == histogram.GaugeType {
+				pI, nI, bpI, bnI, _, _, okToAppend := app.(*chunkenc.FloatHistogramAppender).AppendableGauge(h)
+				if !okToAppend {
+					err = errors.New("unable to append histogram due to unexpected schema change")
+					break
+				}
+				if len(pI)+len(nI)+len(bpI)+len(bnI) > 0 {
+					err = fmt.Errorf(
+						"bucket layout has changed unexpectedly: forward %d positive, %d negative, backward %d positive %d negative bucket interjections required",
+						len(pI), len(nI), len(bpI), len(bnI),
+					)
+					break
+				}
+			} else {
+				pI, nI, okToAppend, counterReset := app.(*chunkenc.FloatHistogramAppender).Appendable(h)
+				if len(pI)+len(nI) > 0 {
+					err = fmt.Errorf(
+						"bucket layout has changed unexpectedly: %d positive and %d negative bucket interjections required",
+						len(pI), len(nI),
+					)
+					break
+				}
+				if counterReset {
+					err = errors.New("detected unexpected counter reset in histogram")
+					break
+				}
+				if !okToAppend {
+					err = errors.New("unable to append histogram due to unexpected schema change")
+					break
+				}
 			}
+
+			app.AppendFloatHistogram(t, h)
 		}
 	default:
 		err = fmt.Errorf("populateWithDelChunkSeriesIterator: value type %v unsupported", valueType)
diff --git a/tsdb/tsdbutil/chunks.go b/tsdb/tsdbutil/chunks.go
index c9d796f9b..6e57016fe 100644
--- a/tsdb/tsdbutil/chunks.go
+++ b/tsdb/tsdbutil/chunks.go
@@ -40,13 +40,12 @@ func (s SampleSlice) Get(i int) Sample { return s[i] }
 func (s SampleSlice) Len() int         { return len(s) }
 
 // ChunkFromSamples requires all samples to have the same type.
-func ChunkFromSamples(s []Sample) (chunks.Meta, error) {
+func ChunkFromSamples(s []Sample) chunks.Meta {
 	return ChunkFromSamplesGeneric(SampleSlice(s))
 }
 
 // ChunkFromSamplesGeneric requires all samples to have the same type.
-func ChunkFromSamplesGeneric(s Samples) (chunks.Meta, error) {
-	emptyChunk := chunks.Meta{Chunk: chunkenc.NewXORChunk()}
+func ChunkFromSamplesGeneric(s Samples) chunks.Meta {
 	mint, maxt := int64(0), int64(0)
 
 	if s.Len() > 0 {
@@ -54,37 +53,36 @@ func ChunkFromSamplesGeneric(s Samples) (chunks.Meta, error) {
 	}
 
 	if s.Len() == 0 {
-		return emptyChunk, nil
+		return chunks.Meta{
+			Chunk: chunkenc.NewXORChunk(),
+		}
 	}
 
 	sampleType := s.Get(0).Type()
 	c, err := chunkenc.NewEmptyChunk(sampleType.ChunkEncoding())
 	if err != nil {
-		return chunks.Meta{}, err
+		panic(err) // TODO(codesome): dont panic.
 	}
 
 	ca, _ := c.Appender()
-	var newChunk chunkenc.Chunk
 
 	for i := 0; i < s.Len(); i++ {
 		switch sampleType {
 		case chunkenc.ValFloat:
 			ca.Append(s.Get(i).T(), s.Get(i).F())
 		case chunkenc.ValHistogram:
-			newChunk, _, ca, err = ca.AppendHistogram(nil, s.Get(i).T(), s.Get(i).H(), false)
-			if err != nil {
-				return emptyChunk, err
-			}
-			if newChunk != nil {
-				return emptyChunk, fmt.Errorf("did not expect to start a second chunk")
+			h := s.Get(i).H()
+			ca.AppendHistogram(s.Get(i).T(), h)
+			if i == 0 && h.CounterResetHint == histogram.GaugeType {
+				hc := c.(*chunkenc.HistogramChunk)
+				hc.SetCounterResetHeader(chunkenc.GaugeType)
 			}
 		case chunkenc.ValFloatHistogram:
-			newChunk, _, ca, err = ca.AppendFloatHistogram(nil, s.Get(i).T(), s.Get(i).FH(), false)
-			if err != nil {
-				return emptyChunk, err
-			}
-			if newChunk != nil {
-				return emptyChunk, fmt.Errorf("did not expect to start a second chunk")
+			fh := s.Get(i).FH()
+			ca.AppendFloatHistogram(s.Get(i).T(), fh)
+			if i == 0 && fh.CounterResetHint == histogram.GaugeType {
+				hc := c.(*chunkenc.FloatHistogramChunk)
+				hc.SetCounterResetHeader(chunkenc.GaugeType)
 			}
 		default:
 			panic(fmt.Sprintf("unknown sample type %s", sampleType.String()))
@@ -94,7 +92,7 @@ func ChunkFromSamplesGeneric(s Samples) (chunks.Meta, error) {
 		MinTime: mint,
 		MaxTime: maxt,
 		Chunk:   c,
-	}, nil
+	}
 }
 
 type sample struct {
@@ -132,7 +130,7 @@ func (s sample) Type() chunkenc.ValueType {
 }
 
 // PopulatedChunk creates a chunk populated with samples every second starting at minTime
-func PopulatedChunk(numSamples int, minTime int64) (chunks.Meta, error) {
+func PopulatedChunk(numSamples int, minTime int64) chunks.Meta {
 	samples := make([]Sample, numSamples)
 	for i := 0; i < numSamples; i++ {
 		samples[i] = sample{t: minTime + int64(i*1000), f: 1.0}
diff --git a/web/api/v1/api.go b/web/api/v1/api.go
index 99589ac46..73892aaf6 100644
--- a/web/api/v1/api.go
+++ b/web/api/v1/api.go
@@ -217,7 +217,6 @@ type API struct {
 
 	remoteWriteHandler http.Handler
 	remoteReadHandler  http.Handler
-	otlpWriteHandler   http.Handler
 
 	codecs []Codec
 }
@@ -250,8 +249,6 @@ func NewAPI(
 	gatherer prometheus.Gatherer,
 	registerer prometheus.Registerer,
 	statsRenderer StatsRenderer,
-	rwEnabled bool,
-	otlpEnabled bool,
 ) *API {
 	a := &API{
 		QueryEngine:       qe,
@@ -288,16 +285,9 @@ func NewAPI(
 		a.statsRenderer = statsRenderer
 	}
 
-	if ap == nil && (rwEnabled || otlpEnabled) {
-		panic("remote write or otlp write enabled, but no appender passed in.")
-	}
-
-	if rwEnabled {
+	if ap != nil {
 		a.remoteWriteHandler = remote.NewWriteHandler(logger, registerer, ap)
 	}
-	if otlpEnabled {
-		a.otlpWriteHandler = remote.NewOTLPWriteHandler(logger, ap)
-	}
 
 	return a
 }
@@ -390,7 +380,6 @@ func (api *API) Register(r *route.Router) {
 	r.Get("/status/walreplay", api.serveWALReplayStatus)
 	r.Post("/read", api.ready(api.remoteRead))
 	r.Post("/write", api.ready(api.remoteWrite))
-	r.Post("/otlp/v1/metrics", api.ready(api.otlpWrite))
 
 	r.Get("/alerts", wrapAgent(api.alerts))
 	r.Get("/rules", wrapAgent(api.rules))
@@ -1592,14 +1581,6 @@ func (api *API) remoteWrite(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func (api *API) otlpWrite(w http.ResponseWriter, r *http.Request) {
-	if api.otlpWriteHandler != nil {
-		api.otlpWriteHandler.ServeHTTP(w, r)
-	} else {
-		http.Error(w, "otlp write receiver needs to be enabled with --enable-feature=otlp-write-receiver", http.StatusNotFound)
-	}
-}
-
 func (api *API) deleteSeries(r *http.Request) apiFuncResult {
 	if !api.enableAdmin {
 		return apiFuncResult{nil, &apiError{errorUnavailable, errors.New("admin APIs disabled")}, nil, nil}
diff --git a/web/ui/module/codemirror-promql/package.json b/web/ui/module/codemirror-promql/package.json
index c7fafb133..8a9dbcb08 100644
--- a/web/ui/module/codemirror-promql/package.json
+++ b/web/ui/module/codemirror-promql/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@prometheus-io/codemirror-promql",
-  "version": "0.45.0",
+  "version": "0.46.0",
   "description": "a CodeMirror mode for the PromQL language",
   "types": "dist/esm/index.d.ts",
   "module": "dist/esm/index.js",
@@ -29,7 +29,7 @@
   },
   "homepage": "https://github.com/prometheus/prometheus/blob/main/web/ui/module/codemirror-promql/README.md",
   "dependencies": {
-    "@prometheus-io/lezer-promql": "0.45.0",
+    "@prometheus-io/lezer-promql": "0.46.0",
     "lru-cache": "^6.0.0"
   },
   "devDependencies": {
diff --git a/web/ui/module/lezer-promql/package.json b/web/ui/module/lezer-promql/package.json
index 847a9610e..6e4f599a3 100644
--- a/web/ui/module/lezer-promql/package.json
+++ b/web/ui/module/lezer-promql/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@prometheus-io/lezer-promql",
-  "version": "0.45.0",
+  "version": "0.46.0",
   "description": "lezer-based PromQL grammar",
   "main": "dist/index.cjs",
   "type": "module",
diff --git a/web/ui/package-lock.json b/web/ui/package-lock.json
index ac365d0d0..f6593bee9 100644
--- a/web/ui/package-lock.json
+++ b/web/ui/package-lock.json
@@ -28,10 +28,10 @@
     },
     "module/codemirror-promql": {
       "name": "@prometheus-io/codemirror-promql",
-      "version": "0.45.0",
+      "version": "0.46.0",
       "license": "Apache-2.0",
       "dependencies": {
-        "@prometheus-io/lezer-promql": "0.45.0",
+        "@prometheus-io/lezer-promql": "0.46.0",
         "lru-cache": "^6.0.0"
       },
       "devDependencies": {
@@ -61,7 +61,7 @@
     },
     "module/lezer-promql": {
       "name": "@prometheus-io/lezer-promql",
-      "version": "0.45.0",
+      "version": "0.46.0",
       "license": "Apache-2.0",
       "devDependencies": {
         "@lezer/generator": "^1.2.3",
@@ -20765,7 +20765,7 @@
     },
     "react-app": {
       "name": "@prometheus-io/app",
-      "version": "0.45.0",
+      "version": "0.46.0",
       "dependencies": {
         "@codemirror/autocomplete": "^6.7.1",
         "@codemirror/commands": "^6.2.4",
@@ -20783,7 +20783,7 @@
         "@lezer/lr": "^1.3.6",
         "@nexucis/fuzzy": "^0.4.1",
         "@nexucis/kvsearch": "^0.8.1",
-        "@prometheus-io/codemirror-promql": "0.45.0",
+        "@prometheus-io/codemirror-promql": "0.46.0",
         "bootstrap": "^4.6.2",
         "css.escape": "^1.5.1",
         "downshift": "^7.6.0",
@@ -23423,7 +23423,7 @@
         "@lezer/lr": "^1.3.6",
         "@nexucis/fuzzy": "^0.4.1",
         "@nexucis/kvsearch": "^0.8.1",
-        "@prometheus-io/codemirror-promql": "0.45.0",
+        "@prometheus-io/codemirror-promql": "0.46.0",
         "@testing-library/react-hooks": "^7.0.2",
         "@types/enzyme": "^3.10.13",
         "@types/flot": "0.0.32",
@@ -23487,7 +23487,7 @@
         "@lezer/common": "^1.0.3",
         "@lezer/highlight": "^1.1.6",
         "@lezer/lr": "^1.3.6",
-        "@prometheus-io/lezer-promql": "0.45.0",
+        "@prometheus-io/lezer-promql": "0.46.0",
         "@types/lru-cache": "^5.1.1",
         "isomorphic-fetch": "^3.0.0",
         "lru-cache": "^6.0.0",
diff --git a/web/ui/react-app/package.json b/web/ui/react-app/package.json
index 81eba150a..969de4279 100644
--- a/web/ui/react-app/package.json
+++ b/web/ui/react-app/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@prometheus-io/app",
-  "version": "0.45.0",
+  "version": "0.46.0",
   "private": true,
   "dependencies": {
     "@codemirror/autocomplete": "^6.7.1",
@@ -19,7 +19,7 @@
     "@lezer/common": "^1.0.3",
     "@nexucis/fuzzy": "^0.4.1",
     "@nexucis/kvsearch": "^0.8.1",
-    "@prometheus-io/codemirror-promql": "0.45.0",
+    "@prometheus-io/codemirror-promql": "0.46.0",
     "bootstrap": "^4.6.2",
     "css.escape": "^1.5.1",
     "downshift": "^7.6.0",
diff --git a/web/web.go b/web/web.go
index 7022db64e..6a3eab327 100644
--- a/web/web.go
+++ b/web/web.go
@@ -259,7 +259,6 @@ type Options struct {
 	RemoteReadConcurrencyLimit int
 	RemoteReadBytesInFrame     int
 	EnableRemoteWriteReceiver  bool
-	EnableOTLPWriteReceiver    bool
 	IsAgent                    bool
 	AppName                    string
 
@@ -318,7 +317,7 @@ func New(logger log.Logger, o *Options) *Handler {
 	FactoryRr := func(_ context.Context) api_v1.RulesRetriever { return h.ruleManager }
 
 	var app storage.Appendable
-	if o.EnableRemoteWriteReceiver || o.EnableOTLPWriteReceiver {
+	if o.EnableRemoteWriteReceiver {
 		app = h.storage
 	}
 
@@ -350,8 +349,6 @@ func New(logger log.Logger, o *Options) *Handler {
 		o.Gatherer,
 		o.Registerer,
 		nil,
-		o.EnableRemoteWriteReceiver,
-		o.EnableOTLPWriteReceiver,
 	)
 
 	if o.RoutePrefix != "/" {


Test patch:
diff --git a/model/histogram/float_histogram_test.go b/model/histogram/float_histogram_test.go
index dd3e30427..ce749b710 100644
--- a/model/histogram/float_histogram_test.go
+++ b/model/histogram/float_histogram_test.go
@@ -2205,50 +2205,3 @@ func TestAllReverseFloatBucketIterator(t *testing.T) {
 		})
 	}
 }
-
-func TestFloatBucketIteratorTargetSchema(t *testing.T) {
-	h := FloatHistogram{
-		Count:  405,
-		Sum:    1008.4,
-		Schema: 1,
-		PositiveSpans: []Span{
-			{Offset: 0, Length: 4},
-			{Offset: 1, Length: 3},
-			{Offset: 2, Length: 3},
-		},
-		PositiveBuckets: []float64{100, 344, 123, 55, 3, 63, 2, 54, 235, 33},
-		NegativeSpans: []Span{
-			{Offset: 0, Length: 3},
-			{Offset: 7, Length: 4},
-			{Offset: 1, Length: 3},
-		},
-		NegativeBuckets: []float64{10, 34, 1230, 54, 67, 63, 2, 554, 235, 33},
-	}
-	expPositiveBuckets := []Bucket[float64]{
-		{Lower: 0.25, Upper: 1, LowerInclusive: false, UpperInclusive: true, Count: 100, Index: 0},
-		{Lower: 1, Upper: 4, LowerInclusive: false, UpperInclusive: true, Count: 522, Index: 1},
-		{Lower: 4, Upper: 16, LowerInclusive: false, UpperInclusive: true, Count: 68, Index: 2},
-		{Lower: 16, Upper: 64, LowerInclusive: false, UpperInclusive: true, Count: 322, Index: 3},
-	}
-	expNegativeBuckets := []Bucket[float64]{
-		{Lower: -1, Upper: -0.25, LowerInclusive: true, UpperInclusive: false, Count: 10, Index: 0},
-		{Lower: -4, Upper: -1, LowerInclusive: true, UpperInclusive: false, Count: 1264, Index: 1},
-		{Lower: -64, Upper: -16, LowerInclusive: true, UpperInclusive: false, Count: 184, Index: 3},
-		{Lower: -256, Upper: -64, LowerInclusive: true, UpperInclusive: false, Count: 791, Index: 4},
-		{Lower: -1024, Upper: -256, LowerInclusive: true, UpperInclusive: false, Count: 33, Index: 5},
-	}
-
-	it := h.floatBucketIterator(true, 0, -1)
-	for i, b := range expPositiveBuckets {
-		require.True(t, it.Next(), "positive iterator exhausted too early")
-		require.Equal(t, b, it.At(), "bucket %d", i)
-	}
-	require.False(t, it.Next(), "positive iterator not exhausted")
-
-	it = h.floatBucketIterator(false, 0, -1)
-	for i, b := range expNegativeBuckets {
-		require.True(t, it.Next(), "negative iterator exhausted too early")
-		require.Equal(t, b, it.At(), "bucket %d", i)
-	}
-	require.False(t, it.Next(), "negative iterator not exhausted")
-}
diff --git a/model/textparse/protobufparse_test.go b/model/textparse/protobufparse_test.go
index 53523a5dd..07f84f4ae 100644
--- a/model/textparse/protobufparse_test.go
+++ b/model/textparse/protobufparse_test.go
@@ -517,19 +517,6 @@ metric: <
     sample_sum: 1.234
   >
 >
-`,
-		`name: "empty_histogram"
-help: "A histogram without observations and with a zero threshold of zero but with a no-op span to identify it as a native histogram."
-type: HISTOGRAM
-metric: <
-  histogram: <
-    positive_span: <
-      offset: 0
-      length: 0
-    >
-  >
->
-
 `,
 	}
 
@@ -978,25 +965,6 @@ func TestProtobufParse(t *testing.T) {
 						"__name__", "without_quantiles_sum",
 					),
 				},
-				{
-					m:    "empty_histogram",
-					help: "A histogram without observations and with a zero threshold of zero but with a no-op span to identify it as a native histogram.",
-				},
-				{
-					m:   "empty_histogram",
-					typ: MetricTypeHistogram,
-				},
-				{
-					m: "empty_histogram",
-					shs: &histogram.Histogram{
-						CounterResetHint: histogram.UnknownCounterReset,
-						PositiveSpans:    []histogram.Span{},
-						NegativeSpans:    []histogram.Span{},
-					},
-					lset: labels.FromStrings(
-						"__name__", "empty_histogram",
-					),
-				},
 			},
 		},
 		{
@@ -1720,25 +1688,6 @@ func TestProtobufParse(t *testing.T) {
 						"__name__", "without_quantiles_sum",
 					),
 				},
-				{ // 78
-					m:    "empty_histogram",
-					help: "A histogram without observations and with a zero threshold of zero but with a no-op span to identify it as a native histogram.",
-				},
-				{ // 79
-					m:   "empty_histogram",
-					typ: MetricTypeHistogram,
-				},
-				{ // 80
-					m: "empty_histogram",
-					shs: &histogram.Histogram{
-						CounterResetHint: histogram.UnknownCounterReset,
-						PositiveSpans:    []histogram.Span{},
-						NegativeSpans:    []histogram.Span{},
-					},
-					lset: labels.FromStrings(
-						"__name__", "empty_histogram",
-					),
-				},
 			},
 		},
 	}
diff --git a/promql/testdata/operators.test b/promql/testdata/operators.test
index df2311b9b..7056213c9 100644
--- a/promql/testdata/operators.test
+++ b/promql/testdata/operators.test
@@ -477,10 +477,10 @@ load 5m
     trigNaN{} NaN
 
 eval instant at 5m trigy atan2 trigx
-    {} 0.4636476090008061
+    trigy{} 0.4636476090008061
 
 eval instant at 5m trigy atan2 trigNaN
-    {} NaN
+    trigy{} NaN
 
 eval instant at 5m 10 atan2 20
     0.4636476090008061
diff --git a/storage/remote/otlptranslator/prometheus/normalize_label_test.go b/storage/remote/otlptranslator/prometheus/normalize_label_test.go
deleted file mode 100644
index 7346b20f9..000000000
--- a/storage/remote/otlptranslator/prometheus/normalize_label_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package normalize
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestSanitizeDropSanitization(t *testing.T) {
-	require.Equal(t, "", NormalizeLabel(""))
-	require.Equal(t, "_test", NormalizeLabel("_test"))
-	require.Equal(t, "key_0test", NormalizeLabel("0test"))
-	require.Equal(t, "test", NormalizeLabel("test"))
-	require.Equal(t, "test__", NormalizeLabel("test_/"))
-	require.Equal(t, "__test", NormalizeLabel("__test"))
-}
diff --git a/storage/remote/otlptranslator/prometheus/normalize_name_test.go b/storage/remote/otlptranslator/prometheus/normalize_name_test.go
deleted file mode 100644
index 33910636a..000000000
--- a/storage/remote/otlptranslator/prometheus/normalize_name_test.go
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package normalize
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/require"
-	"go.opentelemetry.io/collector/pdata/pmetric"
-)
-
-func TestByte(t *testing.T) {
-	require.Equal(t, "system_filesystem_usage_bytes", BuildPromCompliantName(createGauge("system.filesystem.usage", "By"), ""))
-}
-
-func TestByteCounter(t *testing.T) {
-	require.Equal(t, "system_io_bytes_total", BuildPromCompliantName(createCounter("system.io", "By"), ""))
-	require.Equal(t, "network_transmitted_bytes_total", BuildPromCompliantName(createCounter("network_transmitted_bytes_total", "By"), ""))
-}
-
-func TestWhiteSpaces(t *testing.T) {
-	require.Equal(t, "system_filesystem_usage_bytes", BuildPromCompliantName(createGauge("\t system.filesystem.usage       ", "  By\t"), ""))
-}
-
-func TestNonStandardUnit(t *testing.T) {
-	require.Equal(t, "system_network_dropped", BuildPromCompliantName(createGauge("system.network.dropped", "{packets}"), ""))
-}
-
-func TestNonStandardUnitCounter(t *testing.T) {
-	require.Equal(t, "system_network_dropped_total", BuildPromCompliantName(createCounter("system.network.dropped", "{packets}"), ""))
-}
-
-func TestBrokenUnit(t *testing.T) {
-	require.Equal(t, "system_network_dropped_packets", BuildPromCompliantName(createGauge("system.network.dropped", "packets"), ""))
-	require.Equal(t, "system_network_packets_dropped", BuildPromCompliantName(createGauge("system.network.packets.dropped", "packets"), ""))
-	require.Equal(t, "system_network_packets", BuildPromCompliantName(createGauge("system.network.packets", "packets"), ""))
-}
-
-func TestBrokenUnitCounter(t *testing.T) {
-	require.Equal(t, "system_network_dropped_packets_total", BuildPromCompliantName(createCounter("system.network.dropped", "packets"), ""))
-	require.Equal(t, "system_network_packets_dropped_total", BuildPromCompliantName(createCounter("system.network.packets.dropped", "packets"), ""))
-	require.Equal(t, "system_network_packets_total", BuildPromCompliantName(createCounter("system.network.packets", "packets"), ""))
-}
-
-func TestRatio(t *testing.T) {
-	require.Equal(t, "hw_gpu_memory_utilization_ratio", BuildPromCompliantName(createGauge("hw.gpu.memory.utilization", "1"), ""))
-	require.Equal(t, "hw_fan_speed_ratio", BuildPromCompliantName(createGauge("hw.fan.speed_ratio", "1"), ""))
-	require.Equal(t, "objects_total", BuildPromCompliantName(createCounter("objects", "1"), ""))
-}
-
-func TestHertz(t *testing.T) {
-	require.Equal(t, "hw_cpu_speed_limit_hertz", BuildPromCompliantName(createGauge("hw.cpu.speed_limit", "Hz"), ""))
-}
-
-func TestPer(t *testing.T) {
-	require.Equal(t, "broken_metric_speed_km_per_hour", BuildPromCompliantName(createGauge("broken.metric.speed", "km/h"), ""))
-	require.Equal(t, "astro_light_speed_limit_meters_per_second", BuildPromCompliantName(createGauge("astro.light.speed_limit", "m/s"), ""))
-}
-
-func TestPercent(t *testing.T) {
-	require.Equal(t, "broken_metric_success_ratio_percent", BuildPromCompliantName(createGauge("broken.metric.success_ratio", "%"), ""))
-	require.Equal(t, "broken_metric_success_percent", BuildPromCompliantName(createGauge("broken.metric.success_percent", "%"), ""))
-}
-
-func TestDollar(t *testing.T) {
-	require.Equal(t, "crypto_bitcoin_value_dollars", BuildPromCompliantName(createGauge("crypto.bitcoin.value", "$"), ""))
-	require.Equal(t, "crypto_bitcoin_value_dollars", BuildPromCompliantName(createGauge("crypto.bitcoin.value.dollars", "$"), ""))
-}
-
-func TestEmpty(t *testing.T) {
-	require.Equal(t, "test_metric_no_unit", BuildPromCompliantName(createGauge("test.metric.no_unit", ""), ""))
-	require.Equal(t, "test_metric_spaces", BuildPromCompliantName(createGauge("test.metric.spaces", "   \t  "), ""))
-}
-
-func TestUnsupportedRunes(t *testing.T) {
-	require.Equal(t, "unsupported_metric_temperature_F", BuildPromCompliantName(createGauge("unsupported.metric.temperature", "°F"), ""))
-	require.Equal(t, "unsupported_metric_weird", BuildPromCompliantName(createGauge("unsupported.metric.weird", "+=.:,!* & #"), ""))
-	require.Equal(t, "unsupported_metric_redundant_test_per_C", BuildPromCompliantName(createGauge("unsupported.metric.redundant", "__test $/°C"), ""))
-}
-
-func TestOtelReceivers(t *testing.T) {
-	require.Equal(t, "active_directory_ds_replication_network_io_bytes_total", BuildPromCompliantName(createCounter("active_directory.ds.replication.network.io", "By"), ""))
-	require.Equal(t, "active_directory_ds_replication_sync_object_pending_total", BuildPromCompliantName(createCounter("active_directory.ds.replication.sync.object.pending", "{objects}"), ""))
-	require.Equal(t, "active_directory_ds_replication_object_rate_per_second", BuildPromCompliantName(createGauge("active_directory.ds.replication.object.rate", "{objects}/s"), ""))
-	require.Equal(t, "active_directory_ds_name_cache_hit_rate_percent", BuildPromCompliantName(createGauge("active_directory.ds.name_cache.hit_rate", "%"), ""))
-	require.Equal(t, "active_directory_ds_ldap_bind_last_successful_time_milliseconds", BuildPromCompliantName(createGauge("active_directory.ds.ldap.bind.last_successful.time", "ms"), ""))
-	require.Equal(t, "apache_current_connections", BuildPromCompliantName(createGauge("apache.current_connections", "connections"), ""))
-	require.Equal(t, "apache_workers_connections", BuildPromCompliantName(createGauge("apache.workers", "connections"), ""))
-	require.Equal(t, "apache_requests_total", BuildPromCompliantName(createCounter("apache.requests", "1"), ""))
-	require.Equal(t, "bigip_virtual_server_request_count_total", BuildPromCompliantName(createCounter("bigip.virtual_server.request.count", "{requests}"), ""))
-	require.Equal(t, "system_cpu_utilization_ratio", BuildPromCompliantName(createGauge("system.cpu.utilization", "1"), ""))
-	require.Equal(t, "system_disk_operation_time_seconds_total", BuildPromCompliantName(createCounter("system.disk.operation_time", "s"), ""))
-	require.Equal(t, "system_cpu_load_average_15m_ratio", BuildPromCompliantName(createGauge("system.cpu.load_average.15m", "1"), ""))
-	require.Equal(t, "memcached_operation_hit_ratio_percent", BuildPromCompliantName(createGauge("memcached.operation_hit_ratio", "%"), ""))
-	require.Equal(t, "mongodbatlas_process_asserts_per_second", BuildPromCompliantName(createGauge("mongodbatlas.process.asserts", "{assertions}/s"), ""))
-	require.Equal(t, "mongodbatlas_process_journaling_data_files_mebibytes", BuildPromCompliantName(createGauge("mongodbatlas.process.journaling.data_files", "MiBy"), ""))
-	require.Equal(t, "mongodbatlas_process_network_io_bytes_per_second", BuildPromCompliantName(createGauge("mongodbatlas.process.network.io", "By/s"), ""))
-	require.Equal(t, "mongodbatlas_process_oplog_rate_gibibytes_per_hour", BuildPromCompliantName(createGauge("mongodbatlas.process.oplog.rate", "GiBy/h"), ""))
-	require.Equal(t, "mongodbatlas_process_db_query_targeting_scanned_per_returned", BuildPromCompliantName(createGauge("mongodbatlas.process.db.query_targeting.scanned_per_returned", "{scanned}/{returned}"), ""))
-	require.Equal(t, "nginx_requests", BuildPromCompliantName(createGauge("nginx.requests", "requests"), ""))
-	require.Equal(t, "nginx_connections_accepted", BuildPromCompliantName(createGauge("nginx.connections_accepted", "connections"), ""))
-	require.Equal(t, "nsxt_node_memory_usage_kilobytes", BuildPromCompliantName(createGauge("nsxt.node.memory.usage", "KBy"), ""))
-	require.Equal(t, "redis_latest_fork_microseconds", BuildPromCompliantName(createGauge("redis.latest_fork", "us"), ""))
-}
-
-func TestTrimPromSuffixes(t *testing.T) {
-	require.Equal(t, "active_directory_ds_replication_network_io", TrimPromSuffixes("active_directory_ds_replication_network_io_bytes_total", pmetric.MetricTypeSum, "bytes"))
-	require.Equal(t, "active_directory_ds_name_cache_hit_rate", TrimPromSuffixes("active_directory_ds_name_cache_hit_rate_percent", pmetric.MetricTypeGauge, "percent"))
-	require.Equal(t, "active_directory_ds_ldap_bind_last_successful_time", TrimPromSuffixes("active_directory_ds_ldap_bind_last_successful_time_milliseconds", pmetric.MetricTypeGauge, "milliseconds"))
-	require.Equal(t, "apache_requests", TrimPromSuffixes("apache_requests_total", pmetric.MetricTypeSum, "1"))
-	require.Equal(t, "system_cpu_utilization", TrimPromSuffixes("system_cpu_utilization_ratio", pmetric.MetricTypeGauge, "ratio"))
-	require.Equal(t, "mongodbatlas_process_journaling_data_files", TrimPromSuffixes("mongodbatlas_process_journaling_data_files_mebibytes", pmetric.MetricTypeGauge, "mebibytes"))
-	require.Equal(t, "mongodbatlas_process_network_io", TrimPromSuffixes("mongodbatlas_process_network_io_bytes_per_second", pmetric.MetricTypeGauge, "bytes_per_second"))
-	require.Equal(t, "mongodbatlas_process_oplog_rate", TrimPromSuffixes("mongodbatlas_process_oplog_rate_gibibytes_per_hour", pmetric.MetricTypeGauge, "gibibytes_per_hour"))
-	require.Equal(t, "nsxt_node_memory_usage", TrimPromSuffixes("nsxt_node_memory_usage_kilobytes", pmetric.MetricTypeGauge, "kilobytes"))
-	require.Equal(t, "redis_latest_fork", TrimPromSuffixes("redis_latest_fork_microseconds", pmetric.MetricTypeGauge, "microseconds"))
-	require.Equal(t, "up", TrimPromSuffixes("up", pmetric.MetricTypeGauge, ""))
-
-	// These are not necessarily valid OM units, only tested for the sake of completeness.
-	require.Equal(t, "active_directory_ds_replication_sync_object_pending", TrimPromSuffixes("active_directory_ds_replication_sync_object_pending_total", pmetric.MetricTypeSum, "{objects}"))
-	require.Equal(t, "apache_current", TrimPromSuffixes("apache_current_connections", pmetric.MetricTypeGauge, "connections"))
-	require.Equal(t, "bigip_virtual_server_request_count", TrimPromSuffixes("bigip_virtual_server_request_count_total", pmetric.MetricTypeSum, "{requests}"))
-	require.Equal(t, "mongodbatlas_process_db_query_targeting_scanned_per_returned", TrimPromSuffixes("mongodbatlas_process_db_query_targeting_scanned_per_returned", pmetric.MetricTypeGauge, "{scanned}/{returned}"))
-	require.Equal(t, "nginx_connections_accepted", TrimPromSuffixes("nginx_connections_accepted", pmetric.MetricTypeGauge, "connections"))
-	require.Equal(t, "apache_workers", TrimPromSuffixes("apache_workers_connections", pmetric.MetricTypeGauge, "connections"))
-	require.Equal(t, "nginx", TrimPromSuffixes("nginx_requests", pmetric.MetricTypeGauge, "requests"))
-
-	// Units shouldn't be trimmed if the unit is not a direct match with the suffix, i.e, a suffix "_seconds" shouldn't be removed if unit is "sec" or "s"
-	require.Equal(t, "system_cpu_load_average_15m_ratio", TrimPromSuffixes("system_cpu_load_average_15m_ratio", pmetric.MetricTypeGauge, "1"))
-	require.Equal(t, "mongodbatlas_process_asserts_per_second", TrimPromSuffixes("mongodbatlas_process_asserts_per_second", pmetric.MetricTypeGauge, "{assertions}/s"))
-	require.Equal(t, "memcached_operation_hit_ratio_percent", TrimPromSuffixes("memcached_operation_hit_ratio_percent", pmetric.MetricTypeGauge, "%"))
-	require.Equal(t, "active_directory_ds_replication_object_rate_per_second", TrimPromSuffixes("active_directory_ds_replication_object_rate_per_second", pmetric.MetricTypeGauge, "{objects}/s"))
-	require.Equal(t, "system_disk_operation_time_seconds", TrimPromSuffixes("system_disk_operation_time_seconds_total", pmetric.MetricTypeSum, "s"))
-}
-
-func TestNamespace(t *testing.T) {
-	require.Equal(t, "space_test", BuildPromCompliantName(createGauge("test", ""), "space"))
-	require.Equal(t, "space_test", BuildPromCompliantName(createGauge("#test", ""), "space"))
-}
-
-func TestCleanUpString(t *testing.T) {
-	require.Equal(t, "", CleanUpString(""))
-	require.Equal(t, "a_b", CleanUpString("a b"))
-	require.Equal(t, "hello_world", CleanUpString("hello, world!"))
-	require.Equal(t, "hello_you_2", CleanUpString("hello you 2"))
-	require.Equal(t, "1000", CleanUpString("$1000"))
-	require.Equal(t, "", CleanUpString("*+$^=)"))
-}
-
-func TestUnitMapGetOrDefault(t *testing.T) {
-	require.Equal(t, "", unitMapGetOrDefault(""))
-	require.Equal(t, "seconds", unitMapGetOrDefault("s"))
-	require.Equal(t, "invalid", unitMapGetOrDefault("invalid"))
-}
-
-func TestPerUnitMapGetOrDefault(t *testing.T) {
-	require.Equal(t, "", perUnitMapGetOrDefault(""))
-	require.Equal(t, "second", perUnitMapGetOrDefault("s"))
-	require.Equal(t, "invalid", perUnitMapGetOrDefault("invalid"))
-}
-
-func TestRemoveItem(t *testing.T) {
-	require.Equal(t, []string{}, removeItem([]string{}, "test"))
-	require.Equal(t, []string{}, removeItem([]string{}, ""))
-	require.Equal(t, []string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "d"))
-	require.Equal(t, []string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, ""))
-	require.Equal(t, []string{"a", "b"}, removeItem([]string{"a", "b", "c"}, "c"))
-	require.Equal(t, []string{"a", "c"}, removeItem([]string{"a", "b", "c"}, "b"))
-	require.Equal(t, []string{"b", "c"}, removeItem([]string{"a", "b", "c"}, "a"))
-}
-
-func TestBuildPromCompliantName(t *testing.T) {
-	require.Equal(t, "system_io_bytes_total", BuildPromCompliantName(createCounter("system.io", "By"), ""))
-	require.Equal(t, "system_network_io_bytes_total", BuildPromCompliantName(createCounter("network.io", "By"), "system"))
-	require.Equal(t, "_3_14_digits", BuildPromCompliantName(createGauge("3.14 digits", ""), ""))
-	require.Equal(t, "envoy_rule_engine_zlib_buf_error", BuildPromCompliantName(createGauge("envoy__rule_engine_zlib_buf_error", ""), ""))
-	require.Equal(t, "foo_bar", BuildPromCompliantName(createGauge(":foo::bar", ""), ""))
-	require.Equal(t, "foo_bar_total", BuildPromCompliantName(createCounter(":foo::bar", ""), ""))
-}
diff --git a/storage/remote/otlptranslator/prometheus/testutils_test.go b/storage/remote/otlptranslator/prometheus/testutils_test.go
deleted file mode 100644
index 1a45e46b0..000000000
--- a/storage/remote/otlptranslator/prometheus/testutils_test.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright The OpenTelemetry Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package normalize
-
-import (
-	"go.opentelemetry.io/collector/pdata/pmetric"
-)
-
-var ilm pmetric.ScopeMetrics
-
-func init() {
-	metrics := pmetric.NewMetrics()
-	resourceMetrics := metrics.ResourceMetrics().AppendEmpty()
-	ilm = resourceMetrics.ScopeMetrics().AppendEmpty()
-}
-
-// Returns a new Metric of type "Gauge" with specified name and unit
-func createGauge(name, unit string) pmetric.Metric {
-	gauge := ilm.Metrics().AppendEmpty()
-	gauge.SetName(name)
-	gauge.SetUnit(unit)
-	gauge.SetEmptyGauge()
-	return gauge
-}
-
-// Returns a new Metric of type Monotonic Sum with specified name and unit
-func createCounter(name, unit string) pmetric.Metric {
-	counter := ilm.Metrics().AppendEmpty()
-	counter.SetEmptySum().SetIsMonotonic(true)
-	counter.SetName(name)
-	counter.SetUnit(unit)
-	return counter
-}
diff --git a/storage/remote/write_test.go b/storage/remote/write_test.go
index 824e319c2..b1f1acbc7 100644
--- a/storage/remote/write_test.go
+++ b/storage/remote/write_test.go
@@ -14,9 +14,6 @@
 package remote
 
 import (
-	"bytes"
-	"net/http"
-	"net/http/httptest"
 	"net/url"
 	"testing"
 	"time"
@@ -25,9 +22,6 @@ import (
 	common_config "github.com/prometheus/common/config"
 	"github.com/prometheus/common/model"
 	"github.com/stretchr/testify/require"
-	"go.opentelemetry.io/collector/pdata/pcommon"
-	"go.opentelemetry.io/collector/pdata/pmetric"
-	"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
 
 	"github.com/prometheus/prometheus/config"
 	"github.com/prometheus/prometheus/model/labels"
@@ -379,107 +373,3 @@ func TestWriteStorageApplyConfigsPartialUpdate(t *testing.T) {
 	err = s.Close()
 	require.NoError(t, err)
 }
-
-func TestOTLPWriteHandler(t *testing.T) {
-	exportRequest := generateOTLPWriteRequest(t)
-
-	buf, err := exportRequest.MarshalProto()
-	require.NoError(t, err)
-
-	req, err := http.NewRequest("", "", bytes.NewReader(buf))
-	require.NoError(t, err)
-	req.Header.Set("Content-Type", "application/x-protobuf")
-
-	appendable := &mockAppendable{}
-	handler := NewOTLPWriteHandler(nil, appendable)
-
-	recorder := httptest.NewRecorder()
-	handler.ServeHTTP(recorder, req)
-
-	resp := recorder.Result()
-	require.Equal(t, http.StatusOK, resp.StatusCode)
-
-	require.Equal(t, 12, len(appendable.samples))   // 1 (counter) + 1 (gauge) + 1 (target_info) + 7 (hist_bucket) + 2 (hist_sum, hist_count)
-	require.Equal(t, 1, len(appendable.histograms)) // 1 (exponential histogram)
-	require.Equal(t, 1, len(appendable.exemplars))  // 1 (exemplar)
-}
-
-func generateOTLPWriteRequest(t *testing.T) pmetricotlp.ExportRequest {
-	d := pmetric.NewMetrics()
-
-	// Generate One Counter, One Gauge, One Histogram, One Exponential-Histogram
-	// with resource attributes: service.name="test-service", service.instance.id="test-instance", host.name="test-host"
-	// with metric attibute: foo.bar="baz"
-
-	timestamp := time.Now()
-
-	resourceMetric := d.ResourceMetrics().AppendEmpty()
-	resourceMetric.Resource().Attributes().PutStr("service.name", "test-service")
-	resourceMetric.Resource().Attributes().PutStr("service.instance.id", "test-instance")
-	resourceMetric.Resource().Attributes().PutStr("host.name", "test-host")
-
-	scopeMetric := resourceMetric.ScopeMetrics().AppendEmpty()
-
-	// Generate One Counter
-	counterMetric := scopeMetric.Metrics().AppendEmpty()
-	counterMetric.SetName("test-counter")
-	counterMetric.SetDescription("test-counter-description")
-	counterMetric.SetEmptySum()
-	counterMetric.Sum().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
-	counterMetric.Sum().SetIsMonotonic(true)
-
-	counterDataPoint := counterMetric.Sum().DataPoints().AppendEmpty()
-	counterDataPoint.SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
-	counterDataPoint.SetDoubleValue(10.0)
-	counterDataPoint.Attributes().PutStr("foo.bar", "baz")
-
-	counterExemplar := counterDataPoint.Exemplars().AppendEmpty()
-	counterExemplar.SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
-	counterExemplar.SetDoubleValue(10.0)
-	counterExemplar.SetSpanID(pcommon.SpanID{0, 1, 2, 3, 4, 5, 6, 7})
-	counterExemplar.SetTraceID(pcommon.TraceID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})
-
-	// Generate One Gauge
-	gaugeMetric := scopeMetric.Metrics().AppendEmpty()
-	gaugeMetric.SetName("test-gauge")
-	gaugeMetric.SetDescription("test-gauge-description")
-	gaugeMetric.SetEmptyGauge()
-
-	gaugeDataPoint := gaugeMetric.Gauge().DataPoints().AppendEmpty()
-	gaugeDataPoint.SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
-	gaugeDataPoint.SetDoubleValue(10.0)
-	gaugeDataPoint.Attributes().PutStr("foo.bar", "baz")
-
-	// Generate One Histogram
-	histogramMetric := scopeMetric.Metrics().AppendEmpty()
-	histogramMetric.SetName("test-histogram")
-	histogramMetric.SetDescription("test-histogram-description")
-	histogramMetric.SetEmptyHistogram()
-	histogramMetric.Histogram().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
-
-	histogramDataPoint := histogramMetric.Histogram().DataPoints().AppendEmpty()
-	histogramDataPoint.SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
-	histogramDataPoint.ExplicitBounds().FromRaw([]float64{0.0, 1.0, 2.0, 3.0, 4.0, 5.0})
-	histogramDataPoint.BucketCounts().FromRaw([]uint64{2, 2, 2, 2, 2, 2})
-	histogramDataPoint.SetCount(10)
-	histogramDataPoint.SetSum(30.0)
-	histogramDataPoint.Attributes().PutStr("foo.bar", "baz")
-
-	// Generate One Exponential-Histogram
-	exponentialHistogramMetric := scopeMetric.Metrics().AppendEmpty()
-	exponentialHistogramMetric.SetName("test-exponential-histogram")
-	exponentialHistogramMetric.SetDescription("test-exponential-histogram-description")
-	exponentialHistogramMetric.SetEmptyExponentialHistogram()
-	exponentialHistogramMetric.ExponentialHistogram().SetAggregationTemporality(pmetric.AggregationTemporalityCumulative)
-
-	exponentialHistogramDataPoint := exponentialHistogramMetric.ExponentialHistogram().DataPoints().AppendEmpty()
-	exponentialHistogramDataPoint.SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
-	exponentialHistogramDataPoint.SetScale(2.0)
-	exponentialHistogramDataPoint.Positive().BucketCounts().FromRaw([]uint64{2, 2, 2, 2, 2})
-	exponentialHistogramDataPoint.SetZeroCount(2)
-	exponentialHistogramDataPoint.SetCount(10)
-	exponentialHistogramDataPoint.SetSum(30.0)
-	exponentialHistogramDataPoint.Attributes().PutStr("foo.bar", "baz")
-
-	return pmetricotlp.NewExportRequestFromMetrics(d)
-}
diff --git a/tsdb/agent/db_test.go b/tsdb/agent/db_test.go
index 5c71d548f..e284e1b77 100644
--- a/tsdb/agent/db_test.go
+++ b/tsdb/agent/db_test.go
@@ -333,8 +333,8 @@ func TestRollback(t *testing.T) {
 		}
 	}
 
-	// Check that only series get stored after calling Rollback.
-	require.Equal(t, numSeries*3, walSeriesCount, "series should have been written to WAL")
+	// Check that the rollback ensured nothing got stored.
+	require.Equal(t, 0, walSeriesCount, "series should not have been written to WAL")
 	require.Equal(t, 0, walSamplesCount, "samples should not have been written to WAL")
 	require.Equal(t, 0, walExemplarsCount, "exemplars should not have been written to WAL")
 	require.Equal(t, 0, walHistogramCount, "histograms should not have been written to WAL")
diff --git a/tsdb/chunkenc/float_histogram_test.go b/tsdb/chunkenc/float_histogram_test.go
index 8af381fd7..c662e5ffa 100644
--- a/tsdb/chunkenc/float_histogram_test.go
+++ b/tsdb/chunkenc/float_histogram_test.go
@@ -53,9 +53,7 @@ func TestFloatHistogramChunkSameBuckets(t *testing.T) {
 		},
 		NegativeBuckets: []int64{2, 1, -1, -1}, // counts: 2, 3, 2, 1 (total 8)
 	}
-	chk, _, app, err := app.AppendFloatHistogram(nil, ts, h.ToFloat(), false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	app.AppendFloatHistogram(ts, h.ToFloat())
 	exp = append(exp, floatResult{t: ts, h: h.ToFloat()})
 	require.Equal(t, 1, c.NumSamples())
 
@@ -67,9 +65,7 @@ func TestFloatHistogramChunkSameBuckets(t *testing.T) {
 	h.Sum = 24.4
 	h.PositiveBuckets = []int64{5, -2, 1, -2} // counts: 5, 3, 4, 2 (total 14)
 	h.NegativeBuckets = []int64{4, -1, 1, -1} // counts: 4, 3, 4, 4 (total 15)
-	chk, _, _, err = app.AppendFloatHistogram(nil, ts, h.ToFloat(), false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	app.AppendFloatHistogram(ts, h.ToFloat())
 	expH := h.ToFloat()
 	expH.CounterResetHint = histogram.NotCounterReset
 	exp = append(exp, floatResult{t: ts, h: expH})
@@ -86,9 +82,7 @@ func TestFloatHistogramChunkSameBuckets(t *testing.T) {
 	h.Sum = 24.4
 	h.PositiveBuckets = []int64{6, 1, -3, 6} // counts: 6, 7, 4, 10 (total 27)
 	h.NegativeBuckets = []int64{5, 1, -2, 3} // counts: 5, 6, 4, 7 (total 22)
-	chk, _, _, err = app.AppendFloatHistogram(nil, ts, h.ToFloat(), false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	app.AppendFloatHistogram(ts, h.ToFloat())
 	expH = h.ToFloat()
 	expH.CounterResetHint = histogram.NotCounterReset
 	exp = append(exp, floatResult{t: ts, h: expH})
@@ -176,9 +170,7 @@ func TestFloatHistogramChunkBucketChanges(t *testing.T) {
 		NegativeBuckets: []int64{1},
 	}
 
-	chk, _, app, err := app.AppendFloatHistogram(nil, ts1, h1.ToFloat(), false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	app.AppendFloatHistogram(ts1, h1.ToFloat())
 	require.Equal(t, 1, c.NumSamples())
 
 	// Add a new histogram that has expanded buckets.
@@ -204,15 +196,14 @@ func TestFloatHistogramChunkBucketChanges(t *testing.T) {
 	h2.NegativeBuckets = []int64{2, -1} // 2 1 (total 3)
 	// This is how span changes will be handled.
 	hApp, _ := app.(*FloatHistogramAppender)
-	posInterjections, negInterjections, ok, cr := hApp.appendable(h2.ToFloat())
+	posInterjections, negInterjections, ok, cr := hApp.Appendable(h2.ToFloat())
 	require.Greater(t, len(posInterjections), 0)
 	require.Greater(t, len(negInterjections), 0)
 	require.True(t, ok) // Only new buckets came in.
 	require.False(t, cr)
-	c, app = hApp.recode(posInterjections, negInterjections, h2.PositiveSpans, h2.NegativeSpans)
-	chk, _, _, err = app.AppendFloatHistogram(nil, ts2, h2.ToFloat(), false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	c, app = hApp.Recode(posInterjections, negInterjections, h2.PositiveSpans, h2.NegativeSpans)
+	app.AppendFloatHistogram(ts2, h2.ToFloat())
+
 	require.Equal(t, 2, c.NumSamples())
 
 	// Because the 2nd histogram has expanded buckets, we should expect all
@@ -239,61 +230,49 @@ func TestFloatHistogramChunkBucketChanges(t *testing.T) {
 }
 
 func TestFloatHistogramChunkAppendable(t *testing.T) {
-	setup := func() (Chunk, *FloatHistogramAppender, int64, *histogram.FloatHistogram) {
-		c := Chunk(NewFloatHistogramChunk())
-
-		// Create fresh appender and add the first histogram.
-		app, err := c.Appender()
-		require.NoError(t, err)
-		require.Equal(t, 0, c.NumSamples())
-
-		ts := int64(1234567890)
-		h1 := &histogram.FloatHistogram{
-			Count:         5,
-			ZeroCount:     2,
-			Sum:           18.4,
-			ZeroThreshold: 1e-125,
-			Schema:        1,
-			PositiveSpans: []histogram.Span{
-				{Offset: 0, Length: 2},
-				{Offset: 2, Length: 1},
-				{Offset: 3, Length: 2},
-				{Offset: 3, Length: 1},
-				{Offset: 1, Length: 1},
-			},
-			PositiveBuckets: []float64{6, 3, 3, 2, 4, 5, 1},
-		}
+	c := Chunk(NewFloatHistogramChunk())
+
+	// Create fresh appender and add the first histogram.
+	app, err := c.Appender()
+	require.NoError(t, err)
+	require.Equal(t, 0, c.NumSamples())
 
-		chk, _, app, err := app.AppendFloatHistogram(nil, ts, h1.Copy(), false)
-		require.NoError(t, err)
-		require.Nil(t, chk)
-		require.Equal(t, 1, c.NumSamples())
-		require.Equal(t, UnknownCounterReset, c.(*FloatHistogramChunk).GetCounterResetHeader())
-		return c, app.(*FloatHistogramAppender), ts, h1
+	ts := int64(1234567890)
+	h1 := &histogram.FloatHistogram{
+		Count:         5,
+		ZeroCount:     2,
+		Sum:           18.4,
+		ZeroThreshold: 1e-125,
+		Schema:        1,
+		PositiveSpans: []histogram.Span{
+			{Offset: 0, Length: 2},
+			{Offset: 2, Length: 1},
+			{Offset: 3, Length: 2},
+			{Offset: 3, Length: 1},
+			{Offset: 1, Length: 1},
+		},
+		PositiveBuckets: []float64{6, 3, 3, 2, 4, 5, 1},
 	}
 
+	app.AppendFloatHistogram(ts, h1.Copy())
+	require.Equal(t, 1, c.NumSamples())
+	hApp, _ := app.(*FloatHistogramAppender)
+
 	{ // Schema change.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.Schema++
-		_, _, ok, _ := hApp.appendable(h2)
+		_, _, ok, _ := hApp.Appendable(h2)
 		require.False(t, ok)
-
-		assertNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, UnknownCounterReset)
 	}
 
 	{ // Zero threshold change.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.ZeroThreshold += 0.1
-		_, _, ok, _ := hApp.appendable(h2)
+		_, _, ok, _ := hApp.Appendable(h2)
 		require.False(t, ok)
-
-		assertNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, UnknownCounterReset)
 	}
 
 	{ // New histogram that has more buckets.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 3},
@@ -306,18 +285,14 @@ func TestFloatHistogramChunkAppendable(t *testing.T) {
 		h2.Sum = 30
 		h2.PositiveBuckets = []float64{7, 5, 1, 3, 1, 0, 2, 5, 5, 0, 1}
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Greater(t, len(posInterjections), 0)
 		require.Equal(t, 0, len(negInterjections))
 		require.True(t, ok) // Only new buckets came in.
 		require.False(t, cr)
-
-		assertRecodedFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, UnknownCounterReset)
 	}
 
 	{ // New histogram that has a bucket missing.
-		c, hApp, ts, h1 := setup()
-
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 2},
@@ -328,32 +303,26 @@ func TestFloatHistogramChunkAppendable(t *testing.T) {
 		h2.Sum = 21
 		h2.PositiveBuckets = []float64{6, 3, 2, 4, 5, 1}
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Equal(t, 0, len(posInterjections))
 		require.Equal(t, 0, len(negInterjections))
 		require.False(t, ok) // Need to cut a new chunk.
 		require.True(t, cr)
-
-		assertNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, CounterReset)
 	}
 
 	{ // New histogram that has a counter reset while buckets are same.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.Sum = 23
 		h2.PositiveBuckets = []float64{6, 2, 3, 2, 4, 5, 1}
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Equal(t, 0, len(posInterjections))
 		require.Equal(t, 0, len(negInterjections))
 		require.False(t, ok) // Need to cut a new chunk.
 		require.True(t, cr)
-
-		assertNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, CounterReset)
 	}
 
 	{ // New histogram that has a counter reset while new buckets were added.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 3},
@@ -364,17 +333,14 @@ func TestFloatHistogramChunkAppendable(t *testing.T) {
 		h2.Sum = 29
 		h2.PositiveBuckets = []float64{7, 5, 1, 3, 1, 0, 2, 5, 5, 0, 0}
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Equal(t, 0, len(posInterjections))
 		require.Equal(t, 0, len(negInterjections))
 		require.False(t, ok) // Need to cut a new chunk.
 		require.True(t, cr)
-
-		assertNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, CounterReset)
 	}
 
 	{
-		c, hApp, ts, h1 := setup()
 		// New histogram that has a counter reset while new buckets were
 		// added before the first bucket and reset on first bucket.  (to
 		// catch the edge case where the new bucket should be forwarded
@@ -391,57 +357,14 @@ func TestFloatHistogramChunkAppendable(t *testing.T) {
 		h2.Sum = 26
 		h2.PositiveBuckets = []float64{1, 2, 5, 3, 3, 2, 4, 5, 1}
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Equal(t, 0, len(posInterjections))
 		require.Equal(t, 0, len(negInterjections))
 		require.False(t, ok) // Need to cut a new chunk.
 		require.True(t, cr)
-
-		assertNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, CounterReset)
 	}
 }
 
-func assertNewFloatHistogramChunkOnAppend(t *testing.T, oldChunk Chunk, hApp *FloatHistogramAppender, ts int64, h *histogram.FloatHistogram, expectHeader CounterResetHeader) {
-	oldChunkBytes := oldChunk.Bytes()
-	newChunk, recoded, newAppender, err := hApp.AppendFloatHistogram(nil, ts, h, false)
-	require.Equal(t, oldChunkBytes, oldChunk.Bytes()) // Sanity check that previous chunk is untouched.
-	require.NoError(t, err)
-	require.NotNil(t, newChunk)
-	require.False(t, recoded)
-	require.NotEqual(t, oldChunk, newChunk)
-	require.Equal(t, expectHeader, newChunk.(*FloatHistogramChunk).GetCounterResetHeader())
-	require.NotNil(t, newAppender)
-	require.NotEqual(t, hApp, newAppender)
-	assertSampleCount(t, newChunk, 1, ValFloatHistogram)
-}
-
-func assertNoNewFloatHistogramChunkOnAppend(t *testing.T, oldChunk Chunk, hApp *FloatHistogramAppender, ts int64, h *histogram.FloatHistogram, expectHeader CounterResetHeader) {
-	oldChunkBytes := oldChunk.Bytes()
-	newChunk, recoded, newAppender, err := hApp.AppendFloatHistogram(nil, ts, h, false)
-	require.NotEqual(t, oldChunkBytes, oldChunk.Bytes()) // Sanity check that previous chunk is untouched.
-	require.NoError(t, err)
-	require.Nil(t, newChunk)
-	require.False(t, recoded)
-	require.Equal(t, expectHeader, oldChunk.(*FloatHistogramChunk).GetCounterResetHeader())
-	require.NotNil(t, newAppender)
-	require.Equal(t, hApp, newAppender)
-	assertSampleCount(t, oldChunk, 2, ValFloatHistogram)
-}
-
-func assertRecodedFloatHistogramChunkOnAppend(t *testing.T, prevChunk Chunk, hApp *FloatHistogramAppender, ts int64, h *histogram.FloatHistogram, expectHeader CounterResetHeader) {
-	prevChunkBytes := prevChunk.Bytes()
-	newChunk, recoded, newAppender, err := hApp.AppendFloatHistogram(nil, ts, h, false)
-	require.Equal(t, prevChunkBytes, prevChunk.Bytes()) // Sanity check that previous chunk is untouched. This may change in the future if we implement in-place recoding.
-	require.NoError(t, err)
-	require.NotNil(t, newChunk)
-	require.True(t, recoded)
-	require.NotEqual(t, prevChunk, newChunk)
-	require.Equal(t, expectHeader, newChunk.(*FloatHistogramChunk).GetCounterResetHeader())
-	require.NotNil(t, newAppender)
-	require.NotEqual(t, hApp, newAppender)
-	assertSampleCount(t, newChunk, 2, ValFloatHistogram)
-}
-
 func TestFloatHistogramChunkAppendableWithEmptySpan(t *testing.T) {
 	h1 := &histogram.FloatHistogram{
 		Schema:        0,
@@ -489,12 +412,11 @@ func TestFloatHistogramChunkAppendableWithEmptySpan(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, 0, c.NumSamples())
 
-	_, _, _, err = app.AppendFloatHistogram(nil, 1, h1, true)
-	require.NoError(t, err)
+	app.AppendFloatHistogram(1, h1)
 	require.Equal(t, 1, c.NumSamples())
 	hApp, _ := app.(*FloatHistogramAppender)
 
-	pI, nI, okToAppend, counterReset := hApp.appendable(h2)
+	pI, nI, okToAppend, counterReset := hApp.Appendable(h2)
 	require.Empty(t, pI)
 	require.Empty(t, nI)
 	require.True(t, okToAppend)
@@ -502,62 +424,51 @@ func TestFloatHistogramChunkAppendableWithEmptySpan(t *testing.T) {
 }
 
 func TestFloatHistogramChunkAppendableGauge(t *testing.T) {
-	setup := func() (Chunk, *FloatHistogramAppender, int64, *histogram.FloatHistogram) {
-		c := Chunk(NewFloatHistogramChunk())
-
-		// Create fresh appender and add the first histogram.
-		app, err := c.Appender()
-		require.NoError(t, err)
-		require.Equal(t, 0, c.NumSamples())
-
-		ts := int64(1234567890)
-		h1 := &histogram.FloatHistogram{
-			CounterResetHint: histogram.GaugeType,
-			Count:            5,
-			ZeroCount:        2,
-			Sum:              18.4,
-			ZeroThreshold:    1e-125,
-			Schema:           1,
-			PositiveSpans: []histogram.Span{
-				{Offset: 0, Length: 2},
-				{Offset: 2, Length: 1},
-				{Offset: 3, Length: 2},
-				{Offset: 3, Length: 1},
-				{Offset: 1, Length: 1},
-			},
-			PositiveBuckets: []float64{6, 3, 3, 2, 4, 5, 1},
-		}
+	c := Chunk(NewFloatHistogramChunk())
 
-		chk, _, app, err := app.AppendFloatHistogram(nil, ts, h1.Copy(), false)
-		require.NoError(t, err)
-		require.Nil(t, chk)
-		require.Equal(t, 1, c.NumSamples())
-		require.Equal(t, GaugeType, c.(*FloatHistogramChunk).GetCounterResetHeader())
-		return c, app.(*FloatHistogramAppender), ts, h1
+	// Create fresh appender and add the first histogram.
+	app, err := c.Appender()
+	require.NoError(t, err)
+	require.Equal(t, 0, c.NumSamples())
+
+	ts := int64(1234567890)
+	h1 := &histogram.FloatHistogram{
+		Count:         5,
+		ZeroCount:     2,
+		Sum:           18.4,
+		ZeroThreshold: 1e-125,
+		Schema:        1,
+		PositiveSpans: []histogram.Span{
+			{Offset: 0, Length: 2},
+			{Offset: 2, Length: 1},
+			{Offset: 3, Length: 2},
+			{Offset: 3, Length: 1},
+			{Offset: 1, Length: 1},
+		},
+		PositiveBuckets: []float64{6, 3, 3, 2, 4, 5, 1},
 	}
 
+	app.AppendFloatHistogram(ts, h1.Copy())
+	require.Equal(t, 1, c.NumSamples())
+	c.(*FloatHistogramChunk).SetCounterResetHeader(GaugeType)
+
 	{ // Schema change.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.Schema++
-		_, _, _, _, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*FloatHistogramAppender)
+		_, _, _, _, _, _, ok := hApp.AppendableGauge(h2)
 		require.False(t, ok)
-
-		assertNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, GaugeType)
 	}
 
 	{ // Zero threshold change.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.ZeroThreshold += 0.1
-		_, _, _, _, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*FloatHistogramAppender)
+		_, _, _, _, _, _, ok := hApp.AppendableGauge(h2)
 		require.False(t, ok)
-
-		assertNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, GaugeType)
 	}
 
 	{ // New histogram that has more buckets.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 3},
@@ -570,18 +481,16 @@ func TestFloatHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum = 30
 		h2.PositiveBuckets = []float64{7, 5, 1, 3, 1, 0, 2, 5, 5, 0, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*FloatHistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Greater(t, len(pI), 0)
 		require.Len(t, nI, 0)
 		require.Len(t, pBackwardI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		assertRecodedFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, GaugeType)
 	}
 
 	{ // New histogram that has buckets missing.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 2},
@@ -594,18 +503,16 @@ func TestFloatHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum--
 		h2.PositiveBuckets = []float64{6, 3, 3, 2, 5, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*FloatHistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Len(t, pI, 0)
 		require.Len(t, nI, 0)
 		require.Greater(t, len(pBackwardI), 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		assertNoNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, GaugeType)
 	}
 
 	{ // New histogram that has a bucket missing and new buckets.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 2},
@@ -616,34 +523,30 @@ func TestFloatHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum = 21
 		h2.PositiveBuckets = []float64{6, 3, 2, 4, 5, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*FloatHistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Greater(t, len(pI), 0)
 		require.Greater(t, len(pBackwardI), 0)
 		require.Len(t, nI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		assertRecodedFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, GaugeType)
 	}
 
 	{ // New histogram that has a counter reset while buckets are same.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.Sum = 23
 		h2.PositiveBuckets = []float64{6, 2, 3, 2, 4, 5, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*FloatHistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Len(t, pI, 0)
 		require.Len(t, nI, 0)
 		require.Len(t, pBackwardI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		assertNoNewFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, GaugeType)
 	}
 
 	{ // New histogram that has a counter reset while new buckets were added.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 3},
@@ -654,20 +557,18 @@ func TestFloatHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum = 29
 		h2.PositiveBuckets = []float64{7, 5, 1, 3, 1, 0, 2, 5, 5, 0, 0}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*FloatHistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Greater(t, len(pI), 0)
 		require.Len(t, nI, 0)
 		require.Len(t, pBackwardI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		assertRecodedFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, GaugeType)
 	}
 
 	{
 		// New histogram that has a counter reset while new buckets were
 		// added before the first bucket and reset on first bucket.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: -3, Length: 2},
@@ -680,13 +581,12 @@ func TestFloatHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum = 26
 		h2.PositiveBuckets = []float64{1, 2, 5, 3, 3, 2, 4, 5, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*FloatHistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Greater(t, len(pI), 0)
 		require.Len(t, nI, 0)
 		require.Len(t, pBackwardI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		assertRecodedFloatHistogramChunkOnAppend(t, c, hApp, ts+1, h2, GaugeType)
 	}
 }
diff --git a/tsdb/chunkenc/histogram_test.go b/tsdb/chunkenc/histogram_test.go
index 449af5902..9ef7e24a4 100644
--- a/tsdb/chunkenc/histogram_test.go
+++ b/tsdb/chunkenc/histogram_test.go
@@ -14,6 +14,7 @@
 package chunkenc
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -54,9 +55,7 @@ func TestHistogramChunkSameBuckets(t *testing.T) {
 		},
 		NegativeBuckets: []int64{2, 1, -1, -1}, // counts: 2, 3, 2, 1 (total 8)
 	}
-	chk, _, app, err := app.AppendHistogram(nil, ts, h, false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	app.AppendHistogram(ts, h)
 	exp = append(exp, result{t: ts, h: h, fh: h.ToFloat()})
 	require.Equal(t, 1, c.NumSamples())
 
@@ -68,9 +67,7 @@ func TestHistogramChunkSameBuckets(t *testing.T) {
 	h.Sum = 24.4
 	h.PositiveBuckets = []int64{5, -2, 1, -2} // counts: 5, 3, 4, 2 (total 14)
 	h.NegativeBuckets = []int64{4, -1, 1, -1} // counts: 4, 3, 4, 4 (total 15)
-	chk, _, _, err = app.AppendHistogram(nil, ts, h, false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	app.AppendHistogram(ts, h)
 	hExp := h.Copy()
 	hExp.CounterResetHint = histogram.NotCounterReset
 	exp = append(exp, result{t: ts, h: hExp, fh: hExp.ToFloat()})
@@ -87,9 +84,7 @@ func TestHistogramChunkSameBuckets(t *testing.T) {
 	h.Sum = 24.4
 	h.PositiveBuckets = []int64{6, 1, -3, 6} // counts: 6, 7, 4, 10 (total 27)
 	h.NegativeBuckets = []int64{5, 1, -2, 3} // counts: 5, 6, 4, 7 (total 22)
-	chk, _, _, err = app.AppendHistogram(nil, ts, h, false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	app.AppendHistogram(ts, h)
 	hExp = h.Copy()
 	hExp.CounterResetHint = histogram.NotCounterReset
 	exp = append(exp, result{t: ts, h: hExp, fh: hExp.ToFloat()})
@@ -187,9 +182,7 @@ func TestHistogramChunkBucketChanges(t *testing.T) {
 		NegativeBuckets: []int64{1},
 	}
 
-	chk, _, app, err := app.AppendHistogram(nil, ts1, h1, false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	app.AppendHistogram(ts1, h1)
 	require.Equal(t, 1, c.NumSamples())
 
 	// Add a new histogram that has expanded buckets.
@@ -215,15 +208,13 @@ func TestHistogramChunkBucketChanges(t *testing.T) {
 	h2.NegativeBuckets = []int64{2, -1} // 2 1 (total 3)
 	// This is how span changes will be handled.
 	hApp, _ := app.(*HistogramAppender)
-	posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+	posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 	require.Greater(t, len(posInterjections), 0)
 	require.Greater(t, len(negInterjections), 0)
 	require.True(t, ok) // Only new buckets came in.
 	require.False(t, cr)
-	c, app = hApp.recode(posInterjections, negInterjections, h2.PositiveSpans, h2.NegativeSpans)
-	chk, _, _, err = app.AppendHistogram(nil, ts2, h2, false)
-	require.NoError(t, err)
-	require.Nil(t, chk)
+	c, app = hApp.Recode(posInterjections, negInterjections, h2.PositiveSpans, h2.NegativeSpans)
+	app.AppendHistogram(ts2, h2)
 
 	require.Equal(t, 2, c.NumSamples())
 
@@ -253,61 +244,49 @@ func TestHistogramChunkBucketChanges(t *testing.T) {
 }
 
 func TestHistogramChunkAppendable(t *testing.T) {
-	setup := func() (Chunk, *HistogramAppender, int64, *histogram.Histogram) {
-		c := Chunk(NewHistogramChunk())
-
-		// Create fresh appender and add the first histogram.
-		app, err := c.Appender()
-		require.NoError(t, err)
-		require.Equal(t, 0, c.NumSamples())
-
-		ts := int64(1234567890)
-		h1 := &histogram.Histogram{
-			Count:         5,
-			ZeroCount:     2,
-			Sum:           18.4,
-			ZeroThreshold: 1e-125,
-			Schema:        1,
-			PositiveSpans: []histogram.Span{
-				{Offset: 0, Length: 2},
-				{Offset: 2, Length: 1},
-				{Offset: 3, Length: 2},
-				{Offset: 3, Length: 1},
-				{Offset: 1, Length: 1},
-			},
-			PositiveBuckets: []int64{6, -3, 0, -1, 2, 1, -4}, // counts: 6, 3, 3, 2, 4, 5, 1 (total 24)
-		}
+	c := Chunk(NewHistogramChunk())
 
-		chk, _, app, err := app.AppendHistogram(nil, ts, h1.Copy(), false)
-		require.NoError(t, err)
-		require.Nil(t, chk)
-		require.Equal(t, 1, c.NumSamples())
-		require.Equal(t, UnknownCounterReset, c.(*HistogramChunk).GetCounterResetHeader())
-		return c, app.(*HistogramAppender), ts, h1
+	// Create fresh appender and add the first histogram.
+	app, err := c.Appender()
+	require.NoError(t, err)
+	require.Equal(t, 0, c.NumSamples())
+
+	ts := int64(1234567890)
+	h1 := &histogram.Histogram{
+		Count:         5,
+		ZeroCount:     2,
+		Sum:           18.4,
+		ZeroThreshold: 1e-125,
+		Schema:        1,
+		PositiveSpans: []histogram.Span{
+			{Offset: 0, Length: 2},
+			{Offset: 2, Length: 1},
+			{Offset: 3, Length: 2},
+			{Offset: 3, Length: 1},
+			{Offset: 1, Length: 1},
+		},
+		PositiveBuckets: []int64{6, -3, 0, -1, 2, 1, -4}, // counts: 6, 3, 3, 2, 4, 5, 1 (total 24)
 	}
 
+	app.AppendHistogram(ts, h1.Copy())
+	require.Equal(t, 1, c.NumSamples())
+	hApp, _ := app.(*HistogramAppender)
+
 	{ // Schema change.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.Schema++
-		_, _, ok, _ := hApp.appendable(h2)
+		_, _, ok, _ := hApp.Appendable(h2)
 		require.False(t, ok)
-
-		assertNewHistogramChunkOnAppend(t, c, hApp, ts+1, h2, UnknownCounterReset)
 	}
 
 	{ // Zero threshold change.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.ZeroThreshold += 0.1
-		_, _, ok, _ := hApp.appendable(h2)
+		_, _, ok, _ := hApp.Appendable(h2)
 		require.False(t, ok)
-
-		assertNewHistogramChunkOnAppend(t, c, hApp, ts+1, h2, UnknownCounterReset)
 	}
 
 	{ // New histogram that has more buckets.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 3},
@@ -323,17 +302,14 @@ func TestHistogramChunkAppendable(t *testing.T) {
 		// so the new histogram should have new counts >= these per-bucket counts, e.g.:
 		h2.PositiveBuckets = []int64{7, -2, -4, 2, -2, -1, 2, 3, 0, -5, 1} // 7 5 1 3 1 0 2 5 5 0 1 (total 30)
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Greater(t, len(posInterjections), 0)
 		require.Equal(t, 0, len(negInterjections))
 		require.True(t, ok) // Only new buckets came in.
 		require.False(t, cr)
-
-		assertRecodedHistogramChunkOnAppend(t, c, hApp, ts+1, h2, UnknownCounterReset)
 	}
 
 	{ // New histogram that has a bucket missing.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 2},
@@ -344,32 +320,26 @@ func TestHistogramChunkAppendable(t *testing.T) {
 		h2.Sum = 21
 		h2.PositiveBuckets = []int64{6, -3, -1, 2, 1, -4} // counts: 6, 3, 2, 4, 5, 1 (total 21)
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Equal(t, 0, len(posInterjections))
 		require.Equal(t, 0, len(negInterjections))
 		require.False(t, ok) // Need to cut a new chunk.
 		require.True(t, cr)
-
-		assertNewHistogramChunkOnAppend(t, c, hApp, ts+1, h2, CounterReset)
 	}
 
 	{ // New histogram that has a counter reset while buckets are same.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.Sum = 23
 		h2.PositiveBuckets = []int64{6, -4, 1, -1, 2, 1, -4} // counts: 6, 2, 3, 2, 4, 5, 1 (total 23)
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Equal(t, 0, len(posInterjections))
 		require.Equal(t, 0, len(negInterjections))
 		require.False(t, ok) // Need to cut a new chunk.
 		require.True(t, cr)
-
-		assertNewHistogramChunkOnAppend(t, c, hApp, ts+1, h2, CounterReset)
 	}
 
 	{ // New histogram that has a counter reset while new buckets were added.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 3},
@@ -383,13 +353,11 @@ func TestHistogramChunkAppendable(t *testing.T) {
 		// so the new histogram should have new counts >= these per-bucket counts, e.g.:
 		h2.PositiveBuckets = []int64{7, -2, -4, 2, -2, -1, 2, 3, 0, -5, 0} // 7 5 1 3 1 0 2 5 5 0 0 (total 29)
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Equal(t, 0, len(posInterjections))
 		require.Equal(t, 0, len(negInterjections))
 		require.False(t, ok) // Need to cut a new chunk.
 		require.True(t, cr)
-
-		assertNewHistogramChunkOnAppend(t, c, hApp, ts+1, h2, CounterReset)
 	}
 
 	{
@@ -397,7 +365,6 @@ func TestHistogramChunkAppendable(t *testing.T) {
 		// added before the first bucket and reset on first bucket.  (to
 		// catch the edge case where the new bucket should be forwarded
 		// ahead until first old bucket at start)
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: -3, Length: 2},
@@ -413,55 +380,14 @@ func TestHistogramChunkAppendable(t *testing.T) {
 		// so the new histogram should have new counts >= these per-bucket counts, e.g.:
 		h2.PositiveBuckets = []int64{1, 1, 3, -2, 0, -1, 2, 1, -4} // counts: 1, 2, 5, 3, 3, 2, 4, 5, 1 (total 26)
 
-		posInterjections, negInterjections, ok, cr := hApp.appendable(h2)
+		posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
 		require.Equal(t, 0, len(posInterjections))
 		require.Equal(t, 0, len(negInterjections))
 		require.False(t, ok) // Need to cut a new chunk.
 		require.True(t, cr)
-
-		assertNewHistogramChunkOnAppend(t, c, hApp, ts+1, h2, CounterReset)
 	}
 }
 
-func assertNewHistogramChunkOnAppend(t *testing.T, oldChunk Chunk, hApp *HistogramAppender, ts int64, h *histogram.Histogram, expectHeader CounterResetHeader) {
-	oldChunkBytes := oldChunk.Bytes()
-	newChunk, recoded, newAppender, err := hApp.AppendHistogram(nil, ts, h, false)
-	require.Equal(t, oldChunkBytes, oldChunk.Bytes()) // Sanity check that previous chunk is untouched.
-	require.NoError(t, err)
-	require.NotNil(t, newChunk)
-	require.False(t, recoded)
-	require.NotEqual(t, oldChunk, newChunk)
-	require.Equal(t, expectHeader, newChunk.(*HistogramChunk).GetCounterResetHeader())
-	require.NotNil(t, newAppender)
-	require.NotEqual(t, hApp, newAppender)
-	assertSampleCount(t, newChunk, 1, ValHistogram)
-}
-
-func assertRecodedHistogramChunkOnAppend(t *testing.T, prevChunk Chunk, hApp *HistogramAppender, ts int64, h *histogram.Histogram, expectHeader CounterResetHeader) {
-	prevChunkBytes := prevChunk.Bytes()
-	newChunk, recoded, newAppender, err := hApp.AppendHistogram(nil, ts, h, false)
-	require.Equal(t, prevChunkBytes, prevChunk.Bytes()) // Sanity check that previous chunk is untouched. This may change in the future if we implement in-place recoding.
-	require.NoError(t, err)
-	require.NotNil(t, newChunk)
-	require.True(t, recoded)
-	require.NotEqual(t, prevChunk, newChunk)
-	require.Equal(t, expectHeader, newChunk.(*HistogramChunk).GetCounterResetHeader())
-	require.NotNil(t, newAppender)
-	require.NotEqual(t, hApp, newAppender)
-	assertSampleCount(t, newChunk, 2, ValHistogram)
-}
-
-func assertSampleCount(t *testing.T, c Chunk, exp int64, vtype ValueType) {
-	count := int64(0)
-	it := c.Iterator(nil)
-	require.NoError(t, it.Err())
-	for it.Next() == vtype {
-		count++
-	}
-	require.NoError(t, it.Err())
-	require.Equal(t, exp, count)
-}
-
 func TestHistogramChunkAppendableWithEmptySpan(t *testing.T) {
 	h1 := &histogram.Histogram{
 		Schema:        0,
@@ -509,12 +435,11 @@ func TestHistogramChunkAppendableWithEmptySpan(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, 0, c.NumSamples())
 
-	_, _, _, err = app.AppendHistogram(nil, 1, h1, true)
-	require.NoError(t, err)
+	app.AppendHistogram(1, h1)
 	require.Equal(t, 1, c.NumSamples())
 	hApp, _ := app.(*HistogramAppender)
 
-	pI, nI, okToAppend, counterReset := hApp.appendable(h2)
+	pI, nI, okToAppend, counterReset := hApp.Appendable(h2)
 	require.Empty(t, pI)
 	require.Empty(t, nI)
 	require.True(t, okToAppend)
@@ -648,9 +573,11 @@ func TestAtFloatHistogram(t *testing.T) {
 	app, err := chk.Appender()
 	require.NoError(t, err)
 	for i := range input {
-		newc, _, _, err := app.AppendHistogram(nil, int64(i), &input[i], false)
-		require.NoError(t, err)
-		require.Nil(t, newc)
+		if i > 0 {
+			_, _, okToAppend, _ := app.(*HistogramAppender).Appendable(&input[i])
+			require.True(t, okToAppend, fmt.Sprintf("idx: %d", i))
+		}
+		app.AppendHistogram(int64(i), &input[i])
 	}
 	it := chk.Iterator(nil)
 	i := int64(0)
@@ -663,75 +590,51 @@ func TestAtFloatHistogram(t *testing.T) {
 }
 
 func TestHistogramChunkAppendableGauge(t *testing.T) {
-	setup := func() (Chunk, *HistogramAppender, int64, *histogram.Histogram) {
-		c := Chunk(NewHistogramChunk())
-
-		// Create fresh appender and add the first histogram.
-		app, err := c.Appender()
-		require.NoError(t, err)
-		require.Equal(t, 0, c.NumSamples())
-
-		ts := int64(1234567890)
-		h1 := &histogram.Histogram{
-			CounterResetHint: histogram.GaugeType,
-			Count:            5,
-			ZeroCount:        2,
-			Sum:              18.4,
-			ZeroThreshold:    1e-125,
-			Schema:           1,
-			PositiveSpans: []histogram.Span{
-				{Offset: 0, Length: 2},
-				{Offset: 2, Length: 1},
-				{Offset: 3, Length: 2},
-				{Offset: 3, Length: 1},
-				{Offset: 1, Length: 1},
-			},
-			PositiveBuckets: []int64{6, -3, 0, -1, 2, 1, -4}, // {6, 3, 3, 2, 4, 5, 1}
-		}
+	c := Chunk(NewHistogramChunk())
 
-		chk, _, app, err := app.AppendHistogram(nil, ts, h1.Copy(), false)
-		require.NoError(t, err)
-		require.Nil(t, chk)
-		require.Equal(t, 1, c.NumSamples())
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
+	// Create fresh appender and add the first histogram.
+	app, err := c.Appender()
+	require.NoError(t, err)
+	require.Equal(t, 0, c.NumSamples())
 
-		return c, app.(*HistogramAppender), ts, h1
+	ts := int64(1234567890)
+	h1 := &histogram.Histogram{
+		Count:         5,
+		ZeroCount:     2,
+		Sum:           18.4,
+		ZeroThreshold: 1e-125,
+		Schema:        1,
+		PositiveSpans: []histogram.Span{
+			{Offset: 0, Length: 2},
+			{Offset: 2, Length: 1},
+			{Offset: 3, Length: 2},
+			{Offset: 3, Length: 1},
+			{Offset: 1, Length: 1},
+		},
+		PositiveBuckets: []int64{6, -3, 0, -1, 2, 1, -4}, // {6, 3, 3, 2, 4, 5, 1}
 	}
 
+	app.AppendHistogram(ts, h1.Copy())
+	require.Equal(t, 1, c.NumSamples())
+	c.(*HistogramChunk).SetCounterResetHeader(GaugeType)
+
 	{ // Schema change.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.Schema++
-		_, _, _, _, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*HistogramAppender)
+		_, _, _, _, _, _, ok := hApp.AppendableGauge(h2)
 		require.False(t, ok)
-
-		newc, recoded, _, err := hApp.AppendHistogram(nil, ts+1, h2, false)
-		require.NoError(t, err)
-		require.NotNil(t, newc)
-		require.False(t, recoded)
-		require.NotEqual(t, c, newc)
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
-		require.Equal(t, GaugeType, newc.(*HistogramChunk).GetCounterResetHeader())
 	}
 
 	{ // Zero threshold change.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.ZeroThreshold += 0.1
-		_, _, _, _, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*HistogramAppender)
+		_, _, _, _, _, _, ok := hApp.AppendableGauge(h2)
 		require.False(t, ok)
-
-		newc, recoded, _, err := hApp.AppendHistogram(nil, ts+1, h2, false)
-		require.NoError(t, err)
-		require.NotNil(t, newc)
-		require.False(t, recoded)
-		require.NotEqual(t, c, newc)
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
-		require.Equal(t, GaugeType, newc.(*HistogramChunk).GetCounterResetHeader())
 	}
 
 	{ // New histogram that has more buckets.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 3},
@@ -744,22 +647,16 @@ func TestHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum = 30
 		h2.PositiveBuckets = []int64{7, -2, -4, 2, -2, -1, 2, 3, 0, -5, 1} // {7, 5, 1, 3, 1, 0, 2, 5, 5, 0, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*HistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Greater(t, len(pI), 0)
 		require.Len(t, nI, 0)
 		require.Len(t, pBackwardI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		newc, recoded, _, err := hApp.AppendHistogram(nil, ts+1, h2, false)
-		require.NoError(t, err)
-		require.NotNil(t, newc)
-		require.True(t, recoded)
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
 	}
 
 	{ // New histogram that has buckets missing.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 2},
@@ -772,22 +669,16 @@ func TestHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum--
 		h2.PositiveBuckets = []int64{6, -3, 0, -1, 3, -4} // {6, 3, 3, 2, 5, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*HistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Len(t, pI, 0)
 		require.Len(t, nI, 0)
 		require.Greater(t, len(pBackwardI), 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		newc, recoded, _, err := hApp.AppendHistogram(nil, ts+1, h2, false)
-		require.NoError(t, err)
-		require.Nil(t, newc)
-		require.False(t, recoded)
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
 	}
 
 	{ // New histogram that has a bucket missing and new buckets.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 2},
@@ -798,42 +689,30 @@ func TestHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum = 21
 		h2.PositiveBuckets = []int64{6, -3, -1, 2, 1, -4} // {6, 3, 2, 4, 5, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*HistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Greater(t, len(pI), 0)
 		require.Greater(t, len(pBackwardI), 0)
 		require.Len(t, nI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		newc, recoded, _, err := hApp.AppendHistogram(nil, ts+1, h2, false)
-		require.NoError(t, err)
-		require.NotNil(t, newc)
-		require.True(t, recoded)
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
 	}
 
 	{ // New histogram that has a counter reset while buckets are same.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.Sum = 23
 		h2.PositiveBuckets = []int64{6, -4, 1, -1, 2, 1, -4} // {6, 2, 3, 2, 4, 5, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*HistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Len(t, pI, 0)
 		require.Len(t, nI, 0)
 		require.Len(t, pBackwardI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		newc, recoded, _, err := hApp.AppendHistogram(nil, ts+1, h2, false)
-		require.NoError(t, err)
-		require.Nil(t, newc)
-		require.False(t, recoded)
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
 	}
 
 	{ // New histogram that has a counter reset while new buckets were added.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: 0, Length: 3},
@@ -844,24 +723,18 @@ func TestHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum = 29
 		h2.PositiveBuckets = []int64{7, -2, -4, 2, -2, -1, 2, 3, 0, -5, 0} // {7, 5, 1, 3, 1, 0, 2, 5, 5, 0, 0}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*HistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Greater(t, len(pI), 0)
 		require.Len(t, nI, 0)
 		require.Len(t, pBackwardI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		newc, recoded, _, err := hApp.AppendHistogram(nil, ts+1, h2, false)
-		require.NoError(t, err)
-		require.NotNil(t, newc)
-		require.True(t, recoded)
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
 	}
 
 	{
 		// New histogram that has a counter reset while new buckets were
 		// added before the first bucket and reset on first bucket.
-		c, hApp, ts, h1 := setup()
 		h2 := h1.Copy()
 		h2.PositiveSpans = []histogram.Span{
 			{Offset: -3, Length: 2},
@@ -874,17 +747,12 @@ func TestHistogramChunkAppendableGauge(t *testing.T) {
 		h2.Sum = 26
 		h2.PositiveBuckets = []int64{1, 1, 3, -2, 0, -1, 2, 1, -4} // {1, 2, 5, 3, 3, 2, 4, 5, 1}
 
-		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.appendableGauge(h2)
+		hApp, _ := app.(*HistogramAppender)
+		pI, nI, pBackwardI, nBackwardI, _, _, ok := hApp.AppendableGauge(h2)
 		require.Greater(t, len(pI), 0)
 		require.Len(t, nI, 0)
 		require.Len(t, pBackwardI, 0)
 		require.Len(t, nBackwardI, 0)
 		require.True(t, ok)
-
-		newc, recoded, _, err := hApp.AppendHistogram(nil, ts+1, h2, false)
-		require.NoError(t, err)
-		require.NotNil(t, newc)
-		require.True(t, recoded)
-		require.Equal(t, GaugeType, c.(*HistogramChunk).GetCounterResetHeader())
 	}
 }
diff --git a/tsdb/db_test.go b/tsdb/db_test.go
index 772fcf9d1..c746d5018 100644
--- a/tsdb/db_test.go
+++ b/tsdb/db_test.go
@@ -2713,20 +2713,14 @@ func TestDBQueryDoesntSeeAppendsAfterCreation(t *testing.T) {
 	require.Equal(t, map[string][]sample{`{foo="bar"}`: {{t: 0, f: 0}}}, seriesSet)
 }
 
-func assureChunkFromSamples(t *testing.T, samples []tsdbutil.Sample) chunks.Meta {
-	chks, err := tsdbutil.ChunkFromSamples(samples)
-	require.NoError(t, err)
-	return chks
-}
-
 // TestChunkWriter_ReadAfterWrite ensures that chunk segment are cut at the set segment size and
 // that the resulted segments includes the expected chunks data.
 func TestChunkWriter_ReadAfterWrite(t *testing.T) {
-	chk1 := assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 1, nil, nil}})
-	chk2 := assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 2, nil, nil}})
-	chk3 := assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 3, nil, nil}})
-	chk4 := assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 4, nil, nil}})
-	chk5 := assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 5, nil, nil}})
+	chk1 := tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 1, nil, nil}})
+	chk2 := tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 2, nil, nil}})
+	chk3 := tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 3, nil, nil}})
+	chk4 := tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 4, nil, nil}})
+	chk5 := tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 5, nil, nil}})
 	chunkSize := len(chk1.Chunk.Bytes()) + chunks.MaxChunkLengthFieldSize + chunks.ChunkEncodingSize + crc32.Size
 
 	tests := []struct {
@@ -2926,11 +2920,11 @@ func TestRangeForTimestamp(t *testing.T) {
 // Regression test for https://github.com/prometheus/prometheus/pull/6514.
 func TestChunkReader_ConcurrentReads(t *testing.T) {
 	chks := []chunks.Meta{
-		assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 1, nil, nil}}),
-		assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 2, nil, nil}}),
-		assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 3, nil, nil}}),
-		assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 4, nil, nil}}),
-		assureChunkFromSamples(t, []tsdbutil.Sample{sample{1, 5, nil, nil}}),
+		tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 1, nil, nil}}),
+		tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 2, nil, nil}}),
+		tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 3, nil, nil}}),
+		tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 4, nil, nil}}),
+		tsdbutil.ChunkFromSamples([]tsdbutil.Sample{sample{1, 5, nil, nil}}),
 	}
 
 	tempDir := t.TempDir()
diff --git a/tsdb/head_test.go b/tsdb/head_test.go
index 0a6eef66c..f9eb88ba1 100644
--- a/tsdb/head_test.go
+++ b/tsdb/head_test.go
@@ -3841,7 +3841,7 @@ func TestHistogramCounterResetHeader(t *testing.T) {
 			appendHistogram(h)
 			checkExpCounterResetHeader(chunkenc.CounterReset)
 
-			// Add 2 non-counter reset histogram chunks.
+			// Add 2 non-counter reset histograms.
 			for i := 0; i < 250; i++ {
 				appendHistogram(h)
 			}
diff --git a/tsdb/querier_test.go b/tsdb/querier_test.go
index a63041b53..e9dd3b75f 100644
--- a/tsdb/querier_test.go
+++ b/tsdb/querier_test.go
@@ -670,7 +670,7 @@ func createFakeReaderAndNotPopulatedChunks(s ...[]tsdbutil.Sample) (*fakeChunksR
 	chks := make([]chunks.Meta, 0, len(s))
 
 	for ref, samples := range s {
-		chk, _ := tsdbutil.ChunkFromSamples(samples)
+		chk := tsdbutil.ChunkFromSamples(samples)
 		f.chks[chunks.ChunkRef(ref)] = chk.Chunk
 
 		chks = append(chks, chunks.Meta{
@@ -713,7 +713,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 			chks: [][]tsdbutil.Sample{{}},
 
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{}),
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{}),
 			},
 		},
 		{
@@ -721,9 +721,9 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 			chks: [][]tsdbutil.Sample{{}, {}, {}},
 
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{}),
-				assureChunkFromSamples(t, []tsdbutil.Sample{}),
-				assureChunkFromSamples(t, []tsdbutil.Sample{}),
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{}),
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{}),
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{}),
 			},
 		},
 		{
@@ -736,7 +736,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{1, 2, nil, nil}, sample{2, 3, nil, nil}, sample{3, 5, nil, nil}, sample{6, 1, nil, nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 2, nil, nil}, sample{2, 3, nil, nil}, sample{3, 5, nil, nil}, sample{6, 1, nil, nil},
 				}),
 			},
@@ -752,10 +752,10 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{1, 2, nil, nil}, sample{2, 3, nil, nil}, sample{3, 5, nil, nil}, sample{6, 1, nil, nil}, sample{7, 89, nil, nil}, sample{9, 8, nil, nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 2, nil, nil}, sample{2, 3, nil, nil}, sample{3, 5, nil, nil}, sample{6, 1, nil, nil},
 				}),
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{7, 89, nil, nil}, sample{9, 8, nil, nil},
 				}),
 			},
@@ -772,13 +772,13 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{1, 2, nil, nil}, sample{2, 3, nil, nil}, sample{3, 5, nil, nil}, sample{6, 1, nil, nil}, sample{7, 89, nil, nil}, sample{9, 8, nil, nil}, sample{10, 22, nil, nil}, sample{203, 3493, nil, nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 2, nil, nil}, sample{2, 3, nil, nil}, sample{3, 5, nil, nil}, sample{6, 1, nil, nil},
 				}),
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{7, 89, nil, nil}, sample{9, 8, nil, nil},
 				}),
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{10, 22, nil, nil}, sample{203, 3493, nil, nil},
 				}),
 			},
@@ -845,10 +845,10 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{3, 5, nil, nil}, sample{6, 1, nil, nil}, sample{7, 89, nil, nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{3, 5, nil, nil}, sample{6, 1, nil, nil},
 				}),
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{7, 89, nil, nil},
 				}),
 			},
@@ -865,10 +865,10 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{1, 2, nil, nil}, sample{6, 1, nil, nil}, sample{7, 89, nil, nil}, sample{9, 8, nil, nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 2, nil, nil}, sample{6, 1, nil, nil},
 				}),
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{7, 89, nil, nil}, sample{9, 8, nil, nil},
 				}),
 			},
@@ -885,10 +885,10 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{1, 2, nil, nil}, sample{2, 3, nil, nil}, sample{3, 5, nil, nil}, sample{9, 8, nil, nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 2, nil, nil}, sample{2, 3, nil, nil}, sample{3, 5, nil, nil},
 				}),
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{9, 8, nil, nil},
 				}),
 			},
@@ -925,7 +925,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{6, 0, tsdbutil.SetHistogramNotCounterReset(tsdbutil.GenerateTestHistogram(6)), nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 0, tsdbutil.GenerateTestHistogram(1), nil},
 					sample{2, 0, tsdbutil.SetHistogramNotCounterReset(tsdbutil.GenerateTestHistogram(2)), nil},
 					sample{3, 0, tsdbutil.SetHistogramNotCounterReset(tsdbutil.GenerateTestHistogram(3)), nil},
@@ -950,7 +950,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{3, 0, tsdbutil.SetHistogramNotCounterReset(tsdbutil.GenerateTestHistogram(3)), nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 0, tsdbutil.GenerateTestHistogram(1), nil},
 					sample{2, 0, tsdbutil.SetHistogramNotCounterReset(tsdbutil.GenerateTestHistogram(2)), nil},
 					sample{3, 0, tsdbutil.SetHistogramNotCounterReset(tsdbutil.GenerateTestHistogram(3)), nil},
@@ -974,7 +974,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{6, 0, nil, tsdbutil.SetFloatHistogramNotCounterReset(tsdbutil.GenerateTestFloatHistogram(6))},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 0, nil, tsdbutil.GenerateTestFloatHistogram(1)},
 					sample{2, 0, nil, tsdbutil.SetFloatHistogramNotCounterReset(tsdbutil.GenerateTestFloatHistogram(2))},
 					sample{3, 0, nil, tsdbutil.SetFloatHistogramNotCounterReset(tsdbutil.GenerateTestFloatHistogram(3))},
@@ -999,7 +999,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{3, 0, nil, tsdbutil.SetFloatHistogramNotCounterReset(tsdbutil.GenerateTestFloatHistogram(3))},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 0, nil, tsdbutil.GenerateTestFloatHistogram(1)},
 					sample{2, 0, nil, tsdbutil.SetFloatHistogramNotCounterReset(tsdbutil.GenerateTestFloatHistogram(2))},
 					sample{3, 0, nil, tsdbutil.SetFloatHistogramNotCounterReset(tsdbutil.GenerateTestFloatHistogram(3))},
@@ -1023,7 +1023,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{6, 0, tsdbutil.GenerateTestGaugeHistogram(6), nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 0, tsdbutil.GenerateTestGaugeHistogram(1), nil},
 					sample{2, 0, tsdbutil.GenerateTestGaugeHistogram(2), nil},
 					sample{3, 0, tsdbutil.GenerateTestGaugeHistogram(3), nil},
@@ -1048,7 +1048,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{3, 0, tsdbutil.GenerateTestGaugeHistogram(3), nil},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 0, tsdbutil.GenerateTestGaugeHistogram(1), nil},
 					sample{2, 0, tsdbutil.GenerateTestGaugeHistogram(2), nil},
 					sample{3, 0, tsdbutil.GenerateTestGaugeHistogram(3), nil},
@@ -1072,7 +1072,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{6, 0, nil, tsdbutil.GenerateTestGaugeFloatHistogram(6)},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 0, nil, tsdbutil.GenerateTestGaugeFloatHistogram(1)},
 					sample{2, 0, nil, tsdbutil.GenerateTestGaugeFloatHistogram(2)},
 					sample{3, 0, nil, tsdbutil.GenerateTestGaugeFloatHistogram(3)},
@@ -1097,7 +1097,7 @@ func TestPopulateWithTombSeriesIterators(t *testing.T) {
 				sample{3, 0, nil, tsdbutil.GenerateTestGaugeFloatHistogram(3)},
 			},
 			expectedChks: []chunks.Meta{
-				assureChunkFromSamples(t, []tsdbutil.Sample{
+				tsdbutil.ChunkFromSamples([]tsdbutil.Sample{
 					sample{1, 0, nil, tsdbutil.GenerateTestGaugeFloatHistogram(1)},
 					sample{2, 0, nil, tsdbutil.GenerateTestGaugeFloatHistogram(2)},
 					sample{3, 0, nil, tsdbutil.GenerateTestGaugeFloatHistogram(3)},
diff --git a/web/api/v1/errors_test.go b/web/api/v1/errors_test.go
index afdd67337..4947afd81 100644
--- a/web/api/v1/errors_test.go
+++ b/web/api/v1/errors_test.go
@@ -134,8 +134,6 @@ func createPrometheusAPI(q storage.SampleAndChunkQueryable) *route.Router {
 		prometheus.DefaultGatherer,
 		nil,
 		nil,
-		false,
-		false,
 	)
 
 	promRouter := route.New().WithPrefix("/api/v1")