HADOOP-17928. Syncable: S3A to warn and downgrade (#3585)

This switches the default behavior of S3A output streams
to warning that Syncable.hsync() or hflush() have been
called; it's not considered an error unless the defaults
are overridden.

This avoids breaking applications which call the APIs,
at the risk of people trying to use S3 as a safe store
of streamed data (HBase WALs, audit logs etc).

Contributed by Steve Loughran.
This commit is contained in:
Steve Loughran 2021-11-02 13:26:16 +00:00 committed by GitHub
parent 2f35cc36cd
commit 6c6d1b64d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 13 deletions

View File

@ -2205,7 +2205,16 @@
</description>
</property>
<!-- Azure file system properties -->
<property>
<name>fs.s3a.downgrade.syncable.exceptions</name>
<value>true</value>
<description>
Warn but continue when applications use Syncable.hsync when writing
to S3A.
</description>
</property>
<!-- Azure file system properties -->
<property>
<name>fs.AbstractFileSystem.wasb.impl</name>
<value>org.apache.hadoop.fs.azure.Wasb</value>

View File

@ -387,7 +387,7 @@ private Constants() {
* Value: {@value}.
*/
public static final boolean DOWNGRADE_SYNCABLE_EXCEPTIONS_DEFAULT =
false;
true;
/**
* The capacity of executor queues for operations other than block

View File

@ -924,30 +924,48 @@ connector isn't saving any data at all. The `Syncable` API, especially the
`hsync()` call, are critical for applications such as HBase to safely
persist data.
The S3A connector throws an `UnsupportedOperationException` when these API calls
are made, because the guarantees absolutely cannot be met: nothing is being flushed
or saved.
When configured to do so, the S3A connector throws an `UnsupportedOperationException`
when these API calls are made, because the API guarantees absolutely cannot be met:
_nothing is being flushed or saved_.
* Applications which intend to invoke the Syncable APIs call `hasCapability("hsync")` on
* Applications which intend to invoke the Syncable APIs should call `hasCapability("hsync")` on
the stream to see if they are supported.
* Or catch and downgrade `UnsupportedOperationException`.
These recommendations _apply to all filesystems_.
These recommendations _apply to all filesystems_.
To downgrade the S3A connector to simply warning of the use of
For consistency with other filesystems, S3A output streams
do not by default reject the `Syncable` calls -instead
they print a warning of its use.
The count of invocations of the two APIs are collected in the S3A filesystem
Statistics/IOStatistics and so their use can be monitored.
To switch the S3A connector to rejecting all use of
`hsync()` or `hflush()` calls, set the option
`fs.s3a.downgrade.syncable.exceptions` to true.
`fs.s3a.downgrade.syncable.exceptions` to `false`.
```xml
<property>
<name>fs.s3a.downgrade.syncable.exceptions</name>
<value>true</value>
<value>false</value>
</property>
```
The count of invocations of the two APIs are collected
in the S3A filesystem Statistics/IOStatistics and so
their use can be monitored.
Regardless of the setting, the `Syncable` API calls do not work.
Telling the store to *not* downgrade the calls is a way to
1. Prevent applications which require Syncable to work from being deployed
against S3.
2. Identify applications which are making the calls even though they don't
need to. These applications can then be fixed -something which may take
time.
Put differently: it is safest to disable downgrading syncable exceptions.
However, enabling the downgrade stops applications unintentionally using the API
from breaking.
*Tip*: try turning it on in staging environments to see what breaks.
### `RemoteFileChangedException` and read-during-overwrite

View File

@ -141,6 +141,10 @@ public void testCallingCloseAfterCallingAbort() throws Exception {
*/
@Test
public void testSyncableUnsupported() throws Exception {
final S3ABlockOutputStream.BlockOutputStreamBuilder
builder = mockS3ABuilder();
builder.withDowngradeSyncableExceptions(false);
stream = spy(new S3ABlockOutputStream(builder));
intercept(UnsupportedOperationException.class, () -> stream.hflush());
intercept(UnsupportedOperationException.class, () -> stream.hsync());
}