From fa6a1f97d020ea13afeb79e03c59cf4853873b5d Mon Sep 17 00:00:00 2001 From: "Matt T. Proud" Date: Fri, 10 May 2013 16:41:02 +0200 Subject: [PATCH] Expose interfaces for pruner and make pruner tool. In order to run database cleanups and diagnostics, we should have a means for pruning a database---even if LevelDB does this for us. --- Makefile | 8 +++-- storage/metric/leveldb.go | 32 ++++++++++++++++++ storage/raw/index/leveldb/leveldb.go | 10 ++++++ storage/raw/leveldb/leveldb.go | 33 ++++++++++++++++++- tools/Makefile | 26 +++++++++++++++ tools/pruner/.gitignore | 1 + tools/pruner/Makefile | 28 ++++++++++++++++ tools/pruner/main.go | 49 ++++++++++++++++++++++++++++ 8 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 tools/Makefile create mode 100644 tools/pruner/.gitignore create mode 100644 tools/pruner/Makefile create mode 100644 tools/pruner/main.go diff --git a/Makefile b/Makefile index a0312213c..8195def9c 100644 --- a/Makefile +++ b/Makefile @@ -23,12 +23,13 @@ advice: binary: build go build $(BUILDFLAGS) . -build: preparation config model web +build: preparation config model tools web clean: $(MAKE) -C build clean $(MAKE) -C config clean $(MAKE) -C model clean + $(MAKE) -C tools clean $(MAKE) -C web clean rm -rf $(TEST_ARTIFACTS) -find . -type f -iname '*~' -exec rm '{}' ';' @@ -72,7 +73,10 @@ source_path: test: build go test ./... $(GO_TEST_FLAGS) +tools: + $(MAKE) -C tools + web: preparation config model $(MAKE) -C web -.PHONY: advice binary build clean config documentation format model package preparation run search_index source_path test +.PHONY: advice binary build clean config documentation format model package preparation run search_index source_path test tools diff --git a/storage/metric/leveldb.go b/storage/metric/leveldb.go index 343a57f7d..b5d5b0d19 100644 --- a/storage/metric/leveldb.go +++ b/storage/metric/leveldb.go @@ -876,3 +876,35 @@ func (l *LevelDBMetricPersistence) GetAllValuesForLabel(labelName model.LabelNam func (l *LevelDBMetricPersistence) ForEachSample(builder IteratorsForFingerprintBuilder) (err error) { panic("not implemented") } + +// CompactKeyspace compacts each database's keyspace serially. An error may +// be returned if there are difficulties with the underlying database or if +// it's empty. +// +// Beware that it would probably be imprudent to run this on a live user-facing +// server due to latency implications. +func (l *LevelDBMetricPersistence) CompactKeyspaces() error { + if err := l.CurationRemarks.CompactKeyspace(); err != nil { + return fmt.Errorf("Could not compact curation remarks: %s", err) + } + if err := l.fingerprintToMetrics.CompactKeyspace(); err != nil { + return fmt.Errorf("Could not compact fingerprint to metric index: %s", err) + } + if err := l.labelNameToFingerprints.CompactKeyspace(); err != nil { + return fmt.Errorf("Could not compact label name to fingerprint index: %s", err) + } + if err := l.labelSetToFingerprints.CompactKeyspace(); err != nil { + return fmt.Errorf("Could not compact label pair to fingerprint index: %s", err) + } + if err := l.MetricHighWatermarks.CompactKeyspace(); err != nil { + return fmt.Errorf("Could not compact metric high watermarks: %s", err) + } + if err := l.metricMembershipIndex.CompactKeyspace(); err != nil { + return fmt.Errorf("Could not compact metric membership index: %s", err) + } + if err := l.MetricSamples.CompactKeyspace(); err != nil { + return fmt.Errorf("Could not compact metric samples database: %s", err) + } + + return nil +} diff --git a/storage/raw/index/leveldb/leveldb.go b/storage/raw/index/leveldb/leveldb.go index 27303276e..24b62d648 100644 --- a/storage/raw/index/leveldb/leveldb.go +++ b/storage/raw/index/leveldb/leveldb.go @@ -61,3 +61,13 @@ func NewLevelDBMembershipIndex(storageRoot string, cacheCapacity, bitsPerBloomFi func (l *LevelDBMembershipIndex) Commit(batch raw.Batch) error { return l.persistence.Commit(batch) } + +// CompactKeyspace compacts the entire database's keyspace. An error may be +// returned if there are difficulties with the underlying database or if it's +// empty. +// +// Beware that it would probably be imprudent to run this on a live user-facing +// server due to latency implications. +func (l *LevelDBMembershipIndex) CompactKeyspace() error { + return l.persistence.CompactKeyspace() +} diff --git a/storage/raw/leveldb/leveldb.go b/storage/raw/leveldb/leveldb.go index 17367345b..4304c194d 100644 --- a/storage/raw/leveldb/leveldb.go +++ b/storage/raw/leveldb/leveldb.go @@ -195,10 +195,12 @@ func NewLevelDBPersistence(storageRoot string, cacheCapacity, bitsPerBloomFilter p = &LevelDBPersistence{ cache: cache, filterPolicy: filterPolicy, + options: options, readOptions: readOptions, - storage: storage, writeOptions: writeOptions, + + storage: storage, } return @@ -306,6 +308,35 @@ func (l *LevelDBPersistence) Commit(b raw.Batch) (err error) { return l.storage.Write(l.writeOptions, batch.batch) } +// CompactKeyspace compacts the entire database's keyspace. An error may be +// returned if there are difficulties with the underlying database or if it's +// empty. +// +// Beware that it would probably be imprudent to run this on a live user-facing +// server due to latency implications. +func (l *LevelDBPersistence) CompactKeyspace() error { + iterator := l.NewIterator(false) + defer iterator.Close() + + if !iterator.SeekToFirst() { + return fmt.Errorf("could not seek to first key") + } + + keyspace := levigo.Range{} + + keyspace.Start = iterator.Key() + + if !iterator.SeekToLast() { + return fmt.Errorf("could not seek to last key") + } + + keyspace.Limit = iterator.Key() + + l.storage.CompactRange(keyspace) + + return nil +} + // NewIterator creates a new levigoIterator, which follows the Iterator // interface. // diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 000000000..aa19205b2 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,26 @@ +# Copyright 2013 Prometheus Team +# 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. + +all: pruner + +SUFFIXES: + +include ../Makefile.INCLUDE + +pruner: + $(MAKE) -C pruner + +clean: + $(MAKE) -C pruner clean + +.PHONY: clean pruner diff --git a/tools/pruner/.gitignore b/tools/pruner/.gitignore new file mode 100644 index 000000000..f4903b6bc --- /dev/null +++ b/tools/pruner/.gitignore @@ -0,0 +1 @@ +pruner diff --git a/tools/pruner/Makefile b/tools/pruner/Makefile new file mode 100644 index 000000000..d7b668242 --- /dev/null +++ b/tools/pruner/Makefile @@ -0,0 +1,28 @@ +# Copyright 2013 Prometheus Team +# 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. + +MAKE_ARTIFACTS = pruner + +all: pruner + +SUFFIXES: + +include ../../Makefile.INCLUDE + +pruner: $(shell find . -iname '*.go') + go build -o pruner . + +clean: + rm -rf $(MAKE_ARTIFACTS) + +.PHONY: clean diff --git a/tools/pruner/main.go b/tools/pruner/main.go new file mode 100644 index 000000000..80b63059d --- /dev/null +++ b/tools/pruner/main.go @@ -0,0 +1,49 @@ +// Copyright 2013 Prometheus Team +// 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. + +// Pruner is responsible for cleaning all Prometheus disk databases, which +// minimally includes 1. applying pending commit logs, 2. compacting SSTables, +// 3. purging stale SSTables, and 4. removing old tombstones. +package main + +import ( + "flag" + "github.com/prometheus/prometheus/storage/metric" + "log" + "time" +) + +var ( + storageRoot = flag.String("storage.root", "", "The path to the storage root for Prometheus.") +) + +func main() { + flag.Parse() + + if storageRoot == nil || *storageRoot == "" { + log.Fatal("Must provide a path...") + } + + persistences, err := metric.NewLevelDBMetricPersistence(*storageRoot) + if err != nil { + log.Fatal(err) + } + defer persistences.Close() + + start := time.Now() + log.Printf("Starting compaction...") + if err := persistences.CompactKeyspaces(); err != nil { + log.Fatalf("Abording after %s", time.Since(start)) + } + log.Printf("Finished in %s", time.Since(start)) +}