Aller au contenu principal

Stockage Google Cloud Spanner

L'implémentation du stockage Google Cloud Spanner fournit à Mastra un backend de stockage à forte capacité, évolutif horizontalement et fortement cohérent. Elle cible le dialecte GoogleSQL de Cloud Spanner.

Installation
Lien direct vers Installation

npm install @mastra/spanner@latest

Utilisation
Lien direct vers Utilisation

import { SpannerStore } from '@mastra/spanner'

const storage = new SpannerStore({
id: 'spanner-storage',
projectId: process.env.SPANNER_PROJECT_ID!,
instanceId: process.env.SPANNER_INSTANCE_ID!,
databaseId: process.env.SPANNER_DATABASE_ID!,
})

L'instance et la base de données doivent déjà exister. L'adaptateur crée les tables requises lors de la première utilisation ; les identifiants fournis au client Spanner doivent donc autoriser les modifications du schéma (vous pouvez aussi exécuter storage.init() une fois pendant une étape de déploiement avec des identifiants disposant de privilèges élevés).

Paramètres
Lien direct vers Paramètres

id:

string
Identifiant unique de cette instance de stockage.

projectId?:

string
ID du projet Google Cloud. Requis sauf si database est fourni.

instanceId?:

string
ID de l’instance Cloud Spanner. Requis sauf si database est fourni.

databaseId?:

string
ID de la base de données Cloud Spanner. Requis sauf si database est fourni.

database?:

@google-cloud/spanner Database
Handle Spanner Database préconfiguré. Utilisez-le lorsque vous gérez le client Spanner ailleurs (par exemple, pour partager des options d’authentification ou de connexion entre plusieurs services).

spannerOptions?:

object
Options transmises au constructeur du client @google-cloud/spanner. Utilisez-les pour définir les identifiants, des points de terminaison personnalisés ou l’émulateur local.

disableInit?:

boolean
= false
Lorsque cette valeur est true, ignore la création automatique des tables lors de la première utilisation. Vous devez appeler explicitement storage.init() pendant une étape de déploiement distincte.

skipDefaultIndexes?:

boolean
= false
Lorsque cette valeur est true, ignore la création des index par défaut pendant l’initialisation.

indexes?:

CreateIndexOptions[]
Index secondaires personnalisés à créer. Chaque index doit indiquer la table à laquelle il appartient. Les index sont acheminés vers le domaine approprié selon le nom de la table.

initMode?:

'sync' | 'validate'
= 'sync'
Contrôle le comportement d’initialisation du schéma. 'sync' crée les tables, colonnes et index manquants pendant init() (comportement historique). 'validate' n’émet aucune instruction DDL et vérifie à la place que toutes les tables, colonnes et tous les index par défaut ou personnalisés attendus existent déjà. Une erreur utilisateur typée est levée si un élément manque. Cette option est utile lorsqu’un processus externe (Terraform, Liquibase, pipeline de publication, etc.) gère le schéma et que Mastra doit uniquement le vérifier.

Exemples de constructeurs
Lien direct vers Exemples de constructeurs

Vous pouvez instancier SpannerStore de plusieurs manières :

import { Spanner } from '@google-cloud/spanner'
import { SpannerStore } from '@mastra/spanner'

// Using projectId / instanceId / databaseId
const store1 = new SpannerStore({
id: 'spanner-storage-1',
projectId: 'my-gcp-project',
instanceId: 'my-instance',
databaseId: 'mastra',
})

// Reusing an existing Spanner Database handle
const spanner = new Spanner({ projectId: 'my-gcp-project' })
const database = spanner.instance('my-instance').database('mastra')

const store2 = new SpannerStore({
id: 'spanner-storage-2',
database,
})

// Using the local Spanner emulator (set the SPANNER_EMULATOR_HOST env var)
process.env.SPANNER_EMULATOR_HOST = 'localhost:9010'
const store3 = new SpannerStore({
id: 'spanner-storage-emulator',
projectId: 'test-project',
instanceId: 'test-instance',
databaseId: 'test-db',
spannerOptions: { servicePath: 'localhost', port: 9010, sslCreds: undefined },
})

Remarques complémentaires
Lien direct vers Remarques complémentaires

Gestion du schéma
Lien direct vers Gestion du schéma

L'adaptateur de stockage crée les tables suivantes, toutes à l'aide du dialecte GoogleSQL :

  • mastra_workflow_snapshot : état du workflow et données d'exécution
  • mastra_threads : threads de conversation
  • mastra_messages : messages individuels
  • mastra_resources : mémoire de travail des ressources
  • mastra_scorers : scores d'évaluation
  • mastra_background_tasks : état d'exécution des Tools en arrière-plan
  • mastra_agents : enregistrements légers des agents (ID, état, version active)
  • mastra_agent_versions : instantanés versionnés de la configuration des agents
  • mastra_mcp_clients / mastra_mcp_client_versions : configurations des clients MCP et historique de leurs versions
  • mastra_mcp_servers / mastra_mcp_server_versions : configurations des serveurs MCP et historique de leurs versions
  • mastra_skills / mastra_skill_versions : enregistrements de Skills et instantanés versionnés des Skills (instructions, références, scripts, ressources, arborescence du contenu)
  • mastra_skill_blobs : stockage de blobs adressables par contenu, indexé par hachage SHA-256 et utilisé pour le contenu des versions de Skills
  • mastra_prompt_blocks / mastra_prompt_block_versions : enregistrements de blocs de prompt et instantanés versionnés du contenu (contenu du modèle, règles, schéma Request Context)
  • mastra_scorer_definitions / mastra_scorer_definition_versions : enregistrements de définitions de Scorers et instantanés versionnés de la configuration (instructions du juge, modèle, plage de scores, configuration prédéfinie, échantillonnage par défaut)
  • mastra_schedules / mastra_schedule_triggers : planifications de workflows pilotées par cron et historique des déclenchements, utilisés par le WorkflowScheduler intégré de Mastra
  • mastra_workspaces / mastra_workspace_versions : enregistrements de Workspaces et instantanés versionnés de leur configuration (système de fichiers, Sandbox, montages, recherche, Skills, Tools)
  • mastra_datasets / mastra_dataset_items / mastra_dataset_versions : jeux de données d'évaluation, éléments versionnés selon SCD-2 et instantanés de versions
  • mastra_experiments / mastra_experiment_results : exécutions d'expériences et résultats de chaque élément
  • mastra_favorites : favoris par utilisateur pour les agents et les Skills, avec le champ favoriteCount dénormalisé conservé dans l'enregistrement parent
  • mastra_channel_installations / mastra_channel_config : installations de canaux multiplateformes et configuration propre à chaque plateforme
  • mastra_ai_spans : spans de traçage de l'IA pour l'observabilité (enregistrements par trace et par span, utilisés pour alimenter l'interface des traces de Studio)

Les tables sont créées avec STRING(MAX) pour le texte et les payloads JSON, ainsi qu'avec INT64, FLOAT64, BOOL et TIMESTAMP.

Les tables suivantes contiennent des colonnes générées STORED propres à Spanner. L'adaptateur les renseigne à partir des payloads JSON afin que les filtres courants puissent utiliser un index secondaire standard plutôt qu'une analyse JSON_VALUE :

  • mastra_workflow_snapshot.snapshotStatus : extrait $.status de snapshot. Prend en charge listWorkflowRuns({ status }).
  • mastra_schedules.target_workflow_id : extrait $.workflowId de target. Prend en charge listSchedules({ workflowId }).

Ces deux colonnes sont ajoutées avec ALTER TABLE ... ADD COLUMN IF NOT EXISTS pendant init() et ignorées lorsque initMode: 'validate' est utilisé (le schéma est alors géré en externe). Si la colonne est absente, l'adaptateur utilise à l'exécution un filtre JSON_VALUE comme solution de repli.

L'adaptateur ne crée ni n'utilise de schémas. Utilisez une base de données dédiée pour garantir l'isolation.

Initialisation
Lien direct vers Initialisation

Lorsque vous transmettez le stockage à la classe Mastra, init() est appelée automatiquement avant toute opération de stockage :

import { Mastra } from '@mastra/core'
import { SpannerStore } from '@mastra/spanner'

const storage = new SpannerStore({
id: 'spanner-storage',
projectId: process.env.SPANNER_PROJECT_ID!,
instanceId: process.env.SPANNER_INSTANCE_ID!,
databaseId: process.env.SPANNER_DATABASE_ID!,
})

const mastra = new Mastra({
storage, // init() is called automatically
})

Si vous utilisez directement le stockage, appelez init() une fois avant la première opération. Spanner n'autorise pas les modifications simultanées du schéma ; SpannerStore.init() exécute donc séquentiellement la configuration de chaque domaine.

const storage = new SpannerStore({
id: 'spanner-storage',
projectId: process.env.SPANNER_PROJECT_ID!,
instanceId: process.env.SPANNER_INSTANCE_ID!,
databaseId: process.env.SPANNER_DATABASE_ID!,
})

await storage.init()
const memory = await storage.getStore('memory')
const thread = await memory?.getThreadById({ threadId: '...' })
attention

Si init() n'est pas appelée et que disableInit vaut true, les tables requises n'existeront pas et les opérations de stockage échoueront.

Particularités de GoogleSQL
Lien direct vers Particularités de GoogleSQL

Certains comportements diffèrent de ceux des autres adaptateurs relationnels :

  • Les upserts utilisent INSERT OR UPDATE. Spanner ne fournit aucune clause RETURNING pour les upserts ; les appelants qui ont besoin de l'état après l'écriture doivent donc le relire.
  • Il n'existe pas de commande TRUNCATE. dangerouslyClearAll() émet DELETE WHERE TRUE.
  • Les identifiants sont placés entre des accents graves.
  • Les instructions DDL sont appliquées avec database.updateSchema(...), qui est asynchrone (opération de longue durée).
  • NULLS FIRST/LAST n'est pas pris en charge. Le tri avec gestion des valeurs NULL est émulé à l'aide d'une clé de tri IS NULL.
  • La contenance JSON n'est pas prise en charge nativement. Dans listTraces, les filtres metadata et scope sont compilés en contrôles d'égalité JSON_VALUE(...) = @v par clé, tandis que les filtres tags sont compilés en EXISTS sur JSON_QUERY_ARRAY(...). Ce comportement diffère de l'opérateur de contenance @> de Postgres, qui peut faire correspondre une structure imbriquée en une seule analyse d'index : la plupart des recherches ponctuelles fonctionnent toujours, mais les correspondances structurelles profondément imbriquées ne peuvent pas être exprimées.

Accès direct à la base de données
Lien direct vers Accès direct à la base de données

SpannerStore expose les objets sous-jacents du client Spanner :

store.database // @google-cloud/spanner Database
store.instance // @google-cloud/spanner Instance (when created internally)
store.spanner // @google-cloud/spanner Spanner client (when created internally)

Ces objets sont destinés aux scénarios avancés, comme les transactions personnalisées ou l'introspection du schéma. Lorsque vous réutilisez directement la base de données, vous contournez la logique de validation et de conversion JSON de l'adaptateur.

Développement local avec l'émulateur
Lien direct vers Développement local avec l'émulateur

Exécutez localement l'émulateur Cloud Spanner avec Docker :

docker run -p 9010:9010 -p 9020:9020 gcr.io/cloud-spanner-emulator/emulator

Définissez SPANNER_EMULATOR_HOST=localhost:9010, puis créez l'instance et la base de données avant d'exécuter votre application :

gcloud spanner instances create test-instance --config=emulator-config --nodes=1
gcloud spanner databases create test-db --instance=test-instance

Connectez-vous ensuite en définissant la même variable d'environnement dans votre processus Node.js. Le client @google-cloud/spanner détecte automatiquement l'émulateur.