# Dec 1st, 2021: \[fr\] L'impact du filtrage de champs sur les performances d'Elasticsearch

**URL:** <https://discuss.elastic.co/t/dec-1st-2021-fr-limpact-du-filtrage-de-champs-sur-les-performances-delasticsearch/290560>\
**Category:** Advent Calendar\
**Created:** [December 1, 2021, 8:00am UTC](https://discuss.elastic.co/t/dec-1st-2021-fr-limpact-du-filtrage-de-champs-sur-les-performances-delasticsearch/290560 "2021-12-01T08:00:43Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Quentin\_Pradet](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/quentin_pradet/32/94192_2.png) [@Quentin\_Pradet](https://discuss.elastic.co/u/Quentin_Pradet)\
**Post date:** [December 1, 2021, 8:00am UTC](https://discuss.elastic.co/t/dec-1st-2021-fr-limpact-du-filtrage-de-champs-sur-les-performances-delasticsearch/290560/1 "2021-12-01T08:00:43Z")

</div>

ℹ This post is also available in English: [Dec 1st, 2021: [en] The impact of Elasticsearch source filtering on performance](https://discuss.elastic.co/t/dec-1st-2021-en-the-impact-of-elasticsearch-source-filtering-on-performance/289181)

Il y a quelques années, je voulais récupérer de nombreux documents d'Elasticsearch, et ça allait prendre une heure. Quand j'ai mentionné ça à un collègue, il m'a immédiatement demandé : « As-tu utilisé `_source` pour ne récupérer que les champs dont tu as besoin ? ». Je ne l'avais pas fait, et appliquer son conseil a considérablement accéléré mes requêtes.

Est-ce que ce conseil est toujours valable ? S'il l'est, à quelles améliorations s'attendre ? Je ne sais pas : c'est pour ça que j'écris ce billet.

## Pagination

Supposons qu'on veuille récupérer 25 000 documents depuis Elasticsearch. Le faire en une requête pourrait surcharger Elasticsearch, échouer avec un timeout ou même être bloqué par un proxy. À la place, il faut effectuer plusieurs petites requêtes, chacune d'entre elles récupérant 1 000 documents à la fois grâce aux paramètres `size` et `search_after`.

Mais entre deux requêtes, les données sous-jacentes pourraient avoir changé ! C'est là que l'API dite [point in time](https://www.elastic.co/guide/en/elasticsearch/reference/current/point-in-time-api.html) nous aide: grâce à elle, on peut faire comme si rien ne changeait et que le temps était gelé entre deux requêtes.

## Rally

Pour benchmarker Elasticsearch, nous allons utiliser [Rally](https://esrally.readthedocs.io/en/stable/), conçu pour donner des résultats fiables tout en étant facile à utiliser.

Avec Rally, des voitures (clusters Elasticsearch) font des courses (benchmarks) sur des pistes (étapes comme ingestion de données et requêtes). Dans ce billet, nous utiliserons la [piste (ou _track_) PMC](https://github.com/elastic/rally-tracks/tree/master/pmc). Voilà à quoi ses documents ressemblent :

```json
{
 "name": "3_Biotech_2015_Dec_13_5(6)_1007-1019",
 "journal": "3 Biotech",
 "date": "2015 Dec 13",
 "volume": "5(6)",
 "issue": "1007-1019",
 "accession": "PMC4624133",
 "timestamp": "2015-10-30 20:08:11",
 "pmid": "",
 "body": "\n==== Front\n3 Biotech3 Biotech3 Biotech2190-572X2190-5738Springer ..."
}

```

Ce qui nous intéresse ici, c'est que `body` est de loin le champ contenant le plus de données, donc ne pas le demander devrait donner des requêtes plus performantes. Après avoir cloné le dépôt [elastic/rally-tracks](https://github.com/elastic/rally-tracks/), j'ai [installé Rally](https://esrally.readthedocs.io/en/stable/install.html), [appliqué cette modification](https://github.com/elastic/rally/pull/1391) et modifié le challenge `append-no-conflicts` pour tester la pagination.

Pour ce faire, [je me suis inspiré de la documentation de Rally](https://esrally.readthedocs.io/en/stable/track.html#close-point-in-time) pour ajouter le fichier `pmc/operations/pagination.json`:

```json
{
  "name": "search-after-with-pit-default",
  "operation-type": "composite",
  "requests": [
    {
      "stream": [
        {
          "operation-type": "open-point-in-time",
          "name": "open-pit",
          "index": "pmc"
        },
        {
          "operation-type": "paginated-search",
          "name": "paginate",
          "index": "pmc",
          "with-point-in-time-from": "open-pit",
          "pages": 25,
          "results-per-page": 1000,
          "body": {
            "sort": [
              {"timestamp": "desc"}
            ],
            "query": {
              "match_all": {}
            }
          }        
        },
        {
          "name": "close-pit",
          "operation-type": "close-point-in-time",
          "with-point-in-time-from": "open-pit"
        }
      ]
    }
  ]
}

```

Ensuite, j'ai utilisé cette nouvelle opération en modifiant le challenge `append-no-conflict` dans `pmc/challenges/default.json`:

```json
{
  "operation": "search-after-with-pit-default",
  "warmup-iterations": 10,
  "iterations": 20
}

```

J'ai aussi supprimé toutes les opérations après `wait-until-merges-finish` comme on n'est qu'intéressés par la pagination ici. Voilà [le commit où je fais ça](https://github.com/pquentin/rally-tracks/commit/c618fa5523741a0f317d21d91daf04453589e527).

## Mise en place du benchmark

Pour obtenir des résultats reproductibles et ne concernant qu'Elasticsearch (et pas l'environnement autour), j'ai pris les précautions suivantes :

- Le générateur de charge (i3en.6xlarge) et le nœud Elasticsearch (m5d.4xlarge) sont deux machines différentes dans le même data center, ce qui évite un goulot d'étranglement côté réseau ou client.
- Avant de lancer les benchmarks, j'ai vidé le cache Linux et lancé la commande TRIM sur les SSDs.
- Je me suis assuré qu'aucune mise à jour ne tournait en tâche de fond

## esrally race

Si vous suivez depuis chez vous, vous pouvez lancer le benchmark comme ceci, puis attendre quelques minutes.

```auto
$ esrally race --distribution-version=7.15.2 --track-path=~/src/rally-tracks/pmc

    ________
   / __\____ _/ / /_ __
  / /_/ / __ `/ / / / / /
 / _, _/ /_/ / / / /_/ /
/_/ |_|\ __,_/_/_/\__ , /
                / ____ /

[INFO] Race id is [79992a48-57cd-41fb-9549-68f71354c5dd]
[INFO] Preparing for race ...
[INFO] Racing on track [pmc], challenge [append-no-conflicts] and car ['defaults'] with version [7.15.2].

Running put-settings [100% done]
Running delete-index [100% done]
Running create-index [100% done]
Running check-cluster-health [100% done]
Running index-append [100% done]
Running refresh-after-index [100% done]
Running force-merge [100% done]
Running refresh-after-force-merge [100% done]
Running wait-until-merges-finish [100% done]
Running search-after-with-pit-default [100% done]

------------------------------------------------------
    _________  _____
   / ____(_)___  _____/ / /___ / _________________
  / /_ / / __\/__ `/ / \ __\/___ / __\/___ / _ \
 / __/ / / / / / /_/ / /___ / / / __/ /_/ / / /__ /
/_/ /_/_/ /_/\ __,_/_/ /____ /\ ___/\____ /_/ \___/
------------------------------------------------------

| Metric | Task | Value | Unit |
|--------------------------:|------------------------------:|------------:|-------:|
...
| 50th percentile latency | search-after-with-pit-default | 3314.37 | ms |
| 90th percentile latency | search-after-with-pit-default | 3366.62 | ms |
| 100th percentile latency | search-after-with-pit-default | 3400.26 | ms |
| error rate | search-after-with-pit-default | 0 | % |

```

Ouvrir/fermer le _point-in-time_ et chercher 25 000 documents a pris environ 3,3 secondes.

## Comparer différentes configurations de `_source`

Maintenant que nous avons cette valeur de 3,3 secondes pour récupérer toutes les données (dont le champ volumineux `body`), comparons différentes options en changeant nos requêtes dans `pmc/operations/pagination.json`:

1. Filtrer l'ensemble du document avec `_source: false`
2. Ne lire que le petit champ `journal`
3. Ne lire que le champ volumineux `body`

Pour ne lire qu'un champ, nous testons les [méthodes `_source` and `fields`](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/search-fields.html). Voici une table montrant la latence médiane pour chaque option :

| Méthode | Latence médiane |
| --- | --- |
| Pas de filtrage | 3.3s |
| `"_source": false` | 1.1s |
| `journal` seulement via `_source` | 1.3s |
| `journal` seulement via `fields` | 1.3s |
| `body` seulement via `_source` | 3.7s |
| `body` seulement via `fields` | 4.8s |

Logiquement, demander peu ou pas de données est plus rapide, ici 3 fois plus ! On peut donc recommander le filtrage pour améliorer les performances de la pagination.

Par contre, ne demander que `body` est plus lent que de demander le document complet, que ce soit avec `_source` ou `fields`. Quand un champ représente l'essentiel d'un document, filtrer semble être une mauvaise idée. (Et passer par `fields` était pire !)

## Recommandations

Au vu de ces résultats, voilà ce que nous pouvons recommander en pratique pour la pagination :

1. Si vous n'utilisez qu'une petite partie d'un document, alors le filtrage ne peut qu'aider, en transmettant moins de données, demandant moins de compression/décompression et moins d'encodage/décodage JSON.
2. Si vous utilisez la plupart du document, alors il faudra mesurer l'exécution de votre code. En pratique, le client peut être moins puissant. Dans ce cas, la compression et le décodage JSON peuvent devenir des goulots d'étranglement: il faut alors envisager de désactiver la compression et d'utiliser un parser de JSON plus rapide (par exemple `orjson` en Python).

## Travaux futurs

Malgré les précautions prises, j'ai quand même utilisé des instances AWS. Utiliser des serveurs dédiés à la place (comme nous le faisons pour les [benchmarks Elasticsearch](https://elasticsearch-benchmarks.elastic.co/)) pourrait donner des résultats encore plus précis et plus fiables.

Un autre point important : ici les documents étaient assez petits pour être dans le cache mémoire, donc Elasticsearch n'avait rien à lire depuis le disque après la phase dite d'échauffement. Pour des jeu de données plus importants, lire depuis le disque pourrait devenir le goulot d'étranglement. À ce moment, le filtrage peut aider Lucene à lire depuis le disque plus efficacement grâce à [l'optimisation dite des doc values](https://www.elastic.co/blog/sparse-versus-dense-document-values-with-apache-lucene). Mais ce serait à confirmer avec un benchmark !

Et enfin, comme mentionné dans les recommandations, en pratique il est facile d'introduire de nouveaux goulots d'étranglement accidentellement : utilisez un profileur statistique tel que py-spy en Python pour savoir ce qui prend du temps !

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/elastic/original/3X/1/a/1ac57faf039f6b580b3f104ef42a2a89e41014de.png) [@system](https://discuss.elastic.co/u/system)\
**Post date:** [December 29, 2021, 8:00am UTC](https://discuss.elastic.co/t/dec-1st-2021-fr-limpact-du-filtrage-de-champs-sur-les-performances-delasticsearch/290560/2 "2021-12-29T08:00:56Z")

</div>

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.
