PHP et les résolveurs DNS
Aujourd’hui, la plupart des applications Web communiquent avec des API externes. La résolution des noms des domaines associés à ces API externes doit être performante. Une mauvaise configuration du « resolver DNS » du système (linux) hébergeant votre applicatif pourra entraîner des lenteurs de votre applicatif. Selon l’applicatif, cela sera plus ou moins critique.
Section intitulée contexteContexte
Sur l’applicatif d’un client que nos équipes maintiennent, nous avions parfois des temps de réponse de plus de 5 secondes comme les traces newrelic nous l’indiquent :
Sous le capot, cet applicatif envoie des logs en utilisant le handler gelf de monolog (comme expliqué dans l’article Introduction au monitoring d’une application Symfony2). Les logs sont envoyés en UDP. La méthode d’initialisation de la socket permettant l’envoi des informations ressemble à ça :
private function buildSocket()
{
$socketDescriptor = sprintf(
"%s://%s:%d",
$this->scheme,
$this->host,
$this->port
);
$socket = @stream_socket_client(
$socketDescriptor,
$errNo,
$errStr,
$this->connectTimeout,
\STREAM_CLIENT_CONNECT,
stream_context_create($this->context)
);
if ($socket === false) {
throw new RuntimeException(
sprintf(
"Failed to create socket-client for %s: %s (%s)",
$socketDescriptor,
$errStr,
$errNo
)
);
}
// set non-blocking for UDP
if (strcasecmp("udp", $this->scheme) == 0) {
stream_set_blocking($socket, 0);
}
return $socket;
}
Source : https://github.com/bzikarsky/gelf-php/blob/1.4.2/src/Gelf/Transport/StreamSocketClient.php#L137
La variable $host
contient un nom de domaine (logstash-gelf.client.com) et non pas une IP. Nous utilisons un nom de domaine plutôt qu’une IP pour toutes les bonnes raisons que vous connaissez (simplifie la gestion et l’identification du service, changement d’IP facilité, …).
Notre applicatif met donc jusqu’à 5 secondes pour initialiser la socket. Comment est-ce possible ? Nous avons remplacé le nom de domaine logstash-gelf.client.com par son IP et le problème a disparu.
Section intitulée fonctionnement-generalFonctionnement général
Sur un système d’exploitation (nous prenons ici pour exemple Linux), lorsqu’un applicatif doit communiquer avec un service tiers et que le service tiers est référencé avec son nom de domaine (exemple : logstash-gelf.client.com), une résolution DNS doit être effectuée. Certains applicatifs vont mettre en cache cette résolution DNS au démarrage de l’applicatif. C’est le cas de Varnish ou NGINX. Il est à noter que curl (donc l’extension curl de PHP) met par défaut en cache les résolutions DNS :
Pass a long, this sets the timeout in seconds. Name resolves will be kept in memory and used for this number of seconds. Set to zero to completely disable caching, or set to –1 to make the cached entries remain forever. By default, libcurl caches this info for 60 seconds. source : https://curl.haxx.se/libcurl/c/CURLOPT_DNS_CACHE_TIMEOUT.html
Lorsque vous utilisez l’extension curl avec PHP, la mise en cache ne sera possible que dans le cas de l’exécution de PHP via php-fpm ou mod_php (plus d’explications).
D’autres applicatifs (par exemple, PHP (n’utilisant pas l’extension curl) ou nodejs) ne vont pas mettre en cache cette résolution DNS et chaque fois qu’ils devront accéder au service, la résolution DNS sera effectuée. Dans la plupart des cas, ce sera extrêmement rapide.
Sous linux, c’est le fichier /etc/resolv.conf
qui contient la configuration du « resolver » DNS.
Ce fichier contient en général le nom des serveurs DNS qui seront utilisés :
search client.com
nameserver 212.X.X.X
nameserver 212.Y.Y.y
Une résolution DNS peut prendre jusqu’à 5 secondes car le timeout par défaut de la configuration du resolver DNS (/etc/resolv.conf
) est de 5 secondes (cf. http://man7.org/linux/man-pages/man5/resolv.conf.5.html).
Voilà, l’origine des 5 secondes que nous avions.
Par défaut, la plupart des systèmes Linux ne mettront pas en cache la résolution DNS (ce n’est pas le cas de Windows qui dispose par défaut d’un cache DNS). Cela signifie qu’à chaque fois que nous tenterons d’interroger le service distant, une résolution DNS sera effectuée. Comme dit plus haut, c’est en général extrêmement rapide mais dans certains cas, cela pourra être plus long et cela dépendra notamment du timeout défini dans le fichier /etc/resolv.conf
.
La requête DNS système pourra être plus lente dans le cas, par exemple, où le resolveur DNS qui est interrogé doit mettre à jour sa base.
Section intitulée amelioration-de-la-configurationAmélioration de la configuration
Pour améliorer les choses, il est possible de réduire ce timeout (via l’option timeout dans le fichier /etc/resolv.conf
—voir toutes les options).
search client.com
nameserver 212.X.X.X
nameserver 212.Y.Y.y
options timeout:1
Cette configuration peut néanmoins créer des latences et un script pourra attendre jusqu’à 1 seconde la réponse de la résolution DNS.
C’est la première chose que nous avons mise en place sur notre applicatif. Nous avons néanmoins eu des lenteurs. La méthode buildSocket
peut désormais mettre jusqu’à 1 seconde. C’est mieux que nos 5 secondes initiales.
Attention, selon la configuration de votre système, ce fichier peut être dynamique et géré par votre système d’exploitation (exemple sur Ubuntu avec le NetworkManager).
Section intitulée mise-en-cache-des-requetes-dns-au-niveau-systemeMise en cache des requêtes DNS au niveau système
Comme évoqué plus haut, par défaut, le système ne met pas en cache les résolutions DNS. Il a plein de bonnes raisons à ne pas le faire. En cas de changement d’IP, l’applicatif sera plus rapidement informé de ce changement, la modification ne nécessitera pas d’intervention « humaine » sur le système pour invalider le cache DNS…
Pour améliorer les performances de résolution DNS, il est donc possible d’installer un cache pour le « resolver » DNS. Il existes plusieurs solutions :
De notre côté, notre hébergeur nous a recommandé d’installer nscd. La mise en place du cache DNS a supprimé nos problèmes de lenteur.
En interne, nous utilisons dnsmasq sur nos machines locales car il est pratique.
En 2 lignes, nous pouvons résoudre tous les *.dev
et *.localhost
vers 127.0.0.1 :
address=/dev/127.0.0.1
address=/localhost/127.0.0.1
Pour conclure, petit avertissement, dans notre cas, cette solution nous a permis de gagner en performance car notre applicatif effectuait de nombreuses requêtes DNS (via stream_socket_client). Elle n’est pas nécessairement à mettre en œuvre partout sans réfléchir. Une résolution DNS lente peut être liée à d’autres problèmes (réseaux, routage, …).
Quelques ressources pour approfondir le sujet :
Commentaires et discussions
Ces clients ont profité de notre expertise
Dans le cadre d’une refonte complète de son architecture Web, Expertissim a sollicité l’expertise de JoliCode afin de tenir les délais et le niveau de qualité attendus. Le domaine métier d’Expertissim n’est pas trivial : les spécificités du marché de l’art apportent une logique métier bien particulière et un processus complexe. La plateforme propose…
À l’occasion de la 12e édition du concours Europan Europe, JoliCode a conçu la plateforme technique du concours. Ce site permet la présentation des différents sites pour lesquels il y a un appel à projets, et encadre le processus de recueil des projets soumis par des milliers d’architectes candidats. L’application gère également toute la partie post-concours…
L’équipe de Finarta a fait appel à JoliCode pour le développement de leur plateforme Web. Basée sur le framework Symfony 2, l’application est un réseau privé de galerie et se veut être une place de communication et de vente d’oeuvres d’art entre ses membres. Pour cela, de nombreuses règles de droits ont été mises en places et une administration poussée…