HDFS-17009. RBF: state store putAll should also return failed records (#5664)

This commit is contained in:
Viraj Jasani 2023-05-17 09:33:34 -07:00 committed by GitHub
parent 482897a0f6
commit 8e17385141
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 143 additions and 32 deletions

View File

@ -0,0 +1,79 @@
/*
* 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.hadoop.hdfs.server.federation.store.driver;
import java.util.Collections;
import java.util.List;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
/**
* State store operation result with list of failed records.
*/
@InterfaceAudience.Private
@InterfaceStability.Unstable
public class StateStoreOperationResult {
private final List<String> failedRecordsKeys;
private final boolean isOperationSuccessful;
private static final StateStoreOperationResult DEFAULT_OPERATION_SUCCESS_RESULT =
new StateStoreOperationResult(Collections.emptyList(), true);
/**
* State store operation result constructor with list of failed records keys and boolean
* to inform whether the overall operation is successful.
*
* @param failedRecordsKeys The list of failed records keys.
* @param isOperationSuccessful True if the operation was successful, False otherwise.
*/
public StateStoreOperationResult(List<String> failedRecordsKeys,
boolean isOperationSuccessful) {
this.failedRecordsKeys = failedRecordsKeys;
this.isOperationSuccessful = isOperationSuccessful;
}
/**
* State store operation result constructor with a single failed record key.
*
* @param failedRecordKey The failed record key.
*/
public StateStoreOperationResult(String failedRecordKey) {
if (failedRecordKey != null && failedRecordKey.length() > 0) {
this.isOperationSuccessful = false;
this.failedRecordsKeys = Collections.singletonList(failedRecordKey);
} else {
this.isOperationSuccessful = true;
this.failedRecordsKeys = Collections.emptyList();
}
}
public List<String> getFailedRecordsKeys() {
return failedRecordsKeys;
}
public boolean isOperationSuccessful() {
return isOperationSuccessful;
}
public static StateStoreOperationResult getDefaultSuccessResult() {
return DEFAULT_OPERATION_SUCCESS_RESULT;
}
}

View File

@ -107,12 +107,11 @@ public interface StateStoreRecordOperations {
* @param allowUpdate True if update of exiting record is allowed. * @param allowUpdate True if update of exiting record is allowed.
* @param errorIfExists True if an error should be returned when inserting * @param errorIfExists True if an error should be returned when inserting
* an existing record. Only used if allowUpdate = false. * an existing record. Only used if allowUpdate = false.
* @return true if all operations were successful. * @return The result of the putAll operation.
*
* @throws IOException Throws exception if unable to query the data store. * @throws IOException Throws exception if unable to query the data store.
*/ */
@AtMostOnce @AtMostOnce
<T extends BaseRecord> boolean putAll( <T extends BaseRecord> StateStoreOperationResult putAll(
List<T> records, boolean allowUpdate, boolean errorIfExists) List<T> records, boolean allowUpdate, boolean errorIfExists)
throws IOException; throws IOException;

View File

@ -75,7 +75,7 @@ public abstract class StateStoreBaseImpl extends StateStoreDriver {
T record, boolean allowUpdate, boolean errorIfExists) throws IOException { T record, boolean allowUpdate, boolean errorIfExists) throws IOException {
List<T> singletonList = new ArrayList<>(); List<T> singletonList = new ArrayList<>();
singletonList.add(record); singletonList.add(record);
return putAll(singletonList, allowUpdate, errorIfExists); return putAll(singletonList, allowUpdate, errorIfExists).isOperationSuccessful();
} }
@Override @Override

View File

@ -47,6 +47,7 @@ import org.apache.hadoop.hdfs.server.federation.metrics.StateStoreMetrics;
import org.apache.hadoop.hdfs.server.federation.store.StateStoreUnavailableException; import org.apache.hadoop.hdfs.server.federation.store.StateStoreUnavailableException;
import org.apache.hadoop.hdfs.server.federation.store.StateStoreUtils; import org.apache.hadoop.hdfs.server.federation.store.StateStoreUtils;
import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreDriver; import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreDriver;
import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreOperationResult;
import org.apache.hadoop.hdfs.server.federation.store.records.BaseRecord; import org.apache.hadoop.hdfs.server.federation.store.records.BaseRecord;
import org.apache.hadoop.hdfs.server.federation.store.records.Query; import org.apache.hadoop.hdfs.server.federation.store.records.Query;
import org.apache.hadoop.hdfs.server.federation.store.records.QueryResult; import org.apache.hadoop.hdfs.server.federation.store.records.QueryResult;
@ -372,12 +373,12 @@ public abstract class StateStoreFileBaseImpl
} }
@Override @Override
public <T extends BaseRecord> boolean putAll( public <T extends BaseRecord> StateStoreOperationResult putAll(
List<T> records, boolean allowUpdate, boolean errorIfExists) List<T> records, boolean allowUpdate, boolean errorIfExists)
throws StateStoreUnavailableException { throws StateStoreUnavailableException {
verifyDriverReady(); verifyDriverReady();
if (records.isEmpty()) { if (records.isEmpty()) {
return true; return StateStoreOperationResult.getDefaultSuccessResult();
} }
long start = monotonicNow(); long start = monotonicNow();
@ -402,7 +403,7 @@ public abstract class StateStoreFileBaseImpl
if (metrics != null) { if (metrics != null) {
metrics.addFailure(monotonicNow() - start); metrics.addFailure(monotonicNow() - start);
} }
return false; return new StateStoreOperationResult(primaryKey);
} else { } else {
LOG.debug("Not updating {}", record); LOG.debug("Not updating {}", record);
} }
@ -414,7 +415,9 @@ public abstract class StateStoreFileBaseImpl
// Write the records // Write the records
final AtomicBoolean success = new AtomicBoolean(true); final AtomicBoolean success = new AtomicBoolean(true);
final List<Callable<Void>> callables = new ArrayList<>(); final List<Callable<Void>> callables = new ArrayList<>();
toWrite.entrySet().forEach(entry -> callables.add(() -> writeRecordToFile(success, entry))); final List<String> failedRecordsKeys = Collections.synchronizedList(new ArrayList<>());
toWrite.entrySet().forEach(
entry -> callables.add(() -> writeRecordToFile(success, entry, failedRecordsKeys)));
if (this.concurrentStoreAccessPool != null) { if (this.concurrentStoreAccessPool != null) {
// Write records concurrently // Write records concurrently
List<Future<Void>> futures = null; List<Future<Void>> futures = null;
@ -454,36 +457,40 @@ public abstract class StateStoreFileBaseImpl
metrics.addFailure(end - start); metrics.addFailure(end - start);
} }
} }
return success.get(); return new StateStoreOperationResult(failedRecordsKeys, success.get());
} }
/** /**
* Writes the state store record to the file. At first, the record is written to a temp location * Writes the state store record to the file. At first, the record is written to a temp location
* and then later renamed to the final location that is passed with the entry key. * and then later renamed to the final location that is passed with the entry key.
* *
* @param <T> Record class of the records.
* @param success The atomic boolean that gets updated to false if the file write operation fails. * @param success The atomic boolean that gets updated to false if the file write operation fails.
* @param entry The entry of the record path and the state store record to be written to the file * @param entry The entry of the record path and the state store record to be written to the file
* by first writing to a temp location and then renaming it to the record path. * by first writing to a temp location and then renaming it to the record path.
* @param <T> Record class of the records. * @param failedRecordsList The list of paths of the failed records.
* @return Void. * @return Void.
*/ */
private <T extends BaseRecord> Void writeRecordToFile(AtomicBoolean success, private <T extends BaseRecord> Void writeRecordToFile(AtomicBoolean success,
Entry<String, T> entry) { Entry<String, T> entry, List<String> failedRecordsList) {
String recordPath = entry.getKey(); final String recordPath = entry.getKey();
String recordPathTemp = recordPath + "." + now() + TMP_MARK; final T record = entry.getValue();
final String primaryKey = getPrimaryKey(record);
final String recordPathTemp = recordPath + "." + now() + TMP_MARK;
boolean recordWrittenSuccessfully = true; boolean recordWrittenSuccessfully = true;
try (BufferedWriter writer = getWriter(recordPathTemp)) { try (BufferedWriter writer = getWriter(recordPathTemp)) {
T record = entry.getValue();
String line = serializeString(record); String line = serializeString(record);
writer.write(line); writer.write(line);
} catch (IOException e) { } catch (IOException e) {
LOG.error("Cannot write {}", recordPathTemp, e); LOG.error("Cannot write {}", recordPathTemp, e);
recordWrittenSuccessfully = false; recordWrittenSuccessfully = false;
failedRecordsList.add(primaryKey);
success.set(false); success.set(false);
} }
// Commit // Commit
if (recordWrittenSuccessfully && !rename(recordPathTemp, recordPath)) { if (recordWrittenSuccessfully && !rename(recordPathTemp, recordPath)) {
LOG.error("Failed committing record into {}", recordPath); LOG.error("Failed committing record into {}", recordPath);
failedRecordsList.add(primaryKey);
success.set(false); success.set(false);
} }
return null; return null;

View File

@ -36,6 +36,7 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.server.federation.metrics.StateStoreMetrics; import org.apache.hadoop.hdfs.server.federation.metrics.StateStoreMetrics;
import org.apache.hadoop.hdfs.server.federation.router.security.token.SQLConnectionFactory; import org.apache.hadoop.hdfs.server.federation.router.security.token.SQLConnectionFactory;
import org.apache.hadoop.hdfs.server.federation.store.StateStoreUtils; import org.apache.hadoop.hdfs.server.federation.store.StateStoreUtils;
import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreOperationResult;
import org.apache.hadoop.hdfs.server.federation.store.records.BaseRecord; import org.apache.hadoop.hdfs.server.federation.store.records.BaseRecord;
import org.apache.hadoop.hdfs.server.federation.store.records.DisabledNameservice; import org.apache.hadoop.hdfs.server.federation.store.records.DisabledNameservice;
import org.apache.hadoop.hdfs.server.federation.store.records.MembershipState; import org.apache.hadoop.hdfs.server.federation.store.records.MembershipState;
@ -161,10 +162,10 @@ public class StateStoreMySQLImpl extends StateStoreSerializableImpl {
} }
@Override @Override
public <T extends BaseRecord> boolean putAll( public <T extends BaseRecord> StateStoreOperationResult putAll(
List<T> records, boolean allowUpdate, boolean errorIfExists) throws IOException { List<T> records, boolean allowUpdate, boolean errorIfExists) throws IOException {
if (records.isEmpty()) { if (records.isEmpty()) {
return true; return StateStoreOperationResult.getDefaultSuccessResult();
} }
verifyDriverReady(); verifyDriverReady();
@ -173,6 +174,7 @@ public class StateStoreMySQLImpl extends StateStoreSerializableImpl {
long start = Time.monotonicNow(); long start = Time.monotonicNow();
boolean success = true; boolean success = true;
final List<String> failedRecordsKeys = new ArrayList<>();
for (T record : records) { for (T record : records) {
String tableName = getAndValidateTableNameForClass(record.getClass()); String tableName = getAndValidateTableNameForClass(record.getClass());
String primaryKey = getPrimaryKey(record); String primaryKey = getPrimaryKey(record);
@ -185,6 +187,7 @@ public class StateStoreMySQLImpl extends StateStoreSerializableImpl {
record.setDateModified(this.getTime()); record.setDateModified(this.getTime());
if (!updateRecord(tableName, primaryKey, data)) { if (!updateRecord(tableName, primaryKey, data)) {
LOG.error("Cannot write {} into table {}", primaryKey, tableName); LOG.error("Cannot write {} into table {}", primaryKey, tableName);
failedRecordsKeys.add(primaryKey);
success = false; success = false;
} }
} else { } else {
@ -194,7 +197,7 @@ public class StateStoreMySQLImpl extends StateStoreSerializableImpl {
if (metrics != null) { if (metrics != null) {
metrics.addFailure(Time.monotonicNow() - start); metrics.addFailure(Time.monotonicNow() - start);
} }
return false; return new StateStoreOperationResult(primaryKey);
} else { } else {
LOG.debug("Not updating {} as updates are not allowed", record); LOG.debug("Not updating {} as updates are not allowed", record);
} }
@ -202,6 +205,7 @@ public class StateStoreMySQLImpl extends StateStoreSerializableImpl {
} else { } else {
if (!insertRecord(tableName, primaryKey, data)) { if (!insertRecord(tableName, primaryKey, data)) {
LOG.error("Cannot write {} in table {}", primaryKey, tableName); LOG.error("Cannot write {} in table {}", primaryKey, tableName);
failedRecordsKeys.add(primaryKey);
success = false; success = false;
} }
} }
@ -215,7 +219,7 @@ public class StateStoreMySQLImpl extends StateStoreSerializableImpl {
metrics.addFailure(end - start); metrics.addFailure(end - start);
} }
} }
return success; return new StateStoreOperationResult(failedRecordsKeys, success);
} }
@Override @Override

View File

@ -24,6 +24,7 @@ import static org.apache.hadoop.util.Time.monotonicNow;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -40,6 +41,7 @@ import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.server.federation.router.RBFConfigKeys; import org.apache.hadoop.hdfs.server.federation.router.RBFConfigKeys;
import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreDriver; import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreDriver;
import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreOperationResult;
import org.apache.hadoop.hdfs.server.federation.store.records.BaseRecord; import org.apache.hadoop.hdfs.server.federation.store.records.BaseRecord;
import org.apache.hadoop.hdfs.server.federation.store.records.Query; import org.apache.hadoop.hdfs.server.federation.store.records.Query;
import org.apache.hadoop.hdfs.server.federation.store.records.QueryResult; import org.apache.hadoop.hdfs.server.federation.store.records.QueryResult;
@ -230,11 +232,11 @@ public class StateStoreZooKeeperImpl extends StateStoreSerializableImpl {
} }
@Override @Override
public <T extends BaseRecord> boolean putAll( public <T extends BaseRecord> StateStoreOperationResult putAll(
List<T> records, boolean update, boolean error) throws IOException { List<T> records, boolean update, boolean error) throws IOException {
verifyDriverReady(); verifyDriverReady();
if (records.isEmpty()) { if (records.isEmpty()) {
return true; return StateStoreOperationResult.getDefaultSuccessResult();
} }
// All records should be the same // All records should be the same
@ -245,6 +247,7 @@ public class StateStoreZooKeeperImpl extends StateStoreSerializableImpl {
long start = monotonicNow(); long start = monotonicNow();
final AtomicBoolean status = new AtomicBoolean(true); final AtomicBoolean status = new AtomicBoolean(true);
List<Callable<Void>> callables = new ArrayList<>(); List<Callable<Void>> callables = new ArrayList<>();
final List<String> failedRecordsKeys = Collections.synchronizedList(new ArrayList<>());
records.forEach(record -> records.forEach(record ->
callables.add( callables.add(
() -> { () -> {
@ -252,6 +255,7 @@ public class StateStoreZooKeeperImpl extends StateStoreSerializableImpl {
String recordZNode = getNodePath(znode, primaryKey); String recordZNode = getNodePath(znode, primaryKey);
byte[] data = serialize(record); byte[] data = serialize(record);
if (!writeNode(recordZNode, data, update, error)) { if (!writeNode(recordZNode, data, update, error)) {
failedRecordsKeys.add(primaryKey);
status.set(false); status.set(false);
} }
return null; return null;
@ -276,7 +280,7 @@ public class StateStoreZooKeeperImpl extends StateStoreSerializableImpl {
} else { } else {
getMetrics().addFailure(end - start); getMetrics().addFailure(end - start);
} }
return status.get(); return new StateStoreOperationResult(failedRecordsKeys, status.get());
} }
@Override @Override

View File

@ -145,7 +145,7 @@ public class MountTableStoreImpl extends MountTableStore {
final String src = mountTable.getSourcePath(); final String src = mountTable.getSourcePath();
checkMountTablePermission(src); checkMountTablePermission(src);
} }
boolean status = getDriver().putAll(mountTables, false, true); boolean status = getDriver().putAll(mountTables, false, true).isOperationSuccessful();
AddMountTableEntriesResponse response = AddMountTableEntriesResponse.newInstance(); AddMountTableEntriesResponse response = AddMountTableEntriesResponse.newInstance();
response.setStatus(status); response.setStatus(status);
if (status) { if (status) {

View File

@ -235,9 +235,7 @@ public final class FederationStateStoreTestUtils {
StateStoreDriver driver = stateStore.getDriver(); StateStoreDriver driver = stateStore.getDriver();
driver.verifyDriverReady(); driver.verifyDriverReady();
if (driver.removeAll(clazz)) { if (driver.removeAll(clazz)) {
if (driver.putAll(records, true, false)) { return driver.putAll(records, true, false).isOperationSuccessful();
return true;
}
} }
return false; return false;
} }

View File

@ -307,7 +307,15 @@ public class TestStateStoreDriverBase {
} }
// Verify // Verify
assertTrue(driver.putAll(insertList, false, true)); StateStoreOperationResult result1 = driver.putAll(insertList, false, true);
assertTrue(result1.isOperationSuccessful());
assertEquals(0, result1.getFailedRecordsKeys().size());
StateStoreOperationResult result2 = driver.putAll(insertList.subList(0, 1), false, true);
assertFalse(result2.isOperationSuccessful());
assertEquals(1, result2.getFailedRecordsKeys().size());
assertEquals(getPrimaryKey(insertList.get(0)), result2.getFailedRecordsKeys().get(0));
records = driver.get(clazz); records = driver.get(clazz);
assertEquals(records.getRecords().size(), 10); assertEquals(records.getRecords().size(), 10);
@ -384,7 +392,10 @@ public class TestStateStoreDriverBase {
} }
// Verify // Verify
assertTrue(driver.putAll(insertList, false, true)); StateStoreOperationResult result = driver.putAll(insertList, false, true);
assertTrue(result.isOperationSuccessful());
assertEquals(0, result.getFailedRecordsKeys().size());
records = driver.get(clazz); records = driver.get(clazz);
assertEquals(records.getRecords().size(), 10); assertEquals(records.getRecords().size(), 10);
@ -689,4 +700,12 @@ public class TestStateStoreDriverBase {
} }
return null; return null;
} }
private static String getPrimaryKey(BaseRecord record) {
String primaryKey = record.getPrimaryKey();
primaryKey = primaryKey.replaceAll("/", "0SLASH0");
primaryKey = primaryKey.replaceAll(":", "_");
return primaryKey;
}
} }

View File

@ -18,11 +18,13 @@
package org.apache.hadoop.hdfs.server.federation.store.records; package org.apache.hadoop.hdfs.server.federation.store.records;
import org.apache.hadoop.hdfs.server.federation.store.StateStoreUtils; import org.apache.hadoop.hdfs.server.federation.store.StateStoreUtils;
import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreOperationResult;
import org.apache.hadoop.hdfs.server.federation.store.driver.impl.StateStoreBaseImpl; import org.apache.hadoop.hdfs.server.federation.store.driver.impl.StateStoreBaseImpl;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -89,10 +91,9 @@ public class MockStateStoreDriver extends StateStoreBaseImpl {
} }
@Override @Override
public <T extends BaseRecord> boolean putAll(List<T> records, public <T extends BaseRecord> StateStoreOperationResult putAll(List<T> records,
boolean allowUpdate, boolean allowUpdate,
boolean errorIfExists) boolean errorIfExists) throws IOException {
throws IOException {
checkErrors(); checkErrors();
for (T record : records) { for (T record : records) {
Map<String, BaseRecord> map = Map<String, BaseRecord> map =
@ -107,7 +108,7 @@ public class MockStateStoreDriver extends StateStoreBaseImpl {
+ ": " + key); + ": " + key);
} }
} }
return true; return new StateStoreOperationResult(Collections.emptyList(), true);
} }
/** /**