Configure MariaDB ColumnStore for high-performance analytics workloads

Advanced 45 min Sep 19, 2026
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Deploy and tune MariaDB ColumnStore for columnar analytics, covering single-node and multi-node cluster setup, table design, memory tuning, bulk loading with cpimport, and cluster health monitoring.

Prerequisites

  • Root or sudo access on all cluster nodes
  • Minimum 4 CPU cores and 16GB RAM per node for production workloads
  • Network connectivity between nodes on ports 3306 and 8640
  • Basic familiarity with SQL and MariaDB administration
  • S3-compatible object storage for multi-node shared DBRoot setups (optional)

What this solves

MariaDB ColumnStore is a columnar storage engine built for OLAP workloads: aggregations, scans and joins over billions of rows. This tutorial covers installing ColumnStore, building single-node and multi-node clusters, designing columnar tables, tuning memory and query execution, bulk loading with cpimport, and monitoring cluster health in production.

Note: ColumnStore requires a minimum of 4 CPU cores and 16GB RAM per node for realistic analytics workloads. Test environments can run with less, but production sizing should follow the memory tuning section below.

Step-by-step installation

Prepare the operating system

ColumnStore needs specific kernel parameters, disabled swappiness sensitivity and a dedicated filesystem mount for columnar data. Apply these on every node before installing packages.

vm.swappiness = 1
vm.max_map_count = 262144
net.core.somaxconn = 4096
fs.file-max = 6553600
sudo sysctl --system

Set resource limits

ColumnStore's PrimProc and ExeMgr processes open large numbers of file descriptors during scans. Raise the limits for the mysql user.

mysql soft nofile 65536
mysql hard nofile 65536
mysql soft nproc 32768
mysql hard nproc 32768

Add the MariaDB repository

ColumnStore ships as an engine plugin bundled with the MariaDB Enterprise/Community server packages. Use the official MariaDB repository setup script to pull the correct version for your distro.

curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
sudo apt update
curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
sudo dnf makecache

Install MariaDB server and ColumnStore packages

Install the server along with the columnstore-engine package and its dependencies. This includes the storage manager, distributed metadata layer and cpimport tooling.

sudo apt install -y mariadb-server mariadb-plugin-columnstore boost-* jq
sudo dnf install -y MariaDB-server MariaDB-columnstore-engine boost jq
Warning: Do not mix ColumnStore package versions across nodes in a cluster. Version skew between the storage manager and the DMLProc/ExeMgr layer causes query failures that are hard to diagnose.

Run the ColumnStore post-install configuration

The postConfigure script initializes the system catalog, sets storage locations and, for multi-node setups, registers all module IPs.

sudo systemctl start mariadb-columnstore-cmapi
sudo /usr/bin/mcs cluster start

For a single-node deployment, verify the module is active.

sudo mcs cluster status

Secure the root account and create an analytics user

Run the standard hardening script, then create a dedicated user for ETL and reporting workloads instead of using root for application connections.

sudo mariadb-secure-installation
sudo mariadb -e "CREATE USER 'analytics_etl'@'10.0.1.%' IDENTIFIED BY 'Cx7!qLm2Vr9Tze';"
sudo mariadb -e "GRANT SELECT, INSERT, CREATE, DROP ON analytics_db.* TO 'analytics_etl'@'10.0.1.%';"
sudo mariadb -e "FLUSH PRIVILEGES;"

Configuring multi-node ColumnStore clusters

Plan the module roles

A production cluster separates performance modules (PM, storage plus query execution) from user modules (UM, connection handling and result aggregation). A minimal HA cluster uses 3 PMs and 2 UMs.

RoleResponsibilityMinimum count
UM (User Module)Accepts client connections, parses SQL, returns results2
PM (Performance Module)Stores column extents, executes scans and joins3
DBRootsData storage units assigned to PMs, one or more per PM1 per PM minimum

Register additional nodes via CMAPI

Use the ColumnStore Management API to add each node to the cluster. Run this from the first PM node, replacing the IPs with your actual node addresses.

curl -k -s -X PUT https://127.0.0.1:8640/cmapi/0.4.0/cluster/node \
  -H 'x-api-key: YOUR_CMAPI_KEY' \
  -d '{"timeout":120, "node": "203.0.113.11"}'
curl -k -s -X PUT https://127.0.0.1:8640/cmapi/0.4.0/cluster/node \
  -H 'x-api-key: YOUR_CMAPI_KEY' \
  -d '{"timeout":120, "node": "203.0.113.12"}'

Retrieve the generated API key from the CMAPI config if you have not set one manually.

sudo cat /etc/columnstore/cmapi_server.conf | grep x-api-key

Configure shared storage or replication for DBRoots

ColumnStore supports local storage with replication or shared storage (S3-compatible object storage) for DBRoots. For most self-hosted clusters, S3-compatible storage simplifies failover since any PM can mount any DBRoot.

[ObjectStorage]
service = S3

[S3]
bucket = columnstore-data
endpoint = https://s3.example.com
aws_access_key_id = AKIAEXAMPLE123
aws_secret_access_key = REPLACE_WITH_STRONG_SECRET
region = eu-central-1

If you are running your own S3-compatible endpoint, see configuring MinIO high availability clustering for a self-hosted object storage backend.

Verify cluster membership

Confirm every node reports healthy status before loading data.

curl -k -s https://127.0.0.1:8640/cmapi/0.4.0/cluster/status -H 'x-api-key: YOUR_CMAPI_KEY' | jq

Table design for analytics workloads

Create a columnstore database and fact table

ColumnStore tables are declared with ENGINE=Columnstore. Design fact tables wide and denormalized; ColumnStore reads only the columns a query touches, so extra columns cost little at scan time.

sudo mariadb -e "CREATE DATABASE IF NOT EXISTS analytics_db;"
sudo mariadb analytics_db -e "
CREATE TABLE order_events (
  event_id BIGINT,
  order_id BIGINT,
  customer_id INT,
  region_code CHAR(2),
  event_type VARCHAR(32),
  event_timestamp DATETIME,
  amount DECIMAL(12,2)
) ENGINE=Columnstore;"

Choose extent maps and partitioning wisely

ColumnStore builds extent maps automatically, but query performance improves significantly when data arrives sorted by a high-cardinality time or ID column. Load data pre-sorted by event_timestamp to enable extent elimination on range filters.

Note: Unlike InnoDB, ColumnStore does not use traditional B-tree indexes. Extent elimination based on min/max block metadata is the primary mechanism for skipping irrelevant data during scans.

Tuning memory, buffer pool, and query execution

Size the PrimProc buffer cache

PrimProc's block cache holds decompressed column blocks in memory. Set it to roughly 50-60% of available RAM on PM nodes, leaving headroom for the OS page cache and ExeMgr.

<PrimitiveServers>
  <NumThreads>32</NumThreads>
  <NumBlocksPct>55</NumBlocksPct>
</PrimitiveServers>
sudo mcs cluster restart

Tune parallel query execution

Increase the DML and DDL execution thread pools on UM nodes to match core count, and set the number of concurrent casual partitioning threads on PM nodes for large table scans.

<SystemConfig>
  <DBRootCount>3</DBRootCount>
  <SystemQueryReadyWaitTimeout>30</SystemQueryReadyWaitTimeout>
</SystemConfig>
<ExeMgr1>
  <NumThreads>24</NumThreads>
</ExeMgr1>

Adjust the MariaDB server-level memory settings

Even though ColumnStore manages its own block cache, the MariaDB server layer still needs sensible connection and sort buffer limits for query planning.

[mysqld]
max_connections = 500
sort_buffer_size = 4M
join_buffer_size = 8M
columnstore_use_import_for_batchinsert = ON
sudo systemctl restart mariadb

For general MariaDB performance baseline tuning outside ColumnStore-specific settings, see optimizing MariaDB performance with query analysis and indexing.

Loading bulk data with cpimport

Prepare a delimited source file

cpimport reads flat files directly into extents, bypassing the SQL layer entirely. It is dramatically faster than INSERT for bulk loads, often ingesting millions of rows per minute.

cat > /tmp/order_events.csv <<'EOF'
1001|500001|9001|EU|purchase|2025-01-15 10:22:00|129.99
1002|500002|9002|US|refund|2025-01-15 10:23:11|59.50
1003|500003|9003|EU|purchase|2025-01-15 10:24:47|89.00
EOF

Run cpimport against the target table

Specify the delimiter and null handling explicitly to avoid silent data corruption on malformed rows.

sudo cpimport -s '|' -n 5 analytics_db order_events /tmp/order_events.csv

The -n flag sets the number of parallel read threads. Increase it on PM nodes with more cores for large files.

Batch ETL pipelines with staging tables

For continuous ingestion pipelines, load into a staging table with cpimport, validate row counts, then use INSERT SELECT to move validated rows into the production fact table. This avoids partial loads corrupting live analytics.

sudo mariadb analytics_db -e "
CREATE TABLE order_events_staging LIKE order_events;"
sudo cpimport -s '|' analytics_db order_events_staging /tmp/order_events.csv
sudo mariadb analytics_db -e "
INSERT INTO order_events SELECT * FROM order_events_staging WHERE amount > 0;
TRUNCATE TABLE order_events_staging;"

If your pipeline sources data from Kafka or Spark, see setting up Spark Streaming with Kafka and Delta Lake for the upstream ingestion side before landing files for cpimport.

Automate cpimport with cron or systemd timers

Schedule recurring loads for batch ETL windows rather than running cpimport ad hoc.

[Unit]
Description=ColumnStore bulk load job

[Service]
Type=oneshot
User=mysql
ExecStart=/usr/bin/cpimport -s | analytics_db order_events /data/incoming/orders.csv
[Unit]
Description=Run ColumnStore load every 15 minutes

[Timer]
OnCalendar=*:0/15
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl enable --now cs-cpimport.timer

Monitoring cluster health and performance

Check module and DBRoot status

Run this regularly, or wire it into a health check script, to catch a downed PM before it affects query correctness.

sudo mcs cluster status

Query system performance views

ColumnStore exposes execution statistics through the calpontsys system database, useful for spotting slow extents or skewed table distribution.

sudo mariadb -e "SELECT * FROM information_schema.columnstore_extents LIMIT 10;"
sudo mariadb -e "SELECT * FROM information_schema.columnstore_files ORDER BY compressedsize DESC LIMIT 10;"

Export metrics to Prometheus

Use the mysqld_exporter alongside a custom textfile collector for cpimport job durations and mcs cluster status. This lets you build dashboards similar to existing MariaDB monitoring setups.

sudo systemctl status prometheus-mysqld-exporter

See configuring MariaDB performance monitoring with Prometheus and Grafana for the full exporter and dashboard setup, which applies directly to the mysqld layer that ColumnStore sits on top of.

Set up log rotation for ColumnStore logs

ColumnStore writes verbose logs to /var/log/mariadb/columnstore/ that grow quickly under heavy load. Rotate them to avoid filling disk on PM nodes.

/var/log/mariadb/columnstore/*.log {
  daily
  rotate 14
  compress
  missingok
  notifempty
  copytruncate
}

Verify your setup

sudo mcs cluster status
sudo mariadb -e "SELECT VERSION();"
sudo mariadb analytics_db -e "SELECT COUNT(*) FROM order_events;"
sudo mariadb analytics_db -e "EXPLAIN SELECT region_code, SUM(amount) FROM order_events GROUP BY region_code;"

Confirm the query plan shows a Columnstore scan and that row counts match expected load volumes after a cpimport run.

Common issues

SymptomCauseFix
cpimport fails with lock file existsA previous import crashed mid-load and left a stale lockRemove the lock under /var/lib/columnstore/data1/systemFiles/dbrm/ after confirming no active import, then retry
Query returns stale results after cpimportUM node cache not refreshed after bulk loadRun FLUSH TABLES; or restart the affected UM's PrimProc connection pool
Node marked as DOWN in cluster statusNetwork partition or CMAPI service crashed on that nodeCheck systemctl status mariadb-columnstore-cmapi on the affected node and confirm firewall allows CMAPI port 8640 between nodes
Slow full table scans on large fact tablesData loaded unsorted, preventing extent eliminationReload with cpimport sorted by the primary filter column, typically an event timestamp
PrimProc using excessive memory and OOM killer triggersNumBlocksPct set too high relative to available RAMLower NumBlocksPct in Columnstore.xml to leave headroom for OS cache and ExeMgr
cpimport permission denied writing to data directoryImport run as a user without ownership of ColumnStore data pathsRun cpimport as the mysql user, or fix ownership with sudo chown -R mysql:mysql /var/lib/columnstore rather than loosening permissions
Never use chmod 777. If cpimport or PrimProc reports permission errors on data directories, the fix is correct ownership with chown to the mysql user, not opening permissions to every user on the system.

Next steps

Running this in production?

Want this handled for you? Running ColumnStore at scale adds a second layer of work: capacity planning across PM nodes, failover drills, storage cost control on object storage backends, and being on call when a node drops out of the cluster at 3am. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Wil je dit niet zelf beheren?

Wij beheren infrastructuur voor bedrijven die afhankelijk zijn van uptime. Volledig beheerd, met één vast aanspreekpunt dat je omgeving kent.

U krijgt één vast aanspreekpunt dat uw omgeving kent

Rotterdam 13:37 · bereikbaar in een bericht, geen ticketformulier