François Castets

Journal

13 October 202613 October 2026 3 min de lecturemin read Cartographie et géolocalisationMapping and geolocation

Répondre à « autour de moi » sur 3 645 escape games en moins de 100 msAnswering “near me” across 3,645 escape rooms in under 100 ms

Une recherche géolocalisée sur 3 645 salles, sans serveur dédié ni base spécialisée. Le détail de la requête, des index et du cache qui tiennent la charge.Geolocated search across 3,645 escape rooms, with no dedicated server and no specialised database. The query, the indexes and the cache that hold up.

Photo : GeoJango Maps sur Unsplash · licence Unsplash, utilisation libre.

Un annuaire d’escape games a une particularité : personne ne cherche « escape game ». On cherche « escape game près de chez moi », et on veut la réponse avant d’avoir reposé le téléphone. Sur Escape Gamer, cela veut dire interroger 3 645 salles réparties sur toute la France, trier par distance, et afficher le résultat sans que le visiteur ait le temps de s’impatienter.An escape-game directory has a quirk: nobody searches for “escape game”. People search for “escape game near me”, and they want the answer before they have put the phone down. On Escape Gamer that means querying 3,645 rooms spread across France, sorting them by distance, and showing the result before the visitor gets restless.

Le réflexe serait de sortir l’artillerie : une base géospatiale dédiée, un service de recherche séparé, un serveur à part. Sur un site qui tourne sur un hébergement mutualisé, c’est hors sujet. Voici ce qui a été fait à la place.The reflex would be to bring out the heavy machinery: a dedicated geospatial database, a separate search service, its own server. On a site running on shared hosting, that is beside the point. Here is what was done instead.

Le calcul de distance tient en une expression SQLDistance fits into a single SQL expression

Chaque salle porte une latitude et une longitude, rangées dans deux colonnes numériques indexées — pas dans une métadonnée WordPress sérialisée, où elles seraient inutilisables. La distance à vol d’oiseau entre le visiteur et chaque salle se calcule avec la formule de Haversine, directement dans la requête. Le moteur SQL fait le travail une fois, renvoie les lignes déjà triées, et PHP n’a plus qu’à les mettre en forme.Every room carries a latitude and a longitude, stored in two indexed numeric columns — not in a serialised WordPress meta field, where they would be unusable. The straight-line distance between the visitor and each room is computed with the Haversine formula, directly inside the query. The SQL engine does the work once, returns the rows already sorted, and PHP only has to format them.

Le piège classique est de calculer la distance pour les 3 645 lignes à chaque requête. On l’évite avec un pré-filtre : une boîte rectangulaire autour du point de départ, calculée en quelques opérations, qui élimine 95 % des salles avant même que la trigonométrie entre en jeu. Le calcul exact ne s’applique qu’aux survivantes.The classic trap is computing the distance for all 3,645 rows on every request. A pre-filter avoids it: a rectangular box around the starting point, computed in a handful of operations, which removes 95% of the rooms before trigonometry even enters the picture. The exact calculation only applies to the survivors.

Les index font la différence, pas le serveurIndexes make the difference, not the server

  • Un index composite sur latitude et longitude, pour que le pré-filtre rectangulaire soit instantané.A composite index on latitude and longitude, so the rectangular pre-filter is instant.
  • Un index sur le département, parce que la moitié des visites arrivent depuis une page de ville et non depuis une géolocalisation.An index on the area, because half the visits arrive from a town page rather than from a geolocation.
  • Les champs affichés dans la liste — nom, ville, note, vignette — stockés en colonnes propres, pour éviter une deuxième requête par salle.The fields shown in the list — name, town, rating, thumbnail — stored in proper columns, to avoid a second query per room.

Cette dernière ligne est celle qui a le plus compté. Un annuaire qui fait une requête supplémentaire par résultat affiché, c’est quarante requêtes pour une page de résultats. Les regrouper en une seule divise le temps de réponse par six.That last line is the one that mattered most. A directory that makes one extra query per displayed result means forty queries for one page of results. Collapsing them into one divides the response time by six.

Le cache, mais pas n'importe lequelCaching, but not just any caching

Une recherche géolocalisée ne se met pas en cache comme une page d’accueil : chaque visiteur a ses coordonnées. En revanche, les coordonnées sont arrondies avant d’entrer dans la requête — deux décimales, soit environ un kilomètre. Deux visiteurs du même quartier tombent donc sur la même clé de cache, et la deuxième recherche ne coûte rien.A geolocated search cannot be cached like a home page: every visitor has their own coordinates. The coordinates are however rounded before they enter the query — two decimal places, roughly a kilometre. Two visitors from the same neighbourhood therefore land on the same cache key, and the second search costs nothing.

Arrondir la position du visiteur avant de chercher ne dégrade pas le résultat : à un kilomètre près, le classement des salles ne change pas. En revanche, cela transforme une requête unique par visiteur en une requête partagée par tout un quartier.Rounding the visitor position before searching does not degrade the result: within a kilometre, the ranking of the rooms does not change. It does turn a per-visitor query into one shared by a whole neighbourhood.

Ce que ça donneWhat it adds up to

La recherche répond en moins de 100 ms sur un hébergement mutualisé, sans service externe, sans abonnement à une base géospatiale, et sans que la facture d’hébergement bouge. L’ensemble tient dans un plugin maison de quelques centaines de lignes.The search answers in under 100 ms on shared hosting, with no external service, no geospatial database subscription, and no change to the hosting bill. The whole thing fits in an in-house plugin of a few hundred lines.

La leçon n’est pas « il ne faut jamais de base spécialisée ». Elle est qu’à 3 645 enregistrements, on est très loin d’en avoir besoin, et qu’un index bien posé rend plus de services qu’une couche technique de plus à maintenir.The lesson is not “never use a specialised database”. It is that at 3,645 records you are nowhere near needing one, and that a well-placed index does more for you than another technical layer to maintain.

Le projet derrière cette noteThe project behind this note

Tout est parti de Escape Gamer, nouvelle version. La fiche détaille le contexte, ce qui a été construit et ce que le client en a tiré.It all started with Escape Gamer, nouvelle version. The project page covers the context, what was built and what the client got out of it.

À lire ensuiteRead next