Skip to content

Commit 0284959

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Reduce lock contention on subnets" into stable/zed
2 parents 5bd0b88 + d25c129 commit 0284959

File tree

3 files changed

+41
-15
lines changed

3 files changed

+41
-15
lines changed

neutron/db/db_base_plugin_v2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575

7676

7777
def _ensure_subnet_not_used(context, subnet_id):
78-
models_v2.Subnet.lock_register(
78+
models_v2.Subnet.write_lock_register(
7979
context, exc.SubnetInUse(subnet_id=subnet_id), id=subnet_id)
8080
try:
8181
registry.publish(

neutron/db/ipam_backend_mixin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ def _ipam_get_subnets(self, context, network_id, host, service_type=None,
683683
msg = ('This subnet is being modified by another concurrent '
684684
'operation')
685685
for subnet in subnets:
686-
subnet.lock_register(
686+
subnet.read_lock_register(
687687
context, exc.SubnetInUse(subnet_id=subnet.id, reason=msg),
688688
id=subnet.id)
689689
subnet_dicts = [self._make_subnet_dict(subnet, context=context)

neutron/db/models_v2.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,51 @@
3333
class HasInUse(object):
3434
"""NeutronBaseV2 mixin, to add the flag "in_use" to a DB model.
3535
36-
The content of this flag (boolean) parameter is not relevant. The goal of
37-
this field is to be used in a write transaction to mark a DB register as
38-
"in_use". Writing any value on this DB parameter will lock the container
39-
register. At the end of the DB transaction, the DB engine will check if
40-
this register was modified or deleted. In such case, the transaction will
41-
fail and won't be committed.
42-
43-
"lock_register" is the method to write the register "in_use" column.
44-
Because the lifespan of this DB lock is the DB transaction, there isn't an
45-
unlock method. The lock will finish once the transaction ends.
36+
The goal of this class is to allow users lock specific database rows with
37+
a shared or exclusive lock (without necessarily introducing a change in
38+
the table itself). Having these locks allows the DB engine to prevent
39+
concurrent modifications (e.g. the deletion of a resource while we are
40+
currently adding a new dependency on the resource).
41+
42+
"read_lock_register" takes a shared DB lock on the row specified by the
43+
filters. The lock is automatically released once the transaction ends.
44+
You can have any number of parallel read locks on the same DB row. But
45+
you can not have any write lock in parallel.
46+
47+
"write_lock_register" takes an exclusive DB lock on the row specified by
48+
the filters. The lock is automatically released on transaction commit.
49+
You may only have one write lock on each row at a time. It therefor
50+
blocks all other read and write locks to this row.
4651
"""
52+
# keep this value to not need to update the database schema
53+
# only at backport
4754
in_use = sa.Column(sa.Boolean(), nullable=False,
4855
server_default=sql.false(), default=False)
4956

5057
@classmethod
51-
def lock_register(cls, context, exception, **filters):
58+
def write_lock_register(cls, context, exception, **filters):
59+
# we use `with_for_update()` to include `FOR UPDATE` in the sql
60+
# statement.
61+
# we need to set `enable_eagerloads(False)` so that we do not try to
62+
# load attached resources (e.g. standardattributes) as this breaks the
63+
# `FOR UPDATE` statement.
5264
num_reg = context.session.query(
53-
cls).filter_by(**filters).update({'in_use': True})
54-
if num_reg != 1:
65+
cls).filter_by(**filters).enable_eagerloads(
66+
False).with_for_update().first()
67+
if num_reg is None:
68+
raise exception
69+
70+
@classmethod
71+
def read_lock_register(cls, context, exception, **filters):
72+
# we use `with_for_update(read=True)` to include `LOCK IN SHARE MODE`
73+
# in the sql statement.
74+
# we need to set `enable_eagerloads(False)` so that we do not try to
75+
# load attached resources (e.g. standardattributes) as this breaks the
76+
# `LOCK IN SHARE MODE` statement.
77+
num_reg = context.session.query(
78+
cls).filter_by(**filters).enable_eagerloads(
79+
False).with_for_update(read=True).first()
80+
if num_reg is None:
5581
raise exception
5682

5783

0 commit comments

Comments
 (0)