From 5f6bd8cc3c2ab557d2659830f5f651c2e2225717 Mon Sep 17 00:00:00 2001 From: Nickolas Grigoriadis Date: Sun, 21 Jul 2019 11:58:06 +0200 Subject: [PATCH] Efficiency improvements in model & query construction for 5-30% speedup for fetch operations (#158) * Split model consructor into from-Python and from-DB paths, leading to 15-25% speedup for large fetch operations. * More efficient queryset manipulation, 5-30% speedup for small fetches. --- CHANGELOG.rst | 8 +- docs/ORM_Perf.png | Bin 32810 -> 31049 bytes tortoise/__init__.py | 5 +- tortoise/backends/base/executor.py | 4 +- tortoise/models.py | 162 ++++++++++++++--------------- tortoise/query_utils.py | 2 + tortoise/queryset.py | 80 +++++++------- 7 files changed, 129 insertions(+), 132 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f43c0d5ce..05ba9b173 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,7 +5,13 @@ Changelog 0.12.6 ------ -* Handle a __models__ variable within modules to override the model discovery mechanism. +* Handle a ``__models__`` variable within modules to override the model discovery mechanism. + + If you define the ``__models__`` variable in ``yourapp.models`` (or wherever you specify to load your models from), + ``generate_schema()`` will use that list, rather than automatically finding all models for you. + +* Split model consructor into from-Python and from-DB paths, leading to 15-25% speedup for large fetch operations. +* More efficient queryset manipulation, 5-30% speedup for small fetches. 0.12.5 ------ diff --git a/docs/ORM_Perf.png b/docs/ORM_Perf.png index 85fc48f6a4c1601484b5528070b7eca07cf9cb3e..0b13cce1410ca926c5aa1c12386a7539833993a0 100644 GIT binary patch literal 31049 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
g+EQ=UJKmeTVfVBC~(A2cG0t&i$QH4Kqw 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 List[Type[Model]] if attr._meta.app and attr._meta.app != app_label: continue attr._meta.app = app_label + attr._meta.finalise_pk() discovered_models.append(attr) return discovered_models @@ -275,7 +276,7 @@ def _get_config_from_config_file(cls, config_file: str) -> dict: def _build_initial_querysets(cls) -> None: for app in cls.apps.values(): for model in app.values(): - model._meta.generate_filters() + model._meta.finalise_model() model._meta.basequery = model._meta.db.query_class.from_(model._meta.table) model._meta.basequery_all_fields = model._meta.basequery.select( *model._meta.db_fields @@ -465,4 +466,4 @@ async def do_stuff(): loop.run_until_complete(Tortoise.close_connections()) -__version__ = "0.12.5" +__version__ = "0.12.6" diff --git a/tortoise/backends/base/executor.py b/tortoise/backends/base/executor.py index 3ad772485..858961863 100644 --- a/tortoise/backends/base/executor.py +++ b/tortoise/backends/base/executor.py @@ -1,3 +1,4 @@ +from copy import copy from functools import partial from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, Type # noqa @@ -50,7 +51,7 @@ async def execute_select(self, query, custom_fields: Optional[list] = None) -> l raw_results = await self.db.execute_query(query.get_sql()) instance_list = [] for row in raw_results: - instance = self.model(_from_db=True, **row) + instance = self.model._init_from_db(**row) if custom_fields: for field in custom_fields: setattr(instance, field, row[field]) @@ -248,6 +249,7 @@ def _make_prefetch_queries(self) -> None: related_model_field = self.model._meta.fields_map.get(field) related_model = related_model_field.type related_query = related_model.all().using_db(self.db) + related_query.query = copy(related_query.model._meta.basequery) if forwarded_prefetches: related_query = related_query.prefetch_related(*forwarded_prefetches) self._prefetch_queries[field] = related_query diff --git a/tortoise/models.py b/tortoise/models.py index 6a1027f4b..ac9185cd3 100644 --- a/tortoise/models.py +++ b/tortoise/models.py @@ -36,15 +36,15 @@ class MetaInfo: "abstract", "table", "app", - "_fields", - "_db_fields", + "fields", + "db_fields", "m2m_fields", "fk_fields", "backward_fk_fields", - "_fetch_fields", + "fetch_fields", "fields_db_projection", "_inited", - "_fields_db_projection_reverse", + "fields_db_projection_reverse", "filters", "fields_map", "default_connection", @@ -53,24 +53,26 @@ class MetaInfo: "_filters", "unique_together", "pk_attr", - "_generated_db_fields", + "generated_db_fields", "_model", "table_description", + "pk", + "db_pk_field", ) def __init__(self, meta) -> None: self.abstract = getattr(meta, "abstract", False) # type: bool self.table = getattr(meta, "table", "") # type: str self.app = getattr(meta, "app", None) # type: Optional[str] - self.unique_together = get_unique_together(meta) # type: Optional[Union[Tuple, List]] - self._fields = None # type: Optional[Set[str]] - self._db_fields = None # type: Optional[Set[str]] + self.unique_together = get_unique_together(meta) # type: Union[Tuple, List] + self.fields = set() # type: Set[str] + self.db_fields = set() # type: Set[str] self.m2m_fields = set() # type: Set[str] self.fk_fields = set() # type: Set[str] self.backward_fk_fields = set() # type: Set[str] - self._fetch_fields = None # type: Optional[Set[str]] + self.fetch_fields = set() # type: Set[str] self.fields_db_projection = {} # type: Dict[str,str] - self._fields_db_projection_reverse = None # type: Optional[Dict[str,str]] + self.fields_db_projection_reverse = {} # type: Dict[str,str] self._filters = {} # type: Dict[str, Dict[str, dict]] self.filters = {} # type: Dict[str, dict] self.fields_map = {} # type: Dict[str, fields.Field] @@ -79,9 +81,11 @@ def __init__(self, meta) -> None: self.basequery = Query() # type: Query self.basequery_all_fields = Query() # type: Query self.pk_attr = getattr(meta, "pk_attr", "") # type: str - self._generated_db_fields = None # type: Optional[Tuple[str]] + self.generated_db_fields = None # type: Tuple[str] # type: ignore self._model = None # type: "Model" # type: ignore self.table_description = getattr(meta, "table_description", "") # type: str + self.pk = None # type: fields.Field # type: ignore + self.db_pk_field = "" # type: str def add_field(self, name: str, value: Field): if name in self.fields_map: @@ -89,76 +93,20 @@ def add_field(self, name: str, value: Field): setattr(self._model, name, value) value.model = self._model self.fields_map[name] = value - self._fields = None if value.has_db_field: self.fields_db_projection[name] = value.source_field or name - self._fields_db_projection_reverse = None if isinstance(value, fields.ManyToManyField): self.m2m_fields.add(name) - self._fetch_fields = None elif isinstance(value, fields.BackwardFKRelation): self.backward_fk_fields.add(name) - self._fetch_fields = None field_filters = get_filters_for_field( field_name=name, field=value, source_field=value.source_field or name ) self._filters.update(field_filters) - self.generate_filters() - - @property - def fields_db_projection_reverse(self) -> Dict[str, str]: - if self._fields_db_projection_reverse is None: - self._fields_db_projection_reverse = { - value: key for key, value in self.fields_db_projection.items() - } - return self._fields_db_projection_reverse - - @property - def fields(self) -> Set[str]: - if self._fields is None: - self._fields = set(self.fields_map.keys()) - return self._fields - - @property - def db_fields(self) -> Set[str]: - if self._db_fields is None: - self._db_fields = set(self.fields_db_projection.values()) - return self._db_fields - - @property - def fetch_fields(self): - if self._fetch_fields is None: - self._fetch_fields = self.m2m_fields | self.backward_fk_fields | self.fk_fields - return self._fetch_fields - - @property - def pk(self): - return self.fields_map[self.pk_attr] - - @property - def db_pk_field(self) -> str: - field_object = self.fields_map[self.pk_attr] - return field_object.source_field or self.pk_attr - - @property - def is_pk_generated(self) -> bool: - field_object = self.fields_map[self.pk_attr] - return field_object.generated - - @property - def generated_db_fields(self) -> Tuple[str]: - """Return list of names of db fields that are generated on db side""" - if self._generated_db_fields is None: - generated_fields = [] - for field in self.fields_map.values(): - if not field.generated: - continue - generated_fields.append(field.source_field or field.model_field_name) - self._generated_db_fields = tuple(generated_fields) # type: ignore - return self._generated_db_fields # type: ignore + self.finalise_fields() @property def db(self) -> BaseDBAsyncClient: @@ -170,7 +118,33 @@ def db(self) -> BaseDBAsyncClient: def get_filter(self, key: str) -> dict: return self.filters[key] - def generate_filters(self) -> None: + def finalise_pk(self) -> None: + self.pk = self.fields_map[self.pk_attr] + self.db_pk_field = self.pk.source_field or self.pk_attr + + def finalise_model(self) -> None: + """ + Finalise the model after it had been fully loaded. + """ + self.finalise_fields() + self._generate_filters() + + def finalise_fields(self) -> None: + self.db_fields = set(self.fields_db_projection.values()) + self.fields = set(self.fields_map.keys()) + self.fields_db_projection_reverse = { + value: key for key, value in self.fields_db_projection.items() + } + self.fetch_fields = self.m2m_fields | self.backward_fk_fields | self.fk_fields + + generated_fields = [] + for field in self.fields_map.values(): + if not field.generated: + continue + generated_fields.append(field.source_field or field.model_field_name) + self.generated_db_fields = tuple(generated_fields) # type: ignore + + def _generate_filters(self) -> None: get_overridden_filter_func = self.db.executor_class.get_overridden_filter_func for key, filter_info in self._filters.items(): overridden_operator = get_overridden_filter_func( # type: ignore @@ -301,6 +275,7 @@ def __search_for_field_attributes(base, attrs: dict): field.model = new_class meta._model = new_class + meta.finalise_fields() return new_class @@ -311,8 +286,42 @@ class Model(metaclass=ModelMeta): def __init__(self, *args, _from_db: bool = False, **kwargs) -> None: # self._meta is a very common attribute lookup, lets cache it. meta = self._meta - self._saved_in_db = _from_db or (meta.pk_attr in kwargs and meta.is_pk_generated) + self._saved_in_db = _from_db or (meta.pk_attr in kwargs and meta.pk.generated) + self._init_lazy_fkm2m() + + # Assign values and do type conversions + passed_fields = {*kwargs.keys()} + passed_fields.update(meta.fetch_fields) + passed_fields |= self._set_field_values(kwargs) + # Assign defaults for missing fields + for key in meta.fields.difference(passed_fields): + field_object = meta.fields_map[key] + if callable(field_object.default): + setattr(self, key, field_object.default()) + else: + setattr(self, key, field_object.default) + + @classmethod + def _init_from_db(cls, **kwargs) -> MODEL_TYPE: + self = cls.__new__(cls) + self._saved_in_db = True + self._init_lazy_fkm2m() + + meta = self._meta + + for key, value in kwargs.items(): + if key in meta.fields: + field_object = meta.fields_map[key] + setattr(self, key, field_object.to_python_value(value)) + elif key in meta.db_fields: + field_object = meta.fields_map[meta.fields_db_projection_reverse[key]] + setattr(self, key, field_object.to_python_value(value)) + + return self + + def _init_lazy_fkm2m(self) -> None: + meta = self._meta # Create lazy fk/m2m objects for key in meta.backward_fk_fields: field_object = meta.fields_map[key] @@ -332,19 +341,6 @@ def __init__(self, *args, _from_db: bool = False, **kwargs) -> None: ManyToManyRelationManager(field_object.type, self, field_object), # type: ignore ) - # Assign values and do type conversions - passed_fields = set(kwargs.keys()) - passed_fields.update(meta.fetch_fields) - passed_fields |= self._set_field_values(kwargs) - - # Assign defaults for missing fields - for key in meta.fields.difference(passed_fields): - field_object = meta.fields_map[key] - if callable(field_object.default): - setattr(self, key, field_object.default()) - else: - setattr(self, key, field_object.default) - def _set_field_values(self, values_map: Dict[str, Any]) -> Set[str]: """ Sets values for fields honoring type transformations and diff --git a/tortoise/query_utils.py b/tortoise/query_utils.py index c17b8d917..ab8792f1b 100644 --- a/tortoise/query_utils.py +++ b/tortoise/query_utils.py @@ -1,3 +1,4 @@ +from copy import copy from typing import Any, List, Mapping, Optional, Tuple # noqa from pypika import Table @@ -309,6 +310,7 @@ class Prefetch: def __init__(self, relation, queryset) -> None: self.relation = relation self.queryset = queryset + self.queryset.query = copy(self.queryset.model._meta.basequery) def resolve_for_queryset(self, queryset) -> None: relation_split = self.relation.split("__") diff --git a/tortoise/queryset.py b/tortoise/queryset.py index 8e5749dc4..70d92c947 100644 --- a/tortoise/queryset.py +++ b/tortoise/queryset.py @@ -11,6 +11,9 @@ from tortoise.query_utils import Prefetch, Q, QueryModifier, _get_joins_for_related_field from tortoise.utils import QueryAsyncIterator +# Empty placeholder - Should never be edited. +QUERY = Query() + class AwaitableQuery: __slots__ = ("_joined_tables", "query", "model", "_db", "capabilities") @@ -18,7 +21,7 @@ class AwaitableQuery: def __init__(self, model) -> None: self._joined_tables = [] # type: List[Table] self.model = model - self.query = model._meta.basequery # type: Query + self.query = QUERY # type: Query self._db = None # type: Optional[BaseDBAsyncClient] self.capabilities = model._meta.db.capabilities @@ -33,11 +36,8 @@ def resolve_filters(self, model, q_objects, annotations, custom_filters) -> None self.query = self.query.join(join[0], how=JoinType.left_outer).on(join[1]) self._joined_tables.append(join[0]) - if where_criterion: - self.query = self.query.where(where_criterion) - - if having_criterion: - self.query = self.query.having(having_criterion) + self.query._wheres = where_criterion + self.query._havings = having_criterion def _join_table_by_field(self, table, related_field_name, related_field) -> None: joins = _get_joins_for_related_field(table, related_field, related_field_name) @@ -81,6 +81,9 @@ def __await__(self): self._make_query() return self._execute().__await__() + def __aiter__(self) -> QueryAsyncIterator: + return QueryAsyncIterator(self) + async def _execute(self): raise NotImplementedError() # pragma: nocoverage @@ -433,10 +436,10 @@ def _resolve_annotate(self) -> None: aggregation_info = aggregate.resolve(self.model) for join in aggregation_info["joins"]: self._join_table_by_field(*join) - self.query = self.query.select(aggregation_info["field"].as_(key)) + self.query._select_other(aggregation_info["field"].as_(key)) def _make_query(self) -> Query: - self.query = self.model._meta.basequery_all_fields + self.query = copy(self.model._meta.basequery_all_fields) self._resolve_annotate() self.resolve_filters( model=self.model, @@ -445,11 +448,11 @@ def _make_query(self) -> Query: custom_filters=self._custom_filters, ) if self._limit: - self.query = self.query.limit(self._limit) + self.query._limit = self._limit if self._offset: - self.query = self.query.offset(self._offset) + self.query._offset = self._offset if self._distinct: - self.query = self.query.distinct() + self.query._distinct = True self.resolve_ordering(self.model, self._orderings, self._annotations) return self.query @@ -460,30 +463,18 @@ async def _execute(self): prefetch_map=self._prefetch_map, prefetch_queries=self._prefetch_queries, ).execute_select(self.query, custom_fields=list(self._annotations.keys())) - if not instance_list: - if self._get: + if self._get: + if len(instance_list) == 1: + return instance_list[0] + if not instance_list: raise DoesNotExist("Object does not exist") - if self._single: + raise MultipleObjectsReturned("Multiple objects returned, expected exactly one") + if self._single: + if not instance_list: return None - return [] - elif self._get: - if len(instance_list) > 1: - raise MultipleObjectsReturned("Multiple objects returned, expected exactly one") - return instance_list[0] - elif self._single: return instance_list[0] return instance_list - def __await__(self): - clone = self._clone() - if clone._db is None: - clone._db = self.model._meta.db - clone._make_query() - return clone._execute().__await__() - - def __aiter__(self) -> QueryAsyncIterator: - return QueryAsyncIterator(self) - class UpdateQuery(AwaitableQuery): __slots__ = ("update_kwargs", "q_objects", "annotations", "custom_filters") @@ -534,14 +525,14 @@ def __init__(self, model, db, q_objects, annotations, custom_filters) -> None: self._db = db def _make_query(self): - self.query = self.model._meta.basequery + self.query = copy(self.model._meta.basequery) self.resolve_filters( model=self.model, q_objects=self.q_objects, annotations=self.annotations, custom_filters=self.custom_filters, ) - self.query = self.query.delete() + self.query._delete_from = True async def _execute(self): await self._db.execute_query(str(self.query)) @@ -558,14 +549,14 @@ def __init__(self, model, db, q_objects, annotations, custom_filters) -> None: self._db = db def _make_query(self): - self.query = self.model._meta.basequery + self.query = copy(self.model._meta.basequery) self.resolve_filters( model=self.model, q_objects=self.q_objects, annotations=self.annotations, custom_filters=self.custom_filters, ) - self.query = self.query.select(Count("*")) + self.query._select_other(Count("*")) async def _execute(self): result = await self._db.execute_query(str(self.query)) @@ -610,7 +601,7 @@ def add_field_to_select_query(self, field, return_as) -> None: table = Table(self.model._meta.table) if field in self.model._meta.fields_db_projection: db_field = self.model._meta.fields_db_projection[field] - self.query = self.query.select(getattr(table, db_field).as_(return_as)) + self.query._select_field(getattr(table, db_field).as_(return_as)) return if field in self.model._meta.fetch_fields: @@ -624,7 +615,7 @@ def add_field_to_select_query(self, field, return_as) -> None: related_table, related_db_field = self._join_table_with_forwarded_fields( model=self.model, field=field_split[0], forwarded_fields="__".join(field_split[1:]) ) - self.query = self.query.select(getattr(related_table, related_db_field).as_(return_as)) + self.query._select_field(getattr(related_table, related_db_field).as_(return_as)) return raise FieldError('Unknown field "{}" for model "{}"'.format(field, self.model.__name__)) @@ -691,8 +682,7 @@ def __init__( self._db = db def _make_query(self): - self.query = self.model._meta.basequery - + self.query = copy(self.model._meta.basequery) for positional_number, field in self.fields.items(): self.add_field_to_select_query(field, positional_number) @@ -703,11 +693,11 @@ def _make_query(self): custom_filters=self.custom_filters, ) if self.limit: - self.query = self.query.limit(self.limit) + self.query._limit = self.limit if self.offset: - self.query = self.query.offset(self.offset) + self.query._offset = self.offset if self.distinct: - self.query = self.query.distinct() + self.query._distinct = True self.resolve_ordering(self.model, self.orderings, self.annotations) async def _execute(self): @@ -759,7 +749,7 @@ def __init__( self._db = db def _make_query(self): - self.query = self.model._meta.basequery + self.query = copy(self.model._meta.basequery) for return_as, field in self.fields_for_select.items(): self.add_field_to_select_query(field, return_as) @@ -770,11 +760,11 @@ def _make_query(self): custom_filters=self.custom_filters, ) if self.limit: - self.query = self.query.limit(self.limit) + self.query._limit = self.limit if self.offset: - self.query = self.query.offset(self.offset) + self.query._offset = self.offset if self.distinct: - self.query = self.query.distinct() + self.query._distinct = True self.resolve_ordering(self.model, self.orderings, self.annotations) async def _execute(self):