Archives de l’auteur : Yannick Pereira-Reis

À propos Yannick Pereira-Reis

Yannick (@yannickpr69) est Ingénieur études et développement, établit dans la région Lyonnaise. Il publie régulièrement des entrées sur ce blog pour partager ses connaissances en tant qu'expert ElasticSearch, Symfony et AngularJS.

ElasticSearch

ElasticSearch – Réindexation sans interruption de service grâce aux alias (zero downtime)

Lorsqu’un cluster ElasticSearch contient plusieurs nœuds, indexes, shards, replicas et surtout des millions, voire des milliards, de documents, il peut parfois être très long de réindexer tout le contenu.

En effet, il peut parfois arriver que l’on ait besoin de changer d’infrastructure technique (machine, virtualisation,…) ou que l’on veuille changer l’architecture du cluster ElasticSearch. Dans ce cas, comment maintenir notre service de recherche opérationnel 24h/24 et 7j/7 (SLA) tout en réalisant les opérations de maintenance désirées (zero downtime) ?

L’utilisation des alias ElasticSearch

Une fois encore les alias vont nous permettre de résoudre notre problématique. Un alias ElasticSearch peut être configuré de manière à pointer vers un ou plusieurs indexes d’un cluster tout en spécifiant des filtres ou des clés de routage.

Le point le plus important réside dans le fait que l’on peut changer la configuration d’un alias en une seule requête vers notre service d’indexation, et ainsi faire pointer un alias vers un ou plusieurs nouveaux indexes que l’on vient de reconstruire ou dont l’architecture vient de changer.

Ainsi, en utilisant systématiquement (bonne pratique) des alias (nom logique) à la place des noms d’indexes (nom physique) dans le code de nos applications clientes des services d’indexation, on pourra très facilement changer le ou les indexes cibles de nos recherches.

Exemple :

L’alias « produits » pointe vers les indexes « produits catégorie A » et « produits catégorie B » depuis que l’instance ElasticSearch a été démarrée. En une seule requête vers le cluster, on peut instantanément faire pointer notre alias « produits » vers les indexes « nouveaux produits catégorie A », « nouveaux produits catégorie B1 » et « nouveaux produits catégorie B2 » et sans interruption de services. On vient de changer par la même occasion notre découpage des indexes.

curl -XPOST localhost:9200/_aliases -d '
{
    "actions": [
        { "add": {
            "alias": "produits",
            "index": "produits_catégorie_A"
        }},
        { "add": {
            "alias": "produits",
            "index": "produits_catégorie_B"
        }}
    ]
}
'

puis

curl -XPOST localhost:9200/_aliases -d '
{
    "actions": [
        { "remove": {
            "alias": "produits",
            "index": "produits_catégorie_A"
        }},
        { "remove": {
            "alias": "produits",
            "index": "produits_catégorie_B"
        }},
        { "add": {
            "alias": "produits",
            "index": "nouveaux_produits_catégorie_A"
        }},
        { "add": {
            "alias": "produits",
            "index": "nouveaux_produits_catégorie_B1"
        }},
        { "add": {
            "alias": "produits",
            "index": "nouveaux_produits_catégorie_B2"
        }}
    ]
}
'

L’alias est la solution à privilégier pour garantir la haute disponibilité d’une application. Les alias permettent également d’organiser logiquement les indexes.

Voici un article sur le blog d’elasticsearch expliquant plus en détail ces problématiques et solutions : http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/

ElasticSearch

ElasticSearch – The Split Brain problem

Lorsqu’on met en place un cluster ElasticSearch, un problème très embêtant peut survenir si on ne configure par correctement le nombre de nœuds « master » dans ce dernier.

Split Brain

Le problème nommé « split brain » se produira plus aisément sur de petits clusters pour lesquels le nombre de nœuds « master » n’a pas correctement été calculé/configuré. Pour notre exemple, prenons un cluster avec deux nœuds répartis sur des sites géographiques différents et contenant chacun un index ElasticSearch constitué d’un shard et d’un replica.

split brainOn peut rencontrer le problème du « split brain » par exemple si une coupure réseau intervient pendant un temps suffisamment long pour que chacun de nos nœuds reçoive une exception ConnectTransportException.

  1. Pour fonctionner correctement, un cluster Elasticsearch a besoin d’un nœud maître qui détermine où et comment sont répartis les différents shards et réplicas. Le nœud 1 joue ce rôle. Il héberge la partition primaire (shard 0). Le nœud 2 qui est esclave, contient le réplica de la partition du nœud 1. Le réplica est une copie de la partition primaire et peut jouer le rôle de backup en cas de problème sur le nœud 1.
  2. Une coupure réseau survient entre les 2 nœuds.  Lorsqu’il tente de communiquer avec son nœud maître, le nœud 2 reçoit une ConnectTransportException. A plusieurs reprises, toutes les n secondes, le nœud 2 essaie de se reconnecter au cluster.  Mais en vain… Possédant un réplica complet de l’index, sans nœud voisin, le nœud 2 va alors s’autoproclamer maître. Deux « sous-clusters » existent alors sur le réseau, chacun étant capable de répondre à des requêtes de recherche et d’indexation de manière totalement indépendante.
  3. Une fois le réseau correctement rétabli, les 2 nœuds ne se réunissent pas en un seul et même cluster comme on pourrait le prévoir. Deux clusters indépendants coexistent parfaitement. C’est ce que l’on appelle le Split Brain. Cette situation entraîne une divergence progressive des données présentes sur chacun des sous-clusters, parfois les requêtes d’indexation arrivant sur le nœud 1, parfois sur le nœud 2.

La solution à ce problème est parfaitement connue. Il faut fixer le paramètre discovery.zen.minimum_master_node à N/2 + 1 (avec N le nombre de nœuds), soit 2 dans notre cas. Ce paramètre permet de spécifier que l’élection d’un nouveau nœud maître nécessite la majorité absolue. Coupé en deux, un cluster de 2 nœuds comme le notre n’aurait pas pu élire de nouveau maître.  Le Nœud 1 se serait mis en attente du rétablissement du réseau. Le cluster aurait perdu sa haute disponibilité : aucun des nœuds n’aurait pu répondre aux requêtes de recherche.

Avec 3 nœuds, l’isolement d’un nœud par rapport aux deux autres aurait permis de conserver un cluster parfaitement fonctionnel grâce à une bonne répartition des shards et réplicas.

Il faut également noter que le troisième nœud peut être configuré pour ne pas stocker de données (paramètre node.data à false). Il jouera alors simplement un rôle d’arbitre.

En paramétrant un nœud avec les configurations suivantes, on peut lui faire jouer un rôle de Load Balancer pour nos requêtes :

node.data : false
node.master : false

Une configuration intéressante pourrait être la suivante :

  1. Un nœud non master et sans data permettant de router les requêtes de recherche vers tous les nœuds contenant des données.
  2. Un nœud non master et sans data permettant d’indexer des données vers les nœuds contenant des données.
  3. Une configuration correcte du nombre minimale de nœud master.

Le but est ici de ne pas indexer et rechercher des données en passant par le même nœud. On augmente ainsi sensiblement les performances en répartissant ces actions sur différents nœuds. On profite également de la fonctionnalité de réplication de données pour les obtenir quelques secondes plus tard dans nos requêtes de recherches.

En utilisant une solution de type RabbitMQ on augmentera encore les performances applicatives en indexant les données de manière asynchrone.

Pour finir…

Pensez à l’utilisation du cluster Elasticsearch avec vagrant disponible sur github, pour vos tests ou vos POC : https://github.com/ypereirareis/vagrant-elasticsearch-cluster

Sources

Symfony – Behavioral design pattern

Lorsque l’on développe une application avec Symfony, on implémente divers design pattern sans forcément trop sans rendre compte. En effet, le framework intègre directement un nombre important de bonnes pratiques.

Behavioral Design Pattern

Il existe un design pattern très intéressant, BEHAVIORAL, qui permet de définir des comportements sur des objets métiers par exemple. On peut ainsi définir un objet comme étant loggable, blameable ou bien encore timestampable.

Les traits ou l’héritage multiple en PHP

En PHP, ajouter un comportement à un objet n’est pas forcément chose aisée car l’héritage multiple n’existe pas. Heureusement, à partir de PHP 5.4, les « traits » permettent de simuler une forme d’héritage multiple en ajoutant à une classe des propriétés et des méthodes définies dans un « trait ».

<?php

namespace Knp\DoctrineBehaviors\Model\Timestampable;

trait Timestampable
{
    /**
     * @var \DateTime $createdAt
     *
     * @ORM\Column(type="datetime", nullable=true)
     */
    protected $createdAt;

    /**
     * @var \DateTime $updatedAt
     *
     * @ORM\Column(type="datetime", nullable=true)
     */
    protected $updatedAt;

    /**
     * Returns createdAt value.
     *
     * @return \DateTime
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Returns updatedAt value.
     *
     * @return \DateTime
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

    /**
     * @param \DateTime $createdAt
     * @return $this
     */
    public function setCreatedAt(\DateTime $createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * @param \DateTime $updatedAt
     * @return $this
     */
    public function setUpdatedAt(\DateTime $updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

    /**
     * Updates createdAt and updatedAt timestamps.
     */
    public function updateTimestamps()
    {
        if (null === $this->createdAt) {
            $this->createdAt = new \DateTime('now');
        }

        $this->updatedAt = new \DateTime('now');
    }
}

L’objet de l’article n’étant pas de détailler le fonctionnement des « traits » en PHP, je vous laisse parcourir la documentation à ce sujet. http://www.php.net/manual/fr/language.oop5.traits.php

Doctrine Behaviors

Avec Symfony et l’aide d’un bundle bien pratique, DoctrineBehaviors de KnpLabs, il devient très aisé d’implémenter ce design pattern en un temps record. De plus, si vous avez l’habitude d’utiliser les annotations pour votre mapping ORM/ODM, vous n’aurez rien d’autre à faire que d’utiliser les « traits » PHP offerts par le bundle pour ajouter des comportements à vos objets métiers. Le gestion (CRUD) de ces comportements en base de données se fera alors de manière automatique via les listeners Symfony/Doctrine.

<?php

use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;

/**
 * @ORM\Entity(repositoryClass="CategoryRepository")
 */
class Category implements ORMBehaviors\Tree\NodeInterface, \ArrayAccess
{
    use ORMBehaviors\Tree\Node,
        ORMBehaviors\Translatable\Translatable,
        ORMBehaviors\Timestampable\Timestampable,
        ORMBehaviors\SoftDeletable\SoftDeletable,
        ORMBehaviors\Blameable\Blameable,
        ORMBehaviors\Geocodable\Geocodable,
        ORMBehaviors\Loggable\Loggable,
        ORMBehaviors\Sluggable\Sluggable
    ;

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="NONE")
     */
    protected $id;
}

Visitez vite le github du bundle : https://github.com/KnpLabs/DoctrineBehaviors

Symfony – Best Bundles

En tant que développeur Symfony, j’essaye lorsque c’est possible, de ne jamais réinventer la roue. Cela tombe bien car Symfony et sa vaste communauté, offrent de nombreux Bundles très bien faits répondant aux besoins les plus courants notamment en terme d’implémentation de bonnes pratiques.

Devenez un expert en utilisant les « Best Bundles » Symfony

The FOSUserBundle adds support for a database-backed user system in Symfony2. It provides a flexible framework for user management that aims to handle common tasks such as user registration and password retrieval.

This bundle provides various tools to rapidly develop RESTful API’s & applications with Symfony2.

Elastica and ElasticSearch integration in Symfony2

This Bundle provides a Symfony2 authentication provider so that users can login to a Symfony2 application via Facebook. Furthermore via custom user provider support the Facebook login can also be integrated with other data sources like the database based solution provided by FOSUserBundle.

JMSDiExtraBundle adds more powerful dependency injection features to Symfony2

This bundle allows you to create i18n routes.

This bundle adds AOP capabilities to Symfony2.

If you haven’t heard of AOP yet, it basically allows you to separate a cross-cutting concern (for example, security checks) into a dedicated class, and not having to repeat that code in all places where it is needed.

In other words, this allows you to execute custom code before, and after the invocation of certain methods in your service layer, or your controllers. You can also choose to skip the invocation of the original method, or throw exceptions.

XHProf is a hierarchical profiler for PHP. It reports function-level call counts and inclusive and exclusive metrics such as wall (elapsed) time, CPU time and memory usage. A function’s profile can be broken down by callers or callees. The raw data collection component is implemented in C as a PHP Zend extension called xhprof. XHProf has a simple HTML based user interface (written in PHP). The browser based UI for viewing profiler results makes it easy to view results or to share results with peers. A callgraph image view is also supported.

This bundle is a fork of AvalancheImagineBundle which provides easy image manipulation support for Symfony2. The goal of the fork is to make the code more extensible and as a result applicable for more use cases.

This php 5.4+ library is a collection of traits that add behaviors to Doctrine2 entites and repositories.

This bundle integrates the Doctrine2 Migrations library. into Symfony so that you can safely and quickly manage database migrations.

Provide markdown conversion (based on Michel Fortin work) to your Symfony2 projects.

Alice allows you to create a ton of fixtures/fake data for use while developing or testing your project. It gives you a few essential tools to make it very easy to generate complex data with constraints in a readable and easy to edit way, so that everyone on your team can tweak the fixtures if needed.

It adds in your WebProfiler extra sections (routing, container, twig,…)

This bundle allows you to expose your routing in your JavaScript code. That means you’ll be able to generate URL with given parameters like you can do with the Router component provided in the Symfony2 core.

Pour finir…

…la liste n’est bien sûr pas exhaustive, retenez que vous trouverez souvent des bundles existant répondant à vos besoins. Attention cependant, car certains sont de meilleure qualité que d’autres, certains sont bien maintenus d’autres pas, enfin plus grave, certains ne fonctionnent pas ou contiennent de nombreux bugs. Alors attention !

Quelques liens utiles