-
Notifications
You must be signed in to change notification settings - Fork 963
BP-46: Data integrity check for running without journal #2936
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
0a94476 to
a0e4253
Compare
a0e4253 to
e977381
Compare
dlg99
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't get very far in the review (didn't look at the tests, how upgrade/downgrade is handled etc). Realistically I'll have time for the second pass only in 2022.
Basic "happy path" that I looked makes sense; I submitted a couple of comments.
| ledgerIndex.clearLimbo(ledgerId); | ||
| } | ||
|
|
||
| private void throwIfLimbo(long ledgerId) throws IOException, BookieException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe do throws .. DataUnknownException (instead of BookieException)? with similar changes wherever it bubbles up (readLastAddConfirmed/...), to make sure that some other Bookie Exception is not handled as a DataUnknown case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A DataUnknownException is thrown which is a subclass of BookieException.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's exactly what I wanted to avoid - some other subclassed exception thrown but handled the same because IDE generated catch for BookieException.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used a subclass of BookieException as I try to follow the existing approaches to things. So I guess the question is why do we have a BookieException with subclasses? Is there a guideline for how to use it correctly or is the use of BookieException in general something we need to start refactoring out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is mostly for the convenience of the IDE code generation (+ documentation) and a rather generic discussion:
if the method throws BookieException, the IDE-generated catch will put one catch block and developer may need to chase code down to understand what specifically can happen.
If the exceptions in throws are specified as "throws DataUnknownException, OperationRejectedException, MetadataStoreException" one can easily see that exception handling might need to be different (or use catch BookieException if it does not).
This one is a private method with limited scope of use but, if its exception is rethrown, more specific exception in throws lets one define more specific exception in the throws of method that uses it.
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/UncleanShutdownDetectionImpl.java
Show resolved
Hide resolved
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/datainteg/DataIntegrityCheck.java
Show resolved
Hide resolved
| /** | ||
| * Ledger in unknown state. | ||
| */ | ||
| int EUNKNOWN = 107; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to bump up CURRENT_PROTOCOL_VERSION?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the current guidelines for modifying the CURRENT_PROTOCOL_VERSION?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| MetadataAsyncIterator iter = new MetadataAsyncIterator(scheduler, | ||
| ledgerManager, MAX_INFLIGHT, ZK_TIMEOUT_S, TimeUnit.SECONDS); | ||
| CompletableFuture<Void> promise = new CompletableFuture<>(); | ||
| Map<Long, LedgerMetadata> ledgersCache = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we be concerned about memory requirements here in case of large disks (to many ledgers/metadata with many segments)? See #1949
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can keep this here, and address the problem in #1949
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/InterleavedLedgerStorage.java
Show resolved
Hide resolved
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/UncleanShutdownDetectionImpl.java
Show resolved
Hide resolved
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/UncleanShutdownDetectionImpl.java
Outdated
Show resolved
Hide resolved
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/UncleanShutdownDetectionImpl.java
Outdated
Show resolved
Hide resolved
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/UncleanShutdownDetectionImpl.java
Outdated
Show resolved
Hide resolved
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/datainteg/DataIntegrityCheck.java
Outdated
Show resolved
Hide resolved
| MetadataAsyncIterator iter = new MetadataAsyncIterator(scheduler, | ||
| ledgerManager, MAX_INFLIGHT, ZK_TIMEOUT_S, TimeUnit.SECONDS); | ||
| CompletableFuture<Void> promise = new CompletableFuture<>(); | ||
| Map<Long, LedgerMetadata> ledgersCache = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can keep this here, and address the problem in #1949
...eper-server/src/main/java/org/apache/bookkeeper/bookie/datainteg/DataIntegrityCheckImpl.java
Show resolved
Hide resolved
...rver/src/main/java/org/apache/bookkeeper/bookie/datainteg/DataIntegrityCookieValidation.java
Show resolved
Hide resolved
bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/PerChannelBookieClient.java
Outdated
Show resolved
Hide resolved
eolivelli
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
I left some minor comments
bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/InterleavedLedgerStorage.java
Show resolved
Hide resolved
bookkeeper-server/src/test/java/org/apache/bookkeeper/bookie/MockLedgerStorage.java
Show resolved
Hide resolved
dlg99
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
@Vanlightly this patch has been around for a while, I believe it is time to commit it. Can you please resolve the conflicts? |
|
The conflict is about an ExceptionCode: int DataUnknownException = -110; How can we solve the conflict ? |
* Data integrity check The integrity check is comprised two parts: a preboot check that is triggered by either an unclean shutdown or an invalid cookie. The preboot check marks any open ledgers as both fenced and in limbo to prevent ledgers affected by potential data loss from being written to. The limbo status prevents NoSuchEntry and NoSuchLedger responses from being sent which avoid ledger truncation from any ledger recovery operations. Finally it sets a storage flag that a full check is required. A new data integrity check service has been to run the full integrity check once the bookie is running. If the service sees that the full check storage flag is set then it runs a full check. This involves scanning the index and comparing it against metadata to discover missing entries. Any missing entries are sourced from peer bookies by the EntryCopier and written to ledger storage. The data integrity check also has a different cookie validation implementation. The following configurations have been added to the conf file: - dataIntegrityCheckingEnabled=true/false. False by default. This config enables or disables data integrity checking. When set to false the legacy cookie validation is used. - dataIntegrityStampMissingCookies=true/false. False by default. This config allows the data integrity process to stamp new cookies if a cookie is missing from a directory. The full check will repair any lost data if the directory data was lost. * Cookie verification for data integrity checking The algorithm differs from that of LegacyCookieValidation in the following ways: - A empty directory isn't considered a fatal condition. It just means that the preboot phase of the data integrity checker must run. Once the preboot phase runs, it should be safe to stamp the cookies again. - Bookies are not allowed to change their identity. If they do, manual operator intervention is required (which is ok as it is expected that an operator would have to intervene to change the identity in the first place). - A missing cookie in zookeeper is only valid, if there are no cookies in any of the directories, as this is considered a new boot. Otherwise, manual operator intervention is required. * Async iterator for ledger metadata Common code for iterating over ledger metadata. There is already asyncProcessLedgers in LedgerManager, but that only gives the ledgerId and the API is nasty (it even uses ZK specific callbacks). This change adds a more modern iterator, which takes a function which returns a CompletableFuture. The iterator has rudimentary rate limiting, by limiting the number of ledgers which can be processed at a time. We should add something more advanced later, which takes into account the response time from ZK. * Add limbo state to bookie ledger representation Limbo state for a ledger means that we don't know whether we should have an entry for the ledger or not, which can happen when a bookie is started after having its disk wiped. We cannot response with a NoSuchEntryException or NoSuchLedgerException as this tell the client that we never had the requested entry, which may or may not be true, but if we tell it to the client, the client will act like it's true and possible mark the end of the ledger at an incorrect point. This change also adds locking to LedgerMetadataIndex. Previously it relied on the good graces to the calling code to avoid modifying the same ledger concurrently. Now that we are also using the index to store limbo state, we can't be so blasé. * Add entryExists call to ledgerStorage Currently the only way to check if an entry exists in the storage is to try to read the entry. However, this means pulling data out of the entrylog, which it should be sufficient to check that the entry exists in the index. This change adds the entryExists call to ledgerStorage. This has only been implemented for DbLedgerStorage. The implementation for the others should be trivial, but it needs to be tested. * Pregenerate the writeset from ledger metadata The bookkeeper client uses DistributionSchedule (of which RoundRobinDistributionSchedule is the only impl) to decide which members of the ensemble it writes an entry to. This writeset is generated for each entry. However, there is only |ensemble| possible writesets, so we should pregenerate them for the ledger and stop trashing memory. WriteSets represent a set of pregenerated writesets as would be otherwise generated from the distribution schedule. The constructor takes a list of indices (which should be generated based on the list of bookies in the ensemble), which specifies the preferred order that bookies should be tried for reads. * Storage state flags for LedgerStorage If a bookie crashes in the middle of a full integrity check, it needs to know to start it again when it reboots. For this, we need to persist some flag to persistent storage. This change adds persistent flags to the ledger storage interface. Multiple flags can be added in future, for example to mark the storage as dirty on boot, so we can detect non-clean shutdown. Flags are only implemented for DbLedgerStorage. The flags are stored in the metadata index, with a negative ledger id as key. The key of the storage selected for ledger 0 is used. This does mean flags will be lost if there is a change in the storage disk configuration, but data integrity checks will run in this case regardless. * EUNKNOWN code A new response code has been added to communicate that the state of an entry is unknown due the ledger being in limbo. * Added bookie unclean shutdown detection Adds unclean shutdown detection. When running with journal writes disabled and data integrity checking enabled, if the prior shutdown was unclean (not a graceful shutdown) then the data integrity checks are triggered. These checks avoid additional data loss scenarios and repair any lost data caused by the loss of unflushed data at the time of the unclean shutdown. The BookieServer registers start-up and shutdown with the UncleanShutdownDetection class. This class adds a dirty file to each ledger dir on registering start-up and clears all these files on registering shutdown. The presence of any of these files on boot-up indicates the prior shutdown was unclean.
98101b9 to
8e4bcd6
Compare
* Data integrity check The integrity check is comprised two parts: a preboot check that is triggered by either an unclean shutdown or an invalid cookie. The preboot check marks any open ledgers as both fenced and in limbo to prevent ledgers affected by potential data loss from being written to. The limbo status prevents NoSuchEntry and NoSuchLedger responses from being sent which avoid ledger truncation from any ledger recovery operations. Finally it sets a storage flag that a full check is required. A new data integrity check service has been to run the full integrity check once the bookie is running. If the service sees that the full check storage flag is set then it runs a full check. This involves scanning the index and comparing it against metadata to discover missing entries. Any missing entries are sourced from peer bookies by the EntryCopier and written to ledger storage. The data integrity check also has a different cookie validation implementation. The following configurations have been added to the conf file: - dataIntegrityCheckingEnabled=true/false. False by default. This config enables or disables data integrity checking. When set to false the legacy cookie validation is used. - dataIntegrityStampMissingCookies=true/false. False by default. This config allows the data integrity process to stamp new cookies if a cookie is missing from a directory. The full check will repair any lost data if the directory data was lost. * Cookie verification for data integrity checking The algorithm differs from that of LegacyCookieValidation in the following ways: - A empty directory isn't considered a fatal condition. It just means that the preboot phase of the data integrity checker must run. Once the preboot phase runs, it should be safe to stamp the cookies again. - Bookies are not allowed to change their identity. If they do, manual operator intervention is required (which is ok as it is expected that an operator would have to intervene to change the identity in the first place). - A missing cookie in zookeeper is only valid, if there are no cookies in any of the directories, as this is considered a new boot. Otherwise, manual operator intervention is required. * Async iterator for ledger metadata Common code for iterating over ledger metadata. There is already asyncProcessLedgers in LedgerManager, but that only gives the ledgerId and the API is nasty (it even uses ZK specific callbacks). This change adds a more modern iterator, which takes a function which returns a CompletableFuture. The iterator has rudimentary rate limiting, by limiting the number of ledgers which can be processed at a time. We should add something more advanced later, which takes into account the response time from ZK. * Add limbo state to bookie ledger representation Limbo state for a ledger means that we don't know whether we should have an entry for the ledger or not, which can happen when a bookie is started after having its disk wiped. We cannot response with a NoSuchEntryException or NoSuchLedgerException as this tell the client that we never had the requested entry, which may or may not be true, but if we tell it to the client, the client will act like it's true and possible mark the end of the ledger at an incorrect point. This change also adds locking to LedgerMetadataIndex. Previously it relied on the good graces to the calling code to avoid modifying the same ledger concurrently. Now that we are also using the index to store limbo state, we can't be so blasé. * Add entryExists call to ledgerStorage Currently the only way to check if an entry exists in the storage is to try to read the entry. However, this means pulling data out of the entrylog, which it should be sufficient to check that the entry exists in the index. This change adds the entryExists call to ledgerStorage. This has only been implemented for DbLedgerStorage. The implementation for the others should be trivial, but it needs to be tested. * Pregenerate the writeset from ledger metadata The bookkeeper client uses DistributionSchedule (of which RoundRobinDistributionSchedule is the only impl) to decide which members of the ensemble it writes an entry to. This writeset is generated for each entry. However, there is only |ensemble| possible writesets, so we should pregenerate them for the ledger and stop trashing memory. WriteSets represent a set of pregenerated writesets as would be otherwise generated from the distribution schedule. The constructor takes a list of indices (which should be generated based on the list of bookies in the ensemble), which specifies the preferred order that bookies should be tried for reads. * Storage state flags for LedgerStorage If a bookie crashes in the middle of a full integrity check, it needs to know to start it again when it reboots. For this, we need to persist some flag to persistent storage. This change adds persistent flags to the ledger storage interface. Multiple flags can be added in future, for example to mark the storage as dirty on boot, so we can detect non-clean shutdown. Flags are only implemented for DbLedgerStorage. The flags are stored in the metadata index, with a negative ledger id as key. The key of the storage selected for ledger 0 is used. This does mean flags will be lost if there is a change in the storage disk configuration, but data integrity checks will run in this case regardless. * EUNKNOWN code A new response code has been added to communicate that the state of an entry is unknown due the ledger being in limbo. * Added bookie unclean shutdown detection Adds unclean shutdown detection. When running with journal writes disabled and data integrity checking enabled, if the prior shutdown was unclean (not a graceful shutdown) then the data integrity checks are triggered. These checks avoid additional data loss scenarios and repair any lost data caused by the loss of unflushed data at the time of the unclean shutdown. The BookieServer registers start-up and shutdown with the UncleanShutdownDetection class. This class adds a dirty file to each ledger dir on registering start-up and clears all these files on registering shutdown. The presence of any of these files on boot-up indicates the prior shutdown was unclean. Co-authored-by: Ivan Kelly <ikelly@splunk.com>
Motivation
To be able to run without the journal without compromising the replication protocol.
See #2705
Changes
Introduction of a data integrity check that prevents data loss due to
running without the journal and auto-repairs the data.
The integrity check is comprised two parts: a preboot check that
is triggered by either an unclean shutdown or an invalid cookie.
The preboot check marks any open ledgers as both fenced and in
limbo to prevent ledgers affected by potential data loss from
being written to. The limbo status prevents NoSuchEntry and
NoSuchLedger responses from being sent which avoid ledger
truncation from any ledger recovery operations. Finally it sets
a storage flag that a full check is required.
A new data integrity check service has been to run the full
integrity check once the bookie is running. If the service
sees that the full check storage flag is set then it runs
a full check. This involves scanning the index and comparing
it against metadata to discover missing entries. Any
missing entries are sourced from peer bookies by the
EntryCopier and written to ledger storage.
The data integrity check also has a different cookie validation
implementation.
The following configurations have been added to the conf
file:
This config enables or disables data integrity checking.
When set to false the legacy cookie validation is used.
default. This config allows the data integrity process
to stamp new cookies if a cookie is missing from a
directory. The full check will repair any lost data if
the directory data was lost.
The algorithm differs from that of LegacyCookieValidation in the
following ways:
that the preboot phase of the data integrity checker must run. Once
the preboot phase runs, it should be safe to stamp the cookies
again.
operator intervention is required (which is ok as it is expected
that an operator would have to intervene to change the identity in
the first place).
in any of the directories, as this is considered a new
boot. Otherwise, manual operator intervention is required.
Common code for iterating over ledger metadata. There is already
asyncProcessLedgers in LedgerManager, but that only gives the ledgerId
and the API is nasty (it even uses ZK specific callbacks).
This change adds a more modern iterator, which takes a function which
returns a CompletableFuture. The iterator has rudimentary rate
limiting, by limiting the number of ledgers which can be processed at
a time. We should add something more advanced later, which takes into
account the response time from ZK.
Limbo state for a ledger means that we don't know whether we should
have an entry for the ledger or not, which can happen when a bookie is
started after having its disk wiped. We cannot response with a
NoSuchEntryException or NoSuchLedgerException as this tell the client
that we never had the requested entry, which may or may not be true,
but if we tell it to the client, the client will act like it's true
and possible mark the end of the ledger at an incorrect point.
This change also adds locking to LedgerMetadataIndex. Previously it
relied on the good graces to the calling code to avoid modifying the
same ledger concurrently. Now that we are also using the index to
store limbo state, we can't be so blasé.
Currently the only way to check if an entry exists in the storage is
to try to read the entry. However, this means pulling data out of the
entrylog, which it should be sufficient to check that the entry exists
in the index.
This change adds the entryExists call to ledgerStorage. This has only
been implemented for DbLedgerStorage. The implementation for the
others should be trivial, but it needs to be tested.
The bookkeeper client uses DistributionSchedule (of which
RoundRobinDistributionSchedule is the only impl) to decide which
members of the ensemble it writes an entry to. This writeset is
generated for each entry. However, there is only |ensemble| possible
writesets, so we should pregenerate them for the ledger and stop
trashing memory.
WriteSets represent a set of pregenerated writesets as would be
otherwise generated from the distribution schedule. The constructor
takes a list of indices (which should be generated based on the list
of bookies in the ensemble), which specifies the preferred order that
bookies should be tried for reads.
If a bookie crashes in the middle of a full integrity check, it needs
to know to start it again when it reboots. For this, we need to
persist some flag to persistent storage.
This change adds persistent flags to the ledger storage
interface. Multiple flags can be added in future.
Flags are only implemented for DbLedgerStorage. The flags are stored
in the metadata index, with a negative ledger id as key. The key of
the storage selected for ledger 0 is used. This does mean flags will
be lost if there is a change in the storage disk configuration, but
data integrity checks will run in this case regardless.
A new response code has been added to communicate that the state
of an entry is unknown due the ledger being in limbo.
Adds unclean shutdown detection. When running with journal
writes disabled and data integrity checking enabled, if
the prior shutdown was unclean (not a graceful shutdown)
then the data integrity checks are triggered. These checks
avoid additional data loss scenarios and repair any lost
data caused by the loss of unflushed data at the time
of the unclean shutdown.
The BookieServer registers start-up and shutdown with the
UncleanShutdownDetection class. This class adds a dirty
file to each ledger dir on registering start-up and clears
all these files on registering shutdown. The presence of
any of these files on boot-up indicates the prior shutdown
was unclean.
Master Issue: #2705