Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.fluss.server.kv;

/** Defines whether closing a KV tablet must persist its current local RocksDB state. */
public enum KvCloseMode {
/** Preserve the existing local-reopen behavior by allowing RocksDB to flush on close. */
PRESERVE_LOCAL_STATE,

/** Skip flushing data that recovery will rebuild from a snapshot and changelog. */
DISCARD_UNPERSISTED_STATE
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

Expand Down Expand Up @@ -203,10 +204,17 @@ public void startup() {
}

public void shutdown() {
LOG.info("Shutting down KvManager");
shutdown(KvCloseMode.PRESERVE_LOCAL_STATE);
}

public void shutdown(KvCloseMode closeMode) {
Objects.requireNonNull(closeMode, "closeMode");
LOG.info("Shutting down KvManager with close mode {}.", closeMode);
isShutdown = true;
List<KvTablet> kvs = new ArrayList<>(currentKvs.values());
closeTabletsConcurrently(kvs, "kv-tablet-closing", this::closeKvTablet).join();
closeTabletsConcurrently(
kvs, "kv-tablet-closing", kvTablet -> closeKvTablet(kvTablet, closeMode))
.join();
arrowBufferAllocator.close();
memorySegmentPool.close();
if (sharedRocksDBRateLimiter != null) {
Expand All @@ -215,11 +223,15 @@ public void shutdown() {
LOG.info("Shut down KvManager complete.");
}

private void closeKvTablet(KvTablet kvTablet) {
private void closeKvTablet(KvTablet kvTablet, KvCloseMode closeMode) {
try {
kvTablet.close();
kvTablet.close(closeMode);
} catch (Exception e) {
LOG.warn("Exception while closing kv tablet {}.", kvTablet.getTableBucket(), e);
LOG.warn(
"Exception while closing kv tablet {} with mode {}.",
kvTablet.getTableBucket(),
closeMode,
e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,15 @@ public KvBatchWriter createKvBatchWriter() {
}

public void close() throws Exception {
LOG.debug("close kv tablet {} for table {}.", tableBucket, physicalPath);
close(KvCloseMode.PRESERVE_LOCAL_STATE);
}

public void close(KvCloseMode closeMode) throws Exception {
LOG.debug(
"Close kv tablet {} for table {} with mode {}.",
tableBucket,
physicalPath,
closeMode);
inWriteLock(
kvLock,
() -> {
Expand All @@ -852,7 +860,7 @@ public void close() throws Exception {
// Note: RocksDB metrics lifecycle is managed by TableMetricGroup
// No need to close it here
if (rocksDBKv != null) {
rocksDBKv.close();
rocksDBKv.close(closeMode);
}
isClosed = true;
});
Expand All @@ -864,7 +872,7 @@ public void drop() throws Exception {
kvLock,
() -> {
// first close the kv.
close();
close(KvCloseMode.DISCARD_UNPERSISTED_STATE);
// then delete the directory.
FileUtils.deleteDirectory(kvTabletDir);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@

package org.apache.fluss.server.kv.rocksdb;

import org.apache.fluss.annotation.VisibleForTesting;
import org.apache.fluss.exception.FlussRuntimeException;
import org.apache.fluss.metrics.Counter;
import org.apache.fluss.metrics.Histogram;
import org.apache.fluss.rocksdb.RocksDBOperationUtils;
import org.apache.fluss.server.kv.KvCloseMode;
import org.apache.fluss.server.utils.ResourceGuard;
import org.apache.fluss.utils.BytesUtils;
import org.apache.fluss.utils.IOUtils;

import org.rocksdb.Cache;
import org.rocksdb.ColumnFamilyHandle;
import org.rocksdb.ColumnFamilyOptions;
import org.rocksdb.MutableDBOptions;
import org.rocksdb.ReadOptions;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
Expand All @@ -40,6 +43,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/** A wrapper for the operation of {@link org.rocksdb.RocksDB}. */
public class RocksDBKv implements AutoCloseable {
Expand Down Expand Up @@ -178,6 +182,13 @@ public void checkIfRocksDBClosed() {

@Override
public void close() throws Exception {
close(KvCloseMode.PRESERVE_LOCAL_STATE);
}

/** Closes this RocksDB KV instance using the provided persistence mode. */
public void close(KvCloseMode closeMode) throws Exception {
Objects.requireNonNull(closeMode);

if (this.closed) {
return;
}
Expand All @@ -188,29 +199,45 @@ public void close() throws Exception {
// parallel.
rocksDBResourceGuard.close();

// IMPORTANT: null reference to signal potential async checkpoint workers that the db was
// disposed, as
// working on the disposed object results in SEGFAULTS.
if (db != null) {
RocksDBException avoidFlushFailure = null;
try {
if (closeMode == KvCloseMode.DISCARD_UNPERSISTED_STATE) {
setAvoidFlushDuringShutdown();
}
} catch (RocksDBException e) {
avoidFlushFailure = e;
} finally {
// IMPORTANT: null reference to signal potential async checkpoint workers that the db
// was disposed, as working on the disposed object results in SEGFAULTS.
if (db != null) {

// RocksDB's native memory management requires that *all* CFs (including default)
// are closed before the DB is closed. See:
// https://github.com/facebook/rocksdb/wiki/RocksJava-Basics#opening-a-database-with-column-families
// Start with default CF ...
List<ColumnFamilyOptions> columnFamilyOptions = new ArrayList<>();
RocksDBOperationUtils.addColumnFamilyOptionsToCloseLater(
columnFamilyOptions, defaultColumnFamilyHandle);
IOUtils.closeQuietly(defaultColumnFamilyHandle);

// RocksDB's native memory management requires that *all* CFs (including default) are
// closed before the
// DB is closed. See:
// https://github.com/facebook/rocksdb/wiki/RocksJava-Basics#opening-a-database-with-column-families
// Start with default CF ...
List<ColumnFamilyOptions> columnFamilyOptions = new ArrayList<>();
RocksDBOperationUtils.addColumnFamilyOptionsToCloseLater(
columnFamilyOptions, defaultColumnFamilyHandle);
IOUtils.closeQuietly(defaultColumnFamilyHandle);
// ... and finally close the DB instance ...
IOUtils.closeQuietly(db);

// ... and finally close the DB instance ...
IOUtils.closeQuietly(db);
columnFamilyOptions.forEach(IOUtils::closeQuietly);

columnFamilyOptions.forEach(IOUtils::closeQuietly);
IOUtils.closeQuietly(optionsContainer);
}
this.closed = true;
}

IOUtils.closeQuietly(optionsContainer);
if (avoidFlushFailure != null) {
throw avoidFlushFailure;
}
this.closed = true;
}

@VisibleForTesting
void setAvoidFlushDuringShutdown() throws RocksDBException {
db.setDBOptions(MutableDBOptions.builder().setAvoidFlushDuringShutdown(true).build());
}

public RocksDB getDb() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.fluss.server.authorizer.AuthorizerLoader;
import org.apache.fluss.server.coordinator.LakeCatalogDynamicLoader;
import org.apache.fluss.server.coordinator.MetadataManager;
import org.apache.fluss.server.kv.KvCloseMode;
import org.apache.fluss.server.kv.KvManager;
import org.apache.fluss.server.kv.scan.ScannerManager;
import org.apache.fluss.server.kv.snapshot.DefaultCompletedKvSnapshotCommitter;
Expand Down Expand Up @@ -571,9 +572,7 @@ void shutdownReplicaManager() throws InterruptedException {

@VisibleForTesting
void shutdownTabletManagers() throws IOException {
if (kvManager != null) {
kvManager.shutdown();
}
shutdownKvManager(KvCloseMode.DISCARD_UNPERSISTED_STATE);

if (remoteLogManager != null) {
remoteLogManager.close();
Expand All @@ -584,6 +583,13 @@ void shutdownTabletManagers() throws IOException {
}
}

@VisibleForTesting
void shutdownKvManager(KvCloseMode closeMode) {
if (kvManager != null) {
kvManager.shutdown(closeMode);
}
}

private void controlledShutDown() {
long startTime = System.currentTimeMillis();
LOG.info("Starting controlled shutdown.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -232,6 +233,29 @@ void testRecoveryAfterKvManagerShutDown(String partitionName) throws Exception {
assertThat(kv2.multiGet(kv2Keys)).containsExactlyElementsOf(kv2Values);
}

@ParameterizedTest
@MethodSource("partitionProvider")
void testDiscardShutdownDoesNotPersistUnflushedState(String partitionName) throws Exception {
initTableBuckets(partitionName);
KvTablet kv = getOrCreateKv(tablePath1, partitionName, tableBucket1);
byte[] key = "discarded-key".getBytes(StandardCharsets.UTF_8);
put(kv, kvRecordFactory.ofRecord(key, new Object[] {1, "value"}));

kvManager.shutdown(KvCloseMode.DISCARD_UNPERSISTED_STATE);
kvManager =
KvManager.create(
conf,
zkClient,
logManager,
TestingMetricGroups.TABLET_SERVER_METRICS,
localDiskManager);
kvManager.startup();

KvTablet reopened = getOrCreateKv(tablePath1, partitionName, tableBucket1);
assertThat(reopened.multiGet(Collections.singletonList(key)))
.containsExactly((byte[]) null);
}

@ParameterizedTest
@MethodSource("partitionProvider")
void testRecoveryWithSchemaChange(String partitionName) throws Exception {
Expand Down Expand Up @@ -317,13 +341,16 @@ void testSameTableNameInDifferentDb(String partitionName) throws Exception {
void testDropKv(String partitionName) throws Exception {
initTableBuckets(partitionName);
KvTablet kv1 = getOrCreateKv(tablePath1, partitionName, tableBucket1);
byte[] key = "dropped-key".getBytes(StandardCharsets.UTF_8);
put(kv1, kvRecordFactory.ofRecord(key, new Object[] {1, "old"}));
kvManager.dropKv(kv1.getTableBucket());

assertThat(kv1.getKvTabletDir()).doesNotExist();
assertThat(kvManager.getKv(tableBucket1)).isNotPresent();

kv1 = getOrCreateKv(tablePath1, partitionName, tableBucket1);
assertThat(kv1.getKvTabletDir()).exists();
assertThat(kv1.multiGet(Collections.singletonList(key))).containsExactly((byte[]) null);
assertThat(kvManager.getKv(tableBucket1)).isPresent();
}

Expand All @@ -334,6 +361,26 @@ void testGetNonExistentKv() {
assertThat(kv).isNotPresent();
}

@Test
void testShutdownRejectsNullCloseModeBeforeClosingTablets() throws Exception {
initTableBuckets(null);
KvTablet kv = getOrCreateKv(tablePath1, null, tableBucket1);
byte[] key = "still-usable-key".getBytes(StandardCharsets.UTF_8);
KvRecord record = kvRecordFactory.ofRecord(key, new Object[] {1, "value"});
put(kv, record);

assertThatThrownBy(() -> kvManager.shutdown(null))
.isInstanceOf(NullPointerException.class)
.hasMessage("closeMode");

verifyMultiGet(kv, key, valueOf(record));

kvManager.shutdown();
assertThatThrownBy(kv.getRocksDBKv()::checkIfRocksDBClosed)
.isInstanceOf(FlussRuntimeException.class);
kvManager = null;
}

@Test
void testShutdownClosesKvTabletsConcurrently() throws Exception {
int maxClosingThreads = 2;
Expand All @@ -352,7 +399,7 @@ void testShutdownClosesKvTabletsConcurrently() throws Exception {
KvManager managerToShutdown = kvManager;
kvManager = null;
ExecutorService shutdownExecutor = Executors.newSingleThreadExecutor();
Future<?> shutdownFuture = shutdownExecutor.submit(managerToShutdown::shutdown);
Future<?> shutdownFuture = shutdownExecutor.submit(() -> managerToShutdown.shutdown());
try {
waitUntil(
() ->
Expand Down
Loading
Loading