# MasterMemory compiles your table definitions into query methods at build time

> MasterMemory is a Roslyn source generator plus an embedded read-only document database for .NET and Unity, where the cost of the design is that data is immutable at query time and any schema change is a rebuild; the headline speed numbers in the README are the maintainer's own.

**Cysharp/MasterMemory** — Source Generator based Embedded Typed Readonly In-Memory Document Database for .NET and Unity.

- Repository: https://github.com/Cysharp/MasterMemory
- Stars: 1,840 · Forks: 123
- Language: C#
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/cysharp-mastermemory

## The generator is the product, not the database

MasterMemory describes itself as a source generator based embedded typed read-only in-memory document database for .NET and Unity. Both halves of that sentence matter, and the first half is the unusual one.

You define tables as ordinary C# types marked with an attribute.

```csharp
[MemoryTable("person"), MessagePackObject(true)]
public record Person
{
    [PrimaryKey]
    public required int PersonId { get; init; }

    [SecondaryKey(0), NonUnique]
    [SecondaryKey(1, keyOrder: 1), NonUnique]
    public required int Age { get; init; }

    [SecondaryKey(2), NonUnique]
    [SecondaryKey(1, keyOrder: 0), NonUnique]
    public required Gender Gender { get; init; }

    public required string Name { get; init; }
}
```

The generator reads those attributes at compile time and emits typed code: a table property and one method per key. That is why a query such as FindByPersonId exists at all, and why you get IntelliSense on it. There is no runtime query builder, no string-keyed API and no chance of passing a column name that does not exist.

The storage side follows from the same design. Data is serialized with MessagePack for C#, and the README gives a size comparison of its own: 222kb against SQLite's 3560kb. Whether that ratio holds for your schema is something to measure, but it is consistent with an in-memory dictionary layout plus string interning.

## Index attributes decide which query methods appear

PrimaryKey marks the identity field. SecondaryKey can be applied multiple times to the same property, with an index number and an optional keyOrder, which is how composite keys are expressed.

In the README's example the Age property carries two secondary index attributes, at index 0 and index 1 with keyOrder 1, and Gender carries index 2 and index 1 with keyOrder 0. Combined, Gender and Age form one composite key with Gender first and Age second, while Age alone and Gender alone remain separately searchable.

NonUnique is the other half of the model. A unique key returns one row; a non-unique key returns a RangeView, a range over the matching rows. The README is explicit that the return type differs, so a schema change that adds or removes uniqueness changes your calling code, which the compiler will tell you about.

Three query shapes are documented beyond equality: FindClosestByAge returns the nearest value choosing lower by default or higher, FindRangeByAge returns an inclusive min-max range, and a composite FindByGenderAndAge takes a tuple. Nothing here needs a scan, which is the point of the attributes.

## Building the binary and opening it are separate steps

The runtime API is small enough to read in one sitting. You collect rows, build a byte array, and open a MemoryDatabase over it.

```csharp
var builder = new DatabaseBuilder();
builder.Append(new Person[]
{
    new (){ PersonId = 0, Age = 13, Gender = Gender.Male,   Name = "Dana Terry" },
});

byte[] data = builder.Build();

var db = new MemoryDatabase(data);
```

WriteToStream is the alternative to Build when you want the bytes on disk, which is what you would do for a shipping application that loads master data from a stream rather than embedding it.

The README recommends storing the MemoryDatabase instance in a singleton, either a static field or injected through DI, since constructing it per request would throw away the whole point of loading once. That guidance is the design in one sentence: build the binary at build time or startup, hold it, then query.

The generated surface is typed end to end, because every table marked with MemoryTableAttribute and every method created by PrimaryKeyAttribute or SecondaryKeyAttribute is emitted with the types filled in.

## Unity support starts at a specific version, for a specific reason

The Unity story has a documented floor. The minimum supported version is 2022.3.12f1, and the reason given is concrete: supporting the C# incremental source generator requires compiler version 4.3.0.

Installation goes through NuGetForUnity rather than a Unity Package Manager git URL. You install NuGetForUnity, open the NuGet window, search for MasterMemory and press Install.

The README then asks you to define assembly attributes in any C# file to enable the generator, which is the standard step for getting a Roslyn analyzer to run inside a Unity assembly.

Because the library arrives as NuGet packages rather than a Unity package, the Unity build step is part of your project setup rather than something the project handles for you. That is a real difference from libraries distributed through the Unity asset store, and it is worth checking against your CI before committing to it.

## Read-only is the constraint that shapes everything

The README states plainly that data in MasterMemory is readonly, and recommends an immutable structure. Records and classes are both supported, with records suggested because they generate more readable ToString methods.

That is the whole architecture in one property. There is no insert, no update and no delete. The DatabaseBuilder is a construction-time API that appends rows and serializes them. If you need to change a row after the binary exists, MasterMemory has no answer for you.

So the fit is narrower than an in-memory database sounds. The README names the intended workloads as master data management, which it describes as write-once and read-heavy, on embedded applications, data analysis and games. A user profile, an inventory or an order is not that. A table of item definitions, a monster stat table, a lookup of tax rates or an offline dataset loaded once at startup is.

The feature list adds the pieces that make such tables manageable: custom validation written in C#, metadata for building custom importer or exporter behaviour, an ImmutableBuilder, a RangeView abstraction, and Extend Table support.

## How it differs from an embedded database and from PalDB

The README names PalDB, LinkedIn's embeddable write-once key-value store, as sharing the concept while differing in implementation and performance characteristics. The distinction is worth taking seriously rather than skipping as marketing: PalDB has a similar embeddable write-once key-value-store concept, but its implementation and performance characteristics differ.

Against SQLite, the difference is architectural rather than incremental. MasterMemory documents no runtime write path; its indexed query methods are generated from the table definitions. That buys you dictionary-speed lookups and typed methods, and costs you everything a general database gives you.

The README's headline claim of being 4700 times faster than SQLite and achieving zero allocation per query comes with no benchmark, methodology or dataset attached, so treat it as a claim rather than a measurement. The architectural argument does not depend on it: a lookup against a hash index in memory is simply a different amount of work than a query through a SQL engine.

## Version 3, a documented migration, and MIT

The repository is not archived and the last push was on 2026-10-06. Recent releases are 3.0.4 from 2025-06-12, and 3.0.3 and 3.0.2 both from 2025-01-09, which suggests a settled 3.x line with the last tag several months old.

The README's table of contents includes a v2 to v3 migration section, so the major version bump came with documented breaking changes and anyone upgrading from 2.x has a written path rather than a diff to reverse-engineer.

Licensing is MIT, in the LICENSE file at the repository root, with no LGPL or attribution obligations beyond preserving the notice.

The repository layout is small: src/ holds the library and its generator, tests/ the test suite, sandbox/ the playground, and Directory.Build.props carries the shared MSBuild properties for the solution. There is no external service, no native dependency and no runtime configuration file, which means the deployment story is a NuGet reference and a byte array.

## Conclusion

Adopt MasterMemory when your data is write-once and read-heavy inside a.NET or Unity application: master data such as item or enemy tables, or read-mostly reference data you ship inside the binary. Do not adopt it for anything that writes at runtime, and do not adopt it because of the performance headline: the README's claim of 4700 times faster than SQLite is the maintainer's own number with no benchmark shown, so measure your own queries before it enters a decision. Verify first that your Unity version is 2022.3.12f1 or newer, since the incremental source generator needs C# compiler 4.3.0, that your entity types are MessagePack-serializable, and that MemoryDatabase can live in a singleton for the lifetime of the process. MIT licensing requires preserving its notice and license conditions.

## FAQ

### How do I install MasterMemory in a .NET project?

Install the library and its source generator from NuGet with dotnet add package MasterMemory. The generator ships in the same package as an analyzer, so no separate reference is needed.

### What is the minimum Unity version for MasterMemory?

The minimum supported Unity version is 2022.3.12f1, because the C# incremental source generator requires compiler version 4.3.0. Installation in Unity goes through NuGetForUnity rather than a Unity package.

### Can MasterMemory write or update data at runtime?

No. Data is readonly once built. Rows are collected with DatabaseBuilder and serialised with Build or WriteToStream, then queried through a MemoryDatabase that the README recommends keeping in a singleton.

### How do composite and non-unique keys work in MasterMemory?

SecondaryKey can be applied several times to a property with an index number and keyOrder to build composite keys, and NonUnique marks a key whose queries return a RangeView rather than a single row.

### Is MasterMemory faster than SQLite?

The README claims 4700 times faster than SQLite with zero allocation per query and gives a size comparison of 222kb against 3560kb, but it publishes no benchmark or dataset behind those numbers, so measure your own queries before relying on them.

## Sources

- [Cysharp/MasterMemory on GitHub](https://github.com/Cysharp/MasterMemory)
- [Issues](https://github.com/Cysharp/MasterMemory/issues)
- [License: MIT](https://github.com/Cysharp/MasterMemory/blob/master/LICENSE)
- [README](https://github.com/Cysharp/MasterMemory/blob/master/README.md)
- [Releases](https://github.com/Cysharp/MasterMemory/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cysharp-mastermemory
