Les lectures par lots ont besoin d’un contrat de résultat

Remplacer une boucle de requêtes par clé primaire par une seule requête ANY supprime des allers-retours. Cela peut aussi modifier la forme de la réponse. Un appelant peut demander [42, 7, 42, NULL, -1] et attendre cinq positions de résultat. La sémantique des ensembles SQL ne promet pas cet alignement.

Un ensemble de lignes n’est pas une liste de réponses

Avec WHERE id = ANY($1::bigint[]), un ID dupliqué correspond normalement une seule fois à sa ligne de table. Un ID absent ne contribue à aucune ligne. Une entrée NULL ne correspond pas à une clé primaire non nulle, et le résultat ne garantit pas l’ordre d’entrée. Ajouter ORDER BY id trie par clé ; cela ne reproduit toujours pas les positions demandées.

Si les consommateurs ont besoin d’un ensemble, cela convient. S’ils ont besoin d’un résultat pour chaque entrée, faites des positions une partie de la requête ou restaurez-les dans l’application.

Garder les positions explicites en SQL

Démarrez la démo locale et exécutez ceci dans sa session psql :

WITH requested AS (
  SELECT key, position
  FROM unnest(ARRAY[42, 7, 42, NULL, -1]::bigint[])
       WITH ORDINALITY AS input(key, position)
)
SELECT requested.position,
       requested.key,
       CASE WHEN items.id IS NULL THEN NULL
            ELSE row_to_json(items)::text END AS row
FROM requested
LEFT JOIN public.items AS items ON items.id = requested.key
ORDER BY requested.position;

La colonne d’ordinalité distingue les deux occurrences de 42. La jointure externe gauche conserve les cinq positions, y compris l’entrée nulle et toute clé absente. Pour une clé manquante, row vaut NULL SQL. Dans le code applicatif, passez le tableau comme paramètre plutôt que de concaténer les ID dans le SQL. L’exemple Node.js montre l’alignement côté client.

Comparer l’API de lignes complètes

Sur une table attachée, l’appel de cache explicite correspondant est :

SELECT local_cache.mget(
  'public.items'::regclass,
  ARRAY[42, 7, 42, NULL, -1]::bigint[]
) AS rows;

Il renvoie text[], en conservant l’ordre d’entrée et les doublons. Les clés absentes et les entrées nulles produisent des éléments NULL SQL alignés ; chaque élément présent est une ligne complète sérialisée. Un miss ou un contournement du cache lit PostgreSQL. L’API accepte au plus 1 024 clés par appel. Elle ne remplace ni les projections, ni les jointures, ni les verrous de lignes, ni la mise en cache arbitraire des résultats de requêtes.

Garder les lots bornés et observables

Au-delà de 1 024 clés, divisez explicitement les requêtes ou gardez une requête SQL ordinaire. Découper entre plusieurs instructions peut observer des snapshots READ COMMITTED différents ; choisissez délibérément la sémantique transactionnelle. Les lots plus grands augmentent aussi la taille de la réponse et le travail de décodage du client ; « moins de requêtes » ne prouve pas à lui seul une requête plus rapide.

Pour GraphQL, une fonction de lot DataLoader doit renvoyer une réponse par clé d’entrée dans le même ordre. La mémoïsation locale à la requête et le cache partagé de PostgreSQL sont des couches séparées ; supprimez les entrées concernées après les mutations. Consultez le guide complet des lots et comparez la latence, les charges utiles et le débit avec le runner de benchmarks.