This is an automated email from the ASF dual-hosted git repository.
cshannon pushed a commit to branch elasticity
in repository https://gitbox.apache.org/repos/asf/accumulo.git
The following commit(s) were added to refs/heads/elasticity by this push:
new 490a4634f6 Move the tracking of unsplittable tablets to metadata table
(#4317)
490a4634f6 is described below
commit 490a4634f6a299b26287bda7ae596572b2ee0808
Author: Christopher L. Shannon <[email protected]>
AuthorDate: Fri Mar 8 17:04:12 2024 -0500
Move the tracking of unsplittable tablets to metadata table (#4317)
This adds a new column to store information for tracking unsplittable
tablets in the metadata table instead of in memory. This information
can be used by the tablet management iterator to know if a tablet needs
to split and avoid unnecessarily trying to split a tablet that can't be
split. The data stored includes a hash of the file set and the relevant
config related to splits and if this changes then the iterator will try
and split again and retest.
Co-authored-by: Keith Turner <[email protected]>
---
.../accumulo/core/metadata/schema/Ample.java | 4 +
.../core/metadata/schema/MetadataSchema.java | 13 ++
.../core/metadata/schema/TabletMetadata.java | 36 ++++-
.../metadata/schema/TabletMetadataBuilder.java | 13 ++
.../core/metadata/schema/TabletMutatorBase.java | 13 ++
.../core/metadata/schema/TabletsMetadata.java | 4 +
.../core/metadata/schema/UnSplittableMetadata.java | 116 ++++++++++++++
.../java/org/apache/accumulo/core/util/Merge.java | 7 +-
.../core/metadata/schema/TabletMetadataTest.java | 110 ++++++++++++-
.../server/constraints/MetadataConstraints.java | 20 ++-
.../manager/state/TabletManagementIterator.java | 37 +++--
.../apache/accumulo/server}/split/SplitUtils.java | 46 +++++-
.../constraints/MetadataConstraintsTest.java | 60 ++++++-
.../accumulo/server}/split/SplitUtilsTest.java | 2 +-
.../manager/tableOps/split/FindSplits.java | 82 +++++++++-
.../manager/tableOps/split/UpdateTablets.java | 6 +
.../manager/tableOps/split/UpdateTabletsTest.java | 7 +-
.../org/apache/accumulo/test/LargeSplitRowIT.java | 177 ++++++++++++++++++++-
18 files changed, 708 insertions(+), 45 deletions(-)
diff --git
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java
index d3f27a7f36..d85553f75e 100644
--- a/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java
+++ b/core/src/main/java/org/apache/accumulo/core/metadata/schema/Ample.java
@@ -419,6 +419,10 @@ public interface Ample {
T putUserCompactionRequested(FateId fateId);
T deleteUserCompactionRequested(FateId fateId);
+
+ T setUnSplittable(UnSplittableMetadata unSplittableMeta);
+
+ T deleteUnSplittable();
}
interface TabletMutator extends TabletUpdates<TabletMutator> {
diff --git
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/MetadataSchema.java
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/MetadataSchema.java
index 46cbfb7b06..b093a4bccf 100644
---
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/MetadataSchema.java
+++
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/MetadataSchema.java
@@ -430,6 +430,19 @@ public class MetadataSchema {
public static final Text NAME = new Text(STR_NAME);
}
+ /**
+ * This family is used to track information needed for splits. Currently,
the only thing stored
+ * is if the tablets are un-splittable based on the files the tablet and
configuration related
+ * to splits.
+ */
+ public static class SplitColumnFamily {
+ public static final String STR_NAME = "split";
+ public static final Text NAME = new Text(STR_NAME);
+ public static final String UNSPLITTABLE_QUAL = "unsplittable";
+ public static final ColumnFQ UNSPLITTABLE_COLUMN =
+ new ColumnFQ(NAME, new Text(UNSPLITTABLE_QUAL));
+ }
+
// TODO when removing the Upgrader12to13 class in the upgrade package,
also remove this class.
public static class Upgrade12to13 {
diff --git
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
index ccdb2acaec..be7d2fb056 100644
---
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
+++
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadata.java
@@ -70,6 +70,7 @@ import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Lo
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.MergedColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
+import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SplitColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SuspendLocationColumn;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.UserCompactionRequestedColumnFamily;
@@ -82,6 +83,8 @@ import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -120,6 +123,8 @@ public class TabletMetadata {
private boolean futureAndCurrentLocationSet = false;
private Set<FateId> compacted;
private Set<FateId> userCompactionsRequested;
+ private UnSplittableMetadata unSplittableMetadata;
+ private Supplier<Long> fileSize;
public static TabletMetadataBuilder builder(KeyExtent extent) {
return new TabletMetadataBuilder(extent);
@@ -150,7 +155,8 @@ public class TabletMetadata {
OPID,
SELECTED,
COMPACTED,
- USER_COMPACTION_REQUESTED
+ USER_COMPACTION_REQUESTED,
+ UNSPLITTABLE
}
public static class Location {
@@ -316,6 +322,14 @@ public class TabletMetadata {
return files;
}
+ /**
+ * @return the sum of the tablets files sizes
+ */
+ public long getFileSize() {
+ ensureFetched(ColumnType.FILES);
+ return fileSize.get();
+ }
+
public SelectedFiles getSelectedFiles() {
ensureFetched(ColumnType.SELECTED);
return selectedFiles;
@@ -381,6 +395,11 @@ public class TabletMetadata {
return onDemandHostingRequested;
}
+ public UnSplittableMetadata getUnSplittable() {
+ ensureFetched(ColumnType.UNSPLITTABLE);
+ return unSplittableMetadata;
+ }
+
@Override
public String toString() {
return new ToStringBuilder(this,
ToStringStyle.SHORT_PREFIX_STYLE).append("tableId", tableId)
@@ -394,7 +413,8 @@ public class TabletMetadata {
.append("onDemandHostingRequested", onDemandHostingRequested)
.append("operationId", operationId).append("selectedFiles",
selectedFiles)
.append("futureAndCurrentLocationSet", futureAndCurrentLocationSet)
- .append("userCompactionsRequested",
userCompactionsRequested).toString();
+ .append("userCompactionsRequested", userCompactionsRequested)
+ .append("unSplittableMetadata", unSplittableMetadata).toString();
}
public SortedMap<Key,Value> getKeyValues() {
@@ -545,6 +565,13 @@ public class TabletMetadata {
case UserCompactionRequestedColumnFamily.STR_NAME:
userCompactionsRequestedBuilder.add(FateId.from(qual));
break;
+ case SplitColumnFamily.STR_NAME:
+ if (qual.equals(SplitColumnFamily.UNSPLITTABLE_QUAL)) {
+ te.unSplittableMetadata = UnSplittableMetadata.toUnSplittable(val);
+ } else {
+ throw new IllegalStateException("Unexpected SplitColumnFamily
qualifier: " + qual);
+ }
+ break;
default:
throw new IllegalStateException("Unexpected family " + fam);
@@ -557,7 +584,10 @@ public class TabletMetadata {
te.availability = TabletAvailability.HOSTED;
}
- te.files = filesBuilder.build();
+ var files = filesBuilder.build();
+ te.files = files;
+ te.fileSize =
+ Suppliers.memoize(() ->
files.values().stream().mapToLong(DataFileValue::getSize).sum());
te.loadedFiles = loadedFilesBuilder.build();
te.fetchedCols = fetchedColumns;
te.scans = scansBuilder.build();
diff --git
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadataBuilder.java
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadataBuilder.java
index 55a2107599..fb79fc8066 100644
---
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadataBuilder.java
+++
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMetadataBuilder.java
@@ -36,6 +36,7 @@ import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType
import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.SELECTED;
import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.SUSPEND;
import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.TIME;
+import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.UNSPLITTABLE;
import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.USER_COMPACTION_REQUESTED;
import java.util.Arrays;
@@ -295,6 +296,18 @@ public class TabletMetadataBuilder implements
Ample.TabletUpdates<TabletMetadata
throw new UnsupportedOperationException();
}
+ @Override
+ public TabletMetadataBuilder setUnSplittable(UnSplittableMetadata
unSplittableMeta) {
+ fetched.add(UNSPLITTABLE);
+ internalBuilder.setUnSplittable(unSplittableMeta);
+ return this;
+ }
+
+ @Override
+ public TabletMetadataBuilder deleteUnSplittable() {
+ throw new UnsupportedOperationException();
+ }
+
/**
* @param extraFetched Anything that was put on the builder will
automatically be added to the
* fetched set. However, for the case where something was not put and
it needs to be
diff --git
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMutatorBase.java
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMutatorBase.java
index 190439ab2d..9cc8143dc3 100644
---
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMutatorBase.java
+++
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletMutatorBase.java
@@ -44,6 +44,7 @@ import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.La
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.MergedColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
+import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SplitColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SuspendLocationColumn;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.UserCompactionRequestedColumnFamily;
@@ -347,6 +348,18 @@ public abstract class TabletMutatorBase<T extends
Ample.TabletUpdates<T>>
return getThis();
}
+ @Override
+ public T setUnSplittable(UnSplittableMetadata unSplittableMeta) {
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new
Value(unSplittableMeta.toBase64()));
+ return getThis();
+ }
+
+ @Override
+ public T deleteUnSplittable() {
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.putDelete(mutation);
+ return getThis();
+ }
+
public void setCloseAfterMutate(AutoCloseable closeable) {
this.closeAfterMutate = closeable;
}
diff --git
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletsMetadata.java
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletsMetadata.java
index 4fee556643..9e61d9103c 100644
---
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletsMetadata.java
+++
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/TabletsMetadata.java
@@ -81,6 +81,7 @@ import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.La
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LogColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.MergedColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
+import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SplitColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SuspendLocationColumn;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.UserCompactionRequestedColumnFamily;
import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
@@ -396,6 +397,9 @@ public class TabletsMetadata implements
Iterable<TabletMetadata>, AutoCloseable
case USER_COMPACTION_REQUESTED:
families.add(UserCompactionRequestedColumnFamily.NAME);
break;
+ case UNSPLITTABLE:
+ qualifiers.add(SplitColumnFamily.UNSPLITTABLE_COLUMN);
+ break;
default:
throw new IllegalArgumentException("Unknown col type " +
colToFetch);
}
diff --git
a/core/src/main/java/org/apache/accumulo/core/metadata/schema/UnSplittableMetadata.java
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/UnSplittableMetadata.java
new file mode 100644
index 0000000000..5c53c82952
--- /dev/null
+++
b/core/src/main/java/org/apache/accumulo/core/metadata/schema/UnSplittableMetadata.java
@@ -0,0 +1,116 @@
+/*
+ * 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
+ *
+ * https://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.accumulo.core.metadata.schema;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Base64;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.StoredTabletFile;
+
+import com.google.common.base.Preconditions;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+
+public class UnSplittableMetadata {
+
+ private final HashCode hashOfSplitParameters;
+
+ private UnSplittableMetadata(KeyExtent keyExtent, long splitThreshold, long
maxEndRowSize,
+ int maxFilesToOpen, Set<StoredTabletFile> files) {
+ this(calculateSplitParamsHash(keyExtent, splitThreshold, maxEndRowSize,
maxFilesToOpen, files));
+ }
+
+ private UnSplittableMetadata(HashCode hashOfSplitParameters) {
+ this.hashOfSplitParameters = Objects.requireNonNull(hashOfSplitParameters);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ UnSplittableMetadata that = (UnSplittableMetadata) o;
+ return Objects.equals(hashOfSplitParameters, that.hashOfSplitParameters);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(hashOfSplitParameters);
+ }
+
+ @Override
+ public String toString() {
+ return toBase64();
+ }
+
+ public String toBase64() {
+ return Base64.getEncoder().encodeToString(hashOfSplitParameters.asBytes());
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ private static HashCode calculateSplitParamsHash(KeyExtent keyExtent, long
splitThreshold,
+ long maxEndRowSize, int maxFilesToOpen, Set<StoredTabletFile> files) {
+ Preconditions.checkArgument(splitThreshold > 0, "splitThreshold must be
greater than 0");
+ Preconditions.checkArgument(maxEndRowSize > 0, "maxEndRowSize must be
greater than 0");
+ Preconditions.checkArgument(maxFilesToOpen > 0, "maxFilesToOpen must be
greater than 0");
+
+ // Use static call to murmur3_128() so the seed is always the same
+ // Hashing.goodFastHash will seed with the current time, and we need the
seed to be
+ // the same across restarts and instances
+ var hasher = Hashing.murmur3_128().newHasher();
+
hasher.putBytes(serializeKeyExtent(keyExtent)).putLong(splitThreshold).putLong(maxEndRowSize)
+ .putInt(maxFilesToOpen);
+ files.stream().map(StoredTabletFile::getMetadata).sorted()
+ .forEach(path -> hasher.putString(path, UTF_8));
+ return hasher.hash();
+ }
+
+ public static UnSplittableMetadata toUnSplittable(KeyExtent keyExtent, long
splitThreshold,
+ long maxEndRowSize, int maxFilesToOpen, Set<StoredTabletFile> files) {
+ return new UnSplittableMetadata(keyExtent, splitThreshold, maxEndRowSize,
maxFilesToOpen,
+ files);
+ }
+
+ public static UnSplittableMetadata toUnSplittable(String
base64HashOfSplitParameters) {
+ return new UnSplittableMetadata(
+
HashCode.fromBytes(Base64.getDecoder().decode(base64HashOfSplitParameters)));
+ }
+
+ private static byte[] serializeKeyExtent(KeyExtent keyExtent) {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos)) {
+ keyExtent.writeTo(dos);
+ dos.close();
+ return baos.toByteArray();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/accumulo/core/util/Merge.java
b/core/src/main/java/org/apache/accumulo/core/util/Merge.java
index 4eba179640..c872086af7 100644
--- a/core/src/main/java/org/apache/accumulo/core/util/Merge.java
+++ b/core/src/main/java/org/apache/accumulo/core/util/Merge.java
@@ -37,7 +37,6 @@ import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.dataImpl.KeyExtent;
import org.apache.accumulo.core.metadata.AccumuloTable;
-import org.apache.accumulo.core.metadata.schema.DataFileValue;
import org.apache.accumulo.core.metadata.schema.TabletsMetadata;
import org.apache.accumulo.core.trace.TraceUtil;
import org.apache.hadoop.io.Text;
@@ -162,10 +161,8 @@ public class Merge {
.overRange(new KeyExtent(tableId, end,
start).toMetaRange()).fetch(FILES, PREV_ROW)
.build()) {
- Iterator<Size> sizeIterator = tablets.stream().map(tm -> {
- long size =
tm.getFilesMap().values().stream().mapToLong(DataFileValue::getSize).sum();
- return new Size(tm.getExtent(), size);
- }).iterator();
+ Iterator<Size> sizeIterator =
+ tablets.stream().map(tm -> new Size(tm.getExtent(),
tm.getFileSize())).iterator();
while (sizeIterator.hasNext()) {
Size next = sizeIterator.next();
diff --git
a/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java
b/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java
index 8131a1a0df..e2fc8b8167 100644
---
a/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java
+++
b/core/src/test/java/org/apache/accumulo/core/metadata/schema/TabletMetadataTest.java
@@ -32,6 +32,7 @@ import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType
import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.LOCATION;
import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.MERGED;
import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.SUSPEND;
+import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.UNSPLITTABLE;
import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.USER_COMPACTION_REQUESTED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -52,6 +53,7 @@ import
org.apache.accumulo.core.client.admin.TabletAvailability;
import org.apache.accumulo.core.client.admin.TimeType;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.dataImpl.KeyExtent;
@@ -69,6 +71,7 @@ import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Da
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.FutureLocationColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.LastLocationColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
+import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SplitColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SuspendLocationColumn;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.UserCompactionRequestedColumnFamily;
@@ -131,6 +134,9 @@ public class TabletMetadataTest {
MERGED_COLUMN.put(mutation, new Value());
mutation.put(UserCompactionRequestedColumnFamily.STR_NAME,
FateId.from(type, 17).canonical(),
"");
+ var unsplittableMeta =
+ UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf1,
sf2));
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new
Value(unsplittableMeta.toBase64()));
SortedMap<Key,Value> rowMap = toRowMap(mutation);
@@ -143,6 +149,8 @@ public class TabletMetadataTest {
assertEquals(extent, tm.getExtent());
assertEquals(Set.of(tf1, tf2), Set.copyOf(tm.getFiles()));
assertEquals(Map.of(tf1, dfv1, tf2, dfv2), tm.getFilesMap());
+
assertEquals(tm.getFilesMap().values().stream().mapToLong(DataFileValue::getSize).sum(),
+ tm.getFileSize());
assertEquals(6L, tm.getFlushId().getAsLong());
assertEquals(rowMap, tm.getKeyValues());
assertEquals(Map.of(new StoredTabletFile(bf1), fateId56L, new
StoredTabletFile(bf2), fateId59L),
@@ -162,6 +170,7 @@ public class TabletMetadataTest {
assertEquals(Set.of(sf1, sf2), Set.copyOf(tm.getScans()));
assertTrue(tm.hasMerged());
assertTrue(tm.getUserCompactionsRequested().contains(FateId.from(type,
17)));
+ assertEquals(unsplittableMeta, tm.getUnSplittable());
}
@Test
@@ -339,7 +348,92 @@ public class TabletMetadataTest {
}
@Test
- public void testUnkownColFamily() {
+ public void testUnsplittableColumn() {
+ KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new
Text("da"));
+
+ StoredTabletFile sf1 = StoredTabletFile.of(new
Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf"));
+ StoredTabletFile sf2 = StoredTabletFile.of(new
Path("hdfs://nn1/acc/tables/1/t-0001/sf2.rf"));
+ StoredTabletFile sf3 = StoredTabletFile.of(new
Path("hdfs://nn1/acc/tables/1/t-0001/sf3.rf"));
+ // Same path as sf4 but with a range
+ StoredTabletFile sf4 =
+ StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf3.rf"),
new Range("a", "b"));
+
+ // Test with files
+ var unsplittableMeta1 =
+ UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf1,
sf2, sf3));
+ Mutation mutation = TabletColumnFamily.createPrevRowMutation(extent);
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new
Value(unsplittableMeta1.toBase64()));
+ TabletMetadata tm =
TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(),
+ EnumSet.of(UNSPLITTABLE), true, false);
+ assertUnsplittable(unsplittableMeta1, tm.getUnSplittable(), true);
+
+ // Test empty file set
+ var unsplittableMeta2 = UnSplittableMetadata.toUnSplittable(extent, 100,
110, 120, Set.of());
+ mutation = TabletColumnFamily.createPrevRowMutation(extent);
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new
Value(unsplittableMeta2.toBase64()));
+ tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(),
+ EnumSet.of(UNSPLITTABLE), true, false);
+ assertUnsplittable(unsplittableMeta2, tm.getUnSplittable(), true);
+
+ // Make sure not equals works as well
+ assertUnsplittable(unsplittableMeta1, unsplittableMeta2, false);
+
+ // Test with ranges
+ // use sf4 which includes sf4 instead of sf3 which has a range
+ var unsplittableMeta3 =
+ UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf1,
sf2, sf4));
+ mutation = TabletColumnFamily.createPrevRowMutation(extent);
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.put(mutation, new
Value(unsplittableMeta3.toBase64()));
+ tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(),
+ EnumSet.of(UNSPLITTABLE), true, false);
+ assertUnsplittable(unsplittableMeta3, tm.getUnSplittable(), true);
+
+ // make sure not equals when all the file paths are equal but one has a
range
+ assertUnsplittable(unsplittableMeta1, unsplittableMeta3, false);
+
+ // Column not set
+ mutation = TabletColumnFamily.createPrevRowMutation(extent);
+ tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(),
+ EnumSet.of(UNSPLITTABLE), true, false);
+ assertNull(tm.getUnSplittable());
+
+ // Column not fetched
+ mutation = TabletColumnFamily.createPrevRowMutation(extent);
+ tm = TabletMetadata.convertRow(toRowMap(mutation).entrySet().iterator(),
+ EnumSet.of(ColumnType.PREV_ROW), true, false);
+ assertThrows(IllegalStateException.class, tm::getUnSplittable);
+ }
+
+ @Test
+ public void testUnsplittableWithRange() {
+ KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new
Text("da"));
+
+ // Files with same path and different ranges
+ StoredTabletFile sf1 = StoredTabletFile.of(new
Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf"));
+ StoredTabletFile sf2 =
+ StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf"),
new Range("a", "b"));
+ StoredTabletFile sf3 =
+ StoredTabletFile.of(new Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf"),
new Range("a", "d"));
+
+ var meta1 = UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120,
Set.of(sf1));
+ var meta2 = UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120,
Set.of(sf2));
+ var meta3 = UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120,
Set.of(sf3));
+
+ // compare each against the others to make sure not equal
+ assertUnsplittable(meta1, meta2, false);
+ assertUnsplittable(meta1, meta3, false);
+ assertUnsplittable(meta2, meta3, false);
+ }
+
+ private void assertUnsplittable(UnSplittableMetadata meta1,
UnSplittableMetadata meta2,
+ boolean equal) {
+ assertEquals(equal, meta1.equals(meta2));
+ assertEquals(equal, meta1.hashCode() == meta2.hashCode());
+ assertEquals(equal, meta1.toBase64().equals(meta2.toBase64()));
+ }
+
+ @Test
+ public void testUnknownColFamily() {
KeyExtent extent = new KeyExtent(TableId.of("5"), new Text("df"), new
Text("da"));
Mutation mutation = TabletColumnFamily.createPrevRowMutation(extent);
@@ -390,13 +484,15 @@ public class TabletMetadataTest {
.putFile(sf1, dfv1).putFile(sf2, dfv2).putBulkFile(rf1,
FateId.from(type, 25))
.putBulkFile(rf2, FateId.from(type,
35)).putFlushId(27).putDirName("dir1").putScan(sf3)
.putScan(sf4).putCompacted(FateId.from(type,
17)).putCompacted(FateId.from(type, 23))
- .build(ECOMP, HOSTING_REQUESTED, MERGED, USER_COMPACTION_REQUESTED);
+ .build(ECOMP, HOSTING_REQUESTED, MERGED, USER_COMPACTION_REQUESTED,
UNSPLITTABLE);
assertEquals(extent, tm.getExtent());
assertEquals(TabletAvailability.UNHOSTED, tm.getTabletAvailability());
assertEquals(Location.future(ser1), tm.getLocation());
assertEquals(27L, tm.getFlushId().orElse(-1));
assertEquals(Map.of(sf1, dfv1, sf2, dfv2), tm.getFilesMap());
+
assertEquals(tm.getFilesMap().values().stream().mapToLong(DataFileValue::getSize).sum(),
+ tm.getFileSize());
assertEquals(Map.of(rf1.insert(), FateId.from(type, 25L), rf2.insert(),
FateId.from(type, 35L)),
tm.getLoaded());
assertEquals("dir1", tm.getDirName());
@@ -406,6 +502,7 @@ public class TabletMetadataTest {
assertFalse(tm.getHostingRequested());
assertTrue(tm.getUserCompactionsRequested().isEmpty());
assertFalse(tm.hasMerged());
+ assertNull(tm.getUnSplittable());
assertThrows(IllegalStateException.class, tm::getOperationId);
assertThrows(IllegalStateException.class, tm::getSuspend);
assertThrows(IllegalStateException.class, tm::getTime);
@@ -429,6 +526,9 @@ public class TabletMetadataTest {
assertThrows(IllegalStateException.class, tm2::getHostingRequested);
assertThrows(IllegalStateException.class, tm2::getSelectedFiles);
assertThrows(IllegalStateException.class, tm2::getCompacted);
+ assertThrows(IllegalStateException.class, tm2::hasMerged);
+ assertThrows(IllegalStateException.class,
tm2::getUserCompactionsRequested);
+ assertThrows(IllegalStateException.class, tm2::getUnSplittable);
var ecid1 = ExternalCompactionId.generate(UUID.randomUUID());
CompactionMetadata ecm = new CompactionMetadata(Set.of(sf1, sf2), rf1,
"cid1",
@@ -438,11 +538,14 @@ public class TabletMetadataTest {
LogEntry le2 = LogEntry.fromPath("localhost+8020/" + UUID.randomUUID());
SelectedFiles selFiles = new SelectedFiles(Set.of(sf1, sf4), false,
FateId.from(type, 159L));
+ var unsplittableMeta =
+ UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120, Set.of(sf1,
sf2));
TabletMetadata tm3 =
TabletMetadata.builder(extent).putExternalCompaction(ecid1, ecm)
.putSuspension(ser1, 45L).putTime(new MetadataTime(479,
TimeType.LOGICAL)).putWal(le1)
.putWal(le2).setHostingRequested().putSelectedFiles(selFiles).setMerged()
- .putUserCompactionRequested(FateId.from(type, 159L)).build();
+ .putUserCompactionRequested(FateId.from(type,
159L)).setUnSplittable(unsplittableMeta)
+ .build();
assertEquals(Set.of(ecid1), tm3.getExternalCompactions().keySet());
assertEquals(Set.of(sf1, sf2),
tm3.getExternalCompactions().get(ecid1).getJobFiles());
@@ -458,6 +561,7 @@ public class TabletMetadataTest {
assertEquals(selFiles.getMetadataValue(),
tm3.getSelectedFiles().getMetadataValue());
assertTrue(tm3.hasMerged());
assertTrue(tm3.getUserCompactionsRequested().contains(FateId.from(type,
159L)));
+ assertEquals(unsplittableMeta, tm3.getUnSplittable());
}
}
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/constraints/MetadataConstraints.java
b/server/base/src/main/java/org/apache/accumulo/server/constraints/MetadataConstraints.java
index abb1c7a27d..c6a30040a1 100644
---
a/server/base/src/main/java/org/apache/accumulo/server/constraints/MetadataConstraints.java
+++
b/server/base/src/main/java/org/apache/accumulo/server/constraints/MetadataConstraints.java
@@ -52,12 +52,14 @@ import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Lo
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.MergedColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
+import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SplitColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SuspendLocationColumn;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Upgrade12to13;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.UserCompactionRequestedColumnFamily;
import org.apache.accumulo.core.metadata.schema.SelectedFiles;
import org.apache.accumulo.core.metadata.schema.TabletOperationId;
+import org.apache.accumulo.core.metadata.schema.UnSplittableMetadata;
import org.apache.accumulo.core.util.ColumnFQ;
import org.apache.accumulo.core.util.cleaner.CleanerUtil;
import org.apache.accumulo.server.ServerContext;
@@ -95,6 +97,7 @@ public class MetadataConstraints implements Constraint {
TabletColumnFamily.AVAILABILITY_COLUMN,
TabletColumnFamily.REQUESTED_COLUMN,
ServerColumnFamily.SELECTED_COLUMN,
+ SplitColumnFamily.UNSPLITTABLE_COLUMN,
Upgrade12to13.COMPACT_COL);
@SuppressWarnings("deprecation")
@@ -270,11 +273,20 @@ public class MetadataConstraints implements Constraint {
} catch (RuntimeException e) {
violations = addViolation(violations, 11);
}
- } else if (CompactedColumnFamily.NAME.equals(columnFamily)
- || UserCompactionRequestedColumnFamily.NAME.equals(columnFamily)) {
+ } else if (CompactedColumnFamily.NAME.equals(columnFamily)) {
if (!FateId.isFateId(columnQualifier.toString())) {
violations = addViolation(violations, 13);
}
+ } else if
(UserCompactionRequestedColumnFamily.NAME.equals(columnFamily)) {
+ if (!FateId.isFateId(columnQualifier.toString())) {
+ violations = addViolation(violations, 14);
+ }
+ } else if (SplitColumnFamily.UNSPLITTABLE_COLUMN.equals(columnFamily,
columnQualifier)) {
+ try {
+ UnSplittableMetadata.toUnSplittable(new
String(columnUpdate.getValue(), UTF_8));
+ } catch (RuntimeException e) {
+ violations = addViolation(violations, 15);
+ }
} else if (columnFamily.equals(BulkFileColumnFamily.NAME)) {
if (!columnUpdate.isDeleted() && !checkedBulk) {
/*
@@ -435,6 +447,10 @@ public class MetadataConstraints implements Constraint {
return "Invalid data file metadata format";
case 13:
return "Invalid compacted column";
+ case 14:
+ return "Invalid user compaction requested column";
+ case 15:
+ return "Invalid unsplittable column";
}
return null;
}
diff --git
a/server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletManagementIterator.java
b/server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletManagementIterator.java
index df37df441c..4704f691c8 100644
---
a/server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletManagementIterator.java
+++
b/server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletManagementIterator.java
@@ -29,6 +29,7 @@ import java.util.Set;
import java.util.SortedMap;
import org.apache.accumulo.core.client.IteratorSetting;
+import org.apache.accumulo.core.client.PluginEnvironment.Configuration;
import org.apache.accumulo.core.client.ScannerBase;
import org.apache.accumulo.core.conf.AccumuloConfiguration;
import org.apache.accumulo.core.conf.ConfigurationCopy;
@@ -44,9 +45,9 @@ import
org.apache.accumulo.core.manager.state.TabletManagement;
import
org.apache.accumulo.core.manager.state.TabletManagement.ManagementAction;
import org.apache.accumulo.core.manager.thrift.ManagerState;
import org.apache.accumulo.core.metadata.TabletState;
-import org.apache.accumulo.core.metadata.schema.DataFileValue;
import org.apache.accumulo.core.metadata.schema.TabletMetadata;
import org.apache.accumulo.core.metadata.schema.TabletOperationType;
+import org.apache.accumulo.core.metadata.schema.UnSplittableMetadata;
import org.apache.accumulo.core.spi.balancer.SimpleLoadBalancer;
import org.apache.accumulo.core.spi.balancer.TabletBalancer;
import org.apache.accumulo.core.spi.compaction.CompactionKind;
@@ -54,6 +55,7 @@ import
org.apache.accumulo.server.compaction.CompactionJobGenerator;
import org.apache.accumulo.server.fs.VolumeUtil;
import org.apache.accumulo.server.iterators.TabletIteratorEnvironment;
import org.apache.accumulo.server.manager.balancer.BalancerEnvironmentImpl;
+import org.apache.accumulo.server.split.SplitUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -69,12 +71,28 @@ public class TabletManagementIterator extends
SkippingIterator {
private TabletBalancer balancer;
private static boolean shouldReturnDueToSplit(final TabletMetadata tm,
- final long splitThreshold) {
- final long sumOfFileSizes =
-
tm.getFilesMap().values().stream().mapToLong(DataFileValue::getSize).sum();
- final boolean shouldSplit = sumOfFileSizes > splitThreshold;
- LOG.trace("{} should split? sum: {}, threshold: {}, result: {}",
tm.getExtent(), sumOfFileSizes,
- splitThreshold, shouldSplit);
+ final Configuration tableConfig) {
+
+ final long splitThreshold = ConfigurationTypeHelper
+
.getFixedMemoryAsBytes(tableConfig.get(Property.TABLE_SPLIT_THRESHOLD.getKey()));
+ final long maxEndRowSize = ConfigurationTypeHelper
+
.getFixedMemoryAsBytes(tableConfig.get(Property.TABLE_MAX_END_ROW_SIZE.getKey()));
+ final int maxFilesToOpen = (int)
ConfigurationTypeHelper.getFixedMemoryAsBytes(
+
tableConfig.get(Property.TSERV_TABLET_SPLIT_FINDMIDPOINT_MAXOPEN.getKey()));
+
+ // If the current computed metadata matches the current marker then we
can't split,
+ // so we return false. If the marker is set but doesn't match then return
true
+ // which gives a chance to clean up the marker and recheck.
+ var unsplittable = tm.getUnSplittable();
+ if (unsplittable != null) {
+ return
!unsplittable.equals(UnSplittableMetadata.toUnSplittable(tm.getExtent(),
+ splitThreshold, maxEndRowSize, maxFilesToOpen, tm.getFiles()));
+ }
+
+ // If unsplittable is not set at all then check if over split threshold
+ final boolean shouldSplit = SplitUtils.needsSplit(tableConfig, tm);
+ LOG.trace("{} should split? sum: {}, threshold: {}, result: {}",
tm.getExtent(),
+ tm.getFileSize(), splitThreshold, shouldSplit);
return shouldSplit;
}
@@ -255,10 +273,7 @@ public class TabletManagementIterator extends
SkippingIterator {
if (tm.getOperationId() == null
&& Collections.disjoint(REASONS_NOT_TO_SPLIT_OR_COMPACT,
reasonsToReturnThisTablet)) {
try {
- final long splitThreshold =
-
ConfigurationTypeHelper.getFixedMemoryAsBytes(this.env.getPluginEnv()
-
.getConfiguration(tm.getTableId()).get(Property.TABLE_SPLIT_THRESHOLD.getKey()));
- if (shouldReturnDueToSplit(tm, splitThreshold)) {
+ if (shouldReturnDueToSplit(tm,
this.env.getPluginEnv().getConfiguration(tm.getTableId()))) {
reasonsToReturnThisTablet.add(ManagementAction.NEEDS_SPLITTING);
}
// important to call this since reasonsToReturnThisTablet is passed to
it
diff --git
a/server/manager/src/main/java/org/apache/accumulo/manager/split/SplitUtils.java
b/server/base/src/main/java/org/apache/accumulo/server/split/SplitUtils.java
similarity index 82%
rename from
server/manager/src/main/java/org/apache/accumulo/manager/split/SplitUtils.java
rename to
server/base/src/main/java/org/apache/accumulo/server/split/SplitUtils.java
index bce10badb2..fcf4e2422f 100644
---
a/server/manager/src/main/java/org/apache/accumulo/manager/split/SplitUtils.java
+++ b/server/base/src/main/java/org/apache/accumulo/server/split/SplitUtils.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.accumulo.manager.split;
+package org.apache.accumulo.server.split;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -29,6 +29,8 @@ import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Predicate;
+import org.apache.accumulo.core.client.PluginEnvironment.Configuration;
+import org.apache.accumulo.core.conf.ConfigurationTypeHelper;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Key;
@@ -39,8 +41,8 @@ import
org.apache.accumulo.core.iterators.SortedKeyValueIterator;
import org.apache.accumulo.core.iteratorsImpl.system.MultiIterator;
import org.apache.accumulo.core.metadata.StoredTabletFile;
import org.apache.accumulo.core.metadata.TabletFile;
-import org.apache.accumulo.core.metadata.schema.DataFileValue;
import org.apache.accumulo.core.metadata.schema.TabletMetadata;
+import org.apache.accumulo.core.metadata.schema.UnSplittableMetadata;
import org.apache.accumulo.server.ServerContext;
import org.apache.accumulo.server.conf.TableConfiguration;
import org.apache.hadoop.fs.FileSystem;
@@ -197,8 +199,6 @@ public class SplitUtils {
}
public static SortedSet<Text> findSplits(ServerContext context,
TabletMetadata tabletMetadata) {
- var estimatedSize =
-
tabletMetadata.getFilesMap().values().stream().mapToLong(DataFileValue::getSize).sum();
var tableConf = context.getTableConfiguration(tabletMetadata.getTableId());
var threshold = tableConf.getAsBytes(Property.TABLE_SPLIT_THRESHOLD);
var maxEndRowSize = tableConf.getAsBytes(Property.TABLE_MAX_END_ROW_SIZE);
@@ -207,7 +207,8 @@ public class SplitUtils {
// anymore.
int maxFilesToOpen =
tableConf.getCount(Property.TSERV_TABLET_SPLIT_FINDMIDPOINT_MAXOPEN);
- if (estimatedSize <= threshold) {
+ var estimatedSize = tabletMetadata.getFileSize();
+ if (!needsSplit(context, tabletMetadata)) {
return new TreeSet<>();
}
@@ -295,4 +296,39 @@ public class SplitUtils {
return splits;
}
+
+ public static boolean needsSplit(ServerContext context, TabletMetadata
tabletMetadata) {
+ var tableConf = context.getTableConfiguration(tabletMetadata.getTableId());
+ var splitThreshold = tableConf.getAsBytes(Property.TABLE_SPLIT_THRESHOLD);
+ return needsSplit(splitThreshold, tabletMetadata);
+ }
+
+ public static boolean needsSplit(final Configuration tableConf,
TabletMetadata tabletMetadata) {
+ var splitThreshold = ConfigurationTypeHelper
+
.getFixedMemoryAsBytes(tableConf.get(Property.TABLE_SPLIT_THRESHOLD.getKey()));
+ return needsSplit(splitThreshold, tabletMetadata);
+ }
+
+ public static boolean needsSplit(long splitThreshold, TabletMetadata
tabletMetadata) {
+ return tabletMetadata.getFileSize() > splitThreshold;
+ }
+
+ public static UnSplittableMetadata toUnSplittable(ServerContext context,
+ TabletMetadata tabletMetadata) {
+ var tableConf = context.getTableConfiguration(tabletMetadata.getTableId());
+ var splitThreshold = tableConf.getAsBytes(Property.TABLE_SPLIT_THRESHOLD);
+ var maxEndRowSize = tableConf.getAsBytes(Property.TABLE_MAX_END_ROW_SIZE);
+ int maxFilesToOpen =
tableConf.getCount(Property.TSERV_TABLET_SPLIT_FINDMIDPOINT_MAXOPEN);
+
+ var unSplittableMetadata =
UnSplittableMetadata.toUnSplittable(tabletMetadata.getExtent(),
+ splitThreshold, maxEndRowSize, maxFilesToOpen,
tabletMetadata.getFiles());
+
+ log.trace(
+ "Created unsplittable metadata for tablet {}. splitThreshold: {},
maxEndRowSize:{}, maxFilesToOpen: {}, hashCode: {}",
+ tabletMetadata.getExtent(), splitThreshold, maxEndRowSize,
maxFilesToOpen,
+ unSplittableMetadata);
+
+ return unSplittableMetadata;
+ }
+
}
diff --git
a/server/base/src/test/java/org/apache/accumulo/server/constraints/MetadataConstraintsTest.java
b/server/base/src/test/java/org/apache/accumulo/server/constraints/MetadataConstraintsTest.java
index 89e22a09de..5f3132acd3 100644
---
a/server/base/src/test/java/org/apache/accumulo/server/constraints/MetadataConstraintsTest.java
+++
b/server/base/src/test/java/org/apache/accumulo/server/constraints/MetadataConstraintsTest.java
@@ -19,8 +19,10 @@
package org.apache.accumulo.server.constraints;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.lang.reflect.Method;
import java.util.Base64;
@@ -31,6 +33,7 @@ import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
import org.apache.accumulo.core.fate.FateId;
import org.apache.accumulo.core.fate.FateInstanceType;
import org.apache.accumulo.core.metadata.AccumuloTable;
@@ -43,9 +46,11 @@ import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.Cu
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.DataFileColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ScanFileColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.ServerColumnFamily;
+import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.SplitColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.TabletColumnFamily;
import
org.apache.accumulo.core.metadata.schema.MetadataSchema.TabletsSection.UserCompactionRequestedColumnFamily;
import org.apache.accumulo.core.metadata.schema.SelectedFiles;
+import org.apache.accumulo.core.metadata.schema.UnSplittableMetadata;
import org.apache.accumulo.server.ServerContext;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
@@ -493,17 +498,17 @@ public class MetadataConstraintsTest {
@Test
public void testCompacted() {
- testFateCqValidation(CompactedColumnFamily.STR_NAME);
+ testFateCqValidation(CompactedColumnFamily.STR_NAME, (short) 13);
}
@Test
public void testUserCompactionRequested() {
- testFateCqValidation(UserCompactionRequestedColumnFamily.STR_NAME);
+ testFateCqValidation(UserCompactionRequestedColumnFamily.STR_NAME, (short)
14);
}
// Verify that columns that store a FateId in their CQ
// validate and only allow a correctly formatted FateId
- private void testFateCqValidation(String column) {
+ private void testFateCqValidation(String column, short violation) {
MetadataConstraints mc = new MetadataConstraints();
Mutation m;
List<Short> violations;
@@ -519,7 +524,54 @@ public class MetadataConstraintsTest {
violations = mc.check(createEnv(), m);
assertNotNull(violations);
assertEquals(1, violations.size());
- assertEquals(Short.valueOf((short) 13), violations.get(0));
+ assertEquals(violation, violations.get(0));
+ }
+
+ @Test
+ public void testUnsplittableColumn() {
+ MetadataConstraints mc = new MetadataConstraints();
+ Mutation m;
+ List<Short> violations;
+
+ StoredTabletFile sf1 = StoredTabletFile.of(new
Path("hdfs://nn1/acc/tables/1/t-0001/sf1.rf"));
+ var unsplittableMeta = UnSplittableMetadata
+ .toUnSplittable(KeyExtent.fromMetaRow(new Text("0;foo")), 100, 110,
120, Set.of(sf1));
+
+ m = new Mutation(new Text("0;foo"));
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.put(m, new
Value(unsplittableMeta.toBase64()));
+ violations = mc.check(createEnv(), m);
+ assertNull(violations);
+
+ // Verify empty value not allowed
+ m = new Mutation(new Text("0;foo"));
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.put(m, new Value());
+ violations = mc.check(createEnv(), m);
+ assertNotNull(violations);
+ assertEquals(2, violations.size());
+ assertIterableEquals(List.of((short) 6, (short) 15), violations);
+
+ // test invalid args
+ KeyExtent extent = KeyExtent.fromMetaRow(new Text("0;foo"));
+ assertThrows(IllegalArgumentException.class,
+ () -> UnSplittableMetadata.toUnSplittable(extent, -100, 110, 120,
Set.of(sf1)));
+ assertThrows(IllegalArgumentException.class,
+ () -> UnSplittableMetadata.toUnSplittable(extent, 100, -110, 120,
Set.of(sf1)));
+ assertThrows(IllegalArgumentException.class,
+ () -> UnSplittableMetadata.toUnSplittable(extent, 100, 110, -120,
Set.of(sf1)));
+ assertThrows(NullPointerException.class,
+ () -> UnSplittableMetadata.toUnSplittable(extent, 100, 110, 120,
null));
+
+ // Test metadata constraints validate invalid hashcode
+ m = new Mutation(new Text("0;foo"));
+ unsplittableMeta = UnSplittableMetadata.toUnSplittable(extent, 100, 110,
120, Set.of(sf1));
+ // partial hashcode is invalid
+ var invalidHashCode =
+ unsplittableMeta.toBase64().substring(0,
unsplittableMeta.toBase64().length() - 1);
+ SplitColumnFamily.UNSPLITTABLE_COLUMN.put(m, new Value(invalidHashCode));
+ violations = mc.check(createEnv(), m);
+ assertNotNull(violations);
+ assertEquals(1, violations.size());
+ assertEquals(Short.valueOf((short) 15), violations.get(0));
}
// Encode a row how it would appear in Json
diff --git
a/server/manager/src/test/java/org/apache/accumulo/manager/split/SplitUtilsTest.java
b/server/base/src/test/java/org/apache/accumulo/server/split/SplitUtilsTest.java
similarity index 99%
rename from
server/manager/src/test/java/org/apache/accumulo/manager/split/SplitUtilsTest.java
rename to
server/base/src/test/java/org/apache/accumulo/server/split/SplitUtilsTest.java
index 80258e4914..e8281e4c08 100644
---
a/server/manager/src/test/java/org/apache/accumulo/manager/split/SplitUtilsTest.java
+++
b/server/base/src/test/java/org/apache/accumulo/server/split/SplitUtilsTest.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.accumulo.manager.split;
+package org.apache.accumulo.server.split;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
diff --git
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/split/FindSplits.java
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/split/FindSplits.java
index 695f5650cc..7e3acd8d7f 100644
---
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/split/FindSplits.java
+++
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/split/FindSplits.java
@@ -18,15 +18,22 @@
*/
package org.apache.accumulo.manager.tableOps.split;
+import static
org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType.FILES;
+
+import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.function.Consumer;
import org.apache.accumulo.core.dataImpl.KeyExtent;
import org.apache.accumulo.core.fate.FateId;
import org.apache.accumulo.core.fate.Repo;
+import org.apache.accumulo.core.metadata.schema.Ample.ConditionalResult;
+import org.apache.accumulo.core.metadata.schema.Ample.ConditionalResult.Status;
+import org.apache.accumulo.core.metadata.schema.UnSplittableMetadata;
import org.apache.accumulo.manager.Manager;
-import org.apache.accumulo.manager.split.SplitUtils;
import org.apache.accumulo.manager.tableOps.ManagerRepo;
+import org.apache.accumulo.server.split.SplitUtils;
import org.apache.hadoop.io.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +52,9 @@ public class FindSplits extends ManagerRepo {
@Override
public Repo<Manager> call(FateId fateId, Manager manager) throws Exception {
var extent = splitInfo.getOriginal();
- var tabletMetadata = manager.getContext().getAmple().readTablet(extent);
+ var ample = manager.getContext().getAmple();
+ var tabletMetadata = ample.readTablet(extent);
+ Optional<UnSplittableMetadata> computedUnsplittable = Optional.empty();
if (tabletMetadata == null) {
log.trace("Table {} no longer exist, so not gonna try to find a split
point for it", extent);
@@ -58,12 +67,25 @@ public class FindSplits extends ManagerRepo {
return null;
}
+ // The TabletManagementIterator should normally not be trying to split if
the tablet was marked
+ // as unsplittable and the metadata hasn't changed so check that the
metadata is different
+ if (tabletMetadata.getUnSplittable() != null) {
+ computedUnsplittable =
+ Optional.of(SplitUtils.toUnSplittable(manager.getContext(),
tabletMetadata));
+ if
(tabletMetadata.getUnSplittable().equals(computedUnsplittable.orElseThrow())) {
+ log.debug("Not splitting {} because unsplittable metadata is present
and did not change",
+ extent);
+ return null;
+ }
+ }
+
if (!tabletMetadata.getLogs().isEmpty()) {
// This code is only called by system initiated splits, so if walogs are
present it probably
// makes sense to wait for the data in them to be written to a file
before finding splits
// points.
log.debug("Not splitting {} because it has walogs {}",
tabletMetadata.getExtent(),
tabletMetadata.getLogs().size());
+ return null;
}
SortedSet<Text> splits = SplitUtils.findSplits(manager.getContext(),
tabletMetadata);
@@ -73,15 +95,59 @@ public class FindSplits extends ManagerRepo {
}
if (splits.isEmpty()) {
- log.info("Tablet {} needs to split, but no split points could be found.",
- tabletMetadata.getExtent());
- // ELASTICITY_TODO record the fact that tablet is un-splittable in
metadata table in a new
- // column. Record the config used to reach this decision and a hash of
the file. The tablet
- // mgmt iterator can inspect this column and only try to split the
tablet when something has
- // changed.
+ Consumer<ConditionalResult> resultConsumer = result -> {
+ if (result.getStatus() == Status.REJECTED) {
+ log.debug("{} unsplittable metadata update for {} was rejected ",
fateId,
+ result.getExtent());
+ }
+ };
+
+ try (var tabletsMutator =
ample.conditionallyMutateTablets(resultConsumer)) {
+ // No split points were found, so we need to check if the tablet still
+ // needs to be split but is unsplittable, or if a split is not needed
+
+ // Case 1: If a split is needed then set the unsplittable marker as no
split
+ // points could be found so that we don't keep trying again until the
+ // split metadata is changed
+ if (SplitUtils.needsSplit(manager.getContext(), tabletMetadata)) {
+ log.info("Tablet {} needs to split, but no split points could be
found.",
+ tabletMetadata.getExtent());
+ var unSplittableMeta = computedUnsplittable
+ .orElseGet(() -> SplitUtils.toUnSplittable(manager.getContext(),
tabletMetadata));
+
+ // With the current design we don't need to require the files to be
the same
+ // for correctness as the TabletManagementIterator will detect the
difference
+ // when computing the hash and retry a new split operation if there
is not a match.
+ // But if we already know there's a change now, it would be more
efficient to fail and
+ // retry the current fate op vs completing and having the iterator
submit a new one.
+ log.debug("Setting unsplittable metadata on tablet {}. hashCode: {}",
+ tabletMetadata.getExtent(), unSplittableMeta);
+ var mutator =
tabletsMutator.mutateTablet(extent).requireAbsentOperation()
+ .requireSame(tabletMetadata,
FILES).setUnSplittable(unSplittableMeta);
+ mutator.submit(tm -> unSplittableMeta.equals(tm.getUnSplittable()));
+
+ // Case 2: If the unsplittable marker has already been previously
set, but we do not need
+ // to split then clear the marker. This could happen in some
scenarios such as
+ // a compaction that shrinks a previously unsplittable tablet below
the threshold
+ // or if the threshold has been raised higher because the tablet
management iterator
+ // will try and split any time the computed metadata changes.
+ } else if (tabletMetadata.getUnSplittable() != null) {
+ log.info("Tablet {} no longer needs to split, deleting unsplittable
marker.",
+ tabletMetadata.getExtent());
+ var mutator =
tabletsMutator.mutateTablet(extent).requireAbsentOperation()
+ .requireSame(tabletMetadata, FILES).deleteUnSplittable();
+ mutator.submit(tm -> tm.getUnSplittable() == null);
+ // Case 3: The table config and/or set of files changed since the
tablet mgmt iterator
+ // examined this tablet.
+ } else {
+ log.debug("Tablet {} no longer needs to split, ignoring it.",
tabletMetadata.getExtent());
+ }
+ }
+
return null;
}
return new PreSplit(extent, splits);
}
+
}
diff --git
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/split/UpdateTablets.java
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/split/UpdateTablets.java
index f2d1501ae5..808ebf8b3a 100644
---
a/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/split/UpdateTablets.java
+++
b/server/manager/src/main/java/org/apache/accumulo/manager/tableOps/split/UpdateTablets.java
@@ -282,6 +282,12 @@ public class UpdateTablets extends ManagerRepo {
mutator.deleteLocation(tabletMetadata.getLast());
}
+ // Clean up any previous unsplittable marker
+ if (tabletMetadata.getUnSplittable() != null) {
+ mutator.deleteUnSplittable();
+ log.debug("{} deleting unsplittable metadata from {} because of
split", fateId, newExtent);
+ }
+
mutator.submit(tm -> false);
var result = tabletsMutator.process().get(splitInfo.getOriginal());
diff --git
a/server/manager/src/test/java/org/apache/accumulo/manager/tableOps/split/UpdateTabletsTest.java
b/server/manager/src/test/java/org/apache/accumulo/manager/tableOps/split/UpdateTabletsTest.java
index baccd84680..226bc53034 100644
---
a/server/manager/src/test/java/org/apache/accumulo/manager/tableOps/split/UpdateTabletsTest.java
+++
b/server/manager/src/test/java/org/apache/accumulo/manager/tableOps/split/UpdateTabletsTest.java
@@ -52,6 +52,7 @@ import
org.apache.accumulo.core.metadata.schema.TabletMetadata;
import org.apache.accumulo.core.metadata.schema.TabletMetadata.ColumnType;
import org.apache.accumulo.core.metadata.schema.TabletOperationId;
import org.apache.accumulo.core.metadata.schema.TabletOperationType;
+import org.apache.accumulo.core.metadata.schema.UnSplittableMetadata;
import org.apache.accumulo.core.tabletserver.log.LogEntry;
import org.apache.accumulo.manager.Manager;
import org.apache.accumulo.manager.split.Splitter;
@@ -88,7 +89,7 @@ public class UpdateTabletsTest {
ColumnType.LOADED, ColumnType.USER_COMPACTION_REQUESTED,
ColumnType.MERGED,
ColumnType.LAST, ColumnType.SCANS, ColumnType.DIR,
ColumnType.CLONED, ColumnType.FLUSH_ID,
ColumnType.FLUSH_NONCE, ColumnType.SUSPEND, ColumnType.AVAILABILITY,
- ColumnType.HOSTING_REQUESTED, ColumnType.COMPACTED);
+ ColumnType.HOSTING_REQUESTED, ColumnType.COMPACTED,
ColumnType.UNSPLITTABLE);
/**
* The purpose of this test is to catch new tablet metadata columns that
were added w/o
@@ -261,6 +262,9 @@ public class UpdateTabletsTest {
EasyMock.expect(tabletMeta.getHostingRequested()).andReturn(true).atLeastOnce();
EasyMock.expect(tabletMeta.getSuspend()).andReturn(suspendingTServer).atLeastOnce();
EasyMock.expect(tabletMeta.getLast()).andReturn(lastLocation).atLeastOnce();
+ UnSplittableMetadata usm =
+ UnSplittableMetadata.toUnSplittable(origExtent, 1000, 1001, 1002,
tabletFiles.keySet());
+ EasyMock.expect(tabletMeta.getUnSplittable()).andReturn(usm).atLeastOnce();
EasyMock.expect(ample.readTablet(origExtent)).andReturn(tabletMeta);
@@ -340,6 +344,7 @@ public class UpdateTabletsTest {
EasyMock.expect(tablet3Mutator.deleteHostingRequested()).andReturn(tablet3Mutator);
EasyMock.expect(tablet3Mutator.deleteSuspension()).andReturn(tablet3Mutator);
EasyMock.expect(tablet3Mutator.deleteLocation(lastLocation)).andReturn(tablet3Mutator);
+
EasyMock.expect(tablet3Mutator.deleteUnSplittable()).andReturn(tablet3Mutator);
tablet3Mutator.submit(EasyMock.anyObject());
EasyMock.expectLastCall().once();
EasyMock.expect(tabletsMutator.mutateTablet(origExtent)).andReturn(tablet3Mutator);
diff --git a/test/src/main/java/org/apache/accumulo/test/LargeSplitRowIT.java
b/test/src/main/java/org/apache/accumulo/test/LargeSplitRowIT.java
index 68c21d67dd..b810707cba 100644
--- a/test/src/main/java/org/apache/accumulo/test/LargeSplitRowIT.java
+++ b/test/src/main/java/org/apache/accumulo/test/LargeSplitRowIT.java
@@ -44,9 +44,13 @@ import org.apache.accumulo.core.conf.ConfigurationTypeHelper;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.TableId;
import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.dataImpl.KeyExtent;
+import org.apache.accumulo.core.metadata.schema.TabletMetadata;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.miniclusterImpl.MiniAccumuloConfigImpl;
+import org.apache.accumulo.server.split.SplitUtils;
import org.apache.accumulo.test.functional.ConfigurableMacBase;
import org.apache.accumulo.test.util.Wait;
import org.apache.hadoop.conf.Configuration;
@@ -129,7 +133,8 @@ public class LargeSplitRowIT extends ConfigurableMacBase {
Property.TABLE_SPLIT_THRESHOLD.getKey(), "10K",
Property.TABLE_FILE_COMPRESSION_TYPE.getKey(), "none",
Property.TABLE_FILE_COMPRESSED_BLOCK_SIZE.getKey(), "64",
- Property.TABLE_MAX_END_ROW_SIZE.getKey(), "1000"
+ Property.TABLE_MAX_END_ROW_SIZE.getKey(), "1000",
+ Property.TABLE_MAJC_RATIO.getKey(), "9999"
);
// @formatter:on
client.tableOperations().create(tableName, new
NewTableConfiguration().setProperties(props));
@@ -155,7 +160,17 @@ public class LargeSplitRowIT extends ConfigurableMacBase {
// Flush the BatchWriter and table and sleep for a bit to make sure that
there is enough time
// for the table to split if need be.
client.tableOperations().flush(tableName, new Text(), new Text("z"),
true);
- Thread.sleep(500L);
+
+ // Wait for the tablet to be marked as unsplittable due to the system
split running
+ TableId tableId =
TableId.of(client.tableOperations().tableIdMap().get(tableName));
+ Wait.waitFor(() -> getServerContext().getAmple()
+ .readTablet(new KeyExtent(tableId, null, null)).getUnSplittable() !=
null,
+ Wait.MAX_WAIT_MILLIS, 100);
+
+ // Verify that the unsplittable column is read correctly
+ TabletMetadata tm =
+ getServerContext().getAmple().readTablet(new KeyExtent(tableId,
null, null));
+ assertEquals(tm.getUnSplittable(),
SplitUtils.toUnSplittable(getServerContext(), tm));
// Make sure all the data that was put in the table is still correct
int count = 0;
@@ -242,6 +257,164 @@ public class LargeSplitRowIT extends ConfigurableMacBase {
}
}
+ @Test
+ @Timeout(60)
+ public void testUnsplittableColumn() throws Exception {
+ log.info("Unsplittable Column Test");
+ try (AccumuloClient client =
Accumulo.newClient().from(getClientProperties()).build()) {
+ // make a table and lower the configuration properties
+ // @formatter:off
+ var maxEndRow = 100;
+ Map<String,String> props = Map.of(
+ Property.TABLE_SPLIT_THRESHOLD.getKey(), "1K",
+ Property.TABLE_FILE_COMPRESSION_TYPE.getKey(), "none",
+ Property.TABLE_FILE_COMPRESSED_BLOCK_SIZE.getKey(), "64",
+ Property.TABLE_MAX_END_ROW_SIZE.getKey(), "" + maxEndRow,
+ Property.TABLE_MAJC_RATIO.getKey(), "9999"
+ );
+ // @formatter:on
+
+ final String tableName = getUniqueNames(1)[0];
+ client.tableOperations().create(tableName, new
NewTableConfiguration().setProperties(props));
+
+ // Create a key for a table entry that is longer than the allowed size
for an
+ // end row and fill this key with all m's except the last spot
+ byte[] data = new byte[maxEndRow + 1];
+ Arrays.fill(data, 0, data.length - 2, (byte) 'm');
+
+ final int numOfMutations = 20;
+ try (BatchWriter batchWriter = client.createBatchWriter(tableName)) {
+ // Make the last place in the key different for every entry added to
the table
+ for (int i = 0; i < numOfMutations; i++) {
+ data[data.length - 1] = (byte) i;
+ Mutation m = new Mutation(data);
+ m.put("cf", "cq", "value");
+ batchWriter.addMutation(m);
+ }
+ }
+ // Flush the BatchWriter and table
+ client.tableOperations().flush(tableName, null, null, true);
+
+ // Wait for the tablets to be marked as unsplittable due to the system
split running
+ TableId tableId =
TableId.of(client.tableOperations().tableIdMap().get(tableName));
+ Wait.waitFor(() -> getServerContext().getAmple()
+ .readTablet(new KeyExtent(tableId, null, null)).getUnSplittable() !=
null,
+ Wait.MAX_WAIT_MILLIS, 100);
+
+ // Verify that the unsplittable column is read correctly
+ TabletMetadata tm =
+ getServerContext().getAmple().readTablet(new KeyExtent(tableId,
null, null));
+ var unsplittable = tm.getUnSplittable();
+ assertEquals(unsplittable, SplitUtils.toUnSplittable(getServerContext(),
tm));
+
+ // Make sure no splits occurred in the table
+ assertTrue(client.tableOperations().listSplits(tableName).isEmpty());
+
+ // Bump the value for max end row by 1, we should still not be able to
split but this should
+ // trigger an update to the unsplittable metadata value
+ client.tableOperations().setProperty(tableName,
Property.TABLE_MAX_END_ROW_SIZE.getKey(),
+ "101");
+
+ // wait for the unsplittable marker to be set to a new value due to the
property change
+ Wait.waitFor(() -> {
+ var updatedUnsplittable = getServerContext().getAmple()
+ .readTablet(new KeyExtent(tableId, null, null)).getUnSplittable();
+ return updatedUnsplittable != null &&
!updatedUnsplittable.equals(unsplittable);
+ }, Wait.MAX_WAIT_MILLIS, 100);
+ // recheck with the computed meta is correct after property update
+ tm = getServerContext().getAmple().readTablet(new KeyExtent(tableId,
null, null));
+ assertEquals(tm.getUnSplittable(),
SplitUtils.toUnSplittable(getServerContext(), tm));
+
+ // Bump max end row size and verify split occurs and unsplittable column
is cleaned up
+ client.tableOperations().setProperty(tableName,
Property.TABLE_MAX_END_ROW_SIZE.getKey(),
+ "500");
+
+ // Wait for splits to occur
+ assertTrue(client.tableOperations().listSplits(tableName).isEmpty());
+ Wait.waitFor(() ->
!client.tableOperations().listSplits(tableName).isEmpty(),
+ Wait.MAX_WAIT_MILLIS, 100);
+
+ // Verify all tablets have no unsplittable metadata column
+ Wait.waitFor(() -> {
+ try (var tabletsMetadata =
+
getServerContext().getAmple().readTablets().forTable(tableId).build()) {
+ return tabletsMetadata.stream()
+ .allMatch(tabletMetadata -> tabletMetadata.getUnSplittable() ==
null);
+ }
+ }, Wait.MAX_WAIT_MILLIS, 100);
+ }
+ }
+
+ // Test the unsplittable column is cleaned up if a previously marked
unsplittable tablet
+ // no longer needs to be split
+ @Test
+ @Timeout(60)
+ public void testUnsplittableCleanup() throws Exception {
+ log.info("Unsplittable Column Cleanup");
+ try (AccumuloClient client =
Accumulo.newClient().from(getClientProperties()).build()) {
+ // make a table and lower the configuration properties
+ // @formatter:off
+ Map<String,String> props = Map.of(
+ Property.TABLE_SPLIT_THRESHOLD.getKey(), "1K",
+ Property.TABLE_FILE_COMPRESSION_TYPE.getKey(), "none",
+ Property.TABLE_FILE_COMPRESSED_BLOCK_SIZE.getKey(), "64",
+ Property.TABLE_MAJC_RATIO.getKey(), "9999"
+ );
+ // @formatter:on
+
+ final String tableName = getUniqueNames(1)[0];
+ client.tableOperations().create(tableName, new
NewTableConfiguration().setProperties(props));
+
+ byte[] data = new byte[100];
+ Arrays.fill(data, 0, data.length - 1, (byte) 'm');
+
+ // Write enough data that will cause a split. The row is not too large
for a split
+ // but all the rows are the same so tablets won't be able to split
except for
+ // the last tablet (null end row)
+ final int numOfMutations = 20;
+ try (BatchWriter batchWriter = client.createBatchWriter(tableName)) {
+ // Make the last place in the key different for every entry added to
the table
+ for (int i = 0; i < numOfMutations; i++) {
+ Mutation m = new Mutation(data);
+ m.put("cf", "cq" + i, "value");
+ batchWriter.addMutation(m);
+ }
+ }
+ // Flush the BatchWriter and table
+ client.tableOperations().flush(tableName, null, null, true);
+
+ TableId tableId =
TableId.of(client.tableOperations().tableIdMap().get(tableName));
+
+ // Wait for a tablet to be marked as unsplittable due to the system
split running
+ // There is enough data to split more than once so at least one tablet
should be marked
+ // as unsplittable due to the same end row for all keys after the
default tablet is split
+ Wait.waitFor(() -> {
+ try (var tabletsMetadata =
+
getServerContext().getAmple().readTablets().forTable(tableId).build()) {
+ return tabletsMetadata.stream().anyMatch(tm -> tm.getUnSplittable()
!= null);
+ }
+ }, Wait.MAX_WAIT_MILLIS, 100);
+
+ var splits = client.tableOperations().listSplits(tableName);
+
+ // Bump split threshold and verify marker is cleared
+ client.tableOperations().setProperty(tableName,
Property.TABLE_SPLIT_THRESHOLD.getKey(),
+ "1M");
+
+ // All tablets should now be cleared of the unsplittable marker, and we
should have the
+ // same number of splits as before
+ Wait.waitFor(() -> {
+ try (var tabletsMetadata =
+
getServerContext().getAmple().readTablets().forTable(tableId).build()) {
+ return tabletsMetadata.stream().allMatch(tm -> tm.getUnSplittable()
== null);
+ }
+ }, Wait.MAX_WAIT_MILLIS, 100);
+
+ // no more splits should have happened
+ assertEquals(splits, client.tableOperations().listSplits(tableName));
+ }
+ }
+
private void automaticSplit(AccumuloClient client, int max, int spacing)
throws Exception {
// make a table and lower the configuration properties
// @formatter:off