Merge "Use subqueryload() instead of joinedload() for (system_)metadata" into stable/queens

This commit is contained in:
Zuul 2021-05-29 02:46:23 +00:00 committed by Gerrit Code Review
commit 7b90bcd92a
2 changed files with 24 additions and 1 deletions

View File

@ -49,6 +49,7 @@ from sqlalchemy.orm import contains_eager
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from sqlalchemy.orm import joinedload_all from sqlalchemy.orm import joinedload_all
from sqlalchemy.orm import noload from sqlalchemy.orm import noload
from sqlalchemy.orm import subqueryload
from sqlalchemy.orm import undefer from sqlalchemy.orm import undefer
from sqlalchemy.schema import Table from sqlalchemy.schema import Table
from sqlalchemy import sql from sqlalchemy import sql
@ -1919,13 +1920,27 @@ def _build_instance_get(context, columns_to_join=None):
continue continue
if 'extra.' in column: if 'extra.' in column:
query = query.options(undefer(column)) query = query.options(undefer(column))
elif column in ['metadata', 'system_metadata']:
# NOTE(melwitt): We use subqueryload() instead of joinedload() for
# metadata and system_metadata because of the one-to-many
# relationship of the data. Directly joining these columns can
# result in a large number of additional rows being queried if an
# instance has a large number of (system_)metadata items, resulting
# in a large data transfer. Instead, the subqueryload() will
# perform additional queries to obtain metadata and system_metadata
# for the instance.
query = query.options(subqueryload(column))
else: else:
query = query.options(joinedload(column)) query = query.options(joinedload(column))
# NOTE(alaski) Stop lazy loading of columns not needed. # NOTE(alaski) Stop lazy loading of columns not needed.
for col in ['metadata', 'system_metadata']: for col in ['metadata', 'system_metadata']:
if col not in columns_to_join: if col not in columns_to_join:
query = query.options(noload(col)) query = query.options(noload(col))
return query # NOTE(melwitt): We need to use order_by(<unique column>) so that the
# additional queries emitted by subqueryload() include the same ordering as
# used by the parent query.
# https://docs.sqlalchemy.org/en/13/orm/loading_relationships.html#the-importance-of-ordering
return query.order_by(models.Instance.id)
def _instances_fill_metadata(context, instances, manual_joins=None): def _instances_fill_metadata(context, instances, manual_joins=None):

View File

@ -2372,6 +2372,14 @@ class InstanceTestCase(test.TestCase, ModelsObjectComparatorMixin):
sys_meta = utils.metadata_to_dict(inst['system_metadata']) sys_meta = utils.metadata_to_dict(inst['system_metadata'])
self.assertEqual(sys_meta, self.sample_data['system_metadata']) self.assertEqual(sys_meta, self.sample_data['system_metadata'])
def test_instance_get_with_meta(self):
inst_id = self.create_instance_with_args().id
inst = db.instance_get(self.ctxt, inst_id)
meta = utils.metadata_to_dict(inst['metadata'])
self.assertEqual(meta, self.sample_data['metadata'])
sys_meta = utils.metadata_to_dict(inst['system_metadata'])
self.assertEqual(sys_meta, self.sample_data['system_metadata'])
def test_instance_update(self): def test_instance_update(self):
instance = self.create_instance_with_args() instance = self.create_instance_with_args()
metadata = {'host': 'bar', 'key2': 'wuff'} metadata = {'host': 'bar', 'key2': 'wuff'}