Repository: getredash/redash

Issue:
Queries list page is slow when some queries has large query result
### Issue Summary

Queries listing page (`/queries`) is slow when some queries has large query result.
Because it execute admin queries which select all columns from query_results for each queries on the list.

### Steps to Reproduce

1. Create query which will return large result_set
2. Execute it
3. Access `/queries?page=1`

### Technical details:

* Redash Version: 1.0.0-rc2
* Browser/OS: Chrome 56
* How did you install Redash: docker-compose (used default docker-compose.yml for testing)



Discussion:
Thanks! This also uncovers a nasty N+1 queries issue there.

Resolving patch:
diff --git a/redash/models.py b/redash/models.py
--- a/redash/models.py
+++ b/redash/models.py
@@ -16,7 +16,7 @@
 from sqlalchemy.inspection import inspect
 from sqlalchemy.types import TypeDecorator
 from sqlalchemy.ext.mutable import Mutable
-from sqlalchemy.orm import object_session, backref
+from sqlalchemy.orm import object_session, backref, joinedload, subqueryload
 # noinspection PyUnresolvedReferences
 from sqlalchemy.orm.exc import NoResultFound
 from sqlalchemy import or_
@@ -806,12 +806,12 @@ def create(cls, **kwargs):
 
     @classmethod
     def all_queries(cls, group_ids, user_id=None, drafts=False):
-        q = (cls.query.join(User, Query.user_id == User.id)
-            .outerjoin(QueryResult)
+        q = (cls.query
+            .options(joinedload(Query.user),
+                     joinedload(Query.latest_query_data).load_only('runtime', 'retrieved_at'))
             .join(DataSourceGroup, Query.data_source_id == DataSourceGroup.data_source_id)
             .filter(Query.is_archived == False)
             .filter(DataSourceGroup.group_id.in_(group_ids))\
-            .group_by(Query.id, User.id, QueryResult.id, QueryResult.retrieved_at, QueryResult.runtime)
             .order_by(Query.created_at.desc()))
 
         if not drafts:
@@ -826,8 +826,7 @@ def by_user(cls, user):
     @classmethod
     def outdated_queries(cls):
         queries = (db.session.query(Query)
-                   .join(QueryResult)
-                   .join(DataSource)
+                   .options(joinedload(Query.latest_query_data).load_only('retrieved_at'))
                    .filter(Query.schedule != None)
                    .order_by(Query.id))
 
@@ -836,7 +835,7 @@ def outdated_queries(cls):
         for query in queries:
             if should_schedule_next(query.latest_query_data.retrieved_at, now,
                                     query.schedule, query.schedule_failures):
-                key = "{}:{}".format(query.query_hash, query.data_source.id)
+                key = "{}:{}".format(query.query_hash, query.data_source_id)
                 outdated_queries[key] = query
 
         return outdated_queries.values()
@@ -862,12 +861,11 @@ def search(cls, term, group_ids, include_drafts=False):
                 Query.data_source_id == DataSourceGroup.data_source_id)
             .filter(where)).distinct()
 
-        return Query.query.join(User, Query.user_id == User.id).filter(
-            Query.id.in_(query_ids))
+        return Query.query.options(joinedload(Query.user)).filter(Query.id.in_(query_ids))
 
     @classmethod
     def recent(cls, group_ids, user_id=None, limit=20):
-        query = (cls.query.join(User, Query.user_id == User.id)
+        query = (cls.query.options(subqueryload(Query.user))
                  .filter(Event.created_at > (db.func.current_date() - 7))
                  .join(Event, Query.id == Event.object_id.cast(db.Integer))
                  .join(DataSourceGroup, Query.data_source_id == DataSourceGroup.data_source_id)
@@ -879,7 +877,7 @@ def recent(cls, group_ids, user_id=None, limit=20):
                      DataSourceGroup.group_id.in_(group_ids),
                      or_(Query.is_draft == False, Query.user_id == user_id),
                      Query.is_archived == False)
-                 .group_by(Event.object_id, Query.id, User.id)
+                 .group_by(Event.object_id, Query.id)
                  .order_by(db.desc(db.func.count(0))))
 
         if user_id:
@@ -1069,12 +1067,11 @@ class Alert(TimestampMixin, db.Model):
 
     @classmethod
     def all(cls, group_ids):
-        # TODO: there was a join with user here to prevent N+1 queries. need to revisit this.
         return db.session.query(Alert)\
+            .options(joinedload(Alert.user), joinedload(Alert.query_rel))\
             .join(Query)\
             .join(DataSourceGroup, DataSourceGroup.data_source_id==Query.data_source_id)\
-            .filter(DataSourceGroup.group_id.in_(group_ids))\
-            .group_by(Alert)
+            .filter(DataSourceGroup.group_id.in_(group_ids))
 
     @classmethod
     def get_by_id_and_org(cls, id, org):


Test patch: