Running databases on your own servers
Install, lock down, tune, back up and actually restore MySQL, PostgreSQL and Redis.
In 30 seconds
Your database holds the only data you cannot rebuild, so it deserves more care than any other service on the server. Running it yourself means installing it, keeping it off the public internet, giving each app only the access it needs, setting a few memory knobs and, above all, taking backups that are stored elsewhere and regularly restored to prove they work. Managed databases do much of this for you, at a price.
Why I use it
- I run MySQL/MariaDB, PostgreSQL and Redis on production servers, so getting access, tuning and backups right is part of my everyday responsibility.
- I treat a backup as real only after a restore has been tested; that rule has saved more stress than any tool.
- A database listening on a public port is one of the most common ways small companies leak data, so locking it to localhost or a private network is first on my checklist.
How it works
01Where the database should live
There are three common placements. On the same server as the app: simplest and fastest (no network hop), fine for small and medium projects, but the app and database compete for RAM and a lost server means losing both. On a separate server in a private network: the app servers can scale independently and the database gets all the memory. As a managed service (Amazon RDS, DigitalOcean Managed Databases, Google Cloud SQL and others): the provider handles installation, patching, backups, failover and point-in-time recovery. Whichever you choose, the same rules apply: the database is never reachable from the internet, each application has its own user with minimal rights, and there is a backup copy outside the machine. Start with the simplest placement that meets your recovery needs, and move out when memory pressure or availability requirements demand it.
02Install and bind to localhost or a private network
On Ubuntu, apt install postgresql, mysql-server (or mariadb-server) and redis-server gives you working services. Ubuntu 26.04 ships PostgreSQL 18; the official vendor repositories offer other versions if you need to match production elsewhere. The most important setting is where the service listens. MySQL and Redis on Ubuntu already default to 127.0.0.1, and PostgreSQL to localhost, but always verify with ss -tlnp. If app servers connect over a private network, bind to the private interface address only, allow that subnet in the firewall, and for PostgreSQL add a matching line in pg_hba.conf using scram-sha-256 authentication. Redis keeps protected-mode on and should require a password or ACL user even on localhost. Never open 3306, 5432 or 6379 in ufw to the world.
# /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
bind-address = 127.0.0.1
# /etc/postgresql/18/main/postgresql.conf
listen_addresses = 'localhost' # or 'localhost,10.0.0.5' for a private NIC
# /etc/postgresql/18/main/pg_hba.conf (private subnet only)
host app_db app_user 10.0.0.0/24 scram-sha-256
# /etc/redis/redis.conf
bind 127.0.0.1 -::1
protected-mode yes
requirepass change_me_long_random_value
# Verify what is listening and where
sudo ss -tlnp | grep -E '3306|5432|6379'
03Users and least privilege
Never let an application connect as root (MySQL) or postgres (PostgreSQL). Create one database and one user per application, and grant only what that app needs on that database. Many teams also create a separate migration user that can change the schema and a runtime user that can only read and write rows, and a read-only user for reporting or support. Use long random passwords stored in the app's environment file or a secrets manager, not in Git. For PostgreSQL 15 and newer, ordinary users can no longer create objects in the public schema by default, so the owner of the database should be the app or migration role. Review users from time to time and remove accounts of people and apps that are gone.
-- PostgreSQL
CREATE ROLE app_user LOGIN PASSWORD 'long_random_password';
CREATE DATABASE app_db OWNER app_user;
CREATE ROLE report_ro LOGIN PASSWORD 'another_random_password';
GRANT CONNECT ON DATABASE app_db TO report_ro;
\c app_db
GRANT USAGE ON SCHEMA public TO report_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO report_ro;
ALTER DEFAULT PRIVILEGES FOR ROLE app_user IN SCHEMA public GRANT SELECT ON TABLES TO report_ro;
-- MySQL / MariaDB
CREATE DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'long_random_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'localhost';
04Basic tuning: give the database its memory
Databases are fast when the hot data fits in RAM. The defaults are deliberately tiny, so a few settings make a big difference. For MySQL/InnoDB, innodb_buffer_pool_size caches data and indexes; on a dedicated database server 50 to 70 percent of RAM is typical, much less when the app shares the machine. For PostgreSQL, shared_buffers is usually about 25 percent of RAM, effective_cache_size (a hint, not an allocation) 50 to 75 percent, and work_mem stays small because it is allocated per sort or hash operation, per connection. Keep max_connections modest and use a pooler such as PgBouncer when many app processes connect. For Redis, set maxmemory and an eviction policy when it is a cache; use noeviction when it holds queues you must not lose. Then measure: enable the slow query log and use EXPLAIN before buying more hardware.
# MySQL, 8 GB server shared with the app
[mysqld]
innodb_buffer_pool_size = 3G
max_connections = 200
slow_query_log = 1
long_query_time = 1
# PostgreSQL, 8 GB dedicated server
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 16MB
maintenance_work_mem = 512MB
log_min_duration_statement = 1000
# Redis used as a cache
maxmemory 512mb
maxmemory-policy allkeys-lru
05Backups: logical vs physical, local vs off-site
A logical backup is a dump of SQL or a structured archive (mysqldump, pg_dump). It is portable across versions, easy to restore one table from, and perfect for small and medium databases, but slow for very large ones. A physical backup copies the data files themselves (pg_basebackup, pgBackRest, WAL-G, Percona XtraBackup, mariabackup). Combined with continuous archiving of the write-ahead log or binary log, it allows point-in-time recovery: restore to the minute before someone dropped a table. Server snapshots from your provider are useful but are not enough alone: they live in the same account and may capture a database mid-write. Follow 3-2-1: three copies, two kinds of storage, one off-site. Encrypt backups, keep them in a different account or provider, and monitor that the job actually ran.
#!/usr/bin/env bash
# /usr/local/bin/db-backup.sh (cron: 30 2 * * * root /usr/local/bin/db-backup.sh)
set -euo pipefail
STAMP=$(date +%F_%H%M)
DIR=/var/backups/db
mkdir -p "$DIR"
# PostgreSQL: compressed custom format, restorable per table
sudo -u postgres pg_dump -Fc app_db > "$DIR/pg_app_db_$STAMP.dump"
# MySQL/MariaDB: consistent InnoDB snapshot without locking (credentials in /root/.my.cnf)
mysqldump --single-transaction --routines --triggers app_db | gzip > "$DIR/my_app_db_$STAMP.sql.gz"
# Copy off the server to an S3-compatible bucket configured in rclone
rclone copy "$DIR" offsite:db-backups/
# Keep 14 days locally
find "$DIR" -type f -mtime +14 -delete
06Restore tests: the only proof a backup works
Backups fail silently: a full disk, an expired credential, a dump that ran against the wrong database, a script that stopped after a server move. The only way to know is to restore regularly, ideally automatically. A simple restore test loads the latest dump into a temporary database (or a separate test server), runs a few sanity queries such as row counts on key tables and the newest record date, and then drops it. Time it as well: that duration is your real recovery time. Write the steps down as a short runbook, because during an incident nobody wants to discover the right flags for pg_restore. Do a full drill, including restoring uploads and app config, at least a few times a year.
#!/usr/bin/env bash
set -euo pipefail
LATEST=$(ls -t /var/backups/db/pg_app_db_*.dump | head -1)
sudo -u postgres dropdb --if-exists restore_test
sudo -u postgres createdb restore_test
time sudo -u postgres pg_restore --no-owner -d restore_test "$LATEST"
# Sanity checks: data exists and is recent
sudo -u postgres psql -d restore_test -c "SELECT count(*) FROM orders;"
sudo -u postgres psql -d restore_test -c "SELECT max(created_at) FROM orders;"
sudo -u postgres dropdb restore_test
07Managed or self-hosted?
Managed databases cost more per gigabyte of RAM, but you buy time and safety: automated backups with point-in-time recovery, minor version patching, one-click replicas, failover to a standby and metrics out of the box. You still own schema design, indexes, users and query performance. Self-hosting is cheaper and gives full control over versions, extensions and configuration, and it is perfectly reasonable for small teams if backups, monitoring and updates are scripted and tested. A good rule: if a few hours of database downtime or a day of lost data would seriously hurt the business, and nobody on the team enjoys being the DBA, use a managed database. If the project is small, budget-sensitive and the team is comfortable on Linux, self-host carefully and revisit the decision as the project grows.
Use it when
- Self-host on the app server for small and medium projects where cost matters and a short outage is acceptable.
- Move the database to its own private server when it competes with the app for RAM or CPU.
- Use a managed database when you need high availability, point-in-time recovery and little operational effort.
- Use Redis alongside SQL for caching, sessions, rate limits and queues, not as the primary store of record.
Skip it when
- Do not self-host if nobody will own patching, monitoring and restore drills.
- Do not expose a database port publicly to make a GUI client work; use an SSH tunnel instead.
- Do not rely on provider snapshots as your only backup.
Alternatives
- Managed SQL (RDS, Cloud SQL, DigitalOcean)
Backups, failover and patching handled by the provider; higher cost, less low-level control.
- Serverless Postgres (Neon, Supabase and similar)
Fast to start, branching and scale-to-zero; watch connection limits and pricing at scale.
- Docker Compose database
Convenient and reproducible; keep data on named volumes and never publish the port to 0.0.0.0.
- SQLite
Zero administration for small single-server apps; limited concurrent writes.
Best practices
- Bind every database to localhost or a private interface and confirm with ss -tlnp.
- One database and one least-privilege user per application; never connect apps as root or postgres.
- Set buffer pool or shared_buffers deliberately and enable slow query logging from day one.
- Automate nightly dumps or physical backups with WAL/binlog archiving, encrypted and copied off-site.
- Test restores on a schedule and record how long they take.
- Monitor disk space, connections, replication lag and backup job success.
- Upgrade major versions on a copy first, with a tested rollback plan.
Common mistakes
- Opening 3306 or 5432 to the internet, often temporarily, and forgetting to close it.
- Keeping backups only on the same server or the same cloud account as the database.
- Never restoring a backup until the day it is needed, then finding it empty or corrupt.
- Running mysqldump without --single-transaction on a busy InnoDB database, causing locks or inconsistent dumps.
- Using Redis with an eviction policy for queue data, silently losing jobs under memory pressure.
Deep dive
08Connections are expensive: pooling with PgBouncer
Every PostgreSQL connection is a separate operating system process with its own memory, so hundreds of idle connections from PHP-FPM workers, queue workers and cron jobs waste RAM and slow the server, and you hit max_connections long before the CPU is busy. MySQL uses threads and copes better, but the same problem appears at scale. A pooler sits between apps and the database and multiplexes many client connections onto a few server connections. PgBouncer in transaction mode gives a server connection to a client only for the duration of one transaction, which is the most efficient setting. The trade-off is that session state does not survive: SET commands, advisory locks held across transactions, LISTEN/NOTIFY and temporary tables break. Since PgBouncer 1.21, max_prepared_statements makes protocol-level prepared statements work in transaction mode. Size the pool to what the database can actually execute in parallel, roughly a small multiple of its CPU cores.
; /etc/pgbouncer/pgbouncer.ini
[databases]
app_db = host=127.0.0.1 port=5432 dbname=app_db
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
max_prepared_statements = 100
09Write-ahead logs, archiving and point-in-time recovery
Both engines write every change to a sequential log before touching data files: the WAL in PostgreSQL, the redo log plus binary log in MySQL. After a crash, the database replays the log to reach a consistent state. The same log is the basis for replication and point-in-time recovery: restore a physical base backup, then replay archived log segments up to a chosen timestamp. With PostgreSQL, a tool such as pgBackRest handles base backups, WAL archiving to object storage, retention, compression and encryption. archive_command pushes each finished WAL segment; if it starts failing, PostgreSQL keeps the segments locally and pg_wal grows until the disk fills, so alert on archive failures (pg_stat_archiver shows them). On MySQL, keep binary logs with GTIDs enabled and copy them off the server; mysqlbinlog can replay them up to a position or time after restoring a full backup.
# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'pgbackrest --stanza=main archive-push %p'
# /etc/pgbackrest/pgbackrest.conf (S3 credentials kept in a root-only include)
[global]
repo1-type=s3
repo1-s3-bucket=example-db-backups
repo1-s3-endpoint=s3.example.com
repo1-s3-region=eu-central-1
repo1-retention-full=2
repo1-cipher-type=aes-256-cbc
[main]
pg1-path=/var/lib/postgresql/18/main
10Major version upgrades without drama
Minor releases (18.0 to 18.1) only need a package update and restart. Major versions change the on-disk format and need a plan. On Ubuntu, pg_upgradecluster wraps pg_upgrade and the cluster layout; pg_upgrade with --link avoids copying data files and finishes in minutes even for large databases, but the old cluster cannot be started again afterwards, so take a backup first. PostgreSQL 18 also carries planner statistics across the upgrade, so performance is good immediately, but running ANALYZE is still cheap insurance. For near-zero downtime, use logical replication: build the new version alongside, replicate tables from the old primary, let it catch up, then switch the application in seconds. Sequences and schema changes are not replicated, so freeze migrations and sync sequences at cutover. Whatever the method, rehearse on a restored copy, run the application test suite against it, and check extensions first, because a missing extension version is the classic upgrade blocker.
11Finding slow queries and keeping tables healthy
pg_stat_statements aggregates every normalised query with call counts and total time. Sorting by total time, not mean time, finds what actually loads the server: a 2 ms query called a million times often matters more than a 3 second report. In MySQL, performance_schema and the sys schema give the same view. PostgreSQL's MVCC keeps old row versions until VACUUM reclaims them. Autovacuum normally handles this, but very large or update-heavy tables need tighter per-table thresholds, and a long-running transaction or forgotten replication slot blocks cleanup for the whole cluster, causing bloat and growing disk usage. Watch dead tuples, the oldest transaction age and the size of the biggest tables. A healthy database is mostly boring monitoring plus the right indexes, checked with EXPLAIN (ANALYZE, BUFFERS).
-- postgresql.conf: shared_preload_libraries = 'pg_stat_statements' (restart)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
SELECT calls,
round(total_exec_time::numeric) AS total_ms,
round(mean_exec_time::numeric, 1) AS mean_ms,
left(query, 80) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- Tables that need vacuum attention
SELECT relname, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;
Real-world scenarios
The disk fills up overnight on a quiet database
Problem
Disk usage alerts fire at 3 a.m. although traffic is low. The data directory itself has not grown much, but pg_wal is many gigabytes and still increasing.
How to solve it
WAL grows when PostgreSQL is not allowed to remove it. Check two causes. First, SELECT * FROM pg_stat_archiver: a failing archive_command (expired storage credentials, full bucket) keeps every segment locally. Fix the credentials and archiving catches up. Second, SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) FROM pg_replication_slots: an inactive slot from a removed replica or CDC tool pins WAL forever. Drop it with pg_drop_replication_slot if it is truly unused. Prevent a repeat with max_slot_wal_keep_size, an alert on archive failures, and a disk alert at 80 percent instead of 95.
A DELETE without WHERE in production
Problem
At 11:58 someone ran a manual DELETE on the orders table without a WHERE clause. The application is still running and new orders keep arriving.
How to solve it
Do not restore over production, because that would lose every order placed since. Restore the latest base backup to a separate instance and replay WAL to just before the mistake, for example pgbackrest restore --type=time with a target of 11:57:30. Verify the row counts there, then copy back only the deleted rows with a dump of that table or INSERT ... SELECT through postgres_fdw, skipping rows that already exist. Note the exact recovery steps and time taken for the incident report. Afterwards, remove direct write access for humans on production, require transactions for manual fixes, and keep restore drills on the calendar.
'Too many connections' after scaling workers
Problem
The team doubled queue workers and added a second app server. Now PostgreSQL rejects connections at peak with 'sorry, too many clients already', even though CPU is only at 40 percent.
How to solve it
Count the connection budget: PHP-FPM max_children per server times servers, plus queue workers, plus cron and admin tools. It now exceeds max_connections. Raising max_connections to thousands is the wrong fix, because each connection costs memory and contention. Put PgBouncer in transaction mode on each app server or next to the database, point the apps at port 6432, and keep the real pool around 20 to 40 server connections. Check the code for session-level features such as advisory locks or SET statements that transaction pooling breaks. Then monitor pool waits: if clients queue, the database itself is the bottleneck and queries need optimising.
Cheat sheet
- List PostgreSQL clusters on Ubuntu
pg_lsclusters- See running queries (PostgreSQL)
SELECT pid, now() - query_start AS age, state, left(query, 60) FROM pg_stat_activity WHERE state <> 'idle';- Cancel or kill a query
SELECT pg_cancel_backend(12345); -- or pg_terminate_backend(12345)- Database size
SELECT pg_size_pretty(pg_database_size('app_db'));- Restore a custom-format dump in parallel
pg_restore -j 4 --no-owner -d app_db app_db.dump- Replication lag on a PostgreSQL replica
SELECT now() - pg_last_xact_replay_timestamp();- Running queries (MySQL / MariaDB)
SHOW FULL PROCESSLIST;- Replica status (MySQL 8.0.22+)
SHOW REPLICA STATUS\G- Limit binary log retention (MySQL)
binlog_expire_logs_seconds = 604800- Redis memory and big keys
redis-cli INFO memory && redis-cli --bigkeys
In my real projects
- I operate MariaDB/MySQL on hosting-panel servers and PostgreSQL with Redis on a dedicated SaaS server, where backup scripts are part of the setup.
- My security baseline keeps every database bound to localhost or a private network, never public.
- My standard is server snapshots with tested restores, and my current plan moves backups off-server.
Questions & answers
Q1What is the difference between a logical and a physical backup?
A logical backup exports the data as SQL statements or an archive, using tools like pg_dump or mysqldump. It is portable between versions and lets you restore a single table, but it is slow to create and restore for large databases. A physical backup copies the database files, for example with pg_basebackup, pgBackRest or Percona XtraBackup. It is much faster for large data sets and, combined with continuous archiving of the WAL or binary log, enables point-in-time recovery. Most serious setups use physical backups with log archiving for recovery and occasional logical dumps for portability and for restoring individual tables.
Q2How do you make sure the database is not exposed to the internet?
Several layers. First, the service binds to 127.0.0.1 or a private network interface, which I verify with ss -tlnp. Second, the host firewall denies incoming traffic by default and only allows 22, 80 and 443; if app servers need access, I allow the private subnet only. Third, the cloud provider firewall mirrors those rules. Fourth, authentication is still strict: no remote root login, scram-sha-256 in PostgreSQL, strong passwords per app. For admin access from a laptop I use an SSH tunnel instead of opening a port. With Docker, I bind published ports to 127.0.0.1, because Docker can bypass ufw rules.
Q3How would you tune PostgreSQL or MySQL on a new server?
I start with memory. For MySQL, innodb_buffer_pool_size takes most of the RAM on a dedicated server, around 50 to 70 percent, and less if the app shares the machine. For PostgreSQL, shared_buffers around 25 percent of RAM, effective_cache_size around 50 to 75 percent, and a modest work_mem because it multiplies per operation and connection. I keep max_connections reasonable and add PgBouncer for many clients. Then I turn on slow query logging, look at the worst queries with EXPLAIN and add indexes. Most real performance wins come from queries and indexes, not from exotic configuration flags.
Q4What are RPO and RTO, and how do they influence the backup strategy?
RPO, recovery point objective, is how much data you can afford to lose, measured in time. RTO, recovery time objective, is how long you can be down while restoring. A nightly dump gives an RPO of up to 24 hours; if the business can only lose minutes, you need continuous WAL or binlog archiving, or a managed database with point-in-time recovery. RTO depends on data size and practice: a 200 GB logical restore can take hours, while promoting a replica takes minutes. I agree on these numbers with the business first, then choose the tools, and verify both with timed restore drills.
Q5Should we use a managed database or run our own?
Managed is the safer default when downtime or data loss is expensive and the team has no database specialist: you get automated backups, point-in-time recovery, patching, replicas and failover. The trade-offs are cost, less control over versions and extensions, and some vendor lock-in. Self-hosting is cheaper and fully flexible, and works well for small or medium projects when backups, monitoring and updates are automated and restores are tested. I often recommend starting self-hosted on a well-configured server for an MVP, and moving to managed once revenue or availability requirements justify it, since the migration is a planned dump and restore.
Q6How do you give a developer or analyst read-only access safely?
I create a dedicated read-only role with SELECT on the needed schema or tables only, plus default privileges so new tables are covered. Connections go through an SSH tunnel or a bastion, never a public port. Ideally the role points at a read replica or a recent restored copy, so heavy reports cannot slow production. If the data includes personal information, I limit columns through views or provide an anonymised copy. Access is personal, not shared, so it can be revoked individually, and statement logging or audit extensions record what was run. The account is removed when the person no longer needs it.
Senior-level questions
S1How do you design database failover, and how do you avoid split-brain?
Failover means promoting a standby when the primary dies, then pointing the application at it. The dangerous case is split-brain: the old primary is only unreachable, not dead, and both accept writes, creating divergent data that is painful to merge. Prevention needs a consensus or fencing mechanism. Patroni with etcd for PostgreSQL, or a managed service, holds a leader lock and demotes or fences the old primary before promoting. Applications connect through a stable endpoint, such as a virtual IP, a proxy or DNS managed by the cluster. Synchronous replication can guarantee zero data loss at the cost of write latency and availability if the standby fails. For small teams, a managed database usually beats building this correctly.
S2Redis holds our queues and sessions. How do you configure persistence?
Redis offers RDB snapshots, periodic point-in-time dumps that are compact and fast to load but lose writes since the last snapshot, and AOF, an append-only log of every write. With appendfsync everysec, the usual setting, a crash loses at most about a second. For queues and sessions I enable AOF plus RDB, set maxmemory-policy noeviction so Redis returns errors rather than silently dropping jobs, and alert on memory well before the limit. I also separate concerns: a cache instance with allkeys-lru and no persistence, and a queue instance with durability. If losing even a second of jobs is unacceptable, the job's source of truth should be the SQL database, with Redis only as transport.
S3How do you protect backups against ransomware or a compromised server?
Assume an attacker with root on the database server will try to delete backups too. So the server should be able to write backups but not delete or overwrite them: use a bucket in a separate account with object lock or versioning plus a retention policy, and credentials that only allow upload. Encrypt backups client-side, with keys stored outside the server, so a leaked bucket reveals nothing. Keep at least one copy with a different provider. Monitor for missing or unusually small backups, because a quiet failure looks the same as an attack. Finally, run restore drills from the off-site copy, using no access to the original server, which is exactly the situation after an incident.
S4PostgreSQL or MySQL for a new product?
Both are excellent, and the team's experience often matters more than features. PostgreSQL offers richer SQL, strong JSONB with indexes, partial and expression indexes, extensions like PostGIS and pgvector, transactional DDL and strict correctness, which makes it my default for complex domains and analytics-heavy apps. MySQL with InnoDB is simple to operate, has very mature replication, a huge hosting ecosystem and instant column additions for many schema changes, and frameworks like Laravel work equally well on it. Operational differences matter too: PostgreSQL needs connection pooling and vacuum awareness earlier, MySQL needs care with online DDL on big tables. I pick PostgreSQL unless existing systems, hosting or team skills favour MySQL.
Glossary
- WAL
- Write-ahead log: PostgreSQL's sequential record of every change, written before data files, used for crash recovery and replication.
- Binary log
- MySQL's log of committed changes, used for replication and replaying events after restoring a backup.
- PITR
- Point-in-time recovery: restoring a base backup and replaying logs up to an exact moment, such as just before a mistake.
- Replication slot
- A PostgreSQL bookmark that keeps WAL until a replica or consumer has received it; forgotten slots fill disks.
- Replication lag
- How far a replica is behind the primary, in time or bytes; reads from it may be slightly stale.
- Connection pooler
- A proxy such as PgBouncer that shares a few database connections among many application clients.
- MVCC
- Multi-version concurrency control: readers see a snapshot while writers create new row versions, so they rarely block each other.
- VACUUM
- PostgreSQL maintenance that reclaims space from dead row versions and updates visibility information; usually run by autovacuum.
- Buffer pool
- InnoDB's main memory cache for data and index pages; its size largely decides MySQL performance.
- GTID
- Global transaction identifier in MySQL, giving each transaction a unique ID so replicas and failover track position reliably.
Keep learning
Have a product in mind?Let's build it.
Tell me about your idea, your product or your team. I usually reply within one working day.