43 questions
Databases interview questions and answers
Middle questions
1. What is MVCC in PostgreSQL? What does
UPDATEphysically do? What arexmin,xmaxandctidand why don't readers block writers?Answer
MVCC keeps several versions of a row, so each transaction works with its own snapshot of the data.
UPDATEdoes not change a row in place: it creates a new version and the old one stays in the table until cleanup.xminis the transaction that created the version,xmaxis the transaction that deleted or locked it, andctidis the physical address of the version. A reader sees only the versions visible to its snapshot, so it does not wait for writers. The cost is dead versions thatVACUUMhas to clean and table bloat.Follow-up question: Why does a long transaction on one table prevent cleanup in other tables of the same database?
- Course: Databases: going deeper
- Lesson: Transactions, ACID and isolation levels
- Knowledge base: PostgreSQL
- Source: postgresql.org
2. How does row storage in InnoDB differ from PostgreSQL? Why does a random UUID primary key hurt more in MySQL and what is stored in a secondary index?
Answer
InnoDB has a clustered index: rows are stored directly in the B-tree of the primary key. PostgreSQL keeps rows in a separate heap, and all indexes point to the physical address of a version. A secondary index in InnoDB holds the primary key value, so reading through it walks two trees, and a large or random key inflates all secondary indexes. Random inserts also split pages of the clustered index. InnoDB keeps old row versions in undo logs, while PostgreSQL keeps them in the table itself.
Follow-up question: Why can an
UPDATEof a non indexed column in PostgreSQL touch all indexes and how is this related to HOT?- Course: Databases: going deeper
- Lesson: How an index works inside
- Knowledge base: MySQL, PostgreSQL
- Source: dev.mysql.com
3. How does streaming replication work in PostgreSQL and how does sync differ from async? What happens to the primary when the only sync replica goes down?
Answer
Every change first goes into the WAL. The
walsenderprocess on the primary streams WAL records to thewalreceiveron the replica, which replays them; this is a physical copy of the whole cluster. Replication is async by default:COMMITreturns after the WAL fsync on the primary, so losing the primary loses transactions within the lag. Sync is enabled throughsynchronous_standby_names, andsynchronous_commit(remote_write,on,remote_apply) sets what exactlyCOMMITwaits for. If the only sync replica goes down,COMMITs on the primary hang, so you use two replicas withANY 1or Patroni.Follow-up question: How do you measure lag in bytes and in seconds, and why does
now()minuspg_last_xact_replay_timestamp()lie when idle?- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: postgresql.org
4. A report moved to a replica fails after about 30 seconds with
canceling statement due to conflict with recovery, while on the primary the same query runs for 5 minutes. Why, and what settings are there?Answer
The replica replays WAL, and a
VACUUMfrom the primary may remove row versions still seen by the snapshot of a query on the replica, or anACCESS EXCLUSIVElock arrives. The query then conflicts with recovery.max_standby_streaming_delay(30 seconds by default) sets how long recovery waits before cancelling the query.hot_standby_feedback = onprotects the query at the price of bloat on the primary, becauseVACUUMdoes not remove the needed versions. A longer delay means lag for all other readers of the replica, so long reports get a dedicated replica. The conflict type is shown bypg_stat_database_conflicts.Follow-up question: How does a long transaction on a replica with
hot_standby_feedbackshow up in the primary metrics?- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: postgresql.org
5. How does Patroni decide which node is the primary? What happens in those 30 odd seconds when the leader vanishes, and why must an etcd cluster have three nodes rather than two?
Answer
The leader holds the leader key in the DCS (etcd, Consul) with a TTL and renews it every
loop_wait. When the leader disappears, the key expires and healthy replicas with lag withinmaximum_lag_on_failovercompete for the key through compare and set. The winner promotes itself and the rest switchprimary_conninfo; the total time is aboutttlplusloop_wait. etcd is Raft with a quorum: three nodes survive the loss of one, two nodes survive none. Without access to the DCS the leader demotes itself unlessfailsafe_modeis on.Follow-up question: How does a switchover differ from a failover and why does a planned switchover lose almost no data?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: patroni.readthedocs.io
6. B2B SaaS: 5000 tenants, tables
orders,order_items,invoices, 2 TB and growing fast. You decided to shard PostgreSQL across 8 nodes. Comparetenant_id,order_idandcreated_atas the shard key. What do you choose and what changes in the schema?Answer
created_atby ranges: all new writes go to the last shard, producing a hot node.order_idby hash is even, but tenant queries fan out to all shards, and theJOINwithorder_itemshas to be colocated.tenant_idkeeps almost all queries on one shard with localJOINs and transactions, and the only risk is skew from large tenants. I choosetenant_id. The schema needstenant_idin every table, composite primary keys, foreign and unique keys that include the shard key, global IDs and replication of shared reference tables.Follow-up question: How do you find a globally unique user email if
usersare sharded bytenant_id?- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: docs.citusdata.com
7. Pick a Redis structure and commands for these tasks: a user session with a TTL, a player leaderboard with the top 100 and a player's rank, the number of unique visitors per day, a rate limit of 100 requests per sliding minute, the last 50 feed events.
Answer
A session is a Hash with
EXPIRE. A leaderboard is a Sorted Set:ZINCRBYfor points,ZREVRANGEfor the top 100 andZREVRANKfor a rank. Unique visitors: HyperLogLog (PFADD,PFCOUNT) with an error of about 0.81% and 12 KB of memory, while the exact variant is a Set or a bitmap. A sliding window: a Sorted Set withZREMRANGEBYSCORE,ZADDandZCARD, atomically via Lua orMULTI. A feed is a List withLPUSHandLTRIM. Redis should not be the source of truth for data that cannot be lost or needs complex queries.Follow-up question: How do you build a sliding rate limit that does not grow in memory with the number of requests?
- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: Redis
- Source: redis.io
8. Redis was killed with
kill -9. How much data do you lose with default RDB, with AOFeverysecand with AOFalways? Why canBGSAVEon an instance with 20 GB of data on a 32 GB server bring it down?Answer
RDB is a snapshot via
forkaccording to thesaverules, so everything after the last snapshot is lost, that is, minutes of data. AOFeverysecloses about a second,alwaysalmost nothing at the cost of throughput. Recommendation: AOF together with an RDB preamble, which in Redis 7 is multi part AOF.forkblocks the main thread while page tables are copied, and copy on write during writes can double memory use. So you need RAM headroom, Transparent Huge Pages disabled andvm.overcommit_memory = 1.Follow-up question: Does
appendfsync alwaysmake Redis as reliable as PostgreSQL if there is a replica?- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: Redis
- Source: redis.io
9. How does Redis Sentinel differ from Redis Cluster? What does each do when a master fails, how many nodes are needed at minimum and when would you choose one or the other?
Answer
Sentinel gives HA for a single master with replicas and no sharding: you need at least three sentinels, the failover decision is made by quorum, and the client asks a sentinel for the master address. Cluster splits data across 16384 hash slots between masters, nodes exchange gossip, there are at least three masters, and failover is decided by a vote of the masters. Cluster limits: multi key commands only within a single slot, only DB 0, and the client must support clustering. The choice depends on the data volume and the throughput of one node, and in both cases replication is async.
Follow-up question: Why can't you put sentinels only on the same two servers as the master and the replica?
- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: Redis
- Source: redis.io
10. Code takes a lock with
SET key 1 NX PX 10000and runsDEL keyafter the work. The task sometimes takes 15 seconds, and the logs show two workers running it at once. Find the two mistakes and fix them.Answer
The first mistake: the 10 second TTL is shorter than the 15 second work, so the lock expires during execution and another worker takes it. The second:
DELwithout an owner check can delete someone else's lock, because the value is the same for everyone. The fix is to store a unique token and release the lock with a Lua compare and delete. You also need to extend the TTL while the work continues and stop the work if the extension fails. Even so a process pause can outlive the TTL, so correctness needs a fencing token or idempotency.Follow-up question: How does a fencing token protect against a worker that woke up after a pause and who must check it?
- Course: Databases: going deeper
- Knowledge base: Redis
- Source: redis.io
11. Design MongoDB collections for a blog: posts, authors, comments (up to 200 thousand on a popular post), tags. What do you embed in a document, what do you reference and what antipatterns stop you?
Answer
The principle: what is read together is stored together, and the schema follows the queries. I embed tags and brief author data in the post, accepting duplication, and keep authors in a separate collection. I move comments to their own collection with an index on
postIdand time, because an unbounded array would hit the 16 MB document limit and be rewritten constantly. Subset pattern: the post holds the latest comments and a counter. It is worth embedding a bounded one to few relation and referencing the unbounded, shared or independently changing.Follow-up question: How do you update an author's name across thousands of posts and what will readers see during the update?
- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: MongoDB
- Source: mongodb.com
12. Query:
db.orders.find({ status: 'paid', total: { $gt: 100 } }).sort({ createdAt: -1 }).limit(20). Which compound index do you create? Explain the ESR rule and how to check withexplain.Answer
ESR: equality fields first (Equality), then sort fields (Sort), then range fields (Range). Here that is the index
{ status: 1, createdAt: -1, total: 1 }: the sort comes from the index, and withlimit(20)the scan stops early. The orderstatus,total,createdAtwould force an in memory sort over all matches. You check withexplain('executionStats'): the plan should showIXSCANwith noSORTstage, andtotalKeysExaminedandtotalDocsExaminedshould be close tonReturned. ESR is a guideline, and for a very selective range another order may be better.Follow-up question: How does the index choice change if
statusbecomes$in: ['paid', 'shipped']?- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: MongoDB
- Source: mongodb.com
13. What are strong and eventual consistency? Name the intermediate guarantees: read your writes, monotonic reads, causal. For each, give a product bug that appears without it.
Answer
Strong means every read sees the latest committed write, eventual means replicas converge in the end but with no time bound. Read your writes: your own write is visible at once; without it a user sees the old name after saving a profile. Monotonic reads: data does not "go back" on the next read; without it a comment disappears when the page is refreshed on another replica. Causal: an answer is not visible before its question. All of this is implemented by routing to the primary, tracking the LSN or pinning a session to a replica. A stronger guarantee costs latency and load on the primary, so it is chosen per screen.
Follow-up question: How do these guarantees survive a Redis cache between the application and the database?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: jepsen.io
14. How is data modeled in Cassandra and why do you design queries first and tables after? What are the partition key and the clustering key?
Answer
Cassandra supports no
JOINand no efficient arbitrary filters, so a table is designed for a specific query, and data for different queries is duplicated in different tables. The partition key determines which nodes hold the rows: a hash of the key points to a node, and all rows of one partition are stored together. The clustering key sets the order of rows inside a partition, so range queries and sorting by it are cheap. A good partition key spreads data evenly and does not make a partition too large. A query without the partition key requiresALLOW FILTERINGand is usually a modeling mistake.Follow-up question: What is a hot partition and how do you avoid it, for example for sensor data?
- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: Apache Cassandra
- Source: cassandra.apache.org
Senior questions
1. Explain the isolation levels in PostgreSQL. Which is the default? Can
READ UNCOMMITTEDshow uncommitted data? What must the application do on levels aboveREAD COMMITTED?Answer
The default is
READ COMMITTED: each statement sees its own snapshot taken when the statement starts. In PostgreSQLREAD UNCOMMITTEDbehaves likeREAD COMMITTED, so a dirty read is impossible.REPEATABLE READgives one snapshot for the whole transaction, and phantom reads are absent here too.SERIALIZABLEadds anomaly detection through SSI. OnREPEATABLE READandSERIALIZABLEa transaction can fail with error40001, so the application must retry it in full.Follow-up question: Why do you have to retry the whole transaction and not just the last query?
- Course: Databases: going deeper
- Lesson: Transactions, ACID and isolation levels
- Knowledge base: PostgreSQL
- Source: postgresql.org
2. Two parallel purchases each take 10 from a balance of 100, but afterward the balance is 90, not 80. The code reads the balance, computes in the application and writes
UPDATE ... SET bal = $1. Why does this happen underREAD COMMITTEDand how do you fix it?Answer
Both transactions read 100 and both wrote 90, so one update was lost: this is a lost update. The fixes: an atomic
UPDATE acc SET bal = bal-10, so the database computes the value. OrSELECT ... FOR UPDATE, which locks the row until the end of the transaction, or optimistic locking with aversioncolumn. A conditionalUPDATE ... WHERE bal >= 10is also safe: the second statement waits, rereads the row and rechecks the condition. If it changed 0 rows, the application must treat it as a refusal.Follow-up question: When is optimistic locking better than
FOR UPDATE?- Course: Databases: going deeper
- Lesson: Transactions, ACID and isolation levels
- Knowledge base: PostgreSQL
- Source: postgresql.org
3. Rule: at least one doctor must remain on shift. Two doctors ask for leave at the same time: each transaction on
REPEATABLE READchecks that two are on duty and updates its own row. What happens and how do you fix it?Answer
Both transactions commit, and zero doctors remain on duty. This is write skew: the transactions change different rows, so there is no write conflict, and
REPEATABLE READdoes not notice the dependency on what was read.SERIALIZABLEdetects the dangerous dependency and aborts one transaction with code40001, which must be retried. The alternative is explicit locking, for exampleFOR UPDATEon the rows being checked, or a shared lock row. A plainUNIQUEdoes not help here, because the rule is about a count, not uniqueness.Follow-up question: Why does
UNIQUEnot fit this rule?- Course: Databases: going deeper
- Lesson: Transactions, ACID and isolation levels
- Knowledge base: PostgreSQL
- Source: postgresql.org
4. The logs show
ERROR: deadlock detected(40P01). It is a money transfer: each transaction updates the sender account, then the recipient. How does this arise, what does PostgreSQL do and how do you fix it?Answer
A transfer from A to B and simultaneously from B to A: each transaction holds a lock on one row and waits for the other. PostgreSQL detects the wait cycle after
deadlock_timeout(1 second by default) and aborts one transaction while the other continues. The main fix is to always lock rows in the same order, for example the account with the smalleridfirst. The application must also retry a transaction that got40P01. A deadlock is possible even within a singleUPDATE ... WHERE id IN (...)if the order of visiting rows differs.Follow-up question: Why can
UPDATE ... WHERE id IN (...)with many rows also deadlock?- Course: Databases: going deeper
- Lesson: Transactions, ACID and isolation levels
- Knowledge base: PostgreSQL
- Source: postgresql.org
5. Build a simple task queue on a PostgreSQL table for several workers. Why
SKIP LOCKED, why not keep a transaction open for the duration of the task, and how do you handle a worker crash?Answer
A worker takes a task with
SELECT ... FOR UPDATE SKIP LOCKED LIMIT 1, so different workers take different rows and do not wait for each other. WithoutSKIP LOCKEDthey would all queue up behind the same first row. The claim must be short: in a transaction you set the status torunningand commit, then do the work separately. A long transaction holds a connection and a row and hindersVACUUM. You handle a worker crash throughlocked_atwith a heartbeat or a timeout that returns the task to the queue, and the task itself must be idempotent.Follow-up question: When does such a queue stop being a good solution and it is time for a separate broker?
- Course: Databases: going deeper
- Lesson: Transactions, ACID and isolation levels
- Knowledge base: PostgreSQL
- Source: postgresql.org
6. An
ADD COLUMNmigration on a small table takes milliseconds, but in production it caused 30 seconds of downtime: allSELECTs hung. At that time only one long report was running. What happened and how do you protect against it?Answer
ALTER TABLEtakes anACCESS EXCLUSIVElock, which conflicts even withSELECT. It waits until the long report holdingACCESS SHAREfinishes. All new queries line up behind theALTER, even those compatible with the report, so the table effectively stands still. Protection:SET lock_timeoutfor the migration with retries, so it gives up quickly instead of blocking the queue. Additionally you limit long transactions and run migrations in quiet hours.Follow-up question: Which other DDL commands take
ACCESS EXCLUSIVEand which have variants without it?- Course: Databases: going deeper
- Knowledge base: PostgreSQL
- Source: postgresql.org
7. A migration with
CREATE UNIQUE INDEX CONCURRENTLYfirst failed because of a transaction block, then because of duplicates, and a rerun says the relation already exists. Explain each problem and the right procedure.Answer
CONCURRENTLYcannot run inside a transaction, while the migration tool opens one by default, so it has to be disabled for this migration. A plainCREATE INDEXblocks writes andCONCURRENTLYdoes not, but it is slower and makes several passes. If duplicates were found during the build, the index staysINVALID: reads do not use it but writes still maintain it. That is why a rerun sees an existing object. The right way:DROP INDEX CONCURRENTLY, remove the duplicates and repeat. Check withpg_index.indisvalid.Follow-up question: Why can
CONCURRENTLYhang for a very long time even on a small table?- Course: Databases: going deeper
- Knowledge base: PostgreSQL
- Source: postgresql.org
8. A frequently updated table grows and queries slow down even though autovacuum is running.
VACUUM VERBOSEsays: 0 removed, 50000 dead but not yet removable. What is in the way, how do you find the cause and willVACUUM FULLhelp?Answer
Dead versions cannot be removed while the oldest snapshot may still see them. Typical causes: a long transaction or a session in
idle in transactionstate, an inactive replication slot, a prepared transaction. You look inpg_stat_activitybyxact_start, and also inpg_replication_slotsandpg_prepared_xacts. A plainVACUUMfrees space inside the table but does not return it to the operating system.VACUUM FULLrewrites the table underACCESS EXCLUSIVE, which means downtime, and without removing the blocker it will not help either.Follow-up question: What is transaction ID wraparound and how does
VACUUMprotect against it?- Course: Databases: going deeper
- Knowledge base: PostgreSQL
- Source: postgresql.org
9. The PostgreSQL logs say:
WARNING: database "app" must be vacuumed within 10000000 transactions. What does it mean, what happens next and what do you do now?Answer
The transaction identifier is 32 bits, so old rows have to be "frozen", otherwise they become "in the future" and vanish from sight. A forced anti-wraparound
VACUUMstarts atautovacuum_freeze_max_age(200 million by default). When about 3 million transactions remain to the limit, the database stops issuing newxids and refuses writes. You look for blockers: long transactions, abandoned replication slots, prepared transactions,hot_standby_feedback. Then you remove the blocker and runVACUUMon the oldest tables, without panicking about a restart.Follow-up question: How do you set up an alert to learn about the problem weeks ahead and not 10 million transactions before a stop?
- Course: Databases: going deeper
- Knowledge base: PostgreSQL
- Source: postgresql.org
10. The
eventstable grows by 100 million rows a month and must keep 6 months. Propose partitioning. How does it simplify deleting old data, what are the limitations and when does it not speed up a query?Answer
Range partitioning by
created_at, one partition per month. Old data is removed withDROPorDETACHof a partition instead ofDELETE: it is instant, with no bloat and no work forVACUUM. Limitations: unique and primary keys must include the partition key. Partition pruning works only whenWHERErestricts the key directly. A query without the partition key scans all partitions and may even be slower than without partitioning.Follow-up question: How do you move an existing large table to partitioning without downtime?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: postgresql.org
11. The
userstable is being moved to partitioning bycreated_at, andCREATE UNIQUE INDEX ON users (email)fails: unique constraint on partitioned table must include all partitioning columns. Why, and how do you keepemailunique?Answer
PostgreSQL has no global indexes: each partition has its own index, and uniqueness is guaranteed only within a partition. So the partition key must be part of
UNIQUE,PRIMARY KEYand foreign keys referencing such a table. To keepemailunique you move it into a separate unpartitioned table with a unique index or choose a different partition key. It is also worth questioning the idea itself: auserstable is rarely large enough to need partitioning.Follow-up question: How do you migrate a live table of 1 billion rows into a partitioned one without downtime?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: postgresql.org
12. Why pgbouncer if the application already has a pool? How do session, transaction and statement modes differ and what breaks in transaction mode?
Answer
Each PostgreSQL connection is a separate process, so their number is expensive, and
max_connectionsis 100 by default. An application pool does not limit the total across many instances or serverless functions, while pgbouncer does. In session mode a connection is held for the whole session, in transaction mode for one transaction, in statement mode for one query, and multi statement transactions are forbidden. In transaction modeSET,LISTEN, session advisory locks,WITH HOLDcursors and per session temporary tables do not work. Prepared statements work ifmax_prepared_statementsis nonzero.Follow-up question: How do you use
pg_advisory_lockwith transaction pooling?- Course: Databases: going deeper
- Knowledge base: PostgreSQL
- Source: pgbouncer.org
13. MySQL 8.4, InnoDB. Session A runs
SELECT ... WHERE v BETWEEN 10 AND 20 FOR UPDATEand does not commit, session B inserts a row withv = 15. What happens underREPEATABLE READand underREAD COMMITTED, and how does it differ from PostgreSQL?Answer
On
REPEATABLE READ(the default level in InnoDB) theINSERTblocks and may fail with a lock wait timeout, because the query takes next key and gap locks on the range. OnREAD COMMITTEDgap locks are disabled for such queries, so the insert goes through. In PostgreSQLFOR UPDATElocks only existing rows, so the new row is inserted, and phantoms are caught bySERIALIZABLE. In MySQL onREPEATABLE READa plainSELECTreads a snapshot, whileFOR UPDATEreads the latest committed version.Follow-up question: Why does an
UPDATEin MySQL without an index on the condition lock almost the whole table?- Course: Databases: going deeper
- Lesson: Transactions, ACID and isolation levels
- Knowledge base: MySQL, PostgreSQL
- Source: dev.mysql.com
14. A nightly bonus accrual job is started by cron in each of 3 instances and credits the bonuses three times. There is no Redis, only PostgreSQL. How do you guarantee that only one instance runs the job, and is a lock alone enough?
Answer
pg_try_advisory_lockreturnsfalseimmediately if the lock is taken, so the losing instances simply skip the run. A session lock lives with the connection, while a transaction lock (pg_try_advisory_xact_lock) is released at the end of the transaction. PgBouncer in transaction mode breaks session advisory locks, because the connection changes between statements. But a lock gives only mutual exclusion, not run once semantics: if the first instance finished, a second one can start after it. So you also need a run marker withUNIQUEon the date or job identifier.Follow-up question: How do you detect that a job that took the marker died halfway and who must finish it?
- Course: Databases: going deeper
- Knowledge base: PostgreSQL
- Source: postgresql.org
15. The API writes to the primary while reads are spread over two async replicas. A user changes the name in a profile, the page reloads and shows the old one, and a second later everything is fine. Explain the cause and design read your writes.
Answer
The cause is replication lag: the read landed on a replica that has not replayed the WAL yet. Options: after a write, pin that user's reads to the primary for a few seconds (sticky), or track the LSN. For the LSN, take
pg_current_wal_lsn()after the write and read only from a replica whosepg_last_wal_replay_lsn()is not lower. For critical writes you can enableremote_applyat the price of latency. Transactions and sensitive screens always go to the primary, and lagging replicas are removed from the pool.Follow-up question: How do you guarantee monotonic reads so a comment does not vanish on the next refresh when the request lands on another replica?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: postgresql.org
16. At 3 AM an alert: the primary disk is 95% full,
pg_waltakes 400 GB instead of the usual 2 GB. A week ago a replica was removed simply by stopping its container. What happened, how do you find the cause and fix it?Answer
A replication slot retains WAL until the consumer confirms the read position. The slot of a stopped replica is inactive, so WAL accumulates without limit. The cause is visible in
pg_replication_slots: the difference betweenpg_current_wal_lsn()andrestart_lsn. The fix ispg_drop_replication_slot, after which WAL starts to be cleaned; you must not delete files frompg_walby hand. To prevent a repeat, setmax_slot_wal_keep_sizeand add alerts on inactive slots and on the size of retained WAL. The same happens with logical slots (for example Debezium) and with a failingarchive_command.Follow-up question: Why might a replica without a slot fail to catch up with the primary after a long downtime and what does
wal_keep_sizedo then?- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: postgresql.org
17. Three PostgreSQL nodes under Patroni, async replication. The network splits: the primary ends up in the minority, while two replicas and the majority of etcd are on the other side. What happens on each side? Are two primaries possible and how much data can be lost?
Answer
The isolated leader cannot renew the key in the DCS and demotes itself after the TTL, while the other side elects a new leader. There is a window before the TTL when the old primary still accepts writes, and those writes are lost during
pg_rewind. A real split brain is possible when Patroni itself hangs, so fencing through a watchdog is set up.synchronous_modegives RPO 0 at the cost of latency, and strict mode stops writes without a sync replica. HAProxy with a/primarycheck does not let traffic reach a node that is not the leader.Follow-up question: What happens to clients that held a TCP connection directly to the old primary, bypassing HAProxy?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: patroni.readthedocs.io
18. After sharding by
tenant_idone of 8 nodes sits at 85% CPU and the rest at 20%. How do you find the cause and what do you do this week and over a quarter?Answer
Diagnosis:
pg_stat_statementson the hot node, the distribution of rows and queries by tenant and shard. Typical causes: one very large tenant, several large ones in one shard, a hot row, uneven shard placement. This week I move a shard to another node or isolate the large tenant in its own shard. Over a quarter I build many small logical shards, a directory for moving tenants and a composite key for a tenant that does not fit on a single node.Follow-up question: How do you move a tenant to another node without stopping its writes for more than a second?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: docs.citusdata.com
19. Application level sharding:
shard = hash(tenant_id) % 4. You need to add 4 more nodes without downtime. Why is changing to% 8bad and how do you design the data move?Answer
Changing the modulus moves a huge share of keys at once: going from 4 to 8 moves about half. It is better to have a fixed number of logical shards (vshards), for example 256, and a map from vshard to node in a directory. Then adding a node means moving a few vshards, not recomputing all of them. The move: copy via logical replication, catch up, a short freeze of writes for that vshard and a switch of the map. After verifying with counters or checksums, the old data is deleted later. Consistent hashing also reduces movement, but a directory is easier to control.
Follow-up question: How do you prevent one of hundreds of application instances with a stale map from writing to the old node?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: docs.citusdata.com
20. What becomes hard after sharding: queries without the shard key, uniqueness, cross shard transactions, pagination, reports, migrations? How does Citus cope with it, and how does an application that shards itself?
Answer
A query without the shard key is scatter gather: the latency of the slowest shard and the load multiplied by their number.
ORDER BYwithLIMITacross shards requires each to return its top and then a merge, and a deepOFFSETis very expensive. Uniqueness is guaranteed only within a shard, and a global one needs a separate lookup table. Cross shard transactions: in Citus this is 2PC, in custom sharding a saga or outbox, and it is better to avoid them by design. ForJOINs there are colocated tables and reference tables, reports move to an analytical store, and DDL runs on all shards.Follow-up question: How do you make global IDs without a central sequence while keeping approximate time ordering?
- Course: Databases: going deeper
- Lesson: Replication, sharding and partitioning
- Knowledge base: PostgreSQL
- Source: docs.citusdata.com
21. A single 4 GB Redis holds the API cache and BullMQ queues. At a peak some tasks vanished without a trace. After a settings change the next day the API started getting
OOM command not allowed when used memory > 'maxmemory'. What happened and how do you separate the load?Answer
In the first case an
allkeys-lruorvolatile-*policy evicted BullMQ task keys, because Redis treats them as ordinary data. In the secondnoevictionwas enabled: whenmaxmemoryis reached, writes are rejected with OOM while reads work. The separation: the cache in its own instance withallkeys-lfuand TTLs, the queues in another withnoevictionand cleanup of completed tasks. Memory is estimated withMEMORY USAGEon samples. You leave headroom for fork, the replication backlog, client buffers and fragmentation, somaxmemoryis set to about 60 to 70% of RAM.Follow-up question: How do you find the key prefixes that ate memory without blocking production Redis?
- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: Redis
- Source: redis.io
22. How does Redis Cluster decide which node a key lives on? What do
MOVEDandASKreplies mean, how do they differ and what does the client do with them?Answer
A key's slot is
CRC16(key) mod 16384, and a hash tag in curly braces hashes only its content, which allows colocating keys.MOVEDmeans the slot permanently belongs to another node: the client updates its slot map and repeats the request.ASKmeans the slot is currently being migrated and the needed key is already on the target node: the client sendsASKINGand the command once, without updating the map. During migration the slot is in MIGRATING and IMPORTING states, and multi key commands can getTRYAGAIN. Clients such as ioredis handle redirects themselves.Follow-up question: What does the application see in the first seconds after a master failover in a cluster and how do you avoid turning it into a retry storm?
- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: Redis
- Source: redis.io
23. After a failover in Redis Sentinel the writes of the last few seconds vanished, even though the client got
OKfor each. How did that happen? What domin-replicas-to-writeandWAITgive and why do they not make Redis a lossless store?Answer
Redis replication is asynchronous: the master replies
OKbefore the replica received the write. If the master died or was cut off by the network, the newly elected master does not have those writes, and the old one, when it returns, becomes a replica and discards its own.min-replicas-to-writetogether withmin-replicas-max-laglimit the loss window at the cost of availability.WAITwaits for replica acknowledgements, but it is neither consensus nor fsync, so a failover can still choose a replica without the write;WAITAOFexists for fsync. Data that cannot be lost is kept in PostgreSQL, and Redis stays for derived data.Follow-up question: How does BullMQ survive the loss of a few tasks on a Redis failover and what must you add in the application?
- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: Redis
- Source: redis.io
24. What is Redlock and what is the point of Martin Kleppmann's critique? When is
SET NXon a single Redis enough, and when is neither it nor Redlock suitable?Answer
Redlock takes the lock on five independent masters: the lock is considered acquired if the majority replied within a time well below the TTL. Kleppmann criticizes its dependence on time: a process pause, clock drift or a restart without fsync can give two clients the lock at once, and there are no fencing tokens. It is important to distinguish a lock for efficiency (duplicate work only wastes resources) from one for correctness (duplication corrupts data). For efficiency a single Redis is enough. For correctness you need consensus (etcd, ZooKeeper) and fencing or a constraint in the store itself.
Follow-up question: What does a fencing token look like in a setup where the worker writes to PostgreSQL?
- Course: Databases: going deeper
- Knowledge base: Redis
- Source: martin.kleppmann.com
25. Design fencing tokens for workers that update a document in PostgreSQL under a lock. Where does the token come from, who checks it and what happens to a worker that woke up after a pause with an already expired lock?
Answer
The token grows monotonically with each lock acquisition: it can be an
INCRin Redis, a revision in etcd or a sequence. The check is done by the store itself atomically with the write:UPDATE doc SET ..., fence = $t WHERE id = $id AND fence < $t. Zero changed rows means a stale lock, and the worker stops without retrying. A check in the worker itself before the write has a race, because a pause can happen between the check and the write. For an external resource that does not check the token, use conditional writes (If-Match, ETag), an idempotency key or a lock in the same PostgreSQL transaction.Follow-up question: How do you rework this if the worker writes to several tables and to Kafka?
- Course: Databases: going deeper
- Knowledge base: Redis, PostgreSQL
- Source: martin.kleppmann.com
26. Explain write concern and read concern in a MongoDB replica set. What happens to a
w:1write after a failover? How do you get read your writes when reading from a secondary?Answer
A replica set is a primary and secondaries replicating through the oplog, and the primary is elected by majority.
w:1acknowledges the write only on the primary,w:'majority'waits for the majority (the default since MongoDB 5.0), andj:truerequires a journal write. Aw:1write that did not reach the majority is rolled back into rollback files after a failover. Read concern (local,majority,linearizable,snapshot) and read preference decide what is read and from which node. For read your writes on a secondary you use a causally consistent session withmajority.Follow-up question: What happens to
w:'majority'if only one of three members is alive and why does an arbiter hurt here?- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: MongoDB
- Source: mongodb.com
27. MongoDB supports multi document transactions. Why does the documentation advise against building the data model on them? What does a transaction cost, which errors must be handled and how do you do without one?
Answer
An operation on a single document is always atomic, and transactions appeared in 4.0 for replica sets and in 4.2 for sharded clusters. The cost: a snapshot and locking in the WiredTiger cache, write conflicts, a 60 second time limit and 2PC across shards. The errors
TransientTransactionErrorandUnknownTransactionCommitResultare handled by retrying,session.withTransactiondoes that itself, so the callback must tolerate being executed again. Without a transaction you keep the invariant in one document, use a conditionalupdateOnewith amatchedCountcheck, or use an outbox. If many scenarios need transactions, PostgreSQL may suit you better.Follow-up question: How do you implement a transfer between two accounts in MongoDB without a transaction so that a failure does not lose money?
- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: MongoDB
- Source: mongodb.com
28. A candidate says: PostgreSQL is CA, MongoDB is CP, Cassandra is AP. What is wrong with that claim? Explain CAP and PACELC in a way that helps configure a real system.
Answer
CAP: during a network partition you cannot have both linearizability and availability of every node, and the partition (P) is not chosen, it happens. "CA" means a single node, and the labels depend on configuration, not on the product. PACELC adds that even without a partition (Else) you must choose between latency and consistency, and that is a daily choice. Examples: sync replication with
remote_applyversus reading from async replicas in PostgreSQL,majorityversus a secondary in MongoDB, the quorum R+W>N in Cassandra. The decision is made for each class of data: what happens if a read is stale and if an acknowledged write is lost.Follow-up question: What is linearizability and why does a read from the primary after
COMMITgive it and one from an async replica not?- Course: Databases: going deeper
- Lesson: NoSQL models and how to choose a database
- Knowledge base: PostgreSQL, MongoDB, Apache Cassandra
- Source: martin.kleppmann.com
29. Cache aside: a read takes a key from Redis, on a miss reads PostgreSQL and does
SETwith a 1 hour TTL. An update writes to PostgreSQL and then doesDELon the key. Once every few days a product shows an old price for a whole hour. Reproduce the sequence and propose a fix.Answer
The sequence: on a miss the reader read the old price from the database before the commit, the writer committed the new price and deleted the key, and only after that the reader wrote the old price into the cache for an hour. The same effect comes from
DELbefore the commit or from reading a lagging replica on a miss. Fixes: store a version in the value and write only a newer one (via Lua), use leases, do a delayed second delete or use a short TTL. IfDELfailed because Redis was unavailable, you need a retry or invalidation through CDC.Follow-up question: How do you build invalidation that does not depend on every place in the code remembering to do
DEL?- Course: Databases: going deeper
- Knowledge base: Redis, PostgreSQL
- Source: usenix.org
Train these questions
Study the cards, answer by text or voice and get an AI grade. Needs a signed in account.
Answer out loud and get graded
Here you get the questions with model answers. In the ShortLesson app you answer yourself, by text or voice, and AI grades the answer against the rubric and asks a follow-up, like a real interviewer.
The app is coming to the stores soon.
Other topics
- JavaScript interview questions and answers
- TypeScript interview questions and answers
- React interview questions and answers
- Frontend performance interview questions and answers
- Node.js interview questions and answers
- HTTP interview questions and answers
- SQL interview questions and answers
- Git interview questions and answers
- Algorithms interview questions and answers
- System design interview questions and answers
- DevOps interview questions and answers
- Web security interview questions and answers
- Testing interview questions and answers