@ -6,12 +6,14 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/cortexproject/cortex/pkg/chunk"
chunk_util "github.com/cortexproject/cortex/pkg/chunk/util"
util_log "github.com/cortexproject/cortex/pkg/util/log"
util_math "github.com/cortexproject/cortex/pkg/util/math"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"go.etcd.io/bbolt"
@ -42,6 +44,7 @@ type table struct {
tableMarker retention . TableMarker
compactedDB * bbolt . DB
logger log . Logger
ctx context . Context
quit chan struct { }
@ -62,6 +65,7 @@ func newTable(ctx context.Context, workingDirectory string, objectClient chunk.O
applyRetention : applyRetention ,
tableMarker : tableMarker ,
}
table . logger = log . With ( util_log . Logger , "table-name" , table . name )
return & table , nil
}
@ -72,12 +76,12 @@ func (t *table) compact(tableHasExpiredStreams bool) error {
return err
}
level . Info ( util_log . L ogger) . Log ( "msg" , "listed files" , "count" , len ( objects ) )
level . Info ( t . l ogger) . Log ( "msg" , "listed files" , "count" , len ( objects ) )
defer func ( ) {
err := t . cleanup ( )
if err != nil {
level . Error ( util_log . L ogger) . Log ( "msg" , "failed to cleanup table" , "name" , t . name )
level . Error ( t . l ogger) . Log ( "msg" , "failed to cleanup table" )
}
} ( )
@ -85,7 +89,7 @@ func (t *table) compact(tableHasExpiredStreams bool) error {
if ! applyRetention {
if len ( objects ) < compactMinDBs {
level . Info ( util_log . L ogger) . Log ( "msg" , fmt . Sprintf ( "skipping compaction since we have just %d files in storage" , len ( objects ) ) )
level . Info ( t . l ogger) . Log ( "msg" , fmt . Sprintf ( "skipping compaction since we have just %d files in storage" , len ( objects ) ) )
return nil
}
if err := t . compactFiles ( objects ) ; err != nil {
@ -120,16 +124,14 @@ func (t *table) compact(tableHasExpiredStreams bool) error {
if err != nil {
return err
}
t . compactedDB , err = shipper_util . SafeOpenBoltdbFile ( downloadAt )
t . compactedDB , err = openBoltdbFileWithNoSync ( downloadAt )
if err != nil {
return err
}
// no need to enforce write to disk, we'll upload and delete the file anyway.
t . compactedDB . NoSync = true
}
if t . compactedDB == nil {
level . Info ( util_log . L ogger) . Log ( "msg" , "skipping compaction no files found." )
level . Info ( t . l ogger) . Log ( "msg" , "skipping compaction no files found." )
return nil
}
@ -157,15 +159,25 @@ func (t *table) compact(tableHasExpiredStreams bool) error {
func ( t * table ) compactFiles ( objects [ ] chunk . StorageObject ) error {
var err error
// create a new compacted db
t . compactedDB , err = shipper_util . SafeOpenBoltdbFile ( filepath . Join ( t . workingDirectory , fmt . Sprint ( time . Now ( ) . Unix ( ) ) ) )
level . Info ( t . logger ) . Log ( "msg" , "starting compaction of dbs" )
compactedDBName := filepath . Join ( t . workingDirectory , fmt . Sprint ( time . Now ( ) . Unix ( ) ) )
seedFileIdx , err := findSeedObjectIdx ( objects )
if err != nil {
return err
}
level . Info ( t . logger ) . Log ( "msg" , fmt . Sprintf ( "using %s as seed file" , objects [ seedFileIdx ] . Key ) )
err = shipper_util . GetFileFromStorage ( t . ctx , t . storageClient , objects [ seedFileIdx ] . Key , compactedDBName , false )
if err != nil {
return err
}
t . compactedDB , err = openBoltdbFileWithNoSync ( compactedDBName )
if err != nil {
return err
}
// no need to enforce write to disk, we'll upload and delete the file anyway.
// in case of failure we'll restart the whole process anyway.
t . compactedDB . NoSync = true
level . Info ( util_log . Logger ) . Log ( "msg" , "starting compaction of dbs" )
errChan := make ( chan error )
readObjectChan := make ( chan string )
@ -206,7 +218,7 @@ func (t *table) compactFiles(objects []chunk.StorageObject) error {
err = t . readFile ( downloadAt )
if err != nil {
level . Error ( util_log . L ogger) . Log ( "msg" , fmt . Sprintf ( "error reading file %s" , objectKey ) , "err" , err )
level . Error ( t . l ogger) . Log ( "msg" , fmt . Sprintf ( "error reading file %s" , objectKey ) , "err" , err )
return
}
case <- t . quit :
@ -220,7 +232,11 @@ func (t *table) compactFiles(objects []chunk.StorageObject) error {
// send all files to readObjectChan
go func ( ) {
for _ , object := range objects {
for i , object := range objects {
// skip seed file
if i == seedFileIdx {
continue
}
select {
case readObjectChan <- object . Key :
case <- t . quit :
@ -230,7 +246,7 @@ func (t *table) compactFiles(objects []chunk.StorageObject) error {
}
}
level . Debug ( util_log . L ogger) . Log ( "msg" , "closing readObjectChan" )
level . Debug ( t . l ogger) . Log ( "msg" , "closing readObjectChan" )
close ( readObjectChan )
} ( )
@ -257,7 +273,7 @@ func (t *table) compactFiles(objects []chunk.StorageObject) error {
default :
}
level . Info ( util_log . L ogger) . Log ( "msg" , "finished compacting the dbs" )
level . Info ( t . l ogger) . Log ( "msg" , "finished compacting the dbs" )
return nil
}
@ -293,20 +309,20 @@ func (t *table) writeBatch(batch []indexEntry) error {
// readFile reads a boltdb file from a path and writes the index in batched mode to compactedDB
func ( t * table ) readFile ( path string ) error {
level . Debug ( util_log . L ogger) . Log ( "msg" , "reading file for compaction" , "path" , path )
level . Debug ( t . l ogger) . Log ( "msg" , "reading file for compaction" , "path" , path )
db , err := shipper_util . SafeOpenBoltdbFile ( path )
db , err := openBoltdbFileWithNoSync ( path )
if err != nil {
return err
}
db . NoSync = true
defer func ( ) {
if err := db . Close ( ) ; err != nil {
level . Error ( util_log . L ogger) . Log ( "msg" , "failed to close db" , "path" , path , "err" , err )
level . Error ( t . l ogger) . Log ( "msg" , "failed to close db" , "path" , path , "err" , err )
}
if err = os . Remove ( path ) ; err != nil {
level . Error ( util_log . L ogger) . Log ( "msg" , "failed to remove file" , "path" , path , "err" , err )
level . Error ( t . l ogger) . Log ( "msg" , "failed to remove file" , "path" , path , "err" , err )
}
} ( )
@ -379,23 +395,23 @@ func (t *table) upload() error {
defer func ( ) {
if err := compressedDB . Close ( ) ; err != nil {
level . Error ( util_log . L ogger) . Log ( "msg" , "failed to close file" , "path" , compactedDBPath , "err" , err )
level . Error ( t . l ogger) . Log ( "msg" , "failed to close file" , "path" , compactedDBPath , "err" , err )
}
if err := os . Remove ( compressedDBPath ) ; err != nil {
level . Error ( util_log . L ogger) . Log ( "msg" , "failed to remove file" , "path" , compressedDBPath , "err" , err )
level . Error ( t . l ogger) . Log ( "msg" , "failed to remove file" , "path" , compressedDBPath , "err" , err )
}
} ( )
objectKey := fmt . Sprintf ( "%s.gz" , shipper_util . BuildObjectKey ( t . name , uploaderName , fmt . Sprint ( time . Now ( ) . Unix ( ) ) ) )
level . Info ( util_log . L ogger) . Log ( "msg" , "uploading the compacted file" , "objectKey" , objectKey )
level . Info ( t . l ogger) . Log ( "msg" , "uploading the compacted file" , "objectKey" , objectKey )
return t . storageClient . PutObject ( t . ctx , objectKey , compressedDB )
}
// removeObjectsFromStorage deletes objects from storage.
func ( t * table ) removeObjectsFromStorage ( objects [ ] chunk . StorageObject ) error {
level . Info ( util_log . L ogger) . Log ( "msg" , "removing source db files from storage" , "count" , len ( objects ) )
level . Info ( t . l ogger) . Log ( "msg" , "removing source db files from storage" , "count" , len ( objects ) )
for _ , object := range objects {
err := t . storageClient . DeleteObject ( t . ctx , object . Key )
@ -406,3 +422,36 @@ func (t *table) removeObjectsFromStorage(objects []chunk.StorageObject) error {
return nil
}
// openBoltdbFileWithNoSync opens a boltdb file and configures it to not sync the file to disk.
// Compaction process is idempotent and we do not retain the files so there is no need to sync them to disk.
func openBoltdbFileWithNoSync ( path string ) ( * bbolt . DB , error ) {
boltdb , err := shipper_util . SafeOpenBoltdbFile ( path )
if err != nil {
return nil , err
}
// no need to enforce write to disk, we'll upload and delete the file anyway.
boltdb . NoSync = true
return boltdb , nil
}
// findSeedObjectIdx returns index of object to use as seed which would then get index from all the files written to.
// It tries to find previously compacted file(which has uploaderName) which would be the biggest file.
// In a large cluster, using previously compacted file as seed would significantly reduce compaction time.
// If it can't find a previously compacted file, it would just use the first file from the list of files.
func findSeedObjectIdx ( objects [ ] chunk . StorageObject ) ( int , error ) {
for i , object := range objects {
dbName , err := shipper_util . GetDBNameFromObjectKey ( object . Key )
if err != nil {
return 0 , err
}
if strings . HasPrefix ( dbName , uploaderName ) {
return i , nil
}
}
return 0 , nil
}