Skip to content

flush boltdb files to object stores #1533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

sandeepsukhani
Copy link
Contributor

@sandeepsukhani sandeepsukhani commented Jul 26, 2019

This can be useful for running cortex using just boltdb and gcs/s3.
Few details about implementation:

  • Files are stored in a folder per periodic table and are named after ingester and startup timestamp so that integers can keep pushing latest changes by overriding same files
  • Flushes files every 15 mins and before shutting down to make index available to other services
  • New stores can be implemented easily by implementing methods required by store interface
  • Ingester to also query store when using boltdb. Limits query to only last 30 mins when flushing boltdb to store is enabled.
  • Queriers would keep syncing new and updated index files in a local folder which would be cleaned up by default every 24 hours.

@tomwilkie tomwilkie requested a review from gouthamve July 31, 2019 12:36
@sandeepsukhani sandeepsukhani force-pushed the flush-boltdb-to-gcs branch 3 times, most recently from e6b0f42 to 9bd6667 Compare September 17, 2019 10:30
@sandeepsukhani sandeepsukhani marked this pull request as ready for review September 17, 2019 11:05
@sandeepsukhani sandeepsukhani changed the title Draft: Flush boltdb files to gcs flush boltdb files to gcs Sep 18, 2019
@sandeepsukhani sandeepsukhani changed the title flush boltdb files to gcs flush boltdb files to object stores Jan 14, 2020
@pracucci
Copy link
Contributor

@sandeepsukhani Could you rebase master, fix the linter and tests before I start the review, please?

files are stored in folder per periodic table and are named after ingester
flushed every 15 mins to make index available to other services
files are also flushed before ingester stops to avoid any data loss
new stores can be implemented easily
ingester to also query store when using boltdb

Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
…re to come

Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
… or not

Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
Signed-off-by: Sandeep Sukhani <sandeep.d.sukhani@gmail.com>
Copy link
Contributor

@pracucci pracucci left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a relatively quick first pass. I would have several minor comments which I've skipped so far, so that we can focus on the most important things first. My main concern is about the safety of changing BoltDB files on a already opened BoltDB. I assume you've already investigated this and it's safe, but I can't find any detailed comment about it in the code, so I would like to better understand how we can safely change files under the hood to BoltDB while still working on it (ie. querying it).


// ArchiveStoreClient define all the methods that a store needs to implement for managing objects
type ArchiveStoreClient interface {
GetObject(ctx context.Context, objectName string) ([]byte, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable objectName would be more correct to be named objectKey because it's actually used as the object key (made of prefix + object name). I would suggest this renaming across the change set (both interface and implementations).

Same comment applies to PutObject() too.


type StoreConfig struct {
Store string `yaml:"store"`
AWSStorageConfig aws.StorageConfig `yaml:"aws"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate we have to use aws.StorageConfig because it also brings a bunch of config options related to DynamoDB we don't really need. In the aws package we can:

  1. Introduce S3Config (like we have GCSConfig)
  2. Move S3, BucketNames and S3ForcePathStyle to S3Config
  3. Inline embed S3Config into StorageConfig so that we don't introduce a breaking change (you can do it with S3 S3Config `yaml:"inline"` )
  4. Change NewS3ObjectClient to pick S3Config in input, instead of StorageConfig

Remember to re-run make doc so that we see the updated config.


// Put object into the store
func (a S3ObjectClient) PutObject(ctx context.Context, objectName string, object []byte) error {
return instrument.CollectedRequest(ctx, "S3.Put", s3RequestDuration, instrument.ErrorCode, func(ctx context.Context) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S3.Put > S3.PutObject

}

// ArchiveStoreClient define all the methods that a store needs to implement for managing objects
type ArchiveStoreClient interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GetObject() and PutObject() work with []byte. This means we need to load the entire index in-memory while uploading/downloading. What's the expected index size for a large cluster in production?

I'm asking because this could be an issue, and we may want to actually work with io.Reader interfaces (ie. see Thanos S3 objstore client)

return aDB, nil
}

b.archivedDbsMtx.Lock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lock blocks any index query, even of DBs different than name, until this function returns, due to archiver.Sync() and openBoltdbFile(). Have you thought about this?

return nil
}

func (a *Archiver) syncTable(ctx context.Context, tableName string, sendUpdates bool) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this function we do add, update and delete files for an open BoltDB table. Is it a safe operation? Is it safe doing it while, in the meanwhile, we may query it?

return nil
}

func (a *Archiver) syncTable(ctx context.Context, tableName string, sendUpdates bool) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this sendUpdates instead of always doing it?

result.Timeseries = append(result.Timeseries, ts)
return nil
}, nil, 0)
i.metrics.queriedSeries.Observe(float64(numSeries))

if i.cfg.QueryStore {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain me why we do want all this logic, please?

return err
}

buf, err := ioutil.ReadAll(f)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're reading a file while BoltDB may simultaneously write on it. How can we guarantee the uploaded file is not corrupted?


// We would use ingester name and startup timestamp for naming files while uploading so that
// ingester does not override old files when using same id
ingesterNameAndStartUpTs string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may rename it to something like ingesterUniqueID (the fact it's built upon ingester name + timestamp is an implementation detail, but what we need to store in the variable is a unique ID for the ingester).

@sandeepsukhani
Copy link
Contributor Author

Since Cortex now has TSDB support, this is not needed.
Support for custom index clients is added in #2049 for this to be implemented in Loki instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants