I'm currently investigating the feasibility of a generic
compare-and-swap feature for NovaObject.save(). This post isn't about
that, but that's the larger context.

As a preliminary step towards that goal, I've started by investigating
how Nova objects are saved today. Ideally there would be some
consistency in how objects are saved, but unfortunately that's not there
today. An initial inconsistency I have noticed is that some objects
refresh themselves from the database when calling save(), but others don't.

For brevity, I have conflated what happens in object.save() with what
happens in db.api. Where the code lives isn't relevant here: I'm only
looking at what happens.

Specifically, the following objects refresh themselves on save:

Aggregate
BlockDeviceMapping
ComputeNode
FloatingIP
Instance
InstanceGroup
Migration
Network
PciDevice
SecurityGroup
Service

whereas the following objects do not:

Flavor
Agent
FixedIP
InstanceInfoCache
InstancePCIRequest

Excluding irrelevant complexity, the general model for objects which
refresh on update is:

object = <select row from object table>
object.update()
object.save()
return <select row from object table again>

Some objects skip out the second select and return the freshly saved
object. That is, a save involves an update + either 1 or 2 selects.

The lack of consistency in behaviour is obviously a problem, and I can't
think of any good reason for a second select for objects which do that.
However, I don't think it is good design for save() to refresh the
object at all, and the reason is concurrency. The cached contents of a
Nova object are *always* potentially stale. A refresh does nothing to
change that, because the contents are again potentially stale as soon as
it returns. Handling this requires concurrency primitives which we don't
currently have (see the larger context I mentioned above). Refreshing an
object's contents might reduce the probability of a race, but it doesn't
fix it. Callers who want a refresh after save can always call
object.refresh(), but for others it's just wasted hits on the db.

Refresh on save() is also arbitrary. Why should the object be updated
then rather than at any other time? The timing of an update in thread X
is unrelated to the timing of an update in thread Y, but it's a problem
whenever it happens.

Can anybody see a problem if we didn't fetch the row at all, and simply
updated it? Absent locking or compare-and-swap this is effectively what
we're already doing, and it reduces the db cost of save to a single
update statement. The difference would be that the object would remain
stale without an explicit refresh(). Value munging would remain unaffected.

Additionally, Instance, InstanceGroup, and Flavor perform multiple
updates on save(). I would apply the same rule to the sub-updates, and
also move them into a single transaction such that the updates are atomic.

Thanks,

Matt
-- 
Matthew Booth
Red Hat Engineering, Virtualisation Team

Phone: +442070094448 (UK)
GPG ID:  D33C3490
GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490

_______________________________________________
OpenStack-dev mailing list
OpenStack-dev@lists.openstack.org
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev

Reply via email to