IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

ORM PHP Discussion :

[Doctrine] Récupérer une "grosse" quantité de données via doctrine pour un export


Sujet :

ORM PHP

  1. #1
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut [Doctrine] Récupérer une "grosse" quantité de données via doctrine pour un export
    Salut,

    Je rencontre un problème avec symfony et plus particulièrement Doctrine. En fait, je voudrais récupérer une "grosse" quantité (elle me semble pas si énorme que cela à mes yeux ...) de données afin de les exporter dans un fichier CSV.

    J'ai 4 table liées entres-elles à récupérer : une Participation "has many" Newsletters, Fields et Questions.

    Voilà le schéma correspondant aux 4 tables :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    QuizgenParticipation:
      actAs: { Timestampable: ~ }
      columns:
        quiz_id:      { type: integer, notnull: true }
        civility:     { type: string(255), notnull: true }
        birthdate:    { type: timestamp, notnull: true }
        firstname:    { type: string(255), notnull: true }
        lastname:     { type: string(255), notnull: true }
        city:         { type: string(255), notnull: true }
        zip_code:     { type: string(255), notnull: true }
        email:        { type: string(255), notnull: true }
        accept_rules: { type: boolean, notnull: true, default: NULL }
        shares:       { type: integer, notnull: true, default: 0 }
      relations:
        QuizgenQuiz: { onDelete: CASCADE, local: quiz_id, foreign: id, foreignAlias: QuizgenParticipations } 
        QuizgenNewsletters:
          class: QuizgenNewsletter
          refClass: QuizgenParticipationNewsletter
          local: participation_id
          foreign: newsletter_id
          foreignAlias: QuizgenParticipations
        QuizgenFields:
          class: QuizgenField
          refClass: QuizgenParticipationField
          local: participation_id
          foreign: field_id
          foreignAlias: QuizgenParticipations      
        QuizgenQuestions:
          class: QuizgenQuestion
          refClass: QuizgenParticipationQuestion
          local: participation_id
          foreign: question_id
          foreignAlias: QuizgenParticipations 
     
    QuizgenParticipationQuestion:
      columns:    
        participation_id:  { type: integer, notnull: true }
        question_id:       { type: integer, notnull: true }
        answer:            { type: string(255), notnull: true }
      relations:
        QuizgenParticipation: { onDelete: CASCADE, local: participation_id, foreign: id }     
        QuizgenQuestion:      { onDelete: CASCADE, local: question_id, foreign: id }   
     
    QuizgenParticipationNewsletter:
      columns:    
        participation_id:  { type: integer, notnull: true }
        newsletter_id:     { type: integer, notnull: true }
      relations:
        QuizgenParticipation: { onDelete: CASCADE, local: participation_id, foreign: id }     
        QuizgenNewsletter:    { onDelete: CASCADE, local: newsletter_id, foreign: id }
     
    QuizgenParticipationField:
      columns:    
        participation_id:  { type: integer, notnull: true }
        field_id:          { type: integer, notnull: true }
        value:             { type: string(255) }
      relations:
        QuizgenParticipation: { onDelete: CASCADE, local: participation_id, foreign: id }     
        QuizgenField: { onDelete: CASCADE, local: field_id, foreign: id }
    Comme vous vous en doutez, QuizgenParticipationField, QuizgenParticipationNewsletter et QuizgenParticipationQuestion sont liés à d'autres infos, mais je n'en ai pas besoin ici.

    Il y aura entre 1000 et 5000 participations à exporter. Chaque participation est suceptible de posséder environ une 10aine de questions, 3 champs et 3 newsletters.

    J'ai donc optimisé ma requete DQL en faisant des leftJoin pour n'avoir qu'une seule requête de générée. J'ai également hydraté cela en tableau :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     public function getParticipationsForQuiz($quizId)
      {
        $q = $this->createQuery('p INDEXBY p.id')
          ->where('p.quiz_id = ?', $quizId)
          ->leftJoin('p.QuizgenParticipationField pf INDEXBY pf.field_id')
          ->leftJoin('p.QuizgenParticipationNewsletter pn INDEXBY pn.newsletter_id')
          ->leftJoin('p.QuizgenParticipationQuestion pq')
          ->orderBy('p.id');
     
          return  $q->execute(array(),Doctrine::HYDRATE_ARRAY);
      }
    Le problème,c 'est que malgré ça, pour seulement 800 participations (10 questions pour chaque participation), ça me prend 23 secondes d'execution et 37Mo en mémoire ! Mais il ne m'est pas possible de dépasser 16Mo de mémoire.

    Qu'est ce qui n'est pas correct dans ce que j'ai fait ? Comme se fait-il que ma requête nécessite autant de mémoire ?

    Merci d'avance pour votre aide !

  2. #2
    Expert confirmé

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Par défaut
    Hello, t'as les droit sur FILE dans ta base (si t'es en mysql) ?

  3. #3
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut
    Hmm, bonne question.

    Selon toi, je ne peux pas m'en sortir avec Doctrine ?

  4. #4
    Expert confirmé

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Par défaut
    il faudrait déjà retourner le statement plutôt que l'array complet la tu prends de la mémoire pour rien

  5. #5
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut
    J'ai pensé à cela mais en passant par PDO directement. Comment pourrais-je faire à partir de mon objet Doctrine_Query ?

  6. #6
    Expert confirmé

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Par défaut
    Citation Envoyé par Neveldo Voir le message
    J'ai pensé à cela mais en passant par PDO directement. Comment pourrais-je faire à partir de mon objet Doctrine_Query ?
    tu recuperes la requete via getSqlQuery, ensuite tu recuperes pdo via le Doctine_Connection::$dbh

  7. #7
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut
    J'ai testé, c'est effectivement un peu plus léger (le execute() de PDO me prend quand même 10Mo dans la mémoire).

    Mais pour retraiter les données qui sont brute (contrairement à ce que je récupérais avec doctrine avec ses tableaux imbriqués), ça risque d'être particulièrement lourd.

  8. #8
    Expert confirmé

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Par défaut
    oui mais après le exécute tu fait quoi ?

  9. #9
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut
    Je boucle en faisant des appels à fetch().

    Je pensais éventuellement faire une requête qui sélectionne juste les ID de mes participations. Puis je boucle dessus pour récupérer chaque participations et leurs infos associées, comme ça je peux supprimer l'objet doctrine de la mémoire à chaque fois. Mais ça va engendrer un grand nombre de requêtes à la base.

  10. #10
    Expert confirmé

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Par défaut
    Citation Envoyé par Neveldo Voir le message
    Je boucle en faisant des appels à fetch().

    Je pensais éventuellement faire une requête qui sélectionne juste les ID de mes participations. Puis je boucle dessus pour récupérer chaque participations et leurs infos associées, comme ça je peux supprimer l'objet doctrine de la mémoire à chaque fois. Mais ça va engendrer un grand nombre de requêtes à la base.
    ouai, normalement avec le while seul la ressource courante est dans la memoire, comment tu fait pour ecrire dans ton fichier apres ? parce que il faut aussi l'écrire en stream

  11. #11
    Expert confirmé
    Avatar de Michel Rotta
    Homme Profil pro
    DPO
    Inscrit en
    Septembre 2005
    Messages
    4 954
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : DPO
    Secteur : Distribution

    Informations forums :
    Inscription : Septembre 2005
    Messages : 4 954
    Par défaut
    Tu peux peut-être aussi travailler dans une boucle et te limiter à des paquets de 200 enregistrements avec des offset et des limites...

  12. #12
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut
    Oui c'est une bonne idée.

    Mais impossible de libérer la mémoire. J'ai beau faire des appel à free() sur la query et sur les objets générés ainsi que des unset(), aucun effet sur la mémoire.

    Et ce que je ne comprend pas, c'est que même avec la méthode d'hydration HYDRATE_ON_DEMAND, j'atteinds quelque chose comme 60Mo en utilisation mémoire ...

  13. #13
    Expert confirmé

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Par défaut
    non vaut mieux faire en stream que par paquet

  14. #14
    Expert confirmé
    Avatar de Michel Rotta
    Homme Profil pro
    DPO
    Inscrit en
    Septembre 2005
    Messages
    4 954
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : DPO
    Secteur : Distribution

    Informations forums :
    Inscription : Septembre 2005
    Messages : 4 954
    Par défaut
    Question idiote.

    C'est une opération unique ou une fonctionalité rare qui ne doit être effectuée que par un administrateur ou une fonction régulière qui doit pouvoir être exécutée n'importe quant par n'importe quel utilisateur.

    (barrez les mentions inutiles !)

  15. #15
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut
    Ce n'est pas l'export d'une base entière, ni même d'une table entière contenant énormément de données. Juste un export d'entre 1000 et 5000 lignes (5000 pour prévoir très large ...) avec quelques leftjoin ...

    J'ai fait un petit test pour faire des requête qui sélectionne mes données par paquet :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
       for ($i = 0; $i < 500; $i += 10) {
          $q = $this->createQuery('p')
          ->where('p.quiz_id = ?', $quizId)
          ->leftJoin('p.QuizgenParticipationField pf')
          ->leftJoin('p.QuizgenParticipationNewsletter pn')
          ->leftJoin('p.QuizgenParticipationQuestion pq')
          ->orderBy('p.id')
          ->offset($i)
          ->limit(10);
          $results =  $q->execute(array(), Doctrine_Core::HYDRATE_ARRAY);
     
          foreach($results as $obj) {
            echo $obj['id'] . ' ';
          }
        }
    Mais entre chaque exécution, la mémoire augmente, jusqu'à atteindre la limite ... Je comprend vraiment pas pourquoi les données restent en mémoire (je tourne sous PHP 5.3).

  16. #16
    Expert confirmé

    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    7 920
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 7 920
    Par défaut
    normalement tu dois faire qu'une seule requete, apres a chaque fetch, tu dois faire un fwite dans ton fichier ou un fputcsv si c'est pour CSV,
    sinon ta verfier les droits sur FILE, parce que mysql sait généré du CSV a partir d'un requete et mettre tout ca dans un fichier

  17. #17
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut
    Hmmmm, problème à priori résolut ...

    J'étais en environnement de dev sous symfony (normal), et en passant en prod, je retrouve une consommation mémoire absolument normale ... Je vais continuer à faire quelques tests.

  18. #18
    Expert confirmé
    Avatar de Michel Rotta
    Homme Profil pro
    DPO
    Inscrit en
    Septembre 2005
    Messages
    4 954
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : DPO
    Secteur : Distribution

    Informations forums :
    Inscription : Septembre 2005
    Messages : 4 954
    Par défaut
    Effectivement, le mode debug introduit un comportement de ... debug consommateur de mémoire.

  19. #19
    Membre averti
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mars 2008
    Messages
    46
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mars 2008
    Messages : 46
    Par défaut
    Oui effectivement, il fallait y penser.

    Finalement, je sélectionne mes participations par blocs de 60 en hydrate_array et c'est parfait

  20. #20
    Expert confirmé
    Avatar de Michel Rotta
    Homme Profil pro
    DPO
    Inscrit en
    Septembre 2005
    Messages
    4 954
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : DPO
    Secteur : Distribution

    Informations forums :
    Inscription : Septembre 2005
    Messages : 4 954
    Par défaut
    Si cela te conviens, il ne reste plus qu'à faire

Discussions similaires

  1. Réponses: 0
    Dernier message: 31/05/2011, 19h13
  2. Réponses: 3
    Dernier message: 10/11/2008, 11h58
  3. récupérer une image de la base de données
    Par ijklm dans le forum Struts 1
    Réponses: 6
    Dernier message: 24/05/2006, 09h59

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo