Spunto Searchthe Elasticsearch API, small

Elasticsearch’s API,
in 4.3 megabytes.

That is what a docker pull downloads — 10.2 MB unpacked on disk — against 669.1 MB for Elasticsearch 8.15.0, measured on the same machine on 2 September 2026. Elasticsearch is very good, and for most of what it gets deployed for it is enormous: a JVM, 1.08 GB of memory at rest and 18.1 s of startup, to index a few hundred thousand documents and answer a bool with a sort on the end. Spunto Search speaks the same HTTP API — your existing client code doesn’t change a line — from a static Rust binary in an otherwise empty container.

ES 8.15 API4.3 MB to pullanswering 172 ms after docker runsingle node
one command · your machine
$ curl -sSL https://github.com/spuntodotnet/search/releases/download/v0.7.0/ferrite-v0.7.0-x86_64-unknown-linux-musl.tar.gz | tar xz && ./ferrite

static musl binary, x86-64 or arm64 — then point an Elasticsearch client at :9200

01 — What you get

Your client can’t tell the difference.

Not a new SDK, not a « mostly compatible » dialect: the official Elasticsearch libraries connect to it, the handshake they perform on connect succeeds, and the requests you already send come back in the shape they already parse.

The inverted index itself isn’t reinvented — that’s Tantivy, the Rust equivalent of Lucene. The work is the compatibility layer on top of it, and it’s checked rather than claimed: Elastic’s own REST test suite is replayed against a running build, case by case, and what that run found is the number below — not a target, not a claim.

index_and_search.py
from elasticsearch import Elasticsearch   # the official client, unmodified

es = Elasticsearch("http://localhost:9200")

es.indices.create(index="books", mappings={"properties": {
    "title": {"type": "text"}, "author": {"type": "keyword"},
    "year": {"type": "integer"}}})

es.index(index="books", id="1", refresh=True,
         document={"title": "Bel-Ami", "author": "Maupassant", "year": 1885})

es.search(index="books", query={"match": {"title": "bel ami"}})
# {'took': 3, 'hits': {'total': {'value': 1, 'relation': 'eq'}, ...

measured conformance

measured 1 September 2026 · 35 days ago

older than a month

That measurement is 35 days old — past the 30 days after which this page stops presenting it as the current state. Read it as a snapshot taken on 1 September 2026, and check the report for where it stands now.

82.7%

172 / 208 cases — fidelity inside the declared scope

Of the cases that land on something this page claims, the share that answers exactly like Elasticsearch: 172 of 208. Cases that fail on a capability the repo declares refused are out of that denominator — they are the price of the scope, and the list below names every one of them.

14.7%

172 / 1173 cases — of Elasticsearch's own suite

The same 172 cases against the whole suite Elastic ships: 1173. This is how much of Elasticsearch is covered at all — a fraction, and the number that keeps the first one honest.

what ran
Elastic’s own REST suite, version 7.10.2 (Apache-2.0), replayed unmodified against a running build — 1173 cases across 107 suites. Not tests written here to pass here.
against
ferrite-0.10.0, announcing API version 8.15.0, measured on 1 September 2026.
declared
215 capabilities in compat.yaml: 103 supported, 68 partial, 44 refused — the two lists on this page are generated from it.
raw report
docs/conformance.json — every case, its verdict and its reason. docs/compat.md is the same thing for the capabilities.

in scope

Generated from the repository’s compat.yaml, one line per table: 103 capabilities supported and 68 partial — the partial ones marked, because a capability whose edges are missing isn’t a capability you can plan on. Each is named as the repository names it, in French where the repository is French: a translation on this page is one more thing to keep true, and this page has just stopped keeping such things by hand.

cluster
GET / · HEAD / (ping) · GET /_cluster/health, /_cluster/health/{index}partial · GET /_cat/indices, /_cat/indices/{index}partial · GET /_cat/healthpartial · GET /_nodes, /_nodes/{spec}partial · Content-Encoding: gzip / deflate sur le corps d'une requête
indices
Création à l'écriture · Expressions d'index (a,b, logs-*, _all, exclusions, alias) · PUT /{index}partial · DELETE /{index} · HEAD /{index} · GET /{index} · GET /{index}/_mapping · GET /{index}/_settingspartial · PUT /{index}/_settingspartial · PUT /{index}/_mappingpartial · POST /{index}/_refresh · POST|GET /_analyze, /{index}/_analyzepartial · Mapping dynamique · Alias · Templates d'index — _index_template et _templatepartial · GET /{index}/_statspartial
analyzers
standard (défaut) · simple · whitespace · keyword · stop · english · french · snowball · danish, dutch, german, hungarian, italian, norwegian, portuguese, romanian, russian, spanish, swedish, turkish · Analyzers sur mesure (settings.analysis)
custom analysis
analysis.analyzer de type custom · Tokenizerspartial · ngram et edge_ngram — tokenizer et filtre · Filtrespartial
field types
text · keyword · byte, short, integer, long · float, doublepartial · boolean · datepartial · Tableaux de valeurs · null · object (sous-objet), déclaré ou deviné · nestedpartial · join (parent/enfant)partial · analyzerpartial · search_analyzer · copy_to · storepartial · Multi-fields (fields) · ignore_above · index
ingestion
PUT|POST /{index}/_doc/{id}partial · POST /{index}/_doc · PUT|POST /{index}/_create/{id} · GET /{index}/_doc/{id} · HEAD /{index}/_doc/{id} · DELETE /{index}/_doc/{id} · POST|PUT /_bulk, /{index}/_bulkpartial · refresh (true / false / wait_for) · POST /{index}/_update/{id}partial · GET|POST /_mget, /{index}/_mget · GET|POST /{index}/_count · Versionnage optimiste if_seq_no / if_primary_term · POST /{index}/_delete_by_querypartial · POST /{index}/_update_by_querypartial
scroll
?scroll=1m sur _search · POST|GET /_search/scroll · POST|GET /_search/scroll/{scroll_id} · DELETE /_search/scroll, /_search/scroll/{scroll_id}
index patterns
catalogue · produits,marques · audits-2026.08.* · _all, *, URL sans index · audits-*,-audits-2026.07.* · ignore_unavailable · allow_no_indices · expand_wildcardspartial
aliases
POST /_aliasespartial · PUT|POST /{index}/_alias/{nom} et ses six autres URL (/_alias/{nom}, /{index}/_alias, /_alias, _aliases pour chacune) · DELETE /{index}/_alias/{nom} · GET /_alias, /_alias/{nom}, /{index}/_alias, /{index}/_alias/{nom} · HEAD /_alias/{nom}, /{index}/_alias/{nom} · aliases dans PUT /{index} · is_write_index
cluster settings
GET|PUT /_cluster/settingspartial · action.destructive_requires_name
query DSL
match_all · match_none · matchpartial · multi_matchpartial · match_phrasepartial · match_phrase_prefixpartial · existspartial · term · ids · prefixpartial · wildcardpartial · regexppartial · fuzzypartial · les champs de métadonnées en clause (_id, _index, …)partial · constant_score · dis_max · function_scorepartial · boosting · termspartial · rangepartial · boolpartial · _name (nommer une clause), matched_queries, include_named_queries_score · query_stringpartial · simple_query_stringpartial
full-text search
lenient · type: best_fields (défaut) · type: most_fields · type: phrase · type: phrase_prefix · operator sous phrase / phrase_prefixpartial
search body
POST|GET /{index}/_search, POST|GET /_search · query · from / size · sortpartial · _source · fieldspartial · docvalue_fieldspartial · stored_fields · highlightpartial · track_total_hitspartial · Scoringpartial · Format de réponse · preferencepartial · aggs / aggregationspartial · scroll · min_score · search_after · POST /{index}/_pit, DELETE /_pit, et le bloc pit du corps · timeout (paramètre et corps)partial · ignore_unavailable, allow_no_indices, expand_wildcards · GET|POST /{index}/_explain/{id}, explain: truepartial · GET|POST /{index}/_field_capspartial · GET|POST /{index}/_validate/querypartial
date math
now · now±<n><unité> · now/<unité> · <ancre>||<opérations> · date partielle (2026-03-15, 2026-03, 2026-03-15T12) · format sur range · dans term, terms, match, et sous un nested · time_zone
aggregations
min, max, sum, avg, value_count, stats · termspartial · rangepartial · histogrampartial · date_histogrampartial · Sous-agrégations · extended_statspartial · percentilespartial · top_hitspartial · filterpartial
nested
{"nested": {"path", "query"}} · Clauses internespartial · score_modepartial · Champs devinés sous un nested
join
{"type": "join", "relations": {...}} · Indexation · has_child, has_parentpartial · parent_id · {"term": {"lien": "article"}} · routingpartial
02 — What it costs to run

A container you forget is running.

The final image is a scratch holding one statically linked binary — 1 layer against 10. Nothing else — no libc, no shell, no JVM, nothing to patch on a Tuesday.

measured cost

measured 2 September 2026 · 34 days ago

older than a month

That measurement is 34 days old — past the 30 days after which this page stops presenting it as the current state. The binary grows: between the repository’s last two campaigns the image moved and so did the startup, which is exactly why this table is read rather than typed. Check the measurement for where it stands now. The repository flags this campaign as run on a modified tree, so the image measured may not be the one c2b4743 builds — Vrai si un des fichiers dont l'image est faite (src/, Cargo.toml, Cargo.lock, Dockerfile) differait de mesure.ferrite_sha pendant la campagne — donc si l'image mesuree pourrait ne pas etre celle que ce commit produit. Un README modifie au meme moment ne compte pas : il ne change pas un octet du binaire.

Elasticsearch 8.15.0Spunto Search 0.11.0
image, as a registry serves it669.1 MB4.3 MB

La somme des blobs qu'un registre sert pour cette image — le manifeste, la configuration et les couches telles qu'elles sont poussees. C'est ce qu'un docker pull telecharge, et le seul chiffre que ce depot publie. null quand l'archive porte des couches nues : la taille qu'un registre servirait n'en est alors pas deductible, et un nombre plausible mis a sa place serait la taille decompressee sous le nom de l'autre.

image, unpacked1.27 GB10.2 MB

La somme des couches une fois depliees, c'est-a-dire ce que le systeme de fichiers de l'image occupe une fois l'image tiree.

memory at rest1.08 GB4.9 MB

La somme des VmRSS de tous les processus du cgroup du conteneur, en kilo-octets, relevee 2 s apres le premier GET / servi et avant qu'aucun document ne soit indexe. Lire le seul PID 1 rendrait le RSS du shell qui lance la JVM d'Elasticsearch : deux ordres de grandeur en dessous, et du bon cote pour nous.

docker run → first response18.1 s172 ms

Le temps entre docker run -d et le premier GET / servi en 200, mediane de mesure.tours_demarrage demarrages successifs (conteneur recree a chaque tour). Pour ferrite l'essentiel est la creation du conteneur par Docker.

what the container runs/bin/tini/ferrite — 10.2 MB

La taille du fichier que l'ENTRYPOINT du conteneur execute. null quand l'ENTRYPOINT n'est pas un binaire — celui d'Elasticsearch est un lien vers un script qui lance une JVM, et un nombre qui ne se compare a rien vaut moins que son absence.

Both columns are measured by tests/compat/measure_container.sh on the same machine, in the same conditions, against a real Elasticsearch 8.15.0 pulled as Elastic publishes it — never against an idea of what Elasticsearch costs. The 172 ms is the whole docker run round trip to a first served request, and it is the median of 5 consecutive starts: 263 ms · 170 ms · 174 ms · 169 ms · 172 ms. For this binary most of that is Docker creating the container.

what was measured
ferrite:0.11.0, built here from the repository’s Dockerfile at c2b4743, against docker.elastic.co/elasticsearch/elasticsearch:8.15.0.
how
./tests/compat/measure_container.sh --json docs/container.json on linux/amd64, Docker 29.7.2 (overlayfs). La version du serveur Docker et son magasin d'images ne changent pas ce que cet outil mesure — il compte les octets de l'archive — mais ils changent ce que les AUTRES outils repondent a la meme question : docker image inspect --format '{{.Size}}' rendait la taille decompressee jusqu'a Docker 28 et rend la compressee depuis la 29, et la colonne DISK USAGE de docker images en additionne encore une troisieme.
docker run
-p 9299:9200 for Spunto Search, -p 9299:9200 -e discovery.type=single-node -e xpack.security.enabled=false -e ES_JAVA_OPTS=-Xms512m -Xmx512m for Elasticsearch — part of the definition of the memory and startup rows, not a detail.
raw measurement
docs/container.json — every figure above, its definition and the manifest digest of the exact bytes counted.

what that changes

Not the query — the deployment. A search engine that boots instantly and fits in a few megabytes stops being a piece of infrastructure with its own capacity plan and becomes a detail: a sidecar next to the app, a service in a CI job that isn’t worth caching, a container in a dev environment that’s already up before you’ve switched windows.

It is a single node holding a single shard on a single volume. That is the whole trade: no replication, no failover, no rebalancing — and none of the machinery that makes those cost what they cost.

docker-compose.yml
services:
  search:
    build: https://github.com/spuntodotnet/search.git
    ports:
      - "9200:9200"
    volumes:
      - search-data:/data

volumes:
  search-data:

No image is published yet, so this builds from the repo — the same scratch image the table measures. Prefer no Docker at all? The releases carry a static musl binary for x86-64 and arm64.

03 — What it refuses

Nothing fails quietly.

The failure mode that matters in a compatibility layer isn’t a missing feature, it’s a missing feature that answers 0 hits. Every clause, field type and route that isn’t implemented returns an error in Elasticsearch’s own format, with a type — not_implemented_in_ferrite_exception — that means precisely « ES can do this, we can’t yet ».

Some refusals are permanent and deliberate, and they’re the interesting ones: cardinality and slop are refused because Tantivy and Lucene disagree on what they should return, and both would have been easy to accept and quietly wrong. The reason for each refusal is in the repository, and it’s reproduced below rather than summarised.

out of scope

The 44 refusals declared in compat.yaml, by family — deliberate first, « not written yet » last.

out of scope, on purpose
Cluster distribué — _cluster/state, _cluster/stats, _cluster/reroute, _cluster/allocation/explain, _cluster/pending_tasks, _remote/info, _nodes/stats, _tasks · Snapshots, dépôts, restauration — _snapshot/* · Flux de données — _data_stream, data_stream dans un template · Tout ce qu'Elastic ajoute autour du moteur — sécurité et rôles (_security), machine learning (_ml), cycle de vie (_ilm, _slm), _watcher, _transform, _enrich, _eql, _sql, _esql, _inference, connecteurs, *search applications*, _rollup, _ccr, _graph, licence, _monitoring · Scripting — _scripts, _search/template, _render/template, Painless · script_fields, runtime_mappings · routing, filter_path, typed_keys · search_type, max_concurrent_shard_requests, pre_filter_shard_size, batched_reduce_size
engine divergence
finnish · Noms de champ pointés (a.b) ou réservés · type: cross_fields, bool_prefix · slop · histogram, date_histogram, range sur un champ multivalué · cardinality · Champ text dans une clause interne · Tri et agrégations sur un sous-champ de nested, depuis la racine
refused by Elasticsearch too
L'API typée — un {type} dans l'URL, un _type dans la réponse, include_type_name · from avec scroll · Contexte expiré, fermé, ou jamais ouvert · DELETE /{alias} · à l'indexation
not written yet
Les _cat/* autres que _cat/indices et _cat/health · Cycle de vie d'un index — _close, _open, _forcemerge, _shrink, _split, _clone, _rollover, _recovery, _segments, _flush, _upgrade, _cache/clear, _shard_stores, _resolve/index · Les autres routes de recherche — _search_shards, _termvectors, _mtermvectors, _suggest, _source · GET /{index}/_mapping/field/{champs} · _component_template, _simulate_index_template, _simulate · arabic, czech, greek, thai et les autres langues · char_filter · Un analyzer de type autre que custom (french, standard paramétré…) · Tout autre type (geo_point, ip, binary…) · Autres paramètres de champ (null_value, doc_values, norms…) · version / version_type externes · _reindex, pipelines d'ingestion · filter, routing, index_routing, search_routing sur un alias · GET /_cat/aliases · remove_index et add d'un alias du même nom dans le même POST /_aliases · GET /{index}/_alias sur un index fermé · intervals, terms_set, script, span_*… · collapse, knn, seq_no_primary_term, post_filter, suggest, rescore, track_scores, q, terminate_after, version, indices_boost, profile, slice, stats, ext, retriever · rest_total_hits_as_int · _msearch, _search/template · composite, filters, nested, significant_terms, date_range, ip_range, percentile_ranks, median_absolute_deviation… · nested dans un nested · inner_hits, ignore_unmapped
why each refusal — 44 entries, in the repository’s words

Quoted verbatim from compat.yaml, in the language the repository is written in: a translation here would be one more thing to keep true, and this page has just stopped keeping such things by hand.

out of scope, on purpose

Cluster distribué — _cluster/state, _cluster/stats, _cluster/reroute, _cluster/allocation/explain, _cluster/pending_tasks, _remote/info, _nodes/stats, _tasks
ferrite est mono-nœud et mono-shard : il n'y a ni allocation, ni réallocation, ni tâche distribuée à rapporter, et une réponse plausible inventée pour ces routes serait un mensonge sur la nature du serveur.
Snapshots, dépôts, restauration — _snapshot/*
la sauvegarde d'un ferrite est la copie de son répertoire de données, ou l'export par scroll ; un dépôt de snapshots répliqué est de la mécanique de cluster.
Flux de données — _data_stream, data_stream dans un template
un flux de données est une suite d'index gérée par _rollover et l'ILM, donc de l'exploitation de cluster posée au-dessus du moteur. Le refus est déclaré ici parce qu'il est exercé : helpers.reindex du client officiel demande GET /_data_stream/{cible} avant de copier, sur toute cible — c'est la suite serveur du client Python qui l'a montré, et sans cette ligne l'appel comptait indeterminé, donc contre nous.
Tout ce qu'Elastic ajoute autour du moteur — sécurité et rôles (_security), machine learning (_ml), cycle de vie (_ilm, _slm), _watcher, _transform, _enrich, _eql, _sql, _esql, _inference, connecteurs, *search applications*, _rollup, _ccr, _graph, licence, _monitoring
ce sont des produits posés sur le moteur, pas le moteur : chacun a son propre modèle de données et sa propre API, et aucun n'est ce qu'un client Elasticsearch existant appelle pour chercher des documents. C'est le corpus d'usage qui a montré qu'aucune capacité ne les réclamait — donc que 19 % de ses requêtes tombaient dans un trou de la déclaration, ni servies ni refusées.
Scripting — _scripts, _search/template, _render/template, Painless
un moteur de script est un langage à embarquer, à isoler et à maintenir : c'est exactement le genre de poids que ferrite existe pour ne pas porter.
script_fields, runtime_mappings
les deux définissent des champs calculés par un script Painless, que ferrite n'exécute pas. La mesure le confirme plutôt que la supposition : sur les 444 requêtes du corpus qui portent runtime_mappings, 425 l'envoient vide (des gabarits de tracks Rally), et sur les 19 non vides 18 portent un script. L'objet vide est donc accepté — il ne définit aucun champ, donc ne demande rien, et ES rend la même réponse avec ou sans (mesuré) ; un objet non vide est refusé explicitement.
routing, filter_path, typed_keys
ferrite est mono-shard, routing n'a rien à choisir ; les deux autres changent la forme de la réponse, et une forme qui dépend d'un paramètre est une seconde API à mesurer.
search_type, max_concurrent_shard_requests, pre_filter_shard_size, batched_reduce_size
ils reglent la façon dont une recherche se distribue entre shards ; il n'y en a qu'un, donc rien à distribuer et rien à régler.

engine divergence

finnish
le stemmer finnois de rust-stemmers s'écarte de celui de Lucene sur 13 mots des 84 399 du vocabulaire finnois de Snowball (0,015 %) : il coupe la voyelle finale d'un emprunt à diacritique étranger là où l'algorithme la garde (garcía rend garcí chez lui et garcía chez ES ; de même bundesstraße, españa, musée). Un analyzer n'est jamais livré sous le nom d'ES tant qu'il n'est pas mesuré identique — un écart de 0,015 % rendu en 200 est le pire résultat possible ici.
Noms de champ pointés (a.b) ou réservés
un point est le séparateur de chemin d'un objet : le porter dans un nom rendrait ambigu ce qu'un client.ville désigne. Le préfixe _, lui, n'est pas réservé — seuls le sont les champs de métadonnées d'ES (_id, _source, _seq_no…) et les racines des colonnes internes de ferrite (_elem, _nelem, _join_parent), qu'un champ utilisateur écraserait.
type: cross_fields, bool_prefix
le premier demande des statistiques de termes fusionnées entre champs, le second un scoring de suggestion : les approcher rendrait un autre classement que celui qu'un client attend.
slop
refusé quel que soit le type, pour la raison qui le fait refuser dans match_phrase (divergence n° 2).
histogram, date_histogram, range sur un champ multivalué
l'agrégation de tantivy compte les valeurs, Elasticsearch compte les documents : un document dont le champ vaut [1, 2, 3] tombe trois fois dans le bucket qui les contient (mesuré : doc_count de 4 là où ES en compte 2). Le refus n'est prononcé que si la colonne est réellement multivaluée — un champ à une valeur par document, le cas courant, reste servi et exact. terms, value_count et stats ne sont pas concernés : leurs comptes coïncident avec ceux d'ES.
cardinality
l'estimation de tantivy diffère de celle d'ES (mesuré : 582 valeurs distinctes annoncées là où ES en compte 598), y compris sous le seuil où ES est exact — un compte approché sous le nom d'ES serait faux sans le dire.
Champ text dans une clause interne
les colonnes portent la valeur, pas les termes analysés : interroger son multi-field .keyword, ou sortir la clause du nested.
Tri et agrégations sur un sous-champ de nested, depuis la racine
ils porteraient sur les valeurs à plat, donc sur autre chose que ce que la requête a filtré. Chez Elasticsearch ces valeurs vivent dans des documents cachés : une agrégation n'en voit aucune et rend son résultat vide (null, 0.0, buckets: []), un tri est refusé (it is mandatory to set the [nested] context on the nested sort field). ferrite les porte sur le document parent : il rendait donc une moyenne de 7.0 là où ES rend null, et un ordre en 200 là où ES rend 400. Un chiffre plausible et faux est le pire des résultats — le refus est explicite, et c'est le même que pour la requête équivalente (divergence n° 10).

refused by Elasticsearch too

L'API typée — un {type} dans l'URL, un _type dans la réponse, include_type_name
elle a disparu en 8.x, la version que ferrite annonce : un vrai Elasticsearch 8 échoue au même endroit, et la rendre reviendrait à annoncer une version qu'on ne sert pas.
from avec scroll
comme ES : action_request_validation_exception.
Contexte expiré, fermé, ou jamais ouvert
404 search_phase_execution_exception, cause search_context_missing_exception — la forme exacte d'ES, celle que les clients reconnaissent.
DELETE /{alias}
effacer des index que le client n'a pas nommés n'est pas une suppression, c'est un accident ; ES 8 refuse de la même façon (« The provided expression [x] matches an alias, specify the corresponding concrete indices instead »).
à l'indexation
{"d": "now"} est refusé, comme chez ES : le document porterait une date qui dépend de l'instant où il a été écrit.

not written yet

Les _cat/* autres que _cat/indices et _cat/health
ce sont des vues de texte sur un état que ferrite a pour la plupart ; elles ne sont pas écrites, et leurs tests exigent en plus les colonnes h, s et help que les deux _cat existants n'ont pas non plus.
Cycle de vie d'un index — _close, _open, _forcemerge, _shrink, _split, _clone, _rollover, _recovery, _segments, _flush, _upgrade, _cache/clear, _shard_stores, _resolve/index
la moitié de ces routes n'a pas de sens sans shards (_shrink, _split, _recovery, _shard_stores) ; l'autre moitié suppose un index qu'on peut arrêter de servir sans le supprimer (_close / _open), état que ferrite n'a pas.
Les autres routes de recherche — _search_shards, _termvectors, _mtermvectors, _suggest, _source
aucune n'est demandée par le code client qu'on cherche à servir.
GET /{index}/_mapping/field/{champs}
route absente (no handler found) : ferrite a pourtant le mapping, c'est un manque et pas une impossibilité — 15 cas de la suite d'Elastic tombent dessus.
_component_template, _simulate_index_template, _simulate
un template de composants est un template qu'on cite depuis un autre : tant qu'il n'est pas lu, composed_of est refusé à la pose plutôt qu'appliqué à moitié. La simulation, elle, rend l'index qu'on obtiendrait — utile, et sans client qui la demande.
arabic, czech, greek, thai et les autres langues
leur chaîne demande des filtres de normalisation qui ne sont pas portés (et, pour plusieurs, un stemmer absent de rust-stemmers). Rien n'y est mesuré : les douze langues servies le sont mot à mot, et celles-ci ne le seraient pas.
char_filter
aucun mapping venu d'une instance réelle n'en a encore demandé ; c'est une brique à écrire, pas un obstacle.
Un analyzer de type autre que custom (french, standard paramétré…)
paramétrer un analyzer intégré (stopwords, stem_exclusion) demande de reproduire sa composition interne exacte, qui n'est mesurée que dans sa forme par défaut.
Tout autre type (geo_point, ip, binary…)
chacun demande son propre encodage et ses propres requêtes ; aucun n'est apparu dans les mappings des instances reprises jusqu'ici.
Autres paramètres de champ (null_value, doc_values, norms…)
les paramètres acceptés sont type, analyzer, search_analyzer, fields, ignore_above, format, index, copy_to et store ; les autres sont refusés plutôt qu'acceptés sans effet, faute de quoi un doc_values: false laisserait croire qu'un champ n'est pas triable.
version / version_type externes
la version d'un document est gérée par ferrite ; l'imposer de l'extérieur demande de tenir un ordre que le serveur ne contrôle plus, et rien ne l'a encore réclamé.
_reindex, pipelines d'ingestion
_reindex copie d'un index vers un autre, et son intérêt est justement ce que ferrite n'a pas : une source distante, un pipeline, un script. Rien ne s'y oppose, ce n'est pas écrit — et scroll + _bulk le font aujourd'hui côté client, ce que ni _delete_by_query ni _update_by_query ne permettaient.
filter, routing, index_routing, search_routing sur un alias
un alias filtré dont le filtre n'est pas appliqué rendrait précisément les documents qu'il est censé cacher ; le routage, lui, n'a rien à choisir sur un mono-shard.
GET /_cat/aliases
route absente (no handler found) ; ses tests exigent en plus h=, s= et help sur les _cat, que ferrite n'a pas — 10 cas de la suite d'Elastic tombent dessus.
remove_index et add d'un alias du même nom dans le même POST /_aliases
refusé en 400 (« an index or data stream exists with the same name as the alias ») : ferrite applique les alias avant les suppressions, là où ES calcule tout l'état puis l'applique — c'est pourtant l'usage même de remove_index.
GET /{index}/_alias sur un index fermé
_close / _open sont hors périmètre, donc un index fermé n'existe pas ici.
intervals, terms_set, script, span_*…
parsing_exception: unknown query [...], avec la liste des clauses connues.
collapse, knn, seq_no_primary_term, post_filter, suggest, rescore, track_scores, q, terminate_after, version, indices_boost, profile, slice, stats, ext, retriever
aucun n'est un obstacle de moteur. search_after et pit, qui manquaient le plus, sont servis depuis la carte 08.
rest_total_hits_as_int
il change la forme de hits.total (nombre au lieu d'objet) ; ES 8 l'accepte encore et du code venu de la 6.x/7.x s'en sert, voir [compat-es7.md](compat-es7.md).
_msearch, _search/template
_msearch est le plus regretté : un tableau de bord qui pose six facettes fait six appels au lieu d'un.
composite, filters, nested, significant_terms, date_range, ip_range, percentile_ranks, median_absolute_deviation…
filters (la sœur plurielle de filter) et composite sont les deux qui manquent le plus ; aucune n'est un obstacle de moteur.
nested dans un nested
il faudrait un indice d'élément par niveau.
inner_hits, ignore_unmapped
inner_hits demande de rendre l'élément qui a correspondu : ferrite le sait (c'est la colonne jumelle), il ne l'expose simplement pas.

read this bit

This is version 0.10.0, single-node, with no authentication. Anyone who can reach port 9200 can read and write every index, and there is no replica anywhere — the durability of your data is the durability of that one volume. Treat it as a search index you can rebuild from your source of truth, on a private network, and it is exactly the right size. Treat it as a database and it will disappoint you.

04 — Where it earns its keep

The search box, not the search cluster.

Most products that need search need one index, a few hundred thousand documents and a facet or two — and end up running a distributed system designed for petabytes because that’s what the client library talks to. This talks to the same library.

sidecar
in the same compose file as the app, on the same volume, with no capacity plan
CI
a service container that starts faster than the checkout step it follows
dev environments
a Spunto worker that comes up with search already answering
the edge of a product
search inside an appliance, an on-prem install, a Raspberry Pi

↳ also from Spunto