Composite Storage: Optimize for Performance, Scale, and Cost with MastraCompositeStore

You can now use multiple storage providers in a single Mastra project, all configured from one place.

Paul ScanlonPaul Scanlon·

Jan 14, 2026

·

4 min read

Mastra organizes storage into five specialized domains, each responsible for one type of data: memory, workflows, scores, observability, and agents. Mastra now supports configuring different storage providers per domain directly on the main Mastra instance using MastraCompositeStore. We call this approach Composite Storage.

With composite storage, each domain can now use a different storage provider, giving you flexibility to match the storage to the type of data. For example, you might use Redis for fast access to conversational memory, PostgreSQL for workflow state and structured scores, and a specialized analytics engine like ClickHouse for observability data.

The following example configures memory, workflows, scores, and observability to use different storage providers using the latest Mastra 1.0 packages.

 1// src/mastra/index.ts
 2
 3import { Mastra } from "@mastra/core";
 4import { MastraCompositeStore } from "@mastra/core/storage";
 5import { UpstashStore } from "@mastra/upstash";
 6import { WorkflowsPG, ScoresPG } from "@mastra/pg";
 7import { ObservabilityStorageClickhouse } from "@mastra/clickhouse";
 8
 9export const mastra = new Mastra({
10  storage: new MastraCompositeStore({
11    id: "composite",
12    domains: {
13      memory: new UpstashStore({
14        id: "upstash-memory",
15        url: process.env.UPSTASH_REDIS_REST_URL,
16        token: process.env.UPSTASH_REDIS_REST_TOKEN,
17      }),
18
19      workflows: new WorkflowsPG({
20        connectionString: process.env.WORKFLOW_DATABASE_URL,
21      }),
22
23      scores: new ScoresPG({
24        connectionString: process.env.SCORES_DATABASE_URL,
25      }),
26
27      observability: new ObservabilityStorageClickhouse({
28        url: process.env.CLICKHOUSE_URL,
29        username: process.env.CLICKHOUSE_USERNAME,
30        password: process.env.CLICKHOUSE_PASSWORD,
31      }),
32    },
33  }),
34});

See the Composite Storage docs for full configuration options, or browse the supported storage providers.

Incremental adoption with a default store

Composite storage doesn’t require configuring every domain upfront. A default storage provider can be used, with only specific domains overridden as needed. This allows composition to be adopted incrementally without affecting existing setups.

 1// src/mastra/index.ts
 2
 3import { Mastra } from "@mastra/core";
 4import { MastraCompositeStore } from "@mastra/core/storage";
 5import { PostgresStore } from "@mastra/pg";
 6
 7export const mastra = new Mastra({
 8  storage: new MastraCompositeStore({
 9    id: "composite",
10    default: new PostgresStore({
11      id: "pg-default",
12      connectionString: process.env.DATABASE_URL,
13    }),
14    domains: {
15     // ...
16    },
17  }),
18});

When to use composite storage

Composite storage is most useful when different types of data have different performance, scaling, or operational requirements. When memory, workflows, scores, or observability place competing demands on a single database, configuring storage per domain allows each workload to use the most appropriate solution.

Why domains exist

Different types of data workloads place very different demands on storage, and some of those demands sit directly on the user’s critical path. For instance, memory and workflow snapshots are transferred during interactions, where low latency is important to avoid impacting user experience. Observability data, on the other hand, looks very different. Traces are generated continuously at high volume in the background, outside the user’s interaction path, while queries usually focus on a small, recent slice.

These differences are why Mastra models storage as separate domains. Isolating workloads by domain allows each type of data to use a storage provider that matches its latency, throughput, and scaling requirements.

Regional placement and data locality

Composite storage makes it possible to store data where it works best.

Latency-sensitive domains like memory and workflow state can be stored close to where requests are handled, while high-volume domains like observability can be stored in regions optimized for throughput and cost. This separation reduces user-visible latency, helps control infrastructure spend, and avoids forcing all data into a single regional setup.

What didn’t change?

Composite storage doesn’t change how agents, workflows, or memory are used in application code. Existing single-store setups continue to work as before, and storage operations across domains run in parallel and don’t block user interactions.

Breaking changes

Starting in @mastra/core@1.0.0-beta.16, storage.supports was removed and StorageSupports is no longer exported from @mastra/core/storage. All storage adapters now expose a uniform feature surface.

To check whether a specific storage domain is available, use .getStore() instead:

 1import { mastra } from "./mastra";
 2
 3const storage = mastra.getStorage();
 4
 5const memory = await storage.getStore("memory");
 6console.log(memory);
 7
 8const observability = await storage.getStore("observability");
 9console.log(observability);

Get started with composite storage

MastraCompositeStore is available now in Mastra 1.0. If you’re already using Mastra, you can adopt incrementally by introducing a default store and overriding domains as needed. To try it out, update to the latest packages and follow the Composite Storage documentation to configure domains and supported storage providers.

Share:
Paul Scanlon
Paul ScanlonTechnical Product Marketing Manager

Paul Scanlon sits between Developer Education and Product Marketing at Mastra. Previously, he was a Technical Product Marketing Manager at Neon and worked in Developer Relations at Gatsby, where he created educational content and developer experiences.

All articles by Paul Scanlon