---
title: Storage Config
sort: 120
section-id: configuration
keywords: storage, memory, disk, SSD tiers, compression, tablespace, IOPS
description: Configuring NeuralDB storage — memory tiers, disk layout, compression, and tablespaces
language: en
---

# Storage Config

NeuralDB's performance depends heavily on storage configuration. This page covers how to optimise disk layout, configure memory tiers, enable compression, and use tablespaces for data tiering.

## Disk Layout Recommendations

Separate the data directory, WAL, and vector files onto different physical disks (or at least different volumes with guaranteed IOPS):

| Directory | Recommended storage | IOPS requirement |
|-----------|--------------------|--------------------||
| `$DATADIR/base/` | NVMe SSD | High random read/write |
| `$DATADIR/wal/` | NVMe SSD (separate) | High sequential write |
| `$DATADIR/vectors/` | NVMe SSD or RAM disk | High random read |
| `$DATADIR/archive/` | HDD or object storage | Low (sequential write) |

Configure separate paths in `neuraldb.conf`:

```ini
# Separate WAL onto a dedicated volume
wal_directory = '/mnt/nvme-wal/neuraldb/wal'

# Separate vector storage
vector_data_directory = '/mnt/nvme-fast/neuraldb/vectors'

# Archive destination for WAL shipping
archive_status_directory = '/var/lib/neuraldb/archive_status'
```

## Memory Tiers

NeuralDB has three distinct memory pools:

### shared_buffers (Row Store Cache)

The page cache for relational data. Sized at 25% of available RAM for a dedicated database server:

```ini
shared_buffers = 32GB    # for a 128 GB server
```

### vector_buffer (Vector Index Cache)

Holds the HNSW graph in memory. The entire active HNSW graph must fit in `vector_buffer` for optimal performance. When the graph doesn't fit, NeuralDB falls back to disk-based graph traversal, which is 10–50× slower.

Calculate the required size:

```
vector_buffer = num_vectors × dimensions × 4 bytes × hnsw_overhead_factor
```

Where `hnsw_overhead_factor` ≈ 1.3 for default HNSW parameters (m=16).

```sql
-- Check current vector index memory usage
SELECT table_name, index_name,
       pg_size_pretty(hnsw_graph_size_bytes) AS graph_memory,
       pg_size_pretty(vector_data_size_bytes) AS data_size
FROM neuraldb_stat_vector_indexes;
```

### work_mem (Per-Query Buffer)

Used for in-memory sorts, hash joins, and bitmap operations. Set conservatively — each query can allocate multiple `work_mem` buffers:

```ini
# For 200 connections with typical 2 buffers per query:
# Max memory consumption: 200 × 2 × 128MB = 51 GB
work_mem = 128MB
```

Override per-session for analytical queries:

```sql
SET work_mem = '2GB';
SELECT ... complex analytical query ...
```

## Compression

### Row Store Compression

NeuralDB compresses SSTables on disk. Choose the algorithm based on your priorities:

```ini
# Compression algorithm for row data
# 'none': no compression (fastest reads/writes, most disk)
# 'lz4': fast, moderate compression ratio (~2-3×) — default
# 'zstd': slower compression, better ratio (~3-5×)
# 'zstd-9': high compression for archival (slow, ~6-8×)
storage_compression = lz4

# Compression level (for zstd only), 1-19
storage_compression_level = 3
```

### Vector Compression

```ini
# Vector data compression
# 'none': full precision, largest storage
# 'lz4': fast, minimal precision loss
# 'scalar_quantize': reduce to 8-bit (4× smaller, ~1% recall loss)
# 'product_quantize': very high compression, higher recall loss
vector_compression = lz4
```

To enable scalar quantisation for a specific index:

```sql
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (quantization = 'scalar');
```

Scalar-quantised indexes use 4× less memory and may be faster due to better cache utilisation, at a typical recall cost of 0.5–2%.

## Tablespaces

Use tablespaces to store different tables or indexes on different volumes:

```sql
-- Create tablespaces pointing to different mount points
CREATE TABLESPACE fast_ssd LOCATION '/mnt/nvme-fast';
CREATE TABLESPACE bulk_hdd LOCATION '/mnt/hdd-storage';

-- Create a table on fast SSD
CREATE TABLE hot_documents (
  id UUID PRIMARY KEY,
  content TEXT,
  embedding VECTOR(1536)
) TABLESPACE fast_ssd;

-- Move an index to a specific tablespace
CREATE INDEX ON hot_documents USING hnsw (embedding vector_cosine_ops)
TABLESPACE fast_ssd;

-- Move old partitions to cheaper storage
ALTER TABLE documents_2024_q1 SET TABLESPACE bulk_hdd;
```

## Table Partitioning

Partition large tables by time or tenant to improve query performance and manageability:

```sql
-- Partition documents by month
CREATE TABLE documents (
  id UUID NOT NULL DEFAULT gen_random_uuid(),
  content TEXT,
  embedding VECTOR(1536),
  tenant_id UUID,
  created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (created_at);

CREATE TABLE documents_2026_01
  PARTITION OF documents
  FOR VALUES FROM ('2026-01-01') TO ('2026-02-01')
  TABLESPACE fast_ssd;

CREATE TABLE documents_2025_archive
  PARTITION OF documents
  FOR VALUES FROM ('2025-01-01') TO ('2026-01-01')
  TABLESPACE bulk_hdd;
```

## VACUUM and Autovacuum

NeuralDB inherits PostgreSQL's MVCC-based VACUUM system:

```ini
# Autovacuum settings
autovacuum = on
autovacuum_naptime = 1min
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.05    # vacuum when 5% of rows are dead
autovacuum_analyze_threshold = 50
autovacuum_analyze_scale_factor = 0.02   # analyse when 2% of rows change

# For high-write tables, increase autovacuum aggressiveness
autovacuum_vacuum_cost_delay = 2ms       # default: 20ms
autovacuum_vacuum_cost_limit = 400       # default: 200
```

Manually vacuum a table:

```sql
VACUUM ANALYZE documents;         -- reclaim space + update statistics
VACUUM FULL documents;            -- full rewrite (blocking, very thorough)
```

## Monitoring Disk Usage

```sql
-- Table sizes
SELECT table_name,
       pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) AS total,
       pg_size_pretty(pg_relation_size(quote_ident(table_name))) AS table,
       pg_size_pretty(pg_total_relation_size(quote_ident(table_name))
         - pg_relation_size(quote_ident(table_name))) AS indexes
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY pg_total_relation_size(quote_ident(table_name)) DESC;

-- Vector index sizes
SELECT * FROM neuraldb_stat_vector_indexes
ORDER BY hnsw_graph_size_bytes DESC;

-- Bloat estimate
SELECT tablename, n_dead_tup, last_vacuum, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
```
