Skip to content

Issue 4855: fail closed on entry log flush failure - #1

Closed
yangxianjungree wants to merge 233 commits into
masterfrom
fix-4855-entrylog-flush-failure
Closed

Issue 4855: fail closed on entry log flush failure#1
yangxianjungree wants to merge 233 commits into
masterfrom
fix-4855-entrylog-flush-failure

Conversation

@yangxianjungree

@yangxianjungree yangxianjungree commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Descriptions of the changes in this PR:

This PR changes entry log flush failure handling so the bookie fails closed when the entry-log write path becomes uncertain. It also adds a real client e2e reproducer for the failure path.

Motivation

Entry-log write, flush, rotation, or allocation failures can leave the bookie in an uncertain state. Continuing to serve after those failures risks silent corruption or inconsistent recovery.

Changes

  • Introduce EntryLogWriteException as the fatal boundary for entry-log-level failures.
  • Make SyncThread, BookieImpl, DbLedgerStorage, SingleDirectoryDbLedgerStorage, and SortedLedgerStorage stop the bookie on these failures.
  • Keep NoWritableLedgerDirException behavior unchanged.
  • Add a real BookKeeper client e2e that reproduces the flush-failure path and verifies client-visible failure plus bookie shutdown.

Master Issue: apache#4855

Tests:

  • mvn -pl bookkeeper-server -am -Dtest=DbLedgerStorageEntryLogFlushFailureE2ETest -Dsurefire.failIfNoSpecifiedTests=false test
  • mvn -pl bookkeeper-server -am -Dtest=DbLedgerStorageEntryLogFlushFailureE2ETest,BookieImplTest,SyncThreadTest,DbLedgerStorageWriteCacheTest -Dsurefire.failIfNoSpecifiedTests=false test

hangc0276 and others added 30 commits March 23, 2023 04:36
### Motivation
apache#3837 introduced group flush add responses triggered by journal sync. However, if we skip writing journals, the add responses won't be flushed to the netty channel and the client will receive write entries timeout.

### Changes
- Flush the add responses when skipping writing journals
- Add tests to cover V2 protocol and skip writing journal cases.

(cherry picked from commit 5deadcc)
…pache#3888)

### Motivation
In checkAllLedgers when the ledger no missing fragments, will miss invoke` lh.closeAsync()` to close ledgerHandler, which cause autorecovery not invoke `unregisterLedgerMetadataListener` to release ledger metadata listeners. Heap memory be used too much and maybe will cause OOM;

<img width="1567" alt="image" src="https://user-images.githubusercontent.com/84127069/227937422-1113af68-9bf3-4466-97fa-d9b7cc5d72be.png">

### Changes
1.  Invoke` lh.closeAsync()` when no missing fragments;

(cherry picked from commit 164417a)
### Motivation
There is a critical CVE-2019-10202 in `org.codehaus.jackson:jackson-mapper-asl`

Detailed paths
Introduced through: org.apache.distributedlog:dlfs@4.16.0-SNAPSHOT › org.apache.hadoop:hadoop-common@3.3.4 › org.apache.avro:avro@1.7.7 › org.codehaus.jackson:jackson-mapper-asl@1.9.2
Fix: No remediation path available.
Introduced through: org.apache.distributedlog:dlfs@4.16.0-SNAPSHOT › org.apache.hadoop:hadoop-common@3.3.4 › com.sun.jersey:jersey-json@1.19 › org.codehaus.jackson:jackson-mapper-asl@1.9.2
Fix: No remediation path available.
Introduced through: org.apache.distributedlog:dlfs@4.16.0-SNAPSHOT › org.apache.hadoop:hadoop-common@3.3.4 › com.sun.jersey:jersey-json@1.19 › org.codehaus.jackson:jackson-jaxrs@1.9.2 › org.codehaus.jackson:jackson-mapper-asl@1.9.2
Fix: No remediation path available.
Introduced through: org.apache.distributedlog:dlfs@4.16.0-SNAPSHOT › org.apache.hadoop:hadoop-common@3.3.4 › com.sun.jersey:jersey-json@1.19 › org.codehaus.jackson:jackson-xc@1.9.2 › org.codehaus.jackson:jackson-mapper-asl@1.9.2
Fix: No remediation path available.

### Changes
Upgrade hadoop-common version from 3.3.4 to 3.3.5 to resolve this CVE

(cherry picked from commit 0171a40)
Execute clean indexes in finally (apache#3772)

(cherry picked from commit 04e572b)
Issue apache#3812

Descriptions of the changes in this PR:

Fix bug and one UT for verification

### Motivation

Fix the bug

### Changes

To avoid the ledger being created twice, add a state check before setting a new state

(cherry picked from commit b062e9e)
…active (apache#3881)

* Fix forceReadOnly

* add unittest

---------

Co-authored-by: gavingaozhangmin <gavingaozhangmin@didiglobal.com>
(cherry picked from commit a6387d1)
* Return activeLogChannel if new create

(cherry picked from commit 5beb894)
### Motivation
When one ledger file is broken, `getEntryLogMetadata` will throw an IllegalArgumentException, which is a RuntimeException and won't be caught in the whole garbage collector execution path. The exception will be caught by the thread SafeRunnable and interrupt the garbage collector, leading to those deleted ledgers can't be recycled and the ledger disk usage up.

```
2023-04-01T00:55:29,497+0000 [GarbageCollectorThread-11-1] INFO  org.apache.bookkeeper.bookie.GarbageCollectorThread - Garbage collector thread forced to perform GC before expiry of wait time.
2023-04-01T00:55:29,498+0000 [GarbageCollectorThread-11-1] INFO  org.apache.bookkeeper.bookie.GarbageCollectorThread - Extracting entry log meta from entryLogId: 17
2023-04-01T00:55:29,498+0000 [GarbageCollectorThread-11-1] INFO  org.apache.bookkeeper.bookie.EntryLogger - Failed to get ledgers map index from: 17.log : Cannot deserialize ledgers map from ledger 9062744587808030975
2023-04-01T00:55:29,553+0000 [GarbageCollectorThread-11-1] ERROR org.apache.bookkeeper.common.util.SafeRunnable - Unexpected throwable caught
java.lang.IllegalArgumentException: Negative position
	at sun.nio.ch.FileChannelImpl.read(FileChannelImpl.java:785) ~[?:?]
	at org.apache.bookkeeper.bookie.BufferedReadChannel.read(BufferedReadChannel.java:93) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.bookie.BufferedReadChannel.read(BufferedReadChannel.java:65) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.bookie.EntryLogger.readFromLogChannel(EntryLogger.java:418) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.bookie.EntryLogger.scanEntryLog(EntryLogger.java:996) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.bookie.EntryLogger.extractEntryLogMetadataByScanning(EntryLogger.java:1136) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.bookie.EntryLogger.getEntryLogMetadata(EntryLogger.java:1045) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.bookie.GarbageCollectorThread.extractMetaFromEntryLogs(GarbageCollectorThread.java:607) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.bookie.GarbageCollectorThread.runWithFlags(GarbageCollectorThread.java:348) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.bookie.GarbageCollectorThread.safeRun(GarbageCollectorThread.java:329) ~[org.apache.bookkeeper-bookkeeper-server-4.14.5.jar:4.14.5]
	at org.apache.bookkeeper.common.util.SafeRunnable.run(SafeRunnable.java:36) ~[org.apache.bookkeeper-bookkeeper-common-4.14.5.jar:4.14.5]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) ~[?:?]
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) ~[?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) ~[?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[?:?]
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[io.netty-netty-common-4.1.77.Final.jar:4.1.77.Final]
	at java.lang.Thread.run(Thread.java:829) ~[?:?]
```

### Changes
Catch the RuntimeException when getting the metadata for each entry log file.

(cherry picked from commit 8309b18)
…ache#3903)

### Motivation
In EntryLocationIndex#removeOffsetFromDeletedLedgers, if there are no ledgers to delete, the key pair will leak.

### Changes
Check the ledgers to deletion Set first, and if the set is not empty, then generate the keys.

(cherry picked from commit ceba605)
* Cleanup CbThreadFactory

* checkstyle

(cherry picked from commit 912896d)
… on all exception paths (apache#3909)

Co-authored-by: lushiji <lushiji@didiglobal.com>
(cherry picked from commit f1657ea)
…mon to resolve CVE-2019-10202, CVE-2023-1370 and CVE-2022-45685 (apache#3911)

### Motivation
#### [CVE-2019-10202](https://www.cve.org/CVERecord?id=CVE-2019-10202)
After upgrading the Hadoop version to 3.3.5, the CVE-2019-10202 still exists.

Detailed paths
Introduced through: org.apache.distributedlog:dlfs@4.16.0-SNAPSHOT › org.apache.hadoop:hadoop-common@3.3.5 › org.apache.avro:avro@1.7.7 › org.codehaus.jackson:jackson-mapper-asl@1.9.13

#### [CVE-2023-1370](https://www.cve.org/CVERecord?id=CVE-2023-1370)
Detailed paths
Introduced through: org.apache.distributedlog:dlfs@4.16.0-SNAPSHOT › org.apache.hadoop:hadoop-common@3.3.5 › org.apache.hadoop:hadoop-auth@3.3.5 › net.minidev:json-smart@2.4.7
Fix: No remediation path available.

#### [CVE-2022-45685](https://www.cve.org/CVERecord?id=CVE-2022-45685)
Detailed paths
Introduced through: org.apache.distributedlog:dlfs@4.16.0-SNAPSHOT › org.apache.hadoop:hadoop-common@3.3.5 › com.github.pjfanning:jersey-json@1.20 › org.codehaus.jettison:jettison@1.1
Fix: No remediation path available.

After checking the code of package `org.apache.distributedlog.fs`, those classes only use `org.apache.hadoop.conf`, `org.apache.hadoop.fs` and `org.apache.hadoop.util` packages. They don't use any Avro-related, json-smart and jersey-json dependencies. It is safe to remove the those dependencies to resolve the CVE issue.
https://github.com/apache/bookkeeper/tree/master/stream/distributedlog/io/dlfs/src/main/java/org/apache/distributedlog/fs

### Changes
Exclude the Avro dependency from `hadoop-common`

(cherry picked from commit 94e15b3)
apache#3913)

### Motivation

PerChannelBookieClient completionObjects occupy a lot of heap space and cannot be recycled.
The figure below shows that the internal table array of ConcurrentOpenHashMap has used space size=0, but the array length is still 16384, and the memory overhead is 65552bytes.
![image](https://user-images.githubusercontent.com/35599757/231114802-db90c49b-d295-46d7-b7db-785035b341f0.png)

![image](https://user-images.githubusercontent.com/35599757/231113930-bd9f3f54-9052-4c0b-9a3f-2fc493632e35.png)

ConcurrentOpenHashMap default DefaultConcurrencyLevel=16. We have hundreds of bookie nodes. Due to the feature of bookie polling and writing, the client and server have long connection characteristics, which will as a result, the memory usage of about 65552 * 16 * 1776 = 1.74GB cannot be recycled, and the space take up by these tables is all size=0 (The broker's owner topic has drifted to other brokers due to Full GC).
![image](https://user-images.githubusercontent.com/35599757/231117087-08c80320-fa71-49c2-a199-cfee3d83ddc5.png)

When the throughput of the pulsar cluster increases and the bookie cluster expands, these memory usage will also increase. Coupled with the unreasonable memory usage in other aspects of pulsar that we know, this will cause the pulsar broker to continuously generate full gc.

### Changes
I think adding autoShrink to completionObjects can reduce this part of memory usage and reduce the frequency of Full GC.

(cherry picked from commit ca33b31)
…he#3917)

### Motivation
When I decommission one bookie (bk3), one ledger replicate failed and blocked decommission process.

This is the auto-recovery log:
```
2023-03-29T06:29:22,642+0000 [ReplicationWorker] ERROR org.apache.bookkeeper.client.LedgerHandle - ReadEntries exception on ledgerId:904368 firstEntry:14 lastEntry:14 lastAddConfirmed:13
2023-03-29T06:29:22,642+0000 [ReplicationWorker] ERROR org.apache.bookkeeper.replication.ReplicationWorker - Received error: -1 while trying to read entry: 14 of ledger: 904368 in ReplicationWorker
2023-03-29T06:29:22,642+0000 [ReplicationWorker] ERROR org.apache.bookkeeper.replication.ReplicationWorker - Failed to read faulty entries, so giving up replicating ledgerFragment Fragment(LedgerID: 904368, FirstEntryID: 0[0], LastKnownEntryID: 14[14], Host: [betausc1-bk-10.betausc1-bk-headless.o-vaxkx.svc.cluster.local:3181], Closed: true)
2023-03-29T06:29:22,644+0000 [ReplicationWorker] ERROR org.apache.bookkeeper.replication.ReplicationWorker - ReplicationWorker failed to replicate Ledger : 904368 for 6 number of times, so deferring the ledger lock release by 300000 msecs
```
The ledger's metadata:
```
ledgerID: 904368
2023-03-29T06:47:56,511+0000 [main] INFO  org.apache.bookkeeper.tools.cli.commands.
client.LedgerMetaDataCommand - LedgerMetadata{formatVersion=3, ensembleSize=3, writeQuorumSize=3,
ackQuorumSize=2, state=OPEN, digestType=CRC32C, password=base64:,
ensembles={0=[bk1:3181, bk2:3181, bk3:3181], 15=[bk1:3181, bk2:3181, bk4:3181]},...}
```

The ledger (904368) has two ensembles, `ensembles={0=[bk1:3181, bk2:3181, bk3:3181], 15=[bk1:3181, bk2:3181, bk4:3181]}`. However, the replication worker got the ledger's LAC is 13, but it got the replication fragment entry range is [0, 14]. When reading entry 14, it failed.

### One question
**Why the ensembles created a new ensemble starting with entryId = 15, but the ledger's lastAddConfirm is 13.**

This question is related to two parts, one is how the new ensemble was created and the other is how the lastAddConfirm was generated.

#### 1. How the new ensemble was created
The ensemble change is controlled on the bookie client side.

When one entry is ready to send to the bookie server, the bookie client will check whether need to do the ensemble change.
https://github.com/apache/bookkeeper/blob/912896deb2e748389e15e74c37539b2ff36302c7/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/PendingAddOp.java#L254

For the above case, when writing entry 15, one bookie is lost, it will trigger the ensemble change and generate the new ensemble: 15=[bk1:3181, bk2:3181, bk4:3181]. However, entry 15 write failed, such as timeout or bookie server rejected the write.

For now, entry 14 is written succeed.

#### 2. How the lastAddConfirm was generated
Due to the ledger being in the `OPEN` state, the ledger handle will send a readLAC request according to the last ensemble to get the ledger's lastAddConfirm.

For the above case, the readLAC request will send to bk1, bk2, and bk4.

For the `V2` protocol (Pulsar uses the V2 protocol to interact with the BookKeeper cluster), the bookie client put the lastAddConfirm EntryId in the next Entry's metadata.
https://github.com/apache/bookkeeper/blob/df4492012cc03682534cbc8dd68dd81163b0c947/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/checksum/DigestManager.java#L134

When we use the `V2` protocol to open an `OPEN` state ledger to read, it will send a readLastAddConfirm request to the bookie server, and the bookie server gets the last entry of this ledger and return to the client.
https://github.com/apache/bookkeeper/blob/df4492012cc03682534cbc8dd68dd81163b0c947/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/ReadLastConfirmedOp.java#L108

However, the bookie client will parse the response entry and get the lastAddConfirm from the entry's metadata. Due to the entry just recording the previous EntryId as the lastAddConfirm, the LedgerHandle got the lastAddConfirm will be the penultimate EntryId of the ledger.

For the above case, the bk1 holds the max entry 14, bk2 holds the max entry 14, and bk4 returns NoSuchEntryException, LedgerHandle gets lastAddConfirm will be `14  - 1 = 13`, not 14.

When the replicator tries to recover the first ensemble 0=[bk1:3181, bk2:3181, bk3:3181] with entry range [0, 14],  reading entry 14 will throw a ReadEntryException due to the lastAddConfirm is 13.
https://github.com/apache/bookkeeper/blob/df4492012cc03682534cbc8dd68dd81163b0c947/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java#L685-L690

### Solution
When encountered that case that
- The ledger is `OPEN`
- The ledger has multiple ensembles
- The ledger's last ensemble doesn't have any entries, which means `lastAddConfirm < last ensemble key - 1`

We should treat the penultimate segment/ensemble of the ledger as an `OPEN` state instead of a closed state.
https://github.com/apache/bookkeeper/blob/df4492012cc03682534cbc8dd68dd81163b0c947/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerFragment.java#L56-L57

After we treat the segment/ensemble as `OPEN` state, the replicator will close the ledger first and replicate it.

(cherry picked from commit eff38e4)
zymap and others added 26 commits June 2, 2025 15:42
…ache#4607)

* Fix the data loss issue that caused by the wrong entry log header
---

# Motivation

We observed numerous errors in the broker that failed to read
the ledger from the bookkeeper; although the ledger metadata
still exists, it was unable to read from the bookkeeper. After
checking the data, we found the ledger located entry log was
deleted by the bookkeeper. We have a data loss issue with the
bookkeeper.

The entry log file was deleted by the Garbage collector because
the entry log file wrote a wrong file header.

And there is an example that the shows the header is wrong:
```
Failed to get ledgers map index from: 82.log : Not all ledgers were found in ledgers map index. expected: -1932430239 -- found: 0 -- entryLogId: 82
```

* Add test
…e#4522)

* Fix region aware placement policy disk weight dose not update.

(cherry picked from commit 55d274b)
…ache#4557)

[fix] Write stuck due to pending add callback by multiple threads (apache#4557)

(cherry picked from commit e47926b)
* fix pendingDeletedLedgers not remove ledger

(cherry picked from commit 0df3caf)
### Motivation & Changes

Upgrade Jetty to 9.4.57.v20241219 to address CVE-2024-6763
Jetty 9.4.57.v20241219 contains backported CVE-2024-6763 fix in jetty/jetty.project#12532 although it's not explicitly mentioned and most security scanners don't yet contain the information that it's been addressed in 9.4.57.
More details:
* jetty/jetty.project#12630
* https://github.com/jetty/jetty.project/releases/tag/jetty-9.4.57.v20241219

Note: The backport is a partial mitigation and Jetty 9.4.57 will continue to be marked as vulnerable. There's a discussion and explanation here: https://gitlab.eclipse.org/security/cve-assignement/-/issues/25#note_2968611

(cherry picked from commit 99eb63a)
### Motivation

We met the GarbageCollectionThread was stopped by some runtime error, but we didn't catch it then, causing the GC to stop.
Such as:
apache#3901
apache#4544

In our case, the GC stopped because of the OutOfDirectMemoryException then the process stopped and the files can not be deleted. But we didn't see any error logs. This PR enhance the log info when an unhandled error happens.
We already have the [PR](apache#4544) fixed that.

And another fix in this PR is to change the Exception to the Throwable in the getEntryLogMetadata.

Here is the error stack:

```
    io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 213909504 byte(s) of direct memory (used: 645922847, max: 858783744)
	at io.netty.util.internal.PlatformDependent.incrementMemoryCounter(PlatformDependent.java:880)
	at io.netty.util.internal.PlatformDependent.allocateDirectNoCleaner(PlatformDependent.java:809)
	at io.netty.buffer.PoolArena$DirectArena.allocateDirect(PoolArena.java:718)
	at io.netty.buffer.PoolArena$DirectArena.newUnpooledChunk(PoolArena.java:707)
	at io.netty.buffer.PoolArena.allocateHuge(PoolArena.java:224)
	at io.netty.buffer.PoolArena.allocate(PoolArena.java:142)
	at io.netty.buffer.PoolArena.reallocate(PoolArena.java:317)
	at io.netty.buffer.PooledByteBuf.capacity(PooledByteBuf.java:123)
	at io.netty.buffer.AbstractByteBuf.ensureWritable0(AbstractByteBuf.java:305)
	at io.netty.buffer.AbstractByteBuf.ensureWritable(AbstractByteBuf.java:280)
	at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1103)
	at org.apache.bookkeeper.bookie.BufferedReadChannel.read(BufferedReadChannel.java:104)
	at org.apache.bookkeeper.bookie.DefaultEntryLogger.extractEntryLogMetadataFromIndex(DefaultEntryLogger.java:1109)
	at org.apache.bookkeeper.bookie.DefaultEntryLogger.getEntryLogMetadata(DefaultEntryLogger.java:1060)
	at org.apache.bookkeeper.bookie.GarbageCollectorThread.extractMetaFromEntryLogs(GarbageCollectorThread.java:678)
	at org.apache.bookkeeper.bookie.GarbageCollectorThread.runWithFlags(GarbageCollectorThread.java:365)
	at org.apache.bookkeeper.bookie.GarbageCollectorThread.lambda$triggerGC$4(GarbageCollectorThread.java:268)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Unknown Source)
```

You can see it get much more memory used here extractEntryLogMetadataFromIndex(DefaultEntryLogger.java:1109). The reason is that the header has the wrong data of the header, which should already be fixed by apache#4607. Then it reading with a wrong map size which could take a lot of memory.

(cherry picked from commit e80d031)
* [fix]BK stay at read_only state even if the disk is empty

* correct the javadoc

* improve test

(cherry picked from commit bde9ff2)
@cursor

cursor Bot commented Aug 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@yangxianjungree yangxianjungree changed the title fix: fail closed on entry log flush failure Issue 4855: fail closed on entry log flush failure Aug 4, 2026
@yangxianjungree

Copy link
Copy Markdown
Owner Author

Closing this fork-to-fork PR because the fix is now submitted upstream against apache/bookkeeper:branch-4.16 in apache#4860.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.