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

fetcher: add filter to remove old blocks if new overlapping blocks found #2071

Merged
merged 1 commit into from
Feb 5, 2020

Conversation

khyatisoneji
Copy link
Contributor

Changes

Add Filter to filter out old blocks if new overlapping blocks found that fully submatch the old blocks

Please let me know if this is the right direction to go in terms of filtering out the old blocks

Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Nice! Good work, this looks like it will work... but only for basic case of blocks:

[A:A] [B:B] [C:C] [D:ABC]

This is great however if we have only compaction levels higher than 2 we can have e.g:

[H:ABC][I:DFG][J:ABCDEFG] which will be not detected here.

I would say even for development purposes having a table unit test is really helpful for yoursefl! In this case I would even start with unit test (Test driven development!). You specify use cases, you implement the code and verify in seconds ❤️ This is what I would do (:

For your information this is what Prometheus/TSDB is doing when compacting two blocks together, how it combines Sources: https://github.com/prometheus/prometheus/blob/dee6981a6c78dfc28d000f86e6a6639cc37150b3/tsdb/compact.go#L332

return &CompactedBlockFilter{}
}

// Filter filters out blocks that filters out blocks that have newer block
Copy link
Member

Choose a reason for hiding this comment

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

Filter filters out blocks that filters out blocks? =D

Copy link
Member

Choose a reason for hiding this comment

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

Still not addressed ):

@@ -410,3 +411,52 @@ func (f *LabelShardedMetaFilter) Filter(metas map[ulid.ULID]*metadata.Meta, sync
delete(metas, id)
}
}

// CompactedBlockFilter is a MetaFetcher filter that filters out blocks that have newer block
Copy link
Member

Choose a reason for hiding this comment

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

have newer block from two or more overlappings blocks?

What does it mean for block to have blocks? 🤔

Copy link
Member

Choose a reason for hiding this comment

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

Not addressed.

for id, meta := range metas {
sources := make([]ulid.ULID, len(metas))
for _, sourceID := range meta.Compaction.Sources {
if sourceID == id {
Copy link
Member

Choose a reason for hiding this comment

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

Let's use sourceID.Compare(id) == 0 to be safe maybe (:

if areSlicesEqual(sources, meta.Compaction.Sources) {
for _, sourceID := range meta.Compaction.Sources {
if sourceID != id {
delete(metas, sourceID)
Copy link
Member

Choose a reason for hiding this comment

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

Next to deleting, we probably want store the deleted metas and check if we are ready to delete those blocks from bucket (:

@khyatisoneji khyatisoneji force-pushed the old_block_filter branch 6 times, most recently from bc34b89 to 31c1507 Compare February 1, 2020 15:18
Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Nice work!

There are some things to improve, but overall good work!

I would focus on:

  • simplifying vertex, remove forest thing - it's just another vertex.
  • addVertex method name is not enough, it does a lot more than just add.
  • avoid shallow functions/methods
  • Remove repetitions from test cases
  • Be consistent and clear on test cases names

But apart from that this looks amazing! (:

I think once you will address those comments (Of course if you don't agree with anything, just tell me!), And we will be happy with functionality we can look on optimizing things. I can see at least 4 places where we can save some CPU and memory, BUT micro optimization should be done based on numbers, so we will start with creating some benchmark, and then optimize it (:

Do you want to pair program sometime this week on the go benchmark? @khyatisoneji

cmd/thanos/compact.go Outdated Show resolved Hide resolved
metaFetcher, err := block.NewMetaFetcher(logger, 32, bkt, "", extprom.WrapRegistererWithPrefix("thanos_", reg),
block.NewLabelShardedMetaFilter(relabelConfig).Filter,
(&consistencyDelayMetaFilter{logger: logger, consistencyDelay: consistencyDelay}).Filter,
compactedBlockFilter.Filter,
Copy link
Member

Choose a reason for hiding this comment

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

Wonder if it wouldn't be beter if the Syncer would have internal method Filter and DuplicatedBlocks()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Syncer is not used by store component, so we need to add the filter there explicitly then

pkg/block/fetcher.go Outdated Show resolved Hide resolved
return &CompactedBlockFilter{}
}

// Filter filters out blocks that filters out blocks that have newer block
Copy link
Member

Choose a reason for hiding this comment

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

Still not addressed ):

pkg/block/fetcher_test.go Show resolved Hide resolved
},
},
{
name: "3 non compacted metas and compacted meta of level 2 in bucket",
Copy link
Member

Choose a reason for hiding this comment

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

again, meta is never technically compacted -> we need to consistent and clear in naming here. Also compaction does not matter here: Just what constains same sources.

For example this case could be named: Block 4 contains 1, 2, 3

Also I believe :

ULID: ULID(4),
						Compaction: tsdb.BlockMetaCompaction{
							Sources: []ulid.ULID{ULID(1), ULID(2), ULID(3), ULID(4)},
						},

.. is wrong. This will never happen as 4 is compacted block, so it will be never part of sources I believe (That's why I linked the Prometheus code which does it, to double check) (:

pkg/block/fetcher_test.go Show resolved Hide resolved
},
},
} {
f := NewCompactedBlockFilter()
Copy link
Member

Choose a reason for hiding this comment

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

Can we add case with 2 blocks having exactly the same sources?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, its added with test case name: two compacted blocks with same sources

rootSources := metas[vertex.ID].Compaction.Sources
sources := metas[id].Compaction.Sources

if f.containsSources(rootSources, sources) && f.containsSources(sources, rootSources) { // a block exists with same sources.
Copy link
Member

Choose a reason for hiding this comment

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

This line is quite long so maybe we can put comment above? And make it a full sentence? (Capital first letter)

for _, a := range s2 {
found := false
for _, e := range s1 {
if a.Compare(e) == 0 {
Copy link
Member

Choose a reason for hiding this comment

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

Nice. I think we can optimize a bit but this willl do for now (:

We can create some go benchmark for this in next PR 💪

@khyatisoneji khyatisoneji force-pushed the old_block_filter branch 3 times, most recently from efa1d15 to 57a001d Compare February 3, 2020 11:26
@khyatisoneji
Copy link
Contributor Author

@bwplotka I have addressed some concerns that you have made:

  1. Removing forest and having only vertex
  2. Name test cases with compacted blocks and not compacted metas.
  3. Removed the function for expected metas, currently only comparing the slices.
  4. Renamed ulidsToDelete with duplicateIds
  5. Rename CompactedBlockFilter with DeduplicateFilter

I have one suggestion that we should address deletion in a separate PR. Let's narrow down scope of this PR to only filter out the duplicate blocks.
Deletion of the blocks, as you mentioned should be done with checks mentioned in the proposal, so adding them in a separate PR would be better.

@bwplotka
Copy link
Member

bwplotka commented Feb 3, 2020

I have one suggestion that we should address deletion in a separate PR. Let's narrow down scope of this PR to only filter out the duplicate blocks.
Deletion of the blocks, as you mentioned should be done with checks mentioned in the proposal, so adding them in a separate PR would be better.

Very good idea. But in this case we should not probably hook this filter yet in Compactor I think. We can hook in Store Gateway though.

// Filter filters out duplicate blocks that can be formed
// from two or more overlapping blocks that fully submatches the source blocks of the older blocks.
func (f *DeduplicateFilter) Filter(metas map[ulid.ULID]*metadata.Meta, synced GaugeLabeled, _ bool) {
forest := NewVertex(ulid.MustNew(uint64(0), nil), nil)
Copy link
Member

Choose a reason for hiding this comment

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

still forest name, maybe just root?

)

// Vertex type implements a vertex of a Forest.
type Vertex struct {
Copy link
Member

Choose a reason for hiding this comment

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

filename is still old (forest)

return ulids
}

func (f *Vertex) add(id ulid.ULID, startVertex *Vertex, metas map[ulid.ULID]*metadata.Meta) bool {
Copy link
Member

Choose a reason for hiding this comment

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

add is still cryptic. At least can we have a comment what this method does? (: Because it's not just vertex - it's a vertex that

Also:

Suggested change
func (f *Vertex) add(id ulid.ULID, startVertex *Vertex, metas map[ulid.ULID]*metadata.Meta) bool {
func (v *Vertex) add(id ulid.ULID, startVertex *Vertex, metas map[ulid.ULID]*metadata.Meta) bool {

return f.add(id, rootVertex, metas)
}

func (f *Vertex) containsSources(rootSources, sources []ulid.ULID) bool {
Copy link
Member

Choose a reason for hiding this comment

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

Does it have to be method? Also, I don't get why we need this at all this method does nothing ): It might too shallow

@khyatisoneji khyatisoneji force-pushed the old_block_filter branch 5 times, most recently from 32c58b6 to 4b69821 Compare February 3, 2020 18:18
Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Nice! Let's move add method maybe as suggested and fix test suggestions and we are good to start some benchmarking! What do you think? (: In next PRs obviously... Exciting!

pkg/block/fetcher.go Outdated Show resolved Hide resolved
})

for _, meta := range metaSlice {
root.AddMeta(meta.ULID, metas)
Copy link
Member

Choose a reason for hiding this comment

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

Still, I would vote to move it as a separate function (:

{
name: "two blocks with same sources",
input: map[ulid.ULID]*metadata.Meta{
ULID(3): {
Copy link
Member

Choose a reason for hiding this comment

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

not addressed

pkg/block/fetcher_test.go Show resolved Hide resolved
"github.com/thanos-io/thanos/pkg/block/metadata"
)

// Node type implements a node of a Forest.
Copy link
Member

Choose a reason for hiding this comment

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

forest still ):

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggestions? Node of a tree?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aren't both test cases relevant? The first one is for case where two blocks have same sources and second one is where the blocks have overlapping sources.

@khyatisoneji khyatisoneji force-pushed the old_block_filter branch 2 times, most recently from ce6ee20 to e5b6eb4 Compare February 3, 2020 19:13
return ulids
}

func (n *Node) add(id ulid.ULID, startNode *Node, metas map[ulid.ULID]*metadata.Meta, areEqual, isChildNode compareNodes) bool {
Copy link
Member

Choose a reason for hiding this comment

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

Ok, I said like 4 times to move this from node and it's still there and you did not explain why you are against this (:

Maybe I can send PR to your PR to show you how it can be done and show adventages? (:

Copy link
Member

Choose a reason for hiding this comment

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

The main problem is that this is just a node

It's not hierarchicalBlocksNode or anything like this. Method is also just add which suggests nothing is done expect just adding a node to the tree ... somewhere. This is might be not clear for readers of this code so we need to improve this.

Second argument why I believe it would nice to move add to separate function is extensibility. What if we would like to use our tree with meta.jsons data struct for other algorithms? We can't because add has certain logic baked in.

func TestDeduplicateFilter_Filter(t *testing.T) {
for _, tcase := range []struct {
name string
input map[ulid.ULID]tsdb.BlockMetaCompaction
Copy link
Member

Choose a reason for hiding this comment

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

Maybe just map[ulid.ULID][]ulid.ULID?

@khyatisoneji khyatisoneji force-pushed the old_block_filter branch 2 times, most recently from 90d8998 to 4a96f13 Compare February 4, 2020 12:00
Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Awesome! I think we need to fix todo and add more test cases and we are good to merge and play with some benchmarking ❤️

Thanks!

if metas[id] != nil {
f.DuplicateIDs = append(f.DuplicateIDs, id)
}
// TODO(khyati): Increment synced metrics with proper state.
Copy link
Member

Choose a reason for hiding this comment

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

todo

{
name: "compacted block with sources in bucket",
input: map[ulid.ULID][]ulid.ULID{
ULID(4): []ulid.ULID{ULID(1), ULID(2), ULID(3)},
Copy link
Member

Choose a reason for hiding this comment

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

Let's put other blocks here that we don't expect to be compacted

{
name: "two compacted blocks with same sources",
input: map[ulid.ULID][]ulid.ULID{
ULID(3): []ulid.ULID{ULID(1), ULID(2)},
Copy link
Member

Choose a reason for hiding this comment

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

ditto, let's mix it

{
name: "two compacted blocks with overlapping sources",
input: map[ulid.ULID][]ulid.ULID{
ULID(4): []ulid.ULID{ULID(1), ULID(2)},
Copy link
Member

Choose a reason for hiding this comment

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

ditto

name: "two compacted blocks with overlapping sources",
input: map[ulid.ULID][]ulid.ULID{
ULID(4): []ulid.ULID{ULID(1), ULID(2)},
ULID(5): []ulid.ULID{ULID(1), ULID(2), ULID(3)},
Copy link
Member

Choose a reason for hiding this comment

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

Can we reorder ULIDs in Source to check if that will work?

{
name: "3 compacted blocks of level 2 and one compacted block of level 3 in bucket",
input: map[ulid.ULID][]ulid.ULID{
ULID(10): []ulid.ULID{ULID(1), ULID(2), ULID(3)},
Copy link
Member

Choose a reason for hiding this comment

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

ditto, let's mix with some block that should stay

input: map[ulid.ULID][]ulid.ULID{
ULID(1): []ulid.ULID{ULID(1)},
ULID(5): []ulid.ULID{ULID(1), ULID(2)},
ULID(6): []ulid.ULID{ULID(1), ULID(2), ULID(3), ULID(4)},
Copy link
Member

Choose a reason for hiding this comment

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

Let's reorder those a bit to make it more tricky. Yes we put it in map anyway, but sometimes map tends to be.. accidently ordered (:

} {
f := NewDeduplicateFilter()
if ok := t.Run(tcase.name, func(t *testing.T) {
synced := prometheus.NewGaugeVec(prometheus.GaugeOpts{}, []string{"state"})
Copy link
Member

Choose a reason for hiding this comment

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

We can assert on this metric!

See our talk: https://are-you-testing-your-observability.now.sh/

Click right until 26 slide and check it out (:

ULID(5): []ulid.ULID{ULID(1), ULID(2)},
ULID(6): []ulid.ULID{ULID(1), ULID(2), ULID(3), ULID(4)},
ULID(7): []ulid.ULID{ULID(1), ULID(2), ULID(3)},
ULID(8): []ulid.ULID{ULID(1), ULID(2), ULID(3), ULID(4)},
Copy link
Member

Choose a reason for hiding this comment

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

Let's also swap ULID(7) to ULID(8) (: Make it more tricky!

ULID(7): []ulid.ULID{ULID(1), ULID(2), ULID(3)},
ULID(8): []ulid.ULID{ULID(1), ULID(2), ULID(3), ULID(4)},
},
expected: []ulid.ULID{
Copy link
Member

Choose a reason for hiding this comment

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

Can we add one more case?

The case would be something like

10 containing 1,2,6,7
11 containing 1,2,6,8
(:
And maybe:
10 containing 1,2,6,7
11 containing 1,2,3

@khyatisoneji
Copy link
Contributor Author

Added the requested changes:

  1. implemented todo to add sync metric for filtered duplicates
  2. Reordered ulids in test
  3. Mixed compacted and non compacted ulids in test
  4. Added requested test additions
  5. added test to test metric for filtered duplicates.

Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Let's go...!

Solid work, thanks! 👍

}

func compareSliceWithMapKeys(tb testing.TB, m map[ulid.ULID]*metadata.Meta, s []ulid.ULID) {
_, file, line, _ := runtime.Caller(1)
Copy link
Member

Choose a reason for hiding this comment

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

Wow this is epic! Probably bit over-engineered, but... thanks! (:

@bwplotka bwplotka merged commit 6948d5b into thanos-io:master Feb 5, 2020
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