You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by la...@apache.org on 2017/05/09 00:19:16 UTC
phoenix git commit: PHOENIX-3824 Mutable Index partial rebuild should
add only one index row per updated data row.
Repository: phoenix
Updated Branches:
refs/heads/master a1d3c1697 -> 85e344fdf
PHOENIX-3824 Mutable Index partial rebuild should add only one index row per updated data row.
Signed-off-by: Lars Hofhansl <la...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/85e344fd
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/85e344fd
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/85e344fd
Branch: refs/heads/master
Commit: 85e344fdfcc65d4992336eb52868d7ba78ba55d1
Parents: a1d3c16
Author: Vincent Poon <vi...@gmail.com>
Authored: Mon May 8 17:18:54 2017 -0700
Committer: Lars Hofhansl <la...@apache.org>
Committed: Mon May 8 17:18:54 2017 -0700
----------------------------------------------------------------------
.../hbase/index/covered/data/LocalTable.java | 22 +-
.../index/covered/TestNonTxIndexBuilder.java | 317 +++++++++++++++++++
.../index/covered/data/TestLocalTable.java | 63 ++++
3 files changed, 401 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/phoenix/blob/85e344fd/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/covered/data/LocalTable.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/covered/data/LocalTable.java b/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/covered/data/LocalTable.java
index 003df2a..85c54ce 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/covered/data/LocalTable.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/covered/data/LocalTable.java
@@ -33,6 +33,10 @@ import org.apache.hadoop.hbase.regionserver.RegionScanner;
import org.apache.phoenix.hbase.index.covered.update.ColumnReference;
import org.apache.phoenix.hbase.index.util.IndexManagementUtil;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Ordering;
+import com.google.common.primitives.Longs;
+
/**
* Wrapper around a lazily instantiated, local HTable.
* <p>
@@ -61,7 +65,8 @@ public class LocalTable implements LocalHBaseState {
if (ignoreNewerMutations) {
// Provides a means of client indicating that newer cells should not be considered,
// enabling mutations to be replayed to partially rebuild the index when a write fails.
- long ts = m.getFamilyCellMap().firstEntry().getValue().get(0).getTimestamp();
+ // When replaying mutations we want the oldest timestamp (as anything newer we be replayed)
+ long ts = getOldestTimestamp(m.getFamilyCellMap().values());
s.setTimeRange(0,ts);
}
Region region = this.env.getRegion();
@@ -74,4 +79,19 @@ public class LocalTable implements LocalHBaseState {
scanner.close();
return r;
}
+
+ // Returns the smallest timestamp in the given cell lists.
+ // It is assumed that the lists have cells ordered from largest to smallest timestamp
+ protected long getOldestTimestamp(Collection<List<Cell>> cellLists) {
+ Ordering<List<Cell>> cellListOrdering = new Ordering<List<Cell>>() {
+ @Override
+ public int compare(List<Cell> left, List<Cell> right) {
+ // compare the last element of each list, since that is the smallest in that list
+ return Longs.compare(Iterables.getLast(left).getTimestamp(),
+ Iterables.getLast(right).getTimestamp());
+ }
+ };
+ List<Cell> minList = cellListOrdering.min(cellLists);
+ return Iterables.getLast(minList).getTimestamp();
+ }
}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/85e344fd/phoenix-core/src/test/java/org/apache/phoenix/hbase/index/covered/TestNonTxIndexBuilder.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/hbase/index/covered/TestNonTxIndexBuilder.java b/phoenix-core/src/test/java/org/apache/phoenix/hbase/index/covered/TestNonTxIndexBuilder.java
new file mode 100644
index 0000000..d4d69b4
--- /dev/null
+++ b/phoenix-core/src/test/java/org/apache/phoenix/hbase/index/covered/TestNonTxIndexBuilder.java
@@ -0,0 +1,317 @@
+/*
+ * 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.phoenix.hbase.index.covered;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.Properties;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.CellUtil;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.KeyValue.Type;
+import org.apache.hadoop.hbase.client.Mutation;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.io.TimeRange;
+import org.apache.hadoop.hbase.regionserver.Region;
+import org.apache.hadoop.hbase.regionserver.RegionScanner;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.coprocessor.BaseRegionScanner;
+import org.apache.phoenix.hbase.index.MultiMutation;
+import org.apache.phoenix.hbase.index.covered.data.LocalTable;
+import org.apache.phoenix.hbase.index.covered.update.ColumnTracker;
+import org.apache.phoenix.hbase.index.util.GenericKeyValueBuilder;
+import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
+import org.apache.phoenix.index.IndexMaintainer;
+import org.apache.phoenix.index.PhoenixIndexCodec;
+import org.apache.phoenix.index.PhoenixIndexMetaData;
+import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.query.BaseConnectionlessQueryTest;
+import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.PTableKey;
+import org.apache.phoenix.util.PropertiesUtil;
+import org.apache.phoenix.util.TestUtil;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+public class TestNonTxIndexBuilder extends BaseConnectionlessQueryTest {
+ private static final String TEST_TABLE_STRING = "TEST_TABLE";
+ private static final String TEST_TABLE_DDL = "CREATE TABLE IF NOT EXISTS " +
+ TEST_TABLE_STRING + " (\n" +
+ " ORGANIZATION_ID CHAR(4) NOT NULL,\n" +
+ " ENTITY_ID CHAR(7) NOT NULL,\n" +
+ " SCORE INTEGER,\n" +
+ " LAST_UPDATE_TIME TIMESTAMP\n" +
+ " CONSTRAINT TEST_TABLE_PK PRIMARY KEY (\n" +
+ " ORGANIZATION_ID,\n" +
+ " ENTITY_ID\n" +
+ " )\n" +
+ ") VERSIONS=1, MULTI_TENANT=TRUE";
+ private static final String TEST_TABLE_INDEX_STRING = "TEST_TABLE_SCORE";
+ private static final String TEST_TABLE_INDEX_DDL = "CREATE INDEX IF NOT EXISTS " +
+ TEST_TABLE_INDEX_STRING
+ + " ON " + TEST_TABLE_STRING + " (SCORE DESC, ENTITY_ID DESC)";
+ private static final byte[] ROW = Bytes.toBytes("org1entity1"); //length 4 + 7 (see ddl)
+ private static final String FAM_STRING = QueryConstants.DEFAULT_COLUMN_FAMILY;
+ private static final byte[] FAM = Bytes.toBytes(FAM_STRING);
+ private static final byte[] INDEXED_QUALIFIER = Bytes.toBytes("SCORE");
+ private static final byte[] VALUE_1 = Bytes.toBytes(111);
+ private static final byte[] VALUE_2 = Bytes.toBytes(222);
+ private static final byte[] VALUE_3 = Bytes.toBytes(333);
+ private static final byte PUT_TYPE = KeyValue.Type.Put.getCode();
+
+ private NonTxIndexBuilder indexBuilder;
+ private PhoenixIndexMetaData mockIndexMetaData;
+ // Put your current row state in here - the index builder will read from this in LocalTable
+ // to determine whether the index has changed.
+ // Whatever we return here should match the table DDL (e.g. length of column value)
+ private List<Cell> currentRowCells;
+
+ /**
+ * Test setup so that {@link NonTxIndexBuilder#getIndexUpdate(Mutation, IndexMetaData)} can be
+ * called, where any read requests to
+ * {@link LocalTable#getCurrentRowState(Mutation, Collection, boolean)} are read from our test
+ * field 'currentRowCells'
+ */
+ @Before
+ public void setup() throws Exception {
+ RegionCoprocessorEnvironment env = Mockito.mock(RegionCoprocessorEnvironment.class);
+ Configuration conf = new Configuration(false);
+ conf.set(NonTxIndexBuilder.CODEC_CLASS_NAME_KEY, PhoenixIndexCodec.class.getName());
+ Mockito.when(env.getConfiguration()).thenReturn(conf);
+
+ // the following is used by LocalTable#getCurrentRowState()
+ Region mockRegion = Mockito.mock(Region.class);
+ Mockito.when(env.getRegion()).thenReturn(mockRegion);
+
+ Mockito.when(mockRegion.getScanner(Mockito.any(Scan.class)))
+ .thenAnswer(new Answer<RegionScanner>() {
+ @Override
+ public RegionScanner answer(InvocationOnMock invocation) throws Throwable {
+ Scan sArg = (Scan) invocation.getArguments()[0];
+ TimeRange timeRange = sArg.getTimeRange();
+ return getMockTimeRangeRegionScanner(timeRange);
+ }
+ });
+
+ // the following is called by PhoenixIndexCodec#getIndexUpserts() , getIndexDeletes()
+ HRegionInfo mockRegionInfo = Mockito.mock(HRegionInfo.class);
+ Mockito.when(mockRegion.getRegionInfo()).thenReturn(mockRegionInfo);
+ Mockito.when(mockRegionInfo.getStartKey()).thenReturn(Bytes.toBytes("a"));
+ Mockito.when(mockRegionInfo.getEndKey()).thenReturn(Bytes.toBytes("z"));
+
+ mockIndexMetaData = Mockito.mock(PhoenixIndexMetaData.class);
+ Mockito.when(mockIndexMetaData.isImmutableRows()).thenReturn(false);
+ Mockito.when(mockIndexMetaData.getIndexMaintainers())
+ .thenReturn(Collections.singletonList(getTestIndexMaintainer()));
+
+ indexBuilder = new NonTxIndexBuilder();
+ indexBuilder.setup(env);
+ }
+
+ // returns a RegionScanner which filters currentRowCells using the given TimeRange.
+ // This is called from LocalTable#getCurrentRowState()
+ // If testIndexMetaData.ignoreNewerMutations() is not set, default TimeRange is 0 to
+ // Long.MAX_VALUE
+ private RegionScanner getMockTimeRangeRegionScanner(final TimeRange timeRange) {
+ return new BaseRegionScanner(Mockito.mock(RegionScanner.class)) {
+ @Override
+ public boolean next(List<Cell> results) throws IOException {
+ for (Cell cell : currentRowCells) {
+ if (cell.getTimestamp() >= timeRange.getMin()
+ && cell.getTimestamp() < timeRange.getMax()) {
+ results.add(cell);
+ }
+ }
+ return false; // indicate no more results
+ }
+ };
+ }
+
+ private IndexMaintainer getTestIndexMaintainer() throws Exception {
+ Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES);
+ // disable column encoding, makes debugging easier
+ props.put(QueryServices.DEFAULT_COLUMN_ENCODED_BYTES_ATRRIB, "0");
+ Connection conn = DriverManager.getConnection(getUrl(), props);
+ try {
+ conn.setAutoCommit(true);
+ conn.createStatement().execute(TEST_TABLE_DDL);
+ conn.createStatement().execute(TEST_TABLE_INDEX_DDL);
+ PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+ PTable table = pconn.getTable(new PTableKey(pconn.getTenantId(), TEST_TABLE_STRING));
+ ImmutableBytesWritable ptr = new ImmutableBytesWritable();
+ table.getIndexMaintainers(ptr, pconn);
+ List<IndexMaintainer> indexMaintainerList =
+ IndexMaintainer.deserialize(ptr, GenericKeyValueBuilder.INSTANCE, true);
+ assertEquals(1, indexMaintainerList.size());
+ IndexMaintainer indexMaintainer = indexMaintainerList.get(0);
+ return indexMaintainer;
+ } finally {
+ conn.close();
+ }
+ }
+
+ /**
+ * Tests that updating an indexed column results in a DeleteFamily (prior index cell) and a Put
+ * (new index cell)
+ */
+ @Test
+ public void testGetMutableIndexUpdate() throws IOException {
+ setCurrentRowState(FAM, INDEXED_QUALIFIER, 1, VALUE_1);
+
+ // update ts and value
+ Put put = new Put(ROW);
+ put.addImmutable(FAM, INDEXED_QUALIFIER, 2, VALUE_2);
+ MultiMutation mutation = new MultiMutation(new ImmutableBytesPtr(ROW));
+ mutation.addAll(put);
+
+ Collection<Pair<Mutation, byte[]>> indexUpdates =
+ indexBuilder.getIndexUpdate(mutation, mockIndexMetaData);
+ assertEquals(2, indexUpdates.size());
+ assertContains(indexUpdates, 2, ROW, KeyValue.Type.DeleteFamily, FAM,
+ new byte[0] /* qual not needed */, 2);
+ assertContains(indexUpdates, ColumnTracker.NO_NEWER_PRIMARY_TABLE_ENTRY_TIMESTAMP, ROW,
+ KeyValue.Type.Put, FAM, QueryConstants.EMPTY_COLUMN_BYTES, 2);
+ }
+
+ /**
+ * Tests a partial rebuild of a row with multiple versions. 3 versions of the row in data table,
+ * and we rebuild the index starting from time t=2
+ */
+ @Test
+ public void testRebuildMultipleVersionRow() throws IOException {
+ // when doing a rebuild, we are replaying mutations so we want to ignore newer mutations
+ // see LocalTable#getCurrentRowState()
+ Mockito.when(mockIndexMetaData.ignoreNewerMutations()).thenReturn(true);
+
+ // the current row state has 3 versions, but if we rebuild as of t=2, scanner in LocalTable
+ // should only return first
+ Cell currentCell1 = CellUtil.createCell(ROW, FAM, INDEXED_QUALIFIER, 1, PUT_TYPE, VALUE_1);
+ Cell currentCell2 = CellUtil.createCell(ROW, FAM, INDEXED_QUALIFIER, 2, PUT_TYPE, VALUE_2);
+ Cell currentCell3 = CellUtil.createCell(ROW, FAM, INDEXED_QUALIFIER, 3, PUT_TYPE, VALUE_3);
+ setCurrentRowState(Arrays.asList(currentCell3, currentCell2, currentCell1));
+
+ // rebuilder replays mutations starting from t=2
+ MultiMutation mutation = new MultiMutation(new ImmutableBytesPtr(ROW));
+ Put put = new Put(ROW);
+ put.addImmutable(FAM, INDEXED_QUALIFIER, 3, VALUE_3);
+ mutation.addAll(put);
+ put = new Put(ROW);
+ put.addImmutable(FAM, INDEXED_QUALIFIER, 2, VALUE_2);
+ mutation.addAll(put);
+
+ Collection<Pair<Mutation, byte[]>> indexUpdates =
+ indexBuilder.getIndexUpdate(mutation, mockIndexMetaData);
+ assertEquals(2, indexUpdates.size());
+ assertContains(indexUpdates, 2, ROW, KeyValue.Type.DeleteFamily, FAM,
+ new byte[0] /* qual not needed */, 2);
+ assertContains(indexUpdates, ColumnTracker.NO_NEWER_PRIMARY_TABLE_ENTRY_TIMESTAMP, ROW,
+ KeyValue.Type.Put, FAM, QueryConstants.EMPTY_COLUMN_BYTES, 3);
+ }
+
+ /**
+ * Tests getting an index update for a mutation with 200 versions Before, the issue PHOENIX-3807
+ * was causing this test to take >90 seconds, so here we set a timeout of 5 seconds
+ */
+ @Test(timeout = 5000)
+ public void testManyVersions() throws IOException {
+ // when doing a rebuild, we are replaying mutations so we want to ignore newer mutations
+ // see LocalTable#getCurrentRowState()
+ Mockito.when(mockIndexMetaData.ignoreNewerMutations()).thenReturn(true);
+ MultiMutation mutation = getMultipleVersionMutation(200);
+ currentRowCells = mutation.getFamilyCellMap().get(FAM);
+
+ Collection<Pair<Mutation, byte[]>> indexUpdates =
+ indexBuilder.getIndexUpdate(mutation, mockIndexMetaData);
+ assertNotEquals(0, indexUpdates.size());
+ }
+
+ // Assert that the given collection of indexUpdates contains the given cell
+ private void assertContains(Collection<Pair<Mutation, byte[]>> indexUpdates,
+ final long mutationTs, final byte[] row, final Type cellType, final byte[] fam,
+ final byte[] qual, final long cellTs) {
+ Predicate<Pair<Mutation, byte[]>> hasCellPredicate =
+ new Predicate<Pair<Mutation, byte[]>>() {
+ @Override
+ public boolean apply(Pair<Mutation, byte[]> input) {
+ assertEquals(TEST_TABLE_INDEX_STRING, Bytes.toString(input.getSecond()));
+ Mutation mutation = input.getFirst();
+ if (mutationTs == mutation.getTimeStamp()) {
+ NavigableMap<byte[], List<Cell>> familyCellMap =
+ mutation.getFamilyCellMap();
+ Cell updateCell = familyCellMap.get(fam).get(0);
+ if (cellType == KeyValue.Type.codeToType(updateCell.getTypeByte())
+ && Bytes.compareTo(fam, CellUtil.cloneFamily(updateCell)) == 0
+ && Bytes.compareTo(qual,
+ CellUtil.cloneQualifier(updateCell)) == 0
+ && cellTs == updateCell.getTimestamp()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ Optional<Pair<Mutation, byte[]>> tryFind =
+ Iterables.tryFind(indexUpdates, hasCellPredicate);
+ assertTrue(tryFind.isPresent());
+ }
+
+ private void setCurrentRowState(byte[] fam2, byte[] indexedQualifier, int i, byte[] value1) {
+ Cell cell = CellUtil.createCell(ROW, FAM, INDEXED_QUALIFIER, 1, PUT_TYPE, VALUE_1);
+ currentRowCells = Collections.singletonList(cell);
+ }
+
+ private void setCurrentRowState(List<Cell> cells) {
+ currentRowCells = cells;
+ }
+
+ private MultiMutation getMultipleVersionMutation(int versions) {
+ MultiMutation mutation = new MultiMutation(new ImmutableBytesPtr(ROW));
+ for (int i = versions - 1; i >= 0; i--) {
+ Put put = new Put(ROW);
+ put.addImmutable(FAM, INDEXED_QUALIFIER, i, Bytes.toBytes(i));
+ mutation.addAll(put);
+ }
+ return mutation;
+ }
+}
http://git-wip-us.apache.org/repos/asf/phoenix/blob/85e344fd/phoenix-core/src/test/java/org/apache/phoenix/hbase/index/covered/data/TestLocalTable.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/hbase/index/covered/data/TestLocalTable.java b/phoenix-core/src/test/java/org/apache/phoenix/hbase/index/covered/data/TestLocalTable.java
new file mode 100644
index 0000000..b11ac8d
--- /dev/null
+++ b/phoenix-core/src/test/java/org/apache/phoenix/hbase/index/covered/data/TestLocalTable.java
@@ -0,0 +1,63 @@
+/*
+ * 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.phoenix.hbase.index.covered.data;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.KeyValue;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.Test;
+
+public class TestLocalTable {
+ private static final byte[] ROW = Bytes.toBytes("test_row");
+
+ @Test
+ public void testGetOldestTimestamp() {
+ LocalTable localTable = new LocalTable(null);
+
+ List<Cell> cellList1 = getCellList(new KeyValue(ROW, 5L), new KeyValue(ROW, 4L));
+ assertEquals(4L, localTable.getOldestTimestamp(Collections.singletonList(cellList1)));
+
+ List<Cell> cellList2 = getCellList(new KeyValue(ROW, 5L), new KeyValue(ROW, 2L));
+ List<List<Cell>> set1 = new ArrayList<>(Arrays.asList(cellList1, cellList2));
+ assertEquals(2L, localTable.getOldestTimestamp(set1));
+
+ List<Cell> cellList3 = getCellList(new KeyValue(ROW, 1L));
+ set1.add(cellList3);
+ assertEquals(1L, localTable.getOldestTimestamp(set1));
+
+ List<Cell> cellList4 =
+ getCellList(new KeyValue(ROW, 3L), new KeyValue(ROW, 1L), new KeyValue(ROW, 0L));
+ set1.add(cellList4);
+ assertEquals(0L, localTable.getOldestTimestamp(set1));
+ }
+
+ private List<Cell> getCellList(KeyValue... kvs) {
+ List<Cell> cellList = new ArrayList<>();
+ for (KeyValue kv : kvs) {
+ cellList.add(kv);
+ }
+ return cellList;
+ }
+}