mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-10 15:44:05 -08:00
aa3513fc89
* consoles: exclude iowait and steal from CPU Utilisation 'iowait' and 'steal' indicate specific idle/wait states, which shouldn't be counted into CPU Utilisation. Also see https://github.com/prometheus-operator/kube-prometheus/pull/796 and https://github.com/kubernetes-monitoring/kubernetes-mixin/pull/667. Per the iostat man page: %idle Show the percentage of time that the CPU or CPUs were idle and the system did not have an outstanding disk I/O request. %iowait Show the percentage of time that the CPU or CPUs were idle during which the system had an outstanding disk I/O request. %steal Show the percentage of time spent in involuntary wait by the virtual CPU or CPUs while the hypervisor was servicing another virtual processor. Signed-off-by: Julian Wiedmann <jwi@linux.ibm.com> * tsdb: shrink txRing with smaller integers 4 billion active transactions ought to be enough for anyone. Signed-off-by: Bryan Boreham <bjboreham@gmail.com> * tsdb: create isolation transaction slice on demand When Prometheus restarts it creates every series read in from the WAL, but many of those series will be finished, and never receive any more samples. By defering allocation of the txRing slice to when it is first needed, we save 32 bytes per stale series. Signed-off-by: Bryan Boreham <bjboreham@gmail.com> * add cluster variable to Overview dashboard Signed-off-by: Erik Sommer <ersotech@posteo.de> * promql: simplify Native Histogram arithmetics Signed-off-by: Linas Medziunas <linas.medziunas@gmail.com> * Cut 2.49.0-rc.0 (#13270) * Cut 2.49.0-rc.0 Signed-off-by: bwplotka <bwplotka@gmail.com> * Removed the duplicate. Signed-off-by: bwplotka <bwplotka@gmail.com> --------- Signed-off-by: bwplotka <bwplotka@gmail.com> * Add unit protobuf parser Signed-off-by: Arianna Vespri <arianna.vespri@yahoo.it> * Go on adding protobuf parsing for unit Signed-off-by: Arianna Vespri <arianna.vespri@yahoo.it> * ui: create a reproduction for https://github.com/prometheus/prometheus/issues/13292 Signed-off-by: machine424 <ayoubmrini424@gmail.com> * Get conditional right Signed-off-by: Arianna Vespri <arianna.vespri@yahoo.it> * Get VM Scale Set NIC (#13283) Calling `*armnetwork.InterfacesClient.Get()` doesn't work for Scale Set VM NIC, because these use a different Resource ID format. Use `*armnetwork.InterfacesClient.GetVirtualMachineScaleSetNetworkInterface()` instead. This needs both the scale set name and the instance ID, so add an `InstanceID` field to the `virtualMachine` struct. `InstanceID` is empty for a VM that isn't a ScaleSetVM. Signed-off-by: Daniel Nicholls <daniel.nicholls@resdiary.com> * Cut v2.49.0-rc.1 Signed-off-by: bwplotka <bwplotka@gmail.com> * Delete debugging lines, amend error message for unit Signed-off-by: Arianna Vespri <arianna.vespri@yahoo.it> * Correct order in error message Signed-off-by: Arianna Vespri <arianna.vespri@yahoo.it> * Consider storage.ErrTooOldSample as non-retryable Signed-off-by: Daniel Kerbel <nmdanny@gmail.com> * scrape_test.go: Increase scrape interval in TestScrapeLoopCache to reduce potential flakiness Signed-off-by: machine424 <ayoubmrini424@gmail.com> * Avoid creating string for suffix, consider counters without _total suffix Signed-off-by: Arianna Vespri <arianna.vespri@yahoo.it> * build(deps): bump github.com/prometheus/client_golang Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.17.0 to 1.18.0. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.17.0...v1.18.0) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * build(deps): bump actions/setup-node from 3.8.1 to 4.0.1 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.8.1 to 4.0.1. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](5e21ff4d9b...b39b52d121
) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * scripts: sort file list in embed directive Otherwise the resulting string depends on find, which afaict depends on the underlying filesystem. A stable file list make it easier to detect UI changes in downstreams that need to track UI assets. Signed-off-by: Jan Fajerski <jfajersk@redhat.com> * Fix DataTableProps['data'] for resultType string Signed-off-by: Kevin Mingtarja <kevin.mingtarja@gmail.com> * Fix handling of scalar and string in isHeatmapData Signed-off-by: Kevin Mingtarja <kevin.mingtarja@gmail.com> * build(deps): bump github.com/influxdata/influxdb Bumps [github.com/influxdata/influxdb](https://github.com/influxdata/influxdb) from 1.11.2 to 1.11.4. - [Release notes](https://github.com/influxdata/influxdb/releases) - [Commits](https://github.com/influxdata/influxdb/compare/v1.11.2...v1.11.4) --- updated-dependencies: - dependency-name: github.com/influxdata/influxdb dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * build(deps): bump github.com/prometheus/prometheus Bumps [github.com/prometheus/prometheus](https://github.com/prometheus/prometheus) from 0.48.0 to 0.48.1. - [Release notes](https://github.com/prometheus/prometheus/releases) - [Changelog](https://github.com/prometheus/prometheus/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/prometheus/compare/v0.48.0...v0.48.1) --- updated-dependencies: - dependency-name: github.com/prometheus/prometheus dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * Bump client_golang to v1.18.0 (#13373) Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Drop old inmemory samples (#13002) * Drop old inmemory samples Co-authored-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Avoid copying timeseries when the feature is disabled Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Run gofmt Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Clarify docs Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Add more logging info Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Remove loggers Signed-off-by: Marc Tuduri <marctc@protonmail.com> * optimize function and add tests Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Simplify filter Signed-off-by: Marc Tuduri <marctc@protonmail.com> * rename var Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Update help info from metrics Signed-off-by: Marc Tuduri <marctc@protonmail.com> * use metrics to keep track of drop elements during buildWriteRequest Signed-off-by: Marc Tuduri <marctc@protonmail.com> * rename var in tests Signed-off-by: Marc Tuduri <marctc@protonmail.com> * pass time.Now as parameter Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Change buildwriterequest during retries Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Revert "Remove loggers" This reverts commit 54f91dfcae20488944162335ab4ad8be459df1ab. Signed-off-by: Marc Tuduri <marctc@protonmail.com> * use log level debug for loggers Signed-off-by: Marc Tuduri <marctc@protonmail.com> * Fix linter Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Remove noisy debug-level logs; add 'reason' label to drop metrics Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Remove accidentally committed files Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Propagate logger to buildWriteRequest to log dropped data Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Fix docs comment Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Make drop reason more specific Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Remove unnecessary pass of logger Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Use snake_case for reason label Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> * Fix dropped samples metric Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> --------- Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> Signed-off-by: Marc Tuduri <marctc@protonmail.com> Signed-off-by: Paschalis Tsilias <tpaschalis@users.noreply.github.com> Co-authored-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> Co-authored-by: Paschalis Tsilias <tpaschalis@users.noreply.github.com> * fix(discovery): allow requireUpdate util to timeout in discovery/file/file_test.go. The loop ran indefinitely if the condition isn't met. Before, each iteration created a new timer channel which was always outpaced by the other timer channel with smaller duration. minor detail: There was a memory leak: resources of the ~10 previous timers were constantly kept. With the fix, we may keep the resources of one timer around for defaultWait but this isn't worth the changes to make it right. Signed-off-by: machine424 <ayoubmrini424@gmail.com> * Merge pull request #13371 from kevinmingtarja/fix-isHeatmapData ui: fix handling of scalar and string in isHeatmapData * tsdb/{index,compact}: allow using custom postings encoding format (#13242) * tsdb/{index,compact}: allow using custom postings encoding format We would like to experiment with a different postings encoding format in Thanos so in this change I am proposing adding another argument to `NewWriter` which would allow users to change the format if needed. Also, wire the leveled compactor so that it would be possible to change the format there too. Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> * tsdb/compact: use a struct for leveled compactor options As discussed on Slack, let's use a struct for the options in leveled compactor. Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> * tsdb: make changes after Bryan's review - Make changes less intrusive - Turn the postings encoder type into a function - Add NewWriterWithEncoder() Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> --------- Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> * Cut 2.49.0-rc.2 Signed-off-by: bwplotka <bwplotka@gmail.com> * build(deps): bump actions/setup-go from 3.5.0 to 5.0.0 in /scripts (#13362) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.5.0 to 5.0.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](6edd4406fa...0c52d547c9
) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump github/codeql-action from 2.22.8 to 3.22.12 (#13358) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.22.8 to 3.22.12. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](407ffafae6...012739e508
) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * put @nexucis has a release shepherd (#13383) Signed-off-by: Augustin Husson <augustin.husson@amadeus.com> * Add analyze histograms command to promtool (#12331) Add `query analyze` command to promtool This command analyzes the buckets of classic and native histograms, based on data queried from the Prometheus query API, i.e. it doesn't require direct access to the TSDB files. Signed-off-by: Jeanette Tan <jeanette.tan@grafana.com> --------- Signed-off-by: Jeanette Tan <jeanette.tan@grafana.com> * included instance in all necessary descriptions Signed-off-by: Erik Sommer <ersotech@posteo.de> * tsdb/compact: fix passing merge func Fixing a very small logical problem I've introduced :(. Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> * tsdb: add enable overlapping compaction This functionality is needed in downstream projects because they have a separate component that does compaction. Upstreaming7c8e9a2a76/tsdb/compact.go (L323-L325)
. Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> * Cut 2.49.0 Signed-off-by: bwplotka <bwplotka@gmail.com> * promtool: allow setting multiple matchers to "promtool tsdb dump" command. (#13296) Conditions are ANDed inside the same matcher but matchers are ORed Including unit tests for "promtool tsdb dump". Refactor some matchers scraping utils. Signed-off-by: machine424 <ayoubmrini424@gmail.com> * Fixed changelog Signed-off-by: bwplotka <bwplotka@gmail.com> * tsdb/main: wire "EnableOverlappingCompaction" to tsdb.Options (#13398) This added the https://github.com/prometheus/prometheus/pull/13393 "EnableOverlappingCompaction" parameter to the compactor code but not to the tsdb.Options. I forgot about that. Add it to `tsdb.Options` too and set it to `true` in Prometheus. Copy/paste the description from https://github.com/prometheus/prometheus/pull/13393#issuecomment-1891787986 Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> * Issue #13268: fix quality value in accept header Signed-off-by: Kumar Kalpadiptya Roy <kalpadiptya.roy@outlook.com> * Cut 2.49.1 with scrape q= bugfix. Signed-off-by: bwplotka <bwplotka@gmail.com> * Cut 2.49.1 web package. Signed-off-by: bwplotka <bwplotka@gmail.com> * Restore more efficient version of NewPossibleNonCounterInfo annotation (#13022) Restore more efficient version of NewPossibleNonCounterInfo annotation Signed-off-by: Jeanette Tan <jeanette.tan@grafana.com> --------- Signed-off-by: Jeanette Tan <jeanette.tan@grafana.com> * Fix regressions introduced by #13242 Signed-off-by: Marco Pracucci <marco@pracucci.com> * fix slice copy in 1.20 (#13389) The slices package is added to the standard library in Go 1.21; we need to import from the exp area to maintain compatibility with Go 1.20. Signed-off-by: tyltr <tylitianrui@126.com> * Docs: Query Basics: link to rate (#10538) Co-authored-by: Julien Pivotto <roidelapluie@o11y.eu> * chore(kubernetes): check preconditions earlier and avoid unnecessary checks or iterations Signed-off-by: machine424 <ayoubmrini424@gmail.com> * Examples: link to `rate` for new users (#10535) * Examples: link to `rate` for new users Signed-off-by: Ted Robertson 10043369+tredondo@users.noreply.github.com Co-authored-by: Bryan Boreham <bjboreham@gmail.com> * promql: use natural sort in sort_by_label and sort_by_label_desc (#13411) These functions are intended for humans, as robots can already sort the results however they please. Humans like things sorted "naturally": * https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/ A similar thing has been done to Grafana, which is also used by humans: * https://github.com/grafana/grafana/pull/78024 * https://github.com/grafana/grafana/pull/78494 Signed-off-by: Ivan Babrou <github@ivan.computer> * TestLabelValuesWithMatchers: Add test case Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * remove obsolete build tag Signed-off-by: tyltr <tylitianrui@126.com> * Upgrade some golang dependencies for resty 2.11 Signed-off-by: Israel Blancas <iblancasa@gmail.com> * Native Histograms: support `native_histogram_min_bucket_factor` in scrape_config (#13222) Native Histograms: support native_histogram_min_bucket_factor in scrape_config --------- Signed-off-by: Ziqi Zhao <zhaoziqi9146@gmail.com> Signed-off-by: Björn Rabenstein <github@rabenste.in> Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> Co-authored-by: Björn Rabenstein <github@rabenste.in> * Add warnings for histogramRate applied with isCounter not matching counter/gauge histogram (#13392) Add warnings for histogramRate applied with isCounter not matching counter/gauge histogram --------- Signed-off-by: Jeanette Tan <jeanette.tan@grafana.com> * Minor fixes to otlp vendor update script Signed-off-by: Goutham <gouthamve@gmail.com> * build(deps): bump github.com/hetznercloud/hcloud-go/v2 Bumps [github.com/hetznercloud/hcloud-go/v2](https://github.com/hetznercloud/hcloud-go) from 2.4.0 to 2.6.0. - [Release notes](https://github.com/hetznercloud/hcloud-go/releases) - [Changelog](https://github.com/hetznercloud/hcloud-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/hetznercloud/hcloud-go/compare/v2.4.0...v2.6.0) --- updated-dependencies: - dependency-name: github.com/hetznercloud/hcloud-go/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * Enhanced visibility for `promtool test rules` with JSON colored formatting (#13342) * Added diff flag for unit test to improvise readability & debugging Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * Removed blank spaces Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * Fixed linting error Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * Added cli flags to documentation Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * Revert unrrelated linting fixes Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * Fixed review suggestions Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * Cleanup Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * Updated flag description Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * Updated flag description Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> --------- Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> * storage: skip merging when no remote storage configured Prometheus is hard-coded to use a fanout storage between TSDB and a remote storage which by default is empty. This change detects the empty storage and skips merging between result sets, which would make `Select()` sort results. Bottom line: we skip a sort unless there really is some remote storage configured. Signed-off-by: Bryan Boreham <bjboreham@gmail.com> * Remove csmarchbanks from remote write owners (#13432) I have not had the time to keep up with remote write and have no plans to work on it in the near future so I am withdrawing my maintainership of that part of the codebase. I continue to focus on client_python. Signed-off-by: Chris Marchbanks <csmarchbanks@gmail.com> * add more context cancellation check at evaluation time Signed-off-by: Ben Ye <benye@amazon.com> * Optimize label values with matchers by taking shortcuts (#13426) Don't calculate postings beforehand: we may not need them. If all matchers are for the requested label, we can just filter its values. Also, if there are no values at all, no need to run any kind of logic. Also add more labelValuesWithMatchers benchmarks Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com> * Add automatic memory limit handling Enable automatic detection of memory limits and configure GOMEMLIMIT to match. * Also includes a flag to allow controlling the reserved ratio. Signed-off-by: SuperQ <superq@gmail.com> * Update OSSF badge link (#13433) Provide a more user friendly interface Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com> * SD Managers taking over responsibility for registration of debug metrics (#13375) SD Managers take over responsibility for SD metrics registration --------- Signed-off-by: Paulin Todev <paulin.todev@gmail.com> Signed-off-by: Björn Rabenstein <github@rabenste.in> Co-authored-by: Björn Rabenstein <github@rabenste.in> * Optimize histogram iterators (#13340) Optimize histogram iterators Histogram iterators allocate new objects in the AtHistogram and AtFloatHistogram methods, which makes calculating rates over long ranges expensive. In #13215 we allowed an existing object to be reused when converting an integer histogram to a float histogram. This commit follows the same idea and allows injecting an existing object in the AtHistogram and AtFloatHistogram methods. When the injected value is nil, iterators allocate new histograms, otherwise they populate and return the injected object. The commit also adds a CopyTo method to Histogram and FloatHistogram which is used in the BufferedIterator to overwrite items in the ring instead of making new copies. Note that a specialized HPoint pool is needed for all of this to work (`matrixSelectorHPool`). --------- Signed-off-by: Filip Petkovski <filip.petkovsky@gmail.com> Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> * doc: Mark `mad_over_time` as experimental (#13440) We forgot to do that in https://github.com/prometheus/prometheus/pull/13059 Signed-off-by: beorn7 <beorn@grafana.com> * Change metric label for Puppetdb from 'http' to 'puppetdb' Signed-off-by: Paulin Todev <paulin.todev@gmail.com> * mirror metrics.proto change & generate code Signed-off-by: Ziqi Zhao <zhaoziqi9146@gmail.com> * TestHeadLabelValuesWithMatchers: Add test case (#13414) Add test case to TestHeadLabelValuesWithMatchers, while fixing a couple of typos in other test cases. Also enclosing some implicit sub-tests in a `t.Run` call to make them explicitly sub-tests. Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * update all go dependencies (#13438) Signed-off-by: Augustin Husson <husson.augustin@gmail.com> * build(deps): bump the k8s-io group with 2 updates (#13454) Bumps the k8s-io group with 2 updates: [k8s.io/api](https://github.com/kubernetes/api) and [k8s.io/client-go](https://github.com/kubernetes/client-go). Updates `k8s.io/api` from 0.28.4 to 0.29.1 - [Commits](https://github.com/kubernetes/api/compare/v0.28.4...v0.29.1) Updates `k8s.io/client-go` from 0.28.4 to 0.29.1 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.28.4...v0.29.1) --- updated-dependencies: - dependency-name: k8s.io/api dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io - dependency-name: k8s.io/client-go dependency-type: direct:production update-type: version-update:semver-minor dependency-group: k8s-io ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump the go-opentelemetry-io group with 1 update (#13453) Bumps the go-opentelemetry-io group with 1 update: [go.opentelemetry.io/collector/semconv](https://github.com/open-telemetry/opentelemetry-collector). Updates `go.opentelemetry.io/collector/semconv` from 0.92.0 to 0.93.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-collector/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-collector/blob/main/CHANGELOG-API.md) - [Commits](https://github.com/open-telemetry/opentelemetry-collector/compare/v0.92.0...v0.93.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/collector/semconv dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-opentelemetry-io ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump actions/upload-artifact from 3.1.3 to 4.0.0 (#13355) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.1.3 to 4.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](a8a3f3ad30...c7d193f32e
) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump bufbuild/buf-push-action (#13357) Bumps [bufbuild/buf-push-action](https://github.com/bufbuild/buf-push-action) from 342fc4cdcf29115a01cf12a2c6dd6aac68dc51e1 to a654ff18effe4641ebea4a4ce242c49800728459. - [Release notes](https://github.com/bufbuild/buf-push-action/releases) - [Commits](342fc4cdcf...a654ff18ef
) --- updated-dependencies: - dependency-name: bufbuild/buf-push-action dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Labels: Add DropMetricName function, used in PromQL (#13446) This function is called very frequently when executing PromQL functions, and we can do it much more efficiently inside Labels. In the common case that `__name__` comes first in the labels, we simply re-point to start at the next label, which is nearly free. `DropMetricName` is now so cheap I removed the cache - benchmarks show everything still goes faster. Signed-off-by: Bryan Boreham <bjboreham@gmail.com> * tsdb: simplify internal series delete function (#13261) Lifting an optimisation from Agent code, `seriesHashmap.del` can use the unique series reference, doesn't need to check Labels. Also streamline the logic for deleting from `unique` and `conflicts` maps, and add some comments to help the next person. Signed-off-by: Bryan Boreham <bjboreham@gmail.com> * otlptranslator/update-copy.sh: Fix sed command lines Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rollback k8s.io requirements (#13462) Rollback k8s.io Go modules to v0.28.6 to avoid forcing upgrade of Go to 1.21. This allows us to keep compatibility with the currently supported upstream Go releases. Signed-off-by: SuperQ <superq@gmail.com> * Make update-copy.sh work for both OSX and GNU sed Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Name @beorn7 and @krajorama as maintainers for native histograms I have been the de-facto maintainer for native histograms from the beginning. So let's put this into MAINTAINERS.md. In addition, I hereby proposose George Krajcsovits AKA Krajo as a co-maintainer. He has contributed a lot of native histogram code, but more importantly, he has contributed substantially to reviewing other contributors' native histogram code, up to a point where I was merely rubberstamping the PRs he had already reviewed. I'm confident that he is ready to to be granted commit rights as outlined in the "Maintainers" section of the governance: https://prometheus.io/governance/#maintainers According to the same section of the governance, I will announce the proposed change on the developers mailing list and will give some time for lazy consensus before merging this PR. Signed-off-by: beorn7 <beorn@grafana.com> * ui/fix: correct url handling for stacked graphs (#13460) Signed-off-by: Yury Moladau <yurymolodov@gmail.com> * tsdb: use cheaper Mutex on series Mutex is 8 bytes; RWMutex is 24 bytes and much more complicated. Since `RLock` is only used in two places, `UpdateMetadata` and `Delete`, neither of which are hotspots, we should use the cheaper one. Signed-off-by: Bryan Boreham <bjboreham@gmail.com> * Fix last_over_time for native histograms The last_over_time retains a histogram sample without making a copy. This sample is now coming from the buffered iterator used for windowing functions, and can be reused for reading subsequent samples as the iterator progresses. I would propose copying the sample in the last_over_time function, similar to how it is done for rate, sum_over_time and others. Signed-off-by: Filip Petkovski <filip.petkovsky@gmail.com> * Implementation NOTE: Rebased from main after refactor in #13014 Signed-off-by: Danny Kopping <danny.kopping@grafana.com> * Add feature flag Signed-off-by: Danny Kopping <danny.kopping@grafana.com> * Refactor concurrency control Signed-off-by: Danny Kopping <danny.kopping@grafana.com> * Optimising dependencies/dependents funcs to not produce new slices each request Signed-off-by: Danny Kopping <danny.kopping@grafana.com> * Refactoring Signed-off-by: Danny Kopping <danny.kopping@grafana.com> * Rename flag Signed-off-by: Danny Kopping <danny.kopping@grafana.com> * Refactoring for performance, and to allow controller to be overridden Signed-off-by: Danny Kopping <danny.kopping@grafana.com> * Block until all rules, both sync & async, have completed evaluating Updated & added tests Review feedback nits Return empty map if not indeterminate Use highWatermark to track inflight requests counter Appease the linter Clarify feature flag Signed-off-by: Danny Kopping <danny.kopping@grafana.com> * Fix typo in CLI flag description Signed-off-by: Marco Pracucci <marco@pracucci.com> * Fixed auto-generated doc Signed-off-by: Marco Pracucci <marco@pracucci.com> * Improve doc Signed-off-by: Marco Pracucci <marco@pracucci.com> * Simplify the design to update concurrency controller once the rule evaluation has done Signed-off-by: Marco Pracucci <marco@pracucci.com> * Add more test cases to TestDependenciesEdgeCases Signed-off-by: Marco Pracucci <marco@pracucci.com> * Added more test cases to TestDependenciesEdgeCases Signed-off-by: Marco Pracucci <marco@pracucci.com> * Improved RuleConcurrencyController interface doc Signed-off-by: Marco Pracucci <marco@pracucci.com> * Introduced sequentialRuleEvalController Signed-off-by: Marco Pracucci <marco@pracucci.com> * Remove superfluous nil check in Group.metrics Signed-off-by: Marco Pracucci <marco@pracucci.com> * api: Serialize discovered and target labels into JSON directly (#13469) Converted maps into labels.Labels to avoid a lot of copying of data which leads to very high memory consumption while opening the /service-discovery endpoint in the Prometheus UI Signed-off-by: Leegin <114397475+Leegin-darknight@users.noreply.github.com> * api: Serialize discovered labels into JSON directly in dropped targets (#13484) Converted maps into labels.Labels to avoid a lot of copying of data which leads to very high memory consumption while opening the /service-discovery endpoint in the Prometheus UI Signed-off-by: Leegin <114397475+Leegin-darknight@users.noreply.github.com> * Add ShardedPostings() support to TSDB (#10421) This PR is a reference implementation of the proposal described in #10420. In addition to what described in #10420, in this PR I've introduced labels.StableHash(). The idea is to offer an hashing function which doesn't change over time, and that's used by query sharding in order to get a stable behaviour over time. The implementation of labels.StableHash() is the hashing function used by Prometheus before stringlabels, and what's used by Grafana Mimir for query sharding (because built before stringlabels was a thing). Follow up work As mentioned in #10420, if this PR is accepted I'm also open to upload another foundamental piece used by Grafana Mimir query sharding to accelerate the query execution: an optional, configurable and fast in-memory cache for the series hashes. Signed-off-by: Marco Pracucci <marco@pracucci.com> * storage/remote: document why two benchmarks are skipped One was silently doing nothing; one was doing something but the work didn't go up linearly with iteration count. Signed-off-by: Bryan Boreham <bjboreham@gmail.com> * Pod status changes not discovered by Kube Endpoints SD (#13337) * fix(discovery/kubernetes/endpoints): react to changes on Pods because some modifications can occur on them without triggering an update on the related Endpoints (The Pod phase changing from Pending to Running e.g.). --------- Signed-off-by: machine424 <ayoubmrini424@gmail.com> Co-authored-by: Guillermo Sanchez Gavier <gsanchez@newrelic.com> * Small improvements, add const, remove copypasta (#8106) Signed-off-by: Mikhail Fesenko <proggga@gmail.com> Signed-off-by: Jesus Vazquez <jesusvzpg@gmail.com> * Proposal to improve FPointSlice and HPointSlice allocation. (#13448) * Reusing points slice from previous series when the slice is under utilized * Adding comments on the bench test Signed-off-by: Alan Protasio <alanprot@gmail.com> * lint Signed-off-by: Nicolás Pazos <npazosmendez@gmail.com> * go mod tidy Signed-off-by: Nicolás Pazos <npazosmendez@gmail.com> --------- Signed-off-by: Julian Wiedmann <jwi@linux.ibm.com> Signed-off-by: Bryan Boreham <bjboreham@gmail.com> Signed-off-by: Erik Sommer <ersotech@posteo.de> Signed-off-by: Linas Medziunas <linas.medziunas@gmail.com> Signed-off-by: bwplotka <bwplotka@gmail.com> Signed-off-by: Arianna Vespri <arianna.vespri@yahoo.it> Signed-off-by: machine424 <ayoubmrini424@gmail.com> Signed-off-by: Daniel Nicholls <daniel.nicholls@resdiary.com> Signed-off-by: Daniel Kerbel <nmdanny@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Jan Fajerski <jfajersk@redhat.com> Signed-off-by: Kevin Mingtarja <kevin.mingtarja@gmail.com> Signed-off-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> Signed-off-by: Marc Tuduri <marctc@protonmail.com> Signed-off-by: Paschalis Tsilias <tpaschalis@users.noreply.github.com> Signed-off-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> Signed-off-by: Augustin Husson <augustin.husson@amadeus.com> Signed-off-by: Jeanette Tan <jeanette.tan@grafana.com> Signed-off-by: Bartlomiej Plotka <bwplotka@gmail.com> Signed-off-by: Kumar Kalpadiptya Roy <kalpadiptya.roy@outlook.com> Signed-off-by: Marco Pracucci <marco@pracucci.com> Signed-off-by: tyltr <tylitianrui@126.com> Signed-off-by: Ted Robertson 10043369+tredondo@users.noreply.github.com Signed-off-by: Ivan Babrou <github@ivan.computer> Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> Signed-off-by: Israel Blancas <iblancasa@gmail.com> Signed-off-by: Ziqi Zhao <zhaoziqi9146@gmail.com> Signed-off-by: Björn Rabenstein <github@rabenste.in> Signed-off-by: Goutham <gouthamve@gmail.com> Signed-off-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> Signed-off-by: Chris Marchbanks <csmarchbanks@gmail.com> Signed-off-by: Ben Ye <benye@amazon.com> Signed-off-by: Oleg Zaytsev <mail@olegzaytsev.com> Signed-off-by: SuperQ <superq@gmail.com> Signed-off-by: Ben Kochie <superq@gmail.com> Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com> Signed-off-by: Paulin Todev <paulin.todev@gmail.com> Signed-off-by: Filip Petkovski <filip.petkovsky@gmail.com> Signed-off-by: beorn7 <beorn@grafana.com> Signed-off-by: Augustin Husson <husson.augustin@gmail.com> Signed-off-by: Yury Moladau <yurymolodov@gmail.com> Signed-off-by: Danny Kopping <danny.kopping@grafana.com> Signed-off-by: Leegin <114397475+Leegin-darknight@users.noreply.github.com> Signed-off-by: Mikhail Fesenko <proggga@gmail.com> Signed-off-by: Jesus Vazquez <jesusvzpg@gmail.com> Signed-off-by: Alan Protasio <alanprot@gmail.com> Signed-off-by: Nicolás Pazos <npazosmendez@gmail.com> Co-authored-by: Julian Wiedmann <jwi@linux.ibm.com> Co-authored-by: Bryan Boreham <bjboreham@gmail.com> Co-authored-by: Erik Sommer <ersotech@posteo.de> Co-authored-by: Linas Medziunas <linas.medziunas@gmail.com> Co-authored-by: Bartlomiej Plotka <bwplotka@gmail.com> Co-authored-by: Arianna Vespri <arianna.vespri@yahoo.it> Co-authored-by: machine424 <ayoubmrini424@gmail.com> Co-authored-by: daniel-resdiary <109083091+daniel-resdiary@users.noreply.github.com> Co-authored-by: Daniel Kerbel <nmdanny@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jan Fajerski <jfajersk@redhat.com> Co-authored-by: Kevin Mingtarja <kevin.mingtarja@gmail.com> Co-authored-by: Paschalis Tsilias <tpaschalis@users.noreply.github.com> Co-authored-by: Marc Tudurí <marctc@protonmail.com> Co-authored-by: Paschalis Tsilias <paschalis.tsilias@grafana.com> Co-authored-by: Giedrius Statkevičius <giedrius.statkevicius@vinted.com> Co-authored-by: Augustin Husson <husson.augustin@gmail.com> Co-authored-by: Björn Rabenstein <beorn@grafana.com> Co-authored-by: zenador <zenador@users.noreply.github.com> Co-authored-by: gotjosh <josue.abreu@gmail.com> Co-authored-by: Ben Kochie <superq@gmail.com> Co-authored-by: Kumar Kalpadiptya Roy <kalpadiptya.roy@outlook.com> Co-authored-by: Marco Pracucci <marco@pracucci.com> Co-authored-by: tyltr <tylitianrui@126.com> Co-authored-by: Ted Robertson <10043369+tredondo@users.noreply.github.com> Co-authored-by: Julien Pivotto <roidelapluie@o11y.eu> Co-authored-by: Matthias Loibl <mail@matthiasloibl.com> Co-authored-by: Ivan Babrou <github@ivan.computer> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Israel Blancas <iblancasa@gmail.com> Co-authored-by: Ziqi Zhao <zhaoziqi9146@gmail.com> Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> Co-authored-by: Björn Rabenstein <github@rabenste.in> Co-authored-by: Goutham <gouthamve@gmail.com> Co-authored-by: Rewanth Tammana <22347290+rewanthtammana@users.noreply.github.com> Co-authored-by: Chris Marchbanks <csmarchbanks@gmail.com> Co-authored-by: Ben Ye <benye@amazon.com> Co-authored-by: Oleg Zaytsev <mail@olegzaytsev.com> Co-authored-by: Matthieu MOREL <matthieu.morel35@gmail.com> Co-authored-by: Paulin Todev <paulin.todev@gmail.com> Co-authored-by: Filip Petkovski <filip.petkovsky@gmail.com> Co-authored-by: Yury Molodov <yurymolodov@gmail.com> Co-authored-by: Danny Kopping <danny.kopping@grafana.com> Co-authored-by: Leegin <114397475+Leegin-darknight@users.noreply.github.com> Co-authored-by: Guillermo Sanchez Gavier <gsanchez@newrelic.com> Co-authored-by: Mikhail Fesenko <proggga@gmail.com> Co-authored-by: Alan Protasio <alanprot@gmail.com>
2256 lines
68 KiB
Go
2256 lines
68 KiB
Go
// Copyright 2017 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package tsdb implements a time series storage for float64 sample data.
|
|
package tsdb
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-kit/log"
|
|
"github.com/go-kit/log/level"
|
|
"github.com/oklog/ulid"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"go.uber.org/atomic"
|
|
"golang.org/x/exp/slices"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/prometheus/prometheus/config"
|
|
"github.com/prometheus/prometheus/model/labels"
|
|
"github.com/prometheus/prometheus/storage"
|
|
"github.com/prometheus/prometheus/tsdb/chunkenc"
|
|
"github.com/prometheus/prometheus/tsdb/chunks"
|
|
tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
|
|
"github.com/prometheus/prometheus/tsdb/fileutil"
|
|
_ "github.com/prometheus/prometheus/tsdb/goversion" // Load the package into main to make sure minium Go version is met.
|
|
"github.com/prometheus/prometheus/tsdb/tsdbutil"
|
|
"github.com/prometheus/prometheus/tsdb/wlog"
|
|
)
|
|
|
|
const (
|
|
// Default duration of a block in milliseconds.
|
|
DefaultBlockDuration = int64(2 * time.Hour / time.Millisecond)
|
|
|
|
// Block dir suffixes to make deletion and creation operations atomic.
|
|
// We decided to do suffixes instead of creating meta.json as last (or delete as first) one,
|
|
// because in error case you still can recover meta.json from the block content within local TSDB dir.
|
|
// TODO(bwplotka): TSDB can end up with various .tmp files (e.g meta.json.tmp, WAL or segment tmp file. Think
|
|
// about removing those too on start to save space. Currently only blocks tmp dirs are removed.
|
|
tmpForDeletionBlockDirSuffix = ".tmp-for-deletion"
|
|
tmpForCreationBlockDirSuffix = ".tmp-for-creation"
|
|
// Pre-2.21 tmp dir suffix, used in clean-up functions.
|
|
tmpLegacy = ".tmp"
|
|
)
|
|
|
|
// ErrNotReady is returned if the underlying storage is not ready yet.
|
|
var ErrNotReady = errors.New("TSDB not ready")
|
|
|
|
// DefaultOptions used for the DB. They are reasonable for setups using
|
|
// millisecond precision timestamps.
|
|
func DefaultOptions() *Options {
|
|
return &Options{
|
|
WALSegmentSize: wlog.DefaultSegmentSize,
|
|
MaxBlockChunkSegmentSize: chunks.DefaultChunkSegmentSize,
|
|
RetentionDuration: int64(15 * 24 * time.Hour / time.Millisecond),
|
|
MinBlockDuration: DefaultBlockDuration,
|
|
MaxBlockDuration: DefaultBlockDuration,
|
|
NoLockfile: false,
|
|
SamplesPerChunk: DefaultSamplesPerChunk,
|
|
WALCompression: wlog.CompressionNone,
|
|
StripeSize: DefaultStripeSize,
|
|
HeadChunksWriteBufferSize: chunks.DefaultWriteBufferSize,
|
|
IsolationDisabled: defaultIsolationDisabled,
|
|
HeadChunksWriteQueueSize: chunks.DefaultWriteQueueSize,
|
|
OutOfOrderCapMax: DefaultOutOfOrderCapMax,
|
|
EnableOverlappingCompaction: true,
|
|
EnableSharding: false,
|
|
}
|
|
}
|
|
|
|
// Options of the DB storage.
|
|
type Options struct {
|
|
// Segments (wal files) max size.
|
|
// WALSegmentSize = 0, segment size is default size.
|
|
// WALSegmentSize > 0, segment size is WALSegmentSize.
|
|
// WALSegmentSize < 0, wal is disabled.
|
|
WALSegmentSize int
|
|
|
|
// MaxBlockChunkSegmentSize is the max size of block chunk segment files.
|
|
// MaxBlockChunkSegmentSize = 0, chunk segment size is default size.
|
|
// MaxBlockChunkSegmentSize > 0, chunk segment size is MaxBlockChunkSegmentSize.
|
|
MaxBlockChunkSegmentSize int64
|
|
|
|
// Duration of persisted data to keep.
|
|
// Unit agnostic as long as unit is consistent with MinBlockDuration and MaxBlockDuration.
|
|
// Typically it is in milliseconds.
|
|
RetentionDuration int64
|
|
|
|
// Maximum number of bytes in blocks to be retained.
|
|
// 0 or less means disabled.
|
|
// NOTE: For proper storage calculations need to consider
|
|
// the size of the WAL folder which is not added when calculating
|
|
// the current size of the database.
|
|
MaxBytes int64
|
|
|
|
// NoLockfile disables creation and consideration of a lock file.
|
|
NoLockfile bool
|
|
|
|
// WALCompression configures the compression type to use on records in the WAL.
|
|
WALCompression wlog.CompressionType
|
|
|
|
// Maximum number of CPUs that can simultaneously processes WAL replay.
|
|
// If it is <=0, then GOMAXPROCS is used.
|
|
WALReplayConcurrency int
|
|
|
|
// StripeSize is the size in entries of the series hash map. Reducing the size will save memory but impact performance.
|
|
StripeSize int
|
|
|
|
// The timestamp range of head blocks after which they get persisted.
|
|
// It's the minimum duration of any persisted block.
|
|
// Unit agnostic as long as unit is consistent with RetentionDuration and MaxBlockDuration.
|
|
// Typically it is in milliseconds.
|
|
MinBlockDuration int64
|
|
|
|
// The maximum timestamp range of compacted blocks.
|
|
// Unit agnostic as long as unit is consistent with MinBlockDuration and RetentionDuration.
|
|
// Typically it is in milliseconds.
|
|
MaxBlockDuration int64
|
|
|
|
// HeadChunksWriteBufferSize configures the write buffer size used by the head chunks mapper.
|
|
HeadChunksWriteBufferSize int
|
|
|
|
// HeadChunksWriteQueueSize configures the size of the chunk write queue used in the head chunks mapper.
|
|
HeadChunksWriteQueueSize int
|
|
|
|
// SamplesPerChunk configures the target number of samples per chunk.
|
|
SamplesPerChunk int
|
|
|
|
// SeriesLifecycleCallback specifies a list of callbacks that will be called during a lifecycle of a series.
|
|
// It is always a no-op in Prometheus and mainly meant for external users who import TSDB.
|
|
SeriesLifecycleCallback SeriesLifecycleCallback
|
|
|
|
// BlocksToDelete is a function which returns the blocks which can be deleted.
|
|
// It is always the default time and size based retention in Prometheus and
|
|
// mainly meant for external users who import TSDB.
|
|
BlocksToDelete BlocksToDeleteFunc
|
|
|
|
// Enables the in memory exemplar storage.
|
|
EnableExemplarStorage bool
|
|
|
|
// Enables the snapshot of in-memory chunks on shutdown. This makes restarts faster.
|
|
EnableMemorySnapshotOnShutdown bool
|
|
|
|
// MaxExemplars sets the size, in # of exemplars stored, of the single circular buffer used to store exemplars in memory.
|
|
// See tsdb/exemplar.go, specifically the CircularExemplarStorage struct and it's constructor NewCircularExemplarStorage.
|
|
MaxExemplars int64
|
|
|
|
// Disables isolation between reads and in-flight appends.
|
|
IsolationDisabled bool
|
|
|
|
// EnableNativeHistograms enables the ingestion of native histograms.
|
|
EnableNativeHistograms bool
|
|
|
|
// OutOfOrderTimeWindow specifies how much out of order is allowed, if any.
|
|
// This can change during run-time, so this value from here should only be used
|
|
// while initialising.
|
|
OutOfOrderTimeWindow int64
|
|
|
|
// OutOfOrderCapMax is maximum capacity for OOO chunks (in samples).
|
|
// If it is <=0, the default value is assumed.
|
|
OutOfOrderCapMax int64
|
|
|
|
// Compaction of overlapping blocks are allowed if EnableOverlappingCompaction is true.
|
|
// This is an optional flag for overlapping blocks.
|
|
// The reason why this flag exists is because there are various users of the TSDB
|
|
// that do not want vertical compaction happening on ingest time. Instead,
|
|
// they'd rather keep overlapping blocks and let another component do the overlapping compaction later.
|
|
// For Prometheus, this will always be true.
|
|
EnableOverlappingCompaction bool
|
|
|
|
// EnableSharding enables query sharding support in TSDB.
|
|
EnableSharding bool
|
|
}
|
|
|
|
type BlocksToDeleteFunc func(blocks []*Block) map[ulid.ULID]struct{}
|
|
|
|
// DB handles reads and writes of time series falling into
|
|
// a hashed partition of a seriedb.
|
|
type DB struct {
|
|
dir string
|
|
locker *tsdbutil.DirLocker
|
|
|
|
logger log.Logger
|
|
metrics *dbMetrics
|
|
opts *Options
|
|
chunkPool chunkenc.Pool
|
|
compactor Compactor
|
|
blocksToDelete BlocksToDeleteFunc
|
|
|
|
// Mutex for that must be held when modifying the general block layout or lastGarbageCollectedMmapRef.
|
|
mtx sync.RWMutex
|
|
blocks []*Block
|
|
|
|
// The last OOO chunk that was compacted and written to disk. New queriers must not read chunks less
|
|
// than or equal to this reference, as these chunks could be garbage collected at any time.
|
|
lastGarbageCollectedMmapRef chunks.ChunkDiskMapperRef
|
|
|
|
head *Head
|
|
|
|
compactc chan struct{}
|
|
donec chan struct{}
|
|
stopc chan struct{}
|
|
|
|
// cmtx ensures that compactions and deletions don't run simultaneously.
|
|
cmtx sync.Mutex
|
|
|
|
// autoCompactMtx ensures that no compaction gets triggered while
|
|
// changing the autoCompact var.
|
|
autoCompactMtx sync.Mutex
|
|
autoCompact bool
|
|
|
|
// Cancel a running compaction when a shutdown is initiated.
|
|
compactCancel context.CancelFunc
|
|
|
|
// oooWasEnabled is true if out of order support was enabled at least one time
|
|
// during the time TSDB was up. In which case we need to keep supporting
|
|
// out-of-order compaction and vertical queries.
|
|
oooWasEnabled atomic.Bool
|
|
|
|
writeNotified wlog.WriteNotified
|
|
|
|
registerer prometheus.Registerer
|
|
}
|
|
|
|
type dbMetrics struct {
|
|
loadedBlocks prometheus.GaugeFunc
|
|
symbolTableSize prometheus.GaugeFunc
|
|
reloads prometheus.Counter
|
|
reloadsFailed prometheus.Counter
|
|
compactionsFailed prometheus.Counter
|
|
compactionsTriggered prometheus.Counter
|
|
compactionsSkipped prometheus.Counter
|
|
sizeRetentionCount prometheus.Counter
|
|
timeRetentionCount prometheus.Counter
|
|
startTime prometheus.GaugeFunc
|
|
tombCleanTimer prometheus.Histogram
|
|
blocksBytes prometheus.Gauge
|
|
maxBytes prometheus.Gauge
|
|
retentionDuration prometheus.Gauge
|
|
}
|
|
|
|
func newDBMetrics(db *DB, r prometheus.Registerer) *dbMetrics {
|
|
m := &dbMetrics{}
|
|
|
|
m.loadedBlocks = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
|
Name: "prometheus_tsdb_blocks_loaded",
|
|
Help: "Number of currently loaded data blocks",
|
|
}, func() float64 {
|
|
db.mtx.RLock()
|
|
defer db.mtx.RUnlock()
|
|
return float64(len(db.blocks))
|
|
})
|
|
m.symbolTableSize = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
|
Name: "prometheus_tsdb_symbol_table_size_bytes",
|
|
Help: "Size of symbol table in memory for loaded blocks",
|
|
}, func() float64 {
|
|
db.mtx.RLock()
|
|
blocks := db.blocks
|
|
db.mtx.RUnlock()
|
|
symTblSize := uint64(0)
|
|
for _, b := range blocks {
|
|
symTblSize += b.GetSymbolTableSize()
|
|
}
|
|
return float64(symTblSize)
|
|
})
|
|
m.reloads = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "prometheus_tsdb_reloads_total",
|
|
Help: "Number of times the database reloaded block data from disk.",
|
|
})
|
|
m.reloadsFailed = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "prometheus_tsdb_reloads_failures_total",
|
|
Help: "Number of times the database failed to reloadBlocks block data from disk.",
|
|
})
|
|
m.compactionsTriggered = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "prometheus_tsdb_compactions_triggered_total",
|
|
Help: "Total number of triggered compactions for the partition.",
|
|
})
|
|
m.compactionsFailed = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "prometheus_tsdb_compactions_failed_total",
|
|
Help: "Total number of compactions that failed for the partition.",
|
|
})
|
|
m.timeRetentionCount = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "prometheus_tsdb_time_retentions_total",
|
|
Help: "The number of times that blocks were deleted because the maximum time limit was exceeded.",
|
|
})
|
|
m.compactionsSkipped = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "prometheus_tsdb_compactions_skipped_total",
|
|
Help: "Total number of skipped compactions due to disabled auto compaction.",
|
|
})
|
|
m.startTime = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
|
Name: "prometheus_tsdb_lowest_timestamp",
|
|
Help: "Lowest timestamp value stored in the database. The unit is decided by the library consumer.",
|
|
}, func() float64 {
|
|
db.mtx.RLock()
|
|
defer db.mtx.RUnlock()
|
|
if len(db.blocks) == 0 {
|
|
return float64(db.head.MinTime())
|
|
}
|
|
return float64(db.blocks[0].meta.MinTime)
|
|
})
|
|
m.tombCleanTimer = prometheus.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "prometheus_tsdb_tombstone_cleanup_seconds",
|
|
Help: "The time taken to recompact blocks to remove tombstones.",
|
|
})
|
|
m.blocksBytes = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "prometheus_tsdb_storage_blocks_bytes",
|
|
Help: "The number of bytes that are currently used for local storage by all blocks.",
|
|
})
|
|
m.maxBytes = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "prometheus_tsdb_retention_limit_bytes",
|
|
Help: "Max number of bytes to be retained in the tsdb blocks, configured 0 means disabled",
|
|
})
|
|
m.retentionDuration = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "prometheus_tsdb_retention_limit_seconds",
|
|
Help: "How long to retain samples in storage.",
|
|
})
|
|
m.sizeRetentionCount = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "prometheus_tsdb_size_retentions_total",
|
|
Help: "The number of times that blocks were deleted because the maximum number of bytes was exceeded.",
|
|
})
|
|
|
|
if r != nil {
|
|
r.MustRegister(
|
|
m.loadedBlocks,
|
|
m.symbolTableSize,
|
|
m.reloads,
|
|
m.reloadsFailed,
|
|
m.compactionsFailed,
|
|
m.compactionsTriggered,
|
|
m.compactionsSkipped,
|
|
m.sizeRetentionCount,
|
|
m.timeRetentionCount,
|
|
m.startTime,
|
|
m.tombCleanTimer,
|
|
m.blocksBytes,
|
|
m.maxBytes,
|
|
m.retentionDuration,
|
|
)
|
|
}
|
|
return m
|
|
}
|
|
|
|
// DBStats contains statistics about the DB separated by component (eg. head).
|
|
// They are available before the DB has finished initializing.
|
|
type DBStats struct {
|
|
Head *HeadStats
|
|
}
|
|
|
|
// NewDBStats returns a new DBStats object initialized using the
|
|
// new function from each component.
|
|
func NewDBStats() *DBStats {
|
|
return &DBStats{
|
|
Head: NewHeadStats(),
|
|
}
|
|
}
|
|
|
|
// ErrClosed is returned when the db is closed.
|
|
var ErrClosed = errors.New("db already closed")
|
|
|
|
// DBReadOnly provides APIs for read only operations on a database.
|
|
// Current implementation doesn't support concurrency so
|
|
// all API calls should happen in the same go routine.
|
|
type DBReadOnly struct {
|
|
logger log.Logger
|
|
dir string
|
|
closers []io.Closer
|
|
closed chan struct{}
|
|
}
|
|
|
|
// OpenDBReadOnly opens DB in the given directory for read only operations.
|
|
func OpenDBReadOnly(dir string, l log.Logger) (*DBReadOnly, error) {
|
|
if _, err := os.Stat(dir); err != nil {
|
|
return nil, fmt.Errorf("opening the db dir: %w", err)
|
|
}
|
|
|
|
if l == nil {
|
|
l = log.NewNopLogger()
|
|
}
|
|
|
|
return &DBReadOnly{
|
|
logger: l,
|
|
dir: dir,
|
|
closed: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
// FlushWAL creates a new block containing all data that's currently in the memory buffer/WAL.
|
|
// Samples that are in existing blocks will not be written to the new block.
|
|
// Note that if the read only database is running concurrently with a
|
|
// writable database then writing the WAL to the database directory can race.
|
|
func (db *DBReadOnly) FlushWAL(dir string) (returnErr error) {
|
|
blockReaders, err := db.Blocks()
|
|
if err != nil {
|
|
return fmt.Errorf("read blocks: %w", err)
|
|
}
|
|
maxBlockTime := int64(math.MinInt64)
|
|
if len(blockReaders) > 0 {
|
|
maxBlockTime = blockReaders[len(blockReaders)-1].Meta().MaxTime
|
|
}
|
|
w, err := wlog.Open(db.logger, filepath.Join(db.dir, "wal"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var wbl *wlog.WL
|
|
wblDir := filepath.Join(db.dir, wlog.WblDirName)
|
|
if _, err := os.Stat(wblDir); !os.IsNotExist(err) {
|
|
wbl, err = wlog.Open(db.logger, wblDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
opts := DefaultHeadOptions()
|
|
opts.ChunkDirRoot = db.dir
|
|
head, err := NewHead(nil, db.logger, w, wbl, opts, NewHeadStats())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
errs := tsdb_errors.NewMulti(returnErr)
|
|
if err := head.Close(); err != nil {
|
|
errs.Add(fmt.Errorf("closing Head: %w", err))
|
|
}
|
|
returnErr = errs.Err()
|
|
}()
|
|
// Set the min valid time for the ingested wal samples
|
|
// to be no lower than the maxt of the last block.
|
|
if err := head.Init(maxBlockTime); err != nil {
|
|
return fmt.Errorf("read WAL: %w", err)
|
|
}
|
|
mint := head.MinTime()
|
|
maxt := head.MaxTime()
|
|
rh := NewRangeHead(head, mint, maxt)
|
|
compactor, err := NewLeveledCompactor(
|
|
context.Background(),
|
|
nil,
|
|
db.logger,
|
|
ExponentialBlockRanges(DefaultOptions().MinBlockDuration, 3, 5),
|
|
chunkenc.NewPool(), nil,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("create leveled compactor: %w", err)
|
|
}
|
|
// Add +1 millisecond to block maxt because block intervals are half-open: [b.MinTime, b.MaxTime).
|
|
// Because of this block intervals are always +1 than the total samples it includes.
|
|
_, err = compactor.Write(dir, rh, mint, maxt+1, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("writing WAL: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (db *DBReadOnly) loadDataAsQueryable(maxt int64) (storage.SampleAndChunkQueryable, error) {
|
|
select {
|
|
case <-db.closed:
|
|
return nil, ErrClosed
|
|
default:
|
|
}
|
|
blockReaders, err := db.Blocks()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blocks := make([]*Block, len(blockReaders))
|
|
for i, b := range blockReaders {
|
|
b, ok := b.(*Block)
|
|
if !ok {
|
|
return nil, errors.New("unable to convert a read only block to a normal block")
|
|
}
|
|
blocks[i] = b
|
|
}
|
|
|
|
opts := DefaultHeadOptions()
|
|
opts.ChunkDirRoot = db.dir
|
|
head, err := NewHead(nil, db.logger, nil, nil, opts, NewHeadStats())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
maxBlockTime := int64(math.MinInt64)
|
|
if len(blocks) > 0 {
|
|
maxBlockTime = blocks[len(blocks)-1].Meta().MaxTime
|
|
}
|
|
|
|
// Also add the WAL if the current blocks don't cover the requests time range.
|
|
if maxBlockTime <= maxt {
|
|
if err := head.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
w, err := wlog.Open(db.logger, filepath.Join(db.dir, "wal"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var wbl *wlog.WL
|
|
wblDir := filepath.Join(db.dir, wlog.WblDirName)
|
|
if _, err := os.Stat(wblDir); !os.IsNotExist(err) {
|
|
wbl, err = wlog.Open(db.logger, wblDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
opts := DefaultHeadOptions()
|
|
opts.ChunkDirRoot = db.dir
|
|
head, err = NewHead(nil, db.logger, w, wbl, opts, NewHeadStats())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Set the min valid time for the ingested wal samples
|
|
// to be no lower than the maxt of the last block.
|
|
if err := head.Init(maxBlockTime); err != nil {
|
|
return nil, fmt.Errorf("read WAL: %w", err)
|
|
}
|
|
// Set the wal to nil to disable all wal operations.
|
|
// This is mainly to avoid blocking when closing the head.
|
|
head.wal = nil
|
|
}
|
|
|
|
db.closers = append(db.closers, head)
|
|
return &DB{
|
|
dir: db.dir,
|
|
logger: db.logger,
|
|
blocks: blocks,
|
|
head: head,
|
|
}, nil
|
|
}
|
|
|
|
// Querier loads the blocks and wal and returns a new querier over the data partition for the given time range.
|
|
// Current implementation doesn't support multiple Queriers.
|
|
func (db *DBReadOnly) Querier(mint, maxt int64) (storage.Querier, error) {
|
|
q, err := db.loadDataAsQueryable(maxt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return q.Querier(mint, maxt)
|
|
}
|
|
|
|
// ChunkQuerier loads blocks and the wal and returns a new chunk querier over the data partition for the given time range.
|
|
// Current implementation doesn't support multiple ChunkQueriers.
|
|
func (db *DBReadOnly) ChunkQuerier(mint, maxt int64) (storage.ChunkQuerier, error) {
|
|
q, err := db.loadDataAsQueryable(maxt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return q.ChunkQuerier(mint, maxt)
|
|
}
|
|
|
|
// Blocks returns a slice of block readers for persisted blocks.
|
|
func (db *DBReadOnly) Blocks() ([]BlockReader, error) {
|
|
select {
|
|
case <-db.closed:
|
|
return nil, ErrClosed
|
|
default:
|
|
}
|
|
loadable, corrupted, err := openBlocks(db.logger, db.dir, nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Corrupted blocks that have been superseded by a loadable block can be safely ignored.
|
|
for _, block := range loadable {
|
|
for _, b := range block.Meta().Compaction.Parents {
|
|
delete(corrupted, b.ULID)
|
|
}
|
|
}
|
|
if len(corrupted) > 0 {
|
|
for _, b := range loadable {
|
|
if err := b.Close(); err != nil {
|
|
level.Warn(db.logger).Log("msg", "Closing block failed", "err", err, "block", b)
|
|
}
|
|
}
|
|
errs := tsdb_errors.NewMulti()
|
|
for ulid, err := range corrupted {
|
|
if err != nil {
|
|
errs.Add(fmt.Errorf("corrupted block %s: %w", ulid.String(), err))
|
|
}
|
|
}
|
|
return nil, errs.Err()
|
|
}
|
|
|
|
if len(loadable) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
slices.SortFunc(loadable, func(a, b *Block) int {
|
|
switch {
|
|
case a.Meta().MinTime < b.Meta().MinTime:
|
|
return -1
|
|
case a.Meta().MinTime > b.Meta().MinTime:
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
})
|
|
|
|
blockMetas := make([]BlockMeta, 0, len(loadable))
|
|
for _, b := range loadable {
|
|
blockMetas = append(blockMetas, b.Meta())
|
|
}
|
|
if overlaps := OverlappingBlocks(blockMetas); len(overlaps) > 0 {
|
|
level.Warn(db.logger).Log("msg", "Overlapping blocks found during opening", "detail", overlaps.String())
|
|
}
|
|
|
|
// Close all previously open readers and add the new ones to the cache.
|
|
for _, closer := range db.closers {
|
|
closer.Close()
|
|
}
|
|
|
|
blockClosers := make([]io.Closer, len(loadable))
|
|
blockReaders := make([]BlockReader, len(loadable))
|
|
for i, b := range loadable {
|
|
blockClosers[i] = b
|
|
blockReaders[i] = b
|
|
}
|
|
db.closers = blockClosers
|
|
|
|
return blockReaders, nil
|
|
}
|
|
|
|
// LastBlockID returns the BlockID of latest block.
|
|
func (db *DBReadOnly) LastBlockID() (string, error) {
|
|
entries, err := os.ReadDir(db.dir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
max := uint64(0)
|
|
|
|
lastBlockID := ""
|
|
|
|
for _, e := range entries {
|
|
// Check if dir is a block dir or not.
|
|
dirName := e.Name()
|
|
ulidObj, err := ulid.ParseStrict(dirName)
|
|
if err != nil {
|
|
continue // Not a block dir.
|
|
}
|
|
timestamp := ulidObj.Time()
|
|
if timestamp > max {
|
|
max = timestamp
|
|
lastBlockID = dirName
|
|
}
|
|
}
|
|
|
|
if lastBlockID == "" {
|
|
return "", errors.New("no blocks found")
|
|
}
|
|
|
|
return lastBlockID, nil
|
|
}
|
|
|
|
// Block returns a block reader by given block id.
|
|
func (db *DBReadOnly) Block(blockID string) (BlockReader, error) {
|
|
select {
|
|
case <-db.closed:
|
|
return nil, ErrClosed
|
|
default:
|
|
}
|
|
|
|
_, err := os.Stat(filepath.Join(db.dir, blockID))
|
|
if os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("invalid block ID %s", blockID)
|
|
}
|
|
|
|
block, err := OpenBlock(db.logger, filepath.Join(db.dir, blockID), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db.closers = append(db.closers, block)
|
|
|
|
return block, nil
|
|
}
|
|
|
|
// Close all block readers.
|
|
func (db *DBReadOnly) Close() error {
|
|
select {
|
|
case <-db.closed:
|
|
return ErrClosed
|
|
default:
|
|
}
|
|
close(db.closed)
|
|
|
|
return tsdb_errors.CloseAll(db.closers)
|
|
}
|
|
|
|
// Open returns a new DB in the given directory. If options are empty, DefaultOptions will be used.
|
|
func Open(dir string, l log.Logger, r prometheus.Registerer, opts *Options, stats *DBStats) (db *DB, err error) {
|
|
var rngs []int64
|
|
opts, rngs = validateOpts(opts, nil)
|
|
|
|
return open(dir, l, r, opts, rngs, stats)
|
|
}
|
|
|
|
func validateOpts(opts *Options, rngs []int64) (*Options, []int64) {
|
|
if opts == nil {
|
|
opts = DefaultOptions()
|
|
}
|
|
if opts.StripeSize <= 0 {
|
|
opts.StripeSize = DefaultStripeSize
|
|
}
|
|
if opts.HeadChunksWriteBufferSize <= 0 {
|
|
opts.HeadChunksWriteBufferSize = chunks.DefaultWriteBufferSize
|
|
}
|
|
if opts.HeadChunksWriteQueueSize < 0 {
|
|
opts.HeadChunksWriteQueueSize = chunks.DefaultWriteQueueSize
|
|
}
|
|
if opts.SamplesPerChunk <= 0 {
|
|
opts.SamplesPerChunk = DefaultSamplesPerChunk
|
|
}
|
|
if opts.MaxBlockChunkSegmentSize <= 0 {
|
|
opts.MaxBlockChunkSegmentSize = chunks.DefaultChunkSegmentSize
|
|
}
|
|
if opts.MinBlockDuration <= 0 {
|
|
opts.MinBlockDuration = DefaultBlockDuration
|
|
}
|
|
if opts.MinBlockDuration > opts.MaxBlockDuration {
|
|
opts.MaxBlockDuration = opts.MinBlockDuration
|
|
}
|
|
if opts.OutOfOrderCapMax <= 0 {
|
|
opts.OutOfOrderCapMax = DefaultOutOfOrderCapMax
|
|
}
|
|
if opts.OutOfOrderTimeWindow < 0 {
|
|
opts.OutOfOrderTimeWindow = 0
|
|
}
|
|
|
|
if len(rngs) == 0 {
|
|
// Start with smallest block duration and create exponential buckets until the exceed the
|
|
// configured maximum block duration.
|
|
rngs = ExponentialBlockRanges(opts.MinBlockDuration, 10, 3)
|
|
}
|
|
return opts, rngs
|
|
}
|
|
|
|
// open returns a new DB in the given directory.
|
|
// It initializes the lockfile, WAL, compactor, and Head (by replaying the WAL), and runs the database.
|
|
// It is not safe to open more than one DB in the same directory.
|
|
func open(dir string, l log.Logger, r prometheus.Registerer, opts *Options, rngs []int64, stats *DBStats) (_ *DB, returnedErr error) {
|
|
if err := os.MkdirAll(dir, 0o777); err != nil {
|
|
return nil, err
|
|
}
|
|
if l == nil {
|
|
l = log.NewNopLogger()
|
|
}
|
|
if stats == nil {
|
|
stats = NewDBStats()
|
|
}
|
|
|
|
for i, v := range rngs {
|
|
if v > opts.MaxBlockDuration {
|
|
rngs = rngs[:i]
|
|
break
|
|
}
|
|
}
|
|
|
|
// Fixup bad format written by Prometheus 2.1.
|
|
if err := repairBadIndexVersion(l, dir); err != nil {
|
|
return nil, fmt.Errorf("repair bad index version: %w", err)
|
|
}
|
|
|
|
walDir := filepath.Join(dir, "wal")
|
|
wblDir := filepath.Join(dir, wlog.WblDirName)
|
|
|
|
// Migrate old WAL if one exists.
|
|
if err := MigrateWAL(l, walDir); err != nil {
|
|
return nil, fmt.Errorf("migrate WAL: %w", err)
|
|
}
|
|
for _, tmpDir := range []string{walDir, dir} {
|
|
// Remove tmp dirs.
|
|
if err := removeBestEffortTmpDirs(l, tmpDir); err != nil {
|
|
return nil, fmt.Errorf("remove tmp dirs: %w", err)
|
|
}
|
|
}
|
|
|
|
db := &DB{
|
|
dir: dir,
|
|
logger: l,
|
|
opts: opts,
|
|
compactc: make(chan struct{}, 1),
|
|
donec: make(chan struct{}),
|
|
stopc: make(chan struct{}),
|
|
autoCompact: true,
|
|
chunkPool: chunkenc.NewPool(),
|
|
blocksToDelete: opts.BlocksToDelete,
|
|
registerer: r,
|
|
}
|
|
defer func() {
|
|
// Close files if startup fails somewhere.
|
|
if returnedErr == nil {
|
|
return
|
|
}
|
|
|
|
close(db.donec) // DB is never run if it was an error, so close this channel here.
|
|
errs := tsdb_errors.NewMulti(returnedErr)
|
|
if err := db.Close(); err != nil {
|
|
errs.Add(fmt.Errorf("close DB after failed startup: %w", err))
|
|
}
|
|
returnedErr = errs.Err()
|
|
}()
|
|
|
|
if db.blocksToDelete == nil {
|
|
db.blocksToDelete = DefaultBlocksToDelete(db)
|
|
}
|
|
|
|
var err error
|
|
db.locker, err = tsdbutil.NewDirLocker(dir, "tsdb", db.logger, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !opts.NoLockfile {
|
|
if err := db.locker.Lock(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
db.compactor, err = NewLeveledCompactorWithOptions(ctx, r, l, rngs, db.chunkPool, LeveledCompactorOptions{
|
|
MaxBlockChunkSegmentSize: opts.MaxBlockChunkSegmentSize,
|
|
EnableOverlappingCompaction: opts.EnableOverlappingCompaction,
|
|
})
|
|
if err != nil {
|
|
cancel()
|
|
return nil, fmt.Errorf("create leveled compactor: %w", err)
|
|
}
|
|
db.compactCancel = cancel
|
|
|
|
var wal, wbl *wlog.WL
|
|
segmentSize := wlog.DefaultSegmentSize
|
|
// Wal is enabled.
|
|
if opts.WALSegmentSize >= 0 {
|
|
// Wal is set to a custom size.
|
|
if opts.WALSegmentSize > 0 {
|
|
segmentSize = opts.WALSegmentSize
|
|
}
|
|
wal, err = wlog.NewSize(l, r, walDir, segmentSize, opts.WALCompression)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Check if there is a WBL on disk, in which case we should replay that data.
|
|
wblSize, err := fileutil.DirSize(wblDir)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
if opts.OutOfOrderTimeWindow > 0 || wblSize > 0 {
|
|
wbl, err = wlog.NewSize(l, r, wblDir, segmentSize, opts.WALCompression)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
db.oooWasEnabled.Store(opts.OutOfOrderTimeWindow > 0)
|
|
headOpts := DefaultHeadOptions()
|
|
headOpts.ChunkRange = rngs[0]
|
|
headOpts.ChunkDirRoot = dir
|
|
headOpts.ChunkPool = db.chunkPool
|
|
headOpts.ChunkWriteBufferSize = opts.HeadChunksWriteBufferSize
|
|
headOpts.ChunkWriteQueueSize = opts.HeadChunksWriteQueueSize
|
|
headOpts.SamplesPerChunk = opts.SamplesPerChunk
|
|
headOpts.StripeSize = opts.StripeSize
|
|
headOpts.SeriesCallback = opts.SeriesLifecycleCallback
|
|
headOpts.EnableExemplarStorage = opts.EnableExemplarStorage
|
|
headOpts.MaxExemplars.Store(opts.MaxExemplars)
|
|
headOpts.EnableMemorySnapshotOnShutdown = opts.EnableMemorySnapshotOnShutdown
|
|
headOpts.EnableNativeHistograms.Store(opts.EnableNativeHistograms)
|
|
headOpts.OutOfOrderTimeWindow.Store(opts.OutOfOrderTimeWindow)
|
|
headOpts.OutOfOrderCapMax.Store(opts.OutOfOrderCapMax)
|
|
headOpts.EnableSharding = opts.EnableSharding
|
|
if opts.WALReplayConcurrency > 0 {
|
|
headOpts.WALReplayConcurrency = opts.WALReplayConcurrency
|
|
}
|
|
if opts.IsolationDisabled {
|
|
// We only override this flag if isolation is disabled at DB level. We use the default otherwise.
|
|
headOpts.IsolationDisabled = opts.IsolationDisabled
|
|
}
|
|
db.head, err = NewHead(r, l, wal, wbl, headOpts, stats.Head)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db.head.writeNotified = db.writeNotified
|
|
|
|
// Register metrics after assigning the head block.
|
|
db.metrics = newDBMetrics(db, r)
|
|
maxBytes := opts.MaxBytes
|
|
if maxBytes < 0 {
|
|
maxBytes = 0
|
|
}
|
|
db.metrics.maxBytes.Set(float64(maxBytes))
|
|
db.metrics.retentionDuration.Set((time.Duration(opts.RetentionDuration) * time.Millisecond).Seconds())
|
|
|
|
if err := db.reload(); err != nil {
|
|
return nil, err
|
|
}
|
|
// Set the min valid time for the ingested samples
|
|
// to be no lower than the maxt of the last block.
|
|
minValidTime := int64(math.MinInt64)
|
|
// We do not consider blocks created from out-of-order samples for Head's minValidTime
|
|
// since minValidTime is only for the in-order data and we do not want to discard unnecessary
|
|
// samples from the Head.
|
|
inOrderMaxTime, ok := db.inOrderBlocksMaxTime()
|
|
if ok {
|
|
minValidTime = inOrderMaxTime
|
|
}
|
|
|
|
if initErr := db.head.Init(minValidTime); initErr != nil {
|
|
db.head.metrics.walCorruptionsTotal.Inc()
|
|
var e *errLoadWbl
|
|
if errors.As(initErr, &e) {
|
|
level.Warn(db.logger).Log("msg", "Encountered WBL read error, attempting repair", "err", initErr)
|
|
if err := wbl.Repair(e.err); err != nil {
|
|
return nil, fmt.Errorf("repair corrupted WBL: %w", err)
|
|
}
|
|
level.Info(db.logger).Log("msg", "Successfully repaired WBL")
|
|
} else {
|
|
level.Warn(db.logger).Log("msg", "Encountered WAL read error, attempting repair", "err", initErr)
|
|
if err := wal.Repair(initErr); err != nil {
|
|
return nil, fmt.Errorf("repair corrupted WAL: %w", err)
|
|
}
|
|
level.Info(db.logger).Log("msg", "Successfully repaired WAL")
|
|
}
|
|
}
|
|
|
|
if db.head.MinOOOTime() != int64(math.MaxInt64) {
|
|
// Some OOO data was replayed from the disk that needs compaction and cleanup.
|
|
db.oooWasEnabled.Store(true)
|
|
}
|
|
|
|
go db.run(ctx)
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func removeBestEffortTmpDirs(l log.Logger, dir string) error {
|
|
files, err := os.ReadDir(dir)
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, f := range files {
|
|
if isTmpDir(f) {
|
|
if err := os.RemoveAll(filepath.Join(dir, f.Name())); err != nil {
|
|
level.Error(l).Log("msg", "failed to delete tmp block dir", "dir", filepath.Join(dir, f.Name()), "err", err)
|
|
continue
|
|
}
|
|
level.Info(l).Log("msg", "Found and deleted tmp block dir", "dir", filepath.Join(dir, f.Name()))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StartTime implements the Storage interface.
|
|
func (db *DB) StartTime() (int64, error) {
|
|
db.mtx.RLock()
|
|
defer db.mtx.RUnlock()
|
|
|
|
if len(db.blocks) > 0 {
|
|
return db.blocks[0].Meta().MinTime, nil
|
|
}
|
|
return db.head.MinTime(), nil
|
|
}
|
|
|
|
// Dir returns the directory of the database.
|
|
func (db *DB) Dir() string {
|
|
return db.dir
|
|
}
|
|
|
|
func (db *DB) run(ctx context.Context) {
|
|
defer close(db.donec)
|
|
|
|
backoff := time.Duration(0)
|
|
|
|
for {
|
|
select {
|
|
case <-db.stopc:
|
|
return
|
|
case <-time.After(backoff):
|
|
}
|
|
|
|
select {
|
|
case <-time.After(1 * time.Minute):
|
|
db.cmtx.Lock()
|
|
if err := db.reloadBlocks(); err != nil {
|
|
level.Error(db.logger).Log("msg", "reloadBlocks", "err", err)
|
|
}
|
|
db.cmtx.Unlock()
|
|
|
|
select {
|
|
case db.compactc <- struct{}{}:
|
|
default:
|
|
}
|
|
// We attempt mmapping of head chunks regularly.
|
|
db.head.mmapHeadChunks()
|
|
case <-db.compactc:
|
|
db.metrics.compactionsTriggered.Inc()
|
|
|
|
db.autoCompactMtx.Lock()
|
|
if db.autoCompact {
|
|
if err := db.Compact(ctx); err != nil {
|
|
level.Error(db.logger).Log("msg", "compaction failed", "err", err)
|
|
backoff = exponential(backoff, 1*time.Second, 1*time.Minute)
|
|
} else {
|
|
backoff = 0
|
|
}
|
|
} else {
|
|
db.metrics.compactionsSkipped.Inc()
|
|
}
|
|
db.autoCompactMtx.Unlock()
|
|
case <-db.stopc:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Appender opens a new appender against the database.
|
|
func (db *DB) Appender(ctx context.Context) storage.Appender {
|
|
return dbAppender{db: db, Appender: db.head.Appender(ctx)}
|
|
}
|
|
|
|
// ApplyConfig applies a new config to the DB.
|
|
// Behaviour of 'OutOfOrderTimeWindow' is as follows:
|
|
// OOO enabled = oooTimeWindow > 0. OOO disabled = oooTimeWindow is 0.
|
|
// 1) Before: OOO disabled, Now: OOO enabled =>
|
|
// - A new WBL is created for the head block.
|
|
// - OOO compaction is enabled.
|
|
// - Overlapping queries are enabled.
|
|
//
|
|
// 2) Before: OOO enabled, Now: OOO enabled =>
|
|
// - Only the time window is updated.
|
|
//
|
|
// 3) Before: OOO enabled, Now: OOO disabled =>
|
|
// - Time Window set to 0. So no new OOO samples will be allowed.
|
|
// - OOO WBL will stay and will be eventually cleaned up.
|
|
// - OOO Compaction and overlapping queries will remain enabled until a restart or until all OOO samples are compacted.
|
|
//
|
|
// 4) Before: OOO disabled, Now: OOO disabled => no-op.
|
|
func (db *DB) ApplyConfig(conf *config.Config) error {
|
|
oooTimeWindow := int64(0)
|
|
if conf.StorageConfig.TSDBConfig != nil {
|
|
oooTimeWindow = conf.StorageConfig.TSDBConfig.OutOfOrderTimeWindow
|
|
}
|
|
if oooTimeWindow < 0 {
|
|
oooTimeWindow = 0
|
|
}
|
|
|
|
// Create WBL if it was not present and if OOO is enabled with WAL enabled.
|
|
var wblog *wlog.WL
|
|
var err error
|
|
switch {
|
|
case db.head.wbl != nil:
|
|
// The existing WBL from the disk might have been replayed while OOO was disabled.
|
|
wblog = db.head.wbl
|
|
case !db.oooWasEnabled.Load() && oooTimeWindow > 0 && db.opts.WALSegmentSize >= 0:
|
|
segmentSize := wlog.DefaultSegmentSize
|
|
// Wal is set to a custom size.
|
|
if db.opts.WALSegmentSize > 0 {
|
|
segmentSize = db.opts.WALSegmentSize
|
|
}
|
|
oooWalDir := filepath.Join(db.dir, wlog.WblDirName)
|
|
wblog, err = wlog.NewSize(db.logger, db.registerer, oooWalDir, segmentSize, db.opts.WALCompression)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
db.opts.OutOfOrderTimeWindow = oooTimeWindow
|
|
db.head.ApplyConfig(conf, wblog)
|
|
|
|
if !db.oooWasEnabled.Load() {
|
|
db.oooWasEnabled.Store(oooTimeWindow > 0)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EnableNativeHistograms enables the native histogram feature.
|
|
func (db *DB) EnableNativeHistograms() {
|
|
db.head.EnableNativeHistograms()
|
|
}
|
|
|
|
// DisableNativeHistograms disables the native histogram feature.
|
|
func (db *DB) DisableNativeHistograms() {
|
|
db.head.DisableNativeHistograms()
|
|
}
|
|
|
|
// dbAppender wraps the DB's head appender and triggers compactions on commit
|
|
// if necessary.
|
|
type dbAppender struct {
|
|
storage.Appender
|
|
db *DB
|
|
}
|
|
|
|
var _ storage.GetRef = dbAppender{}
|
|
|
|
func (a dbAppender) GetRef(lset labels.Labels, hash uint64) (storage.SeriesRef, labels.Labels) {
|
|
if g, ok := a.Appender.(storage.GetRef); ok {
|
|
return g.GetRef(lset, hash)
|
|
}
|
|
return 0, labels.EmptyLabels()
|
|
}
|
|
|
|
func (a dbAppender) Commit() error {
|
|
err := a.Appender.Commit()
|
|
|
|
// We could just run this check every few minutes practically. But for benchmarks
|
|
// and high frequency use cases this is the safer way.
|
|
if a.db.head.compactable() {
|
|
select {
|
|
case a.db.compactc <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Compact data if possible. After successful compaction blocks are reloaded
|
|
// which will also delete the blocks that fall out of the retention window.
|
|
// Old blocks are only deleted on reloadBlocks based on the new block's parent information.
|
|
// See DB.reloadBlocks documentation for further information.
|
|
func (db *DB) Compact(ctx context.Context) (returnErr error) {
|
|
db.cmtx.Lock()
|
|
defer db.cmtx.Unlock()
|
|
defer func() {
|
|
if returnErr != nil && !errors.Is(returnErr, context.Canceled) {
|
|
// If we got an error because context was canceled then we're most likely
|
|
// shutting down TSDB and we don't need to report this on metrics
|
|
db.metrics.compactionsFailed.Inc()
|
|
}
|
|
}()
|
|
|
|
lastBlockMaxt := int64(math.MinInt64)
|
|
defer func() {
|
|
errs := tsdb_errors.NewMulti(returnErr)
|
|
if err := db.head.truncateWAL(lastBlockMaxt); err != nil {
|
|
errs.Add(fmt.Errorf("WAL truncation in Compact defer: %w", err))
|
|
}
|
|
returnErr = errs.Err()
|
|
}()
|
|
|
|
start := time.Now()
|
|
// Check whether we have pending head blocks that are ready to be persisted.
|
|
// They have the highest priority.
|
|
for {
|
|
select {
|
|
case <-db.stopc:
|
|
return nil
|
|
default:
|
|
}
|
|
if !db.head.compactable() {
|
|
break
|
|
}
|
|
mint := db.head.MinTime()
|
|
maxt := rangeForTimestamp(mint, db.head.chunkRange.Load())
|
|
|
|
// Wrap head into a range that bounds all reads to it.
|
|
// We remove 1 millisecond from maxt because block
|
|
// intervals are half-open: [b.MinTime, b.MaxTime). But
|
|
// chunk intervals are closed: [c.MinTime, c.MaxTime];
|
|
// so in order to make sure that overlaps are evaluated
|
|
// consistently, we explicitly remove the last value
|
|
// from the block interval here.
|
|
rh := NewRangeHeadWithIsolationDisabled(db.head, mint, maxt-1)
|
|
|
|
// Compaction runs with isolation disabled, because head.compactable()
|
|
// ensures that maxt is more than chunkRange/2 back from now, and
|
|
// head.appendableMinValidTime() ensures that no new appends can start within the compaction range.
|
|
// We do need to wait for any overlapping appenders that started previously to finish.
|
|
db.head.WaitForAppendersOverlapping(rh.MaxTime())
|
|
|
|
if err := db.compactHead(rh); err != nil {
|
|
return fmt.Errorf("compact head: %w", err)
|
|
}
|
|
// Consider only successful compactions for WAL truncation.
|
|
lastBlockMaxt = maxt
|
|
}
|
|
|
|
// Clear some disk space before compacting blocks, especially important
|
|
// when Head compaction happened over a long time range.
|
|
if err := db.head.truncateWAL(lastBlockMaxt); err != nil {
|
|
return fmt.Errorf("WAL truncation in Compact: %w", err)
|
|
}
|
|
|
|
compactionDuration := time.Since(start)
|
|
if compactionDuration.Milliseconds() > db.head.chunkRange.Load() {
|
|
level.Warn(db.logger).Log(
|
|
"msg", "Head compaction took longer than the block time range, compactions are falling behind and won't be able to catch up",
|
|
"duration", compactionDuration.String(),
|
|
"block_range", db.head.chunkRange.Load(),
|
|
)
|
|
}
|
|
|
|
if lastBlockMaxt != math.MinInt64 {
|
|
// The head was compacted, so we compact OOO head as well.
|
|
if err := db.compactOOOHead(ctx); err != nil {
|
|
return fmt.Errorf("compact ooo head: %w", err)
|
|
}
|
|
}
|
|
|
|
return db.compactBlocks()
|
|
}
|
|
|
|
// CompactHead compacts the given RangeHead.
|
|
func (db *DB) CompactHead(head *RangeHead) error {
|
|
db.cmtx.Lock()
|
|
defer db.cmtx.Unlock()
|
|
|
|
if err := db.compactHead(head); err != nil {
|
|
return fmt.Errorf("compact head: %w", err)
|
|
}
|
|
|
|
if err := db.head.truncateWAL(head.BlockMaxTime()); err != nil {
|
|
return fmt.Errorf("WAL truncation: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CompactOOOHead compacts the OOO Head.
|
|
func (db *DB) CompactOOOHead(ctx context.Context) error {
|
|
db.cmtx.Lock()
|
|
defer db.cmtx.Unlock()
|
|
|
|
return db.compactOOOHead(ctx)
|
|
}
|
|
|
|
func (db *DB) compactOOOHead(ctx context.Context) error {
|
|
if !db.oooWasEnabled.Load() {
|
|
return nil
|
|
}
|
|
oooHead, err := NewOOOCompactionHead(ctx, db.head)
|
|
if err != nil {
|
|
return fmt.Errorf("get ooo compaction head: %w", err)
|
|
}
|
|
|
|
ulids, err := db.compactOOO(db.dir, oooHead)
|
|
if err != nil {
|
|
return fmt.Errorf("compact ooo head: %w", err)
|
|
}
|
|
if err := db.reloadBlocks(); err != nil {
|
|
errs := tsdb_errors.NewMulti(err)
|
|
for _, uid := range ulids {
|
|
if errRemoveAll := os.RemoveAll(filepath.Join(db.dir, uid.String())); errRemoveAll != nil {
|
|
errs.Add(errRemoveAll)
|
|
}
|
|
}
|
|
return fmt.Errorf("reloadBlocks blocks after failed compact ooo head: %w", errs.Err())
|
|
}
|
|
|
|
lastWBLFile, minOOOMmapRef := oooHead.LastWBLFile(), oooHead.LastMmapRef()
|
|
if lastWBLFile != 0 || minOOOMmapRef != 0 {
|
|
if minOOOMmapRef != 0 {
|
|
// Ensure that no more queriers are created that will reference chunks we're about to garbage collect.
|
|
// truncateOOO waits for any existing queriers that reference chunks we're about to garbage collect to
|
|
// complete before running garbage collection, so we don't need to do that here.
|
|
//
|
|
// We take mtx to ensure that Querier() and ChunkQuerier() don't miss blocks: without this, they could
|
|
// capture the list of blocks before the call to reloadBlocks() above runs, but then capture
|
|
// lastGarbageCollectedMmapRef after we update it here, and therefore not query either the blocks we've just
|
|
// written or the head chunks those blocks were created from.
|
|
db.mtx.Lock()
|
|
db.lastGarbageCollectedMmapRef = minOOOMmapRef
|
|
db.mtx.Unlock()
|
|
}
|
|
|
|
if err := db.head.truncateOOO(lastWBLFile, minOOOMmapRef); err != nil {
|
|
return fmt.Errorf("truncate ooo wbl: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// compactOOO creates a new block per possible block range in the compactor's directory from the OOO Head given.
|
|
// Each ULID in the result corresponds to a block in a unique time range.
|
|
func (db *DB) compactOOO(dest string, oooHead *OOOCompactionHead) (_ []ulid.ULID, err error) {
|
|
start := time.Now()
|
|
|
|
blockSize := oooHead.ChunkRange()
|
|
oooHeadMint, oooHeadMaxt := oooHead.MinTime(), oooHead.MaxTime()
|
|
ulids := make([]ulid.ULID, 0)
|
|
defer func() {
|
|
if err != nil {
|
|
// Best effort removal of created block on any error.
|
|
for _, uid := range ulids {
|
|
_ = os.RemoveAll(filepath.Join(db.dir, uid.String()))
|
|
}
|
|
}
|
|
}()
|
|
|
|
for t := blockSize * (oooHeadMint / blockSize); t <= oooHeadMaxt; t += blockSize {
|
|
mint, maxt := t, t+blockSize
|
|
// Block intervals are half-open: [b.MinTime, b.MaxTime). Block intervals are always +1 than the total samples it includes.
|
|
uid, err := db.compactor.Write(dest, oooHead.CloneForTimeRange(mint, maxt-1), mint, maxt, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if uid.Compare(ulid.ULID{}) != 0 {
|
|
ulids = append(ulids, uid)
|
|
blockDir := filepath.Join(dest, uid.String())
|
|
meta, _, err := readMetaFile(blockDir)
|
|
if err != nil {
|
|
return ulids, fmt.Errorf("read meta: %w", err)
|
|
}
|
|
meta.Compaction.SetOutOfOrder()
|
|
_, err = writeMetaFile(db.logger, blockDir, meta)
|
|
if err != nil {
|
|
return ulids, fmt.Errorf("write meta: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(ulids) == 0 {
|
|
level.Info(db.logger).Log(
|
|
"msg", "compact ooo head resulted in no blocks",
|
|
"duration", time.Since(start),
|
|
)
|
|
return nil, nil
|
|
}
|
|
|
|
level.Info(db.logger).Log(
|
|
"msg", "out-of-order compaction completed",
|
|
"duration", time.Since(start),
|
|
"ulids", fmt.Sprintf("%v", ulids),
|
|
)
|
|
return ulids, nil
|
|
}
|
|
|
|
// compactHead compacts the given RangeHead.
|
|
// The compaction mutex should be held before calling this method.
|
|
func (db *DB) compactHead(head *RangeHead) error {
|
|
uid, err := db.compactor.Write(db.dir, head, head.MinTime(), head.BlockMaxTime(), nil)
|
|
if err != nil {
|
|
return fmt.Errorf("persist head block: %w", err)
|
|
}
|
|
|
|
if err := db.reloadBlocks(); err != nil {
|
|
if errRemoveAll := os.RemoveAll(filepath.Join(db.dir, uid.String())); errRemoveAll != nil {
|
|
return tsdb_errors.NewMulti(
|
|
fmt.Errorf("reloadBlocks blocks: %w", err),
|
|
fmt.Errorf("delete persisted head block after failed db reloadBlocks:%s: %w", uid, errRemoveAll),
|
|
).Err()
|
|
}
|
|
return fmt.Errorf("reloadBlocks blocks: %w", err)
|
|
}
|
|
if err = db.head.truncateMemory(head.BlockMaxTime()); err != nil {
|
|
return fmt.Errorf("head memory truncate: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// compactBlocks compacts all the eligible on-disk blocks.
|
|
// The compaction mutex should be held before calling this method.
|
|
func (db *DB) compactBlocks() (err error) {
|
|
// Check for compactions of multiple blocks.
|
|
for {
|
|
plan, err := db.compactor.Plan(db.dir)
|
|
if err != nil {
|
|
return fmt.Errorf("plan compaction: %w", err)
|
|
}
|
|
if len(plan) == 0 {
|
|
break
|
|
}
|
|
|
|
select {
|
|
case <-db.stopc:
|
|
return nil
|
|
default:
|
|
}
|
|
|
|
uid, err := db.compactor.Compact(db.dir, plan, db.blocks)
|
|
if err != nil {
|
|
return fmt.Errorf("compact %s: %w", plan, err)
|
|
}
|
|
|
|
if err := db.reloadBlocks(); err != nil {
|
|
if err := os.RemoveAll(filepath.Join(db.dir, uid.String())); err != nil {
|
|
return fmt.Errorf("delete compacted block after failed db reloadBlocks:%s: %w", uid, err)
|
|
}
|
|
return fmt.Errorf("reloadBlocks blocks: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getBlock iterates a given block range to find a block by a given id.
|
|
// If found it returns the block itself and a boolean to indicate that it was found.
|
|
func getBlock(allBlocks []*Block, id ulid.ULID) (*Block, bool) {
|
|
for _, b := range allBlocks {
|
|
if b.Meta().ULID == id {
|
|
return b, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// reload reloads blocks and truncates the head and its WAL.
|
|
func (db *DB) reload() error {
|
|
if err := db.reloadBlocks(); err != nil {
|
|
return fmt.Errorf("reloadBlocks: %w", err)
|
|
}
|
|
maxt, ok := db.inOrderBlocksMaxTime()
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if err := db.head.Truncate(maxt); err != nil {
|
|
return fmt.Errorf("head truncate: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// reloadBlocks reloads blocks without touching head.
|
|
// Blocks that are obsolete due to replacement or retention will be deleted.
|
|
func (db *DB) reloadBlocks() (err error) {
|
|
defer func() {
|
|
if err != nil {
|
|
db.metrics.reloadsFailed.Inc()
|
|
}
|
|
db.metrics.reloads.Inc()
|
|
}()
|
|
|
|
// Now that we reload TSDB every minute, there is high chance for race condition with a reload
|
|
// triggered by CleanTombstones(). We need to lock the reload to avoid the situation where
|
|
// a normal reload and CleanTombstones try to delete the same block.
|
|
db.mtx.Lock()
|
|
defer db.mtx.Unlock()
|
|
|
|
loadable, corrupted, err := openBlocks(db.logger, db.dir, db.blocks, db.chunkPool)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
deletableULIDs := db.blocksToDelete(loadable)
|
|
deletable := make(map[ulid.ULID]*Block, len(deletableULIDs))
|
|
|
|
// Mark all parents of loaded blocks as deletable (no matter if they exists). This makes it resilient against the process
|
|
// crashing towards the end of a compaction but before deletions. By doing that, we can pick up the deletion where it left off during a crash.
|
|
for _, block := range loadable {
|
|
if _, ok := deletableULIDs[block.meta.ULID]; ok {
|
|
deletable[block.meta.ULID] = block
|
|
}
|
|
for _, b := range block.Meta().Compaction.Parents {
|
|
if _, ok := corrupted[b.ULID]; ok {
|
|
delete(corrupted, b.ULID)
|
|
level.Warn(db.logger).Log("msg", "Found corrupted block, but replaced by compacted one so it's safe to delete. This should not happen with atomic deletes.", "block", b.ULID)
|
|
}
|
|
deletable[b.ULID] = nil
|
|
}
|
|
}
|
|
|
|
if len(corrupted) > 0 {
|
|
// Corrupted but no child loaded for it.
|
|
// Close all new blocks to release the lock for windows.
|
|
for _, block := range loadable {
|
|
if _, open := getBlock(db.blocks, block.Meta().ULID); !open {
|
|
block.Close()
|
|
}
|
|
}
|
|
errs := tsdb_errors.NewMulti()
|
|
for ulid, err := range corrupted {
|
|
if err != nil {
|
|
errs.Add(fmt.Errorf("corrupted block %s: %w", ulid.String(), err))
|
|
}
|
|
}
|
|
return errs.Err()
|
|
}
|
|
|
|
var (
|
|
toLoad []*Block
|
|
blocksSize int64
|
|
)
|
|
// All deletable blocks should be unloaded.
|
|
// NOTE: We need to loop through loadable one more time as there might be loadable ready to be removed (replaced by compacted block).
|
|
for _, block := range loadable {
|
|
if _, ok := deletable[block.Meta().ULID]; ok {
|
|
deletable[block.Meta().ULID] = block
|
|
continue
|
|
}
|
|
|
|
toLoad = append(toLoad, block)
|
|
blocksSize += block.Size()
|
|
}
|
|
db.metrics.blocksBytes.Set(float64(blocksSize))
|
|
|
|
slices.SortFunc(toLoad, func(a, b *Block) int {
|
|
switch {
|
|
case a.Meta().MinTime < b.Meta().MinTime:
|
|
return -1
|
|
case a.Meta().MinTime > b.Meta().MinTime:
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
})
|
|
|
|
// Swap new blocks first for subsequently created readers to be seen.
|
|
oldBlocks := db.blocks
|
|
db.blocks = toLoad
|
|
|
|
blockMetas := make([]BlockMeta, 0, len(toLoad))
|
|
for _, b := range toLoad {
|
|
blockMetas = append(blockMetas, b.Meta())
|
|
}
|
|
if overlaps := OverlappingBlocks(blockMetas); len(overlaps) > 0 {
|
|
level.Warn(db.logger).Log("msg", "Overlapping blocks found during reloadBlocks", "detail", overlaps.String())
|
|
}
|
|
|
|
// Append blocks to old, deletable blocks, so we can close them.
|
|
for _, b := range oldBlocks {
|
|
if _, ok := deletable[b.Meta().ULID]; ok {
|
|
deletable[b.Meta().ULID] = b
|
|
}
|
|
}
|
|
if err := db.deleteBlocks(deletable); err != nil {
|
|
return fmt.Errorf("delete %v blocks: %w", len(deletable), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func openBlocks(l log.Logger, dir string, loaded []*Block, chunkPool chunkenc.Pool) (blocks []*Block, corrupted map[ulid.ULID]error, err error) {
|
|
bDirs, err := blockDirs(dir)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("find blocks: %w", err)
|
|
}
|
|
|
|
corrupted = make(map[ulid.ULID]error)
|
|
for _, bDir := range bDirs {
|
|
meta, _, err := readMetaFile(bDir)
|
|
if err != nil {
|
|
level.Error(l).Log("msg", "Failed to read meta.json for a block during reloadBlocks. Skipping", "dir", bDir, "err", err)
|
|
continue
|
|
}
|
|
|
|
// See if we already have the block in memory or open it otherwise.
|
|
block, open := getBlock(loaded, meta.ULID)
|
|
if !open {
|
|
block, err = OpenBlock(l, bDir, chunkPool)
|
|
if err != nil {
|
|
corrupted[meta.ULID] = err
|
|
continue
|
|
}
|
|
}
|
|
blocks = append(blocks, block)
|
|
}
|
|
return blocks, corrupted, nil
|
|
}
|
|
|
|
// DefaultBlocksToDelete returns a filter which decides time based and size based
|
|
// retention from the options of the db.
|
|
func DefaultBlocksToDelete(db *DB) BlocksToDeleteFunc {
|
|
return func(blocks []*Block) map[ulid.ULID]struct{} {
|
|
return deletableBlocks(db, blocks)
|
|
}
|
|
}
|
|
|
|
// deletableBlocks returns all currently loaded blocks past retention policy or already compacted into a new block.
|
|
func deletableBlocks(db *DB, blocks []*Block) map[ulid.ULID]struct{} {
|
|
deletable := make(map[ulid.ULID]struct{})
|
|
|
|
// Sort the blocks by time - newest to oldest (largest to smallest timestamp).
|
|
// This ensures that the retentions will remove the oldest blocks.
|
|
slices.SortFunc(blocks, func(a, b *Block) int {
|
|
switch {
|
|
case b.Meta().MaxTime < a.Meta().MaxTime:
|
|
return -1
|
|
case b.Meta().MaxTime > a.Meta().MaxTime:
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
})
|
|
|
|
for _, block := range blocks {
|
|
if block.Meta().Compaction.Deletable {
|
|
deletable[block.Meta().ULID] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for ulid := range BeyondTimeRetention(db, blocks) {
|
|
deletable[ulid] = struct{}{}
|
|
}
|
|
|
|
for ulid := range BeyondSizeRetention(db, blocks) {
|
|
deletable[ulid] = struct{}{}
|
|
}
|
|
|
|
return deletable
|
|
}
|
|
|
|
// BeyondTimeRetention returns those blocks which are beyond the time retention
|
|
// set in the db options.
|
|
func BeyondTimeRetention(db *DB, blocks []*Block) (deletable map[ulid.ULID]struct{}) {
|
|
// Time retention is disabled or no blocks to work with.
|
|
if len(blocks) == 0 || db.opts.RetentionDuration == 0 {
|
|
return
|
|
}
|
|
|
|
deletable = make(map[ulid.ULID]struct{})
|
|
for i, block := range blocks {
|
|
// The difference between the first block and this block is larger than
|
|
// the retention period so any blocks after that are added as deletable.
|
|
if i > 0 && blocks[0].Meta().MaxTime-block.Meta().MaxTime > db.opts.RetentionDuration {
|
|
for _, b := range blocks[i:] {
|
|
deletable[b.meta.ULID] = struct{}{}
|
|
}
|
|
db.metrics.timeRetentionCount.Inc()
|
|
break
|
|
}
|
|
}
|
|
return deletable
|
|
}
|
|
|
|
// BeyondSizeRetention returns those blocks which are beyond the size retention
|
|
// set in the db options.
|
|
func BeyondSizeRetention(db *DB, blocks []*Block) (deletable map[ulid.ULID]struct{}) {
|
|
// Size retention is disabled or no blocks to work with.
|
|
if len(blocks) == 0 || db.opts.MaxBytes <= 0 {
|
|
return
|
|
}
|
|
|
|
deletable = make(map[ulid.ULID]struct{})
|
|
|
|
// Initializing size counter with WAL size and Head chunks
|
|
// written to disk, as that is part of the retention strategy.
|
|
blocksSize := db.Head().Size()
|
|
for i, block := range blocks {
|
|
blocksSize += block.Size()
|
|
if blocksSize > db.opts.MaxBytes {
|
|
// Add this and all following blocks for deletion.
|
|
for _, b := range blocks[i:] {
|
|
deletable[b.meta.ULID] = struct{}{}
|
|
}
|
|
db.metrics.sizeRetentionCount.Inc()
|
|
break
|
|
}
|
|
}
|
|
return deletable
|
|
}
|
|
|
|
// deleteBlocks closes the block if loaded and deletes blocks from the disk if exists.
|
|
// When the map contains a non nil block object it means it is loaded in memory
|
|
// so needs to be closed first as it might need to wait for pending readers to complete.
|
|
func (db *DB) deleteBlocks(blocks map[ulid.ULID]*Block) error {
|
|
for ulid, block := range blocks {
|
|
if block != nil {
|
|
if err := block.Close(); err != nil {
|
|
level.Warn(db.logger).Log("msg", "Closing block failed", "err", err, "block", ulid)
|
|
}
|
|
}
|
|
|
|
toDelete := filepath.Join(db.dir, ulid.String())
|
|
switch _, err := os.Stat(toDelete); {
|
|
case os.IsNotExist(err):
|
|
// Noop.
|
|
continue
|
|
case err != nil:
|
|
return fmt.Errorf("stat dir %v: %w", toDelete, err)
|
|
}
|
|
|
|
// Replace atomically to avoid partial block when process would crash during deletion.
|
|
tmpToDelete := filepath.Join(db.dir, fmt.Sprintf("%s%s", ulid, tmpForDeletionBlockDirSuffix))
|
|
if err := fileutil.Replace(toDelete, tmpToDelete); err != nil {
|
|
return fmt.Errorf("replace of obsolete block for deletion %s: %w", ulid, err)
|
|
}
|
|
if err := os.RemoveAll(tmpToDelete); err != nil {
|
|
return fmt.Errorf("delete obsolete block %s: %w", ulid, err)
|
|
}
|
|
level.Info(db.logger).Log("msg", "Deleting obsolete block", "block", ulid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TimeRange specifies minTime and maxTime range.
|
|
type TimeRange struct {
|
|
Min, Max int64
|
|
}
|
|
|
|
// Overlaps contains overlapping blocks aggregated by overlapping range.
|
|
type Overlaps map[TimeRange][]BlockMeta
|
|
|
|
// String returns human readable string form of overlapped blocks.
|
|
func (o Overlaps) String() string {
|
|
var res []string
|
|
for r, overlaps := range o {
|
|
var groups []string
|
|
for _, m := range overlaps {
|
|
groups = append(groups, fmt.Sprintf(
|
|
"<ulid: %s, mint: %d, maxt: %d, range: %s>",
|
|
m.ULID.String(),
|
|
m.MinTime,
|
|
m.MaxTime,
|
|
(time.Duration((m.MaxTime-m.MinTime)/1000)*time.Second).String(),
|
|
))
|
|
}
|
|
res = append(res, fmt.Sprintf(
|
|
"[mint: %d, maxt: %d, range: %s, blocks: %d]: %s",
|
|
r.Min, r.Max,
|
|
(time.Duration((r.Max-r.Min)/1000)*time.Second).String(),
|
|
len(overlaps),
|
|
strings.Join(groups, ", ")),
|
|
)
|
|
}
|
|
return strings.Join(res, "\n")
|
|
}
|
|
|
|
// OverlappingBlocks returns all overlapping blocks from given meta files.
|
|
func OverlappingBlocks(bm []BlockMeta) Overlaps {
|
|
if len(bm) <= 1 {
|
|
return nil
|
|
}
|
|
var (
|
|
overlaps [][]BlockMeta
|
|
|
|
// pending contains not ended blocks in regards to "current" timestamp.
|
|
pending = []BlockMeta{bm[0]}
|
|
// continuousPending helps to aggregate same overlaps to single group.
|
|
continuousPending = true
|
|
)
|
|
|
|
// We have here blocks sorted by minTime. We iterate over each block and treat its minTime as our "current" timestamp.
|
|
// We check if any of the pending block finished (blocks that we have seen before, but their maxTime was still ahead current
|
|
// timestamp). If not, it means they overlap with our current block. In the same time current block is assumed pending.
|
|
for _, b := range bm[1:] {
|
|
var newPending []BlockMeta
|
|
|
|
for _, p := range pending {
|
|
// "b.MinTime" is our current time.
|
|
if b.MinTime >= p.MaxTime {
|
|
continuousPending = false
|
|
continue
|
|
}
|
|
|
|
// "p" overlaps with "b" and "p" is still pending.
|
|
newPending = append(newPending, p)
|
|
}
|
|
|
|
// Our block "b" is now pending.
|
|
pending = append(newPending, b)
|
|
if len(newPending) == 0 {
|
|
// No overlaps.
|
|
continue
|
|
}
|
|
|
|
if continuousPending && len(overlaps) > 0 {
|
|
overlaps[len(overlaps)-1] = append(overlaps[len(overlaps)-1], b)
|
|
continue
|
|
}
|
|
overlaps = append(overlaps, append(newPending, b))
|
|
// Start new pendings.
|
|
continuousPending = true
|
|
}
|
|
|
|
// Fetch the critical overlapped time range foreach overlap groups.
|
|
overlapGroups := Overlaps{}
|
|
for _, overlap := range overlaps {
|
|
|
|
minRange := TimeRange{Min: 0, Max: math.MaxInt64}
|
|
for _, b := range overlap {
|
|
if minRange.Max > b.MaxTime {
|
|
minRange.Max = b.MaxTime
|
|
}
|
|
|
|
if minRange.Min < b.MinTime {
|
|
minRange.Min = b.MinTime
|
|
}
|
|
}
|
|
overlapGroups[minRange] = overlap
|
|
}
|
|
|
|
return overlapGroups
|
|
}
|
|
|
|
func (db *DB) String() string {
|
|
return "HEAD"
|
|
}
|
|
|
|
// Blocks returns the databases persisted blocks.
|
|
func (db *DB) Blocks() []*Block {
|
|
db.mtx.RLock()
|
|
defer db.mtx.RUnlock()
|
|
|
|
return db.blocks
|
|
}
|
|
|
|
// inOrderBlocksMaxTime returns the max time among the blocks that were not totally created
|
|
// out of out-of-order data. If the returned boolean is true, it means there is at least
|
|
// one such block.
|
|
func (db *DB) inOrderBlocksMaxTime() (maxt int64, ok bool) {
|
|
maxt, ok = int64(math.MinInt64), false
|
|
// If blocks are overlapping, last block might not have the max time. So check all blocks.
|
|
for _, b := range db.Blocks() {
|
|
if !b.meta.Compaction.FromOutOfOrder() && b.meta.MaxTime > maxt {
|
|
ok = true
|
|
maxt = b.meta.MaxTime
|
|
}
|
|
}
|
|
return maxt, ok
|
|
}
|
|
|
|
// Head returns the databases's head.
|
|
func (db *DB) Head() *Head {
|
|
return db.head
|
|
}
|
|
|
|
// Close the partition.
|
|
func (db *DB) Close() error {
|
|
close(db.stopc)
|
|
if db.compactCancel != nil {
|
|
db.compactCancel()
|
|
}
|
|
<-db.donec
|
|
|
|
db.mtx.Lock()
|
|
defer db.mtx.Unlock()
|
|
|
|
var g errgroup.Group
|
|
|
|
// blocks also contains all head blocks.
|
|
for _, pb := range db.blocks {
|
|
g.Go(pb.Close)
|
|
}
|
|
|
|
errs := tsdb_errors.NewMulti(g.Wait(), db.locker.Release())
|
|
if db.head != nil {
|
|
errs.Add(db.head.Close())
|
|
}
|
|
return errs.Err()
|
|
}
|
|
|
|
// DisableCompactions disables auto compactions.
|
|
func (db *DB) DisableCompactions() {
|
|
db.autoCompactMtx.Lock()
|
|
defer db.autoCompactMtx.Unlock()
|
|
|
|
db.autoCompact = false
|
|
level.Info(db.logger).Log("msg", "Compactions disabled")
|
|
}
|
|
|
|
// EnableCompactions enables auto compactions.
|
|
func (db *DB) EnableCompactions() {
|
|
db.autoCompactMtx.Lock()
|
|
defer db.autoCompactMtx.Unlock()
|
|
|
|
db.autoCompact = true
|
|
level.Info(db.logger).Log("msg", "Compactions enabled")
|
|
}
|
|
|
|
// ForceHeadMMap is intended for use only in tests and benchmarks.
|
|
func (db *DB) ForceHeadMMap() {
|
|
db.head.mmapHeadChunks()
|
|
}
|
|
|
|
// Snapshot writes the current data to the directory. If withHead is set to true it
|
|
// will create a new block containing all data that's currently in the memory buffer/WAL.
|
|
func (db *DB) Snapshot(dir string, withHead bool) error {
|
|
if dir == db.dir {
|
|
return fmt.Errorf("cannot snapshot into base directory")
|
|
}
|
|
if _, err := ulid.ParseStrict(dir); err == nil {
|
|
return fmt.Errorf("dir must not be a valid ULID")
|
|
}
|
|
|
|
db.cmtx.Lock()
|
|
defer db.cmtx.Unlock()
|
|
|
|
db.mtx.RLock()
|
|
defer db.mtx.RUnlock()
|
|
|
|
for _, b := range db.blocks {
|
|
level.Info(db.logger).Log("msg", "Snapshotting block", "block", b)
|
|
|
|
if err := b.Snapshot(dir); err != nil {
|
|
return fmt.Errorf("error snapshotting block: %s: %w", b.Dir(), err)
|
|
}
|
|
}
|
|
if !withHead {
|
|
return nil
|
|
}
|
|
|
|
mint := db.head.MinTime()
|
|
maxt := db.head.MaxTime()
|
|
head := NewRangeHead(db.head, mint, maxt)
|
|
// Add +1 millisecond to block maxt because block intervals are half-open: [b.MinTime, b.MaxTime).
|
|
// Because of this block intervals are always +1 than the total samples it includes.
|
|
if _, err := db.compactor.Write(dir, head, mint, maxt+1, nil); err != nil {
|
|
return fmt.Errorf("snapshot head block: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Querier returns a new querier over the data partition for the given time range.
|
|
func (db *DB) Querier(mint, maxt int64) (_ storage.Querier, err error) {
|
|
var blocks []BlockReader
|
|
|
|
db.mtx.RLock()
|
|
defer db.mtx.RUnlock()
|
|
|
|
for _, b := range db.blocks {
|
|
if b.OverlapsClosedInterval(mint, maxt) {
|
|
blocks = append(blocks, b)
|
|
}
|
|
}
|
|
|
|
blockQueriers := make([]storage.Querier, 0, len(blocks)+2) // +2 to allow for possible in-order and OOO head queriers
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
// If we fail, all previously opened queriers must be closed.
|
|
for _, q := range blockQueriers {
|
|
// TODO(bwplotka): Handle error.
|
|
_ = q.Close()
|
|
}
|
|
}
|
|
}()
|
|
|
|
if maxt >= db.head.MinTime() {
|
|
rh := NewRangeHead(db.head, mint, maxt)
|
|
var err error
|
|
inOrderHeadQuerier, err := NewBlockQuerier(rh, mint, maxt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open block querier for head %s: %w", rh, err)
|
|
}
|
|
|
|
// Getting the querier above registers itself in the queue that the truncation waits on.
|
|
// So if the querier is currently not colliding with any truncation, we can continue to use it and still
|
|
// won't run into a race later since any truncation that comes after will wait on this querier if it overlaps.
|
|
shouldClose, getNew, newMint := db.head.IsQuerierCollidingWithTruncation(mint, maxt)
|
|
if shouldClose {
|
|
if err := inOrderHeadQuerier.Close(); err != nil {
|
|
return nil, fmt.Errorf("closing head block querier %s: %w", rh, err)
|
|
}
|
|
inOrderHeadQuerier = nil
|
|
}
|
|
if getNew {
|
|
rh := NewRangeHead(db.head, newMint, maxt)
|
|
inOrderHeadQuerier, err = NewBlockQuerier(rh, newMint, maxt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open block querier for head while getting new querier %s: %w", rh, err)
|
|
}
|
|
}
|
|
|
|
if inOrderHeadQuerier != nil {
|
|
blockQueriers = append(blockQueriers, inOrderHeadQuerier)
|
|
}
|
|
}
|
|
|
|
if overlapsClosedInterval(mint, maxt, db.head.MinOOOTime(), db.head.MaxOOOTime()) {
|
|
rh := NewOOORangeHead(db.head, mint, maxt, db.lastGarbageCollectedMmapRef)
|
|
var err error
|
|
outOfOrderHeadQuerier, err := NewBlockQuerier(rh, mint, maxt)
|
|
if err != nil {
|
|
// If NewBlockQuerier() failed, make sure to clean up the pending read created by NewOOORangeHead.
|
|
rh.isoState.Close()
|
|
|
|
return nil, fmt.Errorf("open block querier for ooo head %s: %w", rh, err)
|
|
}
|
|
|
|
blockQueriers = append(blockQueriers, outOfOrderHeadQuerier)
|
|
}
|
|
|
|
for _, b := range blocks {
|
|
q, err := NewBlockQuerier(b, mint, maxt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open querier for block %s: %w", b, err)
|
|
}
|
|
blockQueriers = append(blockQueriers, q)
|
|
}
|
|
|
|
return storage.NewMergeQuerier(blockQueriers, nil, storage.ChainedSeriesMerge), nil
|
|
}
|
|
|
|
// blockChunkQuerierForRange returns individual block chunk queriers from the persistent blocks, in-order head block, and the
|
|
// out-of-order head block, overlapping with the given time range.
|
|
func (db *DB) blockChunkQuerierForRange(mint, maxt int64) (_ []storage.ChunkQuerier, err error) {
|
|
var blocks []BlockReader
|
|
|
|
db.mtx.RLock()
|
|
defer db.mtx.RUnlock()
|
|
|
|
for _, b := range db.blocks {
|
|
if b.OverlapsClosedInterval(mint, maxt) {
|
|
blocks = append(blocks, b)
|
|
}
|
|
}
|
|
|
|
blockQueriers := make([]storage.ChunkQuerier, 0, len(blocks)+2) // +2 to allow for possible in-order and OOO head queriers
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
// If we fail, all previously opened queriers must be closed.
|
|
for _, q := range blockQueriers {
|
|
// TODO(bwplotka): Handle error.
|
|
_ = q.Close()
|
|
}
|
|
}
|
|
}()
|
|
|
|
if maxt >= db.head.MinTime() {
|
|
rh := NewRangeHead(db.head, mint, maxt)
|
|
inOrderHeadQuerier, err := NewBlockChunkQuerier(rh, mint, maxt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open querier for head %s: %w", rh, err)
|
|
}
|
|
|
|
// Getting the querier above registers itself in the queue that the truncation waits on.
|
|
// So if the querier is currently not colliding with any truncation, we can continue to use it and still
|
|
// won't run into a race later since any truncation that comes after will wait on this querier if it overlaps.
|
|
shouldClose, getNew, newMint := db.head.IsQuerierCollidingWithTruncation(mint, maxt)
|
|
if shouldClose {
|
|
if err := inOrderHeadQuerier.Close(); err != nil {
|
|
return nil, fmt.Errorf("closing head querier %s: %w", rh, err)
|
|
}
|
|
inOrderHeadQuerier = nil
|
|
}
|
|
if getNew {
|
|
rh := NewRangeHead(db.head, newMint, maxt)
|
|
inOrderHeadQuerier, err = NewBlockChunkQuerier(rh, newMint, maxt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open querier for head while getting new querier %s: %w", rh, err)
|
|
}
|
|
}
|
|
|
|
if inOrderHeadQuerier != nil {
|
|
blockQueriers = append(blockQueriers, inOrderHeadQuerier)
|
|
}
|
|
}
|
|
|
|
if overlapsClosedInterval(mint, maxt, db.head.MinOOOTime(), db.head.MaxOOOTime()) {
|
|
rh := NewOOORangeHead(db.head, mint, maxt, db.lastGarbageCollectedMmapRef)
|
|
outOfOrderHeadQuerier, err := NewBlockChunkQuerier(rh, mint, maxt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open block chunk querier for ooo head %s: %w", rh, err)
|
|
}
|
|
|
|
blockQueriers = append(blockQueriers, outOfOrderHeadQuerier)
|
|
}
|
|
|
|
for _, b := range blocks {
|
|
q, err := NewBlockChunkQuerier(b, mint, maxt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open querier for block %s: %w", b, err)
|
|
}
|
|
blockQueriers = append(blockQueriers, q)
|
|
}
|
|
|
|
return blockQueriers, nil
|
|
}
|
|
|
|
// ChunkQuerier returns a new chunk querier over the data partition for the given time range.
|
|
func (db *DB) ChunkQuerier(mint, maxt int64) (storage.ChunkQuerier, error) {
|
|
blockQueriers, err := db.blockChunkQuerierForRange(mint, maxt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return storage.NewMergeChunkQuerier(blockQueriers, nil, storage.NewCompactingChunkSeriesMerger(storage.ChainedSeriesMerge)), nil
|
|
}
|
|
|
|
func (db *DB) ExemplarQuerier(ctx context.Context) (storage.ExemplarQuerier, error) {
|
|
return db.head.exemplars.ExemplarQuerier(ctx)
|
|
}
|
|
|
|
func rangeForTimestamp(t, width int64) (maxt int64) {
|
|
return (t/width)*width + width
|
|
}
|
|
|
|
// Delete implements deletion of metrics. It only has atomicity guarantees on a per-block basis.
|
|
func (db *DB) Delete(ctx context.Context, mint, maxt int64, ms ...*labels.Matcher) error {
|
|
db.cmtx.Lock()
|
|
defer db.cmtx.Unlock()
|
|
|
|
var g errgroup.Group
|
|
|
|
db.mtx.RLock()
|
|
defer db.mtx.RUnlock()
|
|
|
|
for _, b := range db.blocks {
|
|
if b.OverlapsClosedInterval(mint, maxt) {
|
|
g.Go(func(b *Block) func() error {
|
|
return func() error { return b.Delete(ctx, mint, maxt, ms...) }
|
|
}(b))
|
|
}
|
|
}
|
|
if db.head.OverlapsClosedInterval(mint, maxt) {
|
|
g.Go(func() error {
|
|
return db.head.Delete(ctx, mint, maxt, ms...)
|
|
})
|
|
}
|
|
|
|
return g.Wait()
|
|
}
|
|
|
|
// CleanTombstones re-writes any blocks with tombstones.
|
|
func (db *DB) CleanTombstones() (err error) {
|
|
db.cmtx.Lock()
|
|
defer db.cmtx.Unlock()
|
|
|
|
start := time.Now()
|
|
defer func() {
|
|
db.metrics.tombCleanTimer.Observe(time.Since(start).Seconds())
|
|
}()
|
|
|
|
cleanUpCompleted := false
|
|
// Repeat cleanup until there is no tombstones left.
|
|
for !cleanUpCompleted {
|
|
cleanUpCompleted = true
|
|
|
|
for _, pb := range db.Blocks() {
|
|
uid, safeToDelete, cleanErr := pb.CleanTombstones(db.Dir(), db.compactor)
|
|
if cleanErr != nil {
|
|
return fmt.Errorf("clean tombstones: %s: %w", pb.Dir(), cleanErr)
|
|
}
|
|
if !safeToDelete {
|
|
// There was nothing to clean.
|
|
continue
|
|
}
|
|
|
|
// In case tombstones of the old block covers the whole block,
|
|
// then there would be no resultant block to tell the parent.
|
|
// The lock protects against race conditions when deleting blocks
|
|
// during an already running reload.
|
|
db.mtx.Lock()
|
|
pb.meta.Compaction.Deletable = safeToDelete
|
|
db.mtx.Unlock()
|
|
cleanUpCompleted = false
|
|
if err = db.reloadBlocks(); err == nil { // Will try to delete old block.
|
|
// Successful reload will change the existing blocks.
|
|
// We need to loop over the new set of blocks.
|
|
break
|
|
}
|
|
|
|
// Delete new block if it was created.
|
|
if uid != nil && *uid != (ulid.ULID{}) {
|
|
dir := filepath.Join(db.Dir(), uid.String())
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
level.Error(db.logger).Log("msg", "failed to delete block after failed `CleanTombstones`", "dir", dir, "err", err)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("reload blocks: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (db *DB) SetWriteNotified(wn wlog.WriteNotified) {
|
|
db.writeNotified = wn
|
|
// It's possible we already created the head struct, so we should also set the WN for that.
|
|
db.head.writeNotified = wn
|
|
}
|
|
|
|
func isBlockDir(fi fs.DirEntry) bool {
|
|
if !fi.IsDir() {
|
|
return false
|
|
}
|
|
_, err := ulid.ParseStrict(fi.Name())
|
|
return err == nil
|
|
}
|
|
|
|
// isTmpDir returns true if the given file-info contains a block ULID, a checkpoint prefix,
|
|
// or a chunk snapshot prefix and a tmp extension.
|
|
func isTmpDir(fi fs.DirEntry) bool {
|
|
if !fi.IsDir() {
|
|
return false
|
|
}
|
|
|
|
fn := fi.Name()
|
|
ext := filepath.Ext(fn)
|
|
if ext == tmpForDeletionBlockDirSuffix || ext == tmpForCreationBlockDirSuffix || ext == tmpLegacy {
|
|
if strings.HasPrefix(fn, "checkpoint.") {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(fn, chunkSnapshotPrefix) {
|
|
return true
|
|
}
|
|
if _, err := ulid.ParseStrict(fn[:len(fn)-len(ext)]); err == nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func blockDirs(dir string) ([]string, error) {
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var dirs []string
|
|
|
|
for _, f := range files {
|
|
if isBlockDir(f) {
|
|
dirs = append(dirs, filepath.Join(dir, f.Name()))
|
|
}
|
|
}
|
|
return dirs, nil
|
|
}
|
|
|
|
func sequenceFiles(dir string) ([]string, error) {
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var res []string
|
|
|
|
for _, fi := range files {
|
|
if _, err := strconv.ParseUint(fi.Name(), 10, 64); err != nil {
|
|
continue
|
|
}
|
|
res = append(res, filepath.Join(dir, fi.Name()))
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func nextSequenceFile(dir string) (string, int, error) {
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
i := uint64(0)
|
|
for _, f := range files {
|
|
j, err := strconv.ParseUint(f.Name(), 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
i = j
|
|
}
|
|
return filepath.Join(dir, fmt.Sprintf("%0.6d", i+1)), int(i + 1), nil
|
|
}
|
|
|
|
func exponential(d, min, max time.Duration) time.Duration {
|
|
d *= 2
|
|
if d < min {
|
|
d = min
|
|
}
|
|
if d > max {
|
|
d = max
|
|
}
|
|
return d
|
|
}
|