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

Langage PHP Discussion :

Récupération des données et taille mémoire


Sujet :

Langage PHP

  1. #1
    Membre à l'essai
    Homme Profil pro
    Développeur Web
    Inscrit en
    Décembre 2011
    Messages
    22
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : Santé

    Informations forums :
    Inscription : Décembre 2011
    Messages : 22
    Points : 17
    Points
    17
    Par défaut Récupération des données et taille mémoire
    Bonjour,

    nous sommes actuellement en train de mettre une partie de notre application en mode hors ligne (HTML5 indexeddb).

    Lorsque l'utilisateur se connecte pour la première fois à notre application, on lance une requête AJAX afin de récupérer l'ensemble des données de l'utilisateur. La taille des données d'un utilisateur peut atteindre plusieurs 100aine de méga.

    On a donc fait un script PHP pour récupérer l'ensemble des données des utilisateurs dans la base de données.
    Mais lorsque la taille des données à récupérer est importante, on obtient un
    Fatal error: Allowed memory size of 2147483648 bytes exhausted
    Nous avons configuré dans php.ini la variable "memory_size" à 2G.

    Notre script PHP (simplifié pour les tests) :

    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
    <?php
     
    // permet d'initialiser l'instanciation automatique des classes, la connexion vers Doctrine  ...
    include_once 'bootstrap.php';
     
    echo "Memory used: ".number_format(memory_get_usage())." bytes"; // Memory used: 7,241,280 bytes
     
    $output = array();
    $output['records'] = array();
    $output['deletedRecords'] = array();
     
    $connection = $entityManager->getConnection();
     
    // récupération de tous les enregistrements d'une table
    $statement = $connection->executeQuery("
                SELECT EVT.`EVT_ID` as `id`,
                           EVT.`EVT_NAME` as `name`,
                           ...
                FROM `T_EVENT_EVT` EVT
                WHERE  EVT.`USR_ID` = :userId
    ", array('userId' => $userId));
    $output['records']['events'] = $statement->fetchAll(PDO::FETCH_ASSOC); // Retourne 100000 lignes environ
     
    echo "Memory used: ".number_format(memory_get_usage())." bytesnn"; // Memory used: 551,201,776 bytes @@
     
    ?>
    Comme vous pouvez le voir, la mémoire utilisée passe de 7Mo en début de fichier à 551Mo à la fin, pour une seule requête SQL. Dès que je rajoute les autres requêtes pour les autres tables, j'ai l'erreur citée plus haut.

    A quoi peut être dû cette augmentation de la mémoire et comment la régler ?
    Sinon comment feriez vous pour récupérer toutes les données d'un utilisateur et les envoyer sur le poste client ?
    Est ce que PHP est suffisement robuste pour ce genre de traitement ou doit on passer par un autre langage ?

    En vous remerçiant,

    Sébastien BORDAT

  2. #2
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Hello

    A quoi peut être dû cette augmentation de la mémoire et comment la régler ?
    A ça $statement->fetchAll(PDO::FETCH_ASSOC);, tu prends toutes les données de la base et tu les mets dans un (potentiellement) très gros tableau.

    Comme tu le sais sans doute, une base de données sait traiter un volume conséquent de données, c'est son job. Mais en réalité, le SGBD ne fournit pas la donnée avant qu'on fasse un fetch, dans le cas d'une très grosse requête (mettons 10 millions de tuples retournés) il est évident que le SGBD n'a pas tout envoyé directement à PHP. C'est lors du fetch qu'il va fournir la donnée. Donc quand tu fais fetchAll, tu vas tout prendre et tout stocker dans une variable sur le tas et la boom ! Tu dépasses la limite mémoire.

    PHP n'est pas franchement un langage taillé pour l'économie mémoire (y'a qu'a voir ce qu'il se passe sur la pile lors des appels de fonction...) mais de toute façon, même en C++ tu aurais ce problème vu la volumétrie de données que tu utilise. Donc on ne solutionnera pas le problème en changeant de technologie.

    Comment résoudre ce problème ? Selon moi tu devrais envisager de "streamer" les données, c'est à dire produire le flux au fil de l'eau et non tout mettre en cache. T'as rien demandé au niveau de la mise en cache du flux de sortie ? C'est normal, PHP le fait à ta place. En fait, à chaque fois que tu fais un echo ou un print dans ton script, PHP ajoute le contenu au produit au buffer de sortie. Ce buffer n'est réellement envoyé au client qu'a la fin du script donc si tu produit plus de données que la limitation de PHP, alors le script crashe.

    Pour contourner ça, on va tout simplement désactiver la bufferisation de sortie.

    Voici un exemple simple:
    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
    <?php
     
    set_time_limit(0);
    ini_set('zlib.output_compression', 0);
    ini_set('implicit_flush', 1);
     
    // déclencher le téléchargement
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="download.bin"');
     
    // *le fonction débile
    function generate_random_binary_data ($octets) {
        $str = "";
        foreach (range(1,$octets) as $i) {
            $str .= rand(0,9);
        }
        return $str;
    }
     
    // 1 million d'itération
    foreach (range(1,1000000) as $i) {
        echo generate_random_binary_data(1024); // 1ko de données bidon
    }
    La fonction generate_random_binary_data est là pour émuler un PDOStatement::fetch en fournissant 1ko d'information aléatoire.

    Pour que ce script fonctionne correctement, il faut impérativement désactiver la bufferisation de contenu de PHP donc mettre la directive output_buffering à 0 dans php.ini (ça ne marche malheureusement pas avec ini_set).

    Une fois le script lancé, il produit bien un fichier d'1Go et pourtant le script ne plante pas à cause de la limitation mémoire. Le prix à payer c'est qu'on n'envoie pas au navigateur la taille du fichier donc l'utilisateur ne peut plus savoir quand son téléchargement sera fini.

  3. #3
    Membre à l'essai
    Homme Profil pro
    Développeur Web
    Inscrit en
    Décembre 2011
    Messages
    22
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : Santé

    Informations forums :
    Inscription : Décembre 2011
    Messages : 22
    Points : 17
    Points
    17
    Par défaut
    Bonjour,

    merci pour la réponse et l'explication que vous nous avez données.

    J'ai donc essayé de "streamer" les données en PHP avec une requête AJAX, le problème c'est qu'au bout d'un moment, ma page web est bloquée, bien que le fichier continue d'être téléchargé.
    Une fois le téléchargement fini, la page est de nouveau opérationnelle.

    Voici le code que j'ai utilisé pour mes tests :

    Fichier ajax_stream.php :

    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
    <?php
     
        set_time_limit(0);
        ini_set('zlib.output_compression', 0);
        ini_set('implicit_flush', 1);
     
        // entete
        header('Content-Type: application/octet-stream');
        header('Content-Control: no-cache');
     
    	ob_start();
     
        function send_message($id, $message, $progress = NULL) {
            $d = array('id' => $id, 'message' => $message, 'progress' => $progress);
            echo json_encode($d) . PHP_EOL;
    		ob_flush();
            flush();
        }
     
        function generate_random_binary_data ($octets) {
            $str = "";
            foreach (range(1,$octets) as $i) {
                $str .= rand(0,9);
            }
            return $str;
        }
     
        //LONG RUNNING TASK
        foreach (range(1,10000) as $i) {
    		usleep(100);
            $p = ($i * 100) / 10000;
            send_message(generate_random_binary_data(1024), $p . '% complete. server time: ' . date("h:i:s", time()) , $p);
        }
     
        send_message(generate_random_binary_data(1024), 'COMPLETE');
    Fichier client_stream.php :

    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
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Test XHR / Ajax Streaming without polling</title>
        <script type="text/javascript">
     
            function doClear() {
                document.getElementById("divProgress").innerHTML = "";
            }
     
            function log_message(message) {
                document.getElementById("divProgress").innerHTML += message + '<br />';
            }
     
            function ajax_stream() {
                if (!window.XMLHttpRequest) {
                    log_message("Your browser does not support the native XMLHttpRequest object.");
                    return;
                }
     
                try {
     
    				var xhr = new XMLHttpRequest();  
                    xhr.previous_text = '';
                    xhr.onerror = function() { log_message("[XHR] Fatal Error."); };
                    xhr.onreadystatechange = function() {
                        try {
     
                            if (xhr.readyState > 2) {
     
                                var new_response = xhr.responseText.substring(xhr.previous_text.length);
                                var result = JSON.parse( new_response );
     
                                console.log(result);
     
                                xhr.previous_text = xhr.responseText;
     
                            }   
     
                        } catch (e) {
                            //log_message("<b>[XHR] Exception: " + e + "</b>");
                        }
     
                    };
     
                    xhr.open("GET", "ajax_stream.php", true);
                    xhr.send("Making request...");      
     
                } catch (e) {
                    log_message("<b>[XHR] Exception: " + e + "</b>");
                }
            }
     
        </script>
    </head>
     
    <body>
        <button onclick="ajax_stream();">Start Ajax Streaming</button>
        <button onclick="doClear();">Clear Log</button>
        <br />
        Results
        <br />
        <div style="border:1px solid #000; padding:10px; width:300px; height:200px; overflow:auto; background:#eee;" id="divProgress"></div>
        <br />
        <div style="border:1px solid #ccc; width:300px; height:20px; overflow:auto; background:#eee;">
            <div id="progressor" style="background:#07c; width:0%; height:100%;"></div>
        </div>
    </body>
    </html>
    J'ai également désactivé la directive output_buffering dans php.ini.

    Est ce que je part dans la bonne direction ou c'est un peu plus compliqué que ca ?

    En vous remerçiant,

    Sébastien BORDAT

  4. #4
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    L'exemple que je t'ai donné était davantage destiné au téléchargement direct d'un gros fichier. Pour mettre un gros volume de données dans une base coté client c'est un peu plus compliqué.

    Tout en gardant en mémoire ce que je t'ai expliqué précédement concernant l'utilisation mémoire et la nécessité d'envoyer les données au fil de l'eau, on va implémenter une websocket afin de permettre à JavaScript de récupérer les données en mode flux et non d'attendre que tout le fichier soit produit pour l'exploiter.

    Mais avant de commencer, est-ce que tu es root sur ton serveur ? Il est aussi impératif de disposer de la version 5.3.0 de PHP au minimum.

  5. #5
    Membre à l'essai
    Homme Profil pro
    Développeur Web
    Inscrit en
    Décembre 2011
    Messages
    22
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : Santé

    Informations forums :
    Inscription : Décembre 2011
    Messages : 22
    Points : 17
    Points
    17
    Par défaut
    Malheureusement, les serveurs sur lesquels nous sommes ne nous permettent pas de faire des websockets.
    On a déjà demandé à notre hébergeur s'il pouvait faire quelque chose, et il nous a repondu qu'a cause de leurs proxys, c'etait impossible.

  6. #6
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Aïe, ça va méchamment contrecarrer nos plans. Autant provoquer le téléchargement d'un très gros fichier c'est pas compliqué, autant streamer de la data pour JavaScript sans les websockets ça relève de la mission suicide.

    En même temps, "a cause de leurs proxy" non, à cause de leur incapacité à mettre en place autre chose que du proxying de requêtes HTTP oui !

    Du coup, ça va être carrément moins fun, on va devoir faire du long pooling en JS pour obtenir les données blocs par blocs en faisant autant de requêtes HTTP que nécessaire.

    Pour ça, on va devoir extraire les données par segment, donc à grands coups de limit (si on est sur MySQL), préparer une liste des segments et mettre en place une stratégie pour permettre le découpage de grands ensembles en plus petits lots.

    L'idéal serait d'implémenter un mécanisme de pilotage des fetch db en JavaScript, en gros du RPC, ce sera stateful coté serveur et lent par dessus le marché. Il faut implémenter coté serveur un service (REST ou SOAP peu importe) qui permette:

    1. d'obtenir la liste des segments (ou à défaut leur nombre)
    2. un service qui permet d'obtenir le segment suivant
    3. ... et ce pour chaque ensemble de données à transmettre


    Coté PHP, il faudra pour chaque client mémoriser la position courante dans la file d'attente, ce qui implique l'usage d'une session pour faire varier la clause limit de tes requêtes SQL.

  7. #7
    Membre à l'essai
    Homme Profil pro
    Développeur Web
    Inscrit en
    Décembre 2011
    Messages
    22
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : Santé

    Informations forums :
    Inscription : Décembre 2011
    Messages : 22
    Points : 17
    Points
    17
    Par défaut
    Pour commencer, merci de ton aide et tes explications sur le problème rencontré

    Pour le moment, je vais mettre de coté ce problème, le temps d'en discuter avec mon chef. On va voir si on peut pas faire pression sur notre hébergeur pour qu'on puisse utiliser les websockets
    Sinon ben on essaiera de partir sur ce que tu nous a dis, et si on a des problèmes, je reviendrai demander conseil.

    En tout cas, encore merci.

    Bonne fin de journée,

    Sébastien BORDAT

Discussions similaires

  1. Récupération des données d'un formulaire
    Par placenargac dans le forum Balisage (X)HTML et validation W3C
    Réponses: 2
    Dernier message: 04/02/2006, 15h10
  2. récupération des données via une liste déroulante
    Par rahan_dave dans le forum Access
    Réponses: 1
    Dernier message: 13/10/2005, 12h27
  3. [HTML][FORMULAIRE] Probleme dans la récupération des données
    Par baddounet dans le forum Balisage (X)HTML et validation W3C
    Réponses: 6
    Dernier message: 15/08/2005, 18h51
  4. Réponses: 2
    Dernier message: 20/02/2004, 08h47
  5. Réponses: 13
    Dernier message: 20/03/2003, 08h11

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