본문으로 건너뛰기

Google Cloud Spanner 스토리지

Google Cloud Spanner 스토리지 구현은 Mastra를 위한 수평적 고용량의 강력하고 일관된 스토리지 백엔드를 제공합니다. Cloud Spanner의 GoogleSQL 언어를 대상으로 합니다.

설치
설치에 대한 직접 링크

npm install @mastra/spanner@latest

용법
용법에 대한 직접 링크

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!,
})

인스턴스와 데이터베이스가 이미 존재해야 합니다. 어댑터는 처음 사용할 때 필요한 테이블을 생성하므로 Spanner 클라이언트에 제공된 사용자 인증 정보에는 스키마 변경을 실행할 권한이 있어야 합니다. 또는 배포 단계에서 권한이 더 높은 사용자 인증 정보로 storage.init()을 한 번 실행하세요.

매개변수
매개변수에 대한 직접 링크

id:

string
이 저장소 인스턴스의 고유 식별자입니다.

projectId?:

string
Google Cloud 프로젝트 ID입니다. database를 제공하지 않는 경우 필수입니다.

instanceId?:

string
Cloud Spanner 인스턴스 ID입니다. database를 제공하지 않는 경우 필수입니다.

databaseId?:

string
Cloud Spanner 데이터베이스 ID입니다. database를 제공하지 않는 경우 필수입니다.

database?:

@google-cloud/spanner Database
미리 구성된 Spanner Database 핸들입니다. 다른 위치에서 Spanner 클라이언트를 관리하는 경우(예: 서비스 간 인증 또는 연결 옵션 공유) 사용합니다.

spannerOptions?:

object
@google-cloud/spanner 클라이언트 생성자에 전달되는 옵션입니다. 자격 증명이나 사용자 지정 엔드포인트를 설정하거나 로컬 에뮬레이터를 지정할 때 사용합니다.

disableInit?:

boolean
= false
true이면 처음 사용할 때 테이블을 자동으로 생성하지 않습니다. 별도의 배포 단계에서 storage.init()을 명시적으로 호출해야 합니다.

skipDefaultIndexes?:

boolean
= false
true이면 초기화 중 기본 인덱스 생성을 건너뜁니다.

indexes?:

CreateIndexOptions[]
생성할 사용자 지정 보조 인덱스입니다. 각 인덱스에는 해당 인덱스가 속한 테이블을 지정해야 합니다. 인덱스는 테이블 이름을 기준으로 적절한 도메인에 라우팅됩니다.

initMode?:

'sync' | 'validate'
= 'sync'
스키마 초기화 동작을 제어합니다. 'sync'init() 중 누락된 테이블, 열, 인덱스를 생성합니다(기존 동작). 'validate'는 DDL을 실행하지 않고 필요한 모든 테이블, 열, 기본/사용자 지정 인덱스가 이미 존재하는지 확인하며, 누락된 항목이 있으면 형식이 지정된 사용자 오류를 발생시킵니다. 외부 프로세스(Terraform, Liquibase, 릴리스 파이프라인 등)가 스키마를 관리하고 Mastra는 검증만 수행해야 할 때 유용합니다.

생성자 예
생성자 예에 대한 직접 링크

SpannerStore는 여러 방법으로 인스턴스화할 수 있습니다.

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 },
})

추가 참고사항
추가 참고사항에 대한 직접 링크

스키마 관리
스키마 관리에 대한 직접 링크

스토리지 어댑터는 모두 GoogleSQL 언어를 사용하여 다음 테이블을 생성합니다.

  • mastra_workflow_snapshot: Workflow 상태 및 실행 데이터
  • mastra_threads: 대화 스레드
  • mastra_messages: 개별 메시지
  • mastra_resources: 리소스 작업 Memory
  • mastra_scorers: 평가 점수
  • mastra_background_tasks: 백그라운드 Tool 실행 상태
  • mastra_agents: 간소화된 Agent 레코드(ID, 상태, 활성 버전)
  • mastra_agent_versions: 버전이 지정된 Agent 구성 스냅샷
  • mastra_mcp_clients / mastra_mcp_client_versions: MCP 클라이언트 구성 및 버전 기록
  • mastra_mcp_servers / mastra_mcp_server_versions: MCP 서버 구성 및 버전 기록
  • mastra_skills / mastra_skill_versions: Skill 레코드 및 버전이 지정된 Skill 스냅샷(지침, 참조, 스크립트, 에셋, 콘텐츠 트리)
  • mastra_skill_blobs: SHA-256 해시를 키로 사용하며 Skill 버전 콘텐츠에 사용되는 콘텐츠 주소 지정 가능 Blob 저장소
  • mastra_prompt_blocks / mastra_prompt_block_versions: Prompt 블록 레코드 및 버전이 지정된 콘텐츠 스냅샷(템플릿 콘텐츠, 규칙, 요청 컨텍스트 스키마)
  • mastra_scorer_definitions / mastra_scorer_definition_versions: 채점기 정의 레코드 및 버전이 지정된 구성 스냅샷(판정 지침, Model, 점수 범위, 사전 설정 구성, 기본 샘플링)
  • mastra_schedules / mastra_schedule_triggers: Mastra에 내장된 WorkflowScheduler가 사용하는 cron 기반 Workflow 일정 및 트리거 기록
  • mastra_workspaces / mastra_workspace_versions: Workspace 레코드 및 버전이 지정된 구성 스냅샷(파일 시스템, Sandbox, 마운트, 검색, Skill, Tool)
  • mastra_datasets / mastra_dataset_items / mastra_dataset_versions: 평가 데이터 세트, SCD-2 버전 관리 항목 및 버전 스냅샷
  • mastra_experiments / mastra_experiment_results: 실험 실행 및 항목별 결과
  • mastra_favorites: 비정규화된 Agent 및 Skill에 대한 사용자별 즐겨찾기이며, 상위 레코드에서 favoriteCount가 유지됨
  • mastra_channel_installations / mastra_channel_config: 다중 플랫폼 채널 설치 및 플랫폼별 구성
  • mastra_ai_spans: Observability를 위한 AI Trace 범위(Studio Trace UI를 구동하는 데 사용되는 Trace별 및 범위별 레코드) 테이블은 텍스트와 JSON 페이로드에 STRING(MAX)를 사용하고, 그 밖에 INT64, FLOAT64, BOOL, TIMESTAMP를 사용하여 생성됩니다. 다음 Spanner 관련 테이블에는 어댑터가 JSON 페이로드에서 채우는 STORED 생성 열이 포함되어 있어, 일반적인 필터에서 JSON_VALUE 스캔 대신 일반 보조 인덱스를 사용할 수 있습니다.
  • mastra_workflow_snapshot.snapshotStatus: snapshot에서 $.status를 추출합니다. listWorkflowRuns({ status })를 지원합니다.
  • mastra_schedules.target_workflow_id: target에서 $.workflowId를 추출합니다. listSchedules({ workflowId })를 지원합니다. 두 열 모두 init()ALTER TABLE ... ADD COLUMN IF NOT EXISTS를 통해 추가되며, 스키마를 외부에서 관리하는 initMode: 'validate'에서는 건너뜁니다. 열이 없으면 어댑터는 런타임에 JSON_VALUE 필터로 대체합니다. 어댑터는 스키마를 생성하거나 사용하지 않습니다. 격리를 위해 전용 데이터베이스를 사용합니다.

초기화
초기화에 대한 직접 링크

저장소를 Mastra 클래스에 전달하면 저장소 작업 전에 init()이 자동으로 호출됩니다.

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
})

스토리지를 직접 사용하는 경우 첫 번째 작업 전에 init()을 한 번 호출하세요. Spanner는 동시 스키마 변경을 허용하지 않으므로 SpannerStore.init()은 각 도메인의 설정을 순차적으로 실행합니다.

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: '...' })
경고

init()을 호출하지 않고 disableInit이 true이면 필요한 테이블이 존재하지 않아 저장소 작업이 실패합니다.

GoogleSQL 세부사항
GoogleSQL 세부사항에 대한 직접 링크

몇 가지 동작이 다른 관계형 어댑터와 다릅니다.

  • Upsert는 INSERT OR UPDATE를 사용합니다. Spanner는 upsert에 RETURNING 절을 제공하지 않으므로 쓰기 후 상태가 필요한 호출자는 이를 다시 읽어야 합니다.
  • TRUNCATE는 존재하지 않습니다. dangerouslyClearAll()DELETE WHERE TRUE를 실행합니다.
  • 식별자는 백틱으로 묶습니다.
  • DDL은 비동기 장기 실행 작업인 database.updateSchema(...)를 통해 적용됩니다.
  • NULLS FIRST/LAST는 지원되지 않습니다. NULL 처리가 포함된 정렬은 IS NULL 정렬 키를 통해 에뮬레이션됩니다.
  • JSON 포함 연산은 기본적으로 지원되지 않습니다. listTracesmetadatascope 필터는 키별 JSON_VALUE(...) = @v 동등성 검사로 컴파일되고, tags 필터는 JSON_QUERY_ARRAY(...)에 대한 EXISTS로 컴파일됩니다. 이는 단일 인덱스 스캔에서 중첩 구조를 일치시킬 수 있는 Postgres의 @> 포함 연산자와 다릅니다. 대부분의 일회성 조회는 계속 작동하지만 깊이 중첩된 구조 일치는 표현할 수 없습니다.

직접 데이터베이스 액세스
직접 데이터베이스 액세스에 대한 직접 링크

SpannerStore기본 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)

이는 맞춤형 트랜잭션이나 스키마 검사와 같은 고급 시나리오를 위한 것입니다. 데이터베이스를 직접 재사용하는 경우 어댑터의 유효성 검사 및 JSON 변환 논리를 우회합니다.

에뮬레이터를 사용한 로컬 개발
에뮬레이터를 사용한 로컬 개발에 대한 직접 링크

Docker를 사용하여 Cloud Spanner 에뮬레이터를 로컬에서 실행합니다.

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

SPANNER_EMULATOR_HOST=localhost:9010을 설정하고 앱을 실행하기 전에 인스턴스와 데이터베이스를 생성하세요.

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

그런 다음 Node.js 프로세스에 동일한 환경 변수를 설정하여 연결하세요. @google-cloud/spanner 클라이언트가 에뮬레이터를 자동으로 감지합니다.