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

internal/cache: reduce mutex contention #2774

Merged
merged 2 commits into from
Jul 28, 2023

Conversation

jbowens
Copy link
Collaborator

@jbowens jbowens commented Jul 26, 2023

internal/cache: reduce mutex contention from EvictFile

We've observed block cache mutex contention originating from EvictFile. When
evicting a file with a significant count of blocks currently held within the
block cache, EvictFile may hold the block cache shard mutex for an extended
duration, increasing latency for requests that must access the same shard.

This commit mitigates this contention with two approaches. EvictFile now
periodically drops the mutex to provide these other requests with an
opportunity to make progress. Additionally, rather than free()-ing manually
managed memory back to the memory allocator while holding the mutex, the free
is deferred until the shard mutex has been dropped.

Informs #1997.

internal/cache: increase shard count to 4x CPUs

Increase the block cache shard count to reduce mutex contention.

@cockroach-teamcity
Copy link
Member

This change is Reviewable

@jbowens jbowens force-pushed the evictfile-dropmu branch 2 times, most recently from fd83bba to 84588ca Compare July 26, 2023 21:48
@jbowens jbowens requested review from sumeerbhola and a team July 27, 2023 15:32
Copy link
Member

@RaduBerinde RaduBerinde left a comment

Choose a reason for hiding this comment

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

:lgtm: Just some nits.

Reviewable status: 0 of 1 files reviewed, 6 unresolved discussions (waiting on @jbowens and @sumeerbhola)


internal/cache/clockpro.go line 180 at r1 (raw file):

		c.countTest--
		if oldV := c.metaDel(e); oldV != nil {
			oldV.release()

oldV should always be nil here right? (that would be the case above)
Ok to keep so it's idiomatic but perhaps add a comment.


internal/cache/clockpro.go line 259 at r1 (raw file):

	fkey := key{fileKey{id, fileNum}, 0}
	for c.evictFileRun(fkey) {
	}

maybe call runtime.Gosched() here so other goroutines get more of a chance to acquire the mutex?


internal/cache/clockpro.go line 264 at r1 (raw file):

			break
		}
	}

Wouldn't it be a lot simpler to just add a

mu.Unlock()
runtime.GoSched()
mu.Lock()

in here every 5 blocks?


internal/cache/clockpro.go line 399 at r1 (raw file):

// Remove the entry from the cache. This removes the entry from the blocks map,
// the files map, and ensures that hand{Hot,Cold,Test} are not pointing at the
// entry. Returns the deleted value that must be released.

Can also return nil.. Or we could make release tolerate nil and then we can just call metaDel().release() in many cases


internal/cache/clockpro.go line 405 at r1 (raw file):

	}
	// Remove the pointer to the value.
	deletedValue, e.val = e.val, nil

[nit] I find this harder to read than two separate statement


internal/cache/clockpro.go line 710 at r2 (raw file):

	// In tests, we can use large CPU machines with small cache sizes and have
	// many caches in existence at a time. If sharding into m shards would
	// produce small of shards, constrain the number of shards to 4.

[nit] would produce shards that are too small

Copy link
Collaborator Author

@jbowens jbowens left a comment

Choose a reason for hiding this comment

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

TFTR!

Reviewable status: 0 of 3 files reviewed, 3 unresolved discussions (waiting on @RaduBerinde and @sumeerbhola)


internal/cache/clockpro.go line 259 at r1 (raw file):

Previously, RaduBerinde wrote…

maybe call runtime.Gosched() here so other goroutines get more of a chance to acquire the mutex?

Good call, done.


internal/cache/clockpro.go line 264 at r1 (raw file):

Previously, RaduBerinde wrote…

Wouldn't it be a lot simpler to just add a

mu.Unlock()
runtime.GoSched()
mu.Lock()

in here every 5 blocks?

Freeing the memory while the mutex is dropped and adding a defer to handle invariant violation panics while Free-ing memory make it not too simple. But I agree performing the frees and consistency checks in the defer was confusing. I moved it it inline here, but it's still not very simple :/


internal/cache/clockpro.go line 399 at r1 (raw file):

Previously, RaduBerinde wrote…

Can also return nil.. Or we could make release tolerate nil and then we can just call metaDel().release() in many cases

I like it, done.

@RaduBerinde
Copy link
Member

internal/cache/clockpro.go line 264 at r1 (raw file):

Previously, jbowens (Jackson Owens) wrote…

Freeing the memory while the mutex is dropped and adding a defer to handle invariant violation panics while Free-ing memory make it not too simple. But I agree performing the frees and consistency checks in the defer was confusing. I moved it it inline here, but it's still not very simple :/

Ah.. right.. Another option would be for the function to return the slice and have the caller release. That would keep the entire function locked. We can pass in the prealloced slice and the function woulf fill it to the cap.

Also, I don't think we need to checkConsistency in the first return false case, so we could just move it before the second return false

Copy link
Collaborator

@sumeerbhola sumeerbhola left a comment

Choose a reason for hiding this comment

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

:lgtm:

Reviewed 3 of 3 files at r3, 1 of 1 files at r4, all commit messages.
Reviewable status: all files reviewed, 8 unresolved discussions (waiting on @jbowens and @RaduBerinde)


internal/cache/clockpro.go line 264 at r3 (raw file):

	// If most of the file's blocks are held in the block cache, evicting all
	// the blocks may take a while. We don't want to block the entire cache
	// shard, forcing concurrent readers until we're finished. We drop the mutex

... readers to wait until ...


internal/cache/clockpro.go line 277 at r3 (raw file):

	obsoleteValues := obsoleteValuesAlloc[:0]
	defer func() {
		if !moreRemaining {

maybe add a comment
// Don't want to pay the cost of checkConsistency for each batch corresponding to blocksPerMutexAcquisition, so do it only when this was the last batch processed.


internal/cache/clockpro.go line 289 at r3 (raw file):

	if blocks == nil {
		return false
	}

This code could use a few comments. Something like:

// b is the current head of the doubly linked list, and n is the entry after b.


internal/cache/clockpro.go line 293 at r3 (raw file):

		n = b.fileLink.next
		obsoleteValues = append(obsoleteValues, c.metaEvict(b))
		if b == n {

// b == n represents the case where b was the last entry remaining in the doubly linked list, which is why it pointed at itself. So no more entries left.


internal/cache/clockpro.go line 704 at r4 (raw file):

	// In tests we can use large CPU machines with small cache sizes and have
	// many caches in existence at a time. If sharding into m shards would
	// produce too small of shards, constrain the number of shards to 4.

too small shards ...

We've observed block cache mutex contention originating from EvictFile. When
evicting a file with a significant count of blocks currently held within the
block cache, EvictFile may hold the block cache shard mutex for an extended
duration, increasing latency for requests that must access the same shard.

This commit mitigates this contention with two approaches. EvictFile now
periodically drops the mutex to provide these other requests with an
opportunity to make progress. Additionally, rather than free()-ing manually
managed memory back to the memory allocator while holding the mutex, the free
is deferred until the shard mutex has been dropped.

Informs cockroachdb#1997.
Increase the block cache shard count to reduce mutex contention.
Copy link
Collaborator Author

@jbowens jbowens left a comment

Choose a reason for hiding this comment

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

TFTRs!

Reviewable status: 2 of 3 files reviewed, 8 unresolved discussions (waiting on @RaduBerinde and @sumeerbhola)


internal/cache/clockpro.go line 289 at r3 (raw file):

Previously, sumeerbhola wrote…

This code could use a few comments. Something like:

// b is the current head of the doubly linked list, and n is the entry after b.

Done.


internal/cache/clockpro.go line 293 at r3 (raw file):

Previously, sumeerbhola wrote…

// b == n represents the case where b was the last entry remaining in the doubly linked list, which is why it pointed at itself. So no more entries left.

Done.


internal/cache/clockpro.go line 704 at r4 (raw file):

Previously, sumeerbhola wrote…

too small shards ...

Done.

@jbowens jbowens merged commit ce43e65 into cockroachdb:master Jul 28, 2023
9 checks passed
@jbowens jbowens deleted the evictfile-dropmu branch July 28, 2023 15:32
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.

None yet

4 participants