Fermer

juin 19, 2018

Création d'un blog de galerie d'images avec Symfony Flex: test de données –


Cet article fait partie d'une série de sur la construction d'un exemple d'application – un blog de galerie multi-images – pour l'analyse comparative des performances et les optimisations. (Voir le repo ici.)


Dans l'article précédent nous avons montré comment créer un projet Symfony à partir de zéro avec Flex, et comment créer un ensemble simple d'appareils

L'étape suivante de notre parcours consiste à remplir la base de données avec une quantité de données assez réaliste pour tester les performances de l'application.

Note: si vous avez fait le " Premiers pas avec l'application "étape dans le post précédent, vous avez déjà suivi les étapes décrites dans ce post. Si c'est le cas, utilisez ce post comme un explicateur sur la façon dont cela a été fait.

En bonus, nous montrerons comment configurer une simple suite de tests PHPUnit avec des tests de fumée basiques . ] Plus de fausses données

Une fois que vos entités sont polies, et vous avez eu votre "C'est tout! J'ai terminé! ", C'est le moment idéal pour créer un ensemble de données plus important qui peut être utilisé pour tester et préparer l'application pour la production.

Des appareils simples comme ceux que nous avons créés dans l'article précédent sont parfaits pour Le test des performances de l'application, la simulation du trafic réel et la détection des goulets d'étranglement nécessitent des ensembles de données plus volumineux (c'est-à-dire une plus grande quantité d'entrées de la base de données). et des fichiers image pour ce projet). La génération de milliers d'entrées prend du temps (et des ressources informatiques), donc nous voulons le faire une seule fois.

Nous pourrions essayer d'augmenter la constante COUNT dans nos classes d'appareils et voir ce qui va se passer:

 ] // src / DataFixtures / ORM / LoadUsersData.php
class LoadUsersData extends AbstractFixture implémente ContainerAwareInterface, OrderedFixtureInterface
{
    const COUNT = 500;
    ...
}

// src / DataFixtures / ORM / LoadGalleriesData.php
class LoadGalleriesData extends AbstractFixture implémente ContainerAwareInterface, OrderedFixtureInterface
{
    const COUNT = 1000;
    ...
}

Maintenant, si nous exécutons bin / refreshDb.sh après un certain temps nous aurons probablement un message pas si gentil comme PHP Erreur fatale: La taille de la mémoire autorisée de N octets est épuisée .

Mis à part une exécution lente, chaque erreur entraînerait une base de données vide car EntityManager est vidé uniquement à la toute fin de la classe fixture. De plus, Faker télécharge une image aléatoire pour chaque entrée de galerie. Pour 1 000 galeries avec 5 à 10 images par galerie, ce serait 5 000 à 10 000 téléchargements, ce qui est très lent.

Il existe d'excellentes ressources pour optimiser Doctrine et Symfony pour le traitement par lots , et nous allons utiliser certaines de ces astuces pour optimiser le chargement des appareils.

Tout d'abord, nous allons définir une taille de lot de 100 galeries. Après chaque lot, nous allons vider et effacer le EntityManager (c'est-à-dire, détacher les entités persistantes) et dire au garbage collector de faire son travail.

Pour suivre la progression, imprimons quelques méta-informations (identifiant de lot et utilisation de mémoire).

Note: Après avoir appelé $ manager-> clear () tout a persisté les entités sont maintenant non gérées. Le gestionnaire d'entités ne les connaît plus et vous obtiendrez probablement une erreur "entité-non-persistée".

La clé est de fusionner l'entité avec le gestionnaire $ entity = $ manager- > fusionner ($ entity);

Sans l'optimisation, l'utilisation de la mémoire augmente pendant l'exécution d'une classe de fixture LoadGalleriesData :

> loading [200] App  DataFixtures  ORM  LoadGalleriesData
100 Utilisation de la mémoire (actuellement) 24 Mo / (max) 24 Mo
200 Utilisation de la mémoire (actuellement) 26 Mo / (max) 26 Mo
300 Utilisation de la mémoire (actuellement) 28 Mo / (max) 28 Mo
400 Utilisation de la mémoire (actuellement) 30 Mo / (max) 30 Mo
500 Utilisation de la mémoire (actuellement) 32 Mo / (max) 32 Mo
600 Utilisation de la mémoire (actuellement) 34 Mo / (max) 34 Mo
700 Utilisation de la mémoire (actuellement) 36 Mo / (max) 36 Mo
800 Utilisation de la mémoire (actuellement) 38 Mo / (max) 38 Mo
900 Utilisation de la mémoire (actuellement) 40 Mo / (max) 40 Mo
1000 Utilisation de la mémoire (actuellement) 42 Mo / (max) 42 Mo

L'utilisation de la mémoire commence à 24 Mo et augmente de 2 Mo pour chaque lot (100 galeries). Si nous essayions de charger 100 000 galeries, nous aurions besoin de 24 Mo + 999 (999 lots de 100 galeries, 99,900 galeries) * 2 Mo = ~ 2 Go de mémoire .

Après avoir ajouté $ manager-> flush () et gc_collect_cycles () pour chaque lot, en supprimant la journalisation SQL avec $ manager-> getConnection () -> getConfiguration () -> setSQLLogger (null) et en supprimant les références d'entités en commentant $ this-> addReference ('gallery'. $ I, $ gallery); l'utilisation de la mémoire devient quelque peu constante pour chaque lot.

 // Définir la taille du lot en dehors de la boucle for
$ batchSize = 100;

...

pour ($ i = 1; $ i <= self::COUNT; $i++) {
    ...

    // Save the batch at the end of the for loop
    if (($i % $batchSize) == 0 || $i == self::COUNT) {
        $currentMemoryUsage = round(memory_get_usage(true) / 1024);
        $maxMemoryUsage = round(memory_get_peak_usage(true) / 1024);
        echo sprintf("%s Memory usage (currently) %dKB/ (max) %dKB n", $i, $currentMemoryUsage, $maxMemoryUsage);

        $manager-> flush ();
        $ manager-> clear ();

        // Ici vous devriez fusionner les entités que vous réutilisez avec le gestionnaire $
        // car ils ne sont plus gérés après l'appel de $ manager-> clear ();
        // par exemple. si vous avez déjà chargé des entités de catégorie ou d'étiquette
        // $ category = $ manager-> fusionner ($ category);

        gc_collect_cycles ();
    }
}

Comme prévu, l'utilisation de la mémoire est maintenant stable:

> loading [200] App  DataFixtures  ORM  LoadGalleriesData
100 Utilisation de la mémoire (actuellement) 24 Mo / (max) 24 Mo
200 Utilisation de la mémoire (actuellement) 26 Mo / (max.) 28 Mo
300 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
400 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
500 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
600 Utilisation de la mémoire (actuellement) 26 Mo / (max.) 28 Mo
700 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
800 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
900 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
1000 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo

Au lieu de télécharger des images aléatoires à chaque fois, nous pouvons préparer 15 images aléatoires et mettre à jour le script fixture pour en choisir aléatoirement au lieu d'utiliser la méthode $ faker-> image () de Faker. Prenons 15 images de Unsplash et les sauvegardons dans var / demo-data / sample-images .

Ensuite, mettez à jour la méthode LoadGalleriesData :: generateRandomImage :

 fonction privée generateRandomImage ($ imageName)
    {
        $ images = [
            'image1.jpeg',
            'image10.jpeg',
            'image11.jpeg',
            'image12.jpg',
            'image13.jpeg',
            'image14.jpeg',
            'image15.jpeg',
            'image2.jpeg',
            'image3.jpeg',
            'image4.jpeg',
            'image5.jpeg',
            'image6.jpeg',
            'image7.jpeg',
            'image8.jpeg',
            'image9.jpeg',
        ];

        $ sourceDirectory = $ this-> container-> getParameter ('kernel.project_dir'). '/ var / demo-data / sample-images /';
        $ targetDirectory = $ this-> container-> getParameter ('kernel.project_dir'). '/ var / uploads /';

        $ randomImage = $ images [rand(0, count($images) - 1)];
        $ randomImageSourceFilePath = $ sourceDirectory. $ randomImage;
        $ randomImageExtension = explode ('.', $ randomImage) [1];
        $ targetImageFilename = sha1 (microtime (). rand ()). '.' . $ randomImageExtension;
        copy ($ randomImageSourceFilePath, $ targetDirectory. $ targetImageFilename);

        $ image = nouvelle image (
            Uuid :: getFactory () -> uuid4 (),
            $ randomImage,
            $ targetImageFilename
        )

        return $ image;
    }

C'est une bonne idée de supprimer les anciens fichiers dans var / uploads lors du rechargement des appareils, donc j'ajoute la commande rm var / uploads / * à bin / refreshDb .sh script, immédiatement après avoir supprimé le schéma DB

Le chargement de 500 utilisateurs et de 1000 galeries prend maintenant ~ 7 minutes et ~ 28 Mo de mémoire (utilisation de pointe).

 Suppression du schéma de base de ...
Le schéma de base de données a été supprimé avec succès!
ATTENTION: Cette opération ne doit pas être exécutée dans un environnement de production.

Création d'un schéma de base de données ...
Le schéma de base de données a été créé avec succès!
  > base de données de purge
  > loading [100] App  DataFixtures  ORM  LoadUsersData
300 Utilisation de la mémoire (actuellement) 10 Mo / (max) 10 Mo
500 Utilisation de la mémoire (actuellement) 12 Mo / (max) 12 Mo
  > loading [200] App  DataFixtures  ORM  LoadGalleriesData
100 Utilisation de la mémoire (actuellement) 24 Mo / (max) 26 Mo
200 Utilisation de la mémoire (actuellement) 26 Mo / (max.) 28 Mo
300 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
400 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
500 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
600 Utilisation de la mémoire (actuellement) 26 Mo / (max.) 28 Mo
700 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
800 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
900 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo
1000 Utilisation de la mémoire (actuellement) 26 Mo / (max) 28 Mo

Jetez un oeil à la source des classes d'appareils: LoadUsersData.php et LoadGalleriesData.php .

Performance

À ce point le Le rendu de la page d'accueil est très lent – bien trop lent pour la production.

Un utilisateur peut sentir que l'application a du mal à livrer la page, probablement parce que l'application affiche toutes les galeries au lieu d'un nombre limité. toutes les galeries à la fois, nous pourrions mettre à jour l'application pour ne rendre que les 12 premières galeries immédiatement et introduire la charge paresseuse. Lorsque l'utilisateur fait défiler jusqu'à la fin de l'écran, l'application va chercher les 12 prochaines galeries et les présenter à l'utilisateur.

Tests de performance

Pour suivre l'optimisation des performances, nous devons établir un ensemble fixe de tests utilisé pour tester et comparer les améliorations de performance relativement.

Nous utiliserons Siege pour tester la charge. Ici vous pouvez trouver plus à propos de Siège et tests de performance . Au lieu d'installer Siege sur ma machine, nous pouvons utiliser Docker – une plateforme de conteneur puissante

En termes simples, les conteneurs Docker sont similaires aux machines virtuelles ( mais ce ne sont pas la même chose ). À l'exception de la création et du déploiement d'applications, Docker peut être utilisé pour tester des applications sans les installer réellement sur votre machine locale. Vous pouvez construire vos images ou utiliser des images disponibles sur Docker Hub un registre public d'images Docker.

Il est particulièrement utile lorsque vous voulez expérimenter avec différentes versions du même logiciel (par exemple, des versions différentes

Nous utiliserons l'image yokogawa / siege pour tester l'application.

Test de la page d'accueil

Le test de la page d'accueil n'est pas trivial, car il y a des requêtes Ajax exécuté uniquement lorsque l'utilisateur fait défiler jusqu'à la fin de la page.

Nous pourrions nous attendre à ce que tous les utilisateurs atterrissent sur la page d'accueil (c.-à-d., 100%). Nous pourrions également estimer que 50% d'entre eux iraient jusqu'à la fin et demanderaient donc la deuxième page de galeries. Nous pourrions également supposer que 30% d'entre eux chargeraient la troisième page, 15% demanderaient la quatrième page, et 5% demanderaient la cinquième page.

Ces chiffres sont basés sur des prédictions, et ce serait beaucoup mieux si nous pourrait utiliser un outil d'analyse pour obtenir un aperçu réel du comportement des utilisateurs. Mais c'est impossible pour une toute nouvelle application. Néanmoins, c'est une bonne idée de jeter un coup d'œil aux données analytiques et d'ajuster votre suite de tests après le déploiement initial.

Nous testerons la page d'accueil (et les URL de chargement paresseux) en parallèle. Le premier testera l'URL de la page d'accueil uniquement, tandis qu'un autre testera les URL des points de chargement paresseux.

Fichier lazy-load-urls.txt contient une liste aléatoire de pages chargées paresseusement URL dans les prévisions ratios:

  • 10 URL pour la deuxième page (50%)
  • 6 URL pour la troisième page (30%)
  • 3 URL pour la quatrième page (15%)
  • 1 URL pour la cinquième page (5% )
 http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=4
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=3
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=4
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=4
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=3
http://blog.app/galleries-lazy-load?page=3
http://blog.app/galleries-lazy-load?page=3
http://blog.app/galleries-lazy-load?page=5
http://blog.app/galleries-lazy-load?page=3
http://blog.app/galleries-lazy-load?page=2
http://blog.app/galleries-lazy-load?page=3

Le script de test des performances de la page d'accueil exécutera 2 processus de Siege en parallèle, un par page d'accueil et un autre par une liste générée d'URL

Pour exécuter une seule requête HTTP avec Siege (dans Docker), exécutez: [19659057] docker run –rm -t yokogawa / siege -c1 -r1 blog.app

Note: Si vous n'utilisez pas Docker, vous pouvez omettre la partie docker run --rm -t yokogawa / siege et lancer Siege avec les mêmes arguments. [19659008Pourexécuteruntestd'uneminuteavec50utilisateurssimultanéssurlapaged'accueilavecundélaid'unesecondeexécutez:

 docker run --rm -t yokogawa / siege -d1 -c50 -t1M http: // blog. application

Pour exécuter un test d'une minute avec 50 utilisateurs simultanés sur des URL dans lazy-load-urls.txt exécutez:

 docker run --rm -v `pwd`: / var / siège: ro -t yokogawa / siège -i --fichier = / var / siège / lazy-load-urls.txt -d1 -c50 -t1M

Faites-le dans le répertoire où se trouve votre lazy-load-urls.txt (ce répertoire sera monté en tant que volume en lecture seule dans Docker).

Exécution d'un script test-homepage.sh lancera 2 processus de siège (d'une manière suggérée par Stack Overflow answer ) et les résultats de sortie.

Supposons que nous ayons déployé l'application sur un serveur avec Nginx et avec PHP-FPM 7.1 et chargé 25 000 utilisateurs et 30 000 galeries. Les résultats du test de charge de la page d'accueil de l'application sont les suivants:

 ./ test-homepage.sh

Transactions: 499 hits
Disponibilité: 100,00%
Temps écoulé: 59.10 secondes
Données transférées: 1.49 Mo
Temps de réponse: 4.75 secondes
Taux de transaction: 8.44 trans / sec
Débit: 0.03 Mo / s
La concurrence: 40.09
Transactions réussies: 499
Transactions ayant échoué: 0
La transaction la plus longue: 16,47
Transaction la plus courte: 0,17

Transactions: 482 résultats
Disponibilité: 100,00%
Temps écoulé: 59,08 secondes
Données transférées: 6,01 Mo
Temps de réponse: 4.72 secondes
Taux de transaction: 8,16 trans / sec
Débit: 0.10 Mo / sec
La concurrence: 38,49
Transactions réussies: 482
Transactions ayant échoué: 0
La plus longue transaction: 15,36
Transaction la plus courte: 0,15

Même si la disponibilité des applications est de 100% pour les tests de page d'accueil et de chargement paresseux, le temps de réponse est d'environ 5 secondes, ce qui n'est pas le cas d'une application performante.

Le test d'une seule page de galerie est un peu plus simple: nous allons exécuter Siege sur le fichier galleries.txt où nous avons une liste d'URL de pages de galerie à tester.

le répertoire où se trouve le fichier galleries.txt (ce répertoire sera monté en tant que volume en lecture seule dans Docker), exécutez cette commande:

 docker run --rm -v `pwd`: / var / siege: ro -t yokogawa / siege -i --fichier = / var / siege / galleries.txt -d1 -c50 -t1M

Les résultats du test de chargement pour les pages d'une seule galerie sont légèrement meilleurs que pour la page d'accueil:

 ./ test-single-gallery.sh
** SIEGE 3.0.5
** Préparation de 50 utilisateurs simultanés pour la bataille.
Le serveur est maintenant en état de siège ...
Lever le siège du serveur ... terminé.

Transactions: 3589 résultats
Disponibilité: 100,00%
Temps écoulé: 59,64 secondes
Données transférées: 11.15 Mo
Temps de réponse: 0.33 secondes
Taux de transaction: 60.18 trans / sec
Débit: 0.19 Mo / sec
La concurrence: 19,62
Transactions réussies: 3589
Transactions ayant échoué: 0
Transaction la plus longue: 1,25
Transaction la plus courte: 0,10

Tests, tests, tests

Pour nous assurer de ne rien casser avec les améliorations que nous implémenterons dans le futur, nous avons besoin d'au moins quelques tests.

D'abord, nous avons besoin de PHPUnit comme dépendance dev: [19659057] compositeur req –dev phpunit

Ensuite, nous allons créer une configuration PHPUnit simple en copiant phpunit.xml.dist créé par Flex sur phpunit.xml et en mettant à jour les variables d'environnement (par exemple, DATABASE_URL variable pour l'environnement de test). Aussi, j'ajoute phpunit.xml à .gitignore .

Ensuite, nous créons des tests fonctionnels / de fumée de base pour la page d'accueil du blog et le single pages de la galerie. Le test de fumée est un "test préliminaire pour révéler des échecs simples assez sévères pour rejeter une version logicielle prospective" . Comme il est assez facile d'implémenter des tests de fumée, il n'y a aucune raison valable de les éviter!

Ces tests ne font que confirmer que les URL fournies dans la méthode urlProvider () aboutissent à une réponse HTTP réussie code (c'est-à-dire, le code d'état HTTP est 2xx ou 3xx).

Simple test de fumée la page d'accueil et cinq pages de galerie unique pourrait ressembler à ceci :

 namespace App  Tests;

utilisez App  Entity  Gallery;
utilisez Psr  Container  ContainerInterface;
utilisez Symfony  Bundle  FrameworkBundle  Test  WebTestCase;
utilisez Symfony  Component  Routing  RouterInterface;

classe SmokeTest étend WebTestCase
{
    / ** @var ContainerInterface * /
    conteneur privé $;

    / **
     * @dataProvider urlProvider
     * /
    fonction publique testPageIsSuccessful ($ url)
    {
        $ client = self :: createClient ();
        $ client-> request ('GET', $ url);

        $ this-> assertTrue ($ client-> getResponse () -> isSuccessful ());
    }

    fonction publique urlProvider ()
    {
        $ client = self :: createClient ();
        $ this-> container = $ client-> getContainer ();

        $ urls = [
            ['/'],
        ]

        $ urls + = $ this-> getGalleriesUrls ();

        return $ urls;
    }

    fonction privée getGalleriesUrls ()
    {
        $ router = $ this-> container-> get ('routeur');
        $ doctrine = $ this-> container-> get ('doctrine');
        $ galleries = $ doctrine-> getRepository (Galerie :: classe) -> findBy ([]null, 5);

        $ urls = [];

        / ** galerie @var Galerie $ * /
        foreach ($ galeries comme galerie $) {
            $ urls [] = [
                '/' . $router->generate('gallery.single-gallery', ['id' => $gallery->getId()],
                    RouterInterface :: RELATIVE_PATH),
            ]
        }

        return $ urls;
    }

}

Exécutez ./ vendor / bin / phpunit et vérifiez si les tests sont réussis:

 ./ vendor / bin / phpunit
PHPUnit 6.5-dev par Sebastian Bergmann et contributeurs.

...

5/5 (100%)

Temps: 4.06 secondes, mémoire: 16.00 Mo

OK (5 tests, 5 assertions)

Notez qu'il est préférable de coder en dur les URL importantes (par exemple, pour les pages statiques ou certaines URL connues) plutôt que de les générer dans le test. En savoir plus sur PHPUnit et TDD ici

Restez à l'écoute

Les prochains articles de cette série couvriront les détails de l'optimisation des performances PHP et MySQL, amélioreront la perception globale des performances et d'autres astuces.




Source link