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

core: implement ChainIndexer #14522

Merged
merged 2 commits into from
Aug 8, 2017
Merged

core: implement ChainIndexer #14522

merged 2 commits into from
Aug 8, 2017

Conversation

zsfelfoldi
Copy link
Contributor

This PR is an alternative version of #14431 that only uses the event system for updating sections and does not use the chain mutex or modify BlockChain/HeaderChain/LightChain at all.
A rebased version of the bloombits filter can be found on the https://github.com/zsfelfoldi/go-ethereum/commits/bloombits2 branch.

@zsfelfoldi zsfelfoldi requested review from fjl and Arachnid May 26, 2017 10:34
@karalabe
Copy link
Member

I would really really like to see thorough tests on any new stuff added to core.

@zsfelfoldi
Copy link
Contributor Author

@karalabe you are right, although this is not consensus stuff and could be placed in any package but it deserves a test so I added one. Also, it found problems in some corner cases which I have fixed now :)

@zsfelfoldi zsfelfoldi requested a review from karalabe May 28, 2017 12:35
@fjl fjl modified the milestone: 1.6.3 May 31, 2017
@karalabe karalabe modified the milestones: 1.6.4, 1.6.3 Jun 1, 2017
@karalabe
Copy link
Member

Uhm, @zsfelfoldi you did see that all the tests failed, right?

@Arachnid
Copy link
Contributor

@karalabe It looks like that was just timeouts?

}

// ChainIndex interface is a backend for the indexer doing the actual post-processing job
type ChainIndex interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like it should be a verb rather than a noun. Maybe ChainIndexerBackend or somesuch?

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 already had a round about namings with @fjl and this is what we settled on but you're probably right. I renamed it to ChainIndexerBackend until a better suggestion comes up :)

func NewChainIndexer(db ethdb.Database, dbKey []byte, backend ChainIndex, sectionSize, confirmReq uint64, procWait time.Duration, stop chan struct{}) *ChainIndexer {
c := &ChainIndexer{
db: db,
validSectionsKey: append(dbKey, []byte("-count")...),
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 use the table interface here, to help avoid the spread of prefixes all over the place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I also need the unprefixed chain db so I'm passing two Databases (chainDb, indexDb) but it feels nice because now it's clear what those databases are used for.

lastSectionHead = c.getSectionHead(c.calcIdx - 1)
}

c.lock.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

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

This screams race condition to me. Is there any way to avoid this? Are you sure it's safe?

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 think it is safe but now that I think about it maybe I don't need so many locks any more since I changed the way newHead and CanonicalSections work. I think I can simplify 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.

On second thought, I'd leave it like this, see above. Do you see any explicit race conditions? Or are you just concerned about temporarily releasing the lock? I considered the possibility that new head/rollback events can happen while processing and I think the code handles this appropriately (see the comments before processSection).

case <-c.stop:
return
case <-c.tryUpdate:
c.lock.Lock()
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we avoid this lock entirely by message passing, so each value is only touched by a single process?

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 given it some thought but I think it's better to keep it like this. The update loop is a slow loop that blocks while processing sections so I don't want to pass all new head events, only send a "wake up" signal to tryUpdate channel if necessary (when updating flag is false and there is something new to update). Also, it's not ideal to process all new head events sequentially because some new sections might have already been rolled back by the time the update loop gets there.


if c.targetCount > c.stored {
go func() {
time.Sleep(c.procWait)
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the purpose of waiting here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It makes sense when generating an index for a large number of sections. It is a simple but effective way of ensuring that it will leave some processing power for the other tasks too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you not do the equivalent of 'yield' here instead? Building in hardcoded waits does not seem like a great pattern to me.

Copy link
Contributor Author

@zsfelfoldi zsfelfoldi Jul 8, 2017

Choose a reason for hiding this comment

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

I think you mean https://golang.org/pkg/runtime/#Gosched but I'm not sure if that would help. After taking up something like a hundred milliseconds to process a section, it waits a scheduler queue round, which might not be a lot.
I understand that this is not a nice solution but I can't think of an effective, easy and nice solution right now. When updating an entire database in the background, I experienced annoyingly slow console response, adding a sleep was an easy fix to a problem that only occurs during db upgrade.


// processSection processes an entire section by calling backend functions while ensuring
// the continuity of the passed headers. Since the chain mutex is not held while processing,
// the continuity can be broken by a long reorg, in which case the function returns with ok == false.
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a race condition here where the reorg happens after you check for it, isn't there?

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 think it's safe (CanonicalSections always checks for the current canonical hashes and rolls back if needed).

Copy link
Contributor

Choose a reason for hiding this comment

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

This function can definitely return true when the chain is actually no longer canonical, though. What are the consequences of that? If they're insignificant, why return a status at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that it also returns a section head hash. The "ok" flag means that it has processed a valid, continuous chain of headers (parent hashes are matching), which means that IF the returned section head (hash of the last block in the section) is still canonical then the results are also canonical.
setSectionHead stores the last known section head (known to the indexer), then setValidSections marks that we have a new processed valid, continuous chain section (not necessarily canonical). Later, when someone wants to know the number of currently canonical sections, CanonicalSections checks if the last known valid sections are actually still canonical.

}

// getValidSections reads the number of valid sections from the index database
func (c *ChainIndexer) getValidSections() uint64 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we not cache these in memory, rather than reading them off disk each time?

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 don't think it makes sense, it is not called so frequently.

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 only two integers that are usually written from the same process. For that matter, wouldn't it make more sense to write a config struct, rather than a separate entry for each?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not two integers. There's one integer at key "count" and one section head hash entry for each section, at key "shead" + sectionIdx (big endian uint64).

@fjl fjl modified the milestones: 1.6.7, 1.7.0 Jul 17, 2017
@karalabe
Copy link
Member

karalabe commented Aug 3, 2017

@Arachnid PTAL, we've polished up the PR a bit, it's ready from my perspective. More exhaustive tests would be nice, but let's build on top and test, not hold it off.

@ethereum ethereum deleted a comment from GitCop Aug 5, 2017
@ethereum ethereum deleted a comment from GitCop Aug 5, 2017
@ethereum ethereum deleted a comment from GitCop Aug 5, 2017
@ethereum ethereum deleted a comment from GitCop Aug 5, 2017
@ethereum ethereum deleted a comment from GitCop Aug 5, 2017
@ethereum ethereum deleted a comment from GitCop Aug 5, 2017
@ethereum ethereum deleted a comment from GitCop Aug 5, 2017
@ethereum ethereum deleted a comment from GitCop Aug 5, 2017
@fjl fjl merged commit 374c49e into ethereum:master Aug 8, 2017
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.

4 participants