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

storage: introduce indirection between lock table keys and lock strength #108136

Merged

Conversation

arulajmani
Copy link
Collaborator

Lock table keys include a single byte that denotes the lock's strength. Prior to this patch, that byte was directly derived from the lock strength. Versions <= 23.1 considered intents to have Exclusive lock strength; versions > 23.1 treat intents as having a new (stronger) lock strength -- lock.Intent.

This requires us to change how lock table keys are serialized and deserialized -- otherwise, we'd have a incompatibility issue between intents written across these versions. To that end, this patch introduces a layer of indirection between a lock's strength and the strength byte that's persisted in lock table keys. We then ensure the byte itself doesn't change between versions -- i.e, all versions use 3 (the enum value of lock.Exclusive) as the byte written by an intent.

Closes #107947

Release note: None

@arulajmani arulajmani requested a review from a team as a code owner August 3, 2023 19:21
@arulajmani arulajmani requested a review from a team August 3, 2023 19:21
@arulajmani arulajmani requested a review from a team as a code owner August 3, 2023 19:21
@arulajmani arulajmani requested a review from RaduBerinde August 3, 2023 19:21
@cockroach-teamcity
Copy link
Member

This change is Reviewable

Copy link
Member

@nvanbenschoten nvanbenschoten left a comment

Choose a reason for hiding this comment

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

Reviewed 3 of 10 files at r1, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @arulajmani and @RaduBerinde)


pkg/storage/engine_key.go line 273 at r1 (raw file):

// (lock.Strength, StrengthByte), where the StrengthByte corresponds to the byte
// persisted in LockTableKeys.
var replicatedLockStrengthToByteMap = func() [lock.NumLockStrength]byte {

This is close, but the way this usually looks in Go is a little bit more like:

var strToByte = [...]byte{
	lock.Shared:    1,
	lock.Exclusive: 2,
	lock.Intent:    3,
}

var byteToStr = func() (arr [len(strToByte) + 1]lock.Strength) {
	for str, b := range strToByte {
		arr[b] = str
	}
	return arr
}()

func getByteForStr(str int) (byte, error) {
	if str < 0 || str >= len(strToByte) {
		return 0, errors.Error("...")
	}
	b := strToByte[str]
	if b == 0 {
		return 0, errors.Error("...")
	}
	return b, nil
}

func getStrForByte(b byte) (lock.Strength, error) {
	// ... same thing. Maybe try generics.
}

You could even strongly type byte for more type safety. And add a mustGetByteForStr function for testing.

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.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @arulajmani and @RaduBerinde)


pkg/storage/engine_key.go line 266 at r1 (raw file):

var byteToReplicatedLockStrengthMap = func() [lock.NumLockStrength]lock.Strength {
	var m [lock.NumLockStrength]lock.Strength
	m[3] = lock.Intent

If both lock.Exclusive and lock.Intent are using the same byte 3, how can one map from byte to lock.Strength?

Are we never persisting lock.Exclusive locks?

Copy link
Member

@nvanbenschoten nvanbenschoten left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @arulajmani, @RaduBerinde, and @sumeerbhola)


pkg/storage/engine_key.go line 266 at r1 (raw file):

Previously, sumeerbhola wrote…

If both lock.Exclusive and lock.Intent are using the same byte 3, how can one map from byte to lock.Strength?

Are we never persisting lock.Exclusive locks?

The intention is for lock.Exclusive to map to/from byte 2. This means that all prior encoded keys will now be decoded as lock.Intent. Thus far, we have never persisted lock.Exclusive locks.

Copy link
Member

@nvanbenschoten nvanbenschoten left a comment

Choose a reason for hiding this comment

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

This PR is going to need to change a bit with #109082.

I also realized while typing that refactor out that we probably don't want to change the LockTableKey.Strength field itself. Instead, we can push the translation directly into LockTableKey.ToEngineKey and EngineKey.ToLockTableKey. I suspect that will make this change much smaller by hiding the translation behind the LockTableKey abstraction.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @arulajmani, @RaduBerinde, and @sumeerbhola)

Copy link
Collaborator Author

@arulajmani arulajmani left a comment

Choose a reason for hiding this comment

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

Instead, we can push the translation directly into LockTableKey.ToEngineKey and EngineKey.ToLockTableKey.

Ack, I'll make the change. Haven't looked at the other patch yet, but do you want me to build this on top of yours?

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @RaduBerinde and @sumeerbhola)

@arulajmani arulajmani force-pushed the dl-correct-lock-strength-intents-v2 branch from 8381878 to da51833 Compare August 22, 2023 17:56
Copy link
Collaborator Author

@arulajmani arulajmani left a comment

Choose a reason for hiding this comment

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

Rebased this + took your suggestion to push the translation into LockTableKey.ToEngineKey and EngineKey.ToLockTableKey. RFAL.

After coming back to this and implementing your suggestion, I'm wondering how you feel about renaming LockTableKey to ReplicatedLock? It helps make it clear that the Strength field has nothing to do with the key encoding.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @nvanbenschoten, @RaduBerinde, and @sumeerbhola)


pkg/storage/engine_key.go line 266 at r1 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

The intention is for lock.Exclusive to map to/from byte 2. This means that all prior encoded keys will now be decoded as lock.Intent. Thus far, we have never persisted lock.Exclusive locks.

I added some words about this in the commit message to clarify.


pkg/storage/engine_key.go line 273 at r1 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

This is close, but the way this usually looks in Go is a little bit more like:

var strToByte = [...]byte{
	lock.Shared:    1,
	lock.Exclusive: 2,
	lock.Intent:    3,
}

var byteToStr = func() (arr [len(strToByte) + 1]lock.Strength) {
	for str, b := range strToByte {
		arr[b] = str
	}
	return arr
}()

func getByteForStr(str int) (byte, error) {
	if str < 0 || str >= len(strToByte) {
		return 0, errors.Error("...")
	}
	b := strToByte[str]
	if b == 0 {
		return 0, errors.Error("...")
	}
	return b, nil
}

func getStrForByte(b byte) (lock.Strength, error) {
	// ... same thing. Maybe try generics.
}

You could even strongly type byte for more type safety. And add a mustGetByteForStr function for testing.

Thanks for sketching this out! I took ~most of your suggestions. I didn't add lock.Shared or lock.Exclusive to the map just yet.

Now that we're no longer storing StrengthByte in the LockTableKey struct, I didn't feel strongly typing byte would get us much. Happy to iterate over this.

Copy link
Member

@nvanbenschoten nvanbenschoten left a comment

Choose a reason for hiding this comment

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

After coming back to this and implementing your suggestion, I'm wondering how you feel about renaming LockTableKey to ReplicatedLock? It helps make it clear that the Strength field has nothing to do with the key encoding.

Discussed on slack and decided to keep this as LockTableKey.

Reviewed 12 of 12 files at r2, all commit messages.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @arulajmani, @RaduBerinde, and @sumeerbhola)


pkg/storage/engine_key.go line 273 at r1 (raw file):

I didn't feel strongly typing byte would get us much. Happy to iterate over this.

Agreed. I don't think it's needed now.


pkg/storage/engine_key.go line 258 at r2 (raw file):

}

// byteToReplicatedLockStrength is a mapping between the strength byte persisted

small nit: move this below replicatedLockStrengthToByte, since this is derived from that array.


pkg/storage/engine_key.go line 261 at r2 (raw file):

[len(replicatedLockStrengthToByte)]

I thought this had to be len(replicatedLockStrengthToByte)+1, but what you have here also works. It's actually a bit subtle why though, having to do with lock.Intent's value of 4. We should probably just make this a slice and be more explicit. Something like:

var byteToReplicatedLockStrength = func() (arr []lock.Strength) {
    maxByte := 0
    for _, b := range replicatedLockStrengthToByte {
        if b > maxBytes {
            b = maxByte
        }
    }
    arr = make([]lock.Strength, maxByte+1)
    for str, b := range replicatedLockStrengthToByte {
        if b != 0 {
            arr[b] = lock.Strength(str)
        }
    }
    return arr
}()

pkg/storage/engine_key.go line 278 at r2 (raw file):

// a lock's key encoding, given its lock strength.
func getByteForReplicatedLockStrength(str lock.Strength) byte {
	if str <= lock.None || int(str) >= len(replicatedLockStrengthToByte) {

nit: s/str <= lock.None/str < 0/, since the intention is to use str as an index into the array.


pkg/storage/engine_key.go line 291 at r2 (raw file):

// the strength byte from its key encoding.
func getReplicatedLockStrengthForByte(b byte) (lock.Strength, error) {
	if b <= 0 || int(b) >= len(byteToReplicatedLockStrength) {

s/<=/</


pkg/storage/engine_key.go line 295 at r2 (raw file):

	}
	str := byteToReplicatedLockStrength[b]
	if str <= lock.None || str > lock.Intent {

Should this just be if str == 0 {?

Lock table keys include a single byte that denotes the lock's strength.
Prior to this patch, that byte was directly derived from the lock
strength. Versions <= 23.1 considered intents to have Exclusive lock
strength; versions > 23.1 treat intents as having a new (stronger) lock
strength -- lock.Intent.

This requires us to change how lock table keys are serialized and
deserialized -- otherwise, we'd have a incompatibility issue between
intents written across these versions. To that end, this patch
introduces a layer of indirection between a lock's strength and the
strength byte that's persisted in lock table keys. We then ensure the
byte itself doesn't change between versions -- i.e, all versions use 3
(the enum value of lock.Exclusive) as the byte written by an intent.

Note that until 23.1, the only replicated locks we supported were
intents, so this performing this remapping is copacetic. In the future
(23.2) when we support replicated locks of strength `Exclusive`, we'll
use this layer of indirection to assign them a different strength byte
(say 2).

Closes cockroachdb#107947
Release note: None
@arulajmani arulajmani force-pushed the dl-correct-lock-strength-intents-v2 branch from da51833 to 666f67e Compare August 23, 2023 22:40
Copy link
Collaborator Author

@arulajmani arulajmani left a comment

Choose a reason for hiding this comment

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

RFAL

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @nvanbenschoten, @RaduBerinde, and @sumeerbhola)


pkg/storage/engine_key.go line 261 at r2 (raw file):

It's actually a bit subtle why though, having to do with lock.Intent's value of 4.

I agree. I also like this bit:

 if b != 0 { arr[b] = lock.Strength(str) {

it's much clearer than the old thing.


pkg/storage/engine_key.go line 291 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

s/<=/</

We can get rid of this entirely, because a byte can never be < 0.


pkg/storage/engine_key.go line 295 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

Should this just be if str == 0 {?

Yeah, str == 0 makes more sense.

Copy link
Member

@nvanbenschoten nvanbenschoten 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 1 of 1 files at r3, all commit messages.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @RaduBerinde and @sumeerbhola)

@arulajmani
Copy link
Collaborator Author

TFTR!

bors r=nvanbenschoten

@craig
Copy link
Contributor

craig bot commented Aug 24, 2023

Build succeeded:

@craig craig bot merged commit b24aa96 into cockroachdb:master Aug 24, 2023
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.

kv: use correct locking strength for Intents
4 participants