---
{
  "title": "FHIR TX Benchmark: Ronda 0",
  "description": "Publicamos el primero de una serie de benchmarks FHIR abiertos: un benchmark de rendimiento para servidores de Terminology FHIR con 20 casos de prueba y 5 servidores.",
  "date": "2026-04-13",
  "author": "Orlando Osorio",
  "tags": ["Terminology", "Performance", "Benchmark"]
}
---

> For the complete documentation index, see [llms.txt](https://www.health-samurai.io/llms.txt).
> Use it to discover all available pages before guessing URLs.

---
![FHIR TX Benchmark — performance scores across terminology servers](hero.avif)

En Health Samurai, el rendimiento nos importa profundamente.

Estamos trabajando en una serie de benchmarks FHIR abiertos. El primero que publicamos es el FHIR TX Benchmark: un benchmark de servidores de Terminology con 20 casos de prueba, 5 servidores, ejecutándose sobre los mismos datos y hardware, en aislamiento.

Los servidores de Terminology son con frecuencia una parte crítica de la infraestructura de un sistema y pueden convertirse fácilmente en un cuello de botella de rendimiento. Se utilizan como parte de la validación, pueden alimentar elementos de interfaz de usuario, actuar como backend de búsqueda y filtrado, y se están integrando cada vez más en flujos de trabajo potenciados por IA para anclar a los agentes en la realidad.

Queremos que todo sea lo más transparente posible: todos los scripts, pruebas, datos, resultados e instrucciones están disponibles en [GitHub](https://github.com/HealthSamurai/tx-benchmark). Existe un sitio web en vivo construido a partir del repositorio con los resultados en https://healthsamurai.github.io/tx-benchmark.

{% hint style="info" %}
**Sobre la metodología y los sesgos**

Health Samurai desarrolló la versión inicial de este benchmark y nuestro propio servidor (Termbox, que se publica a finales de este mes) es uno de los cinco que se evalúan. Somos conscientes de cómo puede interpretarse esto. Nuestra respuesta es la metodología en sí misma: cada prueba, cada parámetro y cada fórmula están documentados y son abiertos, los datos son reproducibles y hemos estado en conversación con los demás implementadores. Que sepamos, no existe ningún benchmark de este tipo para servidores de [Terminology FHIR](/articles/two-phase-fhir-terminology). La metodología, el conjunto de pruebas y la puntuación están abiertos al escrutinio y damos la bienvenida a sugerencias, correcciones y contribuciones de la comunidad y de otros implementadores.

{% endhint %}

## Metodología

La [especificación del servidor de Terminology FHIR](https://build.fhir.org/terminology-service.html) abarca múltiples operaciones, tipos de recursos, funcionalidades avanzadas y casos de uso. Esto plantea un desafío a la hora de decidir qué probar y cómo probarlo. Las pruebas se seleccionaron por su relevancia para el rendimiento; por ejemplo, una búsqueda de texto completo es importante para las interfaces de usuario y tiene un coste de implementación muy diferente al de una simple búsqueda de código: tenemos una prueba para cada caso.

La conformidad no era un objetivo; ya disponemos de un conjunto de pruebas de conformidad exhaustivo en el [Terminology Ecosystem IG](https://github.com/HL7/fhir-tx-ecosystem-ig).

Las capacidades y objetivos de los servidores varían: algunos ofrecen soporte parcial para ciertas operaciones, otros están diseñados para casos de uso específicos u optimizados para terminologías concretas. Solo evaluamos cada servidor en las funcionalidades que soporta. La metodología completa está documentada en la [página de metodología](https://healthsamurai.github.io/tx-benchmark/methodology/).

### Pruebas

Para las pruebas, seleccionamos algunas de las principales operaciones de Terminology: Lookup, Validate Code, Expand, Translate y Subsumes. Cada prueba se identifica mediante un código corto: las iniciales de la operación seguidas de un número de secuencia: LK01 para la primera prueba de Lookup, EX02 para la segunda de Expand, y así sucesivamente.

Con frecuencia tenemos múltiples pruebas para la misma operación. Esto nos permite evaluar diferentes rutas relevantes para el rendimiento, distintas terminologías o características específicas de una operación que justifican su propia prueba.

La lista completa de pruebas y sus descripciones está documentada en la [página de pruebas](https://healthsamurai.github.io/tx-benchmark/tests/).

### Datos

Cargamos el mismo conjunto de datos de Terminology en cada servidor (siempre que sea posible). Las instrucciones y los requisitos de licencia están documentados en el repositorio de GitHub.

Las terminologías se seleccionaron en función de su importancia y variedad (en estructura, jerarquía, propiedades y tamaño), así como de los diferentes tipos de valuesets. Las principales cargadas para esta ronda son:

- SNOMED International - 20260201
- SNOMED US Edition - 20260301
- SNOMED UK Edition - 20260211
- LOINC - 2.82
- RxNorm - marzo de 2026
- Un conjunto de paquetes FHIR que incluye r4 core, THO, US Core, VSAC y otros

Lista completa en la [página de datos](https://healthsamurai.github.io/tx-benchmark/data/).

### Puntuación

El benchmark produce una puntuación compuesta que permite comparar el rendimiento general de los servidores de un vistazo. Veinte pruebas distribuidas en seis operaciones generan muchos números, y el valor compuesto los destila en un único valor comparable. Nos inspiramos ampliamente en los [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Composite-Scoring-for-Frameworks) y basamos nuestros algoritmos en los suyos.

La puntuación se basa en el rendimiento (throughput). Cada prueba se ejecuta en tres niveles de concurrencia: 1, 10 y 50 usuarios virtuales. Conservamos el mayor throughput entre los tres (si un servidor alcanza su pico a 10 VU y se degrada con mayor concurrencia, utilizamos el mejor valor para la puntuación).

**Normalización**: Los valores de throughput se normalizan con base en `LK01`. Una prueba de lookup siempre será más rápida que una expansión compleja; las RPS brutas harían que las pruebas «fáciles» dominaran todo, lo cual no es justo ni útil.

**Sesgo**: Multiplicamos los resultados de las pruebas por un _coeficiente de sesgo_. Esto nos permite reducir el impacto de una prueba en la puntuación global en función de su importancia. Por ejemplo, el rendimiento de un lookup en SNOMED debería importar más que una búsqueda de CodeSystem para un servidor de Terminology, ya que el primero forma parte con mayor frecuencia de una ruta crítica. Los coeficientes están documentados y abiertos a comentarios (como todo lo demás).

**Imputación**: La falta de soporte no es lo mismo que un rendimiento deficiente. Asignar cero puntos por pruebas no soportadas es demasiado severo; otorgar un pase libre en una prueba difícil tampoco es justo. Los servidores reciben un valor imputado derivado de un percentil de los resultados de los servidores participantes.

**Puntuación global**: El mejor servidor en rendimiento general obtiene 100. La puntuación de cada otro servidor se expresa como un porcentaje de esa cifra, tanto por prueba como en el compuesto global.

### Hardware y stack

Las pruebas de carga se realizan con [k6](https://k6.io/), con [Prometheus](https://prometheus.io/) y [cAdvisor](https://github.com/google/cadvisor) para la recopilación de métricas. Todos los servidores se ejecutan en contenedores Docker en el mismo host que el generador de carga, usando únicamente el loopback — sin latencia de red.

La Ronda 0 se ejecutó en un portátil: Apple M3 de 8 núcleos, 24 GB de RAM, Docker Desktop configurado con 8 CPUs y 20 GB. Las rondas futuras se ejecutarán en una VM cloud dedicada con recursos significativamente mayores, y el proceso estará completamente automatizado.

## Resultados de la Ronda 0

La Ronda 0 es la ronda inicial y la que está actualmente publicada en el sitio web. Es una ejecución piloto (hemos estado trabajando con los implementadores para garantizar que los servidores estén correctamente configurados, y los resultados pueden actualizarse a medida que incorporemos el feedback recibido). Tras la Ronda 1, el proceso se automatizará y se ejecutará de forma periódica con las últimas versiones de cada servidor y un conjunto de pruebas mejorado conforme recibamos feedback.

Esta ronda incluye cinco servidores: [FHIRsmith](https://github.com/HealthIntersections/FHIRsmith), [Hades](https://github.com/wardle/hades), [Ontoserver](https://www.ontoserver.csiro.au/site/), [Snowstorm](https://github.com/IHTSDO/snowstorm) y [Termbox](https://www.health-samurai.io/termbox). No todos los servidores ejecutaron todas las pruebas; esto se refleja en el [panel de la matriz de capacidades](https://healthsamurai.github.io/tx-benchmark/results/round-0/details/).


<div class="table-wrapper narrow" style="background:#181b2e;border-radius:10px;overflow:hidden;font-family:system-ui,-apple-system,sans-serif;margin:2rem 0;border:1px solid #252840;">
  <table style="width:100%;border-collapse:collapse;font-size:14px;white-space:nowrap;">
    <thead>
      <tr style="border-bottom:1px solid #252840;">
        <th style="text-align:left;padding:8px 16px;color:#6b7280;font-size:11px;text-transform:uppercase;letter-spacing:0.06em;background:transparent;border-bottom:1px solid #252840;width:40px;">#</th>
        <th style="text-align:left;padding:8px 16px;color:#6b7280;font-size:11px;text-transform:uppercase;letter-spacing:0.06em;background:transparent;border-bottom:1px solid #252840;width:130px;">Server</th>
        <th style="text-align:left;padding:8px 16px;color:#6b7280;font-size:11px;text-transform:uppercase;letter-spacing:0.06em;background:transparent;border-bottom:1px solid #252840;width:100%;">Score</th>
      </tr>
    </thead>
    <tbody>
      <tr style="border-bottom:1px solid #252840;">
        <td style="padding:9px 16px;color:#6b7280;font-size:12px;vertical-align:middle;border-bottom:1px solid #252840;">1</td>
        <td style="padding:9px 16px;font-weight:500;color:#e4e4e4;vertical-align:middle;border-bottom:1px solid #252840;">termbox</td>
        <td style="padding:9px 16px;min-width:220px;width:100%;vertical-align:middle;border-bottom:1px solid #252840;">
          <div style="display:flex;align-items:center;gap:10px;">
            <div style="flex:1;height:22px;border-radius:4px;background:#1e2236;overflow:hidden;">
              <div style="--s:100;width:calc(var(--s) * 1%);height:100%;border-radius:4px;background:linear-gradient(90deg,#f2495c,#fade2a,#73bf69);background-size:calc(100% * 100 / var(--s)) 100%;"></div>
            </div>
            <span style="font-weight:600;color:#e4e4e4;min-width:38px;text-align:right;">100%</span>
          </div>
        </td>
      </tr>
      <tr style="border-bottom:1px solid #252840;">
        <td style="padding:9px 16px;color:#6b7280;font-size:12px;vertical-align:middle;border-bottom:1px solid #252840;">2</td>
        <td style="padding:9px 16px;font-weight:500;color:#e4e4e4;vertical-align:middle;border-bottom:1px solid #252840;">fhirsmith</td>
        <td style="padding:9px 16px;min-width:220px;width:100%;vertical-align:middle;border-bottom:1px solid #252840;">
          <div style="display:flex;align-items:center;gap:10px;">
            <div style="flex:1;height:22px;border-radius:4px;background:#1e2236;overflow:hidden;">
              <div style="--s:38;width:calc(var(--s) * 1%);height:100%;border-radius:4px;background:linear-gradient(90deg,#f2495c,#fade2a,#73bf69);background-size:calc(100% * 100 / var(--s)) 100%;"></div>
            </div>
            <span style="font-weight:600;color:#e4e4e4;min-width:38px;text-align:right;">38%</span>
          </div>
        </td>
      </tr>
      <tr style="border-bottom:1px solid #252840;">
        <td style="padding:9px 16px;color:#6b7280;font-size:12px;vertical-align:middle;border-bottom:1px solid #252840;">3</td>
        <td style="padding:9px 16px;font-weight:500;color:#e4e4e4;vertical-align:middle;border-bottom:1px solid #252840;">ontoserver</td>
        <td style="padding:9px 16px;min-width:220px;width:100%;vertical-align:middle;border-bottom:1px solid #252840;">
          <div style="display:flex;align-items:center;gap:10px;">
            <div style="flex:1;height:22px;border-radius:4px;background:#1e2236;overflow:hidden;">
              <div style="--s:21;width:calc(var(--s) * 1%);height:100%;border-radius:4px;background:linear-gradient(90deg,#f2495c,#fade2a,#73bf69);background-size:calc(100% * 100 / var(--s)) 100%;"></div>
            </div>
            <span style="font-weight:600;color:#e4e4e4;min-width:38px;text-align:right;">21%</span>
          </div>
        </td>
      </tr>
      <tr style="border-bottom:1px solid #252840;">
        <td style="padding:9px 16px;color:#6b7280;font-size:12px;vertical-align:middle;border-bottom:1px solid #252840;">4</td>
        <td style="padding:9px 16px;font-weight:500;color:#e4e4e4;vertical-align:middle;border-bottom:1px solid #252840;">snowstorm</td>
        <td style="padding:9px 16px;min-width:220px;width:100%;vertical-align:middle;border-bottom:1px solid #252840;">
          <div style="display:flex;align-items:center;gap:10px;">
            <div style="flex:1;height:22px;border-radius:4px;background:#1e2236;overflow:hidden;">
              <div style="--s:7;width:calc(var(--s) * 1%);height:100%;border-radius:4px;background:linear-gradient(90deg,#f2495c,#fade2a,#73bf69);background-size:calc(100% * 100 / var(--s)) 100%;"></div>
            </div>
            <span style="font-weight:600;color:#e4e4e4;min-width:38px;text-align:right;">7%</span>
          </div>
        </td>
      </tr>
      <tr>
        <td style="padding:9px 16px;color:#6b7280;font-size:12px;vertical-align:middle;">5</td>
        <td style="padding:9px 16px;font-weight:500;color:#e4e4e4;vertical-align:middle;">hades</td>
        <td style="padding:9px 16px;min-width:220px;width:100%;vertical-align:middle;">
          <div style="display:flex;align-items:center;gap:10px;">
            <div style="flex:1;height:22px;border-radius:4px;background:#1e2236;overflow:hidden;">
              <div style="--s:6;width:calc(var(--s) * 1%);height:100%;border-radius:4px;background:linear-gradient(90deg,#f2495c,#fade2a,#73bf69);background-size:calc(100% * 100 / var(--s)) 100%;"></div>
            </div>
            <span style="font-weight:600;color:#e4e4e4;min-width:38px;text-align:right;">6%</span>
          </div>
        </td>
      </tr>
    </tbody>
  </table>
</div>

Los resultados completos, los detalles por servidor, las RPS brutas y los percentiles de latencia están disponibles en el [sitio web](https://healthsamurai.github.io/tx-benchmark/results/round-0/).

## Qué viene a continuación

La Ronda 1 llegará pronto: se ejecutará en una VM cloud dedicada, de forma automatizada y con mejores configuraciones basadas en el feedback de esta ronda. Después de eso, planeamos publicar resultados con una periodicidad trimestral.

Concebimos esto como un proyecto comunitario. Health Samurai es el responsable actual, pero el objetivo es que sea gobernado y al que contribuya la comunidad FHIR en general: implementadores, usuarios y cualquier persona con interés en el rendimiento de los servidores de Terminology.

Si desea participar: sugiera nuevas pruebas, notifique una configuración incorrecta, proponga un servidor para incluir o simplemente abra una incidencia [en GitHub](https://github.com/HealthSamurai/tx-benchmark). Toda contribución es bienvenida.

## Preguntas frecuentes

Anticipamos que puede tener muchas preguntas. A continuación figuran algunas que esperamos. No obstante, no dude en contactarnos si tiene alguna pregunta que no abordamos aquí o simplemente quiere decirnos que lo estamos haciendo mal.

<dl>
  <dt><strong>«El servidor X está mal configurado — eso explica los números.»</strong></dt>
  <dd>Es muy posible. Hemos hecho todo lo posible por configurar correctamente cada servidor, pero no somos expertos en todos ellos. Si sabe cómo mejorar una configuración, abra un pull request o una incidencia y actualizaremos los resultados.</dd>

  <dt><strong>«¿Por qué no está incluido el servidor X?»</strong></dt>
  <dd>Es posible que no lo conozcamos. Abra una incidencia o un PR con las instrucciones de configuración y trabajaremos para incluirlo en una ronda futura.</dd>

  <dt><strong>«¿Por qué estas pruebas? ¿Por qué no se prueba la operación X?»</strong></dt>
  <dd>Las pruebas se seleccionaron por su relevancia para el rendimiento. Si considera que falta alguna operación o caso de uso importante, el conjunto de pruebas está pensado para crecer — ya tenemos algunos en mente para la Ronda 1. Por favor, ayúdenos abriendo una incidencia.</dd>

  <dt><strong>«Health Samurai creó este benchmark y Termbox obtiene buenos resultados. ¿No es eso un conflicto de interés?»</strong></dt>
  <dd>Ya abordamos esta cuestión al inicio del artículo. La versión resumida: la metodología, los datos y la puntuación son completamente abiertos y reproducibles. También vale la pena señalar que Termbox fue diseñado específicamente con el rendimiento en mente — era de esperar que obtuviera buenos resultados. Si encuentra algún defecto en la metodología, abra una incidencia.</dd>

  <dt><strong>«Los coeficientes de sesgo y el percentil de imputación podrían ajustarse para favorecer a ciertos servidores.»</strong></dt>
  <dd>Son decisiones de criterio, y los hemos documentado explícitamente por esa razón. Si no está de acuerdo con los pesos, abra una incidencia — estamos abiertos a ajustarlos en función del feedback de la comunidad.</dd>

  <dt><strong>«¿Ejecutaron esto en un portátil?»</strong></dt>
  <dd>Sí. La Ronda 0 es una ejecución piloto en un Apple M3. Los números cambiarán en hardware superior, pero esperamos que las clasificaciones relativas sean en gran medida consistentes. Las rondas futuras se ejecutarán en una VM cloud dedicada.</dd>

  <dt><strong>«¿Qué ocurre con la caché? La mayoría de los despliegues en producción sitúan una caché delante del servidor.»</strong></dt>
  <dd>Cierto, y cambia significativamente el panorama. Pero estamos evaluando el servidor, no la arquitectura de despliegue. Los despliegues en el mundo real son más complejos, y somos conscientes de ello — pero eso es algo diferente que medir.</dd>

  <dt><strong>«¿Qué hay del clustering o el escalado horizontal?»</strong></dt>
  <dd>Este benchmark evalúa únicamente el rendimiento en un único nodo. El escalado horizontal es una dimensión válida, pero depende en gran medida de la arquitectura de despliegue, no solo del servidor en sí.</dd>
</dl>