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
If a change is no-op, then don't create a transaction. #7180
Conversation
Change SetLinkLayerDevices and SetDevicesAddresses so that both of them check if there are actually any changes to be done, and only if we'll actually change something do we create a transaction.
…ssumed we could set things in multiple arguments. It wasn't a valid check, because it happened to work because it always set the values, because it wasn't properly seeing if there was a change. But if we do it sequentially we find the right values.
This currently does create no-op transactions that look like:
would it be better to raise an exception that there is nothing to do so that these don't get created? |
This has been fixed in the last version. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks fine, but I have some concern about dropping the model life assertion. I don't think it needs to be there in the first place, in which case we should remove both parts. But if we do care and must check in memory, we should be mirroring that in an assert.
state/machine_linklayerdevices.go
Outdated
if err := checkModelActive(m.st); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
if err := m.isStillAlive(); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be a little more efficient to use the m.doc.Life value here, and only reload the machine doc on attempt>0. I suspect that's what was intended originally, except the initial check was left out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -206,18 +206,27 @@ func (m *Machine) SetLinkLayerDevices(devicesArgs ...LinkLayerDeviceArgs) (err e | |||
} | |||
} | |||
|
|||
// We've checked the model is alive directly, and we assert the machine is alive, we don't need to also | |||
// assert the model is alive, because then the machine would be dying as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This statement is not true. A model can be dying, but still have alive machines. They will eventually be destroyed on account of cleanups.
If we don't really care about the model life here, should we remove the one above too? The doc comment for the method does not explain why we check the model life. Do you know?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it was back in the "I'm updating something, make sure my model is alive". These aren't actually creating new resources, so I don't think it is critical.
I'm personally ok with a "don't start work that is about to be dropped" check of model, but certainly I don't believe we need the TXN assert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right, and we should only care about checking model life when creating new resources.
IMO, the in-memory life check should be dropped too. It's of dubious value when there's no accompanying txn op, since it's no longer ensuring integrity - so it's really just an optimisation. This makes things more difficult to understand, because it's not clear why we would do it in only memory from the context (hence this question).
FWIW, I've taken to the pattern of:
// checkSomething checks something in memory, and then return txn.Ops that assert the same
func checkSomething() ([]txn.Op, error)
That way it's clear that the ops will be ignored or not, more likely with comments to justify.
state/machine_linklayerdevices.go
Outdated
if len(setDevicesOps) == 0 { | ||
// No need to assert only that the machine is alive | ||
logger.Debugf("no changes to LinkLayerDevices for machine %q", m.Id()) | ||
return nil, errNoChanges |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, jujutxn.ErrNoOperations
then you can drop the check below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, I expected something like that, but there wasn't anything like that around this code.
Check directly if the machine is still alive, instead of hitting the db immediately. We also don't need our own 'noChanges' as jujutxn.ErrNoOperations already exists.
@@ -206,18 +206,27 @@ func (m *Machine) SetLinkLayerDevices(devicesArgs ...LinkLayerDeviceArgs) (err e | |||
} | |||
} | |||
|
|||
// We've checked the model is alive directly, and we assert the machine is alive, we don't need to also | |||
// assert the model is alive, because then the machine would be dying as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right, and we should only care about checking model life when creating new resources.
IMO, the in-memory life check should be dropped too. It's of dubious value when there's no accompanying txn op, since it's no longer ensuring integrity - so it's really just an optimisation. This makes things more difficult to understand, because it's not clear why we would do it in only memory from the context (hence this question).
FWIW, I've taken to the pattern of:
// checkSomething checks something in memory, and then return txn.Ops that assert the same
func checkSomething() ([]txn.Op, error)
That way it's clear that the ops will be ignored or not, more likely with comments to justify.
state/machine_linklayerdevices.go
Outdated
@@ -600,13 +615,13 @@ func (m *Machine) SetDevicesAddresses(devicesAddresses ...LinkLayerDeviceAddress | |||
return nil, errors.Trace(err) | |||
} | |||
|
|||
if err := checkModelActive(m.st); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as above
|
Status: merge request accepted. Url: http://juju-ci.vapour.ws:8080/job/github-merge-juju |
Build failed: Generating tarball failed |
|
Status: merge request accepted. Url: http://juju-ci.vapour.ws:8080/job/github-merge-juju |
Description of change
Change SetLinkLayerDevices and SetDevicesAddresses so that both of them check if there are actually any changes to be done, and only if we'll actually change something do we create a transaction.
QA steps
monitor the transaction log, it should not be filling up with transactions that are ultimately just asserting that a bunch of devices still exist. The debug-log should also include lines like:
should return no documents. Before the last commit we were creating transactions that affected no collections, which was a bit silly.
Documentation changes
None.
Bug reference
lp:1677619
lp:1676427