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.
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 = 6553600sudo sysctl --systemSet 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 32768Add 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 updatecurl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
sudo dnf makecacheInstall 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-* jqsudo dnf install -y MariaDB-server MariaDB-columnstore-engine boost jqRun 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 startFor a single-node deployment, verify the module is active.
sudo mcs cluster statusSecure 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-installationsudo 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.
| Role | Responsibility | Minimum count |
|---|---|---|
| UM (User Module) | Accepts client connections, parses SQL, returns results | 2 |
| PM (Performance Module) | Stores column extents, executes scans and joins | 3 |
| DBRoots | Data storage units assigned to PMs, one or more per PM | 1 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-keyConfigure 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-1If 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' | jqTable 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.
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 restartTune 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 = ONsudo systemctl restart mariadbFor 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
EOFRun 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.csvThe -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.targetsudo systemctl enable --now cs-cpimport.timerMonitoring 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 statusQuery 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-exporterSee 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
| Symptom | Cause | Fix |
|---|---|---|
| cpimport fails with lock file exists | A previous import crashed mid-load and left a stale lock | Remove the lock under /var/lib/columnstore/data1/systemFiles/dbrm/ after confirming no active import, then retry |
| Query returns stale results after cpimport | UM node cache not refreshed after bulk load | Run FLUSH TABLES; or restart the affected UM's PrimProc connection pool |
| Node marked as DOWN in cluster status | Network partition or CMAPI service crashed on that node | Check 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 tables | Data loaded unsorted, preventing extent elimination | Reload with cpimport sorted by the primary filter column, typically an event timestamp |
| PrimProc using excessive memory and OOM killer triggers | NumBlocksPct set too high relative to available RAM | Lower NumBlocksPct in Columnstore.xml to leave headroom for OS cache and ExeMgr |
| cpimport permission denied writing to data directory | Import run as a user without ownership of ColumnStore data paths | Run cpimport as the mysql user, or fix ownership with sudo chown -R mysql:mysql /var/lib/columnstore rather than loosening permissions |
Next steps
- Configure MariaDB performance monitoring with Prometheus and Grafana dashboards
- Set up Spark Streaming with Kafka and Delta Lake for real-time analytics
- Configure MinIO high availability clustering for production
- Benchmark MariaDB ColumnStore with sysbench and TPC-H workloads
- Implement MariaDB ColumnStore backup and disaster recovery automation
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# ---------------------------------------------------------------------------
# MariaDB ColumnStore single-node installer
# Prepares OS, installs MariaDB + ColumnStore engine, starts cluster services,
# hardens root and creates an analytics ETL user.
# ---------------------------------------------------------------------------
# ------------------------------ Colors --------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[OK]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
usage() {
cat <<EOF
Usage: sudo $0 -a <analytics_subnet_cidr_prefix> -p <analytics_password>
-a Subnet allowed to connect for analytics ETL user, e.g. 10.0.1.%
-p Password for the analytics_etl MySQL user
-h Show this help
Example:
sudo $0 -a "10.0.1.%" -p 'Cx7!qLm2Vr9Tze'
EOF
exit 1
}
# ------------------------------ Arg parsing ---------------------------------
ANALYTICS_HOST=""
ANALYTICS_PASS=""
while getopts "a:p:h" opt; do
case "$opt" in
a) ANALYTICS_HOST="$OPTARG" ;;
p) ANALYTICS_PASS="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
[ -z "$ANALYTICS_HOST" ] && usage
[ -z "$ANALYTICS_PASS" ] && usage
# ------------------------------ Prereqs -------------------------------------
if [ "$(id -u)" -ne 0 ]; then
log_err "This script must be run as root (use sudo)."
exit 1
fi
TOTAL_STEPS=9
STEP=0
next_step() { STEP=$((STEP+1)); echo -e "\n${GREEN}[${STEP}/${TOTAL_STEPS}]${NC} $*"; }
# ------------------------------ Rollback ------------------------------------
CLEANUP_NEEDED=0
cleanup() {
if [ "$CLEANUP_NEEDED" -eq 1 ]; then
log_err "Installation failed. Attempting rollback..."
systemctl stop mariadb-columnstore-cmapi 2>/dev/null || true
systemctl stop mariadb 2>/dev/null || true
log_warn "Rollback complete. Packages left installed for inspection; remove manually if needed."
fi
}
trap cleanup ERR
# ------------------------------ Distro detection ----------------------------
next_step "Detecting operating system..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian) PKG_MGR="apt"; PKG_INSTALL="apt install -y" ;;
almalinux|rocky|centos|rhel|ol|fedora) PKG_MGR="dnf"; PKG_INSTALL="dnf install -y" ;;
amzn) PKG_MGR="yum"; PKG_INSTALL="yum install -y" ;;
*) log_err "Unsupported distro: $ID"; exit 1 ;;
esac
else
log_err "/etc/os-release not found. Cannot detect distro."
exit 1
fi
log_info "Detected distro: $ID ($PKG_MGR)"
CLEANUP_NEEDED=1
# ------------------------------ Kernel tuning -------------------------------
next_step "Applying kernel parameters for ColumnStore..."
SYSCTL_FILE="/etc/sysctl.d/99-columnstore.conf"
cat > "$SYSCTL_FILE" <<EOF
vm.swappiness = 1
vm.max_map_count = 262144
net.core.somaxconn = 4096
fs.file-max = 6553600
EOF
chmod 644 "$SYSCTL_FILE"
sysctl --system > /dev/null
log_info "Kernel parameters applied."
# ------------------------------ Resource limits -----------------------------
next_step "Setting resource limits for mysql user..."
LIMITS_FILE="/etc/security/limits.d/99-mariadb-columnstore.conf"
cat > "$LIMITS_FILE" <<EOF
mysql soft nofile 65536
mysql hard nofile 65536
mysql soft nproc 32768
mysql hard nproc 32768
EOF
chmod 644 "$LIMITS_FILE"
log_info "Resource limits configured at $LIMITS_FILE"
# ------------------------------ Prerequisites check -------------------------
next_step "Checking required tools (curl)..."
if ! command -v curl >/dev/null 2>&1; then
log_warn "curl not found, installing..."
if [ "$PKG_MGR" = "apt" ]; then
apt update
$PKG_INSTALL curl
else
$PKG_INSTALL curl
fi
fi
log_info "curl available."
# ------------------------------ Add MariaDB repo ----------------------------
next_step "Adding official MariaDB repository..."
curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash
if [ "$PKG_MGR" = "apt" ]; then
apt update
elif [ "$PKG_MGR" = "dnf" ]; then
dnf makecache
else
yum makecache
fi
log_info "MariaDB repository configured."
# ------------------------------ Install packages ----------------------------
next_step "Installing MariaDB server and ColumnStore engine packages..."
if [ "$PKG_MGR" = "apt" ]; then
DEBIAN_FRONTEND=noninteractive $PKG_INSTALL mariadb-server mariadb-plugin-columnstore libboost-all-dev jq
elif [ "$PKG_MGR" = "dnf" ]; then
$PKG_INSTALL MariaDB-server MariaDB-columnstore-engine boost jq
else
$PKG_INSTALL MariaDB-server MariaDB-columnstore-engine boost jq
fi
log_info "Packages installed."
# ------------------------------ Firewall configuration ----------------------
next_step "Configuring firewall for MariaDB / ColumnStore CMAPI ports..."
if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then
ufw allow 3306/tcp
ufw allow 8640/tcp
log_info "UFW rules added (3306, 8640)."
elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --permanent --add-port=8640/tcp
firewall-cmd --reload
log_info "firewalld rules added (3306, 8640)."
else
log_warn "No active firewall manager detected; skipping firewall rules."
fi
# ------------------------------ Start services ------------------------------
next_step "Starting MariaDB and ColumnStore cluster services..."
systemctl enable --now mariadb
# CMAPI service name is consistent across distros for this package
if systemctl list-unit-files | grep -q mariadb-columnstore-cmapi; then
systemctl enable --now mariadb-columnstore-cmapi
else
log_warn "mariadb-columnstore-cmapi service unit not found; check package install."
fi
# Bring up the columnstore cluster (single node)
if command -v mcs >/dev/null 2>&1; then
mcs cluster start || log_warn "mcs cluster start returned non-zero; check 'mcs cluster status'."
sleep 3
mcs cluster status || true
else
log_warn "'mcs' CLI not found; skipping cluster start step."
fi
log_info "Services started."
# ------------------------------ Secure install + ETL user -------------------
next_step "Hardening root account and creating analytics ETL user..."
# Non-interactive secure-installation equivalent: lock down root remote access,
# remove anonymous users and test DB via direct SQL rather than the prompt-based script,
# since this script must run unattended.
mariadb -e "DELETE FROM mysql.global_priv WHERE User='';" 2>/dev/null || true
mariadb -e "DROP DATABASE IF EXISTS test;" 2>/dev/null || true
mariadb -e "FLUSH PRIVILEGES;"
mariadb -e "CREATE DATABASE IF NOT EXISTS analytics_db;"
mariadb -e "CREATE USER IF NOT EXISTS 'analytics_etl'@'${ANALYTICS_HOST}' IDENTIFIED BY '${ANALYTICS_PASS}';"
mariadb -e "GRANT SELECT, INSERT, CREATE, DROP ON analytics_db.* TO 'analytics_etl'@'${ANALYTICS_HOST}';"
mariadb -e "FLUSH PRIVILEGES;"
log_info "Analytics ETL user '${ANALYTICS_HOST}' created with scoped privileges on analytics_db."
# ------------------------------ Verification ---------------------------------
next_step "Running verification checks..."
VERIFY_OK=1
if systemctl is-active --quiet mariadb; then
log_info "mariadb service is active."
else
log_err "mariadb service is NOT active."
VERIFY_OK=0
fi
if systemctl list-unit-files | grep -q mariadb-columnstore-cmapi; then
if systemctl is-active --quiet mariadb-columnstore-cmapi; then
log_info "mariadb-columnstore-cmapi service is active."
else
log_err "mariadb-columnstore-cmapi service is NOT active."
VERIFY_OK=0
fi
fi
if mariadb -e "SELECT ENGINE FROM information_schema.ENGINES WHERE ENGINE='Columnstore';" 2>/dev/null | grep -qi columnstore; then
log_info "ColumnStore engine is registered in MariaDB."
else
log_warn "ColumnStore engine not detected in information_schema.ENGINES. Verify manually."
fi
if mariadb -e "SELECT User,Host FROM mysql.global_priv WHERE User='analytics_etl';" 2>/dev/null | grep -q analytics_etl; then
log_info "analytics_etl user verified in mysql.global_priv."
else
log_warn "Could not verify analytics_etl user; check manually."
fi
if [ "$VERIFY_OK" -eq 1 ]; then
CLEANUP_NEEDED=0
log_info "MariaDB ColumnStore installation completed successfully."
else
log_err "Verification failed. Review logs above."
exit 1
fi
trap - ERR
exit 0
Review the script before running. Execute with: bash install.sh