
Cachear resultados de transcripción: deduplicación e idempotencia
Summarize this article with:
La transcripción es determinista: el mismo archivo de audio por el mismo motor y modelo siempre devuelve la misma transcripción. Cachear el resultado con una clave de hash de contenido y un hash de parámetros elimina los cargos repetidos de API por el mismo contenido. El coste de almacenamiento de una transcripción cacheada es tan pequeño que una sola retranscripción evitada paga el almacenamiento de ese resultado durante años. Esta guía cubre el modelo de idempotencia, la construcción de la clave de caché, los patrones de almacenamiento en Postgres y Redis, la invalidación al actualizar modelos y la aritmética que muestra si vale la pena construir la caché.
La transcripción es determinista. El mismo archivo de audio enviado por el mismo motor y modelo produce siempre la misma transcripción. Sin embargo, la mayoría de los sistemas en producción vuelven a transcribir los mismos archivos una y otra vez, pagando la API de transcripción en cada llamada. La caché convierte ese determinismo en un activo.
Esta guía es el patrón de caché para producción: claves basadas en hash de contenido para una deduplicación idempotente, almacenamiento en Postgres y Redis, invalidación al actualizar modelos y la aritmética que muestra lo rápido que la caché se amortiza.
Por qué la idempotencia es la base
Antes de pensar en Redis o Postgres, piensa en qué hace correcta la caché aquí: la transcripción es una función pura de (contenido de audio, motor, modelo, parámetros). Mismas entradas, misma salida, siempre. Esa propiedad se llama idempotencia en sentido amplio: repetir la operación no produce información nueva.
La caché es solo el mecanismo para aprovecharla. Construye bien el mecanismo y cada resubida, cada ejecución de backfill, cada botón de «regenerar» que no necesita regenerar se convierte en un acierto gratuito.
Cuatro patrones que desperdician dinero en pipelines sin caché:
- Un usuario sube la misma entrevista dos veces. Se transcribe dos veces.
- Un botón de «regenerar» llama a la API en lugar de devolver el resultado almacenado.
- Un script de backfill reprocesa archivos que ya tienen transcripciones.
- Se despliega una funcionalidad que vuelve a transcribir un corpus cuando solo cambió el procesamiento posterior.
Cada caso desperdicia una llamada a la API. Sumado entre miles de usuarios, la factura se dispara. Hemos observado tasas de retranscripción del 25 al 45 % en pipelines sin caché, aunque tu tasa real depende de tu flujo de trabajo.
Deduplicación por hash de contenido: la clave de caché
La clave de caché correcta es un hash SHA-256 del contenido del archivo de audio, combinado con un hash de los parámetros del motor y el modelo:
function cacheKey(audioBuffer, opts) {
const audioHash = crypto.createHash('sha256').update(audioBuffer).digest('hex');
const paramHash = crypto.createHash('sha256')
.update(JSON.stringify({ engine: opts.engine, language: opts.language, model: opts.model }))
.digest('hex')
.slice(0, 16);
return `transcript:${audioHash}:${paramHash}`;
}
El hash del audio es la identidad del contenido. El hash de parámetros distingue «transcrito con Deepgram Nova-3 English» de «transcrito con gpt-4o-transcribe». Ambas partes deben coincidir para que haya un acierto de caché.
¿Por qué no usar el nombre del archivo? Los nombres son metadatos, no identidad. interview.mp3 e interview_final.mp3 pueden ser el mismo archivo. Dos archivos distintos pueden compartir nombre. El hash del contenido resuelve ambos casos correctamente.
Calcular el hash de archivos grandes sin cargarlos en memoria
Para archivos de audio de varios cientos de MB, cargar el búfer completo solo para calcular su hash es un desperdicio. Calcula el hash en streaming:
import { createReadStream } from 'fs';
import { createHash } from 'crypto';
function hashFile(path) {
return new Promise((resolve, reject) => {
const hash = createHash('sha256');
const stream = createReadStream(path);
stream.on('data', chunk => hash.update(chunk));
stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject);
});
}
Esto lee por fragmentos y actualiza el hash de forma incremental, manteniendo la memoria plana sin importar el tamaño del archivo.
Almacenamiento: Postgres para persistencia, Redis para la ruta caliente
Postgres como almacén principal
Postgres es la opción predeterminada correcta. Las transcripciones se consultan con poca frecuencia y el almacenamiento es barato. El esquema:
CREATE TABLE transcript_cache (
cache_key TEXT PRIMARY KEY,
audio_hash TEXT NOT NULL,
engine TEXT NOT NULL,
language TEXT NOT NULL,
transcript JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
last_accessed TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_audio_hash ON transcript_cache (audio_hash);
Consulta con seguimiento de accesos en una sola petición:
async function getCached(key) {
const row = await db.query(
`UPDATE transcript_cache SET last_accessed = NOW() WHERE cache_key = $1 RETURNING transcript`,
[key]
);
return row.rows[0]?.transcript;
}
El patrón UPDATE ... RETURNING actualiza la marca de tiempo de acceso y devuelve la fila en un solo viaje de ida y vuelta.
Redis como capa de ruta caliente
Si un conjunto pequeño de transcripciones se consulta con mucha frecuencia (grabaciones recientes, contenido compartido popular), Redis reduce la carga sobre Postgres y lleva las consultas a tiempos submilisegundo:
async function getCached(key) {
const redisResult = await redis.get(key);
if (redisResult) return JSON.parse(redisResult);
const pgResult = await db.query(
'SELECT transcript FROM transcript_cache WHERE cache_key = $1',
[key]
);
if (pgResult.rows[0]) {
await redis.setex(key, 3600, JSON.stringify(pgResult.rows[0].transcript));
return pgResult.rows[0].transcript;
}
return null;
}
El TTL de Redis es de 1 hora en este ejemplo. Postgres no tiene TTL; las entradas viven hasta que se expulsan explícitamente. Para la mayoría de los pipelines, Postgres por sí solo es suficiente. Añade Redis solo después de haber medido que la latencia de Postgres es el cuello de botella real.
El patrón completo: inserción segura ante condiciones de carrera
async function getOrTranscribe(audioBuffer, opts) {
const key = cacheKey(audioBuffer, opts);
const cached = await getCached(key);
if (cached) return cached;
const result = await transcribeAPI(audioBuffer, opts);
await db.query(
`INSERT INTO transcript_cache (cache_key, audio_hash, engine, language, transcript)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (cache_key) DO NOTHING`,
[key, hashAudio(audioBuffer), opts.engine, opts.language, result]
);
return result;
}
ON CONFLICT DO NOTHING es una pieza clave. Dos peticiones simultáneas del mismo archivo de audio pueden fallar ambas en la caché y llamar ambas a la API. La primera inserción gana; la segunda se descarta en silencio. Ambos clientes reciben su resultado. Sin bloqueos ni entradas duplicadas.
Cálculo de costes de almacenamiento
Aquí es donde la economía de la caché se vuelve concreta.
Cuánto cuesta transcribir un audio. Deepgram Nova-3 para inglés pregrabado cuesta $0,0043 por minuto (a julio de 2026, según deepgram.com/pricing). Una hora de audio cuesta unos $0,26. El gpt-4o-transcribe de OpenAI cuesta $0,006 por minuto, unos $0,36 por hora (según los precios de developers.openai.com).
Cuánto cuesta almacenar una transcripción. El texto plano de la transcripción de una hora de inglés hablado pesa aproximadamente 48 KB (unos 800 caracteres por minuto en UTF-8). La respuesta JSONB completa de Deepgram, con marcas de tiempo a nivel de palabra, utterances, temas y sentimientos, ronda los 500 KB a 1 MB por hora, según el número de hablantes y el nivel de detalle. En AWS RDS PostgreSQL, el almacenamiento cuesta unos $0,115 por GB-mes (según las páginas de precios de AWS, julio de 2026).
Almacenar 1 MB de JSON de Deepgram durante un mes cuesta unos $0,000115. Con un coste evitado de $0,26 por retranscripción, un solo acierto de caché paga el almacenamiento de esa transcripción durante unos 2.200 meses.
La pregunta del punto de equilibrio no es el coste de almacenamiento, sino el tiempo de ingeniería. Para un sistema que ya usa Postgres, el patrón son unas 30 líneas de código. Para un pipeline que gasta $200 al mes en transcripción con una tasa de retranscripción del 10 %, eso son $20 al mes recuperados, o unos $240 al año por una tarde de trabajo.
Para más tácticas de ahorro en el lado de la API, consulta cómo optimizar costes en las llamadas a la API de transcripción y la comparativa de precios de transcripción más general.
Invalidación al actualizar modelos
El diseño de la clave de caché gestiona las actualizaciones de modelo automáticamente. Cuando pasas de Whisper Large-v3 a un modelo más reciente, el nombre del modelo forma parte del hash de parámetros, así que las entradas existentes dejan de coincidir. Las peticiones nuevas generan claves nuevas y disparan transcripciones frescas. Las entradas antiguas quedan huérfanas y caducan mediante tu consulta de expulsión por último acceso.
Otros dos escenarios de invalidación:
Errores reportados por clientes. Un usuario marca una transcripción errónea y quiere repetirla. Expón un endpoint de administración:
async function invalidateCacheForJob(jobId) {
const job = await db.jobs.findOne({ id: jobId });
await db.query(
'DELETE FROM transcript_cache WHERE cache_key = $1',
[job.cache_key]
);
}
Expulsión por tamaño. Las tablas de transcripciones crecen indefinidamente sin expulsión. Si eso se convierte en un problema, depura por fecha de último acceso:
DELETE FROM transcript_cache
WHERE last_accessed < NOW() - INTERVAL '90 days';
Para la mayoría de los equipos, las transcripciones son pequeñas en comparación con otros datos y la tabla sigue siendo manejable durante años. Mide antes de expulsar.
Caché por segmentos para archivos largos
Para grabaciones muy largas (pódcasts de varias horas, audio de conferencias de todo el día), una caché plana o falla por completo o cachea un blob enorme. La caché por segmentos es más granular:
Divide el audio en trozos de 5 minutos. Calcula el hash de cada trozo de forma independiente y comprueba la caché por trozo. Transcribe solo los segmentos que no estén cacheados. Esto también ayuda a los flujos de edición: si un usuario vuelve a subir un archivo con un cambio pequeño, solo los segmentos modificados necesitan retranscripción.
La caché a nivel de palabra es excesiva. La sobrecarga de almacenamiento por consulta no se amortiza. Los segmentos de 5 a 10 minutos son la granularidad adecuada.
Tasas de aciertos de caché según el caso de uso
Rangos realistas de pipelines en producción:
| Caso de uso | Tasa de aciertos típica |
|---|---|
| El usuario vuelve a enviar el mismo archivo | 8 a 15 % |
| Reejecuciones de herramientas internas | 60 a 80 % |
| Backfill / reprocesamiento por lotes | 80 a 95 % |
| SaaS multiusuario (usuarios independientes) | 0,5 a 3 % |
La primera fila es el caso típico en producción. Una tasa de aciertos del 10 % sobre una factura de transcripción de $500 al mes ahorra $50 al mes. El caso de backfill es el escenario de mayor retorno: si alguna vez necesitas reprocesar un corpus, la caché convierte un posible gran gasto adicional en casi cero.
Medir la eficacia de la caché
Añade contadores desde el principio para saber que la caché funciona:
async function getOrTranscribe(audioBuffer, opts) {
const key = cacheKey(audioBuffer, opts);
const cached = await getCached(key);
if (cached) {
metrics.increment('transcription.cache.hit');
return cached;
}
metrics.increment('transcription.cache.miss');
const result = await transcribeAPI(audioBuffer, opts);
await saveToCache(key, result);
return result;
}
Monitoriza la proporción de aciertos/fallos a lo largo del tiempo. Si la tasa de aciertos cae de forma inesperada, algo cambió: una ola de invalidaciones de caché, un formato de archivo nuevo que se salta el hash o una actualización de motor que generó claves nuevas para contenido existente.
Integración con webhooks
La caché combina muy bien con los pipelines asíncronos de webhooks, tratados en webhook frente a sondeo para transcripciones. El manejador del webhook calcula la clave de caché a partir del hash del audio original y los parámetros de la API, y luego escribe en la tabla de caché al completarse. Las peticiones futuras del mismo audio se saltan la API por completo.
Para los equipos que crean herramientas internas, la caché suele ser la optimización con mayor ROI una vez que el volumen supera aproximadamente las 50 horas al mes, como se explica en cómo crear una herramienta interna de transcripción.

Si tu caso de uso es transcripción de audio sencilla sin gestionar una capa de caché, ConvertAudioToText se encarga de la deduplicación y el almacenamiento de resultados en el servidor, así obtienes resultados al instante en archivos resubidos sin infraestructura que mantener.
Preguntas frecuentes
¿Debo calcular el hash del nombre del archivo o del contenido?
Calcula el hash del contenido. Los nombres engañan: la misma entrevista subida como interview.mp3 y interview_final.mp3 debería dar en la misma entrada de caché. Dos archivos diferentes ambos llamados interview.mp3 no deberían colisionar. El SHA-256 de los bytes del audio es la única señal de identidad fiable.
¿Qué ocurre cuando actualizo a un modelo más reciente de Whisper o Deepgram?
Las entradas antiguas de la caché siguen siendo válidas pero ya no coincidirán con las peticiones nuevas. Como el nombre del modelo forma parte del hash de parámetros en la clave de caché, cualquier cambio de versión del modelo genera una clave diferente y dispara una transcripción nueva. Las entradas antiguas caducan de forma natural mediante tu política de expulsión por último acceso; no necesitas eliminarlas en bloque.
¿Es necesario Redis o puede Postgres encargarse solo de la caché?
Postgres por sí solo es suficiente para la mayoría de los pipelines. Las búsquedas en caché sobre un índice de clave primaria son rápidas. Añade Redis como capa de ruta caliente solo si has medido tráfico alto sobre un conjunto pequeño de transcripciones accedidas recientemente y la latencia de Postgres es el cuello de botella real.
¿Qué tasa de aciertos de caché debería esperar?
Depende mucho de tu caso de uso. La resubida del mismo archivo por parte del usuario (el caso más común) suele producir tasas de aciertos del 8 al 15 %. Los trabajos de backfill o reprocesamiento por lotes pueden alcanzar del 80 al 95 %. Un SaaS multiusuario donde los usuarios suben de forma independiente archivos sin relación es el caso más difícil: del 0,5 al 3 %, lo que puede no justificar la complejidad hasta que tu volumen de transcripción sea alto.
Fuentes
- Precios de Deepgram Nova-3: https://deepgram.com/pricing (verificado en julio de 2026)
- Precios del modelo de transcripción de OpenAI: https://developers.openai.com/api/docs/pricing (verificado en julio de 2026)
- Precios de almacenamiento de AWS RDS PostgreSQL: https://aws.amazon.com/rds/postgresql/pricing/ (consultado en julio de 2026)
- Precios de Upstash Redis: https://upstash.com/pricing/redis (verificado en julio de 2026)
Try transcription free
Convert any audio or video to clean, unwatermarked text — speaker labels, timestamps, and AI summaries included. First 10 minutes free, no account.
Related Articles

Speechmatics Alternative for Non-Developers: Web Transcription Without Code
Speechmatics is genuinely excellent for developers: 50 hours free per month, 56 languages, on-prem deployment. If you need a drag-and-drop web app with flat $9.99/mo pricing instead of an API, here is an honest comparison of the two.

Best Transcription Tools with API Access (2026)
Which transcription SaaS tools actually give you API keys, and on which plan? Verified pricing and plan gates for Descript, Sonix, Fireflies, Happy Scribe, AssemblyAI, and more.