# How the Mito2 Storage Engine Handles Time-Series Data with Region Workers and Memtables

> Discover how the Mito2 storage engine efficiently processes time-series data using region workers and memtables. Learn about its optimized write operations and fast data buffering.

- Repository: [Greptime/greptimedb](https://github.com/greptimeteam/greptimedb)
- Tags: internals
- Published: 2026-03-02

---

**The Mito2 storage engine assigns each region to a dedicated worker thread that serializes all write operations, buffering incoming time-series rows in a column-oriented TimeSeriesMemtable before flushing to immutable SST files, eliminating cross-thread contention on hot paths.**

The Mito2 engine serves as the default storage backend for the `mito` table type in GreptimeDB, designed specifically for high-throughput time-series workloads. It combines a **region-worker model** with specialized **memtables** to provide lock-free ingestion and efficient columnar buffering. This article examines the source code in `greptimeteam/greptimedb` to explain how these components coordinate writes, flushes, and reads.

## Architecture Overview: Region Workers and Serial Execution

Mito2 distributes workload across a fixed-size pool of **region workers** defined by `MitoConfig::num_region_workers` in [`src/mito2/src/config.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/config.rs). By default, the pool size equals half the available CPU cores. Each worker runs a dedicated Tokio task that listens on an mpsc channel for `WorkerRequest` messages, creating a single-threaded event loop that processes all mutations for its assigned regions.

This design guarantees that only one thread ever modifies a region’s internal state. Because the worker serializes requests, operations on **region metadata** and **memtables** require no locks, removing a major source of contention in high-write scenarios.

## Region Ownership and the Single-Threaded Write Path

A **region** represents a logical partition of a table—essentially a contiguous range of rows sharing the same primary key space. In [`src/mito2/src/region.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/region.rs), the codebase enforces a strict rule: *only the region worker thread this region belongs to can modify the metadata*. This binding lasts for the lifetime of the region.

Each region maintains an in-memory **memtable** that buffers incoming writes before they are committed to disk. Because access is restricted to the owning worker, appends to the memtable are lock-free, allowing the engine to achieve high ingestion rates for time-series data.

## TimeSeriesMemtable Design for Columnar Storage

For time-series tables, Mito2 uses the **TimeSeriesMemtable** implementation located in [`src/mito2/src/memtable/time_series.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/memtable/time_series.rs). The memtable type is selected via the region option `memtable.type = "time_series"` defined in [`src/mito2/src/region/options.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/region/options.rs) (line 584).

Unlike generic row-oriented buffers, the `TimeSeriesMemtable` stores each column as a separate vector, keyed by the timestamp column. This columnar layout maximizes cache locality during bulk appends and enables efficient compression algorithms before data is frozen into SST files. The implementation also registers the `time_series_memtable` metric (line 612) to track buffer utilization.

## Write Path: Ingestion to Persistence

The write flow demonstrates how region workers and memtables cooperate:

1. **Request Routing**: A SQL `INSERT` generates a `SenderWriteRequest` that is routed to the appropriate worker via `WorkerRequest::Write` (handled in [`src/mito2/src/worker/handle_write.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/worker/handle_write.rs), line 43).
2. **Batching**: The worker calls `handle_write_requests`, which groups rows and forwards them to the region’s memtable.
3. **Buffering**: Because the region uses `memtable.type = "time_series"`, a `TimeSeriesMemtable` instance stores each column in contiguous vectors and updates the `time_series_memtable` metric.
4. **Flush Trigger**: When the memtable exceeds its size threshold or the `CHECK_REGION_INTERVAL` timer fires (defined in [`src/mito2/src/worker.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/worker.rs), line 94), the worker invokes the `FlushScheduler` from [`src/mito2/src/flush.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/flush.rs).
5. **Persistence**: The flush writes the memtable contents to a new SST file, updates the region manifest, and clears the buffer—all without interrupting the single-threaded event loop.

## Read Path: Direct Scans Without Worker Coordination

Reads bypass the region workers entirely to avoid blocking ingestion. The `Engine::read` implementation (in [`src/mito2/src/engine.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/engine.rs)) opens the target region and performs a parallel scan across on-disk SST files and the current `TimeSeriesMemtable` (if active). Because the memtable is column-oriented, time-range filters and aggregations execute with minimal CPU overhead and optimal cache usage.

## Background Maintenance: Flush, Compaction, and GC

The same worker thread that handles writes also schedules background maintenance, ensuring strict ordering between mutations and housekeeping:

- **FlushScheduler** ([`src/mito2/src/flush.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/flush.rs)): Converts full memtables to SST files.
- **CompactionScheduler** ([`src/mito2/src/compaction.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/compaction.rs)): Merges multiple SST files into larger, sorted files to improve read efficiency.
- **GcLimiter** ([`src/mito2/src/gc.rs`](https://github.com/greptimeteam/greptimedb/blob/main/src/mito2/src/gc.rs)): Removes obsolete SST files after compaction via the `LocalGcWorker`.

Because these operations run on the worker thread, they cannot race with ongoing writes to the same region.

## Summary

- **Region workers** provide a single-threaded execution context per region, eliminating lock contention on metadata and memtables.
- **TimeSeriesMemtables** store data in columnar vectors optimized for time-series patterns, configured via `memtable.type = 'time_series'`.
- The write path serializes requests through `handle_write_requests`, buffering in memtables until a flush is triggered.
- Reads bypass workers entirely, scanning SST files and memtables directly for low-latency queries.
- Flush, compaction, and garbage collection are coordinated by the worker thread to maintain consistency.

## Frequently Asked Questions

### What is the difference between region workers and generic thread pools in Mito2?

Region workers are specialized, long-lived Tokio tasks that own specific regions for their entire lifetime. Unlike generic thread pools that dispatch tasks to any available thread, a region worker guarantees that all writes, flushes, and compactions for its assigned regions execute on the same thread, preserving order and avoiding locks.

### How does TimeSeriesMemtable optimize storage for time-series workloads?

The `TimeSeriesMemtable` stores each column as a separate contiguous vector rather than as rows, which aligns with time-series access patterns where queries typically scan ranges of timestamps across specific metrics. This columnar layout improves CPU cache hit rates and allows vectorized compression before data is written to SST files.

### When does Mito2 trigger a flush from memtable to SST?

A flush occurs when the active memtable reaches the configured size limit or when the periodic `CHECK_REGION_INTERVAL` timer expires, whichever comes first. The worker thread evaluates these conditions in its main loop and invokes the `FlushScheduler` to write the immutable memtable to a new SST file.

### Can read operations block write ingestion in the Mito2 storage engine?

No. Reads operate on a snapshot of the region’s SST files and memtables without acquiring locks or sending requests to the region worker. This design ensures that heavy analytical queries cannot stall the single-threaded write path, maintaining consistent ingestion throughput.