Skip to content

Modify unchecked diffs query#84

Merged
elizabethengelman merged 12 commits intostagingfrom
modify-unchecked-diffs-query
Apr 30, 2020
Merged

Modify unchecked diffs query#84
elizabethengelman merged 12 commits intostagingfrom
modify-unchecked-diffs-query

Conversation

@elizabethengelman
Copy link
Copy Markdown

@elizabethengelman elizabethengelman commented Apr 28, 2020

Proof of concept that this approach will work for not returning diffs from GetNewDiffs if they're from blocks that are "old". Randomly chose the threshold of "old" to be from blocks that are greater than 500 back from the most recent header we have in the system, but totally happy to rethink this/do some more digging into a more appropriate threshold.

func (repository diffRepository) GetFirstDiffForBlockHeight(blockHeight int64) (types.PersistedDiff, error) {
var diff types.PersistedDiff
err := repository.db.Get(&diff,
`SELECT * FROM public.storage_diff WHERE checked IS false AND block_height = $1 LIMIT 1`, blockHeight)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

wondering if it might make sense to say block_height >= $1 ORDER BY block_height ASC LIMIT 1 in case we don't have a diff at exactly that block height

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

ah, good catch!

Copy link
Copy Markdown

@rmulhol rmulhol left a comment

Choose a reason for hiding this comment

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

Awesome work, and impressive turnaround! 😎

My main area of interest is minimizing the likelihood of GetFirstDiffForBlockHeight (or, less likely, GetFirstDiffForBlockHeight) returning an error (esp something like sql.ErrNoRows) so that we don't kill/restart the container unless things are really out of whack.

Other than that I'm curious how we can minimize the effort needed to tune the BlocksBackFromHead number up or down depending on observed results.

Overall, LGTM! 👍

return getHeaderErr
}
blockNumber := mostRecentHeader.BlockNumber - BlocksBackFromHead
diff, getDiffErr := watcher.StorageDiffRepository.GetFirstDiffForBlockHeight(blockNumber)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Don't know if this makes any difference but maybe this function could just return the ID if that's all we want from the diff

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good call - I had initially thought that this method could be useful in the future if it returned the whole diff, but we'll let future us deal with that, just in case it never is necessary.

var ResultsLimit = 500
var (
ResultsLimit = 500
BlocksBackFromHead = int64(500)
Copy link
Copy Markdown

@rmulhol rmulhol Apr 29, 2020

Choose a reason for hiding this comment

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

Thinking that we might want this number to be a good bit higher - at least the first time we run this, since it's possible we could be at a really bad point in terms of cycling through the unchecked (and un-transformable) diffs. I wonder if maybe instead of skipOldDiffs being a bool, it could be an int64 configurable via the CLI? Then we could easily bump the number up or down depending on the results we observe

Copy link
Copy Markdown
Author

@elizabethengelman elizabethengelman Apr 29, 2020

Choose a reason for hiding this comment

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

I really like the idea of passing in an int instead!

minID := 0
var minID int
if watcher.SkipOldDiffs {
mostRecentHeader, getHeaderErr := watcher.HeaderRepository.GetMostRecentHeader()
Copy link
Copy Markdown

@rmulhol rmulhol Apr 29, 2020

Choose a reason for hiding this comment

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

Maybe this could just return the block number?

Previously we were passing the id of the first diff for a given block to
GetNewDiffs, but this method is looking at diffs with ids > than the
given minID, so the diff with the passed minID isn't included, which is
fine for normal circumstances, since it starts at 0.

minID = int(diffID)
minID = int(diffID - 1)
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Just pushed this change up for now... wondering if a cleaner approach would be to change the GetNewDiffs query to look at diffs where id >= instead of just >, but think I need to look at that again with fresh eyes tomorrow.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Makes sense to prefer >= to > 👍

Not sure I follow why we need to set minID to diffID - 1 though?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Cool, i'll make update to >=.
I needed to change the minID to diffID - 1 because when passing in a block to start looking at diffs, we would use the id from the first diff for that block. Then if we pass that id to GetNewDiffs it would look up diffs with an id > than the given id, so that diff would never be processed. So by making the minID one less than that diff's id, it would ensure that it would get processed. Does that make sense?

All that being said, changing GetNewDiffs to be inclusive for the minID seems a lot easier.

Copy link
Copy Markdown
Author

@elizabethengelman elizabethengelman Apr 30, 2020

Choose a reason for hiding this comment

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

Actually now that I look at it a bit closer, if we change GetNewDiffs to look at ids >= minID then with the current watcher implementation we'd end up rechecking the last diff on the collection each time... or we'd need to increment the minID to avoid that. 🤔 Really not sure which approach is clearer.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

just writing out the 2 options again to help myself understand the two approaches:

  1. if we keep GetNewDiffs as is with id > minId then we will need to pass in an id less than the diffId that we’re getting from GetFirstDiffIDForBlockHeight otherwise that id won’t ever be included in GetNewDiffs, so that is why I was passing in firstDiffId -1

  2. on the flip side, if we change GetNewDiffs to do id >= minId then whenever we pass in an updated minID which is the last id from the set of diffs returned, then we’d end up returning that last diff from GetNewDiffs again. So it seems like we’d need to pass in lastDiffId +1 as the new diff id

err := repository.db.Get(&diffID,
`SELECT id FROM public.storage_diff WHERE checked IS false AND block_height >= $1 LIMIT 1`, blockHeight)
`SELECT id FROM public.storage_diff WHERE block_height >= $1 LIMIT 1`, blockHeight)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I figured that this method doesn't necessarily need to care if a diff is checked or not, since we're doing that anyway with GetNewDiffs. Also, was seeing some failures locally when execute caught up with all unchecked diffs, this method would fail because it didn't have an sql results.

This also got me thinking that we'll probably need to tweak this method and MostRecentHeaderBlockNumber for fresh deployed systems - again, will plan to look at that again tomorrow with fresh eyes.

Copy link
Copy Markdown

@rmulhol rmulhol Apr 30, 2020

Choose a reason for hiding this comment

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

Nice catch! Makes me think maybe we want to handle sql.ErrNoRows upstream, since I imagine we could probably end up in this situation in other weird scenarios like if the interval is low and there aren't a lot of diffs being generated.

Also 👍 to thinking about freshly deployed systems. I wish it were as simple as updating docs to suggest not using this feature in a fresh deploy, but suppose that might not be feasible if we're defining the interval in the image. Maybe we could require the interval to be set via an env var that's passed to the image?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Was just checking out the PR updating the image and that looks legit - if we default to -1 but enable config, then fresh deployed systems can just not use this feature?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yeah, that's a good idea. also, i added a guard in the watcher so that if either the headers or the storage_diff table returns a sql.NoRowsErr we just set the minID to 0, so at least the process won't fall over

RawDiff: fakeRawDiff,
ID: rand.Int63(),
Checked: true,
Checked: false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🥇 👀

Expect(insertErr).NotTo(HaveOccurred())

blockBeforeDiffBlockHeight := int64(fakeRawDiff.BlockHeight - 1)
_, diffErr := repo.GetFirstDiffIDForBlockHeight(blockBeforeDiffBlockHeight)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Maybe worth indicating here what is returned? Assuming it's the same as when the diff is unchecked?

var ResultsLimit = 500
var (
ResultsLimit = 500
BlocksBackFromHead = int64(500)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Can probably remove this now?

func (repository HeaderRepository) GetMostRecentHeaderBlockNumber() (int64, error) {
var blockNumber int64
err := repository.database.Get(&blockNumber,
`SELECT block_number FROM headers ORDER BY block_number DESC LIMIT 1`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Any idea how this compares performance-wise with select max(block_number) from headers?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

good question! looks like SELECT block_number FROM headers ORDER BY block_number DESC LIMIT 1 may be slightly more performant?

explain SELECT block_number FROM headers ORDER BY block_number DESC LIMIT 1;

Limit  (cost=0.43..0.47 rows=1 width=8)
  ->  Index Only Scan Backward using headers_block_number_eth_node_id_key on headers  (cost=0.43..113428.77 rows=2925850 width=8)

=======================

 explain select max(block_number) from headers;
Result  (cost=0.47..0.48 rows=1 width=8)
  InitPlan 1 (returns $0)
    ->  Limit  (cost=0.43..0.47 rows=1 width=8)
          ->  Index Only Scan Backward using headers_block_number_eth_node_id_key on headers  (cost=0.43..120743.40 rows=2925850 width=8)
                Index Cond: (block_number IS NOT NULL)

It("returns an error if it fails to get the most recent header", func() {
_, err := repo.GetMostRecentHeaderBlockNumber()
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(sql.ErrNoRows))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This seems like potentially an issue for freshly deployed systems as well, if execute starts before header sync. Wondering if we want to handle sql.ErrNoRows in the storage watched from both of these repository calls - seems like maybe we could delay + retry, set minID to 0, or something

@elizabethengelman elizabethengelman force-pushed the modify-unchecked-diffs-query branch from 6720333 to d9f7c78 Compare April 30, 2020 16:43
minID = int(diffID - 1)
}

return minID, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

So if DiffBlocksFromHeadOfChain is -1 you'll return for minID. Is that deliberate?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, the idea is that if DidffBlocksFromHeadOfChain is -1 then we'll want the minID to be 0. Golang sets the starting value of an int to 0 if you don't define it (line 98), but perhaps it would be clearer to be explicit and set var minID= 0 instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think it's the "-1" that's bothering me - it's a magic number that the execute.go file has to know about to start from the beginning.

Honestly it's not super important, just mildly confusing.

Copy link
Copy Markdown
Author

@elizabethengelman elizabethengelman Apr 30, 2020

Choose a reason for hiding this comment

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

That's super fair w/r/t the magic number! I've added a comment explaining a bit more, and may also pull the 1 out into a variable/constant to give a bit more context.

@elizabethengelman elizabethengelman force-pushed the modify-unchecked-diffs-query branch from d9f7c78 to 88802b7 Compare April 30, 2020 17:43
Copy link
Copy Markdown

@paytonrules paytonrules left a comment

Choose a reason for hiding this comment

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

There's a lot of context I don't understand here yet, but LGTM.

@elizabethengelman elizabethengelman force-pushed the modify-unchecked-diffs-query branch from 88802b7 to ecfa286 Compare April 30, 2020 18:44
@elizabethengelman elizabethengelman force-pushed the modify-unchecked-diffs-query branch 2 times, most recently from 6db0f51 to 307df45 Compare April 30, 2020 22:23
@elizabethengelman elizabethengelman merged commit a6b3c0b into staging Apr 30, 2020
@elizabethengelman elizabethengelman deleted the modify-unchecked-diffs-query branch April 30, 2020 22:41
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.

3 participants