Skip to content
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

Simpler new chunk key v12 #5054

Merged
merged 28 commits into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
90fe9c2
starts hacking new chunk paths
owen-d Nov 10, 2021
c634d38
Create new chunk key for S3; update parsing and add basic test and be…
JordanRushing Nov 24, 2021
d6b8616
Finish plumbing through the chunk.ExternalKey ->
cstyan Dec 2, 2021
ebab2ac
Clean up lint failures
JordanRushing Dec 2, 2021
25b631a
Clean up chunk.go and fix tests
JordanRushing Dec 2, 2021
7fda43b
Break SchemaConfig.ExternalKey() into smaller functions
JordanRushing Dec 2, 2021
db54175
Quickly fix variable name in SchemaConfig.newerExternalKey()
JordanRushing Dec 2, 2021
abee82d
Clean up ExternalKey conditional logic; add better comments; add tests
JordanRushing Dec 3, 2021
ca95847
Fix a bug where we are prepending userID to legacy Chunk keys but nev…
JordanRushing Dec 8, 2021
b753541
Add small SchemaConfig test
JordanRushing Dec 8, 2021
1cc0986
Update docs and CHANGELOG.md
JordanRushing Dec 8, 2021
50911ba
Merge branch 'main' into new-chunk-external-key-v12
JordanRushing Dec 8, 2021
1645a49
Correctly return an error when failing to parse a chunk external key
JordanRushing Dec 8, 2021
a3ae0ea
Revert IdentityEncoder to Base64Encoder after testing
JordanRushing Dec 14, 2021
823a63f
Merge branch 'main' into new-chunk-external-key-v12
JordanRushing Dec 27, 2021
2679054
Fix assignment in test for linter
JordanRushing Dec 27, 2021
aa3dc28
Remove leftover comments from development
JordanRushing Dec 27, 2021
d300f55
Change v12 version comment format; remove redundant login in `ParseEx…
JordanRushing Dec 27, 2021
8b582f6
Change remaining v12 comment style
JordanRushing Dec 27, 2021
0a089c7
Merge remote-tracking branch 'upstream/main' into new-chunk-external-…
JordanRushing Jan 4, 2022
d3d2fc3
Pass chunk.SchemaConfig to new parallel chunk client
JordanRushing Jan 4, 2022
24219ba
Simplify chunk external key prefixes for schema v12
JordanRushing Jan 5, 2022
1d1dab9
Fix broken benchmark; add benchmarks for old parsing conditional
JordanRushing Jan 6, 2022
e23c97a
Remove unneeded lines from upgrading doc
JordanRushing Jan 6, 2022
9bb4960
Add benchmarks for root chunk external key parsing functions
JordanRushing Jan 6, 2022
8ee35f8
memoizes schema number calculations
owen-d Jan 6, 2022
8c833be
Merge pull request #3 from owen-d/pull/5054
JordanRushing Jan 6, 2022
fcb10a6
Resolve linter issue with PeriodConfig receiver name
JordanRushing Jan 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
starts hacking new chunk paths
  • Loading branch information
owen-d authored and JordanRushing committed Dec 1, 2021
commit 90fe9c23857088df84e7da54b41a0c51abfa45af
97 changes: 94 additions & 3 deletions pkg/storage/chunk/chunk.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"unsafe"

"github.com/cortexproject/cortex/pkg/prom1/storage/metric"
Expand Down Expand Up @@ -93,7 +94,7 @@ func ParseExternalKey(userID, externalKey string) (Chunk, error) {
if !strings.Contains(externalKey, "/") {
return parseLegacyChunkID(userID, externalKey)
}
chunk, err := parseNewExternalKey(userID, externalKey)
chunk, err := parseNewerExternalKey(userID, externalKey)
if err != nil {
return Chunk{}, err
}
Expand Down Expand Up @@ -176,9 +177,85 @@ func parseNewExternalKey(userID, key string) (Chunk, error) {
}, nil
}

func parseNewerExternalKey(userID, key string) (Chunk, error) {
// Parse user
userIdx := strings.Index(key, "/")
if userIdx == -1 || userIdx+1 >= len(key) {
return Chunk{}, errInvalidChunkID(key)
}
if userID != key[:userIdx] {
return Chunk{}, errors.WithStack(ErrWrongMetadata)
}
// Parse period, but throw away
hexParts := key[userIdx+1:]
partsBytes := unsafeGetBytes(hexParts)
h, i := readOneHexPart(partsBytes)
if i == 0 || i+1 >= len(partsBytes) {
return Chunk{}, errors.Wrap(errInvalidChunkID(key), "decoding period")
}
_, err := strconv.ParseUint(unsafeGetString(h), 16, 64)
if err != nil {
return Chunk{}, errors.Wrap(err, "parsing period")
}
partsBytes = partsBytes[i+1:]
// Parse shard, but throw away
h, i = readOneHexPart(partsBytes)
if i == 0 || i+1 >= len(partsBytes) {
return Chunk{}, errors.Wrap(errInvalidChunkID(key), "decoding shard")
}
_, err = strconv.ParseUint(unsafeGetString(h), 16, 64)
if err != nil {
return Chunk{}, errors.Wrap(err, "parsing shard")
}
partsBytes = partsBytes[i+1:]
// Parse fingerprint
h, i = readOneHexPart(partsBytes)
if i == 0 || i+1 >= len(partsBytes) {
return Chunk{}, errors.Wrap(errInvalidChunkID(key), "decoding fingerprint")
}
fingerprint, err := strconv.ParseUint(unsafeGetString(h), 16, 64)
if err != nil {
return Chunk{}, errors.Wrap(err, "parsing fingerprint")
}
partsBytes = partsBytes[i+1:]
// Parse start
h, i = readOneHexPart(partsBytes)
if i == 0 || i+1 >= len(partsBytes) {
return Chunk{}, errors.Wrap(errInvalidChunkID(key), "decoding start")
}
from, err := strconv.ParseInt(unsafeGetString(h), 16, 64)
if err != nil {
return Chunk{}, errors.Wrap(err, "parsing start")
}
partsBytes = partsBytes[i+1:]
// Parse through
h, i = readOneHexPart(partsBytes)
if i == 0 || i+1 >= len(partsBytes) {
return Chunk{}, errors.Wrap(errInvalidChunkID(key), "decoding through")
}
through, err := strconv.ParseInt(unsafeGetString(h), 16, 64)
if err != nil {
return Chunk{}, errors.Wrap(err, "parsing through")
}
partsBytes = partsBytes[i+1:]
// Parse checksum
checksum, err := strconv.ParseUint(unsafeGetString(partsBytes), 16, 64)
if err != nil {
return Chunk{}, errors.Wrap(err, "parsing checksum")
}
return Chunk{
UserID: userID,
Fingerprint: model.Fingerprint(fingerprint),
From: model.Time(from),
Through: model.Time(through),
Checksum: uint32(checksum),
ChecksumSet: true,
}, nil
}

func readOneHexPart(hex []byte) (part []byte, i int) {
for i < len(hex) {
if hex[i] != ':' {
if hex[i] != ':' && hex[i] != '/' {
i++
continue
}
Expand All @@ -199,9 +276,23 @@ func unsafeGetString(buf []byte) string {
return *((*string)(unsafe.Pointer(&buf)))
}

// <user>/<period>/<shard>/<fprint>/<start>:<end>:<checksum>
func (c *Chunk) ExternalKey() string {
period := time.Hour
shard := uint64(c.Fingerprint) % 16
// reduce the fingerprint into the <period> space to act as jitter
jitter := uint64(c.Fingerprint) % uint64(period)
// The fingerprint (hash, uniformly distributed) allows us to gradually
// write into the next <period>, "warming" it up in a linear fashion wrt time.
// This means that if we're 10% into the current period, 10% of fingerprints will
// be written into the next period instead.
prefix := (uint64(c.From.UnixNano()) + jitter) % uint64(period)
return fmt.Sprintf("%s/%x/%x/%x/%x:%x:%x", c.UserID, prefix, shard, uint64(c.Fingerprint), int64(c.From), int64(c.Through), c.Checksum)
}

// ExternalKey returns the key you can use to fetch this chunk from external
// storage. For newer chunks, this key includes a checksum.
func (c *Chunk) ExternalKey() string {
func (c *Chunk) OldExternalKey() string {
// Some chunks have a checksum stored in dynamodb, some do not. We must
// generate keys appropriately.
if c.ChecksumSet {
Expand Down
16 changes: 16 additions & 0 deletions pkg/storage/chunk/chunk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,22 @@ func TestChunk_Slice(t *testing.T) {
}
}

func TestNewChunkKey(t *testing.T) {
c := Chunk{
Fingerprint: 100,
UserID: "fake",
From: model.TimeFromUnix(1000),
Through: model.TimeFromUnix(5000),
ChecksumSet: true,
Checksum: 12345,
}
key := c.ExternalKey()
newChunk, err := ParseExternalKey("fake", key)
require.Nil(t, err)
require.Equal(t, c, newChunk)
require.Equal(t, key, newChunk.ExternalKey())
}

func Benchmark_ParseExternalKey(b *testing.B) {
for i := 0; i < b.N; i++ {
_, err := ParseExternalKey("fake", "fake/57f628c7f6d57aad:162c699f000:162c69a07eb:eb242d99")
Expand Down