ReliabilityOps ExpertiseToolsField NotesContact
← Field Notes HADR · War story

The log is full. Don't flip to SIMPLE.

July 2026 · 7 min read · war story

Error 9002. "The transaction log for database 'OrdersDB' is full due to 'LOG_BACKUP'." The app is throwing errors, writes are failing, someone in the incident channel is typing fast, and there is one piece of advice that will inevitably show up within about ninety seconds:

"Just switch it to SIMPLE recovery to clear the log."

Please don't. Or at least, don't do it first. Flipping to SIMPLE recovery mode is the panic move, and most of the time it's both unnecessary and destructive. Here's the calmer path. And why it usually works.

The log isn't full. It's waiting.

A transaction log that won't clear is almost never "out of things to delete." It's log that can't be reused yet because something still needs it. SQL Server will tell you exactly what, if you ask the one column that matters:

SELECT name, log_reuse_wait_desc, recovery_model_desc
FROM sys.databases WHERE name = 'OrdersDB';

That log_reuse_wait_desc is the whole diagnosis. It's not a symptom. It's the reason. And each reason has a fix that is almost never "destroy your recovery chain."

Read the reason, then wait or nudge. Don't nuke

LOG_BACKUP

The most common one, and the most embarrassing to overreact to. In FULL recovery, the log holds everything since the last log backup so it can be shipped. If nobody's taken a log backup. Or the log-backup job failed. The log grows until it fills. The fix is not SIMPLE. The fix is: take a log backup. One BACKUP LOG and the space frees, the chain stays intact, and you go find out why the scheduled job stopped.

ACTIVE_TRANSACTION

Someone left a transaction open. A forgotten BEGIN TRAN, a giant batch mid-flight, an ETL that's still running. The log can't clear past the oldest open transaction. Find it:

-- who is holding the log open, and how long
DBCC OPENTRAN('OrdersDB');
SELECT session_id, database_transaction_log_bytes_used, database_transaction_begin_time
FROM sys.dm_tran_database_transactions WHERE database_id = DB_ID('OrdersDB');

Now here's the part people miss in the panic: sometimes the right move is to watch it. If that open transaction is a legitimate batch that's 80% done and committing steadily, killing it means a rollback that can take longer than just letting it finish. And a rollback generates more log, not less. If you can buy a little space (below), often the cleanest resolution is to let the transaction commit, at which point the log clears on the very next backup or checkpoint. Watch database_transaction_log_bytes_used. If it's climbing toward a known end, it's working; if it's flat and old, then go find the human who owns it.

AVAILABILITY_REPLICA

This is the AG one, and it's where SIMPLE isn't even an option. You can't put a database in an availability group into SIMPLE recovery. The log can't clear because a secondary hasn't hardened it yet: the send queue or the redo queue is backed up, and the primary is dutifully holding the log until the secondary catches up. Watch the queues:

SELECT db_name(database_id) db, synchronization_state_desc,
       log_send_queue_size, redo_queue_size
FROM sys.dm_hadr_database_replica_states WHERE is_local = 0;

If the secondary is just behind. A network blip, a slow-disk redo. The honest fix is to let it catch up. The queue drains, the secondary hardens the log, and the primary releases it automatically. Your job is to keep the primary alive long enough for that to happen, not to sever the AG in a panic.

How to buy time without breaking anything

The thing that makes waiting possible is temporary space. You are allowed to grow the log. Or add a second log file. To survive the next few minutes while the real cause resolves:

ALTER DATABASE OrdersDB
  MODIFY FILE (name = OrdersDB_log, size = 20GB);

This buys headroom without touching the recovery model, the backup chain, or the AG. Once the log backup runs, or the transaction commits, or the secondary catches up, that space becomes reusable and you can shrink it back later in a calm moment. You bent; you didn't break.

The rule: 9002 is a "read log_reuse_wait_desc and address the actual reason" problem, not a "flip to SIMPLE" problem. SIMPLE breaks point-in-time recovery, breaks the log-backup chain, and isn't even legal inside an AG. Reach for it only when you've confirmed the database genuinely doesn't need the log chain. Never as the reflex.

Why the reflex is so tempting. And so costly

SIMPLE recovery feels like it works because it does clear the log, instantly. That's the trap. What you don't see in that moment is that you just broke the ability to restore to any point in time between your last full backup and now. If corruption or a bad deploy shows up an hour later, the restore option you needed is gone. You truncated the very log that would have let you roll forward. You traded a five-minute space problem for an unbounded data-loss exposure, and nobody notices until the day it matters.

The calm version costs you a query, a backup or a patient wait, and maybe a temporary file grow. The panic version costs you your recovery chain. Read the reason. Address the reason. The log was never the problem. It was just telling you what the actual problem was.

The kit's Detect and Errors tabs read log_reuse_wait_desc and route you to the right fix.
Open the Triage tool →