Skip to content

Conversation

@ssd04
Copy link
Contributor

@ssd04 ssd04 commented Mar 13, 2025

Reasoning behind the pull request

  • Remove sort operation when adding to pool
  • Added more unit tests to proofsCache

Testing procedure

  • to be tested with feat branch

Pre-requisites

Based on the Contributing Guidelines the PR author and the reviewers must check the following requirements are met:

  • was the PR targeted to the correct branch?
  • if this is a larger feature that probably needs more than one PR, is there a feat branch created?
  • if this is a feat branch merging, do all satellite projects have a proper tag inside go.mod?

@ssd04 ssd04 self-assigned this Mar 13, 2025
Comment on lines 26 to 44
func (p *proofNonceBucket) insertInNew(proof data.HeaderProofHandler) {
p.insert(proof)
p.maxNonce = proof.GetHeaderNonce()
}

func (p *proofNonceBucket) insertInExisting(proof data.HeaderProofHandler) {
p.insert(proof)

if proof.GetHeaderNonce() > p.maxNonce {
p.maxNonce = proof.GetHeaderNonce()
}
}

func (p *proofNonceBucket) insert(proof data.HeaderProofHandler) {
p.proofsByNonce = append(p.proofsByNonce, &proofNonceMapping{
headerHash: string(proof.GetHeaderHash()),
nonce: proof.GetHeaderNonce(),
})
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

why not move

if proof.GetHeaderNonce() > p.maxNonce {
	p.maxNonce = proof.GetHeaderNonce()
}

into insert method and only keep one method for all 3 operations?

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, pushed

sstanculeanu
sstanculeanu previously approved these changes Mar 17, 2025
@@ -0,0 +1,66 @@
package proofscache_test
Copy link
Contributor

Choose a reason for hiding this comment

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

If we move the test to package proofscache, maybe we can drop some code from the export_test file. Can also stay as it is.

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 would keep it in _test package since there are other useful functions from test

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and we plan to make this component more generic and to export it in other packages as well (for headers pool)

Comment on lines +10 to +20
func Benchmark_AddProof_Bucket10_Pool1000(b *testing.B) {
benchmarkAddProof(b, 10, 1000)
}

func Benchmark_AddProof_Bucket100_Pool10000(b *testing.B) {
benchmarkAddProof(b, 100, 10000)
}

func Benchmark_AddProof_Bucket1000_Pool100000(b *testing.B) {
benchmarkAddProof(b, 1000, 100000)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe also add some output / statistics in the PR description.

Comment on lines 15 to 16
mutProofsByNonce sync.RWMutex
proofsByNonceBuckets []*proofNonceBucket
Copy link
Contributor

Choose a reason for hiding this comment

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

Alternatively, maybe sync.Map is useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the way proofsByNonceBuckets is implemented right now, it needs order, so it doesn't fit very well

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, you are right. No maps here 👍

Comment on lines 26 to 37
func (p *proofNonceBucket) insertInNew(proof data.HeaderProofHandler) {
p.insert(proof)
p.maxNonce = proof.GetHeaderNonce()
}

func (p *proofNonceBucket) insertInExisting(proof data.HeaderProofHandler) {
p.insert(proof)

if proof.GetHeaderNonce() > p.maxNonce {
p.maxNonce = proof.GetHeaderNonce()
}
}
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 merge them into a single insert function or does it break the logic? If it was done for the purpose of optimizing the nonce check - the if proof.GetHeaderNonce() > p.maxNonce { is just a quick comparison (should be negligible).

Copy link
Contributor

Choose a reason for hiding this comment

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

Outdated comment, fixed upon Sorin's suggestion.

Comment on lines 115 to 116
pc.mutProofsByHash.Lock()
defer pc.mutProofsByHash.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

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

The caller function also locks (sorry if I'm mistaken).

Copy link
Contributor Author

@ssd04 ssd04 Mar 17, 2025

Choose a reason for hiding this comment

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

there are 2 separate locks

Copy link
Contributor

Choose a reason for hiding this comment

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

Indeed, my bad / overlooked!

Comment on lines 94 to 101
for _, bucket := range pc.proofsByNonceBuckets {
if nonce > bucket.maxNonce {
wg.Add(1)

proofsByNonce := make([]*proofNonceMapping, 0)
go func(bucket *proofNonceBucket) {
pc.cleanupProofsInBucket(bucket)
wg.Done()
}(bucket)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe allow the cleanup to happen synchronously? I see the workload is lock, delete from map in a loop, unlock. Do we have a speed gain using concurrency in this context (since map deletions are not quite subject to parallelization)?

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 concurrency here

andreibancioiu
andreibancioiu previously approved these changes Mar 17, 2025

pc.mutProofsCache.Lock()
defer pc.mutProofsCache.Unlock()
pc.mutProofsByNonce.Lock()
Copy link
Contributor

Choose a reason for hiding this comment

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

If cleanup procedure does not happen extremely often (~rare event), maybe both locks can happen one near the other - e.g. lock & unlock mutProofsByHash for all buckets at once (I think this was Sorin's internal suggestion).

Comment on lines 47 to 52
defer pc.mutProofsCache.Unlock()
pc.insertProofByNonce(proof)

pc.proofsByNonce = append(pc.proofsByNonce, &proofNonceMapping{
headerHash: string(proof.GetHeaderHash()),
nonce: proof.GetHeaderNonce(),
})
pc.mutProofsByHash.Lock()
pc.proofsByHash[string(proof.GetHeaderHash())] = proof
pc.mutProofsByHash.Unlock()
Copy link
Contributor

@andreibancioiu andreibancioiu Mar 17, 2025

Choose a reason for hiding this comment

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

Having these two locks separated does not seem to be an optimization (at least, not at a first glance). Since:

lock A
do ~trivial (& with short duration) logic around insertion in bucket
unlock A
lock B
do ~trivial (& with short duration) operation of adding a map entry
unlock B

Is not necessarily better than:

lock C
do ~trivial (& with short duration) logic around insertion in bucket
do ~trivial (& with short duration) operation of adding a map entry
unlock C

Especially if extreme concurrency (e.g. a lot of new insertions bootstrapped during each of the ~trivial two parts) isn't expected.

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, updated to keep only one mutex

@AdoAdoAdo AdoAdoAdo mentioned this pull request Mar 17, 2025
sstanculeanu
sstanculeanu previously approved these changes Mar 17, 2025
andreibancioiu
andreibancioiu previously approved these changes Mar 17, 2025
@ssd04 ssd04 changed the title Remove sort operation from proofs pool insert Proofs pool improvements Mar 17, 2025

type proofNonceBucket struct {
maxNonce uint64
proofsByNonce []*proofNonceMapping
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe use a map then the upsert would not need to iterate

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to use map

return len(p.proofsByNonce)
}

func (p *proofNonceBucket) isFull() bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would use fixed size and deterministic range for each bucket.
e.g a proof with nonce x should be added into the bucket with first nonce x/bucketSize * bucketSize (integer division)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to use fixed size buckets

proofsByNonce []*proofNonceMapping
proofsByHash map[string]data.HeaderProofHandler
mutProofsCache sync.RWMutex
proofsByNonceBuckets []*proofNonceBucket
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can be a map as well with the key the lowest nonce that the bucket would hold

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed to use sync.Map


headBucket := pc.proofsByNonceBuckets[0]

if headBucket.isFull() {
Copy link
Contributor

Choose a reason for hiding this comment

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

in case of syncing node this will cause it to keep accumulating new nonces in each old nonces buckets, which will delay their cleanup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

new model with range buckets should cover this case

@ssd04 ssd04 dismissed stale reviews from andreibancioiu and sstanculeanu via 4ea5d8e March 18, 2025 13:02
mutProofsCache sync.RWMutex
proofsByNonceBuckets []*proofNonceBucket
bucketSize int
proofsByNonceBuckets sync.Map
Copy link
Collaborator

Choose a reason for hiding this comment

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

proofNonceMapping struct not used anymore

Copy link
Contributor Author

@ssd04 ssd04 Mar 18, 2025

Choose a reason for hiding this comment

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

👍 deleted it


pc.proofsByNonceBuckets = buckets
for _, key := range bucketsToDelete {
pc.proofsByNonceBuckets.Delete(key)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is already done on L86

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, old code; removed

sstanculeanu
sstanculeanu previously approved these changes Mar 18, 2025
AdoAdoAdo
AdoAdoAdo previously approved these changes Mar 18, 2025
@ssd04 ssd04 dismissed stale reviews from AdoAdoAdo and sstanculeanu via acf47c7 March 18, 2025 14:51
@ssd04 ssd04 merged commit d163e58 into feat/andromeda-fixes Mar 18, 2025
4 checks passed
@ssd04 ssd04 deleted the fix-proofs-pool-insert branch March 18, 2025 15:05
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.

5 participants