
Mise en cache des résultats de transcription : déduplication et idempotence
Summarize this article with:
La transcription est déterministe : un même fichier audio passé par le même moteur et le même modèle renvoie toujours la même transcription. Mettre le résultat en cache avec une clé de hachage du contenu et un hachage des paramètres élimine les frais d'API répétés pour un même contenu. Le coût de stockage d'une transcription en cache est si faible qu'une seule re-transcription évitée finance son stockage pendant des années. Ce guide couvre le modèle d'idempotence, la construction des clés de cache, les schémas de stockage Postgres et Redis, l'invalidation lors des mises à niveau de modèles, et les calculs qui montrent si le cache vaut la peine d'être mis en place.
La transcription est déterministe. Un même fichier audio envoyé via le même moteur et le même modèle produit la même transcription à chaque fois. Pourtant, la plupart des systèmes en production re-transcrivent les mêmes fichiers encore et encore, payant l'API de transcription à chaque appel. La mise en cache traite ce déterminisme comme un atout.
Ce guide présente le schéma de cache utilisé en production : clés de hachage du contenu pour une déduplication idempotente, stockage Postgres et Redis, invalidation lors des mises à niveau de modèles, et les calculs qui montrent à quelle vitesse le cache se rentabilise.
Pourquoi l'idempotence est le fondement
Avant de penser à Redis ou Postgres, réfléchissez à ce qui rend le cache correct ici : la transcription est une fonction pure de (contenu audio, moteur, modèle, paramètres). Mêmes entrées, même sortie, toujours. Cette propriété s'appelle l'idempotence au sens large : répéter l'opération ne produit aucune information nouvelle.
Le cache n'est que le mécanisme permettant de l'exploiter. Construisez bien ce mécanisme et chaque nouveau téléversement, chaque exécution de backfill, chaque bouton « Régénérer » qui n'a pas besoin de régénérer devient un hit gratuit.
Quatre schémas qui gaspillent de l'argent sur des pipelines sans cache :
- Un utilisateur téléverse la même interview deux fois. Elle est re-transcrite deux fois.
- Un bouton « Régénérer » appelle l'API au lieu de renvoyer le résultat stocké.
- Un script de backfill retraite des fichiers qui ont déjà des transcriptions.
- Une fonctionnalité se déploie et re-transcrit tout un corpus alors que seul le traitement en aval a changé.
Chacun gaspille un appel API. Multipliez cela par des milliers d'utilisateurs et la facture grimpe. Nous avons observé des taux de re-transcription de 25 à 45 % sur des pipelines sans cache, mais votre taux réel dépend de votre flux de travail.
Déduplication par hachage du contenu : la clé de cache
La bonne clé de cache est un hachage SHA-256 du contenu du fichier audio, combiné à un hachage des paramètres du moteur et du modèle :
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}`;
}
Le hachage audio est l'identité du contenu. Le hachage des paramètres distingue « transcrit avec Deepgram Nova-3 English » de « transcrit avec gpt-4o-transcribe ». Les deux parties doivent correspondre pour obtenir un hit de cache.
Pourquoi ne pas utiliser le nom de fichier ? Les noms de fichiers sont des métadonnées, pas une identité. interview.mp3 et interview_final.mp3 peuvent être le même fichier. Deux fichiers différents peuvent porter le même nom. Le hachage du contenu résout correctement ces deux cas.
Hacher de gros fichiers sans les charger en mémoire
Pour des fichiers audio de plusieurs centaines de Mo, charger tout le tampon pour le hacher est du gaspillage. Calculez plutôt le hachage 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);
});
}
Cette approche lit par blocs et met à jour le hachage de manière incrémentale, gardant la mémoire constante quelle que soit la taille du fichier.
Stockage : Postgres pour la persistance, Redis pour le chemin critique
Postgres comme magasin principal
Postgres est le bon choix par défaut. Les transcriptions sont rarement consultées et le stockage est peu coûteux. Le schéma :
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);
Recherche avec suivi des accès en une seule requête :
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;
}
Le motif UPDATE ... RETURNING met à jour l'horodatage d'accès et renvoie la ligne en un seul aller-retour.
Redis comme couche de chemin critique
Si un petit ensemble de transcriptions est consulté très fréquemment (enregistrements récents, contenus partagés populaires), Redis réduit la charge sur Postgres et fait passer les recherches sous la milliseconde :
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;
}
Dans cet exemple, le TTL de Redis est d'une heure. Postgres n'a pas de TTL ; les entrées restent jusqu'à leur éviction explicite. Pour la plupart des pipelines, Postgres seul suffit. N'ajoutez Redis qu'après avoir mesuré que la latence de Postgres est le véritable goulot d'étranglement.
Le schéma complet : insertion protégée contre les conditions de course
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 est déterminant. Deux requêtes simultanées pour un même fichier audio peuvent toutes deux manquer le cache et appeler l'API. La première insertion gagne ; la seconde est abandonnée en silence. Les deux appelants obtiennent leur résultat. Aucun verrou nécessaire, aucune entrée dupliquée.
Les calculs de coût de stockage
C'est ici que l'économie de la mise en cache devient concrète.
Ce que coûte la production d'une transcription. Deepgram Nova-3 pré-enregistré en anglais coûte 0,0043 $ par minute (à la date de juillet 2026, selon deepgram.com/pricing). Une heure d'audio coûte environ 0,26 $. Le gpt-4o-transcribe d'OpenAI coûte 0,006 $ par minute, soit environ 0,36 $ par heure (selon les tarifs de developers.openai.com).
Ce que coûte le stockage d'une transcription. Le texte brut de la transcription d'une heure d'anglais parlé représente environ 48 Ko (soit environ 800 caractères par minute en UTF-8). La réponse JSONB complète de Deepgram, avec horodatages au niveau du mot, énoncés, thèmes et sentiments, pèse entre 500 Ko et 1 Mo par heure, selon le nombre de locuteurs et le niveau de détail. Sur AWS RDS PostgreSQL, le stockage coûte environ 0,115 $ par Go et par mois (selon les pages tarifaires d'AWS, juillet 2026).
Stocker 1 Mo de JSON Deepgram pendant un mois coûte environ 0,000115 $. À 0,26 $ par re-transcription évitée, un seul hit de cache finance le stockage de cette transcription pendant environ 2 200 mois.
La vraie question de rentabilité n'est pas le coût de stockage, mais le temps d'ingénierie. Pour un système qui tourne déjà sous Postgres, le schéma tient en environ 30 lignes de code. Pour un pipeline dépensant 200 $ par mois en transcription avec un taux de re-transcription de 10 %, cela représente 20 $ récupérés par mois, soit environ 240 $ par an pour un après-midi de travail.
Pour d'autres tactiques d'économies côté API, consultez l'optimisation des coûts des appels d'API de transcription et la comparaison générale des prix de la transcription.
Invalidation lors des mises à niveau de modèles
La conception de la clé de cache gère automatiquement les mises à niveau de modèles. Lorsque vous passez de Whisper Large-v3 à un modèle plus récent, le nom du modèle fait partie du hachage des paramètres ; les entrées existantes ne correspondent donc plus. Les nouvelles requêtes génèrent de nouvelles clés et déclenchent de nouvelles transcriptions. Les anciennes entrées deviennent des orphelines qui disparaissent progressivement via votre requête d'éviction fondée sur le dernier accès.
Deux autres scénarios d'invalidation :
Erreurs signalées par les clients. Un utilisateur signale une transcription erronée et souhaite la refaire. Exposez un point de terminaison d'administration :
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]
);
}
Éviction par taille. Sans éviction, les tables de transcriptions grossissent indéfiniment. Si cela devient un problème, purgez par date de dernier accès :
DELETE FROM transcript_cache
WHERE last_accessed < NOW() - INTERVAL '90 days';
Pour la plupart des équipes, les transcriptions sont petites par rapport aux autres données et la table reste gérable pendant des années. Mesurez avant d'évincer.
Mise en cache par segment pour les longs fichiers
Pour des enregistrements très longs (podcasts de plusieurs heures, audio de conférence s'étalant sur une journée), un cache plat manque totalement ou met en cache un blob très volumineux. La mise en cache par segment offre une granularité plus fine :
Découpez l'audio en tranches de 5 minutes. Hachez chaque tranche indépendamment et interrogez le cache tranche par tranche. Ne transcrivez que les segments absents du cache. Cela facilite aussi les flux d'édition : si un utilisateur retéléverse un fichier avec une petite modification, seuls les segments modifiés doivent être re-transcrits.
La mise en cache au niveau du mot est superflue. Le surcoût de stockage par recherche ne se rentabilise pas. Des segments de 5 à 10 minutes constituent la bonne granularité.
Taux de hits du cache par cas d'usage
Plages réalistes issues de pipelines en production :
| Cas d'usage | Taux de hits typique |
|---|---|
| L'utilisateur resoumet le même fichier | 8 à 15 % |
| Réexécutions d'un outil interne | 60 à 80 % |
| Backfill / retraitement par lots | 80 à 95 % |
| SaaS multi-locataires (utilisateurs indépendants) | 0,5 à 3 % |
La première ligne correspond au cas de production typique. Un taux de hits de 10 % sur une facture de transcription de 500 $ par mois économise 50 $ par mois. Le scénario du backfill offre le meilleur retour : si vous devez un jour retraiter un corpus, le cache transforme une redépense potentiellement importante en quasi zéro.
Suivre l'efficacité du cache
Ajoutez des compteurs dès le départ pour savoir si votre cache fonctionne :
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;
}
Surveillez l'évolution du ratio hits/manqués dans le temps. Si le taux de hits chute de façon inattendue, quelque chose a changé : une vague d'invalidation du cache, un nouveau format de fichier qui contourne le hachage, ou une mise à niveau du moteur ayant généré de nouvelles clés pour du contenu existant.
Intégration avec les webhooks
La mise en cache s'intègre proprement aux pipelines de webhooks asynchrones, abordés dans webhook ou polling pour les transcriptions. Le gestionnaire de webhook calcule la clé de cache à partir du hachage de l'audio d'origine et des paramètres d'API, puis écrit dans la table de cache une fois le traitement terminé. Les requêtes ultérieures pour le même audio court-circuitent totalement l'API.
Pour les équipes qui développent des outils internes, la mise en cache est souvent l'optimisation offrant le meilleur retour sur investissement dès que le volume dépasse environ 50 heures par mois, comme expliqué dans la création d'un outil de transcription interne.

Si votre cas d'usage se limite à une transcription audio simple, sans gestion d'une couche de cache, ConvertAudioToText prend en charge la déduplication et le stockage des résultats côté serveur : vous obtenez instantanément les résultats pour les fichiers déjà soumis, sans aucune infrastructure à maintenir.
Questions fréquentes
Faut-il hacher le nom du fichier ou son contenu ?
Hachez le contenu. Les noms de fichiers mentent : une même interview téléversée sous interview.mp3 puis interview_final.mp3 doit aboutir à la même entrée de cache. Deux fichiers différents nommés tous les deux interview.mp3 ne doivent pas entrer en collision. Le SHA-256 des octets audio est le seul signal d'identité fiable.
Que se passe-t-il lorsque je passe à un modèle Whisper ou Deepgram plus récent ?
Les anciennes entrées de cache restent valides mais ne correspondront plus aux nouvelles requêtes. Comme le nom du modèle fait partie du hachage des paramètres dans la clé de cache, toute montée de version génère une clé différente et déclenche une nouvelle transcription. Les anciennes entrées expirent naturellement via votre politique d'éviction fondée sur le dernier accès ; inutile de les supprimer en masse.
Redis est-il nécessaire ou Postgres seul peut-il gérer le cache ?
Postgres seul suffit pour la plupart des pipelines. Les recherches dans le cache sur un index de clé primaire sont rapides. N'ajoutez Redis comme couche de chemin critique que si vous avez mesuré un trafic élevé sur un petit ensemble de transcriptions récemment consultées et que la latence de Postgres est le véritable goulot d'étranglement.
Quel taux de hits du cache dois-je attendre ?
Cela dépend fortement de votre cas d'usage. La resoumission d'un même fichier par un utilisateur (le cas le plus courant) donne généralement des taux de hits de 8 à 15 %. Les tâches de backfill ou de retraitement par lots peuvent atteindre 80 à 95 %. Un SaaS multi-locataires où les utilisateurs téléversent chacun des fichiers sans rapport est le cas le plus difficile : 0,5 à 3 %, ce qui peut ne pas justifier la complexité tant que votre volume de transcription n'est pas élevé.
Sources
- Tarification Deepgram Nova-3 : https://deepgram.com/pricing (vérifiée en juillet 2026)
- Tarification des modèles de transcription OpenAI : https://developers.openai.com/api/docs/pricing (vérifiée en juillet 2026)
- Tarification du stockage AWS RDS PostgreSQL : https://aws.amazon.com/rds/postgresql/pricing/ (consultée en juillet 2026)
- Tarification Upstash Redis : https://upstash.com/pricing/redis (vérifiée en juillet 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.