Skip to content

GitLab

  • Menu
Projects Groups Snippets
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in
  • vidjil vidjil
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 1,697
    • Issues 1,697
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 91
    • Merge requests 91
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Monitor
    • Monitor
    • Incidents
  • Packages & Registries
    • Packages & Registries
    • Container Registry
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • vidjil
  • vidjilvidjil
  • Issues
  • #2747

Closed
Open
Created Oct 23, 2017 by Ryan Herbert@RyanHerbContributor

Organiser les sample_sets par projet pour accélérer les requêtes

Actuellement on a un système qui est très flexible en termes de permissions et d'associations de samples à plusieurs sets, voire plusieurs groupes. Mais c'est aussi plutôt lent à cause du système de permissions que j'ai créé.

Avec la notion de hierarchie de groupes, on se retrouve avec des requêtes plutôt complexes pour déterminer si l'utilisateur a accès à un élément.

Il faut chercher si l'utilisateur appartient à un groupe qui a la permission access ou à un groupe dont le parent a la permission access

Donc lister les sets d'un type particulier auxquels un utilisateur a accès revient au code suivant:

def vidjil_accessible_query(self, name, table, user_id=None):
        """
        Returns a query with all accessible records for user_id or
        the current logged in user
        this method does not work on GAE because uses JOIN and IN

        This is an adaptation of accessible_query that better fits
        the current auth system with group associations

        :param: name: The name of the query (eg. 'read')
        :param: table: The table for accessibility
        :param: user_id: ID of the user

        Example:
            Use as::

                db(auth.vidjil_accessible_query('read', db.mytable, 1)).select(db.mytable.ALL)

        """
        if not user_id:
            user_id = self.user_id
        db = self.db
        if isinstance(table, str) and table in self.db.tables():
            table = self.db[table]
        elif isinstance(table, (Set, Query)):
            # experimental: build a chained query for all tables
            if isinstance(table, Set):
                cquery = table.query
            else:
                cquery = table
            tablenames = db._adapter.tables(cquery)
            for tablename in tablenames:
                cquery &= self.vidjil_accessible_query(name, tablename,
                                                user_id=user_id)
            return cquery
        if not isinstance(table, str) and\
                self.has_permission(name, table, 0, user_id):
            return table.id > 0
        membership = self.table_membership()
        permission = self.table_permission()
        perm_groups = self.get_permission_groups(name, user_id)
        query = (table.id.belongs(
                db(((membership.user_id == user_id) &
                    (membership.group_id.belongs(perm_groups)) &
                    (membership.group_id == permission.group_id) &
                    (permission.name == PermissionEnum.access.value) &
                    (permission.table_name == table)))._select(permission.record_id)) |
            table.id.belongs(
                db(((membership.user_id == user_id) &
                    (membership.group_id.belongs(perm_groups)) &
                    (membership.group_id == db.group_assoc.second_group_id) &
                    (db.group_assoc.first_group_id == permission.group_id) &
                    (permission.name == PermissionEnum.access.value) &
                    (permission.table_name == table)))._select(permission.record_id)))
        if self.settings.everybody_group_id:
            query |= table.id.belongs(
                db(permission.group_id == self.settings.everybody_group_id)
                    (permission.name == name)
                    (permission.table_name == table)
                    ._select(permission.record_id))
        return query

Ce qui produit la requête SQL suivante:

SELECT `patient`.`id`, `patient`.`first_name`, `patient`.`last_name`, `patient`.`birth`, `patient`.`info`, `patient`.`id_label`, `patient`.`creator`, `patient`.`sample_set_id` FROM `patient`
WHERE (
  (`patient`.`id` IN
    (SELECT `auth_permission`.`record_id` FROM `auth_permission`, `auth_membership`
    WHERE (
      (
        (
          (
            (`auth_membership`.`user_id` = 2)
            AND (`auth_membership`.`group_id` IN (4))
          )
          AND (`auth_membership`.`group_id` = `auth_permission`.`group_id`)
        ) AND (`auth_permission`.`name` = 'access')
      ) AND (`auth_permission`.`table_name` = 'patient')
    ))
  )
  OR (`patient`.`id` IN
    (SELECT `auth_permission`.`record_id` FROM `auth_permission`, `auth_membership`, `group_assoc`
    WHERE (
      (
        (
          (
            (
              (`auth_membership`.`user_id` = 2)
              AND (`auth_membership`.`group_id` IN (4))
            )
            AND (`auth_membership`.`group_id` = `group_assoc`.`second_group_id`)
          ) AND (`group_assoc`.`first_group_id` = `auth_permission`.`group_id`)
        ) AND (`auth_permission`.`name` = 'access')
      ) AND (`auth_permission`.`table_name` = 'patient')
    )
  )
));

On a donc une requête composée de deux sous-requêtes chacune contenant un IN aussi long que le nombre de groupes auxquels l'utilisateur appartient, l'une avec un join et l'autre avec deux join (ici les joins sont implicites).

Et les autres primitives du model VidjilAuth sont composées de plusieurs joins et souvent plusieurs requêtes.

Avec une organisation par projet (avec une nouvelle table, et une clef étrangère dans la table sample_set) on pourrait se débarrasser complètement de la permission access, et par la même occasion on laisse tomber cette anbiguïté qu'on a avec les groupes de projets/hôpitaux et les groupes de rôles/permissions.

La requête "patient" deviendrait:

def get_project_sets(type, project_id):
    return db(
        (db.sample_set.project_id == project_id) &
        (db.sample_set.sample_type == type) &
        (db[type].sample_set_id == db.sample_set.id)
    ).select(db[type].*)

Avec auparavant avoir vérifié que l'utilisateur ait accès à un groupe/rôle apparetenant au projet (2 joins sur relativement peu de données). Ce qui nous donne un total de 4 joins sur deux requêtes par rapport à un total de 3 joins répartis sur 3 requêtes, et on perd les IN

Il y a plusieurs autres endroits où des gains de jointures ou de requêtes seraient aussi envisageables (get_permission(...), can_view_sample_set(...), ...).

Ainsi que des endroits où les requêtes seraient plus complexes ?

J'aurais tendance à vouloir limiter les associations de samples à des sets du même projet car celà simplifierai certaines requêtes, comme l'autocompletiondes tags, et ainsi que celle des samples_sets à l'ajout d'un sample.

Ca éliminerait également l'impacte des grosses requêtes auxquelles nous les admins soumettons la BDD. Et donc on n'occuperait plus un (voire plus) de processus uwsgi pendant des durées prolongées (par exemple, plus d'une minute pour un compare patient).

Cette moification demanderait beaucoup de travail, donc ça ne serait pas pour tout de suite, mais je pense que celà réduirait le nombre de timeouts subis par nos utilisateurs. Celà demanderait peut-être plus d'analyse, de tests et de benchmarks.

Edited Oct 23, 2017 by Mathieu Giraud
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Assignee
Assign to
Time tracking