Skip to content
This repository was archived by the owner on Aug 13, 2019. It is now read-only.

Fix issues (1), (2) and (3) described in #328 #329

Closed
wants to merge 10 commits into from

Conversation

free
Copy link
Contributor

@free free commented May 9, 2018

Fix issues (1), (2) and (3) described in #328

closes #328

Signed-off-by: Alin Sinpalean [email protected]

@free
Copy link
Contributor Author

free commented May 14, 2018

Ping?

Copy link
Collaborator

@gouthamve gouthamve left a comment

Choose a reason for hiding this comment

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

Sorry I took longer to review, was busy with a couple of things. LGTM, except for minor nits.

Thanks for this!

querier.go Outdated
iCur := it.i
for ; it.i < len(it.chunks); it.i++ {
// Skip chunks with MaxTime < t.
if it.chunks[it.i].MaxTime >= t {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can be simplified to:

if it.chunks[it.i].MaxTime >= t {
  continue
}
// Don't reset the iterator unless we've moved on to a different chunk.
if it.i != iCur {
	it.cur = it.chunks[it.i].Chunk.Iterator()
	if len(it.intervals) > 0 {
		it.cur = &deletedIterator{it: it.cur, intervals: it.intervals}
	}
}

for it.cur.Next() {
	t0, _ := it.cur.At()
	if t0 > it.maxt || it.cur.Err() != nil {
		return false
	}
	if t0 >= t {
		return true
	}
}

This reduces the indentation and makes it easier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, thanks.

@@ -987,7 +1026,7 @@ func TestSeriesIterator(t *testing.T) {
{10, 22}, {203, 3493},
},

seek: 203,
seek: 101,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why would this return false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the [mint, maxt] range is [2, 202] and there are no points in the [101, 202] range.

Previously the test was doing a seek() past maxt, which returned immediately because there is an explicit check for that. With the earlier code seek(101) was returning true and positioning the iterator at time 203, which was incorrect.

Copy link
Contributor Author

@free free left a comment

Choose a reason for hiding this comment

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

All done, PTAL.

querier.go Outdated
iCur := it.i
for ; it.i < len(it.chunks); it.i++ {
// Skip chunks with MaxTime < t.
if it.chunks[it.i].MaxTime >= t {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, thanks.

@@ -987,7 +1026,7 @@ func TestSeriesIterator(t *testing.T) {
{10, 22}, {203, 3493},
},

seek: 203,
seek: 101,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the [mint, maxt] range is [2, 202] and there are no points in the [101, 202] range.

Previously the test was doing a seek() past maxt, which returned immediately because there is an explicit check for that. With the earlier code seek(101) was returning true and positioning the iterator at time 203, which was incorrect.

@free
Copy link
Contributor Author

free commented May 24, 2018

Ping?

@fabxc
Copy link
Contributor

fabxc commented May 28, 2018

@gouthamve ^ can you take a look at this?

@fabxc
Copy link
Contributor

fabxc commented May 28, 2018

@free thanks for all the analysis in the issue – seems we've missed quite a few things.
In (1) you mention a particular test that even just adapts the expected result to pass. If this PR fixes (1) though, why is the test not fixed but still passes?

@free
Copy link
Contributor Author

free commented Jun 5, 2018

[Apologies for dropping this, GitHub (or some mail server) decided I'm probably not interested in email notifications from this PR.]

In (1) you mention a particular test that even just adapts the expected result to pass. If this PR fixes (1) though, why is the test not fixed but still passes?

Seems like that particular test was actually only confusing rather than actually broken: the test code at line 1066 actually filters out samples that fall outside [mint, maxt] from the expected output (when testing a chunked iterator), but not below (when testing a chained iterator, which is supposed to return the t = 3 value).

I'd rather not go down the rabbit hole of rewriting the unit tests, though. I do have a day job that doesn't involve as much Prometheus as I might otherwise like.

@alin-amana
Copy link
Contributor

@gouthamve @fabxc: Ping?

return false
}
// Return early if already past t.
if t0, _ := it.cur.At(); t0 >= t {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just realized that this might call At() on an iterator that Next() or Seek() might not have been called on. It works if one assumes all timestamps to be strictly positive and that the iterator will return a timestamp of 0 when uninitialized.

Should I remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, crap. This is the thing that makes Seek() not advance the iterator when seeking for a timestamp that's in the past (compared to the iterator's position).

So it looks to me like we need to change the contract of either chunkenc.Iterator.At() to return something like math.MinInt64; or that of tsdb.SeriesIterator.Seek() to always advance the iterator. The latter seems more radical.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've changed chunkenc.Iterator.At() to always return math.MinInt64 before it has advanced (and documented it as such). Everything is good in the world again.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yep, before the contract used to be that At() shouldn't be called before Next() or Seek() and it shouldn't be called after they return false.

I like the newer contract of returning math.MinInt64 for non-initialised Iterators.

querier_test.go Outdated
@@ -1116,6 +1155,24 @@ func TestChunkSeriesIterator_DoubleSeek(t *testing.T) {
testutil.Equals(t, float64(2), v)
}

// Regression for: https://github.com/prometheus/tsdb/pull/327
Copy link
Contributor

@krasi-georgiev krasi-georgiev Jun 11, 2018

Choose a reason for hiding this comment

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

I think the test itself explains the desired behaviour so don't think we even need to link it to the issue.

/pull/327 was closed so If you prefer to keep it there maybe point this to https://github.com/prometheus/tsdb/issues/328

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.

@@ -783,6 +783,9 @@ func newChunkSeriesIterator(cs []chunks.Meta, dranges Intervals, mint, maxt int6

func (it *chunkSeriesIterator) Seek(t int64) (ok bool) {
if t > it.maxt {
it.i = len(it.chunks)
}
if it.cur.Err() != nil || it.i >= len(it.chunks) {
return false
}
Copy link
Contributor

Choose a reason for hiding this comment

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

so here we are first advancing and than failing. Normally my logic would be that if an operation failed it shouldn't change the internal state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're not failing, we're simply advancing to the requested timestamp and returning false, meaning there are no more values.

If we would not advance, then a Seek before maxt following a Seek past maxt would incorrectly position the iterator on a valid value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree with @free and adding exactly same thing to #665

@alin-amana
Copy link
Contributor

Any chance of getting this merged?

Although please note that due to the requirement that Seek() not advance the iterator unless necessary, I have had to change the definition of chunkenc.Iterator.At() (and the behavior of its 2 implementations) to return math.MinInt64 before the iterator is advanced.

@gouthamve gouthamve dismissed their stale review July 23, 2018 05:32

Newer commits

querier.go Outdated
}
}
return false
}

func (it *chunkSeriesIterator) At() (t int64, v float64) {
// Should it panic if it.cur.Err() != nil || it.i >= len(it.chunks)?
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, it is now OK to call this at any time. Done.

return false
}
// Return early if already past t.
if t0, _ := it.cur.At(); t0 >= t {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yep, before the contract used to be that At() shouldn't be called before Next() or Seek() and it shouldn't be called after they return false.

I like the newer contract of returning math.MinInt64 for non-initialised Iterators.

@free
Copy link
Contributor Author

free commented Sep 12, 2018 via email

@@ -658,7 +658,7 @@ func (s *chunkSeries) Iterator() SeriesIterator {
type SeriesIterator interface {
// Seek advances the iterator forward to the given timestamp.
// If there's no value exactly at t, it advances to the first value
// after t.
// after t. Has no effect if already past t.
Copy link
Contributor

Choose a reason for hiding this comment

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

// Seek advances the iterator forward to the given timestamp.
// If there's no value exactly at t, it advances to the first value after t. 
// Seek has no effect if already past t.

something like this reads nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

Added to #665 as well

},
chunks: [][]sample{
{
{1, 2}, {10, 11},
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
{1, 2}, {10, 11},
{1, 1}, {10, 10},

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}

// Seek for 3, then 2. Iterator should remain positioned on 3.
res := newChunkSeriesIterator(chkMetas, nil, 2, 8)
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of this can you use the createBlock to create an actual block than just open a db with this block and run the querier and seek against that.
I prefer to use only exported API methods to be as close as possible the expected behaviour for anyone using the library.
the same for TestChunkSeriesIterator_SeekWithMinTime

I know many tests use internal implementation, but I am trying to change that slowly to make these easier to maintain and understand so for any new tests I want to use only exported methods to ensure the correct behaviour.

@@ -64,6 +65,7 @@ type Appender interface {

// Iterator is a simple iterator that can only get the next value.
type Iterator interface {
// At returns (math.MinInt64, 0.0) before the iterator has advanced.
Copy link
Contributor

Choose a reason for hiding this comment

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

should this also be mentioned for the SeriesIterator interface ?

also we need a test to ensure that calling At before Next or Seek returns math.MinInt64

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should this also be mentioned for the SeriesIterator interface ?

Done.

also we need a test to ensure that calling At before Next or Seek returns math.MinInt64

Added checks (not separate tests) for all Iterator and SeriesIterator implementations. Let me know if you prefer separate tests or e.g. extracting the check into expandSeriesIterator() instead.

Copy link
Contributor

@bwplotka bwplotka Aug 13, 2019

Choose a reason for hiding this comment

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

adding this to #665

@@ -756,7 +795,7 @@ func TestSeriesIterator(t *testing.T) {
b: []tsdbutil.Sample{},
c: []tsdbutil.Sample{},

seek: 0,
seek: 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

do you remember why you needed to change that?

the test passes even with seek: 0

t0, _ := it.cur.At()
if t0 >= t {
return true
// Don't reset the iterator unless we've moved on to a different chunk.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Don't reset the iterator unless we've moved on to a different chunk.
// Reset the iterator when moved to a different chunk.

I find this a tiny more clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

adjuste #665 as well, although code looks different (less indents).

1: Intervals{{1, 3}},
2: Intervals{{1, 3}, {6, 10}},
1: Intervals{{1, 2}},
2: Intervals{{2, 3}, {6, 10}},
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this change required?

@krasi-georgiev
Copy link
Contributor

ping @free

@free
Copy link
Contributor Author

free commented Mar 19, 2019

@krasi-georgiev, as things stand right now, it looks like I have about one more week of heads-down effort on my work project. But I will get back to this as soon as I'm done with 14 hour days. Apologies.

@krasi-georgiev
Copy link
Contributor

@free no problem , thanks for letting me know.

@krasi-georgiev
Copy link
Contributor

@free is this ready for another review? Not sure if the comments I left are still relevant after the latest changes.

Copy link
Contributor Author

@free free left a comment

Choose a reason for hiding this comment

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

@free is this ready for another review? Not sure if the comments I left are still relevant after the latest changes.

Nope, not yet, I've only addressed some of the comments. And, as it turns out, I haven't pushed my changes yet.

But since this will submit all my comments (without the underlying code changes, which are on my home computer) I might as well ask what's your preference for "a test to ensure that calling At before Next or Seek returns math.MinInt64": currently I've added explicit checks wherever an Iterator or SeriesIterator is created, just before it is unrolled by expandSeriesIterator(). Would you prefer for the test to be within expandSeriesIterator() (and thus for a *testing.T parameter to be added to it)? Or just for single, separate tests for every iterator implementation?

@@ -64,6 +65,7 @@ type Appender interface {

// Iterator is a simple iterator that can only get the next value.
type Iterator interface {
// At returns (math.MinInt64, 0.0) before the iterator has advanced.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

should this also be mentioned for the SeriesIterator interface ?

Done.

also we need a test to ensure that calling At before Next or Seek returns math.MinInt64

Added checks (not separate tests) for all Iterator and SeriesIterator implementations. Let me know if you prefer separate tests or e.g. extracting the check into expandSeriesIterator() instead.

t0, _ := it.cur.At()
if t0 >= t {
return true
// Don't reset the iterator unless we've moved on to a different chunk.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -658,7 +658,7 @@ func (s *chunkSeries) Iterator() SeriesIterator {
type SeriesIterator interface {
// Seek advances the iterator forward to the given timestamp.
// If there's no value exactly at t, it advances to the first value
// after t.
// after t. Has no effect if already past t.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

},
chunks: [][]sample{
{
{1, 2}, {10, 11},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@krasi-georgiev
Copy link
Contributor

krasi-georgiev commented Jun 18, 2019

Nope, not yet,

ping me when ready for a review and I spend some time for a proper review.

@bwplotka
Copy link
Contributor

Ok, I added all the improvements from here to #665 so closing this. Also we are merging prometheus/prometheus#5805 so I need to redo my PR anyway as well on Prometheus later on.

cc @krasi-georgiev @free @codesome

@bwplotka bwplotka closed this Aug 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

tsdb: SeriesIterator.Seek() is vaguely defined
6 participants