You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by vj...@apache.org on 2026/04/20 04:04:10 UTC
(phoenix) branch master updated: PHOENIX-7794 Eventually Consistent Global Secondary Indexes (#2401)
This is an automated email from the ASF dual-hosted git repository.
virajjasani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/master by this push:
new 4398bc0583 PHOENIX-7794 Eventually Consistent Global Secondary Indexes (#2401)
4398bc0583 is described below
commit 4398bc0583ec5691c45cd8245e4bee29c7bb50e7
Author: Viraj Jasani <vj...@apache.org>
AuthorDate: Sun Apr 19 21:04:03 2026 -0700
PHOENIX-7794 Eventually Consistent Global Secondary Indexes (#2401)
---
Jenkinsfile | 2 +-
Jenkinsfile.yetus | 2 +-
phoenix-core-client/src/main/antlr3/PhoenixSQL.g | 15 +-
.../org/apache/phoenix/compile/JoinCompiler.java | 3 +-
.../coprocessorclient/MetaDataProtocol.java | 4 +-
.../metrics/MetricsIndexCDCConsumerSource.java | 103 ++
.../metrics/MetricsIndexCDCConsumerSourceImpl.java | 120 ++
.../index/metrics/MetricsIndexerSourceFactory.java | 8 +
.../org/apache/phoenix/index/IndexMaintainer.java | 17 +
.../phoenix/jdbc/PhoenixDatabaseMetaData.java | 12 +
.../org/apache/phoenix/jdbc/PhoenixStatement.java | 26 +-
.../apache/phoenix/parse/AlterIndexStatement.java | 15 +-
.../apache/phoenix/parse/CreateIndexStatement.java | 34 +
.../org/apache/phoenix/parse/ParseNodeFactory.java | 8 +
.../phoenix/query/ConnectionQueryServicesImpl.java | 30 +-
.../query/ConnectionlessQueryServicesImpl.java | 8 +
.../org/apache/phoenix/query/QueryConstants.java | 27 +-
.../org/apache/phoenix/schema/DelegateTable.java | 6 +
.../org/apache/phoenix/schema/MetaDataClient.java | 126 +-
.../java/org/apache/phoenix/schema/PTable.java | 13 +
.../java/org/apache/phoenix/schema/PTableImpl.java | 28 +-
.../apache/phoenix/schema/tool/SchemaSQLUtil.java | 7 +
.../phoenix/schema/transform/TransformClient.java | 7 +-
.../phoenix/schema/types/IndexConsistency.java | 109 ++
.../org/apache/phoenix/util/CDCChangeBuilder.java | 86 +-
.../main/java/org/apache/phoenix/util/CDCUtil.java | 29 +
.../java/org/apache/phoenix/util/MetaDataUtil.java | 3 +-
.../src/main/protobuf/IndexMutations.proto | 39 +
phoenix-core-client/src/main/protobuf/PTable.proto | 1 +
.../src/main/protobuf/ServerCachingService.proto | 1 +
phoenix-core-server/pom.xml | 5 +
.../coprocessor/CDCGlobalIndexRegionScanner.java | 290 ++++-
.../phoenix/coprocessor/MetaDataEndpointImpl.java | 19 +-
.../coprocessor/UncoveredIndexRegionScanner.java | 17 +-
.../phoenix/hbase/index/IndexCDCConsumer.java | 1347 ++++++++++++++++++++
.../phoenix/hbase/index/IndexRegionObserver.java | 427 +++++--
.../TrackingParallelWriterIndexCommitter.java | 7 +-
.../apache/phoenix/end2end/BasePermissionsIT.java | 4 +-
.../java/org/apache/phoenix/end2end/Bson4IT.java | 11 +
.../java/org/apache/phoenix/end2end/Bson5IT.java | 108 +-
...ncurrentMutationsCoveredEventualGenerateIT.java | 73 ++
.../ConcurrentMutationsCoveredEventualIT.java | 36 +
.../ConcurrentMutationsCoveredNonEventualIT.java | 36 +
... => ConcurrentMutationsExtendedGenerateIT.java} | 36 +-
.../end2end/ConcurrentMutationsExtendedIT.java | 114 +-
.../ConcurrentMutationsExtendedIndexIT.java | 438 +++++++
.../ConcurrentMutationsLazyPostBatchWriteIT.java | 18 +-
...urrentMutationsUncoveredEventualGenerateIT.java | 73 ++
.../ConcurrentMutationsUncoveredEventualIT.java | 36 +
.../ConcurrentMutationsUncoveredNonEventualIT.java | 36 +
.../end2end/IndexMaintainerConsistencyIT.java | 237 ++++
...xToolForNonTxGlobalIndexEventualGenerateIT.java | 93 ++
.../IndexToolForNonTxGlobalIndexEventualIT.java | 93 ++
.../end2end/IndexToolForNonTxGlobalIndexIT.java | 29 +-
.../org/apache/phoenix/end2end/IndexToolIT.java | 33 +-
.../end2end/MetadataServerConnectionsIT.java | 2 +
.../MigrateSystemTablesToSystemNamespaceIT.java | 16 +-
...ava => MultiTenantEventualIndexGenerateIT.java} | 33 +-
.../end2end/MultiTenantEventualIndexIT.java | 626 +++++++++
.../ParallelStatsDisabledWithRegionMovesIT.java | 2 +
.../phoenix/end2end/QueryDatabaseMetaDataIT.java | 7 +-
.../end2end/SkipSystemTablesExistenceCheckIT.java | 2 +-
.../SystemTablesCreationOnConnectionIT.java | 4 +-
.../phoenix/end2end/TenantSpecificTablesDDLIT.java | 3 +
.../phoenix/end2end/UCFWithServerMetadataIT.java | 2 +
.../org/apache/phoenix/end2end/UpsertSelectIT.java | 2 +
.../end2end/UpsertSelectWithRegionMovesIT.java | 2 +
.../phoenix/end2end/VarBinaryEncoded2IT.java | 97 +-
.../GlobalIndexCheckerEventualGenerateIT.java | 89 ++
.../index/GlobalIndexCheckerEventualIT.java | 89 ++
.../end2end/index/GlobalIndexCheckerIT.java | 49 +-
.../index/GlobalIndexCheckerWithRegionMovesIT.java | 2 +
.../end2end/index/IndexAsyncThresholdIT.java | 2 +
.../end2end/index/PartialSystemCatalogIndexIT.java | 4 +-
.../index/ReplicationWithWALAnnotationIT.java | 3 +
.../UncoveredGlobalIndexRegionScanner2IT.java | 2 +
.../phoenix/jdbc/FailoverPhoenixConnection2IT.java | 6 +
.../phoenix/jdbc/FailoverPhoenixConnectionIT.java | 5 +
.../HAConnectionWithMasterAndRPCRegistryIT.java | 5 +
.../jdbc/HighAvailabilityTestingUtilityIT.java | 5 +
.../jdbc/ParallelPhoenixConnectionFallbackIT.java | 5 +
.../ParallelPhoenixNullComparingResultSetIT.java | 5 +
.../phoenix/monitoring/BasePhoenixMetricsIT.java | 2 +
.../monitoring/PhoenixTableLevelMetricsIT.java | 3 +
.../ConnectionQueryServicesMetricsIT.java | 4 +
.../phoenix/query/MaxConcurrentConnectionsIT.java | 2 +
.../apache/phoenix/query/MetaDataCachingIT.java | 2 +
.../phoenix/parse/IndexConsistencyParseTest.java | 87 ++
.../java/org/apache/phoenix/query/BaseTest.java | 2 +-
.../org/apache/phoenix/util/IndexScrutiny.java | 18 +-
.../java/org/apache/phoenix/util/TestUtil.java | 94 ++
pom.xml | 6 +
92 files changed, 5476 insertions(+), 318 deletions(-)
diff --git a/Jenkinsfile b/Jenkinsfile
index 46a0628388..49a44d3766 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -66,7 +66,7 @@ pipeline {
stage('BuildAndTest') {
options {
- timeout(time: 5, unit: 'HOURS')
+ timeout(time: 9, unit: 'HOURS')
}
steps {
dir("HBASE_${HBASE_PROFILE}") {
diff --git a/Jenkinsfile.yetus b/Jenkinsfile.yetus
index a0e9c43f65..7e99f45b05 100644
--- a/Jenkinsfile.yetus
+++ b/Jenkinsfile.yetus
@@ -37,7 +37,7 @@ pipeline {
}
options {
- timeout(time: 5, unit: 'HOURS')
+ timeout(time: 9, unit: 'HOURS')
}
steps {
diff --git a/phoenix-core-client/src/main/antlr3/PhoenixSQL.g b/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
index 945d981f26..bb7d3d8a89 100644
--- a/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core-client/src/main/antlr3/PhoenixSQL.g
@@ -85,6 +85,8 @@ tokens
PRE='pre';
POST='post';
CHANGE='change';
+ IDX_MUTATIONS='idx_mutations';
+ DATA_ROW_STATE='data_row_state';
LATEST='latest';
ALL='all';
INDEX='index';
@@ -164,6 +166,9 @@ tokens
TRUNCATE = 'truncate';
PRESERVE='preserve';
SPLITS='splits';
+ CONSISTENCY = 'consistency';
+ EVENTUAL = 'eventual';
+ STRONG = 'strong';
}
@@ -212,6 +217,7 @@ import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.IllegalDataException;
import org.apache.phoenix.schema.PIndexState;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.PTable.IndexType;
import org.apache.phoenix.schema.PTable.CDCChangeScope;
@@ -603,7 +609,7 @@ cdc_change_scopes returns [Set<CDCChangeScope> ret]
;
cdc_change_scope returns [CDCChangeScope ret]
- : v=(PRE | POST | CHANGE)
+ : v=(PRE | POST | CHANGE | IDX_MUTATIONS | DATA_ROW_STATE)
{
ret = CDCChangeScope.valueOf(v.getText().toUpperCase());
}
@@ -717,8 +723,8 @@ drop_cdc_node returns [DropCDCStatement ret]
// Parse a alter index statement
alter_index_node returns [AlterIndexStatement ret]
: ALTER INDEX (IF ex=EXISTS)? i=index_name ON t=from_table_name
- ((s=(USABLE | UNUSABLE | REBUILD (isRebuildAll=ALL)? | DISABLE | ACTIVE)) (async=ASYNC)? ((SET?)p=fam_properties)?)
- {ret = factory.alterIndex(factory.namedTable(null, TableName.create(t.getSchemaName(), i.getName())), t.getTableName(), ex!=null, PIndexState.valueOf(SchemaUtil.normalizeIdentifier(s.getText())), isRebuildAll!=null, async!=null, p); }
+ ((s=(USABLE | UNUSABLE | REBUILD (isRebuildAll=ALL)? | DISABLE | ACTIVE)) (async=ASYNC)? ((SET?)p=fam_properties)? | (CONSISTENCY EQ c=(STRONG | EVENTUAL)))
+ {ret = factory.alterIndex(factory.namedTable(null, TableName.create(t.getSchemaName(), i.getName())), t.getTableName(), ex!=null, s!=null ? PIndexState.valueOf(SchemaUtil.normalizeIdentifier(s.getText())) : null, isRebuildAll!=null, async!=null, p, c!=null ? IndexConsistency.valueOf(SchemaUtil.normalizeIdentifier(c.getText())) : null); }
;
// Parse a trace statement.
@@ -1336,6 +1342,9 @@ identifier returns [String ret]
parseNoReserved returns [String ret]
: n=NAME { $ret = n.getText(); }
+ | CONSISTENCY { $ret = "CONSISTENCY"; }
+ | EVENTUAL { $ret = "EVENTUAL"; }
+ | STRONG { $ret = "STRONG"; }
;
case_statement returns [ParseNode ret]
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/JoinCompiler.java b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
index ad31baeaf8..8a091fbdc0 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
@@ -1544,7 +1544,8 @@ public class JoinCompiler {
.setDisableWAL(PTable.DEFAULT_DISABLE_WAL).setMultiTenant(left.isMultiTenant())
.setStoreNulls(left.getStoreNulls()).setViewType(left.getViewType())
.setViewIndexIdType(left.getviewIndexIdType()).setViewIndexId(left.getViewIndexId())
- .setIndexType(left.getIndexType()).setTransactionProvider(left.getTransactionProvider())
+ .setIndexType(left.getIndexType()).setIndexConsistency(left.getIndexConsistency())
+ .setTransactionProvider(left.getTransactionProvider())
.setUpdateCacheFrequency(left.getUpdateCacheFrequency())
.setNamespaceMapped(left.isNamespaceMapped())
.setAutoPartitionSeqName(left.getAutoPartitionSeqName())
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/coprocessorclient/MetaDataProtocol.java b/phoenix-core-client/src/main/java/org/apache/phoenix/coprocessorclient/MetaDataProtocol.java
index b523a44de0..08cac7cbe9 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/coprocessorclient/MetaDataProtocol.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/coprocessorclient/MetaDataProtocol.java
@@ -91,8 +91,8 @@ public abstract class MetaDataProtocol extends MetaDataService {
public static final long MIN_SYSTEM_TABLE_TIMESTAMP_4_16_0 = MIN_TABLE_TIMESTAMP + 33;
public static final long MIN_SYSTEM_TABLE_TIMESTAMP_5_1_0 = MIN_SYSTEM_TABLE_TIMESTAMP_4_16_0;
public static final long MIN_SYSTEM_TABLE_TIMESTAMP_5_2_0 = MIN_TABLE_TIMESTAMP + 38;
- public static final long MIN_SYSTEM_TABLE_TIMESTAMP_5_3_0 = MIN_TABLE_TIMESTAMP + 42;
- public static final long MIN_SYSTEM_TABLE_TIMESTAMP_5_4_0 = MIN_TABLE_TIMESTAMP + 43;
+ public static final long MIN_SYSTEM_TABLE_TIMESTAMP_5_3_0 = MIN_TABLE_TIMESTAMP + 44;
+ public static final long MIN_SYSTEM_TABLE_TIMESTAMP_5_4_0 = MIN_TABLE_TIMESTAMP + 45;
// MIN_SYSTEM_TABLE_TIMESTAMP needs to be set to the max of all the MIN_SYSTEM_TABLE_TIMESTAMP_*
// constants
public static final long MIN_SYSTEM_TABLE_TIMESTAMP = MIN_SYSTEM_TABLE_TIMESTAMP_5_4_0;
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexCDCConsumerSource.java b/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexCDCConsumerSource.java
new file mode 100644
index 0000000000..c278c1fa4e
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexCDCConsumerSource.java
@@ -0,0 +1,103 @@
+/*
+ * 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.metrics;
+
+import org.apache.hadoop.hbase.metrics.BaseSource;
+
+/**
+ * IndexCDCConsumer metrics for eventually consistent index updates.
+ */
+public interface MetricsIndexCDCConsumerSource extends BaseSource {
+
+ String METRICS_NAME = "IndexCDCConsumer";
+ String METRICS_CONTEXT = "phoenix";
+ String METRICS_DESCRIPTION = "Metrics about the Phoenix Index CDC Consumer";
+ String METRICS_JMX_CONTEXT = "RegionServer,sub=" + METRICS_NAME;
+
+ String CDC_BATCH_PROCESS_TIME = "cdcBatchProcessTime";
+ String CDC_BATCH_PROCESS_TIME_DESC =
+ "Histogram for the end-to-end time in milliseconds for processing one CDC batch";
+
+ String CDC_MUTATION_GENERATE_TIME = "cdcMutationGenerateTime";
+ String CDC_MUTATION_GENERATE_TIME_DESC =
+ "Histogram for the time in milliseconds to generate index mutations from data row states";
+
+ String CDC_MUTATION_APPLY_TIME = "cdcMutationApplyTime";
+ String CDC_MUTATION_APPLY_TIME_DESC =
+ "Histogram for the time in milliseconds to apply index mutations to index tables";
+
+ String CDC_BATCH_COUNT = "cdcBatchCount";
+ String CDC_BATCH_COUNT_DESC = "The number of CDC batches processed";
+
+ String CDC_MUTATION_COUNT = "cdcMutationCount";
+ String CDC_MUTATION_COUNT_DESC = "The number of individual index mutations applied through CDC";
+
+ String CDC_BATCH_FAILURE_COUNT = "cdcBatchFailureCount";
+ String CDC_BATCH_FAILURE_COUNT_DESC = "The number of CDC batch processing failures";
+
+ String CDC_INDEX_UPDATE_LAG = "cdcIndexUpdateLag";
+ String CDC_INDEX_UPDATE_LAG_DESC =
+ "Histogram for the lag in milliseconds between current time and the last processed CDC event";
+
+ /**
+ * Updates the CDC batch processing time histogram.
+ * @param dataTableName physical data table name
+ * @param t time taken in milliseconds
+ */
+ void updateCdcBatchProcessTime(String dataTableName, long t);
+
+ /**
+ * Updates the CDC mutation generation time histogram.
+ * @param dataTableName physical data table name
+ * @param t time taken in milliseconds
+ */
+ void updateCdcMutationGenerateTime(String dataTableName, long t);
+
+ /**
+ * Updates the CDC mutation apply time histogram.
+ * @param dataTableName physical data table name
+ * @param t time taken in milliseconds
+ */
+ void updateCdcMutationApplyTime(String dataTableName, long t);
+
+ /**
+ * Increments the CDC batch count.
+ * @param dataTableName physical data table name
+ */
+ void incrementCdcBatchCount(String dataTableName);
+
+ /**
+ * Increments the CDC mutation count by the given amount.
+ * @param dataTableName physical data table name
+ * @param count number of mutations applied
+ */
+ void incrementCdcMutationCount(String dataTableName, long count);
+
+ /**
+ * Increments the CDC batch failure count.
+ * @param dataTableName physical data table name
+ */
+ void incrementCdcBatchFailureCount(String dataTableName);
+
+ /**
+ * Updates the CDC lag histogram.
+ * @param dataTableName physical data table name
+ * @param lag lag in milliseconds between current time and last processed event
+ */
+ void updateCdcLag(String dataTableName, long lag);
+}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexCDCConsumerSourceImpl.java b/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexCDCConsumerSourceImpl.java
new file mode 100644
index 0000000000..71c6382633
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexCDCConsumerSourceImpl.java
@@ -0,0 +1,120 @@
+/*
+ * 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.metrics;
+
+import org.apache.hadoop.hbase.metrics.BaseSourceImpl;
+import org.apache.hadoop.metrics2.MetricHistogram;
+import org.apache.hadoop.metrics2.lib.MutableFastCounter;
+
+/**
+ * Implementation for tracking IndexCDCConsumer metrics.
+ */
+public class MetricsIndexCDCConsumerSourceImpl extends BaseSourceImpl
+ implements MetricsIndexCDCConsumerSource {
+
+ private final MetricHistogram cdcBatchProcessTimeHisto;
+ private final MetricHistogram cdcMutationGenerateTimeHisto;
+ private final MetricHistogram cdcMutationApplyTimeHisto;
+ private final MutableFastCounter cdcBatchCounter;
+ private final MutableFastCounter cdcMutationCounter;
+ private final MutableFastCounter cdcBatchFailureCounter;
+ private final MetricHistogram cdcIndexUpdateLagHisto;
+
+ public MetricsIndexCDCConsumerSourceImpl() {
+ this(METRICS_NAME, METRICS_DESCRIPTION, METRICS_CONTEXT, METRICS_JMX_CONTEXT);
+ }
+
+ public MetricsIndexCDCConsumerSourceImpl(String metricsName, String metricsDescription,
+ String metricsContext, String metricsJmxContext) {
+ super(metricsName, metricsDescription, metricsContext, metricsJmxContext);
+
+ cdcBatchProcessTimeHisto =
+ getMetricsRegistry().newHistogram(CDC_BATCH_PROCESS_TIME, CDC_BATCH_PROCESS_TIME_DESC);
+ cdcMutationGenerateTimeHisto = getMetricsRegistry().newHistogram(CDC_MUTATION_GENERATE_TIME,
+ CDC_MUTATION_GENERATE_TIME_DESC);
+ cdcMutationApplyTimeHisto =
+ getMetricsRegistry().newHistogram(CDC_MUTATION_APPLY_TIME, CDC_MUTATION_APPLY_TIME_DESC);
+ cdcBatchCounter = getMetricsRegistry().newCounter(CDC_BATCH_COUNT, CDC_BATCH_COUNT_DESC, 0L);
+ cdcMutationCounter =
+ getMetricsRegistry().newCounter(CDC_MUTATION_COUNT, CDC_MUTATION_COUNT_DESC, 0L);
+ cdcBatchFailureCounter =
+ getMetricsRegistry().newCounter(CDC_BATCH_FAILURE_COUNT, CDC_BATCH_FAILURE_COUNT_DESC, 0L);
+ cdcIndexUpdateLagHisto =
+ getMetricsRegistry().newHistogram(CDC_INDEX_UPDATE_LAG, CDC_INDEX_UPDATE_LAG_DESC);
+ }
+
+ @Override
+ public void updateCdcBatchProcessTime(String dataTableName, long t) {
+ incrementTableSpecificHistogram(CDC_BATCH_PROCESS_TIME, dataTableName, t);
+ cdcBatchProcessTimeHisto.add(t);
+ }
+
+ @Override
+ public void updateCdcMutationGenerateTime(String dataTableName, long t) {
+ incrementTableSpecificHistogram(CDC_MUTATION_GENERATE_TIME, dataTableName, t);
+ cdcMutationGenerateTimeHisto.add(t);
+ }
+
+ @Override
+ public void updateCdcMutationApplyTime(String dataTableName, long t) {
+ incrementTableSpecificHistogram(CDC_MUTATION_APPLY_TIME, dataTableName, t);
+ cdcMutationApplyTimeHisto.add(t);
+ }
+
+ @Override
+ public void incrementCdcBatchCount(String dataTableName) {
+ incrementTableSpecificCounter(CDC_BATCH_COUNT, dataTableName);
+ cdcBatchCounter.incr();
+ }
+
+ @Override
+ public void incrementCdcMutationCount(String dataTableName, long count) {
+ MutableFastCounter tableCounter =
+ getMetricsRegistry().getCounter(getMetricName(CDC_MUTATION_COUNT, dataTableName), 0);
+ tableCounter.incr(count);
+ cdcMutationCounter.incr(count);
+ }
+
+ @Override
+ public void incrementCdcBatchFailureCount(String dataTableName) {
+ incrementTableSpecificCounter(CDC_BATCH_FAILURE_COUNT, dataTableName);
+ cdcBatchFailureCounter.incr();
+ }
+
+ @Override
+ public void updateCdcLag(String dataTableName, long lag) {
+ incrementTableSpecificHistogram(CDC_INDEX_UPDATE_LAG, dataTableName, lag);
+ cdcIndexUpdateLagHisto.add(lag);
+ }
+
+ private void incrementTableSpecificCounter(String baseName, String tableName) {
+ MutableFastCounter tableCounter =
+ getMetricsRegistry().getCounter(getMetricName(baseName, tableName), 0);
+ tableCounter.incr();
+ }
+
+ private void incrementTableSpecificHistogram(String baseName, String tableName, long t) {
+ MetricHistogram tableHistogram =
+ getMetricsRegistry().getHistogram(getMetricName(baseName, tableName));
+ tableHistogram.add(t);
+ }
+
+ private String getMetricName(String baseName, String tableName) {
+ return baseName + "." + tableName;
+ }
+}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexerSourceFactory.java b/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexerSourceFactory.java
index e579b2f836..55b648c53a 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexerSourceFactory.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/hbase/index/metrics/MetricsIndexerSourceFactory.java
@@ -24,6 +24,7 @@ public class MetricsIndexerSourceFactory {
private static final MetricsIndexerSourceFactory INSTANCE = new MetricsIndexerSourceFactory();
private volatile MetricsIndexerSource indexerSource;
private GlobalIndexCheckerSource globalIndexCheckerSource;
+ private MetricsIndexCDCConsumerSource indexCDCConsumerSource;
private MetricsIndexerSourceFactory() {
}
@@ -45,4 +46,11 @@ public class MetricsIndexerSourceFactory {
}
return INSTANCE.globalIndexCheckerSource;
}
+
+ public synchronized MetricsIndexCDCConsumerSource getIndexCDCConsumerSource() {
+ if (INSTANCE.indexCDCConsumerSource == null) {
+ INSTANCE.indexCDCConsumerSource = new MetricsIndexCDCConsumerSourceImpl();
+ }
+ return INSTANCE.indexCDCConsumerSource;
+ }
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMaintainer.java b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
index e2b33a17d7..70b83d1795 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
@@ -108,6 +108,7 @@ import org.apache.phoenix.schema.transform.TransformMaintainer;
import org.apache.phoenix.schema.tuple.BaseTuple;
import org.apache.phoenix.schema.tuple.MultiKeyValueTuple;
import org.apache.phoenix.schema.tuple.ValueGetterTuple;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.types.PBoolean;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PVarbinaryEncoded;
@@ -477,10 +478,12 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
private Expression indexWhere;
private Set<ColumnReference> indexWhereColumns;
private boolean isCDCIndex;
+ private IndexConsistency indexConsistency;
protected IndexMaintainer(RowKeySchema dataRowKeySchema, boolean isDataTableSalted) {
this.dataRowKeySchema = dataRowKeySchema;
this.isDataTableSalted = isDataTableSalted;
+ this.indexConsistency = null;
}
private IndexMaintainer(final PTable dataTable, final PTable index, PhoenixConnection connection)
@@ -501,6 +504,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
this.isUncovered = index.getIndexType() == IndexType.UNCOVERED_GLOBAL;
this.encodingScheme = index.getEncodingScheme();
this.isCDCIndex = CDCUtil.isCDCIndex(index);
+ this.indexConsistency = index.getIndexConsistency();
// null check for b/w compatibility
this.encodingScheme = index.getEncodingScheme() == null
@@ -1933,6 +1937,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
this.encodingScheme = QualifierEncodingScheme.NON_ENCODED_QUALIFIERS;
this.dataImmutableStorageScheme = ImmutableStorageScheme.ONE_CELL_PER_COLUMN;
this.dataEncodingScheme = QualifierEncodingScheme.NON_ENCODED_QUALIFIERS;
+ this.indexConsistency = null;
initCachedState();
}
@@ -2068,6 +2073,11 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
} else {
maintainer.isCDCIndex = false;
}
+ if (proto.hasIndexConsistency()) {
+ maintainer.indexConsistency = IndexConsistency.valueOf(proto.getIndexConsistency());
+ } else {
+ maintainer.indexConsistency = null;
+ }
maintainer.nDataTableSaltBuckets =
proto.hasDataTableSaltBuckets() ? proto.getDataTableSaltBuckets() : -1;
maintainer.initCachedState();
@@ -2223,6 +2233,9 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
}
}
builder.setIsCDCIndex(maintainer.isCDCIndex);
+ if (maintainer.indexConsistency != null) {
+ builder.setIndexConsistency(maintainer.indexConsistency.name());
+ }
if (maintainer.isDataTableSalted) {
builder.setDataTableSaltBuckets(maintainer.nDataTableSaltBuckets);
}
@@ -2609,6 +2622,10 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
return indexedColumns;
}
+ public IndexConsistency getIndexConsistency() {
+ return indexConsistency;
+ }
+
public static class UDFParseNodeVisitor extends StatelessTraverseAllParseNodeVisitor {
private Map<String, UDFParseNode> udfParseNodes;
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
index 3bc68c5ddb..ed63485440 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixDatabaseMetaData.java
@@ -213,6 +213,8 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
public static final byte[] VIEW_TYPE_BYTES = Bytes.toBytes(VIEW_TYPE);
public static final String INDEX_TYPE = "INDEX_TYPE";
public static final byte[] INDEX_TYPE_BYTES = Bytes.toBytes(INDEX_TYPE);
+ public static final String INDEX_CONSISTENCY = "INDEX_CONSISTENCY";
+ public static final byte[] INDEX_CONSISTENCY_BYTES = Bytes.toBytes(INDEX_CONSISTENCY);
public static final String LINK_TYPE = "LINK_TYPE";
public static final byte[] LINK_TYPE_BYTES = Bytes.toBytes(LINK_TYPE);
public static final String TASK_TYPE = "TASK_TYPE";
@@ -498,6 +500,16 @@ public class PhoenixDatabaseMetaData implements DatabaseMetaData {
"PARTITION_END_TIME IS NOT NULL "
+ "AND TO_NUMBER(CURRENT_TIME()) - TO_NUMBER(PHOENIX_ROW_TIMESTAMP()) >= %d";
+ public static final String SYSTEM_IDX_CDC_TRACKER_TABLE = "IDX_CDC_TRACKER";
+ public static final String SYSTEM_IDX_CDC_TRACKER_NAME =
+ SchemaUtil.getTableName(SYSTEM_CATALOG_SCHEMA, SYSTEM_IDX_CDC_TRACKER_TABLE);
+ public static final String LAST_TIMESTAMP = "LAST_TIMESTAMP";
+ public static final String OWNER_PARTITION_ID = "OWNER_PARTITION_ID";
+ public static final String TRACKER_STATUS = "STATUS";
+ public static final String TRACKER_STATUS_IN_PROGRESS = "i";
+ public static final String TRACKER_STATUS_COMPLETE = "c";
+ public static final int IDX_CDC_TRACKER_TTL = 10 * 24 * 60 * 60;
+
public static final String QUERY_ID = "QUERY_ID";
public static final String USER = "USER";
public static final String CLIENT_IP = "CLIENT_IP";
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index bd0dc32565..20edcbe34d 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -221,6 +221,7 @@ import org.apache.phoenix.schema.stats.StatisticsCollectionScope;
import org.apache.phoenix.schema.tuple.MultiKeyValueTuple;
import org.apache.phoenix.schema.tuple.ResultTuple;
import org.apache.phoenix.schema.tuple.Tuple;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PLong;
import org.apache.phoenix.schema.types.PVarchar;
@@ -1651,9 +1652,10 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
public ExecutableCreateIndexStatement(NamedNode indexName, NamedTableNode dataTable,
IndexKeyConstraint ikConstraint, List<ColumnName> includeColumns, List<ParseNode> splits,
ListMultimap<String, Pair<String, Object>> props, boolean ifNotExists, IndexType indexType,
- boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes, ParseNode where) {
+ boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes, ParseNode where,
+ IndexConsistency indexConsistency) {
super(indexName, dataTable, ikConstraint, includeColumns, splits, props, ifNotExists,
- indexType, async, bindCount, udfParseNodes, where);
+ indexType, async, bindCount, udfParseNodes, where, indexConsistency);
}
@SuppressWarnings("unchecked")
@@ -1885,12 +1887,19 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
private static class ExecutableAlterIndexStatement extends AlterIndexStatement
implements CompilableStatement {
- public ExecutableAlterIndexStatement(NamedTableNode indexTableNode, String dataTableName,
+ ExecutableAlterIndexStatement(NamedTableNode indexTableNode, String dataTableName,
boolean ifExists, PIndexState state, boolean isRebuildAll, boolean async,
ListMultimap<String, Pair<String, Object>> props) {
super(indexTableNode, dataTableName, ifExists, state, isRebuildAll, async, props);
}
+ ExecutableAlterIndexStatement(NamedTableNode indexTableNode, String dataTableName,
+ boolean ifExists, PIndexState state, boolean isRebuildAll, boolean async,
+ ListMultimap<String, Pair<String, Object>> props, IndexConsistency indexConsistency) {
+ super(indexTableNode, dataTableName, ifExists, state, isRebuildAll, async, props,
+ indexConsistency);
+ }
+
@SuppressWarnings("unchecked")
@Override
public MutationPlan compilePlan(final PhoenixStatement stmt, Sequence.ValueOp seqAction)
@@ -2282,7 +2291,8 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
ListMultimap<String, Pair<String, Object>> props, boolean ifNotExists, IndexType indexType,
boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes, ParseNode where) {
return new ExecutableCreateIndexStatement(indexName, dataTable, ikConstraint, includeColumns,
- splits, props, ifNotExists, indexType, async, bindCount, udfParseNodes, where);
+ splits, props, ifNotExists, indexType, async, bindCount, udfParseNodes, where,
+ CreateIndexStatement.getIndexConsistency(props));
}
@Override
@@ -2339,6 +2349,14 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable
isRebuildAll, async, props);
}
+ @Override
+ public AlterIndexStatement alterIndex(NamedTableNode indexTableNode, String dataTableName,
+ boolean ifExists, PIndexState state, boolean isRebuildAll, boolean async,
+ ListMultimap<String, Pair<String, Object>> props, IndexConsistency indexConsistency) {
+ return new ExecutableAlterIndexStatement(indexTableNode, dataTableName, ifExists, state,
+ isRebuildAll, async, props, indexConsistency);
+ }
+
@Override
public TraceStatement trace(boolean isTraceOn, double samplingRate) {
return new ExecutableTraceStatement(isTraceOn, samplingRate);
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/AlterIndexStatement.java b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/AlterIndexStatement.java
index 7ef5a61828..2b12e32ae6 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/AlterIndexStatement.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/AlterIndexStatement.java
@@ -20,6 +20,7 @@ package org.apache.phoenix.parse;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.schema.PIndexState;
import org.apache.phoenix.schema.PTableType;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.thirdparty.com.google.common.collect.ImmutableListMultimap;
import org.apache.phoenix.thirdparty.com.google.common.collect.ListMultimap;
@@ -31,16 +32,23 @@ public class AlterIndexStatement extends SingleTableStatement {
private boolean async;
private boolean isRebuildAll;
private ListMultimap<String, Pair<String, Object>> props;
+ private final IndexConsistency indexConsistency;
private static final PTableType tableType = PTableType.INDEX;
public AlterIndexStatement(NamedTableNode indexTableNode, String dataTableName, boolean ifExists,
PIndexState indexState, boolean isRebuildAll, boolean async) {
- this(indexTableNode, dataTableName, ifExists, indexState, isRebuildAll, async, null);
+ this(indexTableNode, dataTableName, ifExists, indexState, isRebuildAll, async, null, null);
}
public AlterIndexStatement(NamedTableNode indexTableNode, String dataTableName, boolean ifExists,
PIndexState indexState, boolean isRebuildAll, boolean async,
ListMultimap<String, Pair<String, Object>> props) {
+ this(indexTableNode, dataTableName, ifExists, indexState, isRebuildAll, async, props, null);
+ }
+
+ public AlterIndexStatement(NamedTableNode indexTableNode, String dataTableName, boolean ifExists,
+ PIndexState indexState, boolean isRebuildAll, boolean async,
+ ListMultimap<String, Pair<String, Object>> props, IndexConsistency indexConsistency) {
super(indexTableNode, 0);
this.dataTableName = dataTableName;
this.ifExists = ifExists;
@@ -48,6 +56,7 @@ public class AlterIndexStatement extends SingleTableStatement {
this.async = async;
this.isRebuildAll = isRebuildAll;
this.props = props == null ? ImmutableListMultimap.<String, Pair<String, Object>> of() : props;
+ this.indexConsistency = indexConsistency;
}
public String getTableName() {
@@ -82,4 +91,8 @@ public class AlterIndexStatement extends SingleTableStatement {
public PTableType getTableType() {
return tableType;
}
+
+ public IndexConsistency getIndexConsistency() {
+ return indexConsistency;
+ }
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java
index ec71fb1024..8cc7685a31 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/CreateIndexStatement.java
@@ -21,7 +21,9 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.PTable.IndexType;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.thirdparty.com.google.common.collect.ArrayListMultimap;
import org.apache.phoenix.thirdparty.com.google.common.collect.ListMultimap;
@@ -37,11 +39,21 @@ public class CreateIndexStatement extends SingleTableStatement {
private final boolean async;
private final Map<String, UDFParseNode> udfParseNodes;
private final ParseNode where;
+ private final IndexConsistency indexConsistency;
public CreateIndexStatement(NamedNode indexTableName, NamedTableNode dataTable,
IndexKeyConstraint indexKeyConstraint, List<ColumnName> includeColumns, List<ParseNode> splits,
ListMultimap<String, Pair<String, Object>> props, boolean ifNotExists, IndexType indexType,
boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes, ParseNode where) {
+ this(indexTableName, dataTable, indexKeyConstraint, includeColumns, splits, props, ifNotExists,
+ indexType, async, bindCount, udfParseNodes, where, getIndexConsistency(props));
+ }
+
+ public CreateIndexStatement(NamedNode indexTableName, NamedTableNode dataTable,
+ IndexKeyConstraint indexKeyConstraint, List<ColumnName> includeColumns, List<ParseNode> splits,
+ ListMultimap<String, Pair<String, Object>> props, boolean ifNotExists, IndexType indexType,
+ boolean async, int bindCount, Map<String, UDFParseNode> udfParseNodes, ParseNode where,
+ IndexConsistency indexConsistency) {
super(dataTable, bindCount);
this.indexTableName =
TableName.create(dataTable.getName().getSchemaName(), indexTableName.getName());
@@ -56,6 +68,7 @@ public class CreateIndexStatement extends SingleTableStatement {
this.async = async;
this.udfParseNodes = udfParseNodes;
this.where = where;
+ this.indexConsistency = indexConsistency;
}
public CreateIndexStatement(CreateIndexStatement createStmt,
@@ -71,6 +84,23 @@ public class CreateIndexStatement extends SingleTableStatement {
this.async = createStmt.isAsync();
this.udfParseNodes = createStmt.getUdfParseNodes();
this.where = createStmt.where;
+ this.indexConsistency = createStmt.getIndexConsistency();
+ }
+
+ public static IndexConsistency
+ getIndexConsistency(ListMultimap<String, Pair<String, Object>> props) {
+ IndexConsistency indexConsistency = null;
+ if (props != null) {
+ for (Pair<String, Object> prop : props.get(QueryConstants.ALL_FAMILY_PROPERTIES_KEY)) {
+ if (prop != null && "CONSISTENCY".equalsIgnoreCase(prop.getFirst())) {
+ Object value = prop.getSecond();
+ indexConsistency =
+ value == null ? null : IndexConsistency.valueOf(value.toString().toUpperCase());
+ break;
+ }
+ }
+ }
+ return indexConsistency;
}
public IndexKeyConstraint getIndexConstraint() {
@@ -112,4 +142,8 @@ public class CreateIndexStatement extends SingleTableStatement {
public ParseNode getWhere() {
return where;
}
+
+ public IndexConsistency getIndexConsistency() {
+ return indexConsistency;
+ }
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
index f3199a01ee..a5070c7923 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
@@ -49,6 +49,7 @@ import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.TypeMismatchException;
import org.apache.phoenix.schema.stats.StatisticsCollectionScope;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.types.PBinary;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PLong;
@@ -484,6 +485,13 @@ public class ParseNodeFactory {
async, props);
}
+ public AlterIndexStatement alterIndex(NamedTableNode indexTableNode, String dataTableName,
+ boolean ifExists, PIndexState state, boolean isRebuildAll, boolean async,
+ ListMultimap<String, Pair<String, Object>> props, IndexConsistency indexConsistency) {
+ return new AlterIndexStatement(indexTableNode, dataTableName, ifExists, state, isRebuildAll,
+ async, props, indexConsistency);
+ }
+
public AlterIndexStatement alterIndex(NamedTableNode indexTableNode, String dataTableName,
boolean ifExists, PIndexState state) {
return new AlterIndexStatement(indexTableNode, dataTableName, ifExists, state, false, false);
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
index 926089201e..5cd95b648e 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
@@ -60,6 +60,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_STATUS_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_FUNCTION_HBASE_TABLE_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_FUNCTION_NAME_BYTES;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_IDX_CDC_TRACKER_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_MUTEX_COLUMN_NAME_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_MUTEX_FAMILY_NAME_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_MUTEX_NAME;
@@ -4125,6 +4126,10 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices
return ddl + ",TTL='" + ttlExpression + "'";
}
+ protected String getIdxCdcTrackerDDL() {
+ return setSystemDDLProperties(QueryConstants.CREATE_IDX_CDC_TRACKER_METADATA);
+ }
+
private String setSystemDDLProperties(String ddl) {
return String.format(ddl,
props.getInt(DEFAULT_SYSTEM_MAX_VERSIONS_ATTRIB,
@@ -4458,6 +4463,10 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices
metaConnection.createStatement().executeUpdate(getCDCStreamDDL());
} catch (TableAlreadyExistsException ignore) {
}
+ try {
+ metaConnection.createStatement().executeUpdate(getIdxCdcTrackerDDL());
+ } catch (TableAlreadyExistsException ignore) {
+ }
try {
// check if we have old PHOENIX_INDEX_TOOL tables
// move data to the new tables under System, or simply create the new tables
@@ -4816,8 +4825,8 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices
metaConnection = addColumnsIfNotExists(metaConnection, PhoenixDatabaseMetaData.SYSTEM_CATALOG,
MIN_SYSTEM_TABLE_TIMESTAMP_5_4_0 - 1,
PhoenixDatabaseMetaData.IS_STRICT_TTL + " " + PBoolean.INSTANCE.getSqlTypeName());
- // Add any 5.4.0 specific upgrade logic here if needed in the future
- // Currently no new schema changes required for 5.4.0
+ metaConnection = addColumnsIfNotExists(metaConnection, PhoenixDatabaseMetaData.SYSTEM_CATALOG,
+ MIN_SYSTEM_TABLE_TIMESTAMP_5_4_0, PhoenixDatabaseMetaData.INDEX_CONSISTENCY + " CHAR(1)");
// move TTL values stored in descriptor to SYSCAT TTL column.
moveTTLFromHBaseLevelTTLToPhoenixLevelTTL(metaConnection);
@@ -5044,6 +5053,7 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices
metaConnection = upgradeSystemMutex(metaConnection);
metaConnection = upgradeSystemCDCStreamStatus(metaConnection);
metaConnection = upgradeSystemCDCStream(metaConnection);
+ metaConnection = upgradeSystemIdxCdcTracker(metaConnection);
// As this is where the most time will be spent during an upgrade,
// especially when there are large number of views.
@@ -5366,6 +5376,15 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices
return metaConnection;
}
+ private PhoenixConnection upgradeSystemIdxCdcTracker(PhoenixConnection metaConnection)
+ throws SQLException {
+ try {
+ metaConnection.createStatement().executeUpdate(getIdxCdcTrackerDDL());
+ } catch (TableAlreadyExistsException ignored) {
+ }
+ return metaConnection;
+ }
+
// Special method for adding the column qualifier column for 4.10.
private PhoenixConnection addColumnQualifierColumn(PhoenixConnection oldMetaConnection,
Long timestamp) throws SQLException {
@@ -6966,6 +6985,8 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices
"DELETE FROM " + SYSTEM_CDC_STREAM_STATUS_NAME + " WHERE TABLE_NAME = ?";
String deleteStreamPartitionsQuery =
"DELETE FROM " + SYSTEM_CDC_STREAM_NAME + " WHERE TABLE_NAME = ?";
+ String deleteIdxCdcTrackerQuery =
+ "DELETE FROM " + SYSTEM_IDX_CDC_TRACKER_NAME + " WHERE TABLE_NAME = ?";
LOGGER.info("Deleting Stream Metadata for table {}", tableName);
try (PreparedStatement ps = conn.prepareStatement(deleteStreamStatusQuery)) {
ps.setString(1, tableName);
@@ -6977,6 +6998,11 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices
ps.executeUpdate();
conn.commit();
}
+ try (PreparedStatement ps = conn.prepareStatement(deleteIdxCdcTrackerQuery)) {
+ ps.setString(1, tableName);
+ ps.executeUpdate();
+ conn.commit();
+ }
}
/**
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java
index d0fe82ee19..abbc08ac31 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionlessQueryServicesImpl.java
@@ -219,6 +219,10 @@ public class ConnectionlessQueryServicesImpl extends DelegateQueryServices
return setSystemDDLProperties(QueryConstants.CREATE_CDC_STREAM_METADATA);
}
+ protected String getIdxCdcTrackerDDL() {
+ return setSystemDDLProperties(QueryConstants.CREATE_IDX_CDC_TRACKER_METADATA);
+ }
+
private String setSystemDDLProperties(String ddl) {
return String.format(ddl,
props.getInt(DEFAULT_SYSTEM_MAX_VERSIONS_ATTRIB,
@@ -496,6 +500,10 @@ public class ConnectionlessQueryServicesImpl extends DelegateQueryServices
metaConnection.createStatement().executeUpdate(getCDCStreamDDL());
} catch (TableAlreadyExistsException ignore) {
}
+ try {
+ metaConnection.createStatement().executeUpdate(getIdxCdcTrackerDDL());
+ } catch (TableAlreadyExistsException ignore) {
+ }
} catch (SQLException e) {
sqlE = e;
} finally {
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryConstants.java b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryConstants.java
index fe9ea6ef8b..aa73c83314 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryConstants.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryConstants.java
@@ -54,9 +54,11 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.GLOBAL_SCAN_DETAIL
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.GUIDE_POSTS_ROW_COUNT;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.GUIDE_POSTS_WIDTH;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.GUIDE_POST_KEY;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IDX_CDC_TRACKER_TTL;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IMMUTABLE_ROWS;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IMMUTABLE_STORAGE_SCHEME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INCREMENT_BY;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_CONSISTENCY;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_DISABLE_TIMESTAMP;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_STATE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_TYPE;
@@ -73,6 +75,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.JAR_PATH;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.KEY_SEQ;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LAST_DDL_TIMESTAMP;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LAST_STATS_UPDATE_TIME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LAST_TIMESTAMP;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LIMIT_REACHED_FLAG;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LINK_TYPE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.LOGICAL_PARENT_NAME;
@@ -88,6 +91,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.NUM_ARGS;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.NUM_PREC_RADIX;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.OLD_METADATA;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.ORDINAL_POSITION;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.OWNER_PARTITION_ID;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.PARENT_PARTITION_ID;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.PARENT_PARTITION_START_TIME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.PARTITION_END_KEY;
@@ -133,6 +137,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_TABLE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CHILD_LINK_TABLE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_FUNCTION_TABLE;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_IDX_CDC_TRACKER_TABLE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_LOG_TABLE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_MUTEX_TABLE_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_STATS_TABLE;
@@ -150,6 +155,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TASK_TABLE_TTL;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TASK_TS;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TASK_TYPE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TENANT_ID;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TRACKER_STATUS;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TRANSACTIONAL;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TRANSACTION_PROVIDER;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TRANSFORM_FUNCTION;
@@ -376,12 +382,17 @@ public interface QueryConstants {
String CDC_PRE_IMAGE = "pre_image";
String CDC_POST_IMAGE = "post_image";
String CDC_CHANGE_IMAGE = "change_image";
+ String CDC_IDX_MUTATIONS = "idx_mutations";
String CDC_UPSERT_EVENT_TYPE = "upsert";
String CDC_DELETE_EVENT_TYPE = "delete";
String SPLITS_FILE = "SPLITS_FILE";
String CDC_TTL_DELETE_EVENT_TYPE = "ttl_delete";
String CDC_IMAGE_CQ = "_CDC_IMG_";
byte[] CDC_IMAGE_CQ_BYTES = Bytes.toBytes(CDC_IMAGE_CQ);
+ String CDC_INDEX_PRE_MUTATIONS_CQ = "_IDX_PRE_";
+ byte[] CDC_INDEX_PRE_MUTATIONS_CQ_BYTES = Bytes.toBytes(CDC_INDEX_PRE_MUTATIONS_CQ);
+ String CDC_INDEX_POST_MUTATIONS_CQ = "_IDX_POST_";
+ byte[] CDC_INDEX_POST_MUTATIONS_CQ_BYTES = Bytes.toBytes(CDC_INDEX_POST_MUTATIONS_CQ);
/**
* We mark counter values 0 to 10 as reserved. Value 0 is used by
@@ -408,7 +419,7 @@ public interface QueryConstants {
+ " BOOLEAN, \n" + SCHEMA_VERSION + " VARCHAR, \n" + EXTERNAL_SCHEMA_ID + " VARCHAR, \n"
+ STREAMING_TOPIC_NAME + " VARCHAR, \n" + INDEX_WHERE + " VARCHAR, \n" + CDC_INCLUDE_TABLE
+ " VARCHAR, \n" + TTL + " VARCHAR, \n" + ROW_KEY_MATCHER + " VARBINARY_ENCODED, \n"
- + IS_STRICT_TTL + " BOOLEAN, \n" +
+ + IS_STRICT_TTL + " BOOLEAN, \n" + INDEX_CONSISTENCY + " CHAR(1), \n" +
// Column metadata (will be null for table row)
DATA_TYPE + " INTEGER," + COLUMN_SIZE + " INTEGER," + DECIMAL_DIGITS + " INTEGER," + NULLABLE
+ " INTEGER," + ORDINAL_POSITION + " INTEGER," + SORT_ORDER + " INTEGER," + ARRAY_SIZE
@@ -586,4 +597,18 @@ public interface QueryConstants {
+ PARENT_PARTITION_ID + "))\n" + HConstants.VERSIONS + "=%s,\n"
+ ColumnFamilyDescriptorBuilder.KEEP_DELETED_CELLS + "=%s,\n" + TRANSACTIONAL + "="
+ Boolean.FALSE + ",\n" + UPDATE_CACHE_FREQUENCY + "=" + "7200000";
+
+ String CREATE_IDX_CDC_TRACKER_METADATA =
+ "CREATE TABLE " + SYSTEM_CATALOG_SCHEMA + ".\"" + SYSTEM_IDX_CDC_TRACKER_TABLE + "\"(\n" +
+ // PK columns
+ TABLE_NAME + " VARCHAR NOT NULL," + PARTITION_ID + " VARCHAR NOT NULL," + OWNER_PARTITION_ID
+ + " VARCHAR NOT NULL," +
+ // Non-PK columns
+ LAST_TIMESTAMP + " BIGINT," + TRACKER_STATUS + " CHAR(1),\n" + "CONSTRAINT "
+ + SYSTEM_TABLE_PK_NAME + " PRIMARY KEY (" + TABLE_NAME + "," + PARTITION_ID + ","
+ + OWNER_PARTITION_ID + "))\n" + HConstants.VERSIONS + "=%s,\n"
+ + ColumnFamilyDescriptorBuilder.KEEP_DELETED_CELLS + "=%s,\n" + TRANSACTIONAL + "="
+ + Boolean.FALSE + ",\n" + UPDATE_CACHE_FREQUENCY + "=" + "7200000" + ",\n"
+ + ColumnFamilyDescriptorBuilder.TTL + "=" + IDX_CDC_TRACKER_TTL + ",\n"
+ + "\"phoenix.max.lookback.age.seconds\"=0";
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/DelegateTable.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/DelegateTable.java
index 44df43c929..b41da68782 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/DelegateTable.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/DelegateTable.java
@@ -28,6 +28,7 @@ import org.apache.phoenix.hbase.index.util.KeyValueBuilder;
import org.apache.phoenix.index.IndexMaintainer;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.schema.transform.TransformMaintainer;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.transaction.TransactionFactory;
@@ -283,6 +284,11 @@ public class DelegateTable implements PTable {
return delegate.getIndexType();
}
+ @Override
+ public IndexConsistency getIndexConsistency() {
+ return delegate.getIndexConsistency();
+ }
+
private final PTable delegate;
public DelegateTable(PTable delegate) {
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
index 0da85f851d..53f44687e8 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
@@ -59,6 +59,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.FUNCTION_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.GUIDE_POSTS_WIDTH;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IMMUTABLE_ROWS;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IMMUTABLE_STORAGE_SCHEME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_CONSISTENCY;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_DISABLE_TIMESTAMP;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_STATE;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_TYPE;
@@ -279,6 +280,7 @@ import org.apache.phoenix.schema.stats.StatisticsUtil;
import org.apache.phoenix.schema.task.SystemTaskParams;
import org.apache.phoenix.schema.task.Task;
import org.apache.phoenix.schema.transform.TransformClient;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.types.PBson;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PDate;
@@ -347,9 +349,9 @@ public class MetaDataClient {
+ IMMUTABLE_STORAGE_SCHEME + "," + ENCODING_SCHEME + "," + USE_STATS_FOR_PARALLELIZATION + ","
+ VIEW_INDEX_ID_DATA_TYPE + "," + CHANGE_DETECTION_ENABLED + "," + PHYSICAL_TABLE_NAME + ","
+ SCHEMA_VERSION + "," + STREAMING_TOPIC_NAME + "," + INDEX_WHERE + "," + CDC_INCLUDE_TABLE
- + "," + TTL + "," + ROW_KEY_MATCHER + "," + IS_STRICT_TTL
+ + "," + TTL + "," + ROW_KEY_MATCHER + "," + IS_STRICT_TTL + "," + INDEX_CONSISTENCY
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, "
- + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private static final String CREATE_SCHEMA = "UPSERT INTO " + SYSTEM_CATALOG_SCHEMA + ".\""
+ SYSTEM_CATALOG_TABLE + "\"( " + TABLE_SCHEM + "," + TABLE_NAME + ") VALUES (?,?)";
@@ -401,6 +403,10 @@ public class MetaDataClient {
+ INDEX_STATE + "," + INDEX_DISABLE_TIMESTAMP + "," + ASYNC_REBUILD_TIMESTAMP + " "
+ PLong.INSTANCE.getSqlTypeName() + ") VALUES (?, ?, ?, ?, ?, ?)";
+ public static final String UPDATE_INDEX_CONSISTENCY =
+ "UPSERT INTO " + SYSTEM_CATALOG_SCHEMA + ".\"" + SYSTEM_CATALOG_TABLE + "\"( " + TENANT_ID + ","
+ + TABLE_SCHEM + "," + TABLE_NAME + "," + INDEX_CONSISTENCY + ") VALUES (?, ?, ?, ?)";
+
/*
* Custom sql to add a column to SYSTEM.CATALOG table during upgrade. We can't use the regular
* ColumnMetaDataOps.UPSERT_COLUMN sql because the COLUMN_QUALIFIER column was added in 4.10. And
@@ -1065,7 +1071,7 @@ public class MetaDataClient {
}
}
table = createTableInternal(statement, splits, parent, viewStatement, viewType, viewIndexIdType,
- rowKeyMatcher, viewColumnConstants, isViewColumnReferenced, false, null, null, null,
+ rowKeyMatcher, viewColumnConstants, isViewColumnReferenced, false, null, null, null, null,
tableProps, commonFamilyProps);
if (table == null || table.getType() == PTableType.VIEW || statement.isNoVerify() /*
@@ -1194,6 +1200,10 @@ public class MetaDataClient {
continue;
}
+ if (tableType == PTableType.INDEX && "CONSISTENCY".equalsIgnoreCase(prop.getFirst())) {
+ continue;
+ }
+
// HTableDescriptor property or Phoenix Table Property
if (defaultDescriptor.getValue(Bytes.toBytes(prop.getFirst())) == null) {
// See PHOENIX-4891
@@ -1898,7 +1908,7 @@ public class MetaDataClient {
statement.ifNotExists(), null, statement.getWhere(), statement.getBindCount(), null);
table = createTableInternal(tableStatement, splits, dataTable, null, null,
getViewIndexDataType(), null, null, null, allocateIndexId, statement.getIndexType(),
- asyncCreatedDate, null, tableProps, commonFamilyProps);
+ statement.getIndexConsistency(), asyncCreatedDate, null, tableProps, commonFamilyProps);
} finally {
deleteMutexCells(physicalSchemaName, physicalTableName, acquiredColumnMutexSet);
}
@@ -1908,6 +1918,13 @@ public class MetaDataClient {
if (LOGGER.isInfoEnabled())
LOGGER.info("Created index " + table.getName().getString() + " at " + table.getTimeStamp());
+
+ if (
+ statement.getIndexConsistency() != null && statement.getIndexConsistency().isAsynchronous()
+ ) {
+ createCDCForEventuallyConsistentIndex(dataTable);
+ }
+
boolean asyncIndexBuildEnabled =
connection.getQueryServices().getProps().getBoolean(QueryServices.INDEX_ASYNC_BUILD_ENABLED,
QueryServicesOptions.DEFAULT_INDEX_ASYNC_BUILD_ENABLED);
@@ -2021,7 +2038,7 @@ public class MetaDataClient {
columnDefs, FACTORY.primaryKey(null, pkColumnDefs), Collections.emptyList(), PTableType.CDC,
statement.isIfNotExists(), null, null, statement.getBindCount(), null);
createTableInternal(tableStatement, null, dataTable, null, null, null, null, null, null, false,
- null, null, statement.getIncludeScopes(), tableProps, commonFamilyProps);
+ null, null, null, statement.getIncludeScopes(), tableProps, commonFamilyProps);
// for now, only track stream partition metadata for tables, TODO: updatable views
if (PTableType.TABLE == dataTable.getType()) {
updateStreamPartitionMetadata(dataTableFullName, cdcObjName);
@@ -2444,12 +2461,37 @@ public class MetaDataClient {
return parentName.getString().equals(view.getPhysicalName().getString());
}
+ /**
+ * Creates a CDC stream on the data table for eventually consistent indexes if not already
+ * present.
+ * @param dataTable The data table PTable object for which to create the CDC stream.
+ * @throws SQLException If there is an error executing the CREATE CDC statement or accessing the
+ * database connection.
+ */
+ private void createCDCForEventuallyConsistentIndex(PTable dataTable) throws SQLException {
+ String dataTableName = dataTable.getTableName().getString();
+ String schemaName = dataTable.getSchemaName().getString();
+ String cdcName = "CDC_" + dataTableName;
+ String fullTableName = SchemaUtil.getCaseSensitiveColumnDisplayName(schemaName, dataTableName);
+ String cdcSql = "CREATE CDC IF NOT EXISTS \"" + cdcName + "\" ON " + fullTableName;
+ try {
+ connection.createStatement().execute(cdcSql);
+ } catch (SQLException e) {
+ if (SQLExceptionCode.CDC_ALREADY_ENABLED.getErrorCode() != e.getErrorCode()) {
+ LOGGER.error("Error while creating CDC for {}", fullTableName, e);
+ throw e;
+ }
+ LOGGER.debug("CDC already exists for {}", fullTableName, e);
+ }
+ }
+
private PTable createTableInternal(CreateTableStatement statement, byte[][] splits,
final PTable parent, String viewStatement, ViewType viewType, PDataType viewIndexIdType,
final byte[] rowKeyMatcher, final byte[][] viewColumnConstants,
final BitSet isViewColumnReferenced, boolean allocateIndexId, IndexType indexType,
- Date asyncCreatedDate, Set<PTable.CDCChangeScope> cdcIncludeScopes,
- Map<String, Object> tableProps, Map<String, Object> commonFamilyProps) throws SQLException {
+ IndexConsistency indexConsistency, Date asyncCreatedDate,
+ Set<PTable.CDCChangeScope> cdcIncludeScopes, Map<String, Object> tableProps,
+ Map<String, Object> commonFamilyProps) throws SQLException {
final PTableType tableType = statement.getTableType();
boolean wasAutoCommit = connection.getAutoCommit();
TableName tableNameNode = null;
@@ -3399,9 +3441,10 @@ public class MetaDataClient {
.setTimeStamp(MetaDataProtocol.MIN_TABLE_TIMESTAMP).setIndexDisableTimestamp(0L)
.setSequenceNumber(PTable.INITIAL_SEQ_NUM).setImmutableRows(isImmutableRows)
.setDisableWAL(Boolean.TRUE.equals(disableWAL)).setMultiTenant(false).setStoreNulls(false)
- .setViewIndexIdType(viewIndexIdType).setIndexType(indexType).setUpdateCacheFrequency(0)
- .setNamespaceMapped(isNamespaceMapped).setAutoPartitionSeqName(autoPartitionSeq)
- .setAppendOnlySchema(isAppendOnlySchema).setImmutableStorageScheme(ONE_CELL_PER_COLUMN)
+ .setViewIndexIdType(viewIndexIdType).setIndexType(indexType).setIndexConsistency(null)
+ .setUpdateCacheFrequency(0).setNamespaceMapped(isNamespaceMapped)
+ .setAutoPartitionSeqName(autoPartitionSeq).setAppendOnlySchema(isAppendOnlySchema)
+ .setImmutableStorageScheme(ONE_CELL_PER_COLUMN)
.setQualifierEncodingScheme(NON_ENCODED_QUALIFIERS)
.setBaseColumnCount(QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT)
.setEncodedCQCounter(PTable.EncodedCQCounter.NULL_COUNTER)
@@ -3706,6 +3749,16 @@ public class MetaDataClient {
tableUpsert.setBoolean(38, isStrictTTL);
+ if (tableType == PTableType.INDEX) {
+ if (indexConsistency == null) {
+ tableUpsert.setNull(39, Types.CHAR);
+ } else {
+ tableUpsert.setString(39, indexConsistency.getCodeAsString());
+ }
+ } else {
+ tableUpsert.setNull(39, Types.CHAR);
+ }
+
tableUpsert.execute();
if (asyncCreatedDate != null) {
@@ -3809,6 +3862,7 @@ public class MetaDataClient {
.setDisableWAL(Boolean.TRUE.equals(disableWAL)).setMultiTenant(multiTenant)
.setStoreNulls(storeNulls).setViewType(viewType).setViewIndexIdType(viewIndexIdType)
.setViewIndexId(result.getViewIndexId()).setIndexType(indexType)
+ .setIndexConsistency(tableType == PTableType.INDEX ? indexConsistency : null)
.setTransactionProvider(transactionProvider).setUpdateCacheFrequency(updateCacheFrequency)
.setNamespaceMapped(isNamespaceMapped).setAutoPartitionSeqName(autoPartitionSeq)
.setAppendOnlySchema(isAppendOnlySchema).setImmutableStorageScheme(immutableStorageScheme)
@@ -5928,6 +5982,37 @@ public class MetaDataClient {
metaPropertiesEvaluated, table, schemaName, tableName, new MutableBoolean(false));
PIndexState newIndexState = statement.getIndexState();
+ IndexConsistency newIndexConsistency = statement.getIndexConsistency();
+
+ if (newIndexConsistency != null) {
+ try (PreparedStatement consistencyUpsert =
+ connection.prepareStatement(UPDATE_INDEX_CONSISTENCY)) {
+ consistencyUpsert.setString(1, tenantId);
+ consistencyUpsert.setString(2, schemaName);
+ consistencyUpsert.setString(3, indexName);
+ consistencyUpsert.setString(4, newIndexConsistency.getCodeAsString());
+ consistencyUpsert.execute();
+ connection.commit();
+
+ if (newIndexConsistency == IndexConsistency.EVENTUAL) {
+ PTable dataTable =
+ connection.getTable(tenantId, SchemaUtil.getTableName(schemaName, dataTableName));
+ createCDCForEventuallyConsistentIndex(dataTable);
+ }
+
+ connection.getQueryServices().clearTableFromCache(
+ connection.getTenantId() == null
+ ? ByteUtil.EMPTY_BYTE_ARRAY
+ : connection.getTenantId().getBytes(),
+ schemaName == null ? ByteUtil.EMPTY_BYTE_ARRAY : Bytes.toBytes(schemaName),
+ Bytes.toBytes(indexName), HConstants.LATEST_TIMESTAMP);
+ connection.removeTable(connection.getTenantId(),
+ SchemaUtil.getTableName(schemaName, indexName),
+ SchemaUtil.getTableName(schemaName, dataTableName), HConstants.LATEST_TIMESTAMP);
+
+ return new MutationState(1, 1000, connection);
+ }
+ }
if (isAsync && newIndexState != PIndexState.REBUILD) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.ASYNC_NOT_ALLOWED)
@@ -5941,13 +6026,8 @@ public class MetaDataClient {
connection.setAutoCommit(false);
// Confirm index table is valid and up-to-date
TableRef indexRef = FromCompiler.getResolver(statement, connection).getTables().get(0);
- PreparedStatement tableUpsert = null;
- try {
- if (newIndexState == PIndexState.ACTIVE) {
- tableUpsert = connection.prepareStatement(UPDATE_INDEX_STATE_TO_ACTIVE);
- } else {
- tableUpsert = connection.prepareStatement(UPDATE_INDEX_STATE);
- }
+ try (PreparedStatement tableUpsert = connection.prepareStatement(
+ newIndexState == PIndexState.ACTIVE ? UPDATE_INDEX_STATE_TO_ACTIVE : UPDATE_INDEX_STATE)) {
tableUpsert.setString(1,
connection.getTenantId() == null ? null : connection.getTenantId().getString());
tableUpsert.setString(2, schemaName);
@@ -5958,10 +6038,6 @@ public class MetaDataClient {
tableUpsert.setLong(6, 0);
}
tableUpsert.execute();
- } finally {
- if (tableUpsert != null) {
- tableUpsert.close();
- }
}
Long timeStamp = indexRef.getTable().isTransactional() ? indexRef.getTimeStamp() : null;
List<Mutation> tableMetadata =
@@ -6050,8 +6126,8 @@ public class MetaDataClient {
}
}
} else {
- try {
- tableUpsert = connection.prepareStatement(UPDATE_INDEX_REBUILD_ASYNC_STATE);
+ try (PreparedStatement tableUpsert =
+ connection.prepareStatement(UPDATE_INDEX_REBUILD_ASYNC_STATE)) {
tableUpsert.setString(1,
connection.getTenantId() == null ? null : connection.getTenantId().getString());
tableUpsert.setString(2, schemaName);
@@ -6060,10 +6136,6 @@ public class MetaDataClient {
tableUpsert.setLong(4, beginTimestamp);
tableUpsert.execute();
connection.commit();
- } finally {
- if (tableUpsert != null) {
- tableUpsert.close();
- }
}
}
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTable.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTable.java
index 36a9ab739f..effd7773ec 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTable.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTable.java
@@ -39,6 +39,7 @@ import org.apache.phoenix.hbase.index.util.KeyValueBuilder;
import org.apache.phoenix.index.IndexMaintainer;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.schema.transform.TransformMaintainer;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.types.PArrayDataType;
import org.apache.phoenix.schema.types.PArrayDataTypeDecoder;
import org.apache.phoenix.schema.types.PArrayDataTypeEncoder;
@@ -931,6 +932,8 @@ public interface PTable extends PMetaDataEntity {
IndexType getIndexType();
+ IndexConsistency getIndexConsistency();
+
int getBaseColumnCount();
/**
@@ -1154,5 +1157,15 @@ public interface PTable extends PMetaDataEntity {
* Include only the post image (state past the change) of the row.
*/
POST,
+
+ /**
+ * Include index mutations for eventually consistent indexes.
+ */
+ IDX_MUTATIONS,
+
+ /**
+ * Include raw before/after data row states as serialized Puts for index mutation generation.
+ */
+ DATA_ROW_STATE,
}
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTableImpl.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTableImpl.java
index 027d031705..dee96ceafc 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTableImpl.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/PTableImpl.java
@@ -103,6 +103,7 @@ import org.apache.phoenix.protobuf.ProtobufUtil;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.RowKeySchema.RowKeySchemaBuilder;
import org.apache.phoenix.schema.transform.TransformMaintainer;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.types.PBinary;
import org.apache.phoenix.schema.types.PChar;
import org.apache.phoenix.schema.types.PDataType;
@@ -193,6 +194,7 @@ public class PTableImpl implements PTable {
private final Long viewIndexId;
private final int estimatedSize;
private final IndexType indexType;
+ private final IndexConsistency indexConsistency;
private final int baseColumnCount;
private final boolean rowKeyOrderOptimizable; // TODO: remove when required that tables have been
// upgrade for PHOENIX-2067
@@ -267,6 +269,7 @@ public class PTableImpl implements PTable {
private Long viewIndexId;
private int estimatedSize;
private IndexType indexType;
+ private IndexConsistency indexConsistency;
private int baseColumnCount;
private boolean rowKeyOrderOptimizable;
private boolean hasColumnsRequiringUpgrade;
@@ -534,6 +537,11 @@ public class PTableImpl implements PTable {
return this;
}
+ public Builder setIndexConsistency(IndexConsistency indexConsistency) {
+ this.indexConsistency = indexConsistency;
+ return this;
+ }
+
public Builder setBaseColumnCount(int baseColumnCount) {
this.baseColumnCount = baseColumnCount;
return this;
@@ -994,6 +1002,7 @@ public class PTableImpl implements PTable {
this.viewIndexId = builder.viewIndexId;
this.estimatedSize = builder.estimatedSize;
this.indexType = builder.indexType;
+ this.indexConsistency = builder.indexConsistency;
this.baseColumnCount = builder.baseColumnCount;
this.rowKeyOrderOptimizable = builder.rowKeyOrderOptimizable;
this.hasColumnsRequiringUpgrade = builder.hasColumnsRequiringUpgrade;
@@ -1052,6 +1061,7 @@ public class PTableImpl implements PTable {
.setMultiTenant(table.isMultiTenant()).setStoreNulls(table.getStoreNulls())
.setViewType(table.getViewType()).setViewIndexIdType(table.getviewIndexIdType())
.setViewIndexId(table.getViewIndexId()).setIndexType(table.getIndexType())
+ .setIndexConsistency(table.getIndexConsistency())
.setTransactionProvider(table.getTransactionProvider())
.setUpdateCacheFrequency(table.getUpdateCacheFrequency())
.setNamespaceMapped(table.isNamespaceMapped())
@@ -1890,6 +1900,11 @@ public class PTableImpl implements PTable {
return indexType;
}
+ @Override
+ public IndexConsistency getIndexConsistency() {
+ return type == PTableType.INDEX ? indexConsistency : null;
+ }
+
/**
* Construct a PTable instance from ProtoBuffered PTable instance
*/
@@ -2096,6 +2111,14 @@ public class PTableImpl implements PTable {
if (table.hasRowKeyMatcher()) {
rowKeyMatcher = table.getRowKeyMatcher().toByteArray();
}
+ IndexConsistency indexConsistency = null;
+ if (tableType == PTableType.INDEX) {
+ if (table.hasIndexConsistency()) {
+ indexConsistency = IndexConsistency.valueOf(table.getIndexConsistency());
+ } else {
+ indexConsistency = null;
+ }
+ }
try {
return new PTableImpl.Builder().setType(tableType).setState(indexState)
@@ -2103,7 +2126,7 @@ public class PTableImpl implements PTable {
.setSequenceNumber(sequenceNumber).setImmutableRows(isImmutableRows)
.setViewStatement(viewStatement).setDisableWAL(disableWAL).setMultiTenant(multiTenant)
.setStoreNulls(storeNulls).setViewType(viewType).setViewIndexIdType(viewIndexIdType)
- .setViewIndexId(viewIndexId).setIndexType(indexType)
+ .setViewIndexId(viewIndexId).setIndexType(indexType).setIndexConsistency(indexConsistency)
.setTransactionProvider(transactionProvider).setUpdateCacheFrequency(updateCacheFrequency)
.setNamespaceMapped(isNamespaceMapped).setAutoPartitionSeqName(autoPartitionSeqName)
.setAppendOnlySchema(isAppendOnlySchema)
@@ -2166,6 +2189,9 @@ public class PTableImpl implements PTable {
builder.setIndexType(
ByteStringer.wrap(new byte[] { table.getIndexType().getSerializedValue() }));
}
+ if (table.getIndexConsistency() != null) {
+ builder.setIndexConsistency(table.getIndexConsistency().name());
+ }
}
builder.setSequenceNumber(table.getSequenceNumber());
builder.setTimeStamp(table.getTimeStamp());
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/tool/SchemaSQLUtil.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/tool/SchemaSQLUtil.java
index 6fc4c1c98a..d6b513ad26 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/tool/SchemaSQLUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/tool/SchemaSQLUtil.java
@@ -26,6 +26,7 @@ import org.apache.phoenix.parse.CreateIndexStatement;
import org.apache.phoenix.parse.CreateTableStatement;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableType;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.util.SchemaUtil;
import org.apache.phoenix.thirdparty.com.google.common.collect.ListMultimap;
@@ -74,6 +75,12 @@ public class SchemaSQLUtil {
if (createStmt.isAsync()) {
sb.append(" ASYNC");
}
+ if (
+ createStmt.getIndexConsistency() != null
+ && createStmt.getIndexConsistency() != IndexConsistency.getDefault()
+ ) {
+ sb.append(" CONSISTENCY=").append(createStmt.getIndexConsistency().name());
+ }
appendProperties(sb, createStmt.getProps());
return sb.toString();
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/transform/TransformClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/transform/TransformClient.java
index ebcd9f034c..f37c414f5b 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/transform/TransformClient.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/transform/TransformClient.java
@@ -258,9 +258,10 @@ public class TransformClient {
.setEstimatedSize(table.getEstimatedSize()).setFamilies(table.getColumnFamilies())
.setImmutableRows(table.isImmutableRows())
.setIsChangeDetectionEnabled(table.isChangeDetectionEnabled())
- .setIndexType(table.getIndexType()).setIndexes(Collections.<PTable> emptyList())
- .setName(newTableName).setMultiTenant(table.isMultiTenant())
- .setParentName(table.getParentName()).setParentSchemaName(table.getParentSchemaName())
+ .setIndexType(table.getIndexType()).setIndexConsistency(table.getIndexConsistency())
+ .setIndexes(Collections.emptyList()).setName(newTableName)
+ .setMultiTenant(table.isMultiTenant()).setParentName(table.getParentName())
+ .setParentSchemaName(table.getParentSchemaName())
.setNamespaceMapped(table.isNamespaceMapped()).setSchemaName(table.getSchemaName())
.setPkColumns(table.getPKColumns()).setPkName(table.getPKName())
.setRowKeySchema(table.getRowKeySchema()).setStoreNulls(table.getStoreNulls())
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/IndexConsistency.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/IndexConsistency.java
new file mode 100644
index 0000000000..13b61fab4e
--- /dev/null
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/IndexConsistency.java
@@ -0,0 +1,109 @@
+/*
+ * 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.schema.types;
+
+/**
+ * Enum representing the consistency level for Phoenix secondary indexes.
+ * <p>
+ * <b>STRONG</b>: Synchronous index maintenance where index mutations are applied synchronously with
+ * data table mutations. This provides immediate consistency but may impact write performance and
+ * availability.
+ * </p>
+ * <p>
+ * <b>EVENTUAL</b>: Asynchronous index maintenance where index mutations are applied in the
+ * background. This provides better write performance and availability but with eventual consistency
+ * guarantees.
+ * </p>
+ */
+public enum IndexConsistency {
+ /**
+ * Strong consistency - synchronous index maintenance (default behavior). Index updates are
+ * applied synchronously with data table updates.
+ */
+ STRONG('s'),
+
+ /**
+ * Eventual consistency - asynchronous index maintenance using CDC. Index updates are applied in
+ * the background, providing eventual consistency.
+ */
+ EVENTUAL('e');
+
+ private final char code;
+
+ IndexConsistency(char code) {
+ this.code = code;
+ }
+
+ /**
+ * Returns the single character code for the consistency.
+ * @return 's' for STRONG, 'e' for EVENTUAL
+ */
+ public char getCode() {
+ return code;
+ }
+
+ /**
+ * Returns the string representation of the code for storage.
+ * @return single character string for storage
+ */
+ public String getCodeAsString() {
+ return String.valueOf(code);
+ }
+
+ /**
+ * Parses an IndexConsistency from its stored code.
+ * @param code the stored code.
+ * @return the corresponding IndexConsistency, or null if code is null/empty/invalid.
+ */
+ public static IndexConsistency fromCode(String code) {
+ if (code == null || code.isEmpty()) {
+ return null;
+ }
+ char c = code.charAt(0);
+ for (IndexConsistency ic : values()) {
+ if (ic.code == c) {
+ return ic;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the default consistency level for indexes.
+ * @return STRONG consistency as the default
+ */
+ public static IndexConsistency getDefault() {
+ return STRONG;
+ }
+
+ /**
+ * Checks if this consistency level requires synchronous index maintenance.
+ * @return true if synchronous maintenance is required, false otherwise
+ */
+ public boolean isSynchronous() {
+ return this == STRONG;
+ }
+
+ /**
+ * Checks if this consistency level uses asynchronous index maintenance.
+ * @return true if asynchronous maintenance is used, false otherwise
+ */
+ public boolean isAsynchronous() {
+ return this == EVENTUAL;
+ }
+}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/util/CDCChangeBuilder.java b/phoenix-core-client/src/main/java/org/apache/phoenix/util/CDCChangeBuilder.java
index 45a7e957e2..880a0a327b 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/CDCChangeBuilder.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/CDCChangeBuilder.java
@@ -26,9 +26,12 @@ import static org.apache.phoenix.query.QueryConstants.CDC_TTL_DELETE_EVENT_TYPE;
import static org.apache.phoenix.query.QueryConstants.CDC_UPSERT_EVENT_TYPE;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.apache.hadoop.hbase.Cell;
+import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.index.CDCTableInfo;
import org.apache.phoenix.schema.PTable;
@@ -36,6 +39,8 @@ public class CDCChangeBuilder {
private final boolean isChangeImageInScope;
private final boolean isPreImageInScope;
private final boolean isPostImageInScope;
+ private final boolean isIdxMutationsInScope;
+ private final boolean isDataRowStateInScope;
private final CDCTableInfo cdcDataTableInfo;
private String changeType;
private long lastDeletedTimestamp;
@@ -43,12 +48,20 @@ public class CDCChangeBuilder {
private Map<String, Object> preImage = null;
private Map<String, Object> changeImage = null;
+ private boolean isFullRowDelete;
+ private Map<ImmutableBytesPtr, Cell> rawLatestBeforeChange;
+ private Map<ImmutableBytesPtr, Cell> rawAtChange;
+ private Set<ImmutableBytesPtr> rawDeletedColumnsAtChange;
+ private Map<ImmutableBytesPtr, Long> rawDeletedColumnsBeforeChange;
+
public CDCChangeBuilder(CDCTableInfo cdcDataTableInfo) {
this.cdcDataTableInfo = cdcDataTableInfo;
Set<PTable.CDCChangeScope> changeScopes = cdcDataTableInfo.getIncludeScopes();
isChangeImageInScope = changeScopes.contains(PTable.CDCChangeScope.CHANGE);
isPreImageInScope = changeScopes.contains(PTable.CDCChangeScope.PRE);
isPostImageInScope = changeScopes.contains(PTable.CDCChangeScope.POST);
+ isIdxMutationsInScope = changeScopes.contains(PTable.CDCChangeScope.IDX_MUTATIONS);
+ isDataRowStateInScope = changeScopes.contains(PTable.CDCChangeScope.DATA_ROW_STATE);
}
public void initChange(long ts) {
@@ -61,6 +74,13 @@ public class CDCChangeBuilder {
if (isChangeImageInScope || isPostImageInScope) {
changeImage = new HashMap<>();
}
+ if (isDataRowStateInScope) {
+ isFullRowDelete = false;
+ rawLatestBeforeChange = new LinkedHashMap<>();
+ rawAtChange = new LinkedHashMap<>();
+ rawDeletedColumnsAtChange = new HashSet<>();
+ rawDeletedColumnsBeforeChange = new HashMap<>();
+ }
}
public long getChangeTimestamp() {
@@ -77,6 +97,9 @@ public class CDCChangeBuilder {
public void markAsDeletionEvent() {
changeType = CDC_DELETE_EVENT_TYPE;
+ if (isDataRowStateInScope) {
+ isFullRowDelete = true;
+ }
}
public long getLastDeletedTimestamp() {
@@ -141,12 +164,69 @@ public class CDCChangeBuilder {
}
public boolean isOlderThanChange(Cell cell) {
- return (cell.getTimestamp() < changeTimestamp && cell.getTimestamp() > lastDeletedTimestamp)
- ? true
- : false;
+ return cell.getTimestamp() < changeTimestamp && cell.getTimestamp() > lastDeletedTimestamp;
+ }
+
+ public void registerRawPut(Cell cell, ImmutableBytesPtr colKey) {
+ if (cell.getTimestamp() == changeTimestamp) {
+ rawAtChange.putIfAbsent(colKey, cell);
+ } else if (isOlderThanChange(cell)) {
+ Long colDeleteTs = rawDeletedColumnsBeforeChange.get(colKey);
+ if (
+ (colDeleteTs == null || cell.getTimestamp() > colDeleteTs)
+ && !rawLatestBeforeChange.containsKey(colKey)
+ ) {
+ rawLatestBeforeChange.put(colKey, cell);
+ }
+ }
+ }
+
+ public void registerRawDeleteColumn(Cell cell, ImmutableBytesPtr colKey) {
+ if (cell.getTimestamp() == changeTimestamp) {
+ rawDeletedColumnsAtChange.add(colKey);
+ } else if (isOlderThanChange(cell)) {
+ rawDeletedColumnsBeforeChange.putIfAbsent(colKey, cell.getTimestamp());
+ }
+ }
+
+ public boolean hasValidDataRowStateChanges() {
+ return isFullRowDelete || !rawAtChange.isEmpty() || !rawDeletedColumnsAtChange.isEmpty();
+ }
+
+ public boolean isFullRowDelete() {
+ return isFullRowDelete;
+ }
+
+ public Map<ImmutableBytesPtr, Cell> getRawLatestBeforeChange() {
+ return rawLatestBeforeChange;
+ }
+
+ public Map<ImmutableBytesPtr, Cell> getRawAtChange() {
+ return rawAtChange;
+ }
+
+ public Set<ImmutableBytesPtr> getRawDeletedColumnsAtChange() {
+ return rawDeletedColumnsAtChange;
}
public boolean isPreImageInScope() {
return isPreImageInScope;
}
+
+ public boolean isPostImageInScope() {
+ return isPostImageInScope;
+ }
+
+ public boolean isChangeImageInScope() {
+ return isChangeImageInScope;
+ }
+
+ public boolean isIdxMutationsInScope() {
+ return isIdxMutationsInScope;
+ }
+
+ public boolean isDataRowStateInScope() {
+ return isDataRowStateInScope;
+ }
+
}
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/util/CDCUtil.java b/phoenix-core-client/src/main/java/org/apache/phoenix/util/CDCUtil.java
index 68c9b48aa7..d36c957f7a 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/CDCUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/CDCUtil.java
@@ -36,6 +36,7 @@ import org.apache.hadoop.util.StringUtils;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.exception.SQLExceptionInfo;
import org.apache.phoenix.execute.DescVarLengthFastByteComparisons;
+import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.PIndexState;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.types.PDataType;
@@ -148,6 +149,34 @@ public class CDCUtil {
return table.getIndexes().stream().anyMatch(CDCUtil::isCDCIndex);
}
+ /**
+ * Get the CDC object name for the given data table that can be used in SQL queries.
+ * @param dataTable The data table PTable object.
+ * @param withSchema If true, returns the CDC object name with schema prefix. If false, returns
+ * only the CDC object name.
+ * @return The CDC object name that can be used in SQL queries, optionally prefixed with schema
+ * name, or null if no active CDC index exists.
+ */
+ public static String getCDCObjectName(PTable dataTable, boolean withSchema) {
+ if (dataTable == null) {
+ return null;
+ }
+ PTable cdcIndex = getActiveCDCIndex(dataTable);
+ if (cdcIndex == null) {
+ return null;
+ }
+ String cdcIndexName = cdcIndex.getTableName().getString();
+ String schemaName = dataTable.getSchemaName().getString();
+ if (isCDCIndex(cdcIndexName)) {
+ String cdcObj = cdcIndexName.substring(CDC_INDEX_PREFIX.length());
+ if (!withSchema || schemaName == null || schemaName.isEmpty()) {
+ return cdcObj;
+ }
+ return schemaName + QueryConstants.NAME_SEPARATOR + cdcObj;
+ }
+ return null;
+ }
+
public static Scan setupScanForCDC(Scan scan) {
scan.setRaw(true);
scan.readAllVersions();
diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/util/MetaDataUtil.java b/phoenix-core-client/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
index 5396fc70f8..9249a8ad68 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
@@ -126,7 +126,8 @@ public class MetaDataUtil {
public static final List<String> SYSTEM_TABLES_WITH_TTL_SUPPORTED =
Arrays.asList(SchemaUtil.getTableName(SYSTEM_CATALOG_SCHEMA, SYSTEM_CDC_STREAM_TABLE),
- SchemaUtil.getTableName(SYSTEM_CATALOG_SCHEMA, SYSTEM_CDC_STREAM_STATUS_TABLE));
+ SchemaUtil.getTableName(SYSTEM_CATALOG_SCHEMA, SYSTEM_CDC_STREAM_STATUS_TABLE),
+ SchemaUtil.getTableName(SYSTEM_CATALOG_SCHEMA, SYSTEM_IDX_CDC_TRACKER_TABLE));
public static Put getLastDDLTimestampUpdate(byte[] tableHeaderRowKey, long clientTimestamp,
long lastDDLTimestamp) {
diff --git a/phoenix-core-client/src/main/protobuf/IndexMutations.proto b/phoenix-core-client/src/main/protobuf/IndexMutations.proto
new file mode 100644
index 0000000000..cba4360dd7
--- /dev/null
+++ b/phoenix-core-client/src/main/protobuf/IndexMutations.proto
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+option java_package = "org.apache.phoenix.coprocessor.generated";
+option java_outer_classname = "IndexMutationsProtos";
+option java_generic_services = true;
+option java_generate_equals_and_hash = true;
+option optimize_for = SPEED;
+
+// The batch of index mutations for eventually consistent indexes.
+// Each entry pairs a target index table name (tables[i]) with its
+// corresponding serialized HBase MutationProto (mutations[i]).
+message IndexMutations {
+ repeated bytes tables = 1;
+ repeated bytes mutations = 2;
+}
+
+// Raw data row states for generating index mutations.
+// Contains the data row key and serialized MutationProto for the before and after states.
+message DataRowStates {
+ optional bytes dataRowKey = 1;
+ optional bytes currentDataRowState = 2;
+ optional bytes nextDataRowState = 3;
+}
diff --git a/phoenix-core-client/src/main/protobuf/PTable.proto b/phoenix-core-client/src/main/protobuf/PTable.proto
index 43f5315114..a98239f9ed 100644
--- a/phoenix-core-client/src/main/protobuf/PTable.proto
+++ b/phoenix-core-client/src/main/protobuf/PTable.proto
@@ -124,6 +124,7 @@ message PTable {
optional bytes ttl = 55;
optional bytes rowKeyMatcher = 56;
optional bool isStrictTTL = 57;
+ optional string indexConsistency = 58;
}
message EncodedCQCounter {
diff --git a/phoenix-core-client/src/main/protobuf/ServerCachingService.proto b/phoenix-core-client/src/main/protobuf/ServerCachingService.proto
index d8e1945527..2c4f358ccc 100644
--- a/phoenix-core-client/src/main/protobuf/ServerCachingService.proto
+++ b/phoenix-core-client/src/main/protobuf/ServerCachingService.proto
@@ -73,6 +73,7 @@ message IndexMaintainer {
repeated ColumnReference indexWhereColumns = 30;
optional bool isCDCIndex = 31;
optional int32 dataTableSaltBuckets = 32;
+ optional string indexConsistency = 33;
}
message TransformMaintainer {
diff --git a/phoenix-core-server/pom.xml b/phoenix-core-server/pom.xml
index 3b4c36f518..bb236a47f6 100644
--- a/phoenix-core-server/pom.xml
+++ b/phoenix-core-server/pom.xml
@@ -178,7 +178,12 @@
<artifactId>bcprov-jdk18on</artifactId>
<version>1.79</version>
</dependency>
+ <dependency>
+ <groupId>org.xerial.snappy</groupId>
+ <artifactId>snappy-java</artifactId>
+ </dependency>
</dependencies>
+
<build>
<plugins>
<plugin>
diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/CDCGlobalIndexRegionScanner.java b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/CDCGlobalIndexRegionScanner.java
index 5bb0c40217..97b388dc43 100644
--- a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/CDCGlobalIndexRegionScanner.java
+++ b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/CDCGlobalIndexRegionScanner.java
@@ -21,18 +21,22 @@ import static org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverCons
import static org.apache.phoenix.util.ByteUtil.EMPTY_BYTE_ARRAY;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.protobuf.ByteString;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellBuilder;
import org.apache.hadoop.hbase.CellBuilderFactory;
import org.apache.hadoop.hbase.CellBuilderType;
import org.apache.hadoop.hbase.CellUtil;
+import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
@@ -41,6 +45,7 @@ import org.apache.hadoop.hbase.regionserver.Region;
import org.apache.hadoop.hbase.regionserver.RegionScanner;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.coprocessor.generated.CDCInfoProtos;
+import org.apache.phoenix.coprocessor.generated.IndexMutationsProtos;
import org.apache.phoenix.execute.TupleProjector;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.SingleCellColumnExpression;
@@ -50,6 +55,7 @@ import org.apache.phoenix.index.IndexMaintainer;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.tuple.ResultTuple;
import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PVarchar;
import org.apache.phoenix.util.CDCChangeBuilder;
import org.apache.phoenix.util.CDCUtil;
import org.apache.phoenix.util.EncodedColumnsUtil;
@@ -57,6 +63,10 @@ import org.apache.phoenix.util.IndexUtil;
import org.apache.phoenix.util.JacksonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.xerial.snappy.Snappy;
+
+import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
/**
* CDC (Change Data Capture) enabled region scanner for global indexes that processes uncovered CDC
@@ -86,6 +96,10 @@ public class CDCGlobalIndexRegionScanner extends UncoveredGlobalIndexRegionScann
private static final Logger LOGGER = LoggerFactory.getLogger(CDCGlobalIndexRegionScanner.class);
private CDCTableInfo cdcDataTableInfo;
private CDCChangeBuilder changeBuilder;
+ private static final byte[] SEPARATOR = { 0 };
+
+ private static final byte[] EMPTY_IDX_MUTATIONS = PVarchar.INSTANCE.toBytes(Base64.getEncoder()
+ .encodeToString(IndexMutationsProtos.IndexMutations.getDefaultInstance().toByteArray()));
public CDCGlobalIndexRegionScanner(final RegionScanner innerScanner, final Region region,
final Scan scan, final RegionCoprocessorEnvironment env, final Scan dataTableScan,
@@ -102,6 +116,13 @@ public class CDCGlobalIndexRegionScanner extends UncoveredGlobalIndexRegionScann
@Override
protected Scan prepareDataTableScan(Collection<byte[]> dataRowKeys) throws IOException {
+ if (
+ changeBuilder.isIdxMutationsInScope() && !changeBuilder.isDataRowStateInScope()
+ && !changeBuilder.isChangeImageInScope() && !changeBuilder.isPreImageInScope()
+ && !changeBuilder.isPostImageInScope()
+ ) {
+ return null;
+ }
// TODO: Get Timerange from the start row and end row of the index scan object
// and set it in the datatable scan object.
// if (scan.getStartRow().length == 8) {
@@ -112,7 +133,11 @@ public class CDCGlobalIndexRegionScanner extends UncoveredGlobalIndexRegionScann
// stopTimeRange = PLong.INSTANCE.getCodec().decodeLong(
// scan.getStopRow(), 0, SortOrder.getDefault());
// }
- return CDCUtil.setupScanForCDC(prepareDataTableScan(dataRowKeys, true));
+ Scan dataScan = prepareDataTableScan(dataRowKeys, true);
+ if (dataScan == null) {
+ return null;
+ }
+ return CDCUtil.setupScanForCDC(dataScan);
}
protected boolean getNextCoveredIndexRow(List<Cell> result) throws IOException {
@@ -120,9 +145,11 @@ public class CDCGlobalIndexRegionScanner extends UncoveredGlobalIndexRegionScann
List<Cell> indexRow = indexRowIterator.next();
Cell indexCell = indexRow.get(0);
byte[] indexRowKey = ImmutableBytesPtr.cloneCellRowIfNecessary(indexCell);
+ if (handleIdxMutationsCDCEvent(indexRow, indexRowKey, indexCell, result)) {
+ return true;
+ }
if (indexRow.size() > 1) {
- boolean success = handlePreImageCDCEvent(indexRow, indexRowKey, indexCell, result);
- if (success) {
+ if (handlePreImageCDCEvent(indexRow, indexRowKey, indexCell, result)) {
return true;
}
}
@@ -158,59 +185,74 @@ public class CDCGlobalIndexRegionScanner extends UncoveredGlobalIndexRegionScann
// marker after that.
changeBuilder.setLastDeletedTimestamp(cell.getTimestamp());
}
- } else if (
- (cell.getType() == Cell.Type.DeleteColumn || cell.getType() == Cell.Type.Put)
- && !Arrays.equals(cellQual, emptyCQ)
- ) {
- if (!changeBuilder.isChangeRelevant(cell)) {
- // We don't need to build the change image, just skip it.
- continue;
- }
- // In this case, cell is the row, meaning we loop over rows..
- if (isSingleCell) {
- while (curColumnNum < cdcColumnInfoList.size()) {
- boolean hasValue = dataTableProjector.getSchema().extractValue(cell,
- (SingleCellColumnExpression) expressions[curColumnNum], ptr);
- if (hasValue) {
- Object cellValue = getColumnValue(ptr.get(), ptr.getOffset(), ptr.getLength(),
- cdcColumnInfoList.get(curColumnNum).getColumnType());
- changeBuilder.registerChange(cell, curColumnNum, cellValue);
+ } else
+ if (cell.getType() == Cell.Type.DeleteColumn || cell.getType() == Cell.Type.Put) {
+ boolean isEmptyCQ = Arrays.equals(cellQual, emptyCQ);
+ if (changeBuilder.isDataRowStateInScope()) {
+ ImmutableBytesPtr colKey =
+ new ImmutableBytesPtr(Bytes.add(cellFam, SEPARATOR, cellQual));
+ if (cell.getType() == Cell.Type.DeleteColumn) {
+ changeBuilder.registerRawDeleteColumn(cell, colKey);
+ } else {
+ changeBuilder.registerRawPut(cell, colKey);
}
- ++curColumnNum;
}
- break cellLoop;
- }
- while (true) {
- CDCTableInfo.CDCColumnInfo currentColumnInfo = cdcColumnInfoList.get(curColumnNum);
- int columnComparisonResult =
- CDCUtil.compareCellFamilyAndQualifier(cellFam, cellQual,
- currentColumnInfo.getColumnFamily(), currentColumnInfo.getColumnQualifier());
- if (columnComparisonResult > 0) {
- if (++curColumnNum >= cdcColumnInfoList.size()) {
- // Have no more column definitions, so the rest of the cells
- // must be for dropped columns and so can be ignored.
- break cellLoop;
- }
- // Continue looking for the right column definition
- // for this cell.
+ if (
+ isEmptyCQ || changeBuilder.isDataRowStateInScope()
+ && !changeBuilder.isChangeImageInScope() && !changeBuilder.isPreImageInScope()
+ && !changeBuilder.isPostImageInScope()
+ ) {
continue;
- } else if (columnComparisonResult < 0) {
- // We didn't find a column definition for this cell, ignore the
- // current cell but continue working on the rest of the cells.
- continue cellLoop;
}
+ // In this case, cell is the row, meaning we loop over rows..
+ if (isSingleCell) {
+ while (curColumnNum < cdcColumnInfoList.size()) {
+ boolean hasValue = dataTableProjector.getSchema().extractValue(cell,
+ (SingleCellColumnExpression) expressions[curColumnNum], ptr);
+ if (hasValue) {
+ Object cellValue = getColumnValue(ptr.get(), ptr.getOffset(), ptr.getLength(),
+ cdcColumnInfoList.get(curColumnNum).getColumnType());
+ changeBuilder.registerChange(cell, curColumnNum, cellValue);
+ }
+ ++curColumnNum;
+ }
+ break cellLoop;
+ }
+ while (true) {
+ CDCTableInfo.CDCColumnInfo currentColumnInfo =
+ cdcColumnInfoList.get(curColumnNum);
+ int columnComparisonResult =
+ CDCUtil.compareCellFamilyAndQualifier(cellFam, cellQual,
+ currentColumnInfo.getColumnFamily(), currentColumnInfo.getColumnQualifier());
+ if (columnComparisonResult > 0) {
+ if (++curColumnNum >= cdcColumnInfoList.size()) {
+ // Have no more column definitions, so the rest of the cells
+ // must be for dropped columns and so can be ignored.
+ break cellLoop;
+ }
+ // Continue looking for the right column definition
+ // for this cell.
+ continue;
+ } else if (columnComparisonResult < 0) {
+ // We didn't find a column definition for this cell, ignore the
+ // current cell but continue working on the rest of the cells.
+ continue cellLoop;
+ }
- // else, found the column definition.
- Object cellValue = cell.getType() == Cell.Type.DeleteColumn
- ? null
- : getColumnValue(cell, cdcColumnInfoList.get(curColumnNum).getColumnType());
- changeBuilder.registerChange(cell, curColumnNum, cellValue);
- // Done processing the current cell, check the next cell.
- break;
+ // else, found the column definition.
+ Object cellValue = cell.getType() == Cell.Type.DeleteColumn
+ ? null
+ : getColumnValue(cell, cdcColumnInfoList.get(curColumnNum).getColumnType());
+ changeBuilder.registerChange(cell, curColumnNum, cellValue);
+ // Done processing the current cell, check the next cell.
+ break;
+ }
}
- }
}
- if (changeBuilder.isNonEmptyEvent()) {
+ if (changeBuilder.isDataRowStateInScope()) {
+ buildDataRowStateResult(dataRowKey.copyBytesIfNecessary(), indexRowKey, indexCell,
+ result);
+ } else if (changeBuilder.isNonEmptyEvent()) {
Result cdcRow = getCDCImage(indexRowKey, indexCell);
if (cdcRow != null && tupleProjector != null) {
if (indexCell.getType() == Cell.Type.DeleteFamily) {
@@ -235,7 +277,12 @@ public class CDCGlobalIndexRegionScanner extends UncoveredGlobalIndexRegionScann
result.clear();
}
} else {
- result.clear();
+ if (changeBuilder.isDataRowStateInScope()) {
+ buildDataRowStateResult(dataRowKey.copyBytesIfNecessary(), indexRowKey, indexCell,
+ result);
+ } else {
+ result.clear();
+ }
}
return true;
@@ -317,18 +364,151 @@ public class CDCGlobalIndexRegionScanner extends UncoveredGlobalIndexRegionScann
cdcJson.remove(QueryConstants.CDC_PRE_IMAGE);
cdcEventBytes = JacksonUtil.getObjectWriter(HashMap.class).writeValueAsBytes(cdcJson);
}
+ addResult(indexRowKey, indexCell, result, cdcDataCell, cdcEventBytes);
+ return true;
+ }
+
+ /**
+ * Builds a DataRowStates protobuf result from the raw cell maps collected by CDCChangeBuilder
+ * during the raw cell iteration. Constructs before/after HBase Put objects representing the row
+ * state before and after the change, serializes them, and populates the result. If no valid
+ * changes were found at the change timestamp (data not yet visible), only the dataRowKey is
+ * included, the consumer needs to retry.
+ * @param dataRowKey The data table row key bytes.
+ * @param indexRowKey The CDC index row key.
+ * @param indexCell The index cell.
+ * @param result The result list to populate.
+ * @throws IOException if serialization fails.
+ */
+ private void buildDataRowStateResult(byte[] dataRowKey, byte[] indexRowKey, Cell indexCell,
+ List<Cell> result) throws IOException {
+ Put currentDataRowState = null;
+ Put nextDataRowState = null;
+ Map<ImmutableBytesPtr, Cell> latestBeforeChange = changeBuilder.getRawLatestBeforeChange();
+ Map<ImmutableBytesPtr, Cell> atChange = changeBuilder.getRawAtChange();
+ Set<ImmutableBytesPtr> deletedColumnsAtChange = changeBuilder.getRawDeletedColumnsAtChange();
+ if (changeBuilder.hasValidDataRowStateChanges()) {
+ if (!latestBeforeChange.isEmpty()) {
+ currentDataRowState = new Put(dataRowKey);
+ for (Cell cell : latestBeforeChange.values()) {
+ currentDataRowState.add(cell);
+ }
+ }
+ if (!changeBuilder.isFullRowDelete()) {
+ Put nextState = new Put(dataRowKey);
+ for (Map.Entry<ImmutableBytesPtr, Cell> entry : latestBeforeChange.entrySet()) {
+ if (
+ !atChange.containsKey(entry.getKey())
+ && !deletedColumnsAtChange.contains(entry.getKey())
+ ) {
+ nextState.add(entry.getValue());
+ }
+ }
+ for (Cell cell : atChange.values()) {
+ nextState.add(cell);
+ }
+ if (!nextState.isEmpty()) {
+ nextDataRowState = nextState;
+ }
+ }
+ }
+ IndexMutationsProtos.DataRowStates.Builder builder =
+ IndexMutationsProtos.DataRowStates.newBuilder();
+ builder.setDataRowKey(ByteString.copyFrom(dataRowKey));
+ if (currentDataRowState != null) {
+ builder.setCurrentDataRowState(ByteString.copyFrom(
+ ProtobufUtil.toMutation(ClientProtos.MutationProto.MutationType.PUT, currentDataRowState)
+ .toByteArray()));
+ }
+ if (nextDataRowState != null) {
+ builder.setNextDataRowState(ByteString.copyFrom(ProtobufUtil
+ .toMutation(ClientProtos.MutationProto.MutationType.PUT, nextDataRowState).toByteArray()));
+ }
+ String base64String = Base64.getEncoder().encodeToString(builder.build().toByteArray());
+ byte[] cdcEventBytes = PVarchar.INSTANCE.toBytes(base64String);
+ addResult(indexRowKey, indexCell, result, indexCell, cdcEventBytes);
+ }
+
+ /**
+ * Handles CDC event when IDX_MUTATIONS scope is enabled. Returns the index mutations as a
+ * serialized IndexMutations, or an empty proto if no mutations are present. Skips the data table
+ * scan.
+ * @param indexRow The CDC index row.
+ * @param indexRowKey The CDC index row key.
+ * @param indexCell The primary index cell.
+ * @param result The result list to populate.
+ * @return true if IDX_MUTATIONS scope is enabled, false otherwise.
+ * @throws IOException if decompression or proto parsing fails.
+ */
+ private boolean handleIdxMutationsCDCEvent(List<Cell> indexRow, byte[] indexRowKey,
+ Cell indexCell, List<Cell> result) throws IOException {
+ if (!changeBuilder.isIdxMutationsInScope()) {
+ return false;
+ }
+ byte[] idxMutationsBytes = EMPTY_IDX_MUTATIONS;
+ Cell idxMutationsCell = indexCell;
+ IndexMutationsProtos.IndexMutations preMutations = null;
+ IndexMutationsProtos.IndexMutations postMutations = null;
+ Cell preCell = null;
+ for (Cell cell : indexRow) {
+ if (
+ Bytes.equals(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength(),
+ QueryConstants.CDC_INDEX_PRE_MUTATIONS_CQ_BYTES, 0,
+ QueryConstants.CDC_INDEX_PRE_MUTATIONS_CQ_BYTES.length)
+ ) {
+ byte[] rawBytes = CellUtil.cloneValue(cell);
+ preMutations = IndexMutationsProtos.IndexMutations.parseFrom(maybeDecompress(rawBytes));
+ preCell = cell;
+ } else if (
+ Bytes.equals(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength(),
+ QueryConstants.CDC_INDEX_POST_MUTATIONS_CQ_BYTES, 0,
+ QueryConstants.CDC_INDEX_POST_MUTATIONS_CQ_BYTES.length)
+ ) {
+ byte[] rawBytes = CellUtil.cloneValue(cell);
+ postMutations = IndexMutationsProtos.IndexMutations.parseFrom(maybeDecompress(rawBytes));
+ }
+ }
+ if (preMutations != null) {
+ IndexMutationsProtos.IndexMutations merged = mergeIndexMutations(preMutations, postMutations);
+ String base64String = Base64.getEncoder().encodeToString(merged.toByteArray());
+ idxMutationsBytes = PVarchar.INSTANCE.toBytes(base64String);
+ idxMutationsCell = preCell;
+ }
+ addResult(indexRowKey, indexCell, result, idxMutationsCell, idxMutationsBytes);
+ return true;
+ }
+
+ private static byte[] maybeDecompress(byte[] input) {
+ try {
+ if (Snappy.isValidCompressedBuffer(input)) {
+ return Snappy.uncompress(input);
+ }
+ } catch (IOException e) {
+ LOGGER.error("Error uncompressing CDC Index mutations", e);
+ }
+ return input;
+ }
+
+ private IndexMutationsProtos.IndexMutations mergeIndexMutations(
+ IndexMutationsProtos.IndexMutations pre, IndexMutationsProtos.IndexMutations post) {
+ if (post == null) {
+ return pre;
+ }
+ return IndexMutationsProtos.IndexMutations.newBuilder().addAllTables(pre.getTablesList())
+ .addAllMutations(pre.getMutationsList()).addAllTables(post.getTablesList())
+ .addAllMutations(post.getMutationsList()).build();
+ }
+
+ private void addResult(byte[] indexRowKey, Cell indexCell, List<Cell> result, Cell cdcDataCell,
+ byte[] cdcEventBytes) {
Result cdcRow =
createCDCResult(indexRowKey, indexCell, cdcDataCell.getTimestamp(), cdcEventBytes);
-
if (tupleProjector != null) {
result.add(indexCell);
IndexUtil.addTupleAsOneCell(result, new ResultTuple(cdcRow), tupleProjector, ptr);
} else {
result.clear();
}
- LOGGER.debug(
- "Processed CDC event with embedded data, skipped data table scan for" + " row key: {}",
- Bytes.toStringBinary(indexRowKey));
- return true;
}
+
}
diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java
index 9d3d0b3ca4..053976b586 100644
--- a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java
+++ b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/MetaDataEndpointImpl.java
@@ -39,6 +39,7 @@ import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.DISABLE_WAL_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.ENCODING_SCHEME_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.EXTERNAL_SCHEMA_ID_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.IMMUTABLE_ROWS_BYTES;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_CONSISTENCY_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_DISABLE_TIMESTAMP_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_STATE_BYTES;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.INDEX_TYPE_BYTES;
@@ -263,8 +264,10 @@ import org.apache.phoenix.schema.metrics.MetricsMetadataSource;
import org.apache.phoenix.schema.metrics.MetricsMetadataSourceFactory;
import org.apache.phoenix.schema.task.ServerTask;
import org.apache.phoenix.schema.task.SystemTaskParams;
+import org.apache.phoenix.schema.types.IndexConsistency;
import org.apache.phoenix.schema.types.PBinary;
import org.apache.phoenix.schema.types.PBoolean;
+import org.apache.phoenix.schema.types.PChar;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PInteger;
import org.apache.phoenix.schema.types.PLong;
@@ -418,6 +421,8 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements RegionCopr
createFirstOnRow(ByteUtil.EMPTY_BYTE_ARRAY, TABLE_FAMILY_BYTES, CDC_INCLUDE_BYTES);
private static final Cell INDEX_WHERE_KV =
createFirstOnRow(ByteUtil.EMPTY_BYTE_ARRAY, TABLE_FAMILY_BYTES, INDEX_WHERE_BYTES);
+ private static final Cell INDEX_CONSISTENCY_KV =
+ createFirstOnRow(ByteUtil.EMPTY_BYTE_ARRAY, TABLE_FAMILY_BYTES, INDEX_CONSISTENCY_BYTES);
private static final Cell TTL_KV =
createFirstOnRow(ByteUtil.EMPTY_BYTE_ARRAY, TABLE_FAMILY_BYTES, TTL_BYTES);
@@ -436,7 +441,7 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements RegionCopr
AUTO_PARTITION_SEQ_KV, APPEND_ONLY_SCHEMA_KV, STORAGE_SCHEME_KV, ENCODING_SCHEME_KV,
USE_STATS_FOR_PARALLELIZATION_KV, LAST_DDL_TIMESTAMP_KV, CHANGE_DETECTION_ENABLED_KV,
SCHEMA_VERSION_KV, EXTERNAL_SCHEMA_ID_KV, STREAMING_TOPIC_NAME_KV, INDEX_WHERE_KV,
- CDC_INCLUDE_KV, TTL_KV, ROW_KEY_MATCHER_KV, IS_STRICT_TTL_KV);
+ CDC_INCLUDE_KV, TTL_KV, ROW_KEY_MATCHER_KV, IS_STRICT_TTL_KV, INDEX_CONSISTENCY_KV);
static {
Collections.sort(TABLE_KV_COLUMNS, CellComparatorImpl.COMPARATOR);
@@ -498,6 +503,7 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements RegionCopr
private static final int TTL_INDEX = TABLE_KV_COLUMNS.indexOf(TTL_KV);
private static final int ROW_KEY_MATCHER_INDEX = TABLE_KV_COLUMNS.indexOf(ROW_KEY_MATCHER_KV);
private static final int IS_STRICT_TTL_INDEX = TABLE_KV_COLUMNS.indexOf(IS_STRICT_TTL_KV);
+ private static final int INDEX_CONSISTENCY_INDEX = TABLE_KV_COLUMNS.indexOf(INDEX_CONSISTENCY_KV);
// KeyValues for Column
private static final KeyValue DECIMAL_DIGITS_KV =
createFirstOnRow(ByteUtil.EMPTY_BYTE_ARRAY, TABLE_FAMILY_BYTES, DECIMAL_DIGITS_BYTES);
@@ -1586,6 +1592,17 @@ public class MetaDataEndpointImpl extends MetaDataProtocol implements RegionCopr
builder.setIsStrictTTL(
isStrictTTLKv != null ? isStrictTTL : oldTable == null || oldTable.isStrictTTL());
+ Cell indexConsistencyKv = tableKeyValues[INDEX_CONSISTENCY_INDEX];
+ IndexConsistency indexConsistency = null;
+ if (indexConsistencyKv != null) {
+ String consistencyStr = (String) PChar.INSTANCE.toObject(indexConsistencyKv.getValueArray(),
+ indexConsistencyKv.getValueOffset(), indexConsistencyKv.getValueLength());
+ indexConsistency = IndexConsistency.fromCode(consistencyStr);
+ }
+ builder.setIndexConsistency(indexConsistency != null ? indexConsistency
+ : oldTable != null ? oldTable.getIndexConsistency()
+ : null);
+
// Check the cell tag to see whether the view has modified this property
final byte[] tagUseStatsForParallelization = (useStatsForParallelizationKv == null)
? HConstants.EMPTY_BYTE_ARRAY
diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/UncoveredIndexRegionScanner.java b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/UncoveredIndexRegionScanner.java
index 640b85bc5a..5d65754109 100644
--- a/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/UncoveredIndexRegionScanner.java
+++ b/phoenix-core-server/src/main/java/org/apache/phoenix/coprocessor/UncoveredIndexRegionScanner.java
@@ -63,6 +63,7 @@ import org.apache.phoenix.schema.types.PVarbinary;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.EnvironmentEdgeManager;
import org.apache.phoenix.util.IndexUtil;
+import org.apache.phoenix.util.PhoenixKeyValueUtil;
import org.apache.phoenix.util.ScanUtil;
import org.apache.phoenix.util.ServerUtil;
import org.slf4j.Logger;
@@ -280,7 +281,21 @@ public abstract class UncoveredIndexRegionScanner extends BaseRegionScanner {
indexToDataRowKeyMap.put(offset == 0 ? lastIndexRowKey : CellUtil.cloneRow(firstCell),
indexMaintainer.buildDataRowKey(new ImmutableBytesWritable(lastIndexRowKey),
viewConstants));
- indexRows.add(row);
+ // When a row has at most one cell, we can safely reuse it as we don't look at cell value,
+ // and we proceed with data table scan.
+ // For rows with multiple cells, we must copy each cell to avoid issues where the
+ // underlying byte buffers for cell values may be reused or invalidated by the scanner
+ // on subsequent nextRaw() calls and hence the cell value could be garbage under
+ // high GC pressure.
+ if (row.size() <= 1) {
+ indexRows.add(row);
+ } else {
+ List<Cell> dupRow = new ArrayList<>(row.size());
+ for (Cell cell : row) {
+ dupRow.add(PhoenixKeyValueUtil.maybeCopyCell(cell));
+ }
+ indexRows.add(dupRow);
+ }
indexRowCount++;
if (
hasMore && (PhoenixScannerContext.isTimedOut(scannerContext, pageSizeMs)
diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexCDCConsumer.java b/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexCDCConsumer.java
new file mode 100644
index 0000000000..6c4ceab789
--- /dev/null
+++ b/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexCDCConsumer.java
@@ -0,0 +1,1347 @@
+/*
+ * 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;
+
+import com.google.protobuf.ByteString;
+import java.io.IOException;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.client.ConnectionUtils;
+import org.apache.hadoop.hbase.client.Mutation;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.hadoop.hbase.util.Pair;
+import org.apache.phoenix.coprocessor.DelegateRegionCoprocessorEnvironment;
+import org.apache.phoenix.coprocessor.generated.IndexMutationsProtos;
+import org.apache.phoenix.coprocessorclient.MetaDataProtocol;
+import org.apache.phoenix.hbase.index.metrics.MetricsIndexCDCConsumerSource;
+import org.apache.phoenix.hbase.index.metrics.MetricsIndexerSourceFactory;
+import org.apache.phoenix.hbase.index.table.HTableInterfaceReference;
+import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
+import org.apache.phoenix.hbase.index.write.IndexWriter;
+import org.apache.phoenix.index.IndexMaintainer;
+import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.schema.PColumn;
+import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.RowKeySchema;
+import org.apache.phoenix.schema.TableNotFoundException;
+import org.apache.phoenix.schema.types.IndexConsistency;
+import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.util.ByteUtil;
+import org.apache.phoenix.util.CDCUtil;
+import org.apache.phoenix.util.EnvironmentEdgeManager;
+import org.apache.phoenix.util.QueryUtil;
+import org.apache.phoenix.util.SchemaUtil;
+import org.apache.phoenix.util.ServerUtil.ConnectionType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.phoenix.thirdparty.com.google.common.collect.ArrayListMultimap;
+import org.apache.phoenix.thirdparty.com.google.common.collect.ListMultimap;
+
+import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
+
+/**
+ * A single-threaded background consumer that processes CDC mutations for eventually consistent
+ * indexes. This consumer reads mutations from the CDC index table and applies them to the
+ * appropriate secondary indexes.
+ * <p>
+ * The consumer tracks its progress in the SYSTEM.IDX_CDC_TRACKER table, allowing for proper
+ * handling of region splits and merges.
+ * </p>
+ * <p>
+ * <b>Lifecycle:</b>
+ * <ol>
+ * <li>On startup, the consumer first replays any remaining mutations from parent regions (if this
+ * region was created from a split or merge). A region will have multiple parent regions in case of
+ * region merge.</li>
+ * <li>After replaying all parent mutations, the consumer marks each parent region as COMPLETE in
+ * SYSTEM.IDX_CDC_TRACKER.</li>
+ * <li>The consumer then begins processing mutations for the current region. With every batch of
+ * rows consumed (e.g., 1000 rows), it updates LAST_TIMESTAMP in SYSTEM.IDX_CDC_TRACKER. The tracker
+ * entry with status IN_PROGRESS is created on the first batch update.</li>
+ * <li>When the region is closed (due to split, merge, or shutdown), the consumer stops but does NOT
+ * mark itself as COMPLETE. The COMPLETE status is only set by child regions after they have
+ * replayed all parent mutations.</li>
+ * </ol>
+ * </p>
+ */
+public class IndexCDCConsumer implements Runnable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(IndexCDCConsumer.class);
+
+ public static final String INDEX_CDC_CONSUMER_BATCH_SIZE =
+ "phoenix.index.cdc.consumer.batch.size";
+ private static final int DEFAULT_CDC_BATCH_SIZE = 500;
+
+ public static final String INDEX_CDC_CONSUMER_STARTUP_DELAY_MS =
+ "phoenix.index.cdc.consumer.startup.delay.ms";
+ private static final long DEFAULT_STARTUP_DELAY_MS = 10000;
+
+ /**
+ * The interval in milliseconds between processing batches when mutations are found.
+ */
+ public static final String INDEX_CDC_CONSUMER_POLL_INTERVAL_MS =
+ "phoenix.index.cdc.consumer.poll.interval.ms";
+ private static final long DEFAULT_POLL_INTERVAL_MS = 1000;
+
+ /**
+ * The time buffer in milliseconds subtracted from current time when querying CDC mutations to
+ * help avoid reading mutations that are too recent.
+ */
+ public static final String INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS =
+ "phoenix.index.cdc.consumer.timestamp.buffer.ms";
+ private static final long DEFAULT_TIMESTAMP_BUFFER_MS = 5000;
+
+ /**
+ * Maximum number of retries when CDC events exist but the corresponding data table mutations are
+ * not yet visible (or permanently failed). After exceeding this limit, the consumer advances past
+ * the unprocessable events to avoid blocking indefinitely. This is only used for index mutation
+ * generation approach (serializeCDCMutations = false).
+ */
+ public static final String INDEX_CDC_CONSUMER_MAX_DATA_VISIBILITY_RETRIES =
+ "phoenix.index.cdc.consumer.max.data.visibility.retries";
+ private static final int DEFAULT_MAX_DATA_VISIBILITY_RETRIES = 10;
+
+ public static final String INDEX_CDC_CONSUMER_RETRY_PAUSE_MS =
+ "phoenix.index.cdc.consumer.retry.pause.ms";
+ private static final long DEFAULT_RETRY_PAUSE_MS = 2000;
+
+ public static final String INDEX_CDC_CONSUMER_PARENT_PROGRESS_PAUSE_MS =
+ "phoenix.index.cdc.consumer.parent.progress.pause.ms";
+ private static final long DEFAULT_PARENT_PROGRESS_PAUSE_MS = 15000;
+
+ private final RegionCoprocessorEnvironment env;
+ private final String dataTableName;
+ private final String encodedRegionName;
+ private final IndexWriter indexWriter;
+ private final long pause;
+ private final long startupDelayMs;
+ private final int batchSize;
+ private final long pollIntervalMs;
+ private final long timestampBufferMs;
+ private final int maxDataVisibilityRetries;
+ private final long parentProgressPauseMs;
+ private final Configuration config;
+ private final boolean serializeCDCMutations;
+ private final MetricsIndexCDCConsumerSource metricSource;
+ private volatile boolean stopped = false;
+ private Thread consumerThread;
+ private boolean hasParentPartitions = false;
+ private PTable cachedDataTable;
+
+ private boolean tenantInit = false;
+ private boolean isMultiTenant = false;
+ private String tenantIdColName;
+ private PDataType<?> tenantIdDataType;
+ private TenantScanInfo ownRegionScanInfo;
+
+ private final Map<String, TenantScanInfo> ancestorScanInfoCache = new HashMap<>();
+
+ private static class TenantScanInfo {
+
+ private static final TenantScanInfo EMPTY = new TenantScanInfo("", "", null, null, null);
+
+ private final String filter;
+ private final String orderBy;
+ private final Object startValue;
+ private final Object endValue;
+ private final PDataType<?> dataType;
+
+ TenantScanInfo(String filter, String orderBy, Object startValue, Object endValue,
+ PDataType<?> dataType) {
+ this.filter = filter;
+ this.orderBy = orderBy;
+ this.startValue = startValue;
+ this.endValue = endValue;
+ this.dataType = dataType;
+ }
+
+ int bindParams(PreparedStatement ps, int startIndex) throws SQLException {
+ int idx = startIndex;
+ if (startValue != null) {
+ ps.setObject(idx++, startValue, dataType.getSqlType());
+ }
+ if (endValue != null) {
+ ps.setObject(idx++, endValue, dataType.getSqlType());
+ }
+ return idx;
+ }
+ }
+
+ /**
+ * Creates a new IndexCDCConsumer for the given region with configurable serialization mode.
+ * @param env region coprocessor environment.
+ * @param dataTableName name of the data table.
+ * @param serverName server name.
+ * @param serializeCDCMutations when true, consumes pre-serialized index mutations; when false,
+ * generates index mutations from data row states.
+ * @throws IOException if the IndexWriter cannot be created.
+ */
+ public IndexCDCConsumer(RegionCoprocessorEnvironment env, String dataTableName, String serverName,
+ boolean serializeCDCMutations) throws IOException {
+ this.env = env;
+ this.dataTableName = dataTableName;
+ this.encodedRegionName = env.getRegion().getRegionInfo().getEncodedName();
+ this.config = env.getConfiguration();
+ this.serializeCDCMutations = serializeCDCMutations;
+ this.pause = config.getLong(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, DEFAULT_RETRY_PAUSE_MS);
+ this.startupDelayMs =
+ config.getLong(INDEX_CDC_CONSUMER_STARTUP_DELAY_MS, DEFAULT_STARTUP_DELAY_MS);
+ int baseBatchSize = config.getInt(INDEX_CDC_CONSUMER_BATCH_SIZE, DEFAULT_CDC_BATCH_SIZE);
+ int jitter = ThreadLocalRandom.current().nextInt(baseBatchSize / 5 + 1);
+ this.batchSize = baseBatchSize + jitter;
+ this.pollIntervalMs =
+ config.getLong(INDEX_CDC_CONSUMER_POLL_INTERVAL_MS, DEFAULT_POLL_INTERVAL_MS);
+ this.timestampBufferMs =
+ config.getLong(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, DEFAULT_TIMESTAMP_BUFFER_MS);
+ this.maxDataVisibilityRetries = config.getInt(INDEX_CDC_CONSUMER_MAX_DATA_VISIBILITY_RETRIES,
+ DEFAULT_MAX_DATA_VISIBILITY_RETRIES);
+ this.parentProgressPauseMs =
+ config.getLong(INDEX_CDC_CONSUMER_PARENT_PROGRESS_PAUSE_MS, DEFAULT_PARENT_PROGRESS_PAUSE_MS);
+ this.metricSource = MetricsIndexerSourceFactory.getInstance().getIndexCDCConsumerSource();
+ DelegateRegionCoprocessorEnvironment indexWriterEnv =
+ new DelegateRegionCoprocessorEnvironment(env, ConnectionType.INDEX_WRITER_CONNECTION);
+ this.indexWriter =
+ new IndexWriter(indexWriterEnv, serverName + "-index-eventual-writer", false);
+ }
+
+ /**
+ * Starts the consumer thread in the background.
+ */
+ public void start() {
+ consumerThread =
+ new Thread(this, "IndexCDCConsumer-" + dataTableName + "-" + encodedRegionName);
+ consumerThread.setDaemon(true);
+ consumerThread.start();
+ }
+
+ /**
+ * Stops the consumer thread gracefully.
+ */
+ public void stop() {
+ stopped = true;
+ if (consumerThread != null) {
+ consumerThread.interrupt();
+ }
+ if (indexWriter != null) {
+ indexWriter.stop("IndexCDCConsumer stopped for " + dataTableName);
+ }
+ }
+
+ /**
+ * Sleeps for the specified duration if the consumer has not been stopped.
+ * @param millis the duration to sleep in milliseconds.
+ * @throws InterruptedException if the thread is interrupted while sleeping.
+ */
+ private void sleepIfNotStopped(long millis) throws InterruptedException {
+ if (!stopped) {
+ Thread.sleep(millis);
+ }
+ }
+
+ private PTable getDataTable(PhoenixConnection conn) throws SQLException {
+ PTable dataTable = cachedDataTable;
+ if (dataTable == null) {
+ dataTable = conn.getTable(dataTableName);
+ cachedDataTable = dataTable;
+ }
+ return dataTable;
+ }
+
+ private void refreshDataTableCache(PhoenixConnection conn) throws SQLException {
+ cachedDataTable = conn.getTable(dataTableName);
+ }
+
+ private void initTenantInfo(PhoenixConnection conn) throws SQLException {
+ if (tenantInit) {
+ return;
+ }
+ PTable dataTable = getDataTable(conn);
+ isMultiTenant = dataTable.isMultiTenant();
+ if (!isMultiTenant) {
+ ownRegionScanInfo = TenantScanInfo.EMPTY;
+ tenantInit = true;
+ return;
+ }
+ int tenantColIndex = dataTable.getBucketNum() != null ? 1 : 0;
+ PColumn tenantCol = dataTable.getPKColumns().get(tenantColIndex);
+ tenantIdColName = tenantCol.getName().getString();
+ tenantIdDataType = tenantCol.getDataType();
+
+ byte[] regionStartKey = env.getRegion().getRegionInfo().getStartKey();
+ byte[] regionEndKey = env.getRegion().getRegionInfo().getEndKey();
+ ownRegionScanInfo = buildTenantScanInfo(regionStartKey, regionEndKey, dataTable);
+ LOG.debug(
+ "Initialized multi-tenant scan for table {} region {}:"
+ + " tenantCol {}, startTenant {}, endTenant {}",
+ dataTableName, encodedRegionName, tenantIdColName, ownRegionScanInfo.startValue,
+ ownRegionScanInfo.endValue);
+ tenantInit = true;
+ }
+
+ private TenantScanInfo buildTenantScanInfo(byte[] startKey, byte[] endKey, PTable dataTable) {
+ Object startVal = extractTenantIdFromRegionKey(startKey, dataTable);
+ Object endVal = extractTenantIdFromRegionKey(endKey, dataTable);
+ StringBuilder sb = new StringBuilder();
+ if (startVal != null) {
+ sb.append("\"").append(tenantIdColName).append("\" >= ? AND ");
+ }
+ if (endVal != null) {
+ sb.append("\"").append(tenantIdColName).append("\" <= ? AND ");
+ }
+ String filter = sb.toString();
+ String orderBy = filter.isEmpty() ? "" : "\"" + tenantIdColName + "\" ASC,";
+ return new TenantScanInfo(filter, orderBy, startVal, endVal, tenantIdDataType);
+ }
+
+ private Object extractTenantIdFromRegionKey(byte[] regionKey, PTable dataTable) {
+ if (regionKey == null || regionKey.length == 0) {
+ return null;
+ }
+ final RowKeySchema schema = dataTable.getRowKeySchema();
+ int pkPos = dataTable.getBucketNum() != null ? 1 : 0;
+ ImmutableBytesWritable ptr = new ImmutableBytesWritable();
+ int maxOffset = schema.iterator(regionKey, 0, regionKey.length, ptr);
+ for (int i = 0; i <= pkPos; i++) {
+ Boolean hasValue = schema.next(ptr, i, maxOffset);
+ if (!Boolean.TRUE.equals(hasValue)) {
+ return null;
+ }
+ }
+ byte[] tenantBytes = ByteUtil.copyKeyBytesIfNecessary(ptr);
+ PColumn tenantCol = dataTable.getPKColumns().get(pkPos);
+ return tenantCol.getDataType().toObject(tenantBytes, 0, tenantBytes.length,
+ tenantCol.getDataType(), tenantCol.getSortOrder(), tenantCol.getMaxLength(),
+ tenantCol.getScale());
+ }
+
+ private byte[][] lookupPartitionKeys(String partitionId) throws InterruptedException {
+ int retryCount = 0;
+ final String query = "SELECT PARTITION_START_KEY, PARTITION_END_KEY FROM "
+ + PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_NAME
+ + " WHERE TABLE_NAME = ? AND PARTITION_ID = ? LIMIT 1";
+ while (!stopped) {
+ try (
+ PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(env.getConfiguration()).unwrap(PhoenixConnection.class);
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, dataTableName);
+ ps.setString(2, partitionId);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ byte[] startKey = rs.getBytes(1);
+ byte[] endKey = rs.getBytes(2);
+ return new byte[][] { startKey == null ? new byte[0] : startKey,
+ endKey == null ? new byte[0] : endKey };
+ }
+ }
+ LOG.error("No CDC_STREAM entry found for partition {} table {}. This should not happen.",
+ partitionId, dataTableName);
+ return new byte[][] { new byte[0], new byte[0] };
+ } catch (SQLException e) {
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error while retrieving partition keys from CDC_STREAM for partition {} table {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ partitionId, dataTableName, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ return null;
+ }
+
+ private TenantScanInfo getPartitionTenantScanInfo(String partitionId)
+ throws InterruptedException {
+ if (!isMultiTenant) {
+ return TenantScanInfo.EMPTY;
+ }
+ if (partitionId.equals(encodedRegionName)) {
+ return ownRegionScanInfo;
+ }
+ TenantScanInfo cached = ancestorScanInfoCache.get(partitionId);
+ if (cached != null) {
+ return cached;
+ }
+ byte[][] keys = lookupPartitionKeys(partitionId);
+ if (keys == null) {
+ return TenantScanInfo.EMPTY;
+ }
+ TenantScanInfo info = buildTenantScanInfo(keys[0], keys[1], cachedDataTable);
+ ancestorScanInfoCache.put(partitionId, info);
+ return info;
+ }
+
+ @Override
+ public void run() {
+ try {
+ if (startupDelayMs > 0 && getCDCStreamNumPartitions() <= 1) {
+ sleepIfNotStopped(startupDelayMs);
+ }
+ if (stopped) {
+ return;
+ }
+ if (!hasEventuallyConsistentIndexes()) {
+ LOG.trace("No eventually consistent indexes found for table {}. Exiting consumer.",
+ dataTableName);
+ return;
+ }
+ LOG.info(
+ "IndexCDCConsumer started for table {} region {}"
+ + " [batchSize: {}, pollIntervalMs: {}, timestampBufferMs: {}, startupDelayMs: {},"
+ + " pause: {}, maxDataVisibilityRetries: {}, parentProgressPauseMs: {},"
+ + " serializeCDCMutations: {}]",
+ dataTableName, encodedRegionName, batchSize, pollIntervalMs, timestampBufferMs,
+ startupDelayMs, pause, maxDataVisibilityRetries, parentProgressPauseMs,
+ serializeCDCMutations);
+ if (!waitForCDCStreamEntry()) {
+ LOG.error(
+ "IndexCDCConsumer stopped while waiting for CDC_STREAM entry for table {} region {}",
+ dataTableName, encodedRegionName);
+ return;
+ }
+ long lastProcessedTimestamp = checkTrackerStatus(encodedRegionName, encodedRegionName);
+ if (lastProcessedTimestamp == -1) {
+ // should never happen as COMPLETE is only set by child regions for their parent regions
+ LOG.error(
+ "Unexpected COMPLETE status in IDX_CDC_TRACKER for current region. "
+ + "Table: {}, Partition: {}. Exiting consumer thread.",
+ dataTableName, encodedRegionName);
+ return;
+ } else if (lastProcessedTimestamp > 0) {
+ LOG.info(
+ "Found existing tracker entry for table {} region {} with lastTimestamp {}. "
+ + "Resuming from last position (region movement scenario).",
+ dataTableName, encodedRegionName, lastProcessedTimestamp);
+ } else {
+ if (hasParentPartitions) {
+ sleepIfNotStopped(timestampBufferMs + 1);
+ replayAndCompleteParentRegions(encodedRegionName);
+ } else {
+ LOG.info("No parent partitions for table {} region {}, skipping parent replay",
+ dataTableName, encodedRegionName);
+ }
+ }
+ int retryCount = 0;
+ while (!stopped) {
+ try {
+ long previousTimestamp = lastProcessedTimestamp;
+ if (serializeCDCMutations) {
+ lastProcessedTimestamp =
+ processCDCBatch(encodedRegionName, encodedRegionName, lastProcessedTimestamp, false);
+ } else {
+ lastProcessedTimestamp = processCDCBatchGenerated(encodedRegionName, encodedRegionName,
+ lastProcessedTimestamp, false);
+ }
+ if (lastProcessedTimestamp == previousTimestamp) {
+ sleepIfNotStopped(ConnectionUtils.getPauseTime(pause, ++retryCount));
+ } else {
+ retryCount = 0;
+ sleepIfNotStopped(pollIntervalMs);
+ }
+ } catch (Exception e) {
+ if (e instanceof InterruptedException) {
+ throw (InterruptedException) e;
+ }
+ metricSource.incrementCdcBatchFailureCount(dataTableName);
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.error(
+ "Error processing CDC mutations for table {} region {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ dataTableName, encodedRegionName, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ } catch (InterruptedException e) {
+ if (!stopped) {
+ LOG.warn("IndexCDCConsumer interrupted unexpectedly for table {} region {}", dataTableName,
+ encodedRegionName, e);
+ }
+ Thread.currentThread().interrupt();
+ }
+ LOG.info("IndexCDCConsumer exiting for table {} region {}", dataTableName, encodedRegionName);
+ }
+
+ private boolean hasEventuallyConsistentIndexes() throws InterruptedException {
+ int retryCount = 0;
+ while (!stopped) {
+ try (PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(config).unwrap(PhoenixConnection.class)) {
+ refreshDataTableCache(conn);
+ PTable dataTable = getDataTable(conn);
+ String cdcObjectName = CDCUtil.getCDCObjectName(dataTable, false);
+ if (cdcObjectName == null) {
+ LOG.debug("No CDC index found for table {}. Exiting consumer.", dataTableName);
+ return false;
+ }
+ for (PTable index : dataTable.getIndexes()) {
+ IndexConsistency consistency = index.getIndexConsistency();
+ if (consistency != null && consistency.isAsynchronous()) {
+ LOG.debug("Found eventually consistent index {} for table {}",
+ index.getName().getString(), dataTableName);
+ return true;
+ }
+ }
+ return false;
+ } catch (SQLException e) {
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error checking for eventually consistent indexes for table {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ dataTableName, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves the count of partitions for the given table.
+ * @return the count of CDC_STREAM rows for this table.
+ * @throws InterruptedException if the thread is interrupted while waiting.
+ */
+ private long getCDCStreamNumPartitions() throws InterruptedException {
+ int retryCount = 0;
+ String query = "SELECT COUNT(*) FROM " + PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_NAME
+ + " WHERE TABLE_NAME = ?";
+ while (!stopped) {
+ try (
+ PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(config).unwrap(PhoenixConnection.class);
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, dataTableName);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ return rs.getLong(1);
+ }
+ }
+ return 0;
+ } catch (SQLException e) {
+ if (e instanceof TableNotFoundException) {
+ TableNotFoundException tnfe = (TableNotFoundException) e;
+ // 5.3.0+ server with old metadata tables (i.e. EXECUTE_UPGRADE is not yet run)
+ if (PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_TABLE.equals(tnfe.getTableName())) {
+ stopped = true;
+ return -1;
+ }
+ }
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error getting CDC_STREAM row count for table {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ dataTableName, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Waits for the CDC_STREAM entry for the given table and partition to be available.
+ * @return true if the entry was found, false if the consumer was stopped before finding the
+ * entry.
+ * @throws InterruptedException if the thread is interrupted while waiting.
+ */
+ private boolean waitForCDCStreamEntry() throws InterruptedException {
+ int retryCount = 0;
+ String query =
+ "SELECT PARENT_PARTITION_ID FROM " + PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_NAME
+ + " WHERE TABLE_NAME = ? AND PARTITION_ID = ? LIMIT 1";
+ while (!stopped) {
+ try (
+ PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(env.getConfiguration()).unwrap(PhoenixConnection.class);
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, dataTableName);
+ ps.setString(2, encodedRegionName);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ String parentPartitionId = rs.getString(1);
+ hasParentPartitions = parentPartitionId != null;
+ LOG.debug("Found CDC_STREAM entry for table {} partition {}, hasParentPartitions={}",
+ dataTableName, encodedRegionName, hasParentPartitions);
+ return true;
+ }
+ }
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.info(
+ "CDC_STREAM entry not found for table {} partition {}. "
+ + "Attempt #{}, sleeping {} ms before retrying...",
+ dataTableName, encodedRegionName, retryCount, sleepTime);
+ sleepIfNotStopped(sleepTime);
+ } catch (SQLException e) {
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error checking CDC_STREAM for table {} partition {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ dataTableName, encodedRegionName, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks for an existing entry for the given partition and owner.
+ * @param partitionId the partition ID to check.
+ * @param ownerPartitionId the owner partition ID.
+ * @return the last processed timestamp if an entry exists with status IN_PROGRESS, -1 if the
+ * entry exists with status COMPLETE, or 0 if no entry exists.
+ * @throws InterruptedException if the thread is interrupted while waiting
+ */
+ private long checkTrackerStatus(String partitionId, String ownerPartitionId)
+ throws InterruptedException {
+ int retryCount = 0;
+ String query =
+ "SELECT LAST_TIMESTAMP, STATUS FROM " + PhoenixDatabaseMetaData.SYSTEM_IDX_CDC_TRACKER_NAME
+ + " WHERE TABLE_NAME = ? AND PARTITION_ID = ? AND OWNER_PARTITION_ID = ?";
+ while (!stopped) {
+ try (
+ PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(env.getConfiguration()).unwrap(PhoenixConnection.class);
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, dataTableName);
+ ps.setString(2, partitionId);
+ ps.setString(3, ownerPartitionId);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ long lastTimestamp = rs.getLong(1);
+ String status = rs.getString(2);
+ LOG.debug(
+ "Found IDX_CDC_TRACKER entry for table {} partition {} owner {} "
+ + "with status={}, lastTimestamp={}",
+ dataTableName, partitionId, ownerPartitionId, status, lastTimestamp);
+ if (PhoenixDatabaseMetaData.TRACKER_STATUS_IN_PROGRESS.equals(status)) {
+ return lastTimestamp;
+ } else if (PhoenixDatabaseMetaData.TRACKER_STATUS_COMPLETE.equals(status)) {
+ return -1;
+ } else {
+ // Unknown status - should not happen
+ throw new IllegalStateException(
+ String.format("Unknown tracker status '%s' for table %s partition %s owner %s.",
+ status, dataTableName, partitionId, ownerPartitionId));
+ }
+ } else {
+ LOG.debug("No IDX_CDC_TRACKER entry found for table {} partition {} owner {}",
+ dataTableName, partitionId, ownerPartitionId);
+ return 0;
+ }
+ }
+ } catch (SQLException e) {
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error checking IDX_CDC_TRACKER for table {} partition {} owner {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ dataTableName, partitionId, ownerPartitionId, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Checks if any consumer has already completed processing the given partition.
+ * @param partitionId the partition ID to check
+ * @return true if the partition is done processing.
+ * @throws InterruptedException if the thread is interrupted while waiting.
+ */
+ private boolean isPartitionCompleted(String partitionId) throws InterruptedException {
+ int retryCount = 0;
+ String query = "SELECT * FROM " + PhoenixDatabaseMetaData.SYSTEM_IDX_CDC_TRACKER_NAME
+ + " WHERE TABLE_NAME = ? AND PARTITION_ID = ? AND STATUS = ?";
+ while (!stopped) {
+ try (
+ PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(env.getConfiguration()).unwrap(PhoenixConnection.class);
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, dataTableName);
+ ps.setString(2, partitionId);
+ ps.setString(3, PhoenixDatabaseMetaData.TRACKER_STATUS_COMPLETE);
+ try (ResultSet rs = ps.executeQuery()) {
+ return rs.next();
+ }
+ } catch (SQLException e) {
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error checking if partition {} is completed for table {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ partitionId, dataTableName, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the maximum last processed timestamp from any consumer's tracker record for the given
+ * partition. This is used when a child region needs to continue processing from where any
+ * previous consumer left off.
+ * @param partitionId the partition ID to get progress for.
+ * @return the max last processed timestamp across all owners, or 0 if not found.
+ * @throws InterruptedException if the thread is interrupted while waiting.
+ */
+ private long getParentProgress(String partitionId) throws InterruptedException {
+ int retryCount = 0;
+ String query =
+ "SELECT MAX(LAST_TIMESTAMP) FROM " + PhoenixDatabaseMetaData.SYSTEM_IDX_CDC_TRACKER_NAME
+ + " WHERE TABLE_NAME = ? AND PARTITION_ID = ? AND STATUS = ?";
+ while (!stopped) {
+ try (
+ PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(env.getConfiguration()).unwrap(PhoenixConnection.class);
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, dataTableName);
+ ps.setString(2, partitionId);
+ ps.setString(3, PhoenixDatabaseMetaData.TRACKER_STATUS_IN_PROGRESS);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ long maxTimestamp = rs.getLong(1);
+ if (maxTimestamp > 0) {
+ LOG.debug("Found max progress {} for partition {} from previous consumers",
+ maxTimestamp, partitionId);
+ return maxTimestamp;
+ }
+ }
+ return 0;
+ }
+ } catch (SQLException e) {
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error getting parent progress for partition {} table {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ partitionId, dataTableName, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ throw new InterruptedException("IndexCDCConsumer stopped while getting parent progress.");
+ }
+
+ /**
+ * Retrieves all parent partition IDs for the given partition.
+ * @param partitionId the partition ID to find parents for.
+ * @return list of parent partition IDs.
+ * @throws InterruptedException if the thread is interrupted.
+ */
+ private List<String> getParentPartitionIds(String partitionId) throws InterruptedException {
+ int retryCount = 0;
+ String query =
+ "SELECT PARENT_PARTITION_ID FROM " + PhoenixDatabaseMetaData.SYSTEM_CDC_STREAM_NAME
+ + " WHERE TABLE_NAME = ? AND PARTITION_ID = ? AND PARENT_PARTITION_ID IS NOT NULL";
+ while (!stopped) {
+ try (
+ PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(env.getConfiguration()).unwrap(PhoenixConnection.class);
+ PreparedStatement ps = conn.prepareStatement(query)) {
+ ps.setString(1, dataTableName);
+ ps.setString(2, partitionId);
+ List<String> parentIds = new ArrayList<>();
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ parentIds.add(rs.getString(1));
+ }
+ }
+ LOG.debug("Found {} parent partition(s) for table {} partition {}: {}", parentIds.size(),
+ dataTableName, partitionId, parentIds);
+ return parentIds;
+ } catch (SQLException e) {
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error querying parent partitions from CDC_STREAM for table {} partition {}. "
+ + "Retry #{}, sleeping {} ms before retrying...",
+ dataTableName, partitionId, retryCount, sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Processes all remaining CDC mutations for the given partition until completion, then marks the
+ * partition as COMPLETE. Used to replay ancestor partitions (regions) after they are split or
+ * merged into new regions.
+ * @param partitionId the partition ID to process.
+ * @param ownerPartitionId the owner partition ID.
+ * @param lastProcessedTimestamp the timestamp to start processing from.
+ * @throws InterruptedException if the thread is interrupted.
+ */
+ private void processPartitionToCompletion(String partitionId, String ownerPartitionId,
+ long lastProcessedTimestamp) throws InterruptedException {
+ LOG.debug(
+ "Processing partition {} owner {} to completion for table {}, starting from timestamp {}",
+ partitionId, ownerPartitionId, dataTableName, lastProcessedTimestamp);
+ long currentLastProcessedTimestamp = lastProcessedTimestamp;
+ int retryCount = 0;
+ int batchCount = 0;
+ while (!stopped) {
+ try {
+ if (batchCount > 0) {
+ if (isPartitionCompleted(partitionId)) {
+ return;
+ }
+ long otherProgress = getParentProgress(partitionId);
+ if (otherProgress > currentLastProcessedTimestamp) {
+ long previousOtherProgress;
+ do {
+ previousOtherProgress = otherProgress;
+ sleepIfNotStopped(parentProgressPauseMs);
+ if (isPartitionCompleted(partitionId)) {
+ return;
+ }
+ otherProgress = getParentProgress(partitionId);
+ } while (!stopped && otherProgress > previousOtherProgress);
+ currentLastProcessedTimestamp = otherProgress;
+ }
+ }
+ long newTimestamp;
+ if (serializeCDCMutations) {
+ newTimestamp =
+ processCDCBatch(partitionId, ownerPartitionId, currentLastProcessedTimestamp, true);
+ } else {
+ newTimestamp = processCDCBatchGenerated(partitionId, ownerPartitionId,
+ currentLastProcessedTimestamp, true);
+ }
+ batchCount++;
+ retryCount = 0;
+ if (newTimestamp == currentLastProcessedTimestamp) {
+ if (isPartitionCompleted(partitionId)) {
+ LOG.info(
+ "Partition {} for table {} was completed by another consumer before {} could mark it",
+ partitionId, dataTableName, ownerPartitionId);
+ return;
+ }
+ LOG.info("Partition {} owner {} for table {} fully processed, marking as COMPLETE",
+ partitionId, ownerPartitionId, dataTableName);
+ try (PhoenixConnection conn = QueryUtil.getConnectionOnServer(env.getConfiguration())
+ .unwrap(PhoenixConnection.class)) {
+ updateTrackerProgress(conn, partitionId, ownerPartitionId,
+ currentLastProcessedTimestamp, PhoenixDatabaseMetaData.TRACKER_STATUS_COMPLETE);
+ }
+ return;
+ }
+ currentLastProcessedTimestamp = newTimestamp;
+ } catch (SQLException | IOException e) {
+ metricSource.incrementCdcBatchFailureCount(dataTableName);
+ long sleepTime = ConnectionUtils.getPauseTime(pause, ++retryCount);
+ LOG.warn(
+ "Error processing CDC batch for partition {} owner {} table {} "
+ + "lastProcessedTimestamp {}. Retry #{}, sleeping {} ms",
+ partitionId, ownerPartitionId, dataTableName, currentLastProcessedTimestamp, retryCount,
+ sleepTime, e);
+ sleepIfNotStopped(sleepTime);
+ }
+ }
+ LOG.info("Processing partition {} (owner {}) stopped before completion for table {}",
+ partitionId, ownerPartitionId, dataTableName);
+ }
+
+ /**
+ * Recursive helper method to replay and complete parent regions. Replays all remaining mutations
+ * from parent regions and marks them as COMPLETE. This is called during initialization when a
+ * region has parent regions (split/merge scenarios).
+ * @param partitionId the partition (region) ID to find and process parents for.
+ */
+ private void replayAndCompleteParentRegions(String partitionId) throws InterruptedException {
+ List<String> parentIds = getParentPartitionIds(partitionId);
+ if (parentIds.isEmpty()) {
+ LOG.debug("No parent partitions found for table {} partition {}", dataTableName, partitionId);
+ return;
+ }
+ for (String parentId : parentIds) {
+ if (stopped) {
+ return;
+ }
+ if (isPartitionCompleted(parentId)) {
+ LOG.debug("Parent partition {} for table {} already has a COMPLETE record, skipping",
+ parentId, dataTableName);
+ continue;
+ }
+ replayAndCompleteParentRegions(parentId);
+ if (isPartitionCompleted(parentId)) {
+ LOG.debug(
+ "Parent partition {} for table {} was completed by sibling during ancestor processing, "
+ + "skipping",
+ parentId, dataTableName);
+ continue;
+ }
+ long parentProgress = getParentProgress(parentId);
+ LOG.debug("Processing/Resuming parent partition {} for table {} from timestamp {} owner: {}",
+ parentId, dataTableName, parentProgress, encodedRegionName);
+ processPartitionToCompletion(parentId, encodedRegionName, parentProgress);
+ }
+ }
+
+ /**
+ * Processes a batch of CDC mutations for the given partition starting from the specified
+ * timestamp. This method reads mutations from the CDC index, applies them to the respective
+ * eventually consistent index tables, and updates the progress in SYSTEM.IDX_CDC_TRACKER.
+ * @param partitionId the partition (region) ID to process CDC mutations for.
+ * @param ownerPartitionId the owner partition ID.
+ * @param lastProcessedTimestamp the timestamp to start reading CDC mutations from.
+ * @param isParentReplay true if replaying a closed parent partition.
+ * @return the new last processed timestamp after this batch, or the same timestamp if no new
+ * records were found.
+ * @throws SQLException if SQL error occurs.
+ * @throws IOException if an I/O error occurs.
+ * @throws InterruptedException if the thread is interrupted while waiting.
+ */
+ private long processCDCBatch(String partitionId, String ownerPartitionId,
+ long lastProcessedTimestamp, boolean isParentReplay)
+ throws SQLException, IOException, InterruptedException {
+ long batchStartTime = EnvironmentEdgeManager.currentTimeMillis();
+ LOG.debug("Processing CDC batch for table {} partition {} owner {} from timestamp {}",
+ dataTableName, partitionId, ownerPartitionId, lastProcessedTimestamp);
+ try (PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(config).unwrap(PhoenixConnection.class)) {
+ initTenantInfo(conn);
+ String cdcObjectName = getCdcObjectName(conn);
+ TenantScanInfo scanInfo = getPartitionTenantScanInfo(partitionId);
+ String cdcQuery;
+ if (isParentReplay) {
+ cdcQuery = String.format(
+ "SELECT /*+ CDC_INCLUDE(IDX_MUTATIONS) */ PHOENIX_ROW_TIMESTAMP(), \"CDC JSON\" "
+ + "FROM %s WHERE %s PARTITION_ID() = ? AND PHOENIX_ROW_TIMESTAMP() > ? "
+ + "ORDER BY %s PARTITION_ID() ASC, PHOENIX_ROW_TIMESTAMP() ASC LIMIT ?",
+ cdcObjectName, scanInfo.filter, scanInfo.orderBy);
+ } else {
+ cdcQuery = String.format(
+ "SELECT /*+ CDC_INCLUDE(IDX_MUTATIONS) */ PHOENIX_ROW_TIMESTAMP(), \"CDC JSON\" "
+ + "FROM %s WHERE %s PARTITION_ID() = ? AND PHOENIX_ROW_TIMESTAMP() > ? "
+ + "AND PHOENIX_ROW_TIMESTAMP() < ? "
+ + "ORDER BY %s PARTITION_ID() ASC, PHOENIX_ROW_TIMESTAMP() ASC LIMIT ?",
+ cdcObjectName, scanInfo.filter, scanInfo.orderBy);
+ }
+ List<Pair<Long, IndexMutationsProtos.IndexMutations>> batchMutations = new ArrayList<>();
+ long newLastTimestamp = lastProcessedTimestamp;
+ boolean hasMoreRows = true;
+ int retryCount = 0;
+ while (hasMoreRows && batchMutations.isEmpty()) {
+ try (PreparedStatement ps = conn.prepareStatement(cdcQuery)) {
+ setStatementParams(scanInfo, partitionId, isParentReplay, newLastTimestamp, ps);
+ Pair<Long, Boolean> result =
+ getMutationsAndTimestamp(ps, newLastTimestamp, batchMutations);
+ hasMoreRows = result.getSecond();
+ if (hasMoreRows) {
+ newLastTimestamp = result.getFirst();
+ if (batchMutations.isEmpty()) {
+ sleepIfNotStopped(ConnectionUtils.getPauseTime(pause, ++retryCount));
+ }
+ }
+ }
+ }
+ // With predefined LIMIT, there might be more rows with the same timestamp that were not
+ // included in this batch.
+ if (newLastTimestamp > lastProcessedTimestamp) {
+ String sameTimestampQuery = String.format(
+ "SELECT /*+ CDC_INCLUDE(IDX_MUTATIONS) */ PHOENIX_ROW_TIMESTAMP(), \"CDC JSON\" "
+ + "FROM %s WHERE %s PARTITION_ID() = ? AND PHOENIX_ROW_TIMESTAMP() = ? "
+ + "ORDER BY %s PARTITION_ID() ASC, PHOENIX_ROW_TIMESTAMP() ASC",
+ cdcObjectName, scanInfo.filter, scanInfo.orderBy);
+ final long timestampToRefetch = newLastTimestamp;
+ batchMutations.removeIf(pair -> pair.getFirst() == timestampToRefetch);
+ try (PreparedStatement ps = conn.prepareStatement(sameTimestampQuery)) {
+ int idx = scanInfo.bindParams(ps, 1);
+ ps.setString(idx++, partitionId);
+ ps.setDate(idx, new Date(newLastTimestamp));
+ Pair<Long, Boolean> result =
+ getMutationsAndTimestamp(ps, newLastTimestamp, batchMutations);
+ newLastTimestamp = result.getFirst();
+ if (newLastTimestamp != timestampToRefetch) {
+ throw new IOException("Unexpected timestamp mismatch: expected " + timestampToRefetch
+ + " but got " + newLastTimestamp);
+ }
+ }
+ }
+ executeIndexMutations(partitionId, batchMutations, ownerPartitionId, newLastTimestamp);
+ if (!batchMutations.isEmpty()) {
+ metricSource.updateCdcBatchProcessTime(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - batchStartTime);
+ metricSource.incrementCdcBatchCount(dataTableName);
+ metricSource.updateCdcLag(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - newLastTimestamp);
+ updateTrackerProgress(conn, partitionId, ownerPartitionId, newLastTimestamp,
+ PhoenixDatabaseMetaData.TRACKER_STATUS_IN_PROGRESS);
+ }
+ return newLastTimestamp;
+ }
+ }
+
+ private String getCdcObjectName(PhoenixConnection conn) throws SQLException {
+ PTable dataTable = getDataTable(conn);
+ String cdcObjectName = CDCUtil.getCDCObjectName(dataTable, false);
+ if (cdcObjectName == null) {
+ throw new SQLException("No CDC object found for table " + dataTableName);
+ }
+ return SchemaUtil.getEscapedTableName(dataTable.getSchemaName().getString(), cdcObjectName);
+ }
+
+ /**
+ * Processes a batch of CDC events for the given partition starting from the specified timestamp
+ * by generating index mutations from data row states. This method queries the CDC index with the
+ * DATA_ROW_STATE scope, which triggers a server-side data table scan to reconstruct the
+ * before-image ({@code currentDataRowState}) and after-image ({@code nextDataRowState}) for each
+ * change.
+ * @param partitionId the partition (region) ID to process CDC events for.
+ * @param ownerPartitionId the owner partition ID.
+ * @param lastProcessedTimestamp the timestamp to start processing CDC events from.
+ * @param isParentReplay true if replaying a closed parent partition.
+ * @return the new last processed timestamp after this batch, or the same timestamp if no new
+ * records were found.
+ * @throws SQLException if a SQL error occurs.
+ * @throws IOException if an I/O error occurs.
+ * @throws InterruptedException if the thread is interrupted while waiting.
+ */
+ private long processCDCBatchGenerated(String partitionId, String ownerPartitionId,
+ long lastProcessedTimestamp, boolean isParentReplay)
+ throws SQLException, IOException, InterruptedException {
+ long batchStartTime = EnvironmentEdgeManager.currentTimeMillis();
+ LOG.debug(
+ "Processing CDC batch (generated mode) for table {} partition {} owner {} from timestamp {}",
+ dataTableName, partitionId, ownerPartitionId, lastProcessedTimestamp);
+ try (PhoenixConnection conn =
+ QueryUtil.getConnectionOnServer(config).unwrap(PhoenixConnection.class)) {
+ initTenantInfo(conn);
+ String cdcObjectName = getCdcObjectName(conn);
+ TenantScanInfo scanInfo = getPartitionTenantScanInfo(partitionId);
+ String cdcQuery;
+ if (isParentReplay) {
+ cdcQuery = String.format(
+ "SELECT /*+ CDC_INCLUDE(DATA_ROW_STATE) */ PHOENIX_ROW_TIMESTAMP(), \"CDC JSON\" "
+ + "FROM %s WHERE %s PARTITION_ID() = ? AND PHOENIX_ROW_TIMESTAMP() > ? "
+ + "ORDER BY %s PARTITION_ID() ASC, PHOENIX_ROW_TIMESTAMP() ASC LIMIT ?",
+ cdcObjectName, scanInfo.filter, scanInfo.orderBy);
+ } else {
+ cdcQuery = String.format(
+ "SELECT /*+ CDC_INCLUDE(DATA_ROW_STATE) */ PHOENIX_ROW_TIMESTAMP(), \"CDC JSON\" "
+ + "FROM %s WHERE %s PARTITION_ID() = ? AND PHOENIX_ROW_TIMESTAMP() > ? "
+ + "AND PHOENIX_ROW_TIMESTAMP() < ? "
+ + "ORDER BY %s PARTITION_ID() ASC, PHOENIX_ROW_TIMESTAMP() ASC LIMIT ?",
+ cdcObjectName, scanInfo.filter, scanInfo.orderBy);
+ }
+
+ List<Pair<Long, IndexMutationsProtos.DataRowStates>> batchStates = new ArrayList<>();
+ long newLastTimestamp = lastProcessedTimestamp;
+ long[] lastScannedTimestamp = { lastProcessedTimestamp };
+ boolean hasMoreRows = true;
+ int retryCount = 0;
+ while (hasMoreRows && batchStates.isEmpty()) {
+ try (PreparedStatement ps = conn.prepareStatement(cdcQuery)) {
+ setStatementParams(scanInfo, partitionId, isParentReplay, newLastTimestamp, ps);
+ Pair<Long, Boolean> result =
+ getDataRowStatesAndTimestamp(ps, newLastTimestamp, batchStates, lastScannedTimestamp);
+ hasMoreRows = result.getSecond();
+ if (hasMoreRows) {
+ if (!batchStates.isEmpty()) {
+ newLastTimestamp = result.getFirst();
+ } else if (retryCount >= maxDataVisibilityRetries) {
+ LOG.warn(
+ "Skipping CDC events for table {} partition {} from timestamp {}"
+ + " to {} after {} retries — data table mutations may have failed",
+ dataTableName, partitionId, newLastTimestamp, lastScannedTimestamp[0], retryCount);
+ newLastTimestamp = lastScannedTimestamp[0];
+ break;
+ } else {
+ // CDC index entries are written but the data is not yet visible.
+ // Don't advance newLastTimestamp so the same events are re-fetched
+ // once the data becomes visible.
+ sleepIfNotStopped(ConnectionUtils.getPauseTime(pause, ++retryCount));
+ }
+ }
+ }
+ }
+ if (newLastTimestamp > lastProcessedTimestamp) {
+ String sameTimestampQuery = String.format(
+ "SELECT /*+ CDC_INCLUDE(DATA_ROW_STATE) */ PHOENIX_ROW_TIMESTAMP(), \"CDC JSON\" "
+ + "FROM %s WHERE %s PARTITION_ID() = ? AND PHOENIX_ROW_TIMESTAMP() = ? "
+ + "ORDER BY %s PARTITION_ID() ASC, PHOENIX_ROW_TIMESTAMP() ASC",
+ cdcObjectName, scanInfo.filter, scanInfo.orderBy);
+ final long timestampToRefetch = newLastTimestamp;
+ batchStates.removeIf(pair -> pair.getFirst() == timestampToRefetch);
+ try (PreparedStatement ps = conn.prepareStatement(sameTimestampQuery)) {
+ int idx = scanInfo.bindParams(ps, 1);
+ ps.setString(idx++, partitionId);
+ ps.setDate(idx, new Date(newLastTimestamp));
+ Pair<Long, Boolean> result =
+ getDataRowStatesAndTimestamp(ps, newLastTimestamp, batchStates, lastScannedTimestamp);
+ newLastTimestamp = result.getFirst();
+ if (batchStates.isEmpty()) {
+ newLastTimestamp = timestampToRefetch;
+ } else if (newLastTimestamp != timestampToRefetch) {
+ throw new IOException("Unexpected timestamp mismatch: expected " + timestampToRefetch
+ + " but got " + newLastTimestamp);
+ }
+ }
+ }
+ generateAndApplyIndexMutations(conn, batchStates, partitionId, ownerPartitionId,
+ newLastTimestamp);
+ if (!batchStates.isEmpty()) {
+ metricSource.updateCdcBatchProcessTime(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - batchStartTime);
+ metricSource.incrementCdcBatchCount(dataTableName);
+ metricSource.updateCdcLag(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - newLastTimestamp);
+ }
+ if (newLastTimestamp > lastProcessedTimestamp) {
+ updateTrackerProgress(conn, partitionId, ownerPartitionId, newLastTimestamp,
+ PhoenixDatabaseMetaData.TRACKER_STATUS_IN_PROGRESS);
+ }
+ return newLastTimestamp;
+ }
+ }
+
+ private void setStatementParams(TenantScanInfo scanInfo, String partitionId,
+ boolean isParentReplay, long newLastTimestamp, PreparedStatement ps) throws SQLException {
+ int idx = scanInfo.bindParams(ps, 1);
+ ps.setString(idx++, partitionId);
+ ps.setDate(idx++, new Date(newLastTimestamp));
+ if (isParentReplay) {
+ ps.setInt(idx, batchSize);
+ } else {
+ long currentTime = EnvironmentEdgeManager.currentTimeMillis() - timestampBufferMs;
+ ps.setDate(idx++, new Date(currentTime));
+ ps.setInt(idx, batchSize);
+ }
+ }
+
+ private static Pair<Long, Boolean> getDataRowStatesAndTimestamp(PreparedStatement ps,
+ long initialLastTimestamp, List<Pair<Long, IndexMutationsProtos.DataRowStates>> batchStates,
+ long[] lastScannedTimestamp) throws SQLException, IOException {
+ boolean hasRows = false;
+ long lastTimestamp = initialLastTimestamp;
+ lastScannedTimestamp[0] = initialLastTimestamp;
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ hasRows = true;
+ long rowTimestamp = rs.getDate(1).getTime();
+ lastScannedTimestamp[0] = rowTimestamp;
+ String cdcValue = rs.getString(2);
+ if (cdcValue != null && !cdcValue.isEmpty()) {
+ byte[] protoBytes = Base64.getDecoder().decode(cdcValue);
+ IndexMutationsProtos.DataRowStates dataRowStates =
+ IndexMutationsProtos.DataRowStates.parseFrom(protoBytes);
+ if (
+ dataRowStates.hasDataRowKey()
+ && (dataRowStates.hasCurrentDataRowState() || dataRowStates.hasNextDataRowState())
+ ) {
+ batchStates.add(Pair.newPair(rowTimestamp, dataRowStates));
+ lastTimestamp = rowTimestamp;
+ }
+ }
+ }
+ }
+ return Pair.newPair(lastTimestamp, hasRows);
+ }
+
+ private void generateAndApplyIndexMutations(PhoenixConnection conn,
+ List<Pair<Long, IndexMutationsProtos.DataRowStates>> batchStates, String partitionId,
+ String ownerPartitionId, long lastProcessedTimestamp) throws SQLException, IOException {
+ if (batchStates.isEmpty()) {
+ return;
+ }
+ refreshDataTableCache(conn);
+ PTable dataTable = getDataTable(conn);
+ byte[] encodedRegionNameBytes = env.getRegion().getRegionInfo().getEncodedNameAsBytes();
+ List<Pair<IndexMaintainer, HTableInterfaceReference>> indexTables = new ArrayList<>();
+ for (PTable index : dataTable.getIndexes()) {
+ IndexConsistency consistency = index.getIndexConsistency();
+ if (consistency != null && consistency.isAsynchronous()) {
+ IndexMaintainer maintainer = index.getIndexMaintainer(dataTable, conn);
+ HTableInterfaceReference tableRef =
+ new HTableInterfaceReference(new ImmutableBytesPtr(maintainer.getIndexTableName()));
+ indexTables.add(new Pair<>(maintainer, tableRef));
+ }
+ }
+ if (indexTables.isEmpty()) {
+ return;
+ }
+ ListMultimap<HTableInterfaceReference, Mutation> indexUpdates = ArrayListMultimap.create();
+ int totalMutations = 0;
+ long generateStartTime = EnvironmentEdgeManager.currentTimeMillis();
+ for (Pair<Long, IndexMutationsProtos.DataRowStates> entry : batchStates) {
+ long ts = entry.getFirst();
+ IndexMutationsProtos.DataRowStates dataRowStates = entry.getSecond();
+ byte[] dataRowKey = dataRowStates.getDataRowKey().toByteArray();
+ ImmutableBytesPtr rowKeyPtr = new ImmutableBytesPtr(dataRowKey);
+
+ Put currentDataRowState = null;
+ if (dataRowStates.hasCurrentDataRowState()) {
+ ClientProtos.MutationProto currentProto = ClientProtos.MutationProto
+ .parseFrom(dataRowStates.getCurrentDataRowState().toByteArray());
+ Mutation currentMutation = ProtobufUtil.toMutation(currentProto);
+ if (currentMutation instanceof Put) {
+ currentDataRowState = (Put) currentMutation;
+ }
+ }
+ Put nextDataRowState = null;
+ if (dataRowStates.hasNextDataRowState()) {
+ ClientProtos.MutationProto nextProto =
+ ClientProtos.MutationProto.parseFrom(dataRowStates.getNextDataRowState().toByteArray());
+ Mutation nextMutation = ProtobufUtil.toMutation(nextProto);
+ if (nextMutation instanceof Put) {
+ nextDataRowState = (Put) nextMutation;
+ }
+ }
+ if (currentDataRowState == null && nextDataRowState == null) {
+ continue;
+ }
+ IndexRegionObserver.generateIndexMutationsForRow(rowKeyPtr, currentDataRowState,
+ nextDataRowState, ts, encodedRegionNameBytes, QueryConstants.VERIFIED_BYTES, indexTables,
+ indexUpdates);
+ if (indexUpdates.size() >= batchSize) {
+ metricSource.updateCdcMutationGenerateTime(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - generateStartTime);
+ long applyStartTime = EnvironmentEdgeManager.currentTimeMillis();
+ indexWriter.write(indexUpdates, false, MetaDataProtocol.PHOENIX_VERSION);
+ metricSource.updateCdcMutationApplyTime(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - applyStartTime);
+ totalMutations += indexUpdates.size();
+ indexUpdates.clear();
+ generateStartTime = EnvironmentEdgeManager.currentTimeMillis();
+ }
+ }
+ if (!indexUpdates.isEmpty()) {
+ metricSource.updateCdcMutationGenerateTime(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - generateStartTime);
+ long applyStartTime = EnvironmentEdgeManager.currentTimeMillis();
+ indexWriter.write(indexUpdates, false, MetaDataProtocol.PHOENIX_VERSION);
+ metricSource.updateCdcMutationApplyTime(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - applyStartTime);
+ totalMutations += indexUpdates.size();
+ }
+ if (totalMutations > 0) {
+ metricSource.incrementCdcMutationCount(dataTableName, totalMutations);
+ LOG.debug(
+ "Applied total {} index mutations for table {} partition {} owner {} "
+ + ", last processed timestamp {}",
+ totalMutations, dataTableName, partitionId, ownerPartitionId, lastProcessedTimestamp);
+ }
+ }
+
+ private void executeIndexMutations(String partitionId,
+ List<Pair<Long, IndexMutationsProtos.IndexMutations>> batchMutations, String ownerPartitionId,
+ long lastProcessedTimestamp) throws SQLException, IOException {
+ if (!batchMutations.isEmpty()) {
+ ListMultimap<HTableInterfaceReference, Mutation> indexUpdates = ArrayListMultimap.create();
+ Map<ImmutableBytesPtr, HTableInterfaceReference> tableRefCache = new HashMap<>();
+ int totalMutations = 0;
+ for (Pair<Long, IndexMutationsProtos.IndexMutations> batchMutation : batchMutations) {
+ IndexMutationsProtos.IndexMutations mutationsProto = batchMutation.getSecond();
+ List<ByteString> tables = mutationsProto.getTablesList();
+ List<ByteString> mutations = mutationsProto.getMutationsList();
+ if (tables.size() != mutations.size()) {
+ throw new SQLException("Tables and mutations sizes do not match. Tables size: "
+ + tables.size() + ", mutations size: " + mutations.size());
+ }
+ for (int i = 0; i < tables.size(); i++) {
+ byte[] indexTableName = tables.get(i).toByteArray();
+ byte[] mutationBytes = mutations.get(i).toByteArray();
+ ImmutableBytesPtr tableNamePtr = new ImmutableBytesPtr(indexTableName);
+ HTableInterfaceReference tableRef =
+ tableRefCache.computeIfAbsent(tableNamePtr, HTableInterfaceReference::new);
+ ClientProtos.MutationProto mProto = ClientProtos.MutationProto.parseFrom(mutationBytes);
+ Mutation mutation = ProtobufUtil.toMutation(mProto);
+ indexUpdates.put(tableRef, mutation);
+ }
+ if (indexUpdates.size() >= batchSize) {
+ long applyStartTime = EnvironmentEdgeManager.currentTimeMillis();
+ indexWriter.write(indexUpdates, false, MetaDataProtocol.PHOENIX_VERSION);
+ metricSource.updateCdcMutationApplyTime(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - applyStartTime);
+ totalMutations += indexUpdates.size();
+ indexUpdates.clear();
+ }
+ }
+ if (!indexUpdates.isEmpty()) {
+ long applyStartTime = EnvironmentEdgeManager.currentTimeMillis();
+ indexWriter.write(indexUpdates, false, MetaDataProtocol.PHOENIX_VERSION);
+ metricSource.updateCdcMutationApplyTime(dataTableName,
+ EnvironmentEdgeManager.currentTimeMillis() - applyStartTime);
+ totalMutations += indexUpdates.size();
+ }
+ if (totalMutations > 0) {
+ metricSource.incrementCdcMutationCount(dataTableName, totalMutations);
+ LOG.debug(
+ "Applied total {} index mutations for table {} partition {} owner {} "
+ + ", last processed timestamp {}",
+ totalMutations, dataTableName, partitionId, ownerPartitionId, lastProcessedTimestamp);
+ }
+ }
+ }
+
+ private void updateTrackerProgress(PhoenixConnection conn, String partitionId,
+ String ownerPartitionId, long lastTimestamp, String status) throws SQLException {
+ String upsertSql = "UPSERT INTO " + PhoenixDatabaseMetaData.SYSTEM_IDX_CDC_TRACKER_NAME
+ + " (TABLE_NAME, PARTITION_ID, OWNER_PARTITION_ID, LAST_TIMESTAMP, STATUS) "
+ + "VALUES (?, ?, ?, ?, ?)";
+ try (PreparedStatement ps = conn.prepareStatement(upsertSql)) {
+ ps.setString(1, dataTableName);
+ ps.setString(2, partitionId);
+ ps.setString(3, ownerPartitionId);
+ ps.setLong(4, lastTimestamp);
+ ps.setString(5, status);
+ ps.executeUpdate();
+ conn.commit();
+ LOG.debug("Updated tracker for table {} partition {} owner {} to timestamp {} with status {}",
+ dataTableName, partitionId, ownerPartitionId, lastTimestamp, status);
+ }
+ }
+
+ /**
+ * Executes the prepared statement and extracts mutations and timestamps from the result set.
+ * @param ps the prepared statement to execute.
+ * @param initialLastTimestamp the initial last timestamp to use if no rows are found.
+ * @param batchMutations list to add mutations to.
+ * @return Pair of last processed timestamp and boolean to indicate if any rows were returned.
+ */
+ private static Pair<Long, Boolean> getMutationsAndTimestamp(PreparedStatement ps,
+ long initialLastTimestamp, List<Pair<Long, IndexMutationsProtos.IndexMutations>> batchMutations)
+ throws SQLException, IOException {
+ boolean hasRows = false;
+ long lastTimestamp = initialLastTimestamp;
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ hasRows = true;
+ lastTimestamp = rs.getDate(1).getTime();
+ String cdcValue = rs.getString(2);
+ if (cdcValue != null && !cdcValue.isEmpty()) {
+ byte[] protoBytes = Base64.getDecoder().decode(cdcValue);
+ IndexMutationsProtos.IndexMutations mutationsProto =
+ IndexMutationsProtos.IndexMutations.parseFrom(protoBytes);
+ if (mutationsProto.getTablesCount() > 0 && mutationsProto.getMutationsCount() > 0) {
+ batchMutations.add(Pair.newPair(lastTimestamp, mutationsProto));
+ }
+ }
+ }
+ }
+ return Pair.newPair(lastTimestamp, hasRows);
+ }
+
+}
diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexRegionObserver.java b/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexRegionObserver.java
index fc55b2435d..9763388eff 100644
--- a/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexRegionObserver.java
+++ b/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/IndexRegionObserver.java
@@ -27,6 +27,7 @@ import static org.apache.phoenix.index.PhoenixIndexBuilderHelper.ATOMIC_OP_ATTRI
import static org.apache.phoenix.index.PhoenixIndexBuilderHelper.RETURN_RESULT;
import static org.apache.phoenix.util.ByteUtil.EMPTY_BYTE_ARRAY;
+import com.google.protobuf.ByteString;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
@@ -84,6 +85,7 @@ import org.apache.htrace.Trace;
import org.apache.htrace.TraceScope;
import org.apache.phoenix.compile.ScanRanges;
import org.apache.phoenix.coprocessor.DelegateRegionCoprocessorEnvironment;
+import org.apache.phoenix.coprocessor.generated.IndexMutationsProtos;
import org.apache.phoenix.coprocessor.generated.PTableProtos;
import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
import org.apache.phoenix.exception.DataExceedsCapacityException;
@@ -143,6 +145,7 @@ import org.apache.phoenix.util.ServerIndexUtil;
import org.apache.phoenix.util.ServerUtil.ConnectionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.xerial.snappy.Snappy;
import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions;
import org.apache.phoenix.thirdparty.com.google.common.collect.ArrayListMultimap;
@@ -151,6 +154,9 @@ import org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
import org.apache.phoenix.thirdparty.com.google.common.collect.Sets;
+import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
+
/**
* Do all the work of managing index updates from a single coprocessor. All Puts/Delets are passed
* to an {@link IndexBuilder} to determine the actual updates to make. We don't need to implement
@@ -166,6 +172,64 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
private static final OperationStatus NOWRITE = new OperationStatus(SUCCESS);
public static final String PHOENIX_APPEND_METADATA_TO_WAL = "phoenix.append.metadata.to.wal";
public static final boolean DEFAULT_PHOENIX_APPEND_METADATA_TO_WAL = false;
+ public static final String PHOENIX_INDEX_CDC_CONSUMER_ENABLED =
+ "phoenix.index.cdc.consumer.enabled";
+ public static final boolean DEFAULT_PHOENIX_INDEX_CDC_CONSUMER_ENABLED = true;
+ public static final String PHOENIX_INDEX_CDC_MUTATIONS_COMPRESS_ENABLED =
+ "phoenix.index.cdc.mutations.compress.enabled";
+ public static final boolean DEFAULT_PHOENIX_INDEX_CDC_MUTATIONS_COMPRESS_ENABLED = false;
+ /**
+ * Controls which approach is used for implementing eventually consistent global secondary indexes
+ * via the {@link IndexCDCConsumer}.
+ * <p>
+ * <b>Approach 1: Serialized mutations (value = true)</b>
+ * </p>
+ * <p>
+ * During {@code preBatchMutate}, {@link IndexRegionObserver} generates index mutations for each
+ * data table mutation and serializes them into a Protobuf {@code IndexMutations} message. This
+ * serialized payload is written as a column value in the CDC index table row alongside the CDC
+ * event. The {@link IndexCDCConsumer} later reads these pre-computed mutations from the CDC
+ * index, deserializes them, and applies them directly to the index table(s). In this approach,
+ * the consumer does not need to understand index structure or re-derive mutations — it simply
+ * replays what was already computed on the write path. The trade-off is increased CDC index row
+ * size due to the serialized mutation payload, and additional write IO on the CDC index table.
+ * </p>
+ * <p>
+ * <b>Approach 2: Generated mutations from data row states (default, value = false)</b>
+ * </p>
+ * <p>
+ * During {@code preBatchMutate}, {@link IndexRegionObserver} writes only a lightweight CDC index
+ * entry without serialized index mutations. Instead, the CDC event is created with the
+ * {@code DATA_ROW_STATE} scope. When the {@link IndexCDCConsumer} processes these events, it
+ * reads the CDC index rows which trigger a server-side scan of the data table (via
+ * {@code CDCGlobalIndexRegionScanner}) to reconstruct the before-image
+ * ({@code currentDataRowState}) and after-image ({@code nextDataRowState}) of the data row at the
+ * change timestamp. These raw row states are returned as a Protobuf {@code DataRowStates}
+ * message. The consumer then feeds these states into {@code generateIndexMutationsForRow()} — the
+ * same core utility used by {@link IndexRegionObserver#prepareIndexMutations} on the write path —
+ * to derive index mutations at consume time. This approach keeps CDC index rows small, avoids
+ * additional write IO, and generates mutations based on the current index definition, but
+ * requires an additional data table read per CDC event and is sensitive to data visibility
+ * timing. Make sure max lookback age is long enough to retain before and after images of the row.
+ * </p>
+ * <p>
+ * <b>When to use which approach:</b>
+ * </p>
+ * <ul>
+ * <li>Use <b>Approach 2</b> (serialize = false, default) to minimize write IO: no serialized
+ * mutations are written to the CDC index, keeping CDC index rows small and write latency uniform.
+ * The trade-off is higher read IO at consume time — the consumer performs an additional data
+ * table point-lookup with a raw scan per CDC event to reconstruct row states.</li>
+ * <li>Use <b>Approach 1</b> (serialize = true) to minimize read IO: the consumer reads
+ * pre-computed mutations from the CDC index and applies them directly, with no data table scan
+ * required at consume time. The trade-off is higher write IO — serialized index mutations are
+ * written alongside each CDC index entry, increasing CDC index row size and write-path latency.
+ * Although CDC index is expected to have TTL same as the data table max lookback age.</li>
+ * </ul>
+ */
+ public static final String PHOENIX_INDEX_CDC_MUTATION_SERIALIZE =
+ "phoenix.index.cdc.mutation.serialize";
+ public static final boolean DEFAULT_PHOENIX_INDEX_CDC_MUTATION_SERIALIZE = false;
/**
* Class to represent pending data table rows
@@ -183,15 +247,16 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
this.rowKey = rowKey;
}
- public boolean add(BatchMutateContext context) {
+ public BatchMutateContext addAndGetPrevCtx(BatchMutateContext context) {
synchronized (this) {
if (usable) {
+ BatchMutateContext previousContext = lastContext;
count++;
lastContext = context;
- return true;
+ return previousContext;
}
}
- return false;
+ return null;
}
public void remove() {
@@ -208,9 +273,6 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
return count;
}
- public BatchMutateContext getLastContext() {
- return lastContext;
- }
}
private static boolean ignoreIndexRebuildForTesting = false;
@@ -274,6 +336,15 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
// The collection of candidate index mutations that will be applied after the data table
// mutations.
private ListMultimap<HTableInterfaceReference, Pair<Mutation, byte[]>> indexUpdates;
+ // Map of data table row key to IndexMutations bytes
+ // containing pre-index mutations for eventually consistent indexes
+ // (index mutations with UNVERIFIED Puts only, no Deletes)
+ private Map<ImmutableBytesPtr, byte[]> cdcPreMutationsBytes;
+ // Map of data table row key to IndexMutations bytes
+ // containing post-index mutations for eventually consistent indexes
+ // (index mutations with VERIFIED Puts for covered,
+ // no Put mutations for uncovered, and Deletes if needed)
+ private Map<ImmutableBytesPtr, byte[]> cdcPostMutationsBytes;
private List<RowLock> rowLocks =
Lists.newArrayListWithExpectedSize(QueryServicesOptions.DEFAULT_MUTATE_BATCH_SIZE);
// TreeSet to improve locking efficiency and avoid deadlock (PHOENIX-6871 and HBASE-17924)
@@ -410,10 +481,14 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
private int concurrentMutationWaitDuration;
private String dataTableName;
private boolean shouldWALAppend = DEFAULT_PHOENIX_APPEND_METADATA_TO_WAL;
+ private boolean indexCDCConsumerEnabled = DEFAULT_PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
+ private boolean compressCDCMutations = DEFAULT_PHOENIX_INDEX_CDC_MUTATIONS_COMPRESS_ENABLED;
+ private boolean serializeCDCMutations = DEFAULT_PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
private boolean isNamespaceEnabled = false;
private boolean useBloomFilter = false;
private long lastTimestamp = 0;
private List<Set<ImmutableBytesPtr>> batchesWithLastTimestamp = new ArrayList<>();
+ private IndexCDCConsumer indexCDCConsumer;
private static final int DEFAULT_ROWLOCK_WAIT_DURATION = 30000;
private static final int DEFAULT_CONCURRENT_MUTATION_WAIT_DURATION_IN_MS = 100;
private byte[] encodedRegionName;
@@ -465,12 +540,27 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
this.dataTableName = env.getRegionInfo().getTable().getNameAsString();
this.shouldWALAppend = env.getConfiguration().getBoolean(PHOENIX_APPEND_METADATA_TO_WAL,
DEFAULT_PHOENIX_APPEND_METADATA_TO_WAL);
+ this.indexCDCConsumerEnabled = env.getConfiguration()
+ .getBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, DEFAULT_PHOENIX_INDEX_CDC_CONSUMER_ENABLED);
+ this.compressCDCMutations =
+ env.getConfiguration().getBoolean(PHOENIX_INDEX_CDC_MUTATIONS_COMPRESS_ENABLED,
+ DEFAULT_PHOENIX_INDEX_CDC_MUTATIONS_COMPRESS_ENABLED);
+ this.serializeCDCMutations = env.getConfiguration().getBoolean(
+ PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, DEFAULT_PHOENIX_INDEX_CDC_MUTATION_SERIALIZE);
this.isNamespaceEnabled =
SchemaUtil.isNamespaceMappingEnabled(PTableType.INDEX, env.getConfiguration());
TableDescriptor tableDescriptor = env.getRegion().getTableDescriptor();
BloomType bloomFilterType = tableDescriptor.getColumnFamilies()[0].getBloomFilterType();
// when the table descriptor changes, the coproc is reloaded
this.useBloomFilter = bloomFilterType == BloomType.ROW;
+ if (
+ this.indexCDCConsumerEnabled && !this.dataTableName.startsWith("SYSTEM.")
+ && !this.dataTableName.startsWith("SYSTEM:")
+ ) {
+ this.indexCDCConsumer =
+ new IndexCDCConsumer(env, this.dataTableName, serverName, this.serializeCDCMutations);
+ this.indexCDCConsumer.start();
+ }
} catch (NoSuchMethodError ex) {
disabled = true;
LOG.error("Must be too early a version of HBase. Disabled coprocessor ", ex);
@@ -507,6 +597,9 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
this.builder.stop(msg);
this.preWriter.stop(msg);
this.postWriter.stop(msg);
+ if (this.indexCDCConsumer != null) {
+ this.indexCDCConsumer.stop();
+ }
}
/**
@@ -1082,8 +1175,8 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
keys.add(PVarbinary.INSTANCE.getKeyRange(rowKeyPtr.get(), SortOrder.ASC));
} else {
// There is a pending row for this row key. We need to retrieve the row from memory
- BatchMutateContext lastContext = existingPendingRow.getLastContext();
- if (existingPendingRow.add(context)) {
+ BatchMutateContext lastContext = existingPendingRow.addAndGetPrevCtx(context);
+ if (lastContext != null) {
BatchMutatePhase phase = lastContext.getCurrentPhase();
Preconditions.checkArgument(phase != BatchMutatePhase.POST,
"the phase of the last batch cannot be POST");
@@ -1169,6 +1262,76 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
IndexMaintainer.DeleteType.ALL_VERSIONS, ts);
}
+ public static void generateIndexMutationsForRow(ImmutableBytesPtr rowKeyPtr,
+ Put currentDataRowState, Put nextDataRowState, long ts, byte[] encodedRegionName,
+ byte[] emptyColumnValue, List<Pair<IndexMaintainer, HTableInterfaceReference>> indexTables,
+ ListMultimap<HTableInterfaceReference, Mutation> indexUpdates) throws IOException {
+ for (Pair<IndexMaintainer, HTableInterfaceReference> pair : indexTables) {
+ IndexMaintainer indexMaintainer = pair.getFirst();
+ HTableInterfaceReference hTableInterfaceReference = pair.getSecond();
+ if (
+ nextDataRowState != null && indexMaintainer.shouldPrepareIndexMutations(nextDataRowState)
+ ) {
+ ValueGetter nextDataRowVG = new IndexUtil.SimpleValueGetter(nextDataRowState);
+ Put indexPut = indexMaintainer.buildUpdateMutation(GenericKeyValueBuilder.INSTANCE,
+ nextDataRowVG, rowKeyPtr, ts, null, null, false, encodedRegionName);
+ if (indexPut == null) {
+ // No covered column. Just prepare an index row with the empty column
+ byte[] indexRowKey = indexMaintainer.buildRowKey(nextDataRowVG, rowKeyPtr, null, null, ts,
+ encodedRegionName);
+ indexPut = new Put(indexRowKey);
+ } else {
+ IndexUtil.removeEmptyColumn(indexPut,
+ indexMaintainer.getEmptyKeyValueFamily().copyBytesIfNecessary(),
+ indexMaintainer.getEmptyKeyValueQualifier());
+ }
+ byte[] finalEmptyColumnValue =
+ indexMaintainer.isUncovered() ? QueryConstants.UNVERIFIED_BYTES : emptyColumnValue;
+ indexPut.addColumn(indexMaintainer.getEmptyKeyValueFamily().copyBytesIfNecessary(),
+ indexMaintainer.getEmptyKeyValueQualifier(), ts, finalEmptyColumnValue);
+ indexUpdates.put(hTableInterfaceReference, indexPut);
+ if (!ignoreWritingDeleteColumnsToIndex) {
+ Delete deleteColumn = indexMaintainer.buildDeleteColumnMutation(indexPut, ts);
+ if (deleteColumn != null) {
+ indexUpdates.put(hTableInterfaceReference, deleteColumn);
+ }
+ }
+ // Delete the current index row if the new index key is different from the
+ // current one and the index is not a CDC index
+ if (currentDataRowState != null) {
+ ValueGetter currentDataRowVG = new IndexUtil.SimpleValueGetter(currentDataRowState);
+ byte[] indexRowKeyForCurrentDataRow = indexMaintainer.buildRowKey(currentDataRowVG,
+ rowKeyPtr, null, null, ts, encodedRegionName);
+ if (
+ !indexMaintainer.isCDCIndex()
+ && Bytes.compareTo(indexPut.getRow(), indexRowKeyForCurrentDataRow) != 0
+ ) {
+ Mutation del = indexMaintainer.buildRowDeleteMutation(indexRowKeyForCurrentDataRow,
+ IndexMaintainer.DeleteType.ALL_VERSIONS, ts);
+ indexUpdates.put(hTableInterfaceReference, del);
+ }
+ }
+ } else if (
+ currentDataRowState != null
+ && indexMaintainer.shouldPrepareIndexMutations(currentDataRowState)
+ ) {
+ if (indexMaintainer.isCDCIndex()) {
+ // CDC Index needs two a delete marker for referencing the data table
+ // delete mutation with the right index row key, that is, the index row key
+ // starting with ts
+ Put cdcDataRowState = new Put(currentDataRowState.getRow());
+ cdcDataRowState.addColumn(indexMaintainer.getDataEmptyKeyValueCF(),
+ indexMaintainer.getEmptyKeyValueQualifierForDataTable(), ts, ByteUtil.EMPTY_BYTE_ARRAY);
+ indexUpdates.put(hTableInterfaceReference, getDeleteIndexMutation(cdcDataRowState,
+ indexMaintainer, ts, rowKeyPtr, encodedRegionName));
+ } else {
+ indexUpdates.put(hTableInterfaceReference, getDeleteIndexMutation(currentDataRowState,
+ indexMaintainer, ts, rowKeyPtr, encodedRegionName));
+ }
+ }
+ }
+ }
+
/**
* Generate the index update for a data row from the mutation that are obtained by merging the
* previous data row state with the pending row mutation.
@@ -1181,6 +1344,12 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
if (indexMaintainer.isLocalIndex()) {
continue;
}
+ if (
+ !serializeCDCMutations && indexMaintainer.getIndexConsistency() != null
+ && indexMaintainer.getIndexConsistency().isAsynchronous()
+ ) {
+ continue;
+ }
HTableInterfaceReference hTableInterfaceReference =
new HTableInterfaceReference(new ImmutableBytesPtr(indexMaintainer.getIndexTableName()));
indexTables.add(new Pair<>(indexMaintainer, hTableInterfaceReference));
@@ -1193,73 +1362,12 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
if (currentDataRowState == null && nextDataRowState == null) {
continue;
}
- for (Pair<IndexMaintainer, HTableInterfaceReference> pair : indexTables) {
- IndexMaintainer indexMaintainer = pair.getFirst();
- HTableInterfaceReference hTableInterfaceReference = pair.getSecond();
- if (
- nextDataRowState != null && indexMaintainer.shouldPrepareIndexMutations(nextDataRowState)
- ) {
- ValueGetter nextDataRowVG = new IndexUtil.SimpleValueGetter(nextDataRowState);
- Put indexPut = indexMaintainer.buildUpdateMutation(GenericKeyValueBuilder.INSTANCE,
- nextDataRowVG, rowKeyPtr, ts, null, null, false, encodedRegionName);
- if (indexPut == null) {
- // No covered column. Just prepare an index row with the empty column
- byte[] indexRowKey = indexMaintainer.buildRowKey(nextDataRowVG, rowKeyPtr, null, null,
- ts, encodedRegionName);
- indexPut = new Put(indexRowKey);
- } else {
- IndexUtil.removeEmptyColumn(indexPut,
- indexMaintainer.getEmptyKeyValueFamily().copyBytesIfNecessary(),
- indexMaintainer.getEmptyKeyValueQualifier());
- }
- indexPut.addColumn(indexMaintainer.getEmptyKeyValueFamily().copyBytesIfNecessary(),
- indexMaintainer.getEmptyKeyValueQualifier(), ts, QueryConstants.UNVERIFIED_BYTES);
- context.indexUpdates.put(hTableInterfaceReference,
- new Pair<Mutation, byte[]>(indexPut, rowKeyPtr.get()));
- if (!ignoreWritingDeleteColumnsToIndex) {
- Delete deleteColumn = indexMaintainer.buildDeleteColumnMutation(indexPut, ts);
- if (deleteColumn != null) {
- context.indexUpdates.put(hTableInterfaceReference,
- new Pair<Mutation, byte[]>(deleteColumn, rowKeyPtr.get()));
- }
- }
- // Delete the current index row if the new index key is different from the
- // current one and the index is not a CDC index
- if (currentDataRowState != null) {
- ValueGetter currentDataRowVG = new IndexUtil.SimpleValueGetter(currentDataRowState);
- byte[] indexRowKeyForCurrentDataRow = indexMaintainer.buildRowKey(currentDataRowVG,
- rowKeyPtr, null, null, ts, encodedRegionName);
- if (
- !indexMaintainer.isCDCIndex()
- && Bytes.compareTo(indexPut.getRow(), indexRowKeyForCurrentDataRow) != 0
- ) {
- Mutation del = indexMaintainer.buildRowDeleteMutation(indexRowKeyForCurrentDataRow,
- IndexMaintainer.DeleteType.ALL_VERSIONS, ts);
- context.indexUpdates.put(hTableInterfaceReference,
- new Pair<Mutation, byte[]>(del, rowKeyPtr.get()));
- }
- }
- } else if (
- currentDataRowState != null
- && indexMaintainer.shouldPrepareIndexMutations(currentDataRowState)
- ) {
- if (indexMaintainer.isCDCIndex()) {
- // CDC Index needs two a delete marker for referencing the data table
- // delete mutation with the right index row key, that is, the index row key
- // starting with ts
- Put cdcDataRowState = new Put(currentDataRowState.getRow());
- cdcDataRowState.addColumn(indexMaintainer.getDataEmptyKeyValueCF(),
- indexMaintainer.getEmptyKeyValueQualifierForDataTable(), ts,
- ByteUtil.EMPTY_BYTE_ARRAY);
- context.indexUpdates.put(hTableInterfaceReference,
- new Pair<Mutation, byte[]>(getDeleteIndexMutation(cdcDataRowState, indexMaintainer,
- ts, rowKeyPtr, encodedRegionName), rowKeyPtr.get()));
- } else {
- context.indexUpdates.put(hTableInterfaceReference,
- new Pair<Mutation, byte[]>(getDeleteIndexMutation(currentDataRowState,
- indexMaintainer, ts, rowKeyPtr, encodedRegionName), rowKeyPtr.get()));
- }
- }
+ ListMultimap<HTableInterfaceReference, Mutation> idxUpdates = ArrayListMultimap.create();
+ generateIndexMutationsForRow(rowKeyPtr, currentDataRowState, nextDataRowState, ts,
+ encodedRegionName, QueryConstants.UNVERIFIED_BYTES, indexTables, idxUpdates);
+ for (Map.Entry<HTableInterfaceReference, Mutation> idxUpdate : idxUpdates.entries()) {
+ context.indexUpdates.put(idxUpdate.getKey(),
+ new Pair<>(idxUpdate.getValue(), rowKeyPtr.get()));
}
}
}
@@ -1287,9 +1395,20 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
ArrayListMultimap.<HTableInterfaceReference, Pair<Mutation, byte[]>> create();
prepareIndexMutations(context, maintainers, batchTimestamp);
+ if (serializeCDCMutations) {
+ prepareEventuallyConsistentIndexMutations(context, batchTimestamp, maintainers,
+ compressCDCMutations);
+ }
+
context.preIndexUpdates = ArrayListMultimap.<HTableInterfaceReference, Mutation> create();
int updateCount = 0;
for (IndexMaintainer indexMaintainer : maintainers) {
+ if (
+ indexMaintainer.getIndexConsistency() != null
+ && indexMaintainer.getIndexConsistency().isAsynchronous()
+ ) {
+ continue;
+ }
updateCount++;
byte[] emptyCF = indexMaintainer.getEmptyKeyValueFamily().copyBytesIfNecessary();
byte[] emptyCQ = indexMaintainer.getEmptyKeyValueQualifier();
@@ -1299,6 +1418,15 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
for (Pair<Mutation, byte[]> update : updates) {
Mutation m = update.getFirst();
if (m instanceof Put) {
+ if (indexMaintainer.isCDCIndex() && context.cdcPreMutationsBytes != null) {
+ ImmutableBytesPtr dataRowKeyPtr = new ImmutableBytesPtr(update.getSecond());
+ byte[] cdcMutationsBytes = context.cdcPreMutationsBytes.get(dataRowKeyPtr);
+ if (cdcMutationsBytes != null) {
+ ((Put) m).addColumn(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES,
+ QueryConstants.CDC_INDEX_PRE_MUTATIONS_CQ_BYTES, batchTimestamp,
+ cdcMutationsBytes);
+ }
+ }
// This will be done before the data table row is updated (i.e., in the first write
// phase)
context.preIndexUpdates.put(hTableInterfaceReference, m);
@@ -1309,6 +1437,15 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
Put unverifiedPut = new Put(m.getRow());
unverifiedPut.addColumn(emptyCF, emptyCQ, batchTimestamp,
QueryConstants.UNVERIFIED_BYTES);
+ if (indexMaintainer.isCDCIndex() && context.cdcPreMutationsBytes != null) {
+ ImmutableBytesPtr dataRowKeyPtr = new ImmutableBytesPtr(update.getSecond());
+ byte[] cdcMutationsBytes = context.cdcPreMutationsBytes.get(dataRowKeyPtr);
+ if (cdcMutationsBytes != null) {
+ unverifiedPut.addColumn(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES,
+ QueryConstants.CDC_INDEX_PRE_MUTATIONS_CQ_BYTES, batchTimestamp,
+ cdcMutationsBytes);
+ }
+ }
// This will be done before the data table row is updated (i.e., in the first write
// phase)
context.preIndexUpdates.put(hTableInterfaceReference, unverifiedPut);
@@ -1319,6 +1456,113 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
}
}
+ /**
+ * Prepares pre-phase and post-phase cdc mutations for eventually consistent indexes.
+ * @param context batch mutate context.
+ * @param batchTimestamp the timestamp to use for mutations.
+ * @param maintainers the list of index maintainers.
+ * @param compressMutations whether to Snappy-compress the serialized proto bytes.
+ * @throws IOException if there is an error.
+ */
+ private static void prepareEventuallyConsistentIndexMutations(BatchMutateContext context,
+ long batchTimestamp, List<IndexMaintainer> maintainers, boolean compressMutations)
+ throws IOException {
+ // Store pre-index and post-index mutations for each data table rowkey
+ Map<ImmutableBytesPtr, IndexMutationsProtos.IndexMutations.Builder> preBuilderMap =
+ new HashMap<>();
+ Map<ImmutableBytesPtr, IndexMutationsProtos.IndexMutations.Builder> postBuilderMap =
+ new HashMap<>();
+
+ for (IndexMaintainer indexMaintainer : maintainers) {
+ if (
+ indexMaintainer.getIndexConsistency() == null
+ || !indexMaintainer.getIndexConsistency().isAsynchronous()
+ ) {
+ continue;
+ }
+ byte[] emptyCF = indexMaintainer.getEmptyKeyValueFamily().copyBytesIfNecessary();
+ byte[] emptyCQ = indexMaintainer.getEmptyKeyValueQualifier();
+ HTableInterfaceReference hTableInterfaceReference =
+ new HTableInterfaceReference(new ImmutableBytesPtr(indexMaintainer.getIndexTableName()));
+ List<Pair<Mutation, byte[]>> updates = context.indexUpdates.get(hTableInterfaceReference);
+ for (Pair<Mutation, byte[]> update : updates) {
+ Mutation m = update.getFirst();
+ byte[] dataRowKey = update.getSecond();
+ ImmutableBytesPtr rowKeyPtr = new ImmutableBytesPtr(dataRowKey);
+ IndexMutationsProtos.IndexMutations.Builder preBuilder = preBuilderMap
+ .computeIfAbsent(rowKeyPtr, k -> IndexMutationsProtos.IndexMutations.newBuilder());
+ IndexMutationsProtos.IndexMutations.Builder postBuilder = postBuilderMap
+ .computeIfAbsent(rowKeyPtr, k -> IndexMutationsProtos.IndexMutations.newBuilder());
+ if (m instanceof Put) {
+ preBuilder.addTables(ByteString.copyFrom(indexMaintainer.getIndexTableName()));
+ byte[] preMutation =
+ ProtobufUtil.toMutation(ClientProtos.MutationProto.MutationType.PUT, m).toByteArray();
+ preBuilder.addMutations(ByteString.copyFrom(preMutation));
+ if (!indexMaintainer.isUncovered()) {
+ Put verifiedPut = new Put(m.getRow());
+ verifiedPut.addColumn(emptyCF, emptyCQ, batchTimestamp, QueryConstants.VERIFIED_BYTES);
+ postBuilder.addTables(ByteString.copyFrom(indexMaintainer.getIndexTableName()));
+ byte[] postMutation = ProtobufUtil
+ .toMutation(ClientProtos.MutationProto.MutationType.PUT, verifiedPut).toByteArray();
+ postBuilder.addMutations(ByteString.copyFrom(postMutation));
+ }
+ } else {
+ if (IndexUtil.isDeleteFamily(m)) {
+ Put unverifiedPut = new Put(m.getRow());
+ unverifiedPut.addColumn(emptyCF, emptyCQ, batchTimestamp,
+ QueryConstants.UNVERIFIED_BYTES);
+ preBuilder.addTables(ByteString.copyFrom(indexMaintainer.getIndexTableName()));
+ byte[] preMutation = ProtobufUtil
+ .toMutation(ClientProtos.MutationProto.MutationType.PUT, unverifiedPut).toByteArray();
+ preBuilder.addMutations(ByteString.copyFrom(preMutation));
+ }
+ postBuilder.addTables(ByteString.copyFrom(indexMaintainer.getIndexTableName()));
+ byte[] deleteMutation = ProtobufUtil
+ .toMutation(ClientProtos.MutationProto.MutationType.DELETE, m).toByteArray();
+ postBuilder.addMutations(ByteString.copyFrom(deleteMutation));
+ }
+ }
+ }
+
+ if (!preBuilderMap.isEmpty()) {
+ context.cdcPreMutationsBytes = new HashMap<>();
+ for (Map.Entry<ImmutableBytesPtr,
+ IndexMutationsProtos.IndexMutations.Builder> entry : preBuilderMap.entrySet()) {
+ ImmutableBytesPtr rowKey = entry.getKey();
+ IndexMutationsProtos.IndexMutations.Builder builder = entry.getValue();
+ if (builder.getTablesCount() != builder.getMutationsCount()) {
+ throw new DoNotRetryIOException(
+ "Pre-phase tables and mutations sizes do not match for row key. Tables size: "
+ + builder.getTablesCount() + " , mutations size: " + builder.getMutationsCount());
+ }
+ if (builder.getTablesCount() > 0) {
+ byte[] protoBytes = builder.build().toByteArray();
+ context.cdcPreMutationsBytes.put(rowKey,
+ compressMutations ? Snappy.compress(protoBytes) : protoBytes);
+ }
+ }
+ }
+
+ if (!postBuilderMap.isEmpty()) {
+ context.cdcPostMutationsBytes = new HashMap<>();
+ for (Map.Entry<ImmutableBytesPtr,
+ IndexMutationsProtos.IndexMutations.Builder> entry : postBuilderMap.entrySet()) {
+ ImmutableBytesPtr rowKey = entry.getKey();
+ IndexMutationsProtos.IndexMutations.Builder builder = entry.getValue();
+ if (builder.getTablesCount() != builder.getMutationsCount()) {
+ throw new DoNotRetryIOException(
+ "Post-phase tables and mutations sizes do not match for row key. Tables size: "
+ + builder.getTablesCount() + " , mutations size: " + builder.getMutationsCount());
+ }
+ if (builder.getTablesCount() > 0) {
+ byte[] protoBytes = builder.build().toByteArray();
+ context.cdcPostMutationsBytes.put(rowKey,
+ compressMutations ? Snappy.compress(protoBytes) : protoBytes);
+ }
+ }
+ }
+ }
+
protected PhoenixIndexMetaData getPhoenixIndexMetaData(
ObserverContext<RegionCoprocessorEnvironment> observerContext,
MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
@@ -1337,6 +1581,12 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
context.postIndexUpdates = ArrayListMultimap.<HTableInterfaceReference, Mutation> create();
List<IndexMaintainer> maintainers = indexMetaData.getIndexMaintainers();
for (IndexMaintainer indexMaintainer : maintainers) {
+ if (
+ indexMaintainer.getIndexConsistency() != null
+ && indexMaintainer.getIndexConsistency().isAsynchronous()
+ ) {
+ continue;
+ }
byte[] emptyCF = indexMaintainer.getEmptyKeyValueFamily().copyBytesIfNecessary();
byte[] emptyCQ = indexMaintainer.getEmptyKeyValueQualifier();
HTableInterfaceReference hTableInterfaceReference =
@@ -1356,6 +1606,31 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
}
}
}
+
+ if (context.cdcPostMutationsBytes != null && !context.cdcPostMutationsBytes.isEmpty()) {
+ for (IndexMaintainer indexMaintainer : maintainers) {
+ if (!indexMaintainer.isCDCIndex()) {
+ continue;
+ }
+ HTableInterfaceReference hTableInterfaceReference =
+ new HTableInterfaceReference(new ImmutableBytesPtr(indexMaintainer.getIndexTableName()));
+ List<Pair<Mutation, byte[]>> updates = context.indexUpdates.get(hTableInterfaceReference);
+ for (Pair<Mutation, byte[]> update : updates) {
+ Mutation m = update.getFirst();
+ if (m instanceof Put) {
+ ImmutableBytesPtr dataRowKeyPtr = new ImmutableBytesPtr(update.getSecond());
+ byte[] cdcMutationsBytes = context.cdcPostMutationsBytes.get(dataRowKeyPtr);
+ if (cdcMutationsBytes != null) {
+ Put postPut = new Put(m.getRow());
+ postPut.addColumn(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES,
+ QueryConstants.CDC_INDEX_POST_MUTATIONS_CQ_BYTES, batchTimestamp,
+ cdcMutationsBytes);
+ context.postIndexUpdates.put(hTableInterfaceReference, postPut);
+ }
+ }
+ }
+ }
+ }
// all cleanup will be done in postBatchMutateIndispensably()
}
@@ -1445,7 +1720,7 @@ public class IndexRegionObserver implements RegionCoprocessor, RegionObserver {
CountDownLatch countDownLatch = lastContext.getCountDownLatch();
if (countDownLatch == null) {
// phase changed from PRE to either FAILED or POST
- if (phase == BatchMutatePhase.FAILED) {
+ if (lastContext.getCurrentPhase() == BatchMutatePhase.FAILED) {
context.currentPhase = BatchMutatePhase.FAILED;
break;
}
diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/write/TrackingParallelWriterIndexCommitter.java b/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/write/TrackingParallelWriterIndexCommitter.java
index 69d782e078..fcf839b7d3 100644
--- a/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/write/TrackingParallelWriterIndexCommitter.java
+++ b/phoenix-core-server/src/main/java/org/apache/phoenix/hbase/index/write/TrackingParallelWriterIndexCommitter.java
@@ -151,11 +151,13 @@ public class TrackingParallelWriterIndexCommitter implements IndexCommitter {
Set<Entry<HTableInterfaceReference, Collection<Mutation>>> entries = toWrite.asMap().entrySet();
TaskBatch<Boolean> tasks = new TaskBatch<Boolean>(entries.size());
List<HTableInterfaceReference> tables = new ArrayList<HTableInterfaceReference>(entries.size());
+ int totalMutations = 0;
for (Entry<HTableInterfaceReference, Collection<Mutation>> entry : entries) {
// get the mutations for each table. We leak the implementation here a little bit to save
// doing a complete copy over of all the index update for each table.
final List<Mutation> mutations =
kvBuilder.cloneIfNecessary((List<Mutation>) entry.getValue());
+ totalMutations += mutations.size();
// track each reference so we can get at it easily later, when determing failures
final HTableInterfaceReference tableReference = entry.getKey();
final RegionCoprocessorEnvironment env = this.env;
@@ -249,7 +251,10 @@ public class TrackingParallelWriterIndexCommitter implements IndexCommitter {
Pair<List<Boolean>, List<Future<Boolean>>> resultsAndFutures = null;
try {
- LOGGER.debug("Waiting on index update tasks to complete...");
+ LOGGER.debug(
+ "Waiting on index update tasks to complete... "
+ + "tasks size {}, total mutations {}, num tables {}",
+ tasks.size(), totalMutations, tables.size());
resultsAndFutures = this.pool.submitUninterruptible(tasks);
} catch (ExecutionException e) {
throw new RuntimeException(
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java
index e111304e68..7264e3a8b7 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BasePermissionsIT.java
@@ -105,7 +105,7 @@ public abstract class BasePermissionsIT extends BaseTest {
private static final Set<String> PHOENIX_SYSTEM_TABLES =
new HashSet<>(Arrays.asList("SYSTEM.CATALOG", "SYSTEM.SEQUENCE", "SYSTEM.STATS",
"SYSTEM.FUNCTION", "SYSTEM.MUTEX", "SYSTEM.CHILD_LINK", "SYSTEM.TRANSFORM",
- "SYSTEM.CDC_STREAM_STATUS", "SYSTEM.CDC_STREAM"));
+ "SYSTEM.CDC_STREAM_STATUS", "SYSTEM.CDC_STREAM", "SYSTEM.IDX_CDC_TRACKER"));
private static final Set<String> PHOENIX_SYSTEM_TABLES_IDENTIFIERS =
new HashSet<>(Arrays.asList("SYSTEM.\"CATALOG\"", "SYSTEM.\"SEQUENCE\"", "SYSTEM.\"STATS\"",
@@ -123,7 +123,7 @@ public abstract class BasePermissionsIT extends BaseTest {
static final Set<String> PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES =
new HashSet<>(Arrays.asList("SYSTEM:CATALOG", "SYSTEM:SEQUENCE", "SYSTEM:STATS",
"SYSTEM:FUNCTION", "SYSTEM:MUTEX", "SYSTEM:CHILD_LINK", "SYSTEM:TRANSFORM",
- "SYSTEM:CDC_STREAM_STATUS", "SYSTEM:CDC_STREAM"));
+ "SYSTEM:CDC_STREAM_STATUS", "SYSTEM:CDC_STREAM", "SYSTEM:IDX_CDC_TRACKER"));
// Create Multiple users so that we can use Hadoop UGI to run tasks as various users
// Permissions can be granted or revoke by superusers and admins only
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
index 103f0e6df5..f35cc50f5c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
@@ -37,6 +37,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.hbase.util.Bytes;
@@ -47,9 +48,13 @@ import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.hbase.index.metrics.GlobalIndexCheckerSource;
import org.apache.phoenix.hbase.index.metrics.GlobalIndexCheckerSourceImpl;
import org.apache.phoenix.hbase.index.metrics.MetricsIndexerSourceFactory;
+import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
import org.apache.phoenix.jdbc.PhoenixStatement;
+import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.PTableKey;
import org.apache.phoenix.schema.types.PDouble;
+import org.apache.phoenix.util.CDCUtil;
import org.apache.phoenix.util.PropertiesUtil;
import org.bson.BsonArray;
import org.bson.BsonBinary;
@@ -145,6 +150,12 @@ public class Bson4IT extends ParallelStatsDisabledIT {
conn.commit();
+ PhoenixConnection pcon = conn.unwrap(PhoenixConnection.class);
+ PTable dataTable = pcon.getTable(new PTableKey(pcon.getTenantId(), tableName));
+ List<PTable> indexes = dataTable.getIndexes();
+ assertEquals("Data table should have exactly 2 indexes", 2, indexes.size());
+ assertFalse("Data table should not have CDC index", CDCUtil.hasCDCIndex(dataTable));
+
ResultSet rs = conn.createStatement().executeQuery("SELECT count(*) FROM " + tableName);
assertTrue(rs.next());
assertEquals(3, rs.getInt(1));
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java
index 99f4642baf..3e6a4f1989 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java
@@ -17,6 +17,9 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -40,10 +43,15 @@ import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
+import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.types.PDouble;
-import org.apache.phoenix.util.CDCUtil;
import org.apache.phoenix.util.PropertiesUtil;
+import org.apache.phoenix.util.ReadOnlyProps;
import org.bson.BsonArray;
import org.bson.BsonBinary;
import org.bson.BsonDocument;
@@ -52,19 +60,33 @@ import org.bson.BsonNull;
import org.bson.BsonString;
import org.bson.RawBsonDocument;
import org.junit.Assert;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions;
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
/**
* Tests for BSON with expression field key alias.
*/
-@Category(ParallelStatsDisabledTest.class)
+@Category(NeedsOwnMiniClusterTest.class)
public class Bson5IT extends ParallelStatsDisabledIT {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+ @BeforeClass
+ public static synchronized void doSetup() throws Exception {
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(1);
+ props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
+ Integer.toString(60 * 60));
+ props.put(QueryServices.USE_STATS_FOR_PARALLELIZATION, Boolean.toString(false));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(2000));
+ props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(5));
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.TRUE.toString());
+ setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+ }
+
private static String getJsonString(String jsonFilePath) throws IOException {
URL fileUrl = Bson5IT.class.getClassLoader().getResource(jsonFilePath);
Preconditions.checkArgument(fileUrl != null, "File path " + jsonFilePath + " seems invalid");
@@ -79,14 +101,24 @@ public class Bson5IT extends ParallelStatsDisabledIT {
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
String tableName = generateUniqueName();
String cdcName = generateUniqueName();
+ String indexName1 = "IDX1_" + tableName;
+ String indexName2 = "IDX2_" + tableName;
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
String ddl = "CREATE TABLE " + tableName + " (PK1 VARCHAR NOT NULL, C1 VARCHAR, COL BSON"
+ " CONSTRAINT pk PRIMARY KEY(PK1))";
String cdcDdl = "CREATE CDC " + cdcName + " ON " + tableName;
+ String indexDdl1 = "CREATE UNCOVERED INDEX " + indexName1 + " ON " + tableName
+ + "(BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR')) "
+ + "WHERE BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') IS NOT NULL "
+ + "CONSISTENCY = EVENTUAL";
+ String indexDdl2 = "CREATE UNCOVERED INDEX " + indexName2 + " ON " + tableName
+ + "(BSON_VALUE(COL, 'result[1].location.coordinates.longitude', 'DOUBLE')) "
+ + "WHERE BSON_VALUE(COL, 'result[1].location.coordinates.longitude', 'DOUBLE') IS NOT NULL "
+ + "CONSISTENCY = EVENTUAL";
conn.createStatement().execute(ddl);
conn.createStatement().execute(cdcDdl);
- IndexToolIT.runIndexTool(false, "", tableName,
- "\"" + CDCUtil.getCDCIndexName(cdcName) + "\"");
+ conn.createStatement().execute(indexDdl1);
+ conn.createStatement().execute(indexDdl2);
Timestamp ts1 = new Timestamp(System.currentTimeMillis());
Thread.sleep(100);
@@ -118,6 +150,30 @@ public class Bson5IT extends ParallelStatsDisabledIT {
Thread.sleep(100);
Timestamp ts2 = new Timestamp(System.currentTimeMillis());
+ PreparedStatement indexPs = conn.prepareStatement("SELECT PK1, COL FROM " + tableName
+ + " WHERE BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') = ?");
+ indexPs.setString(1, "personal");
+ ResultSet indexRs = indexPs.executeQuery();
+ assertFalse(indexRs.next());
+
+ Thread.sleep(11000);
+ indexRs = indexPs.executeQuery();
+ assertTrue(indexRs.next());
+ assertEquals("pk1010", indexRs.getString(1));
+ BsonDocument actualDoc = (BsonDocument) indexRs.getObject(2);
+ assertEquals(bsonDocument2, actualDoc);
+ assertFalse(indexRs.next());
+ validateExplainPlan(indexPs, indexName1, "RANGE SCAN ");
+
+ indexPs = conn.prepareStatement("SELECT PK1 FROM " + tableName
+ + " WHERE BSON_VALUE(COL, 'result[1].location.coordinates.longitude', 'DOUBLE') = ?");
+ indexPs.setDouble(1, 52.3736);
+ indexRs = indexPs.executeQuery();
+ assertTrue(indexRs.next());
+ assertEquals("pk1011", indexRs.getString(1));
+ assertFalse(indexRs.next());
+ validateExplainPlan(indexPs, indexName2, "RANGE SCAN ");
+
testCDCAfterFirstUpsert(conn, cdcName, ts1, ts2, bsonDocument1, bsonDocument2, bsonDocument3);
ts1 = new Timestamp(System.currentTimeMillis());
@@ -312,14 +368,24 @@ public class Bson5IT extends ParallelStatsDisabledIT {
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
String tableName = generateUniqueName();
String cdcName = generateUniqueName();
+ String indexName1 = "IDX1_" + tableName;
+ String indexName2 = "IDX2_" + tableName;
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
String ddl = "CREATE TABLE " + tableName + " (PK1 VARCHAR NOT NULL, C1 VARCHAR, COL BSON"
+ " CONSTRAINT pk PRIMARY KEY(PK1))";
String cdcDdl = "CREATE CDC " + cdcName + " ON " + tableName;
+ String indexDdl1 = "CREATE INDEX " + indexName1 + " ON " + tableName
+ + "(BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR')) INCLUDE(COL) "
+ + "WHERE BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') IS NOT NULL "
+ + "CONSISTENCY = EVENTUAL";
+ String indexDdl2 = "CREATE UNCOVERED INDEX " + indexName2 + " ON " + tableName
+ + "(BSON_VALUE(COL, 'result[1].location.coordinates.longitude', 'DOUBLE')) "
+ + "WHERE BSON_VALUE(COL, 'result[1].location.coordinates.longitude', 'DOUBLE') IS NOT NULL "
+ + "CONSISTENCY = EVENTUAL";
conn.createStatement().execute(ddl);
conn.createStatement().execute(cdcDdl);
- IndexToolIT.runIndexTool(false, "", tableName,
- "\"" + CDCUtil.getCDCIndexName(cdcName) + "\"");
+ conn.createStatement().execute(indexDdl1);
+ conn.createStatement().execute(indexDdl2);
Timestamp ts1 = new Timestamp(System.currentTimeMillis());
Thread.sleep(100);
@@ -352,6 +418,27 @@ public class Bson5IT extends ParallelStatsDisabledIT {
Thread.sleep(100);
Timestamp ts2 = new Timestamp(System.currentTimeMillis());
+ Thread.sleep(11000);
+ PreparedStatement indexPs = conn.prepareStatement("SELECT PK1, COL FROM " + tableName
+ + " WHERE BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') = ?");
+ indexPs.setString(1, "personal");
+ ResultSet indexRs = indexPs.executeQuery();
+ assertTrue(indexRs.next());
+ assertEquals("pk1010", indexRs.getString(1));
+ BsonDocument actualDoc = (BsonDocument) indexRs.getObject(2);
+ assertEquals(bsonDocument2, actualDoc);
+ assertFalse(indexRs.next());
+ validateExplainPlan(indexPs, indexName1, "RANGE SCAN ");
+
+ indexPs = conn.prepareStatement("SELECT PK1 FROM " + tableName
+ + " WHERE BSON_VALUE(COL, 'result[1].location.coordinates.longitude', 'DOUBLE') = ?");
+ indexPs.setDouble(1, 52.3736);
+ indexRs = indexPs.executeQuery();
+ assertTrue(indexRs.next());
+ assertEquals("pk1011", indexRs.getString(1));
+ assertFalse(indexRs.next());
+ validateExplainPlan(indexPs, indexName2, "RANGE SCAN ");
+
testCDCAfterFirstUpsert(conn, cdcName, ts1, ts2, bsonDocument1, bsonDocument2, bsonDocument3);
Thread.sleep(100);
@@ -654,4 +741,13 @@ public class Bson5IT extends ParallelStatsDisabledIT {
}
}
+ private static void validateExplainPlan(PreparedStatement ps, String tableName, String scanType)
+ throws SQLException {
+ ExplainPlan plan = ps.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+ ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes();
+ assertEquals(tableName, explainPlanAttributes.getTableName());
+ assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize());
+ assertEquals(scanType, explainPlanAttributes.getExplainScanType());
+ }
+
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredEventualGenerateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredEventualGenerateIT.java
new file mode 100644
index 0000000000..0088a38a64
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredEventualGenerateIT.java
@@ -0,0 +1,73 @@
+/*
+ * 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.end2end;
+
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_BATCH_SIZE;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_PARENT_PROGRESS_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import org.apache.phoenix.coprocessor.PhoenixMasterObserver;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.BeforeClass;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class ConcurrentMutationsCoveredEventualGenerateIT
+ extends ConcurrentMutationsExtendedIndexIT {
+
+ private static final int MAX_LOOKBACK_AGE = 1000000;
+
+ public ConcurrentMutationsCoveredEventualGenerateIT(boolean uncovered, boolean eventual) {
+ super(uncovered, eventual);
+ }
+
+ @BeforeClass
+ public static synchronized void doSetup() throws Exception {
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(10);
+ props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
+ props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
+ Integer.toString(MAX_LOOKBACK_AGE));
+ props.put("hbase.rowlock.wait.duration", "100");
+ props.put("phoenix.index.concurrent.wait.duration.ms", "10");
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ props.put(INDEX_CDC_CONSUMER_BATCH_SIZE, Integer.toString(4500));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(5000));
+ props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(200));
+ props.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
+ props.put("hbase.coprocessor.master.classes", PhoenixMasterObserver.class.getName());
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString());
+ props.put(INDEX_CDC_CONSUMER_PARENT_PROGRESS_PAUSE_MS, Integer.toString(1000));
+ setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+ }
+
+ @Parameterized.Parameters(name = "uncovered={0}, eventual={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { { false, true } });
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredEventualIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredEventualIT.java
new file mode 100644
index 0000000000..f83da1bab1
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredEventualIT.java
@@ -0,0 +1,36 @@
+/*
+ * 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.end2end;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class ConcurrentMutationsCoveredEventualIT extends ConcurrentMutationsExtendedIndexIT {
+
+ public ConcurrentMutationsCoveredEventualIT(boolean uncovered, boolean eventual) {
+ super(uncovered, eventual);
+ }
+
+ @Parameterized.Parameters(name = "uncovered={0}, eventual={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { { false, true } });
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredNonEventualIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredNonEventualIT.java
new file mode 100644
index 0000000000..7d7c2c1e5b
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsCoveredNonEventualIT.java
@@ -0,0 +1,36 @@
+/*
+ * 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.end2end;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class ConcurrentMutationsCoveredNonEventualIT extends ConcurrentMutationsExtendedIndexIT {
+
+ public ConcurrentMutationsCoveredNonEventualIT(boolean uncovered, boolean eventual) {
+ super(uncovered, eventual);
+ }
+
+ @Parameterized.Parameters(name = "uncovered={0}, eventual={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { { false, false } });
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedGenerateIT.java
similarity index 54%
copy from phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java
copy to phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedGenerateIT.java
index 761c8658eb..7a5cd7fb67 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedGenerateIT.java
@@ -17,37 +17,51 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_BATCH_SIZE;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Map;
+import org.apache.phoenix.coprocessor.PhoenixMasterObserver;
import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
-import org.apache.phoenix.hbase.index.IndexRegionObserver;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.ReadOnlyProps;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
-/**
- * Test class that extends ConcurrentMutationsExtendedIT with lazy post batch write enabled.
- */
@Category(NeedsOwnMiniClusterTest.class)
-public class ConcurrentMutationsLazyPostBatchWriteIT extends ConcurrentMutationsExtendedIT {
+public class ConcurrentMutationsExtendedGenerateIT extends ConcurrentMutationsExtendedIT {
- public ConcurrentMutationsLazyPostBatchWriteIT(boolean uncovered) {
- super(uncovered);
- Assume.assumeFalse("Only covered index supports lazy post batch write mode", uncovered);
+ public ConcurrentMutationsExtendedGenerateIT(boolean uncovered, boolean eventual) {
+ super(uncovered, eventual);
}
@BeforeClass
public static synchronized void doSetup() throws Exception {
- Map<String, String> props = Maps.newHashMapWithExpectedSize(4);
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(10);
props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
- props.put(IndexRegionObserver.INDEX_LAZY_POST_BATCH_WRITE, "true");
props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
Integer.toString(MAX_LOOKBACK_AGE));
props.put("hbase.rowlock.wait.duration", "100");
props.put("phoenix.index.concurrent.wait.duration.ms", "10");
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ props.put(INDEX_CDC_CONSUMER_BATCH_SIZE, Integer.toString(4500));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(2500));
+ // props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(200));
+ props.put("hbase.coprocessor.master.classes", PhoenixMasterObserver.class.getName());
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString());
+ props.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
+
+ @Parameterized.Parameters(name = "uncovered={0}, eventual={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { { false, true }, { true, true } });
+ }
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedIT.java
index 03ca9e1a68..4e10284a32 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedIT.java
@@ -18,6 +18,10 @@
package org.apache.phoenix.end2end;
import static org.apache.phoenix.end2end.IndexToolIT.verifyIndexTable;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_BATCH_SIZE;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_PARENT_PROGRESS_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -41,6 +45,7 @@ import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.coprocessor.SimpleRegionObserver;
import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.coprocessor.PhoenixMasterObserver;
import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.query.QueryServices;
@@ -64,6 +69,7 @@ import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
private static final Logger LOGGER = LoggerFactory.getLogger(ConcurrentMutationsExtendedIT.class);
private final boolean uncovered;
+ private final boolean eventual;
private static final Random RAND = new Random(5);
private static final String MVCC_LOCK_TEST_TABLE_PREFIX = "MVCCLOCKTEST_";
private static final String LOCK_TEST_TABLE_PREFIX = "LOCKTEST_";
@@ -71,8 +77,9 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
protected static final int MAX_LOOKBACK_AGE = 1000000;
private final Object lock = new Object();
- public ConcurrentMutationsExtendedIT(boolean uncovered) {
+ public ConcurrentMutationsExtendedIT(boolean uncovered, boolean eventual) {
this.uncovered = uncovered;
+ this.eventual = eventual;
}
@BeforeClass
@@ -88,12 +95,20 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
// The following sets the wait duration for the previous concurrent batch to 10 ms to test
// the code path handling timeouts
props.put("phoenix.index.concurrent.wait.duration.ms", "10");
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ props.put(INDEX_CDC_CONSUMER_BATCH_SIZE, Integer.toString(4500));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(1000));
+ props.put("hbase.coprocessor.master.classes", PhoenixMasterObserver.class.getName());
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.TRUE.toString());
+ props.put(INDEX_CDC_CONSUMER_PARENT_PROGRESS_PAUSE_MS, Integer.toString(1000));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
- @Parameterized.Parameters(name = "uncovered={0}")
- public static synchronized Collection<Boolean> data() {
- return Arrays.asList(true, false);
+ @Parameterized.Parameters(name = "uncovered={0}, eventual={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] { { false, false }, { false, true }, { true, false }, { true, true } });
}
@Test
@@ -105,7 +120,7 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
+ "(k1 INTEGER NOT NULL, k2 INTEGER NOT NULL, v1 INTEGER, CONSTRAINT pk PRIMARY KEY (k1,k2)) COLUMN_ENCODED_BYTES = 0");
TestUtil.addCoprocessor(conn, tableName, DelayingRegionObserver.class);
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX "
- + indexName + " ON " + tableName + "(v1)");
+ + indexName + " ON " + tableName + "(v1)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
final CountDownLatch doneSignal = new CountDownLatch(2);
Runnable r1 = new Runnable() {
@@ -167,6 +182,9 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
t2.start();
doneSignal.await(60, TimeUnit.SECONDS);
+ if (eventual) {
+ Thread.sleep(35000);
+ }
verifyIndexTable(tableName, indexName, conn);
}
@@ -180,11 +198,11 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
+ "(k1 INTEGER NOT NULL, k2 INTEGER NOT NULL, v1 INTEGER, CONSTRAINT pk PRIMARY KEY (k1,k2))");
TestUtil.addCoprocessor(conn, tableName, DelayingRegionObserver.class);
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX "
- + indexName + " ON " + tableName + "(v1)");
+ + indexName + " ON " + tableName + "(v1)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
conn.createStatement()
.execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX " + singleCellindexName
- + " ON " + tableName
- + "(v1) IMMUTABLE_STORAGE_SCHEME=SINGLE_CELL_ARRAY_WITH_OFFSETS, COLUMN_ENCODED_BYTES=2");
+ + " ON " + tableName + "(v1)" + (eventual ? " CONSISTENCY = EVENTUAL," : "")
+ + " IMMUTABLE_STORAGE_SCHEME=SINGLE_CELL_ARRAY_WITH_OFFSETS," + " COLUMN_ENCODED_BYTES=2");
final CountDownLatch doneSignal = new CountDownLatch(2);
Runnable r1 = new Runnable() {
@@ -236,6 +254,9 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
t2.start();
doneSignal.await(60, TimeUnit.SECONDS);
+ if (eventual) {
+ Thread.sleep(35000);
+ }
verifyIndexTable(tableName, indexName, conn);
verifyIndexTable(tableName, singleCellindexName, conn);
}
@@ -252,51 +273,56 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
conn.createStatement().execute("CREATE TABLE " + tableName
+ "(k1 INTEGER NOT NULL, k2 INTEGER NOT NULL, a.v1 INTEGER, b.v2 INTEGER, c.v3 INTEGER, d.v4 INTEGER,"
+ "CONSTRAINT pk PRIMARY KEY (k1,k2)) COLUMN_ENCODED_BYTES = 0, VERSIONS=1");
- conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX "
- + indexName + " ON " + tableName + "(v1)" + (uncovered ? "" : "INCLUDE(v2, v3)"));
+ conn.createStatement()
+ .execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX " + indexName + " ON "
+ + tableName + "(v1)" + (uncovered ? "" : " INCLUDE(v2, v3)")
+ + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
final CountDownLatch doneSignal = new CountDownLatch(nThreads);
Runnable[] runnables = new Runnable[nThreads];
long startTime = EnvironmentEdgeManager.currentTimeMillis();
- for (int i = 0; i < nThreads; i++) {
- runnables[i] = new Runnable() {
-
- @Override
- public void run() {
- try {
- Connection conn = DriverManager.getConnection(getUrl());
- for (int i = 0; i < 10000; i++) {
- conn.createStatement()
- .execute("UPSERT INTO " + tableName + " VALUES (" + (i % nRows) + ", 0, "
- + (RAND.nextBoolean() ? null : (RAND.nextInt() % nIndexValues)) + ", "
- + (RAND.nextBoolean() ? null : RAND.nextInt()) + ", "
- + (RAND.nextBoolean() ? null : RAND.nextInt()) + ", "
- + (RAND.nextBoolean() ? null : RAND.nextInt()) + ")");
- if ((i % batchSize) == 0) {
- conn.commit();
- }
- }
- conn.commit();
- } catch (SQLException e) {
- LOGGER.warn("Exception during upsert : " + e);
- } finally {
- doneSignal.countDown();
- }
- }
-
- };
- }
+ buildRunnables(nThreads, runnables, tableName, nRows, nIndexValues, batchSize, doneSignal,
+ 10000);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread(runnables[i]);
t.start();
}
-
assertTrue("Ran out of time", doneSignal.await(120, TimeUnit.SECONDS));
LOGGER.info(
"Total upsert time in ms : " + (EnvironmentEdgeManager.currentTimeMillis() - startTime));
+ if (eventual) {
+ Thread.sleep(350000);
+ }
long actualRowCount = verifyIndexTable(tableName, indexName, conn);
assertEquals(nRows, actualRowCount);
}
+ static void buildRunnables(int nThreads, Runnable[] runnables, String tableName, int nRows,
+ int nIndexValues, int batchSize, CountDownLatch doneSignal, int totalUpserts) {
+ for (int i = 0; i < nThreads; i++) {
+ runnables[i] = () -> {
+ try {
+ Connection conn = DriverManager.getConnection(getUrl());
+ for (int j = 0; j < totalUpserts; j++) {
+ conn.createStatement()
+ .execute("UPSERT INTO " + tableName + " VALUES (" + (j % nRows) + ", 0, "
+ + (RAND.nextBoolean() ? null : (RAND.nextInt() % nIndexValues)) + ", "
+ + (RAND.nextBoolean() ? null : RAND.nextInt()) + ", "
+ + (RAND.nextBoolean() ? null : RAND.nextInt()) + ", "
+ + (RAND.nextBoolean() ? null : RAND.nextInt()) + ")");
+ if ((j % batchSize) == 0) {
+ conn.commit();
+ }
+ }
+ conn.commit();
+ } catch (SQLException e) {
+ LOGGER.warn("Exception during upsert : " + e);
+ } finally {
+ doneSignal.countDown();
+ }
+ };
+ }
+ }
+
@Test
public void testRowLockDuringPreBatchMutateWhenIndexed() throws Exception {
final String tableName = LOCK_TEST_TABLE_PREFIX + generateUniqueName();
@@ -307,7 +333,7 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
"CREATE TABLE " + tableName + "(k VARCHAR PRIMARY KEY, v INTEGER) COLUMN_ENCODED_BYTES = 0");
TestUtil.addCoprocessor(conn, tableName, DelayingRegionObserver.class);
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX "
- + indexName + " ON " + tableName + "(v)");
+ + indexName + " ON " + tableName + "(v)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
final CountDownLatch doneSignal = new CountDownLatch(2);
final String[] failedMsg = new String[1];
Runnable r1 = new Runnable() {
@@ -353,6 +379,9 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
doneSignal.await(ROW_LOCK_WAIT_TIME + 5000, TimeUnit.SECONDS);
assertNull(failedMsg[0], failedMsg[0]);
+ if (eventual) {
+ Thread.sleep(35000);
+ }
long actualRowCount = IndexScrutiny.scrutinizeIndex(conn, tableName, indexName);
assertEquals(1, actualRowCount);
}
@@ -365,7 +394,7 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
conn.createStatement().execute(
"CREATE TABLE " + tableName + "(k VARCHAR PRIMARY KEY, v INTEGER) COLUMN_ENCODED_BYTES = 0");
conn.createStatement().execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX "
- + indexName + " ON " + tableName + "(v,k)");
+ + indexName + " ON " + tableName + "(v,k)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
conn.createStatement().execute("UPSERT INTO " + tableName + " VALUES ('foo',0)");
conn.commit();
TestUtil.addCoprocessor(conn, tableName, DelayingRegionObserver.class);
@@ -411,6 +440,9 @@ public class ConcurrentMutationsExtendedIT extends ParallelStatsDisabledIT {
t2.start();
doneSignal.await(ROW_LOCK_WAIT_TIME + 5000, TimeUnit.SECONDS);
+ if (eventual) {
+ Thread.sleep(35000);
+ }
long actualRowCount = IndexScrutiny.scrutinizeIndex(conn, tableName, indexName);
assertEquals(1, actualRowCount);
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedIndexIT.java
new file mode 100644
index 0000000000..35fcbf1713
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsExtendedIndexIT.java
@@ -0,0 +1,438 @@
+/*
+ * 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.end2end;
+
+import static org.apache.phoenix.end2end.IndexToolIT.verifyIndexTable;
+import static org.apache.phoenix.hbase.index.metrics.MetricsIndexCDCConsumerSource.CDC_BATCH_COUNT;
+import static org.apache.phoenix.hbase.index.metrics.MetricsIndexCDCConsumerSource.CDC_BATCH_PROCESS_TIME;
+import static org.apache.phoenix.hbase.index.metrics.MetricsIndexCDCConsumerSource.CDC_MUTATION_COUNT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.apache.phoenix.hbase.index.metrics.MetricsIndexCDCConsumerSourceImpl;
+import org.apache.phoenix.hbase.index.metrics.MetricsIndexerSourceFactory;
+import org.apache.phoenix.util.EnvironmentEdgeManager;
+import org.apache.phoenix.util.TestUtil;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@RunWith(Parameterized.class)
+public abstract class ConcurrentMutationsExtendedIndexIT extends ParallelStatsDisabledIT {
+
+ private static final Logger LOGGER =
+ LoggerFactory.getLogger(ConcurrentMutationsExtendedIndexIT.class);
+
+ private final boolean uncovered;
+ private final boolean eventual;
+
+ protected ConcurrentMutationsExtendedIndexIT(boolean uncovered, boolean eventual) {
+ this.uncovered = uncovered;
+ this.eventual = eventual;
+ }
+
+ @BeforeClass
+ public static synchronized void doSetup() throws Exception {
+ ConcurrentMutationsExtendedIT.doSetup();
+ }
+
+ // This test is heavy and it might exhaust jenkins resources
+ @Test(timeout = 1800000)
+ public void testConcurrentUpsertsWithTableSplits() throws Exception {
+ Map<String, Long> metricsBefore = getCdcMetricValues();
+ int nThreads = 12;
+ final int batchSize = 100;
+ final int nRows = 777;
+ final int nIndexValues = 23;
+ final int nSplits = 3;
+ final String schemaName = "SchemaN_01-" + generateUniqueName();
+ final String tableName = "Table.Name-00_001_" + generateUniqueName();
+ final String indexName1 = "IndexTable.Name-00_001_" + generateUniqueName();
+ final String indexName2 = "IndexTable.Name-00_002_" + generateUniqueName();
+ final String indexName3 = "IndexTable.Name-00_003_" + generateUniqueName();
+ final String indexName4 = "IndexTable.Name-00_004_" + generateUniqueName();
+ final String indexName5 = "IndexTable.Name-00_005_" + generateUniqueName();
+ Connection conn = DriverManager.getConnection(getUrl());
+ conn.createStatement().execute("CREATE TABLE " + "\"" + schemaName + "\".\"" + tableName + "\""
+ + "(k1 INTEGER NOT NULL, k2 INTEGER NOT NULL, v1 INTEGER, v2 INTEGER, v3 INTEGER, v4 INTEGER,"
+ + "CONSTRAINT pk PRIMARY KEY (k1,k2)) \"phoenix.index.cdc.mutations.compress.enabled\"=true");
+ conn.createStatement()
+ .execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX " + "\"" + indexName1 + "\""
+ + " ON " + "\"" + schemaName + "\".\"" + tableName + "\"" + "(v1)"
+ + (uncovered ? "" : " INCLUDE(v2, v3)")
+ + (eventual ? " CONSISTENCY = EVENTUAL" : " CONSISTENCY = STRONG"));
+ conn.createStatement()
+ .execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX " + "\"" + indexName2 + "\""
+ + " ON " + "\"" + schemaName + "\".\"" + tableName + "\"" + "(v2)"
+ + (uncovered ? "" : " INCLUDE(v1, v4)") + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+ conn.createStatement()
+ .execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX " + "\"" + indexName3 + "\""
+ + " ON " + "\"" + schemaName + "\".\"" + tableName + "\"" + "(v3)"
+ + (uncovered ? "" : " INCLUDE(v1, v2)")
+ + (eventual ? " CONSISTENCY = EVENTUAL" : " CONSISTENCY = STRONG"));
+ conn.createStatement()
+ .execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX " + "\"" + indexName4 + "\""
+ + " ON " + "\"" + schemaName + "\".\"" + tableName + "\"" + "(v4)"
+ + (uncovered ? "" : " INCLUDE(v2, v3)") + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+ conn.createStatement()
+ .execute("CREATE " + (uncovered ? "UNCOVERED " : " ") + "INDEX " + "\"" + indexName5 + "\""
+ + " ON " + "\"" + schemaName + "\".\"" + tableName + "\"" + "(v1, v2)"
+ + (uncovered ? "" : " INCLUDE(v3, v4)")
+ + (eventual ? " CONSISTENCY = EVENTUAL" : " CONSISTENCY = STRONG"));
+
+ final CountDownLatch doneSignal = new CountDownLatch(nThreads);
+ Runnable[] runnables = new Runnable[nThreads];
+ Thread.sleep(3000);
+ long startTime = EnvironmentEdgeManager.currentTimeMillis();
+
+ ConcurrentMutationsExtendedIT.buildRunnables(nThreads, runnables,
+ "\"" + schemaName + "\".\"" + tableName + "\"", nRows, nIndexValues, batchSize, doneSignal,
+ 10000);
+ Thread splitThread = new Thread(() -> TestUtil.splitTable(getUrl(),
+ schemaName + "." + tableName, nSplits, eventual ? 8000 : 2000));
+ splitThread.start();
+ for (int i = 0; i < nThreads; i++) {
+ if (i >= (nThreads - 4)) {
+ Thread.sleep(12000);
+ }
+ Thread t = new Thread(runnables[i]);
+ t.start();
+ }
+ assertTrue("Ran out of time", doneSignal.await(350, TimeUnit.SECONDS));
+ splitThread.join(10000);
+ LOGGER.info(
+ "Total upsert time in ms : " + (EnvironmentEdgeManager.currentTimeMillis() - startTime));
+ Thread.sleep(20000);
+ List<String> allIndexes =
+ new ArrayList<>(Arrays.asList(indexName1, indexName2, indexName3, indexName4, indexName5));
+ verifyRandomIndexes(allIndexes, schemaName, tableName, conn, nRows);
+ assertCdcMetrics(metricsBefore);
+ }
+
+ private void verifyRandomIndexes(List<String> allIndexes, String schemaName, String tableName,
+ Connection conn, int nRows) throws Exception {
+ Collections.shuffle(allIndexes, ThreadLocalRandom.current());
+ if (!eventual) {
+ verifyIndexes(allIndexes, schemaName, tableName, conn, nRows, true);
+ return;
+ }
+ boolean isComplete;
+ int tries = 0;
+ do {
+ Thread.sleep(10000);
+ try {
+ verifyIndexes(allIndexes, schemaName, tableName, conn, nRows, false);
+ isComplete = true;
+ } catch (AssertionError e) {
+ isComplete = false;
+ LOGGER.error("Index verification failed. Tries: {}", tries, e);
+ } finally {
+ dumpTableRows(conn, "SYSTEM.CDC_STREAM");
+ dumpTableRows(conn, "SYSTEM.IDX_CDC_TRACKER");
+ }
+ tries++;
+ } while (!isComplete);
+ LOGGER.info("Index verification complete. Tries: {}", tries);
+ }
+
+ private static void verifyIndexes(List<String> allIndexes, String schemaName, String tableName,
+ Connection conn, int nRows, boolean dumpTables) throws Exception {
+ LOGGER.info("Randomly selected indexes to verify: {}, {}", allIndexes.get(0),
+ allIndexes.get(1));
+ long actualRowCount = verifyIndexTable("\"" + schemaName + "\".\"" + tableName + "\"",
+ "\"" + schemaName + "\".\"" + allIndexes.get(0) + "\"", conn, dumpTables);
+ assertEquals(nRows, actualRowCount);
+ actualRowCount = verifyIndexTable("\"" + schemaName + "\".\"" + tableName + "\"",
+ "\"" + schemaName + "\".\"" + allIndexes.get(1) + "\"", conn, dumpTables);
+ assertEquals(nRows, actualRowCount);
+ }
+
+ // This test is heavy and it might exhaust jenkins resources
+ @Test(timeout = 1800000)
+ @Ignore("too aggressive for jenkins builds")
+ public void testConcurrentUpsertsWithTableSplitsMerges() throws Exception {
+ Assume.assumeFalse(uncovered);
+ Map<String, Long> metricsBefore = getCdcMetricValues();
+ int nThreads = 13;
+ final int batchSize = 100;
+ final int nRows = 1451;
+ final int nIndexValues = 28;
+ final int nSplits = 3;
+ final String schemaName = "SchemaN_01-" + generateUniqueName();
+ final String tableName = "Table.Name-00_001_" + generateUniqueName();
+ final String indexName1 = "IndexTable.Name-00_001_" + generateUniqueName();
+ final String indexName2 = "IndexTable.Name-00_002_" + generateUniqueName();
+ final String indexName3 = "IndexTable.Name-00_003_" + generateUniqueName();
+ final String indexName4 = "IndexTable.Name-00_004_" + generateUniqueName();
+ final String indexName5 = "IndexTable.Name-00_005_" + generateUniqueName();
+ final String indexName6 = "IndexTable.Name-00_006_" + generateUniqueName();
+ Connection conn = DriverManager.getConnection(getUrl());
+ conn.createStatement().execute("CREATE TABLE " + "\"" + schemaName + "\".\"" + tableName + "\""
+ + "(k1 INTEGER NOT NULL, k2 INTEGER NOT NULL, v1 INTEGER, v2 INTEGER, v3 INTEGER, v4 INTEGER,"
+ + "CONSTRAINT pk PRIMARY KEY (k1,k2)) \"phoenix.index.cdc.mutations.compress.enabled\"=true");
+ conn.createStatement()
+ .execute("CREATE INDEX " + "\"" + indexName1 + "\"" + " ON " + "\"" + schemaName + "\".\""
+ + tableName + "\"" + "(v1)" + " INCLUDE(v2, v3)"
+ + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+ conn.createStatement()
+ .execute("CREATE UNCOVERED INDEX " + "\"" + indexName2 + "\"" + " ON " + "\"" + schemaName
+ + "\".\"" + tableName + "\"" + "(v2)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+ conn.createStatement()
+ .execute("CREATE INDEX " + "\"" + indexName3 + "\"" + " ON " + "\"" + schemaName + "\".\""
+ + tableName + "\"" + "(v3)" + " INCLUDE(v1, v2)"
+ + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+ conn.createStatement()
+ .execute("CREATE UNCOVERED INDEX " + "\"" + indexName4 + "\"" + " ON " + "\"" + schemaName
+ + "\".\"" + tableName + "\"" + "(v4)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+ conn.createStatement()
+ .execute("CREATE INDEX " + "\"" + indexName5 + "\"" + " ON " + "\"" + schemaName + "\".\""
+ + tableName + "\"" + "(v1, v2)" + " INCLUDE(v3, v4)"
+ + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+ conn.createStatement()
+ .execute("CREATE UNCOVERED INDEX " + "\"" + indexName6 + "\"" + " ON " + "\"" + schemaName
+ + "\".\"" + tableName + "\"" + "(v3, v1)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+
+ final CountDownLatch doneSignal = new CountDownLatch(nThreads);
+ Runnable[] runnables = new Runnable[nThreads];
+ Thread.sleep(3000);
+ long startTime = EnvironmentEdgeManager.currentTimeMillis();
+
+ ConcurrentMutationsExtendedIT.buildRunnables(nThreads, runnables,
+ "\"" + schemaName + "\".\"" + tableName + "\"", nRows, nIndexValues, batchSize, doneSignal,
+ 10000);
+ Thread splitThread = new Thread(() -> TestUtil.splitTable(getUrl(),
+ schemaName + "." + tableName, nSplits, eventual ? 3500 : 2000));
+ splitThread.start();
+ for (int i = 0; i < (nThreads - 5); i++) {
+ if (i >= (nThreads - 8)) {
+ Thread.sleep(9000);
+ }
+ Thread t = new Thread(runnables[i]);
+ t.start();
+ }
+ splitThread.join(10000);
+ Thread mergeThread =
+ new Thread(() -> TestUtil.mergeTableRegions(getUrl(), schemaName + "." + tableName, 4, 1000));
+ for (int i = nThreads - 5; i < nThreads; i++) {
+ Thread t = new Thread(runnables[i]);
+ t.start();
+ if (i == nThreads - 3) {
+ mergeThread.start();
+ }
+ }
+ mergeThread.join(10000);
+ assertTrue("Ran out of time", doneSignal.await(1500, TimeUnit.SECONDS));
+ LOGGER.info(
+ "Total upsert time in ms : " + (EnvironmentEdgeManager.currentTimeMillis() - startTime));
+ Thread.sleep(20000);
+ List<String> allIndexes = new ArrayList<>(
+ Arrays.asList(indexName1, indexName2, indexName3, indexName4, indexName5, indexName6));
+ verifyRandomIndexes(allIndexes, schemaName, tableName, conn, nRows);
+ assertCdcMetrics(metricsBefore);
+ }
+
+ @Test(timeout = 1800000)
+ public void testConcurrentMutationsWithNonIndexedColumnUpdates() throws Exception {
+ Assume.assumeFalse(uncovered);
+ Map<String, Long> metricsBefore = getCdcMetricValues();
+ final int nThreads = 3;
+ final int batchSize = 100;
+ final int nRows = 500;
+ final int nIndexValues = 23;
+ final String tableName = generateUniqueName();
+ final String coveredIndexName = generateUniqueName();
+ final String uncoveredIndexName = generateUniqueName();
+ Connection conn = DriverManager.getConnection(getUrl());
+
+ conn.createStatement()
+ .execute("CREATE TABLE " + tableName + "(k1 INTEGER NOT NULL, k2 INTEGER NOT NULL, "
+ + "v1 INTEGER, v2 INTEGER, v3 INTEGER, v4 INTEGER, v5 INTEGER, v6 INTEGER, v7 INTEGER, "
+ + "CONSTRAINT pk PRIMARY KEY (k1,k2))");
+
+ conn.createStatement().execute("CREATE INDEX " + coveredIndexName + " ON " + tableName
+ + "(v1) INCLUDE(v2)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+
+ conn.createStatement().execute("CREATE UNCOVERED INDEX " + uncoveredIndexName + " ON "
+ + tableName + "(v3)" + (eventual ? " CONSISTENCY = EVENTUAL" : ""));
+
+ runMutationPhase(tableName, nThreads, 15000, batchSize, nRows, nIndexValues, false);
+ Thread.sleep(5000);
+
+ runMutationPhase(tableName, nThreads, 13334, batchSize, nRows, nIndexValues, true);
+ Thread.sleep(5000);
+
+ runMutationPhase(tableName, nThreads, 15000, batchSize, nRows, nIndexValues, false);
+ Thread.sleep(5000);
+
+ runMutationPhase(tableName, nThreads, 15000, batchSize, nRows, nIndexValues, true);
+ Thread.sleep(20000);
+
+ if (eventual) {
+ boolean verified = false;
+ for (int attempt = 0; attempt < 40 && !verified; attempt++) {
+ Thread.sleep(10000);
+ try {
+ long coveredCount = verifyIndexTable(tableName, coveredIndexName, conn, false);
+ assertEquals(nRows, coveredCount);
+ long uncoveredCount = verifyIndexTable(tableName, uncoveredIndexName, conn, false);
+ assertEquals(nRows, uncoveredCount);
+ verified = true;
+ } catch (AssertionError e) {
+ LOGGER.info("Verification attempt {} failed: {}", attempt, e.getMessage());
+ dumpTableRows(conn, "SYSTEM.CDC_STREAM");
+ dumpTableRows(conn, "SYSTEM.IDX_CDC_TRACKER");
+ }
+ }
+ assertTrue("Index verification failed after retries", verified);
+ } else {
+ long coveredCount = verifyIndexTable(tableName, coveredIndexName, conn);
+ assertEquals(nRows, coveredCount);
+ long uncoveredCount = verifyIndexTable(tableName, uncoveredIndexName, conn);
+ assertEquals(nRows, uncoveredCount);
+ }
+ assertCdcMetrics(metricsBefore);
+ }
+
+ private void runMutationPhase(String tableName, int nThreads, int mutationsPerThread,
+ int batchSize, int nRows, int nIndexValues, boolean includeIndexedColumns) throws Exception {
+ CountDownLatch doneSignal = new CountDownLatch(nThreads);
+ for (int t = 0; t < nThreads; t++) {
+ new Thread(() -> {
+ try {
+ Connection c = DriverManager.getConnection(getUrl());
+ ThreadLocalRandom rand = ThreadLocalRandom.current();
+ for (int j = 0; j < mutationsPerThread; j++) {
+ if (includeIndexedColumns) {
+ c.createStatement()
+ .execute("UPSERT INTO " + tableName + " VALUES (" + (j % nRows) + ", 0, "
+ + randVal(rand, nIndexValues) + ", " + randVal(rand) + ", "
+ + randVal(rand, nIndexValues) + ", " + randVal(rand) + ", " + randVal(rand) + ", "
+ + randVal(rand) + ", " + randVal(rand) + ")");
+ } else {
+ c.createStatement()
+ .execute("UPSERT INTO " + tableName + " (k1, k2, v4, v5, v6, v7) VALUES ("
+ + (j % nRows) + ", 0, " + randVal(rand) + ", " + randVal(rand) + ", "
+ + randVal(rand) + ", " + randVal(rand) + ")");
+ }
+ if ((j % batchSize) == 0) {
+ c.commit();
+ }
+ }
+ c.commit();
+ } catch (SQLException e) {
+ LOGGER.warn("Mutation phase exception: {}", e.getMessage());
+ } finally {
+ doneSignal.countDown();
+ }
+ }).start();
+ }
+ assertTrue("Mutation phase timed out", doneSignal.await(350, TimeUnit.SECONDS));
+ }
+
+ private static String randVal(ThreadLocalRandom rand) {
+ return rand.nextBoolean() ? "null" : Integer.toString(rand.nextInt());
+ }
+
+ private static String randVal(ThreadLocalRandom rand, int bound) {
+ return rand.nextBoolean() ? "null" : Integer.toString(rand.nextInt() % bound);
+ }
+
+ private Map<String, Long> getCdcMetricValues() {
+ MetricsIndexCDCConsumerSourceImpl source =
+ (MetricsIndexCDCConsumerSourceImpl) MetricsIndexerSourceFactory.getInstance()
+ .getIndexCDCConsumerSource();
+ Map<String, Long> values = new HashMap<>();
+ values.put(CDC_BATCH_COUNT, source.getMetricsRegistry().getCounter(CDC_BATCH_COUNT, 0).value());
+ values.put(CDC_MUTATION_COUNT,
+ source.getMetricsRegistry().getCounter(CDC_MUTATION_COUNT, 0).value());
+ values.put(CDC_BATCH_PROCESS_TIME,
+ source.getMetricsRegistry().getHistogram(CDC_BATCH_PROCESS_TIME).getCount());
+ return values;
+ }
+
+ private void assertCdcMetrics(Map<String, Long> before) {
+ Map<String, Long> after = getCdcMetricValues();
+ long batchDelta = after.get(CDC_BATCH_COUNT) - before.get(CDC_BATCH_COUNT);
+ long mutationDelta = after.get(CDC_MUTATION_COUNT) - before.get(CDC_MUTATION_COUNT);
+ long histogramDelta = after.get(CDC_BATCH_PROCESS_TIME) - before.get(CDC_BATCH_PROCESS_TIME);
+ LOGGER.info(
+ "CDC metrics — before: {}, after: {}, delta — batchCount: {}, "
+ + "mutationCount: {}, batchProcessTime samples: {}",
+ before, after, batchDelta, mutationDelta, histogramDelta);
+ if (eventual) {
+ assertTrue("Expected cdcBatchCount to increase by at least 1, got " + batchDelta,
+ batchDelta >= 1);
+ assertTrue("Expected cdcMutationCount to increase by at least 1000, got " + mutationDelta,
+ mutationDelta >= 1000);
+ assertTrue("Expected cdcBatchProcessTime histogram samples to increase by at least 1, got "
+ + histogramDelta, histogramDelta >= 1);
+ } else {
+ assertEquals("Expected no CDC batch processing for synchronous indexes", 0, batchDelta);
+ assertEquals("Expected no CDC mutations for synchronous indexes", 0, mutationDelta);
+ assertEquals("Expected no CDC batch process time samples for synchronous indexes", 0,
+ histogramDelta);
+ }
+ }
+
+ private void dumpTableRows(Connection conn, String tableName) throws SQLException {
+ LOGGER.info("Dumping {} table:", tableName);
+ try (ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName)) {
+ ResultSetMetaData metaData = rs.getMetaData();
+ int columnCount = metaData.getColumnCount();
+ StringBuilder header = new StringBuilder();
+ for (int i = 1; i <= columnCount; i++) {
+ if (i > 1) {
+ header.append("\t|\t");
+ }
+ header.append(metaData.getColumnName(i));
+ }
+ LOGGER.info("{}", header);
+ LOGGER.info("_____________________________________________________________________________");
+ while (rs.next()) {
+ StringBuilder row = new StringBuilder();
+ for (int i = 1; i <= columnCount; i++) {
+ if (i > 1) {
+ row.append("\t|\t");
+ }
+ row.append(rs.getString(i));
+ }
+ LOGGER.info("{}", row);
+ }
+ }
+ }
+
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java
index 761c8658eb..3b49514b99 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java
@@ -17,7 +17,12 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_BATCH_SIZE;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
import java.util.Map;
+import org.apache.phoenix.coprocessor.PhoenixMasterObserver;
import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
import org.apache.phoenix.hbase.index.IndexRegionObserver;
import org.apache.phoenix.query.QueryServices;
@@ -34,20 +39,27 @@ import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
@Category(NeedsOwnMiniClusterTest.class)
public class ConcurrentMutationsLazyPostBatchWriteIT extends ConcurrentMutationsExtendedIT {
- public ConcurrentMutationsLazyPostBatchWriteIT(boolean uncovered) {
- super(uncovered);
+ public ConcurrentMutationsLazyPostBatchWriteIT(boolean uncovered, boolean eventual) {
+ super(uncovered, eventual);
Assume.assumeFalse("Only covered index supports lazy post batch write mode", uncovered);
}
@BeforeClass
public static synchronized void doSetup() throws Exception {
- Map<String, String> props = Maps.newHashMapWithExpectedSize(4);
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(8);
props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
props.put(IndexRegionObserver.INDEX_LAZY_POST_BATCH_WRITE, "true");
props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
Integer.toString(MAX_LOOKBACK_AGE));
props.put("hbase.rowlock.wait.duration", "100");
props.put("phoenix.index.concurrent.wait.duration.ms", "10");
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ props.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
+ props.put(INDEX_CDC_CONSUMER_BATCH_SIZE, Integer.toString(4500));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(2500));
+ props.put("hbase.coprocessor.master.classes", PhoenixMasterObserver.class.getName());
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString());
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredEventualGenerateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredEventualGenerateIT.java
new file mode 100644
index 0000000000..24511b46db
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredEventualGenerateIT.java
@@ -0,0 +1,73 @@
+/*
+ * 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.end2end;
+
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_BATCH_SIZE;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_PARENT_PROGRESS_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import org.apache.phoenix.coprocessor.PhoenixMasterObserver;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.BeforeClass;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class ConcurrentMutationsUncoveredEventualGenerateIT
+ extends ConcurrentMutationsExtendedIndexIT {
+
+ private static final int MAX_LOOKBACK_AGE = 1000000;
+
+ public ConcurrentMutationsUncoveredEventualGenerateIT(boolean uncovered, boolean eventual) {
+ super(uncovered, eventual);
+ }
+
+ @BeforeClass
+ public static synchronized void doSetup() throws Exception {
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(10);
+ props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
+ props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
+ Integer.toString(MAX_LOOKBACK_AGE));
+ props.put("hbase.rowlock.wait.duration", "100");
+ props.put("phoenix.index.concurrent.wait.duration.ms", "10");
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ props.put(INDEX_CDC_CONSUMER_BATCH_SIZE, Integer.toString(4500));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(5000));
+ props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(200));
+ props.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
+ props.put("hbase.coprocessor.master.classes", PhoenixMasterObserver.class.getName());
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString());
+ props.put(INDEX_CDC_CONSUMER_PARENT_PROGRESS_PAUSE_MS, Integer.toString(1000));
+ setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+ }
+
+ @Parameterized.Parameters(name = "uncovered={0}, eventual={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { { true, true } });
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredEventualIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredEventualIT.java
new file mode 100644
index 0000000000..9887f8c546
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredEventualIT.java
@@ -0,0 +1,36 @@
+/*
+ * 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.end2end;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class ConcurrentMutationsUncoveredEventualIT extends ConcurrentMutationsExtendedIndexIT {
+
+ public ConcurrentMutationsUncoveredEventualIT(boolean uncovered, boolean eventual) {
+ super(uncovered, eventual);
+ }
+
+ @Parameterized.Parameters(name = "uncovered={0}, eventual={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { { true, true } });
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredNonEventualIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredNonEventualIT.java
new file mode 100644
index 0000000000..70bb2ac995
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsUncoveredNonEventualIT.java
@@ -0,0 +1,36 @@
+/*
+ * 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.end2end;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class ConcurrentMutationsUncoveredNonEventualIT extends ConcurrentMutationsExtendedIndexIT {
+
+ public ConcurrentMutationsUncoveredNonEventualIT(boolean uncovered, boolean eventual) {
+ super(uncovered, eventual);
+ }
+
+ @Parameterized.Parameters(name = "uncovered={0}, eventual={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] { { true, false } });
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexMaintainerConsistencyIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexMaintainerConsistencyIT.java
new file mode 100644
index 0000000000..26d231f0ea
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexMaintainerConsistencyIT.java
@@ -0,0 +1,237 @@
+/*
+ * 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.end2end;
+
+import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.util.List;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Admin;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.phoenix.coprocessor.generated.ServerCachingProtos;
+import org.apache.phoenix.index.IndexMaintainer;
+import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.PTableKey;
+import org.apache.phoenix.schema.PTableType;
+import org.apache.phoenix.schema.RowKeySchema;
+import org.apache.phoenix.schema.types.IndexConsistency;
+import org.apache.phoenix.util.CDCUtil;
+import org.apache.phoenix.util.PropertiesUtil;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * IndexMaintainer consistency tests.
+ */
+@Category(ParallelStatsDisabledTest.class)
+public class IndexMaintainerConsistencyIT extends ParallelStatsDisabledIT {
+
+ @After
+ public synchronized void afterTest() throws Exception {
+ boolean refCountLeaked = isAnyStoreRefCountLeaked();
+ Assert.assertFalse("refCount leaked", refCountLeaked);
+ }
+
+ @Test
+ public void testIndexMaintainerConsistencyFromPTable() throws Exception {
+ String dataTableName = "XYZ.\"tablENam1.1_0-001\"";
+ String dataTableNameWithoutQuotes = "XYZ.tablENam1.1_0-001";
+ String strongIndexName1 = "\"idXNam1.12._0-001\"";
+ String strongIndexName2 = "\"idXNam1.12._0-002\"";
+ String eventualIndexName1 = "\"idXNam2.12._0-002\"";
+ String eventualIndexName2 = "\"idXNam3.12._0-003\"";
+
+ try (Connection conn =
+ DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES))) {
+ PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+ conn.createStatement().execute(
+ "CREATE TABLE " + dataTableName + " (id INTEGER PRIMARY KEY, name VARCHAR, city VARCHAR)"
+ + " IS_STRICT_TTL=false, UPDATE_CACHE_FREQUENCY=2, "
+ + "\"phoenix.max.lookback.age.seconds\"=97200");
+ conn.createStatement().execute(
+ "CREATE INDEX " + strongIndexName1 + " ON " + dataTableName + " (name) INCLUDE (city)");
+ conn.createStatement().execute("CREATE INDEX " + strongIndexName2 + " ON " + dataTableName
+ + " (name) INCLUDE (city) CONSISTENCY=STRONG");
+ conn.createStatement().execute("CREATE INDEX " + eventualIndexName1 + " ON " + dataTableName
+ + " (name) INCLUDE (city) CONSISTENCY=EVENTUAL");
+ conn.createStatement().execute("CREATE UNCOVERED INDEX " + eventualIndexName2 + " ON "
+ + dataTableName + " (city, name) CONSISTENCY=EVENTUAL");
+ PTable strongIndex1 =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), "XYZ.idXNam1.12._0-001"));
+ PTable strongIndex2 =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), "XYZ.idXNam1.12._0-002"));
+ PTable eventualIndex1 =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), "XYZ.idXNam2.12._0-002"));
+ PTable eventualIndex2 =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), "XYZ.idXNam3.12._0-003"));
+ PTable dataTable =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), dataTableNameWithoutQuotes));
+ assertNull(strongIndex1.getIndexConsistency());
+ assertEquals(IndexConsistency.STRONG, strongIndex2.getIndexConsistency());
+ assertEquals(IndexConsistency.EVENTUAL, eventualIndex1.getIndexConsistency());
+ assertEquals(IndexConsistency.EVENTUAL, eventualIndex2.getIndexConsistency());
+ assertEquals(2, strongIndex1.getUpdateCacheFrequency());
+ assertEquals(2, strongIndex2.getUpdateCacheFrequency());
+ assertEquals(2, eventualIndex1.getUpdateCacheFrequency());
+ assertEquals(2, eventualIndex2.getUpdateCacheFrequency());
+ assertEquals(2, dataTable.getUpdateCacheFrequency());
+ assertFalse(eventualIndex1.isStrictTTL());
+ assertFalse(eventualIndex2.isStrictTTL());
+ assertFalse(strongIndex1.isStrictTTL());
+ assertFalse(strongIndex2.isStrictTTL());
+ assertFalse(dataTable.isStrictTTL());
+ assertNull(dataTable.getIndexConsistency());
+
+ verifyTableDescriptor(pconn, strongIndex1, strongIndex2, eventualIndex1, eventualIndex2,
+ dataTable);
+
+ IndexMaintainer strongMaintainer1 = strongIndex1.getIndexMaintainer(dataTable, pconn);
+ IndexMaintainer strongMaintainer2 = strongIndex2.getIndexMaintainer(dataTable, pconn);
+ IndexMaintainer eventualMaintainer1 = eventualIndex1.getIndexMaintainer(dataTable, pconn);
+ IndexMaintainer eventualMaintainer2 = eventualIndex2.getIndexMaintainer(dataTable, pconn);
+
+ assertNotNull(strongMaintainer1);
+ assertNotNull(strongMaintainer2);
+ assertNotNull(eventualMaintainer1);
+ assertNotNull(eventualMaintainer2);
+
+ verifyIndexMaintainerConsistency(strongMaintainer1, null, dataTable);
+ verifyIndexMaintainerConsistency(strongMaintainer2, IndexConsistency.STRONG, dataTable);
+ verifyIndexMaintainerConsistency(eventualMaintainer1, IndexConsistency.EVENTUAL, dataTable);
+ verifyIndexMaintainerConsistency(eventualMaintainer2, IndexConsistency.EVENTUAL, dataTable);
+
+ RowKeySchema schema = RowKeySchema.EMPTY_SCHEMA;
+ IndexMaintainer maintainer = new IndexMaintainer(schema, false) {
+ };
+
+ assertNull(maintainer.getIndexConsistency());
+
+ assertTrue("Data table should have CDC index after creating eventually consistent index",
+ CDCUtil.hasCDCIndex(dataTable));
+ assertTrue("Data table should have active CDC index", CDCUtil.hasActiveCDCIndex(dataTable));
+
+ String cdcObjectName = CDCUtil.getCDCObjectName(dataTable, false);
+ assertNotNull("CDC object name should be retrievable from data table", cdcObjectName);
+
+ String expectedCDCName = "CDC_tablENam1.1_0-001";
+ assertEquals("CDC object name should follow expected convention", expectedCDCName,
+ cdcObjectName);
+
+ PTable cdcIndex = CDCUtil.getActiveCDCIndex(dataTable);
+ String expectedCDCIndexName = CDCUtil.getCDCIndexName(cdcObjectName);
+ assertEquals("CDC index name should follow expected convention", expectedCDCIndexName,
+ cdcIndex.getTableName().getString());
+
+ PTable cdcTable = pconn.getTable(new PTableKey(pconn.getTenantId(), "XYZ." + cdcObjectName));
+ assertEquals("CDC table should have CDC type", PTableType.CDC, cdcTable.getType());
+
+ List<PTable> indexes = dataTable.getIndexes();
+ assertEquals("Data table should have total 5 indexes", 5, indexes.size());
+ }
+ }
+
+ @Test
+ public void testAlterIndexConsistency() throws Exception {
+ String dataTableName = "\"ScheMa1234-_\".\"alterTable1.1_0-001\"";
+ String dataTableNameWithoutQuotes = "ScheMa1234-_.alterTable1.1_0-001";
+ String indexName = "\"alterIdx1.12._0-001\"";
+ String fullIdxNameWithoutQuotes = "ScheMa1234-_.alterIdx1.12._0-001";
+
+ try (Connection conn =
+ DriverManager.getConnection(getUrl(), PropertiesUtil.deepCopy(TEST_PROPERTIES))) {
+ PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
+
+ conn.createStatement().execute(
+ "CREATE TABLE " + dataTableName + " (id INTEGER PRIMARY KEY, name VARCHAR, city VARCHAR)");
+ conn.createStatement()
+ .execute("CREATE INDEX " + indexName + " ON " + dataTableName + " (name) INCLUDE (city)");
+
+ PTable strongIndex =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), fullIdxNameWithoutQuotes));
+ PTable dataTable =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), dataTableNameWithoutQuotes));
+ assertNull(strongIndex.getIndexConsistency());
+ assertFalse("Data table should not have CDC index initially", CDCUtil.hasCDCIndex(dataTable));
+ verifyTableDescriptor(pconn, strongIndex, dataTable);
+
+ IndexMaintainer strongMaintainer = strongIndex.getIndexMaintainer(dataTable, pconn);
+ verifyIndexMaintainerConsistency(strongMaintainer, null, dataTable);
+
+ conn.createStatement()
+ .execute("ALTER INDEX " + indexName + " ON " + dataTableName + " CONSISTENCY=EVENTUAL");
+ PTable eventualIndex =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), fullIdxNameWithoutQuotes));
+ dataTable = pconn.getTable(new PTableKey(pconn.getTenantId(), dataTableNameWithoutQuotes));
+
+ assertEquals(IndexConsistency.EVENTUAL, eventualIndex.getIndexConsistency());
+ assertTrue("Data table should have CDC index after altering to eventual consistency",
+ CDCUtil.hasCDCIndex(dataTable));
+ verifyTableDescriptor(pconn, eventualIndex, dataTable);
+
+ IndexMaintainer eventualMaintainer = eventualIndex.getIndexMaintainer(dataTable, pconn);
+ verifyIndexMaintainerConsistency(eventualMaintainer, IndexConsistency.EVENTUAL, dataTable);
+ conn.createStatement()
+ .execute("ALTER INDEX " + indexName + " ON " + dataTableName + " CONSISTENCY=STRONG");
+ PTable strongIdx =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), fullIdxNameWithoutQuotes));
+ assertEquals(IndexConsistency.STRONG, strongIdx.getIndexConsistency());
+ verifyTableDescriptor(pconn, strongIdx, dataTable);
+ IndexMaintainer strongIdxMaintainer = strongIdx.getIndexMaintainer(dataTable, pconn);
+ verifyIndexMaintainerConsistency(strongIdxMaintainer, IndexConsistency.STRONG, dataTable);
+ }
+ }
+
+ private void verifyTableDescriptor(PhoenixConnection pconn, PTable... tables) throws Exception {
+ try (Admin admin = pconn.getQueryServices().getAdmin()) {
+ for (PTable table : tables) {
+ TableDescriptor td =
+ admin.getDescriptor(TableName.valueOf(table.getPhysicalName().getBytes()));
+ assertNull(
+ "CONSISTENCY should be present in table descriptor for " + table.getName().getString(),
+ td.getValue("CONSISTENCY"));
+ }
+ }
+ }
+
+ private void verifyIndexMaintainerConsistency(IndexMaintainer maintainer,
+ IndexConsistency expectedConsistency, PTable dataTable) throws IOException {
+ assertEquals(expectedConsistency, maintainer.getIndexConsistency());
+ ServerCachingProtos.IndexMaintainer proto = IndexMaintainer.toProto(maintainer);
+ if (expectedConsistency != null) {
+ assertTrue(proto.hasIndexConsistency());
+ assertEquals(expectedConsistency.toString(), proto.getIndexConsistency());
+ } else {
+ assertFalse(proto.hasIndexConsistency());
+ }
+ IndexMaintainer deserializedMaintainer = IndexMaintainer.fromProto(proto,
+ dataTable.getRowKeySchema(), dataTable.getBucketNum() != null);
+ assertEquals(expectedConsistency, deserializedMaintainer.getIndexConsistency());
+ }
+
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexEventualGenerateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexEventualGenerateIT.java
new file mode 100644
index 0000000000..533427174d
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexEventualGenerateIT.java
@@ -0,0 +1,93 @@
+/*
+ * 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.end2end;
+
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.query.QueryServicesOptions;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.BeforeClass;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class IndexToolForNonTxGlobalIndexEventualGenerateIT extends IndexToolForNonTxGlobalIndexIT {
+
+ public IndexToolForNonTxGlobalIndexEventualGenerateIT(boolean mutable, boolean singleCell) {
+ super(mutable, singleCell);
+ if (indexDDLOptions.trim().isEmpty()) {
+ indexDDLOptions = " CONSISTENCY=EVENTUAL";
+ } else {
+ indexDDLOptions = " CONSISTENCY=EVENTUAL," + indexDDLOptions;
+ }
+ }
+
+ @Override
+ protected void waitForEventualConsistency() throws Exception {
+ Thread.sleep(18000);
+ }
+
+ @BeforeClass
+ public static synchronized void setup() throws Exception {
+ Map<String, String> serverProps = Maps.newHashMapWithExpectedSize(13);
+ serverProps.put(QueryServices.STATS_GUIDEPOST_WIDTH_BYTES_ATTRIB, Long.toString(20));
+ serverProps.put(QueryServices.MAX_SERVER_METADATA_CACHE_TIME_TO_LIVE_MS_ATTRIB,
+ Long.toString(5));
+ serverProps.put(QueryServices.EXTRA_JDBC_ARGUMENTS_ATTRIB,
+ QueryServicesOptions.DEFAULT_EXTRA_JDBC_ARGUMENTS);
+ serverProps.put(QueryServices.INDEX_REBUILD_PAGE_SIZE_IN_ROWS, Long.toString(8));
+ serverProps.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
+ Long.toString(MAX_LOOKBACK_AGE));
+ serverProps.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ serverProps.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ serverProps.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB,
+ Long.toString(0));
+ serverProps.put("hbase.regionserver.rpc.retry.interval", Long.toString(0));
+ serverProps.put("hbase.procedure.remote.dispatcher.delay.msec", Integer.toString(0));
+ serverProps.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(2000));
+ serverProps.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(5));
+ serverProps.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString());
+ serverProps.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
+ Map<String, String> clientProps = Maps.newHashMapWithExpectedSize(5);
+ clientProps.put(QueryServices.USE_STATS_FOR_PARALLELIZATION, Boolean.toString(true));
+ clientProps.put(QueryServices.STATS_UPDATE_FREQ_MS_ATTRIB, Long.toString(5));
+ clientProps.put(QueryServices.TRANSACTIONS_ENABLED, Boolean.TRUE.toString());
+ clientProps.put(QueryServices.FORCE_ROW_KEY_ORDER_ATTRIB, Boolean.TRUE.toString());
+ clientProps.put(QueryServices.SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED_ATTRIB,
+ Boolean.TRUE.toString());
+ destroyDriver();
+ setUpTestDriver(new ReadOnlyProps(serverProps.entrySet().iterator()),
+ new ReadOnlyProps(clientProps.entrySet().iterator()));
+ getUtility().getConfiguration().set(QueryServices.INDEX_REBUILD_RPC_RETRIES_COUNTER, "1");
+ }
+
+ @Parameterized.Parameters(name = "mutable={0}, singleCellIndex={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] { { true, true }, { true, false }, { false, true }, { false, false } });
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexEventualIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexEventualIT.java
new file mode 100644
index 0000000000..737be134cd
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexEventualIT.java
@@ -0,0 +1,93 @@
+/*
+ * 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.end2end;
+
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.query.QueryServicesOptions;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.BeforeClass;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class IndexToolForNonTxGlobalIndexEventualIT extends IndexToolForNonTxGlobalIndexIT {
+
+ public IndexToolForNonTxGlobalIndexEventualIT(boolean mutable, boolean singleCell) {
+ super(mutable, singleCell);
+ if (indexDDLOptions.trim().isEmpty()) {
+ indexDDLOptions = " CONSISTENCY=EVENTUAL";
+ } else {
+ indexDDLOptions = " CONSISTENCY=EVENTUAL," + indexDDLOptions;
+ }
+ }
+
+ @Override
+ protected void waitForEventualConsistency() throws Exception {
+ Thread.sleep(15000);
+ }
+
+ @BeforeClass
+ public static synchronized void setup() throws Exception {
+ Map<String, String> serverProps = Maps.newHashMapWithExpectedSize(12);
+ serverProps.put(QueryServices.STATS_GUIDEPOST_WIDTH_BYTES_ATTRIB, Long.toString(20));
+ serverProps.put(QueryServices.MAX_SERVER_METADATA_CACHE_TIME_TO_LIVE_MS_ATTRIB,
+ Long.toString(5));
+ serverProps.put(QueryServices.EXTRA_JDBC_ARGUMENTS_ATTRIB,
+ QueryServicesOptions.DEFAULT_EXTRA_JDBC_ARGUMENTS);
+ serverProps.put(QueryServices.INDEX_REBUILD_PAGE_SIZE_IN_ROWS, Long.toString(8));
+ serverProps.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
+ Long.toString(MAX_LOOKBACK_AGE));
+ serverProps.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ serverProps.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ serverProps.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB,
+ Long.toString(0));
+ serverProps.put("hbase.regionserver.rpc.retry.interval", Long.toString(0));
+ serverProps.put("hbase.procedure.remote.dispatcher.delay.msec", Integer.toString(0));
+ serverProps.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(2000));
+ serverProps.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(5));
+ serverProps.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
+ serverProps.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.TRUE.toString());
+ Map<String, String> clientProps = Maps.newHashMapWithExpectedSize(5);
+ clientProps.put(QueryServices.USE_STATS_FOR_PARALLELIZATION, Boolean.toString(true));
+ clientProps.put(QueryServices.STATS_UPDATE_FREQ_MS_ATTRIB, Long.toString(5));
+ clientProps.put(QueryServices.TRANSACTIONS_ENABLED, Boolean.TRUE.toString());
+ clientProps.put(QueryServices.FORCE_ROW_KEY_ORDER_ATTRIB, Boolean.TRUE.toString());
+ clientProps.put(QueryServices.SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED_ATTRIB,
+ Boolean.TRUE.toString());
+ destroyDriver();
+ setUpTestDriver(new ReadOnlyProps(serverProps.entrySet().iterator()),
+ new ReadOnlyProps(clientProps.entrySet().iterator()));
+ getUtility().getConfiguration().set(QueryServices.INDEX_REBUILD_RPC_RETRIES_COUNTER, "1");
+ }
+
+ @Parameterized.Parameters(name = "mutable={0}, singleCellIndex={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] { { true, true }, { true, false }, { false, true }, { false, false } });
+ }
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexIT.java
index d6061a8380..5bf5c905ce 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolForNonTxGlobalIndexIT.java
@@ -128,11 +128,11 @@ import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
public static final int MAX_LOOKBACK_AGE = 3600;
- private final String tableDDLOptions;
+ protected final String tableDDLOptions;
private final boolean useSnapshot = false;
- private final boolean mutable;
- private final String indexDDLOptions;
+ protected final boolean mutable;
+ protected String indexDDLOptions;
private boolean singleCell;
@Rule
@@ -194,6 +194,9 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
getUtility().getConfiguration().set(QueryServices.INDEX_REBUILD_RPC_RETRIES_COUNTER, "1");
}
+ protected void waitForEventualConsistency() throws Exception {
+ }
+
@After
public void cleanup() throws Exception {
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
@@ -239,15 +242,18 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
assertEquals(NROWS, indexTool.getJob().getCounters().findCounter(INPUT_RECORDS).getValue());
assertTrue("Index rebuild failed!", indexTool.getJob().isSuccessful());
TestUtil.assertIndexState(conn, indexTableFullName, PIndexState.ACTIVE, null);
+ waitForEventualConsistency();
long actualRowCount =
IndexScrutiny.scrutinizeIndex(conn, dataTableFullName, indexTableFullName);
assertEquals(NROWS, actualRowCount);
IndexToolIT.setEveryNthRowWithNull(NROWS, 5, stmt);
conn.commit();
+ waitForEventualConsistency();
actualRowCount = IndexScrutiny.scrutinizeIndex(conn, dataTableFullName, indexTableFullName);
assertEquals(NROWS, actualRowCount);
IndexToolIT.setEveryNthRowWithNull(NROWS, 7, stmt);
conn.commit();
+ waitForEventualConsistency();
actualRowCount = IndexScrutiny.scrutinizeIndex(conn, dataTableFullName, indexTableFullName);
assertEquals(NROWS, actualRowCount);
actualRowCount = IndexScrutiny.scrutinizeIndex(conn, dataTableFullName, indexTableFullName);
@@ -444,6 +450,7 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
}
assertEquals(0, indexTool.getJob().getCounters()
.findCounter(BEFORE_REBUILD_BEYOND_MAXLOOKBACK_INVALID_INDEX_ROW_COUNT).getValue());
+ waitForEventualConsistency();
long actualRowCount =
IndexScrutiny.scrutinizeIndex(conn, dataTableFullName, indexTableFullName);
assertEquals(NROWS, actualRowCount);
@@ -453,6 +460,7 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
IndexToolIT.upsertRow(stmt1, i);
}
conn.commit();
+ waitForEventualConsistency();
indexTool = IndexToolIT.runIndexTool(useSnapshot, schemaName, dataTableName, indexTableName,
null, 0, IndexTool.IndexVerifyType.BOTH, new String[0]);
assertEquals(2 * NROWS,
@@ -491,6 +499,7 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
.findCounter(AFTER_REBUILD_BEYOND_MAXLOOKBACK_MISSING_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters()
.findCounter(AFTER_REBUILD_BEYOND_MAXLOOKBACK_INVALID_INDEX_ROW_COUNT).getValue());
+ waitForEventualConsistency();
actualRowCount = IndexScrutiny.scrutinizeIndex(conn, dataTableFullName, indexTableFullName);
assertEquals(2 * NROWS, actualRowCount);
}
@@ -553,6 +562,7 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
.findCounter(BEFORE_REBUILD_OLD_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters()
.findCounter(BEFORE_REBUILD_UNKNOWN_INDEX_ROW_COUNT).getValue());
+ waitForEventualConsistency();
long actualRowCount =
IndexScrutiny.scrutinizeIndex(conn, dataTableFullName, indexTableFullName);
assertEquals(N_ROWS, actualRowCount);
@@ -579,6 +589,7 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
null, 0, IndexTool.IndexVerifyType.BEFORE, new String[0]);
assertEquals(0, indexTool.getJob().getCounters()
.findCounter(BEFORE_REBUILD_OLD_INDEX_ROW_COUNT).getValue());
+ waitForEventualConsistency();
actualRowCount = IndexScrutiny.scrutinizeIndex(conn, dataTableFullName, indexTableFullName);
assertEquals(N_ROWS, actualRowCount);
}
@@ -586,6 +597,8 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
@Test
public void testIndexToolVerifyBeforeAndBothOptions() throws Exception {
+ Assume.assumeFalse("View indexes do not support CONSISTENCY=EVENTUAL",
+ indexDDLOptions.contains("CONSISTENCY=EVENTUAL"));
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
String schemaName = generateUniqueName();
@@ -629,6 +642,8 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
@Test
public void testIndexToolVerifyAfterOption() throws Exception {
+ Assume.assumeFalse("View indexes do not support CONSISTENCY=EVENTUAL",
+ indexDDLOptions.contains("CONSISTENCY=EVENTUAL"));
Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
String schemaName = generateUniqueName();
@@ -842,6 +857,8 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
@Test
public void testIndexToolForIncrementalVerify() throws Exception {
+ Assume.assumeFalse("View indexes do not support CONSISTENCY=EVENTUAL",
+ indexDDLOptions.contains("CONSISTENCY=EVENTUAL"));
ManualEnvironmentEdge customEdge = new ManualEnvironmentEdge();
String schemaName = generateUniqueName();
String dataTableName = generateUniqueName();
@@ -977,6 +994,8 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
@Test
public void testIndexToolForIncrementalVerify_viewIndex() throws Exception {
+ Assume.assumeFalse("View indexes do not support CONSISTENCY=EVENTUAL",
+ indexDDLOptions.contains("CONSISTENCY=EVENTUAL"));
ManualEnvironmentEdge customeEdge = new ManualEnvironmentEdge();
String schemaName = generateUniqueName();
String dataTableName = generateUniqueName();
@@ -1387,6 +1406,8 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
@Test
public void testUpdatablePKFilterViewIndexRebuild() throws Exception {
+ Assume.assumeFalse("View indexes do not support CONSISTENCY=EVENTUAL",
+ indexDDLOptions.contains("CONSISTENCY=EVENTUAL"));
if (!mutable) {
return;
}
@@ -1456,6 +1477,8 @@ public class IndexToolForNonTxGlobalIndexIT extends BaseTest {
@Test
public void testUpdatableNonPkFilterViewIndexRebuild() throws Exception {
+ Assume.assumeFalse("View indexes do not support CONSISTENCY=EVENTUAL",
+ indexDDLOptions.contains("CONSISTENCY=EVENTUAL"));
if (!mutable) {
return;
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java
index db0ada9832..23f083f096 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/IndexToolIT.java
@@ -1155,15 +1155,30 @@ public class IndexToolIT extends BaseTest {
public static long verifyIndexTable(String fullTableName, String fullIndexName, Connection conn)
throws Exception {
+ return verifyIndexTable(fullTableName, fullIndexName, conn, true);
+ }
+
+ public static long verifyIndexTable(String fullTableName, String fullIndexName, Connection conn,
+ boolean dumpTables) throws Exception {
String schemaName = SchemaUtil.getSchemaNameFromFullName(fullTableName);
String tableName = SchemaUtil.getTableNameFromFullName(fullTableName);
String indexName = SchemaUtil.getTableNameFromFullName(fullIndexName);
+ String fullTableNameNormalized = null;
+ String fullIndexNameNormalized = null;
+ if (schemaName != null && !schemaName.isEmpty()) {
+ fullTableNameNormalized = SchemaUtil.normalizeIdentifier(schemaName) + "."
+ + SchemaUtil.normalizeIdentifier(tableName);
+ fullIndexNameNormalized = SchemaUtil.normalizeIdentifier(schemaName) + "."
+ + SchemaUtil.normalizeIdentifier(indexName);
+ }
try {
// This checks the state of every raw index row without rebuilding any row
IndexTool indexTool = IndexToolIT.runIndexTool(false, schemaName, tableName, indexName, null,
0, IndexTool.IndexVerifyType.ONLY);
- TestUtil.dumpTable(conn,
- TableName.valueOf(IndexVerificationOutputRepository.getOutputTableName()));
+ if (dumpTables) {
+ TestUtil.dumpTable(conn,
+ TableName.valueOf(IndexVerificationOutputRepository.getOutputTableName()));
+ }
Counters counters = indexTool.getJob().getCounters();
LOGGER.info(counters.toString());
assertEquals(0, counters.findCounter(REBUILT_INDEX_ROW_COUNT).getValue());
@@ -1193,7 +1208,8 @@ public class IndexToolIT extends BaseTest {
// The index scrutiny run will trigger index repair on all unverified rows, and they will be
// repaired or
// deleted (since the age threshold is set to zero ms for these tests
- PTable pIndexTable = conn.unwrap(PhoenixConnection.class).getTable(fullIndexName);
+ PTable pIndexTable = conn.unwrap(PhoenixConnection.class)
+ .getTable(fullIndexNameNormalized != null ? fullIndexNameNormalized : fullIndexName);
if (pIndexTable.getIndexType() != PTable.IndexType.UNCOVERED_GLOBAL) {
assertEquals(0, counters.findCounter(BEFORE_REBUILD_UNVERIFIED_INDEX_ROW_COUNT).getValue());
}
@@ -1232,14 +1248,19 @@ public class IndexToolIT extends BaseTest {
counters.findCounter(AFTER_REBUILD_BEYOND_MAXLOOKBACK_MISSING_INDEX_ROW_COUNT).getValue());
assertEquals(0,
counters.findCounter(AFTER_REBUILD_BEYOND_MAXLOOKBACK_INVALID_INDEX_ROW_COUNT).getValue());
- pConn.getQueryServices().clearTableRegionCache(TableName.valueOf(fullIndexName));
+ pConn.getQueryServices().clearTableRegionCache(TableName
+ .valueOf(fullIndexNameNormalized != null ? fullIndexNameNormalized : fullIndexName));
long actualRowCountAfterCompaction =
IndexScrutiny.scrutinizeIndex(conn, fullTableName, fullIndexName);
assertEquals(actualRowCount, actualRowCountAfterCompaction);
return actualRowCount;
} catch (AssertionError e) {
- TestUtil.dumpTable(conn, TableName.valueOf(fullTableName));
- TestUtil.dumpTable(conn, TableName.valueOf(fullIndexName));
+ if (dumpTables) {
+ TestUtil.dumpTable(conn, TableName
+ .valueOf(fullTableNameNormalized != null ? fullTableNameNormalized : fullTableName));
+ TestUtil.dumpTable(conn, TableName
+ .valueOf(fullIndexNameNormalized != null ? fullIndexNameNormalized : fullIndexName));
+ }
throw e;
}
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MetadataServerConnectionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MetadataServerConnectionsIT.java
index ecebf38016..bebabc9bbf 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MetadataServerConnectionsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MetadataServerConnectionsIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.query.QueryServices.DISABLE_VIEW_SUBTREE_VALIDATION;
import static org.apache.phoenix.query.QueryServicesTestImpl.DEFAULT_HCONNECTION_POOL_CORE_SIZE;
import static org.apache.phoenix.query.QueryServicesTestImpl.DEFAULT_HCONNECTION_POOL_MAX_SIZE;
@@ -81,6 +82,7 @@ public class MetadataServerConnectionsIT extends BaseTest {
Map<String, String> props = Maps.newHashMapWithExpectedSize(1);
props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(Long.MAX_VALUE));
props.put(DISABLE_VIEW_SUBTREE_VALIDATION, "true");
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MigrateSystemTablesToSystemNamespaceIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MigrateSystemTablesToSystemNamespaceIT.java
index 9299bc3816..ac0002a479 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MigrateSystemTablesToSystemNamespaceIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MigrateSystemTablesToSystemNamespaceIT.java
@@ -68,14 +68,14 @@ public class MigrateSystemTablesToSystemNamespaceIT extends BaseTest {
private static final Set<String> HBASE_NAMESPACE_MAPPED_SYSTEM_TABLES =
new HashSet<>(Arrays.asList("SYSTEM:PHOENIX_INDEX_TOOL_RESULT", "SYSTEM:PHOENIX_INDEX_TOOL"));
- private static final Set<String> PHOENIX_SYSTEM_TABLES =
- new HashSet<>(Arrays.asList("SYSTEM.CATALOG", "SYSTEM.SEQUENCE", "SYSTEM.STATS",
- "SYSTEM.FUNCTION", "SYSTEM.MUTEX", "SYSTEM.LOG", "SYSTEM.CHILD_LINK", "SYSTEM.TASK",
- "SYSTEM.TRANSFORM", "SYSTEM.CDC_STREAM_STATUS", "SYSTEM.CDC_STREAM"));
- private static final Set<String> PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES =
- new HashSet<>(Arrays.asList("SYSTEM:CATALOG", "SYSTEM:SEQUENCE", "SYSTEM:STATS",
- "SYSTEM:FUNCTION", "SYSTEM:MUTEX", "SYSTEM:LOG", "SYSTEM:CHILD_LINK", "SYSTEM:TASK",
- "SYSTEM:TRANSFORM", "SYSTEM:CDC_STREAM_STATUS", "SYSTEM:CDC_STREAM"));
+ private static final Set<String> PHOENIX_SYSTEM_TABLES = new HashSet<>(
+ Arrays.asList("SYSTEM.CATALOG", "SYSTEM.SEQUENCE", "SYSTEM.STATS", "SYSTEM.FUNCTION",
+ "SYSTEM.MUTEX", "SYSTEM.LOG", "SYSTEM.CHILD_LINK", "SYSTEM.TASK", "SYSTEM.TRANSFORM",
+ "SYSTEM.CDC_STREAM_STATUS", "SYSTEM.CDC_STREAM", "SYSTEM.IDX_CDC_TRACKER"));
+ private static final Set<String> PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES = new HashSet<>(
+ Arrays.asList("SYSTEM:CATALOG", "SYSTEM:SEQUENCE", "SYSTEM:STATS", "SYSTEM:FUNCTION",
+ "SYSTEM:MUTEX", "SYSTEM:LOG", "SYSTEM:CHILD_LINK", "SYSTEM:TASK", "SYSTEM:TRANSFORM",
+ "SYSTEM:CDC_STREAM_STATUS", "SYSTEM:CDC_STREAM", "SYSTEM:IDX_CDC_TRACKER"));
private static final String SCHEMA_NAME = "MIGRATETEST";
private static final String TABLE_NAME =
SCHEMA_NAME + "." + MigrateSystemTablesToSystemNamespaceIT.class.getSimpleName().toUpperCase();
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiTenantEventualIndexGenerateIT.java
similarity index 56%
copy from phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java
copy to phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiTenantEventualIndexGenerateIT.java
index 761c8658eb..857abf5e81 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsLazyPostBatchWriteIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiTenantEventualIndexGenerateIT.java
@@ -17,37 +17,40 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_BATCH_SIZE;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
import java.util.Map;
+import org.apache.phoenix.coprocessor.PhoenixMasterObserver;
import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
-import org.apache.phoenix.hbase.index.IndexRegionObserver;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.util.ReadOnlyProps;
-import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.experimental.categories.Category;
import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
-/**
- * Test class that extends ConcurrentMutationsExtendedIT with lazy post batch write enabled.
- */
@Category(NeedsOwnMiniClusterTest.class)
-public class ConcurrentMutationsLazyPostBatchWriteIT extends ConcurrentMutationsExtendedIT {
-
- public ConcurrentMutationsLazyPostBatchWriteIT(boolean uncovered) {
- super(uncovered);
- Assume.assumeFalse("Only covered index supports lazy post batch write mode", uncovered);
- }
+public class MultiTenantEventualIndexGenerateIT extends MultiTenantEventualIndexIT {
@BeforeClass
public static synchronized void doSetup() throws Exception {
- Map<String, String> props = Maps.newHashMapWithExpectedSize(4);
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(12);
props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
- props.put(IndexRegionObserver.INDEX_LAZY_POST_BATCH_WRITE, "true");
props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
Integer.toString(MAX_LOOKBACK_AGE));
- props.put("hbase.rowlock.wait.duration", "100");
- props.put("phoenix.index.concurrent.wait.duration.ms", "10");
+ props.put(INDEX_CDC_CONSUMER_BATCH_SIZE, Integer.toString(3500));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(4000));
+ props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Long.toString(10));
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ props.put(QueryServices.SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED_ATTRIB, Boolean.TRUE.toString());
+ props.put("hbase.coprocessor.master.classes", PhoenixMasterObserver.class.getName());
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString());
+ props.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
+
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiTenantEventualIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiTenantEventualIndexIT.java
new file mode 100644
index 0000000000..e0a4ab2a4f
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MultiTenantEventualIndexIT.java
@@ -0,0 +1,626 @@
+/*
+ * 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.end2end;
+
+import static org.apache.phoenix.end2end.IndexToolIT.verifyIndexTable;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_BATCH_SIZE;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.coprocessor.PhoenixMasterObserver;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.util.EnvironmentEdgeManager;
+import org.apache.phoenix.util.PhoenixRuntime;
+import org.apache.phoenix.util.PropertiesUtil;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.apache.phoenix.util.TestUtil;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class MultiTenantEventualIndexIT extends ParallelStatsDisabledIT {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MultiTenantEventualIndexIT.class);
+ protected static final int MAX_LOOKBACK_AGE = 1000000;
+ private static final long WAIT_MS = 25000;
+ private static final int ROWS_PER_TENANT_PER_PHASE = 10;
+ private static final String[] TENANT_PREFIXES = { "AA_", "BB_", "CC_", "DD_", "EE_", "FF_" };
+
+ @BeforeClass
+ public static synchronized void doSetup() throws Exception {
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(12);
+ props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
+ props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
+ Integer.toString(MAX_LOOKBACK_AGE));
+ props.put(INDEX_CDC_CONSUMER_BATCH_SIZE, Integer.toString(3500));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(4000));
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ props.put(QueryServices.SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED_ATTRIB, Boolean.TRUE.toString());
+ props.put("hbase.coprocessor.master.classes", PhoenixMasterObserver.class.getName());
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.TRUE.toString());
+ setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+ }
+
+ private Connection getTenantConnection(String tenantId) throws SQLException {
+ Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES);
+ props.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, tenantId);
+ return DriverManager.getConnection(getUrl(), props);
+ }
+
+ private Connection getGlobalConnection() throws SQLException {
+ Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES);
+ return DriverManager.getConnection(getUrl(), props);
+ }
+
+ private void waitForEventualConsistency() throws InterruptedException {
+ Thread.sleep(WAIT_MS);
+ }
+
+ private String[] createTenants() {
+ String[] tenants = new String[TENANT_PREFIXES.length];
+ for (int i = 0; i < TENANT_PREFIXES.length; i++) {
+ tenants[i] = TENANT_PREFIXES[i] + generateUniqueName();
+ }
+ return tenants;
+ }
+
+ private void insertRows(String[] tenants, String tableName, String phase, int startRow,
+ int endRow) throws Exception {
+ for (String tenant : tenants) {
+ try (Connection conn = getTenantConnection(tenant)) {
+ for (int j = startRow; j <= endRow; j++) {
+ conn.createStatement().execute(
+ String.format("UPSERT INTO %s(PK2, V1, V2) VALUES ('%s_r%d', '%s_v%d', '%s_d%d')",
+ tableName, phase, j, phase, j, phase, j));
+ }
+ conn.commit();
+ }
+ }
+ }
+
+ private void updateRows(String[] tenants, String tableName, String phase, int startRow,
+ int endRow, String suffix) throws Exception {
+ for (String tenant : tenants) {
+ try (Connection conn = getTenantConnection(tenant)) {
+ for (int j = startRow; j <= endRow; j++) {
+ conn.createStatement()
+ .execute(String.format(
+ "UPSERT INTO %s(PK2, V1, V2) VALUES ('%s_r%d', '%s_v%d_%s', '%s_d%d_%s')", tableName,
+ phase, j, phase, j, suffix, phase, j, suffix));
+ }
+ conn.commit();
+ }
+ }
+ }
+
+ private void deleteRows(String[] tenants, String tableName, String phase, int startRow,
+ int endRow) throws Exception {
+ for (String tenant : tenants) {
+ try (Connection conn = getTenantConnection(tenant)) {
+ for (int j = startRow; j <= endRow; j++) {
+ conn.createStatement()
+ .execute(String.format("DELETE FROM %s WHERE PK2 = '%s_r%d'", tableName, phase, j));
+ }
+ conn.commit();
+ }
+ }
+ }
+
+ private void verifyRowCount(String[] tenants, String tableName, int expectedCount, String phase)
+ throws Exception {
+ for (String tenant : tenants) {
+ try (Connection conn = getTenantConnection(tenant)) {
+ ResultSet rs = conn.createStatement()
+ .executeQuery("SELECT COUNT(*) FROM " + tableName + " WHERE V1 IS NOT NULL");
+ assertTrue(rs.next());
+ assertEquals(phase + ": tenant " + tenant, expectedCount, rs.getInt(1));
+ }
+ }
+ }
+
+ private void verifyIndexLookup(String[] tenants, String tableName, String v1Value,
+ String expectedPk2, String expectedV2) throws Exception {
+ for (String tenant : tenants) {
+ try (Connection conn = getTenantConnection(tenant)) {
+ ResultSet rs = conn.createStatement()
+ .executeQuery("SELECT PK2, V2 FROM " + tableName + " WHERE V1 = '" + v1Value + "'");
+ assertTrue("Row with V1=" + v1Value + " not found for " + tenant, rs.next());
+ assertEquals(expectedPk2, rs.getString(1));
+ if (expectedV2 != null) {
+ assertEquals(expectedV2, rs.getString(2));
+ }
+ assertFalse(rs.next());
+ }
+ }
+ }
+
+ private void verifyNoResult(String[] tenants, String tableName, String v1Value, String message)
+ throws Exception {
+ for (String tenant : tenants) {
+ try (Connection conn = getTenantConnection(tenant)) {
+ ResultSet rs = conn.createStatement()
+ .executeQuery("SELECT PK2 FROM " + tableName + " WHERE V1 = '" + v1Value + "'");
+ assertFalse(message + " for " + tenant, rs.next());
+ }
+ }
+ }
+
+ private int getRegionCount(Connection conn, String tableName) throws Exception {
+ List<HRegionLocation> regions = TestUtil.getAllTableRegions(conn, tableName);
+ return regions.size();
+ }
+
+ @Test
+ public void testBasicMultiTenantEventualIndex() throws Exception {
+ String tableName = generateUniqueName();
+ String indexName = generateUniqueName();
+ String tenantA = "TENANT_A_" + generateUniqueName();
+ String tenantB = "TENANT_B_" + generateUniqueName();
+
+ try (Connection globalConn = getGlobalConnection()) {
+ globalConn.createStatement()
+ .execute("CREATE TABLE " + tableName
+ + " (TENANT_ID VARCHAR NOT NULL, PK2 VARCHAR NOT NULL, V1 VARCHAR, V2 VARCHAR"
+ + " CONSTRAINT pk PRIMARY KEY (TENANT_ID, PK2))"
+ + " MULTI_TENANT=true, COLUMN_ENCODED_BYTES=0");
+ globalConn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName
+ + "(V1) INCLUDE (V2) CONSISTENCY=EVENTUAL");
+ }
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ connA.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r1', 'a1', 'x1')");
+ connA.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r2', 'a2', 'x2')");
+ connA.commit();
+ }
+
+ try (Connection connB = getTenantConnection(tenantB)) {
+ connB.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r1', 'b1', 'y1')");
+ connB.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r2', 'b2', 'y2')");
+ connB.commit();
+ }
+
+ waitForEventualConsistency();
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ ResultSet rs = connA.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'a1'");
+ assertTrue(rs.next());
+ assertEquals("r1", rs.getString(1));
+ assertEquals("a1", rs.getString(2));
+ assertEquals("x1", rs.getString(3));
+ assertFalse(rs.next());
+
+ rs = connA.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'a2'");
+ assertTrue(rs.next());
+ assertEquals("r2", rs.getString(1));
+ assertEquals("a2", rs.getString(2));
+ assertEquals("x2", rs.getString(3));
+ assertFalse(rs.next());
+ }
+
+ try (Connection connB = getTenantConnection(tenantB)) {
+ ResultSet rs = connB.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'b1'");
+ assertTrue(rs.next());
+ assertEquals("r1", rs.getString(1));
+ assertEquals("b1", rs.getString(2));
+ assertEquals("y1", rs.getString(3));
+ assertFalse(rs.next());
+ }
+
+ try (Connection globalConn = getGlobalConnection()) {
+ long rowCount = verifyIndexTable(tableName, indexName, globalConn);
+ assertEquals(4, rowCount);
+ }
+ }
+
+ @Test
+ public void testMultiTenantDeleteAndUpsert() throws Exception {
+ String tableName = generateUniqueName();
+ String indexName = generateUniqueName();
+ String tenantA = "TENANT_A_" + generateUniqueName();
+ String tenantB = "TENANT_B_" + generateUniqueName();
+
+ try (Connection globalConn = getGlobalConnection()) {
+ globalConn.createStatement()
+ .execute("CREATE TABLE " + tableName
+ + " (TENANT_ID VARCHAR NOT NULL, PK2 VARCHAR NOT NULL, V1 VARCHAR, V2 VARCHAR"
+ + " CONSTRAINT pk PRIMARY KEY (TENANT_ID, PK2))"
+ + " MULTI_TENANT=true, COLUMN_ENCODED_BYTES=0");
+ globalConn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName
+ + "(V1) INCLUDE (V2) CONSISTENCY=EVENTUAL");
+ }
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ connA.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r1', 'a1', 'x1')");
+ connA.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r2', 'a2', 'x2')");
+ connA.commit();
+ }
+
+ try (Connection connB = getTenantConnection(tenantB)) {
+ connB.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r1', 'b1', 'y1')");
+ connB.commit();
+ }
+
+ waitForEventualConsistency();
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ connA.createStatement().execute("DELETE FROM " + tableName + " WHERE PK2 = 'r1'");
+ connA.commit();
+ }
+
+ try (Connection connB = getTenantConnection(tenantB)) {
+ connB.createStatement().execute(
+ "UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r1', 'b1_updated', 'y1_updated')");
+ connB.commit();
+ }
+
+ waitForEventualConsistency();
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ ResultSet rs = connA.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'a1'");
+ assertFalse("Deleted row should not be visible", rs.next());
+
+ rs = connA.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'a2'");
+ assertTrue(rs.next());
+ assertEquals("r2", rs.getString(1));
+ assertFalse(rs.next());
+ }
+
+ try (Connection connB = getTenantConnection(tenantB)) {
+ ResultSet rs = connB.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'b1_updated'");
+ assertTrue(rs.next());
+ assertEquals("r1", rs.getString(1));
+ assertEquals("y1_updated", rs.getString(3));
+ assertFalse(rs.next());
+ }
+
+ try (Connection globalConn = getGlobalConnection()) {
+ long rowCount = verifyIndexTable(tableName, indexName, globalConn);
+ assertEquals(2, rowCount);
+ }
+ }
+
+ @Test
+ public void testMultiTenantUncoveredEventualIndex() throws Exception {
+ String tableName = generateUniqueName();
+ String indexName = generateUniqueName();
+ String tenantA = "TENANT_A_" + generateUniqueName();
+ String tenantB = "TENANT_B_" + generateUniqueName();
+
+ try (Connection globalConn = getGlobalConnection()) {
+ globalConn.createStatement()
+ .execute("CREATE TABLE " + tableName
+ + " (TENANT_ID VARCHAR NOT NULL, PK2 VARCHAR NOT NULL, V1 VARCHAR, V2 VARCHAR"
+ + " CONSTRAINT pk PRIMARY KEY (TENANT_ID, PK2))"
+ + " MULTI_TENANT=true, COLUMN_ENCODED_BYTES=0");
+ globalConn.createStatement().execute(
+ "CREATE UNCOVERED INDEX " + indexName + " ON " + tableName + "(V1) CONSISTENCY=EVENTUAL");
+ }
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ connA.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r1', 'a1', 'x1')");
+ connA.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r2', 'a2', 'x2')");
+ connA.commit();
+ }
+
+ try (Connection connB = getTenantConnection(tenantB)) {
+ connB.createStatement()
+ .execute("UPSERT INTO " + tableName + "(PK2, V1, V2) VALUES ('r1', 'b1', 'y1')");
+ connB.commit();
+ }
+
+ waitForEventualConsistency();
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ ResultSet rs = connA.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'a1'");
+ assertTrue(rs.next());
+ assertEquals("r1", rs.getString(1));
+ assertEquals("a1", rs.getString(2));
+ assertEquals("x1", rs.getString(3));
+ assertFalse(rs.next());
+ }
+
+ try (Connection connB = getTenantConnection(tenantB)) {
+ ResultSet rs = connB.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'b1'");
+ assertTrue(rs.next());
+ assertEquals("r1", rs.getString(1));
+ assertEquals("b1", rs.getString(2));
+ assertEquals("y1", rs.getString(3));
+ assertFalse(rs.next());
+ }
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ connA.createStatement().execute("DELETE FROM " + tableName + " WHERE PK2 = 'r1'");
+ connA.commit();
+ }
+
+ waitForEventualConsistency();
+
+ try (Connection connA = getTenantConnection(tenantA)) {
+ ResultSet rs = connA.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'a1'");
+ assertFalse("Deleted row should not appear for tenant A", rs.next());
+ }
+
+ try (Connection connB = getTenantConnection(tenantB)) {
+ ResultSet rs = connB.createStatement()
+ .executeQuery("SELECT PK2, V1, V2 FROM " + tableName + " WHERE V1 = 'b1'");
+ assertTrue("Tenant B row should still be visible", rs.next());
+ assertEquals("r1", rs.getString(1));
+ assertFalse(rs.next());
+ }
+
+ try (Connection globalConn = getGlobalConnection()) {
+ long rowCount = verifyIndexTable(tableName, indexName, globalConn);
+ assertEquals(2, rowCount);
+ }
+ }
+
+ @Test
+ public void testMultiTenantCoveredIndexWithSplits() throws Exception {
+ String tableName = generateUniqueName();
+ String indexName = generateUniqueName();
+ String[] tenants = createTenants();
+
+ try (Connection globalConn = getGlobalConnection()) {
+ globalConn.createStatement()
+ .execute("CREATE TABLE " + tableName
+ + " (TENANT_ID VARCHAR NOT NULL, PK2 VARCHAR NOT NULL, V1 VARCHAR, V2 VARCHAR"
+ + " CONSTRAINT pk PRIMARY KEY (TENANT_ID, PK2))"
+ + " MULTI_TENANT=true, COLUMN_ENCODED_BYTES=0");
+ globalConn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName
+ + "(V1) INCLUDE (V2) CONSISTENCY=EVENTUAL");
+ }
+
+ // Phase 1: Insert 10 rows per tenant (60 total), single region
+ insertRows(tenants, tableName, "p1", 1, ROWS_PER_TENANT_PER_PHASE);
+ waitForEventualConsistency();
+ verifyRowCount(tenants, tableName, ROWS_PER_TENANT_PER_PHASE, "Phase 1");
+ verifyIndexLookup(tenants, tableName, "p1_v5", "p1_r5", "p1_d5");
+
+ try (Connection globalConn = getGlobalConnection()) {
+ TestUtil.splitTable(globalConn, tableName, Bytes.toBytes("CC"));
+ assertEquals("Region count after split 1", 2, getRegionCount(globalConn, tableName));
+ }
+
+ // Phase 2: Insert 10 more rows per tenant after first split
+ insertRows(tenants, tableName, "p2", 1, ROWS_PER_TENANT_PER_PHASE);
+ waitForEventualConsistency();
+ verifyRowCount(tenants, tableName, 2 * ROWS_PER_TENANT_PER_PHASE, "Phase 2");
+ verifyIndexLookup(tenants, tableName, "p1_v3", "p1_r3", "p1_d3");
+ verifyIndexLookup(tenants, tableName, "p2_v7", "p2_r7", "p2_d7");
+
+ try (Connection globalConn = getGlobalConnection()) {
+ TestUtil.splitTable(globalConn, tableName, Bytes.toBytes("EE"));
+ assertEquals("Region count after split 2", 3, getRegionCount(globalConn, tableName));
+ }
+
+ insertRows(tenants, tableName, "p3", 1, 5);
+ updateRows(tenants, tableName, "p1", 1, 5, "upd");
+ waitForEventualConsistency();
+ // 10 (p1, 5 updated) + 10 (p2) + 5 (p3) = 25 unique PKs
+ verifyRowCount(tenants, tableName, 2 * ROWS_PER_TENANT_PER_PHASE + 5, "Phase 3");
+
+ verifyIndexLookup(tenants, tableName, "p1_v1_upd", "p1_r1", "p1_d1_upd");
+ verifyIndexLookup(tenants, tableName, "p1_v5_upd", "p1_r5", "p1_d5_upd");
+ verifyNoResult(tenants, tableName, "p1_v1", "Old p1_v1 should not exist");
+ verifyNoResult(tenants, tableName, "p1_v5", "Old p1_v5 should not exist");
+ verifyIndexLookup(tenants, tableName, "p1_v6", "p1_r6", "p1_d6");
+
+ deleteRows(tenants, tableName, "p2", 1, 2);
+ waitForEventualConsistency();
+ verifyRowCount(tenants, tableName, 2 * ROWS_PER_TENANT_PER_PHASE + 3, "Phase 4");
+ verifyNoResult(tenants, tableName, "p2_v1", "Deleted p2_r1 should not be visible via index");
+ verifyNoResult(tenants, tableName, "p2_v2", "Deleted p2_r2 should not be visible via index");
+ verifyIndexLookup(tenants, tableName, "p2_v3", "p2_r3", "p2_d3");
+
+ try (Connection globalConn = getGlobalConnection()) {
+ long rowCount = verifyIndexTable(tableName, indexName, globalConn);
+ assertEquals(23 * tenants.length, rowCount);
+ }
+ }
+
+ @Test
+ public void testMultiTenantUncoveredIndexWithSplits() throws Exception {
+ String tableName = generateUniqueName();
+ String indexName = generateUniqueName();
+ String[] tenants = createTenants();
+
+ try (Connection globalConn = getGlobalConnection()) {
+ globalConn.createStatement()
+ .execute("CREATE TABLE " + tableName
+ + " (TENANT_ID VARCHAR NOT NULL, PK2 VARCHAR NOT NULL, V1 VARCHAR, V2 VARCHAR"
+ + " CONSTRAINT pk PRIMARY KEY (TENANT_ID, PK2))"
+ + " MULTI_TENANT=true, COLUMN_ENCODED_BYTES=0");
+ globalConn.createStatement().execute(
+ "CREATE UNCOVERED INDEX " + indexName + " ON " + tableName + "(V1) CONSISTENCY=EVENTUAL");
+ }
+
+ insertRows(tenants, tableName, "p1", 1, ROWS_PER_TENANT_PER_PHASE);
+ waitForEventualConsistency();
+ verifyRowCount(tenants, tableName, ROWS_PER_TENANT_PER_PHASE, "Phase 1");
+ verifyIndexLookup(tenants, tableName, "p1_v5", "p1_r5", "p1_d5");
+
+ try (Connection globalConn = getGlobalConnection()) {
+ TestUtil.splitTable(globalConn, tableName, Bytes.toBytes("CC"));
+ assertEquals(2, getRegionCount(globalConn, tableName));
+ }
+
+ insertRows(tenants, tableName, "p2", 1, ROWS_PER_TENANT_PER_PHASE);
+ waitForEventualConsistency();
+ verifyRowCount(tenants, tableName, 2 * ROWS_PER_TENANT_PER_PHASE, "Phase 2");
+
+ try (Connection globalConn = getGlobalConnection()) {
+ TestUtil.splitTable(globalConn, tableName, Bytes.toBytes("EE"));
+ assertEquals(3, getRegionCount(globalConn, tableName));
+ }
+
+ insertRows(tenants, tableName, "p3", 1, 5);
+ updateRows(tenants, tableName, "p1", 1, 5, "upd");
+ waitForEventualConsistency();
+ verifyRowCount(tenants, tableName, 2 * ROWS_PER_TENANT_PER_PHASE + 5, "Phase 3");
+ verifyIndexLookup(tenants, tableName, "p1_v3_upd", "p1_r3", "p1_d3_upd");
+ verifyNoResult(tenants, tableName, "p1_v3", "Old value should not exist after update");
+
+ deleteRows(tenants, tableName, "p2", 1, 2);
+ waitForEventualConsistency();
+ verifyRowCount(tenants, tableName, 2 * ROWS_PER_TENANT_PER_PHASE + 3, "Phase 4");
+ verifyNoResult(tenants, tableName, "p2_v1", "Deleted row should not be visible");
+ verifyIndexLookup(tenants, tableName, "p2_v5", "p2_r5", "p2_d5");
+
+ try (Connection globalConn = getGlobalConnection()) {
+ long rowCount = verifyIndexTable(tableName, indexName, globalConn);
+ assertEquals(23 * tenants.length, rowCount);
+ }
+ }
+
+ @Test(timeout = 1800000)
+ @Ignore("too aggressive for jenkins builds")
+ public void testConcurrentUpsertsWithTableSplits() throws Exception {
+ int nThreads = 8;
+ final int batchSize = 100;
+ final int nRows = 777;
+ final int nIndexValues = 23;
+ final int nSplits = 3;
+ final int totalUpserts = 10000;
+ final String tableName = generateUniqueName();
+ final String indexName1 = generateUniqueName();
+ final String indexName2 = generateUniqueName();
+ final String indexName3 = generateUniqueName();
+ final String indexName4 = generateUniqueName();
+ final String indexName5 = generateUniqueName();
+ final String[] tenants = createTenants();
+
+ try (Connection globalConn = getGlobalConnection()) {
+ globalConn.createStatement()
+ .execute("CREATE TABLE " + tableName
+ + "(TENANT_ID VARCHAR NOT NULL, K2 INTEGER NOT NULL, V1 INTEGER, V2 INTEGER,"
+ + " V3 INTEGER, V4 INTEGER," + " CONSTRAINT pk PRIMARY KEY (TENANT_ID, K2))"
+ + " MULTI_TENANT=true, COLUMN_ENCODED_BYTES=0");
+ globalConn.createStatement().execute("CREATE INDEX " + indexName1 + " ON " + tableName
+ + "(V1) INCLUDE (V2, V3) CONSISTENCY=EVENTUAL");
+ globalConn.createStatement().execute(
+ "CREATE UNCOVERED INDEX " + indexName2 + " ON " + tableName + "(V2) CONSISTENCY=EVENTUAL");
+ globalConn.createStatement().execute("CREATE INDEX " + indexName3 + " ON " + tableName
+ + "(V3) INCLUDE (V1, V2) CONSISTENCY=EVENTUAL");
+ globalConn.createStatement().execute(
+ "CREATE UNCOVERED INDEX " + indexName4 + " ON " + tableName + "(V4) CONSISTENCY=EVENTUAL");
+ globalConn.createStatement().execute("CREATE INDEX " + indexName5 + " ON " + tableName
+ + "(V1, V2) INCLUDE (V3, V4) CONSISTENCY=EVENTUAL");
+ }
+
+ final CountDownLatch doneSignal = new CountDownLatch(nThreads);
+ Runnable[] runnables = new Runnable[nThreads];
+ Thread.sleep(3000);
+ long startTime = EnvironmentEdgeManager.currentTimeMillis();
+
+ for (int i = 0; i < nThreads; i++) {
+ final String tenant = tenants[i % tenants.length];
+ runnables[i] = () -> {
+ try (Connection conn = getTenantConnection(tenant)) {
+ ThreadLocalRandom rand = ThreadLocalRandom.current();
+ for (int j = 0; j < totalUpserts; j++) {
+ conn.createStatement()
+ .execute("UPSERT INTO " + tableName + " VALUES (" + (j % nRows) + ", "
+ + (rand.nextBoolean() ? null : (rand.nextInt() % nIndexValues)) + ", "
+ + (rand.nextBoolean() ? null : rand.nextInt()) + ", "
+ + (rand.nextBoolean() ? null : rand.nextInt()) + ", "
+ + (rand.nextBoolean() ? null : rand.nextInt()) + ")");
+ if ((j % batchSize) == 0) {
+ conn.commit();
+ }
+ }
+ conn.commit();
+ } catch (SQLException e) {
+ LOG.warn("Exception during concurrent upsert for tenant {}", tenant, e);
+ } finally {
+ doneSignal.countDown();
+ }
+ };
+ }
+
+ Thread splitThread = new Thread(() -> TestUtil.splitTable(getUrl(), tableName, nSplits, 8000));
+ splitThread.start();
+ for (int i = 0; i < nThreads; i++) {
+ if (i >= (nThreads - 4)) {
+ Thread.sleep(12000);
+ }
+ Thread t = new Thread(runnables[i]);
+ t.start();
+ }
+ assertTrue("Ran out of time", doneSignal.await(350, TimeUnit.SECONDS));
+ splitThread.join(10000);
+ LOG.info("Total upsert time: {} ms", EnvironmentEdgeManager.currentTimeMillis() - startTime);
+
+ int expectedTotal = tenants.length * nRows;
+ List<String> allIndexes =
+ new ArrayList<>(Arrays.asList(indexName1, indexName2, indexName3, indexName4, indexName5));
+ Collections.shuffle(allIndexes, ThreadLocalRandom.current());
+ LOG.info("Randomly selected indexes to verify: {}, {}", allIndexes.get(0), allIndexes.get(1));
+ try (Connection globalConn = getGlobalConnection()) {
+ Thread.sleep(500000);
+ long rowCount = verifyIndexTable(tableName, allIndexes.get(0), globalConn, false);
+ assertEquals("Index " + allIndexes.get(0), expectedTotal, rowCount);
+ rowCount = verifyIndexTable(tableName, allIndexes.get(1), globalConn, false);
+ assertEquals("Index " + allIndexes.get(1), expectedTotal, rowCount);
+ }
+ }
+
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledWithRegionMovesIT.java
index 4a4bd754f9..5de095496f 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledWithRegionMovesIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ParallelStatsDisabledWithRegionMovesIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -117,6 +118,7 @@ public abstract class ParallelStatsDisabledWithRegionMovesIT extends BaseTest {
props.put(HConstants.HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE_KEY, String.valueOf(1));
props.put(QueryServices.PHOENIX_POST_DUMMY_PROCESS,
TestScanningResultPostDummyResultCaller.class.getName());
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryDatabaseMetaDataIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryDatabaseMetaDataIT.java
index 7d839f4c16..53adfd8554 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryDatabaseMetaDataIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/QueryDatabaseMetaDataIT.java
@@ -189,6 +189,11 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
assertEquals(PTableType.SYSTEM.toString(), rs.getString("TABLE_TYPE"));
assertTrue(rs.next());
assertEquals(SYSTEM_CATALOG_SCHEMA, rs.getString("TABLE_SCHEM"));
+ assertEquals(PhoenixDatabaseMetaData.SYSTEM_IDX_CDC_TRACKER_TABLE,
+ rs.getString("TABLE_NAME"));
+ assertEquals(PTableType.SYSTEM.toString(), rs.getString("TABLE_TYPE"));
+ assertTrue(rs.next());
+ assertEquals(SYSTEM_CATALOG_SCHEMA, rs.getString("TABLE_SCHEM"));
assertEquals(PhoenixDatabaseMetaData.SYSTEM_LOG_TABLE, rs.getString("TABLE_NAME"));
assertEquals(PTableType.SYSTEM.toString(), rs.getString("TABLE_TYPE"));
assertTrue(rs.next());
@@ -425,7 +430,7 @@ public class QueryDatabaseMetaDataIT extends ParallelStatsDisabledIT {
tables.add(rs.getString("TABLE_NAME"));
assertEquals("SYSTEM", rs.getString("TABLE_SCHEM"));
}
- assertEquals(11, tables.size());
+ assertEquals(12, tables.size());
assertTrue(tables.contains("CATALOG"));
assertTrue(tables.contains("FUNCTION"));
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SkipSystemTablesExistenceCheckIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SkipSystemTablesExistenceCheckIT.java
index e24f0f69ff..3568628ec7 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SkipSystemTablesExistenceCheckIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SkipSystemTablesExistenceCheckIT.java
@@ -67,7 +67,7 @@ public class SkipSystemTablesExistenceCheckIT {
private static final Set<String> PHOENIX_SYSTEM_TABLES =
new HashSet<>(Arrays.asList("SYSTEM.CATALOG", "SYSTEM.SEQUENCE", "SYSTEM.STATS",
"SYSTEM.FUNCTION", "SYSTEM.MUTEX", "SYSTEM.LOG", "SYSTEM.CHILD_LINK", "SYSTEM.TASK",
- "SYSTEM.TRANSFORM", "SYSTEM.CDC_STREAM_STATUS", "SYSTEM.CDC_STREAM",
+ "SYSTEM.TRANSFORM", "SYSTEM.CDC_STREAM_STATUS", "SYSTEM.CDC_STREAM", "SYSTEM.IDX_CDC_TRACKER",
"SYSTEM.PHOENIX_INDEX_TOOL_RESULT", "SYSTEM.PHOENIX_INDEX_TOOL"));
private static class PhoenixSystemTablesCreationTestDriver extends PhoenixTestDriver {
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablesCreationOnConnectionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablesCreationOnConnectionIT.java
index f88385faa6..5d1cb86a2b 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablesCreationOnConnectionIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SystemTablesCreationOnConnectionIT.java
@@ -107,13 +107,13 @@ public class SystemTablesCreationOnConnectionIT {
private static final Set<String> PHOENIX_SYSTEM_TABLES =
new HashSet<>(Arrays.asList("SYSTEM.CATALOG", "SYSTEM.SEQUENCE", "SYSTEM.STATS",
"SYSTEM.FUNCTION", "SYSTEM.MUTEX", "SYSTEM.LOG", "SYSTEM.CHILD_LINK", "SYSTEM.TASK",
- "SYSTEM.TRANSFORM", "SYSTEM.CDC_STREAM_STATUS", "SYSTEM.CDC_STREAM",
+ "SYSTEM.TRANSFORM", "SYSTEM.CDC_STREAM_STATUS", "SYSTEM.CDC_STREAM", "SYSTEM.IDX_CDC_TRACKER",
"SYSTEM.PHOENIX_INDEX_TOOL_RESULT", "SYSTEM.PHOENIX_INDEX_TOOL"));
private static final Set<String> PHOENIX_NAMESPACE_MAPPED_SYSTEM_TABLES =
new HashSet<>(Arrays.asList("SYSTEM:CATALOG", "SYSTEM:SEQUENCE", "SYSTEM:STATS",
"SYSTEM:FUNCTION", "SYSTEM:MUTEX", "SYSTEM:LOG", "SYSTEM:CHILD_LINK", "SYSTEM:TASK",
- "SYSTEM:TRANSFORM", "SYSTEM:CDC_STREAM_STATUS", "SYSTEM:CDC_STREAM",
+ "SYSTEM:TRANSFORM", "SYSTEM:CDC_STREAM_STATUS", "SYSTEM:CDC_STREAM", "SYSTEM:IDX_CDC_TRACKER",
"SYSTEM:PHOENIX_INDEX_TOOL_RESULT", "SYSTEM:PHOENIX_INDEX_TOOL"));
private static class PhoenixSysCatCreationServices extends ConnectionQueryServicesImpl {
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java
index bf396d25d9..07ca749575 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDDLIT.java
@@ -578,6 +578,9 @@ public class TenantSpecificTablesDDLIT extends BaseTenantSpecificTablesIT {
assertTrue(rs.next());
assertTableMetaData(rs, SYSTEM_CATALOG_SCHEMA, SYSTEM_FUNCTION_TABLE, SYSTEM);
assertTrue(rs.next());
+ assertTableMetaData(rs, PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA,
+ PhoenixDatabaseMetaData.SYSTEM_IDX_CDC_TRACKER_TABLE, PTableType.SYSTEM);
+ assertTrue(rs.next());
assertTableMetaData(rs, PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA,
PhoenixDatabaseMetaData.SYSTEM_LOG_TABLE, PTableType.SYSTEM);
assertTrue(rs.next());
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java
index 6a19bec2e6..fe29929456 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.junit.Assert.assertEquals;
import com.google.protobuf.RpcCallback;
@@ -100,6 +101,7 @@ public class UCFWithServerMetadataIT extends BaseTest {
public static synchronized void doSetup() throws Exception {
Map<String, String> props = new HashMap<>(1);
props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(Long.MAX_VALUE));
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java
index 588e08b6b6..1bf090cdae 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.monitoring.GlobalClientMetrics.GLOBAL_OPEN_PHOENIX_CONNECTIONS;
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
import static org.apache.phoenix.util.PhoenixRuntime.UPSERT_BATCH_SIZE_ATTRIB;
@@ -88,6 +89,7 @@ public class UpsertSelectIT extends ParallelStatsDisabledIT {
// any addition to GLOBAL_OPEN_PHOENIX_CONNECTIONS
props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(Long.MAX_VALUE));
props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(Long.MAX_VALUE));
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectWithRegionMovesIT.java
index 9ad2a9bad5..c2ffbc30a5 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectWithRegionMovesIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectWithRegionMovesIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
import static org.apache.phoenix.util.PhoenixRuntime.UPSERT_BATCH_SIZE_ATTRIB;
import static org.apache.phoenix.util.TestUtil.A_VALUE;
@@ -94,6 +95,7 @@ public class UpsertSelectWithRegionMovesIT extends ParallelStatsDisabledWithRegi
props.put(HConstants.HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE_KEY, String.valueOf(1));
props.put(QueryServices.PHOENIX_POST_DUMMY_PROCESS,
TestScanningResultPostDummyResultCaller.class.getName());
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded2IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded2IT.java
index c5213df866..6b05b7dd0a 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded2IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/VarBinaryEncoded2IT.java
@@ -17,6 +17,9 @@
*/
package org.apache.phoenix.end2end;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
import java.sql.Connection;
@@ -24,40 +27,80 @@ import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.Map;
import java.util.Properties;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.compile.ExplainPlan;
import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.schema.types.PVarbinary;
import org.apache.phoenix.util.PropertiesUtil;
+import org.apache.phoenix.util.ReadOnlyProps;
import org.junit.Assert;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-@Category(ParallelStatsDisabledTest.class)
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
+
+@Category(NeedsOwnMiniClusterTest.class)
@RunWith(Parameterized.class)
public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
+ private static final long EVENTUAL_CONSISTENCY_WAIT_MS = 17000;
+
private final boolean columnEncoded;
private final boolean coveredIndex;
private final boolean isBindStatement;
+ private final boolean eventual;
- public VarBinaryEncoded2IT(boolean columnEncoded, boolean coveredIndex, boolean isBindStatement) {
+ public VarBinaryEncoded2IT(boolean columnEncoded, boolean coveredIndex, boolean isBindStatement,
+ boolean eventual) {
this.columnEncoded = columnEncoded;
this.coveredIndex = coveredIndex;
this.isBindStatement = isBindStatement;
+ this.eventual = eventual;
}
- @Parameterized.Parameters(name = "VarBinary2IT_columnEncoded={0}, coveredIndex={1}")
+ @BeforeClass
+ public static synchronized void doSetup() throws Exception {
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(1);
+ props.put(BaseScannerRegionObserverConstants.PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY,
+ Integer.toString(60 * 60));
+ props.put(QueryServices.USE_STATS_FOR_PARALLELIZATION, Boolean.toString(false));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(2000));
+ props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(5));
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString());
+ setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+ }
+
+ @Parameterized.Parameters(
+ name = "VarBinary2IT_columnEncoded={0}, coveredIndex={1}, isBindStatement={2}, eventual={3}")
public static synchronized Collection<Object[]> data() {
- return Arrays.asList(new Object[][] { { false, false, false }, { false, true, false },
- { true, false, false }, { true, true, false }, { false, false, true }, { false, true, true },
- { true, false, true }, { true, true, true } });
+ List<Object[]> params = new ArrayList<>();
+ for (boolean columnEncoded : new boolean[] { false, true }) {
+ for (boolean coveredIndex : new boolean[] { false, true }) {
+ for (boolean isBindStatement : new boolean[] { false, true }) {
+ for (boolean eventual : new boolean[] { false, true }) {
+ params.add(new Object[] { columnEncoded, coveredIndex, isBindStatement, eventual });
+ }
+ }
+ }
+ }
+ return params;
+ }
+
+ private void waitForEventualConsistency() throws InterruptedException {
+ if (eventual) {
+ Thread.sleep(EVENTUAL_CONSISTENCY_WAIT_MS);
+ }
}
@Test
@@ -73,12 +116,13 @@ public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
+ " COL3 VARBINARY_ENCODED CONSTRAINT pk PRIMARY KEY(PK1, PK2, PK3)) "
+ (this.columnEncoded ? "" : "COLUMN_ENCODED_BYTES=0"));
+ String consistencyClause = eventual ? " CONSISTENCY = EVENTUAL" : " CONSISTENCY = STRONG";
if (this.coveredIndex) {
- conn.createStatement().execute(
- "CREATE INDEX " + indexName + " ON " + tableName + " (COL1, COL2) INCLUDE (COL3)");
+ conn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName
+ + " (COL1, COL2) INCLUDE (COL3)" + consistencyClause);
} else {
- conn.createStatement()
- .execute("CREATE UNCOVERED INDEX " + indexName + " ON " + tableName + " (COL1, COL2)");
+ conn.createStatement().execute("CREATE UNCOVERED INDEX " + indexName + " ON " + tableName
+ + " (COL1, COL2)" + consistencyClause);
}
byte[] b1 = new byte[] { 1, 1, 19, -28, 24, 1, 1, -11, -21, 1 };
@@ -152,6 +196,7 @@ public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
upsertRow(preparedStatement, b16, b26, b36, b46, b56, b66);
}
conn.commit();
+ waitForEventualConsistency();
PreparedStatement preparedStatement =
conn.prepareStatement("SELECT * FROM " + tableName + " WHERE COL1 = ? AND COL2 = ?");
@@ -367,12 +412,13 @@ public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
+ " COL3 VARBINARY_ENCODED CONSTRAINT pk PRIMARY KEY(PK1, PK2, PK3)) "
+ (this.columnEncoded ? "" : "COLUMN_ENCODED_BYTES=0"));
+ String consistencyClause = eventual ? " CONSISTENCY = EVENTUAL" : " CONSISTENCY = STRONG";
if (this.coveredIndex) {
- conn.createStatement().execute(
- "CREATE INDEX " + indexName + " ON " + tableName + " (COL1, COL2) INCLUDE (COL3)");
+ conn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName
+ + " (COL1, COL2) INCLUDE (COL3)" + consistencyClause);
} else {
- conn.createStatement()
- .execute("CREATE UNCOVERED INDEX " + indexName + " ON " + tableName + " (COL1, COL2)");
+ conn.createStatement().execute("CREATE UNCOVERED INDEX " + indexName + " ON " + tableName
+ + " (COL1, COL2)" + consistencyClause);
}
byte[] b1 = new byte[] { 1, 1, 19, -28, 24, 1, 1, -11, -21, 1 };
@@ -449,6 +495,7 @@ public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
upsertRow(preparedStatement, b16, b26, b36, b46, b56, b66);
}
conn.commit();
+ waitForEventualConsistency();
PreparedStatement preparedStatement =
conn.prepareStatement("SELECT * FROM " + tableName + " WHERE COL1 = ? AND COL2 = ?");
@@ -664,12 +711,13 @@ public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
+ " COL3 VARBINARY_ENCODED CONSTRAINT pk PRIMARY KEY(PK1, PK2, PK3)) "
+ (this.columnEncoded ? "" : "COLUMN_ENCODED_BYTES=0"));
+ String consistencyClause = eventual ? " CONSISTENCY = EVENTUAL" : " CONSISTENCY = STRONG";
if (this.coveredIndex) {
- conn.createStatement().execute(
- "CREATE INDEX " + indexName + " ON " + tableName + " (COL1, COL2) INCLUDE (COL3)");
+ conn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName
+ + " (COL1, COL2) INCLUDE (COL3)" + consistencyClause);
} else {
- conn.createStatement()
- .execute("CREATE UNCOVERED INDEX " + indexName + " ON " + tableName + " (COL1, COL2)");
+ conn.createStatement().execute("CREATE UNCOVERED INDEX " + indexName + " ON " + tableName
+ + " (COL1, COL2)" + consistencyClause);
}
byte[] b1 = new byte[] { 1, 1, 19, -28, 24, 1, 1, -11, -21, 1 };
@@ -743,6 +791,7 @@ public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
upsertRow(preparedStatement, b16, b26, b36, b46, b56, b66);
}
conn.commit();
+ waitForEventualConsistency();
PreparedStatement preparedStatement =
conn.prepareStatement("SELECT * FROM " + tableName + " WHERE COL1 = ? AND COL2 = ?");
@@ -951,12 +1000,13 @@ public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
+ " COL3 VARBINARY_ENCODED CONSTRAINT pk PRIMARY KEY(PK1, PK2, PK3)) "
+ (this.columnEncoded ? "" : "COLUMN_ENCODED_BYTES=0"));
+ String consistencyClause = eventual ? " CONSISTENCY = EVENTUAL" : " CONSISTENCY = STRONG";
if (this.coveredIndex) {
- conn.createStatement().execute(
- "CREATE INDEX " + indexName + " ON " + tableName + " (COL1, COL2) INCLUDE (COL3)");
+ conn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName
+ + " (COL1, COL2) INCLUDE (COL3)" + consistencyClause);
} else {
- conn.createStatement()
- .execute("CREATE UNCOVERED INDEX " + indexName + " ON " + tableName + " (COL1, COL2)");
+ conn.createStatement().execute("CREATE UNCOVERED INDEX " + indexName + " ON " + tableName
+ + " (COL1, COL2)" + consistencyClause);
}
byte[] b1 = new byte[] { 1, 1, 19, -28, 24, 1, 1, -11, -21, 1 };
@@ -1027,6 +1077,7 @@ public class VarBinaryEncoded2IT extends ParallelStatsDisabledIT {
upsertRow(preparedStatement, b16, b26, b36, b46, b56, b66);
}
conn.commit();
+ waitForEventualConsistency();
PreparedStatement preparedStatement =
conn.prepareStatement("SELECT * FROM " + tableName + " WHERE COL1 = ? AND COL2 = ?");
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerEventualGenerateIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerEventualGenerateIT.java
new file mode 100644
index 0000000000..a862df07ad
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerEventualGenerateIT.java
@@ -0,0 +1,89 @@
+/*
+ * 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.end2end.index;
+
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+import org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class GlobalIndexCheckerEventualGenerateIT extends GlobalIndexCheckerIT {
+
+ public GlobalIndexCheckerEventualGenerateIT(boolean async, boolean encoded) {
+ super(async, encoded);
+ }
+
+ @BeforeClass
+ public static synchronized void doSetup() throws Exception {
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(10);
+ props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(2000));
+ props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(5));
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.FALSE.toString());
+ props.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+ }
+
+ @Before
+ public void appendEventualConsistency() {
+ if (indexDDLOptions.trim().isEmpty()) {
+ indexDDLOptions = " CONSISTENCY=EVENTUAL";
+ } else {
+ indexDDLOptions = " CONSISTENCY=EVENTUAL," + indexDDLOptions;
+ }
+ }
+
+ @After
+ public void after() throws Exception {
+ }
+
+ @Override
+ protected void waitForEventualConsistency() throws Exception {
+ Thread.sleep(18000);
+ }
+
+ @Parameterized.Parameters(name = "async={0},encoded={1}")
+ public static synchronized Collection<Object[]> data() {
+ List<Object[]> list = Lists.newArrayListWithExpectedSize(4);
+ boolean[] Booleans = new boolean[] { true, false };
+ for (boolean async : Booleans) {
+ for (boolean encoded : Booleans) {
+ list.add(new Object[] { async, encoded });
+ }
+ }
+ return list;
+ }
+
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerEventualIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerEventualIT.java
new file mode 100644
index 0000000000..d7b5a35e1c
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerEventualIT.java
@@ -0,0 +1,89 @@
+/*
+ * 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.end2end.index;
+
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_RETRY_PAUSE_MS;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_MUTATION_SERIALIZE;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.apache.phoenix.end2end.NeedsOwnMiniClusterTest;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.experimental.categories.Category;
+import org.junit.runners.Parameterized;
+
+import org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
+import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
+
+@Category(NeedsOwnMiniClusterTest.class)
+public class GlobalIndexCheckerEventualIT extends GlobalIndexCheckerIT {
+
+ public GlobalIndexCheckerEventualIT(boolean async, boolean encoded) {
+ super(async, encoded);
+ }
+
+ @BeforeClass
+ public static synchronized void doSetup() throws Exception {
+ Map<String, String> props = Maps.newHashMapWithExpectedSize(8);
+ props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
+ props.put(INDEX_CDC_CONSUMER_TIMESTAMP_BUFFER_MS, Integer.toString(2000));
+ props.put(INDEX_CDC_CONSUMER_RETRY_PAUSE_MS, Integer.toString(5));
+ props.put(QueryServices.PHOENIX_SERVER_PAGE_SIZE_MS, Integer.toString(-1));
+ props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(2));
+ props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(1));
+ props.put(PHOENIX_INDEX_CDC_MUTATION_SERIALIZE, Boolean.TRUE.toString());
+ setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+ }
+
+ @Before
+ public void appendEventualConsistency() {
+ if (indexDDLOptions.trim().isEmpty()) {
+ indexDDLOptions = " CONSISTENCY=EVENTUAL";
+ } else {
+ indexDDLOptions = " CONSISTENCY=EVENTUAL," + indexDDLOptions;
+ }
+ }
+
+ @After
+ public void after() throws Exception {
+ }
+
+ @Override
+ protected void waitForEventualConsistency() throws Exception {
+ Thread.sleep(15000);
+ }
+
+ @Parameterized.Parameters(name = "async={0},encoded={1}")
+ public static synchronized Collection<Object[]> data() {
+ List<Object[]> list = Lists.newArrayListWithExpectedSize(4);
+ boolean[] Booleans = new boolean[] { true, false };
+ for (boolean async : Booleans) {
+ for (boolean encoded : Booleans) {
+ list.add(new Object[] { async, encoded });
+ }
+ }
+ return list;
+ }
+
+}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
index 67ccf665e2..8d7d4c546a 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerIT.java
@@ -85,8 +85,8 @@ import org.apache.phoenix.thirdparty.com.google.common.collect.Maps;
public class GlobalIndexCheckerIT extends BaseTest {
private final boolean async;
- private String indexDDLOptions;
- private String tableDDLOptions;
+ protected String indexDDLOptions;
+ protected String tableDDLOptions;
private StringBuilder optionBuilder;
private StringBuilder indexOptionBuilder;
private final boolean encoded;
@@ -266,6 +266,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
+ "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')";
// Verify that we will read from the index table
assertExplainPlan(conn, query, dataTableName, indexTableName);
+ waitForEventualConsistency();
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
assertEquals("bc", rs.getString(1));
@@ -300,6 +301,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
"SELECT val1, val2, PHOENIX_ROW_TIMESTAMP() from " + dataTableName + " WHERE val1 = 'de'";
// Verify that we will read from the index table
assertExplainPlan(conn, query, dataTableName, indexTableName);
+ waitForEventualConsistency();
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
assertEquals("de", rs.getString(1));
@@ -322,6 +324,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " values ('e', 'ae', 'efg', 'efgh')");
conn.commit();
+ waitForEventualConsistency();
// Write a query to get all the rows in the order of their timestamps
query = "SELECT val1, val2, PHOENIX_ROW_TIMESTAMP() from " + dataTableName + " WHERE "
+ "PHOENIX_ROW_TIMESTAMP() > TO_DATE('" + initial.toString()
@@ -365,6 +368,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
+ "','yyyy-MM-dd HH:mm:ss.SSS', '" + timeZoneID + "')";
assertExplainPlan(conn, query, dataTableName, indexTableName);
+ waitForEventualConsistency();
rs = conn.createStatement().executeQuery(query);
assertTrue(rs.next());
assertEquals("ab", rs.getString(1));
@@ -565,6 +569,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.commit();
// Now the expected state of the index table is {('ab', 'a', 'abcc' , null), ('ab', 'b', null,
// 'bcde')}
+ waitForEventualConsistency();
ResultSet rs = conn.createStatement().executeQuery("SELECT * from " + indexTableName);
assertTrue(rs.next());
assertEquals("ab", rs.getString(1));
@@ -606,6 +611,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
String dml = "DELETE from " + dataTableName + " WHERE id = 'a'";
assertEquals(1, conn.createStatement().executeUpdate(dml));
conn.commit();
+ waitForEventualConsistency();
// The index rows are actually not deleted yet because IndexRegionObserver failed delete
// operation. However, they are
@@ -653,6 +659,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
String selectSql = "SELECT * from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
+ waitForEventualConsistency();
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
@@ -666,6 +673,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.commit();
// Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
+ waitForEventualConsistency();
rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
assertEquals("a", rs.getString(1));
@@ -795,6 +803,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
// run the index MR job.
IndexToolIT.runIndexTool(false, null, dataTableName, indexTableName);
}
+ waitForEventualConsistency();
// Configure IndexRegionObserver to fail the last write phase (i.e., the post index update
// phase) where the verify flag is set
// to true and/or index rows are deleted and check that this does not impact the correctness
@@ -805,6 +814,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " (id, val1, val2) values ('c', 'cd','cde')");
conn.commit();
+ waitForEventualConsistency();
IndexTool indexTool = IndexToolIT.runIndexTool(false, "", dataTableName, indexTableName, null,
0, IndexTool.IndexVerifyType.ONLY);
assertEquals(3, indexTool.getJob().getCounters().findCounter(INPUT_RECORDS).getValue());
@@ -824,8 +834,10 @@ public class GlobalIndexCheckerIT extends BaseTest {
.findCounter(BEFORE_REBUILD_BEYOND_MAXLOOKBACK_MISSING_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters()
.findCounter(BEFORE_REBUILD_BEYOND_MAXLOOKBACK_INVALID_INDEX_ROW_COUNT).getValue());
- assertEquals(2, indexTool.getJob().getCounters()
- .findCounter(BEFORE_REBUILD_UNVERIFIED_INDEX_ROW_COUNT).getValue());
+ if (!indexDDLOptions.contains("CONSISTENCY=EVENTUAL")) {
+ assertEquals(2, indexTool.getJob().getCounters()
+ .findCounter(BEFORE_REBUILD_UNVERIFIED_INDEX_ROW_COUNT).getValue());
+ }
assertEquals(0, indexTool.getJob().getCounters()
.findCounter(BEFORE_REBUILD_OLD_INDEX_ROW_COUNT).getValue());
assertEquals(0, indexTool.getJob().getCounters()
@@ -872,6 +884,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " (id, val1, val2) values ('a', 'ab','abc')");
conn.commit();
+ waitForEventualConsistency();
// At this moment val3 in the data table row has null value
String selectSql = "SELECT val3 from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the index table
@@ -919,6 +932,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " values ('a', 'ab','abc', 'abcd')");
conn.commit();
+ waitForEventualConsistency();
// At this moment val3 in the data table row should not have null value
selectSql = "SELECT val3 from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the index table
@@ -932,6 +946,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " (id, val1, val3) values ('a', 'ab','abcde')");
commitWithException(conn);
+ waitForEventualConsistency();
// The above upsert will create an unverified index row
// Configure IndexRegionObserver to allow the data write phase
IndexRegionObserver.setFailDataTableUpdatesForTesting(false);
@@ -983,6 +998,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
String selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val1 = 'cd'";
// Verify that we will read from the first index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName + "1");
+ waitForEventualConsistency();
// Verify the first write is visible but the second one is not
ResultSet rs = conn.createStatement().executeQuery(selectSql);
assertTrue(rs.next());
@@ -1120,6 +1136,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " (id, val3) values ('a', 'abcdd')");
conn.commit();
+ waitForEventualConsistency();
String selectSql = "SELECT val2, val3 from " + dataTableName + " WHERE val1 = 'ab'";
// Verify that we will read from the first index table
assertExplainPlan(conn, selectSql, dataTableName, indexName + "1");
@@ -1326,6 +1343,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " " + "values ('g', 'val1', 'val2g', null)");
conn.commit();
+ waitForEventualConsistency();
// Fail phase 3
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
String selectSql = "SELECT id from " + dataTableName
@@ -1390,6 +1408,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement().execute("upsert into " + dataTableName + " "
+ "values ('a2', 'a1', 'val1', 'val2a', 'val31', 'val4')");
conn.commit();
+ waitForEventualConsistency();
// create an unverified update to the index row pointing to an existing data row
IndexRegionObserver.setFailDataTableUpdatesForTesting(true);
@@ -1400,6 +1419,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement().execute("upsert into " + dataTableName + " "
+ "values ('a2', 'a2', 'val1', 'val1b', 'val3', 'val4')");
conn.commit();
+ waitForEventualConsistency();
ArrayList<String> expectedValues = Lists.newArrayList("a1", "a2");
String selectSql =
@@ -1415,6 +1435,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement().execute("upsert into " + dataTableName + " "
+ "values ('a3', 'a2', 'val1', 'val2a', 'val3', 'val4')");
conn.commit();
+ waitForEventualConsistency();
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
expectedValues = Lists.newArrayList("a1", "a2", "a3");
selectSql =
@@ -1431,6 +1452,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement().execute("upsert into " + dataTableName + " "
+ "values ('a5', 'a1', 'val1_4', 'val1_4', 'val1_4', 'val1_4')");
conn.commit();
+ waitForEventualConsistency();
IndexRegionObserver.setFailPostIndexUpdatesForTesting(false);
expectedValues = Lists.newArrayList("a4", "a5");
selectSql =
@@ -1475,6 +1497,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement().execute("upsert into " + dataTableName + " "
+ "values ('a2', 'a1', 'val1a', 'val2a', 'val31', 'val4')");
conn.commit();
+ waitForEventualConsistency();
ArrayList<String> expectedValues = Lists.newArrayList("a1", "a2");
// condition on val1 in WHERE clause so that query will use the uncovered index
@@ -1499,6 +1522,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement().execute("upsert into " + dataTableName + " "
+ "values ('a4', 'a1', 'val1b', 'val2a', 'val31', 'val4')");
conn.commit();
+ waitForEventualConsistency();
expectedValues = Lists.newArrayList("a3", "a4");
selectSql = "SELECT distinct(id1) from " + dataTableName + " WHERE val1 = 'val1b'";
verifyDistinctQueryOnIndex(conn, uncoveredIndex1, selectSql, expectedValues);
@@ -1589,6 +1613,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " (id, val3) values ('a', null)");
conn.commit();
+ waitForEventualConsistency();
String dql =
String.format("select id, val2 from %s where val1='ab' and val3='abcd'", dataTableName);
@@ -1613,6 +1638,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " values ('a', 'ac', null, null)");
conn.commit();
+ waitForEventualConsistency();
dql =
String.format("select id, val2 from %s where val1='ac' and val3 is null", dataTableName);
@@ -1651,6 +1677,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " (id, val1, val2) values ('c', 'cd', 'cde')");
conn.commit();
+ waitForEventualConsistency();
IndexRegionObserver.setIgnoreWritingDeleteColumnsToIndex(false);
IndexTool it = IndexToolIT.runIndexTool(false, null, dataTableName, indexName, null, 0,
IndexTool.IndexVerifyType.BEFORE);
@@ -1689,6 +1716,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
String delete = String.format("DELETE FROM %s where id = 'a'", dataTableName);
conn.createStatement().execute(delete);
conn.commit();
+ waitForEventualConsistency();
// skip phase2, inserts an unverified row in index
IndexRegionObserver.setFailDataTableUpdatesForTesting(true);
String dml = "upsert into " + dataTableName + " (id, val1, val3) values ('a', 'ab', ?)";
@@ -1715,6 +1743,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
conn.createStatement()
.execute("upsert into " + dataTableName + " (id, val1, val3) values ('a', 'ab', null)");
conn.commit();
+ waitForEventualConsistency();
IndexTool it = IndexToolIT.runIndexTool(false, null, dataTableName, indexName, null, 0,
IndexTool.IndexVerifyType.ONLY);
CounterGroup mrJobCounters = IndexToolIT.getMRJobCounters(it);
@@ -1737,6 +1766,8 @@ public class GlobalIndexCheckerIT extends BaseTest {
// No need to run the same test twice one for async = true and the other for async = false
return;
}
+ Assume.assumeFalse("View indexes do not support CONSISTENCY=EVENTUAL",
+ indexDDLOptions.contains("CONSISTENCY=EVENTUAL"));
try (Connection conn = DriverManager.getConnection(getUrl())) {
// Create a base table
String dataTableName = generateUniqueName();
@@ -1813,6 +1844,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
+ "val1 = val1 || val1, val2 = val2 || val2";
conn.createStatement().execute(upsertSql);
conn.commit();
+ waitForEventualConsistency();
String selectSql = "SELECT * from " + dataTableName + " WHERE val1 = 'abab'";
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
ResultSet rs = conn.createStatement().executeQuery(selectSql);
@@ -1849,6 +1881,7 @@ public class GlobalIndexCheckerIT extends BaseTest {
ps.executeUpdate();
}
conn.commit();
+ waitForEventualConsistency();
String distinctQuery = "SELECT DISTINCT val1 FROM " + dataTableName;
try (ResultSet rs = conn.createStatement().executeQuery(distinctQuery)) {
PhoenixResultSet prs = rs.unwrap(PhoenixResultSet.class);
@@ -1876,14 +1909,18 @@ public class GlobalIndexCheckerIT extends BaseTest {
}
}
- static private void verifyTableHealth(Connection conn, String dataTableName,
- String indexTableName) throws Exception {
+ protected void waitForEventualConsistency() throws Exception {
+ }
+
+ protected void verifyTableHealth(Connection conn, String dataTableName, String indexTableName)
+ throws Exception {
// Add two rows and check everything is still okay
conn.createStatement()
.execute("upsert into " + dataTableName + " values ('a', 'ab', 'abc', 'abcd')");
conn.createStatement()
.execute("upsert into " + dataTableName + " values ('z', 'za', 'zab', 'zabc')");
conn.commit();
+ waitForEventualConsistency();
String selectSql = "SELECT * from " + dataTableName + " WHERE val1 = 'ab'";
/// Verify that we will read from the index table
assertExplainPlan(conn, selectSql, dataTableName, indexTableName);
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerWithRegionMovesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerWithRegionMovesIT.java
index 8632eaf013..6583df2d84 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerWithRegionMovesIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/GlobalIndexCheckerWithRegionMovesIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end.index;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.mapreduce.PhoenixJobCounters.INPUT_RECORDS;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_BEYOND_MAXLOOKBACK_INVALID_INDEX_ROW_COUNT;
import static org.apache.phoenix.mapreduce.index.PhoenixIndexToolJobCounters.BEFORE_REBUILD_BEYOND_MAXLOOKBACK_MISSING_INDEX_ROW_COUNT;
@@ -142,6 +143,7 @@ public class GlobalIndexCheckerWithRegionMovesIT extends BaseTest {
props.put(QueryServices.PHOENIX_POST_DUMMY_PROCESS,
TestScanningResultPostDummyResultCaller.class.getName());
props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexAsyncThresholdIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexAsyncThresholdIT.java
index 4db0b3c5bd..6b6f3059b3 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexAsyncThresholdIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexAsyncThresholdIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end.index;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_STARTUP_DELAY_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -100,6 +101,7 @@ public class IndexAsyncThresholdIT extends BaseTest {
Configuration conf = HBaseConfiguration.create();
Map<String, String> props = Maps.newHashMapWithExpectedSize(1);
props.put(QueryServices.CLIENT_INDEX_ASYNC_THRESHOLD, Long.toString(threshold));
+ props.put(INDEX_CDC_CONSUMER_STARTUP_DELAY_MS, Integer.toString(Integer.MAX_VALUE));
url = setUpTestCluster(conf, new ReadOnlyProps(props.entrySet().iterator()));
driver = initAndRegisterTestDriver(url, new ReadOnlyProps(props.entrySet().iterator()));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialSystemCatalogIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialSystemCatalogIndexIT.java
index 2a46162e8b..8184f92396 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialSystemCatalogIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/PartialSystemCatalogIndexIT.java
@@ -18,7 +18,7 @@
package org.apache.phoenix.end2end.index;
import static org.apache.phoenix.exception.SQLExceptionCode.CANNOT_INDEX_SYSTEM_TABLE;
-import static org.apache.phoenix.exception.SQLExceptionCode.MISMATCHED_TOKEN;
+import static org.apache.phoenix.exception.SQLExceptionCode.PARSER_ERROR;
import static org.apache.phoenix.query.PhoenixTestBuilder.DDLDefaults.COLUMN_TYPES;
import static org.apache.phoenix.query.PhoenixTestBuilder.DDLDefaults.TENANT_VIEW_COLUMNS;
import static org.apache.phoenix.query.QueryServices.SYSTEM_CATALOG_INDEXES_ENABLED;
@@ -907,7 +907,7 @@ public class PartialSystemCatalogIndexIT extends ParallelStatsDisabledIT {
"CREATE INDEX IF NOT EXISTS SYS_INDEX_FUNCTION_IDX ON SYSTEM.FUNCTION(CLASS_NAME,JAR_PATH) WHERE FUNCTION_NAME = '4'");
fail();
} catch (SQLException sqle) {
- Assert.assertEquals(MISMATCHED_TOKEN.getErrorCode(), sqle.getErrorCode());
+ Assert.assertEquals(PARSER_ERROR.getErrorCode(), sqle.getErrorCode());
}
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ReplicationWithWALAnnotationIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ReplicationWithWALAnnotationIT.java
index 3293a9525a..9848452bd8 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ReplicationWithWALAnnotationIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ReplicationWithWALAnnotationIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.end2end.index;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_STARTUP_DELAY_MS;
import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -151,6 +152,7 @@ public class ReplicationWithWALAnnotationIT extends BaseTest {
ReplicationSinkEndpoint.class.getName());
conf1.setBoolean(IndexRegionObserver.PHOENIX_APPEND_METADATA_TO_WAL, true);
conf1.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/1");
+ conf1.set(INDEX_CDC_CONSUMER_STARTUP_DELAY_MS, Integer.toString(Integer.MAX_VALUE));
setUpConfigForMiniCluster(conf1);
utility1 = new HBaseTestingUtility(conf1);
@@ -165,6 +167,7 @@ public class ReplicationWithWALAnnotationIT extends BaseTest {
conf2.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6);
conf2.setBoolean("dfs.support.append", true);
conf2.setBoolean("hbase.tests.use.shortcircuit.reads", false);
+ conf2.set(INDEX_CDC_CONSUMER_STARTUP_DELAY_MS, Integer.toString(Integer.MAX_VALUE));
utility2 = new HBaseTestingUtility(conf2);
utility2.startMiniZKCluster();
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScanner2IT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScanner2IT.java
index a008150d6b..555f87ff0c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScanner2IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/UncoveredGlobalIndexRegionScanner2IT.java
@@ -19,6 +19,7 @@ package org.apache.phoenix.end2end.index;
import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.assertExplainPlan;
import static org.apache.phoenix.end2end.index.GlobalIndexCheckerIT.assertExplainPlanWithLimit;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -133,6 +134,7 @@ public class UncoveredGlobalIndexRegionScanner2IT extends BaseTest {
props.put(QueryServices.GLOBAL_INDEX_ROW_AGE_THRESHOLD_TO_DELETE_MS_ATTRIB, Long.toString(0));
props.put(QueryServices.PHOENIX_POST_DUMMY_PROCESS,
TestScanningResultPostDummyResultCaller.class.getName());
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnection2IT.java b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnection2IT.java
index 7e348a0902..68221e6340 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnection2IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnection2IT.java
@@ -18,6 +18,7 @@
package org.apache.phoenix.jdbc;
import static org.apache.hadoop.test.GenericTestUtils.waitFor;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.jdbc.HighAvailabilityGroup.PHOENIX_HA_GROUP_ATTR;
import static org.apache.phoenix.jdbc.HighAvailabilityTestingUtility.HBaseTestingUtilityPair.doTestWhenOneZKDown;
import static org.apache.phoenix.jdbc.HighAvailabilityTestingUtility.doTestBasicOperationsWithConnection;
@@ -76,6 +77,11 @@ public class FailoverPhoenixConnection2IT {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
+ CLUSTERS.getHBaseCluster1().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
+ CLUSTERS.getHBaseCluster2().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
+
CLUSTERS.start();
DriverManager.registerDriver(PhoenixDriver.INSTANCE);
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnectionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnectionIT.java
index f633294df2..7ee3f49b9b 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnectionIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/FailoverPhoenixConnectionIT.java
@@ -19,6 +19,7 @@ package org.apache.phoenix.jdbc;
import static org.apache.hadoop.test.GenericTestUtils.waitFor;
import static org.apache.phoenix.exception.SQLExceptionCode.CANNOT_ESTABLISH_CONNECTION;
+import static org.apache.phoenix.hbase.index.IndexCDCConsumer.INDEX_CDC_CONSUMER_STARTUP_DELAY_MS;
import static org.apache.phoenix.jdbc.HighAvailabilityGroup.PHOENIX_HA_GROUP_ATTR;
import static org.apache.phoenix.jdbc.HighAvailabilityGroup.URLS;
import static org.apache.phoenix.jdbc.HighAvailabilityTestingUtility.HBaseTestingUtilityPair;
@@ -99,6 +100,10 @@ public class FailoverPhoenixConnectionIT {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
+ CLUSTERS.getHBaseCluster1().getConfiguration().setInt(INDEX_CDC_CONSUMER_STARTUP_DELAY_MS,
+ Integer.MAX_VALUE);
+ CLUSTERS.getHBaseCluster2().getConfiguration().setInt(INDEX_CDC_CONSUMER_STARTUP_DELAY_MS,
+ Integer.MAX_VALUE);
CLUSTERS.start();
DriverManager.registerDriver(PhoenixDriver.INSTANCE);
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HAConnectionWithMasterAndRPCRegistryIT.java b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HAConnectionWithMasterAndRPCRegistryIT.java
index 1464db36df..46674b61db 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HAConnectionWithMasterAndRPCRegistryIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HAConnectionWithMasterAndRPCRegistryIT.java
@@ -18,6 +18,7 @@
package org.apache.phoenix.jdbc;
import static org.apache.hadoop.test.GenericTestUtils.waitFor;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.jdbc.HighAvailabilityGroup.PHOENIX_HA_GROUP_ATTR;
import static org.apache.phoenix.jdbc.HighAvailabilityTestingUtility.HBaseTestingUtilityPair.doTestWhenOneHBaseDown;
import static org.apache.phoenix.jdbc.HighAvailabilityTestingUtility.doTestBasicOperationsWithConnection;
@@ -86,6 +87,10 @@ public class HAConnectionWithMasterAndRPCRegistryIT {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
+ CLUSTERS.getHBaseCluster1().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
+ CLUSTERS.getHBaseCluster2().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
CLUSTERS.start();
DriverManager.registerDriver(PhoenixDriver.INSTANCE);
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HighAvailabilityTestingUtilityIT.java b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HighAvailabilityTestingUtilityIT.java
index 1564beda21..7453c59903 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HighAvailabilityTestingUtilityIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/HighAvailabilityTestingUtilityIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.jdbc;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.jdbc.HighAvailabilityGroup.PHOENIX_HA_GROUP_ATTR;
import static org.apache.phoenix.jdbc.HighAvailabilityTestingUtility.HBaseTestingUtilityPair;
import static org.apache.phoenix.jdbc.HighAvailabilityTestingUtility.HBaseTestingUtilityPair.doTestWhenOneHBaseDown;
@@ -81,6 +82,10 @@ public class HighAvailabilityTestingUtilityIT {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
+ CLUSTERS.getHBaseCluster1().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
+ CLUSTERS.getHBaseCluster2().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
CLUSTERS.start();
DriverManager.registerDriver(PhoenixDriver.INSTANCE);
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixConnectionFallbackIT.java b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixConnectionFallbackIT.java
index 076e418636..3a1ae7d311 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixConnectionFallbackIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixConnectionFallbackIT.java
@@ -18,6 +18,7 @@
package org.apache.phoenix.jdbc;
import static org.apache.hadoop.test.GenericTestUtils.waitFor;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.jdbc.HighAvailabilityGroup.PHOENIX_HA_GROUP_ATTR;
import static org.apache.phoenix.jdbc.HighAvailabilityPolicy.PARALLEL;
import static org.apache.phoenix.jdbc.HighAvailabilityTestingUtility.HBaseTestingUtilityPair;
@@ -57,6 +58,10 @@ public class ParallelPhoenixConnectionFallbackIT {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
haGroupName = ParallelPhoenixConnectionFallbackIT.class.getSimpleName();
+ CLUSTERS.getHBaseCluster1().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
+ CLUSTERS.getHBaseCluster2().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
CLUSTERS.start();
DriverManager.registerDriver(PhoenixDriver.INSTANCE);
PROPERTIES.setProperty(PHOENIX_HA_GROUP_ATTR, haGroupName);
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixNullComparingResultSetIT.java b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixNullComparingResultSetIT.java
index d14f00ae8a..04b385f762 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixNullComparingResultSetIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/ParallelPhoenixNullComparingResultSetIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.jdbc;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.jdbc.ClusterRoleRecordGeneratorTool.PHOENIX_HA_GROUP_STORE_PEER_ID_DEFAULT;
import static org.apache.phoenix.jdbc.HighAvailabilityGroup.PHOENIX_HA_GROUP_ATTR;
import static org.apache.phoenix.jdbc.HighAvailabilityPolicy.PARALLEL;
@@ -72,6 +73,10 @@ public class ParallelPhoenixNullComparingResultSetIT {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
String haGroupName = ParallelPhoenixNullComparingResultSetIT.class.getSimpleName();
+ CLUSTERS.getHBaseCluster1().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
+ CLUSTERS.getHBaseCluster2().getConfiguration().setBoolean(PHOENIX_INDEX_CDC_CONSUMER_ENABLED,
+ false);
CLUSTERS.start();
DriverManager.registerDriver(PhoenixDriver.INSTANCE);
PROPERTIES.setProperty(PHOENIX_HA_GROUP_ATTR, haGroupName);
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/monitoring/BasePhoenixMetricsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/monitoring/BasePhoenixMetricsIT.java
index 05538f5da4..fe059f8451 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/monitoring/BasePhoenixMetricsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/monitoring/BasePhoenixMetricsIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.monitoring;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.monitoring.MetricType.DELETE_BATCH_FAILED_COUNTER;
import static org.apache.phoenix.monitoring.MetricType.DELETE_BATCH_FAILED_SIZE;
import static org.apache.phoenix.monitoring.MetricType.MUTATION_COMMIT_TIME;
@@ -65,6 +66,7 @@ public abstract class BasePhoenixMetricsIT extends BaseTest {
props.put(QueryServices.COLLECT_REQUEST_LEVEL_METRICS, String.valueOf(true));
// disable renewing leases as this will force spooling to happen.
props.put(QueryServices.RENEW_LEASE_ENABLED, String.valueOf(false));
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
// need the non-test driver for some tests that check number of hconnections, etc.
DriverManager.registerDriver(PhoenixDriver.INSTANCE);
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/monitoring/PhoenixTableLevelMetricsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/monitoring/PhoenixTableLevelMetricsIT.java
index fdaf8a8639..4ca910964a 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/monitoring/PhoenixTableLevelMetricsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/monitoring/PhoenixTableLevelMetricsIT.java
@@ -20,6 +20,7 @@ package org.apache.phoenix.monitoring;
import static org.apache.phoenix.exception.SQLExceptionCode.DATA_EXCEEDS_MAX_CAPACITY;
import static org.apache.phoenix.exception.SQLExceptionCode.GET_TABLE_REGIONS_FAIL;
import static org.apache.phoenix.exception.SQLExceptionCode.OPERATION_TIMED_OUT;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME;
import static org.apache.phoenix.monitoring.GlobalClientMetrics.GLOBAL_MUTATION_BYTES;
import static org.apache.phoenix.monitoring.GlobalClientMetrics.GLOBAL_QUERY_TIME;
@@ -158,6 +159,7 @@ public class PhoenixTableLevelMetricsIT extends BaseTest {
@BeforeClass
public static void doSetup() throws Exception {
final Configuration conf = HBaseConfiguration.create();
+ conf.set(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpConfigForMiniCluster(conf);
conf.set(QueryServices.TABLE_LEVEL_METRICS_ENABLED, String.valueOf(true));
conf.set(QueryServices.METRIC_PUBLISHER_ENABLED, String.valueOf(true));
@@ -189,6 +191,7 @@ public class PhoenixTableLevelMetricsIT extends BaseTest {
Map<String, String> props = Maps.newHashMapWithExpectedSize(1);
props.put(BaseTest.DRIVER_CLASS_NAME_ATTRIB, PhoenixMetricsTestingDriver.class.getName());
props.put(ENABLE_SERVER_UPSERT_SELECT, "true");
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
initAndRegisterTestDriver(url, new ReadOnlyProps(props.entrySet().iterator()));
}
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/monitoring/connectionqueryservice/ConnectionQueryServicesMetricsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/monitoring/connectionqueryservice/ConnectionQueryServicesMetricsIT.java
index 54d53afdf6..57791072cd 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/monitoring/connectionqueryservice/ConnectionQueryServicesMetricsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/monitoring/connectionqueryservice/ConnectionQueryServicesMetricsIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.monitoring.connectionqueryservice;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.monitoring.MetricType.OPEN_INTERNAL_PHOENIX_CONNECTIONS_COUNTER;
import static org.apache.phoenix.monitoring.MetricType.OPEN_PHOENIX_CONNECTIONS_COUNTER;
import static org.apache.phoenix.monitoring.MetricType.PHOENIX_CONNECTIONS_THROTTLED_COUNTER;
@@ -98,6 +99,7 @@ public class ConnectionQueryServicesMetricsIT extends BaseTest {
// counter won't be increased at all. So we need to set max allowed connection count
conf.set(CLIENT_CONNECTION_MAX_ALLOWED_CONNECTIONS, "2");
conf.set(INTERNAL_CONNECTION_MAX_ALLOWED_CONNECTIONS, "1");
+ conf.set(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
return conf;
}
@@ -109,6 +111,7 @@ public class ConnectionQueryServicesMetricsIT extends BaseTest {
// counter won't be increased at all. So we need to set max allowed connection count
conf.set(CLIENT_CONNECTION_MAX_ALLOWED_CONNECTIONS, "2");
conf.set(INTERNAL_CONNECTION_MAX_ALLOWED_CONNECTIONS, "1");
+ conf.set(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
Configuration copy = new Configuration(conf);
copy.addResource(confToClone);
return copy;
@@ -117,6 +120,7 @@ public class ConnectionQueryServicesMetricsIT extends BaseTest {
Configuration conf = HBaseFactoryProvider.getConfigurationFactory().getConfiguration();
conf.set(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(Long.MAX_VALUE));
conf.set(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(Long.MAX_VALUE));
+ conf.set(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
hbaseTestUtil = new HBaseTestingUtility(conf);
setUpConfigForMiniCluster(conf);
conf.set(QueryServices.EXTRA_JDBC_ARGUMENTS_ATTRIB,
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/query/MaxConcurrentConnectionsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/query/MaxConcurrentConnectionsIT.java
index e9df1d0018..2a28c61e78 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/query/MaxConcurrentConnectionsIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/query/MaxConcurrentConnectionsIT.java
@@ -18,6 +18,7 @@
package org.apache.phoenix.query;
import static org.apache.phoenix.exception.SQLExceptionCode.NEW_INTERNAL_CONNECTION_THROTTLED;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.apache.phoenix.monitoring.GlobalClientMetrics.GLOBAL_OPEN_INTERNAL_PHOENIX_CONNECTIONS;
import static org.apache.phoenix.monitoring.GlobalClientMetrics.GLOBAL_OPEN_PHOENIX_CONNECTIONS;
import static org.apache.phoenix.query.QueryServices.CLIENT_CONNECTION_MAX_ALLOWED_CONNECTIONS;
@@ -60,6 +61,7 @@ public class MaxConcurrentConnectionsIT extends BaseTest {
@BeforeClass
public static void setUp() throws Exception {
final Configuration conf = HBaseConfiguration.create();
+ conf.set(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpConfigForMiniCluster(conf);
hbaseTestUtil = new HBaseTestingUtility(conf);
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/query/MetaDataCachingIT.java b/phoenix-core/src/it/java/org/apache/phoenix/query/MetaDataCachingIT.java
index 03e70ddf77..c84d62c76a 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/query/MetaDataCachingIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/query/MetaDataCachingIT.java
@@ -17,6 +17,7 @@
*/
package org.apache.phoenix.query;
+import static org.apache.phoenix.hbase.index.IndexRegionObserver.PHOENIX_INDEX_CDC_CONSUMER_ENABLED;
import static org.junit.Assert.*;
import java.sql.Connection;
@@ -67,6 +68,7 @@ public class MetaDataCachingIT extends BaseTest {
props.put(QueryServices.CLIENT_CACHE_ENCODING, "object");
props.put(QueryServices.TASK_HANDLING_INTERVAL_MS_ATTRIB, Long.toString(Long.MAX_VALUE));
props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(Long.MAX_VALUE));
+ props.put(PHOENIX_INDEX_CDC_CONSUMER_ENABLED, Boolean.toString(false));
setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
}
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/parse/IndexConsistencyParseTest.java b/phoenix-core/src/test/java/org/apache/phoenix/parse/IndexConsistencyParseTest.java
new file mode 100644
index 0000000000..559a57d3f8
--- /dev/null
+++ b/phoenix-core/src/test/java/org/apache/phoenix/parse/IndexConsistencyParseTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.parse;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.apache.phoenix.schema.types.IndexConsistency;
+import org.junit.Test;
+
+/**
+ * Test class for parsing CREATE INDEX statements with CONSISTENCY clause.
+ */
+public class IndexConsistencyParseTest {
+
+ @Test
+ public void testCreateIndexWithStrongConsistency() throws Exception {
+ String sql = "CREATE INDEX idx ON mytable (col1) CONSISTENCY=STRONG";
+ SQLParser parser = new SQLParser(sql);
+ CreateIndexStatement stmt = (CreateIndexStatement) parser.parseStatement();
+
+ assertNotNull("Statement should not be null", stmt);
+ assertEquals("Should have STRONG consistency", IndexConsistency.STRONG,
+ stmt.getIndexConsistency());
+ }
+
+ @Test
+ public void testCreateIndexWithEventualConsistency() throws Exception {
+ String sql = "CREATE INDEX idx ON mytable (col1) CONSISTENCY=EVENTUAL";
+ SQLParser parser = new SQLParser(sql);
+ CreateIndexStatement stmt = (CreateIndexStatement) parser.parseStatement();
+
+ assertNotNull("Statement should not be null", stmt);
+ assertEquals("Should have EVENTUAL consistency", IndexConsistency.EVENTUAL,
+ stmt.getIndexConsistency());
+ }
+
+ @Test
+ public void testCreateIndexWithoutConsistency() throws Exception {
+ String sql = "CREATE INDEX idx ON mytable (col1)";
+ SQLParser parser = new SQLParser(sql);
+ CreateIndexStatement stmt = (CreateIndexStatement) parser.parseStatement();
+
+ assertNotNull("Statement should not be null", stmt);
+ assertNull("Should default to STRONG consistency", stmt.getIndexConsistency());
+ }
+
+ @Test
+ public void testCreateIndexWithConsistencyAndOtherClauses() throws Exception {
+ String sql = "CREATE INDEX idx ON mytable (col1) INCLUDE (col2) ASYNC CONSISTENCY=EVENTUAL";
+ SQLParser parser = new SQLParser(sql);
+ CreateIndexStatement stmt = (CreateIndexStatement) parser.parseStatement();
+
+ assertNotNull("Statement should not be null", stmt);
+ assertEquals("Should have EVENTUAL consistency", IndexConsistency.EVENTUAL,
+ stmt.getIndexConsistency());
+ assertEquals("Should have include columns", 1, stmt.getIncludeColumns().size());
+ assertEquals("Should be async", true, stmt.isAsync());
+ }
+
+ @Test
+ public void testLocalIndexWithConsistency() throws Exception {
+ String sql = "CREATE LOCAL INDEX idx ON mytable (col1) CONSISTENCY=EVENTUAL";
+ SQLParser parser = new SQLParser(sql);
+ CreateIndexStatement stmt = (CreateIndexStatement) parser.parseStatement();
+
+ assertNotNull("Statement should not be null", stmt);
+ assertEquals("Should have EVENTUAL consistency", IndexConsistency.EVENTUAL,
+ stmt.getIndexConsistency());
+ }
+}
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
index 7b14996fba..e2e3afedef 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/BaseTest.java
@@ -2070,7 +2070,7 @@ public abstract class BaseTest {
* @throws IOException if something went wrong while connecting to Admin
*/
public synchronized static boolean isAnyStoreRefCountLeaked(Admin admin) throws IOException {
- int retries = 5;
+ int retries = 15;
while (retries > 0) {
boolean isStoreRefCountLeaked = isStoreRefCountLeaked(admin);
if (!isStoreRefCountLeaked) {
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/util/IndexScrutiny.java b/phoenix-core/src/test/java/org/apache/phoenix/util/IndexScrutiny.java
index 75cc9e3843..565d7aec7d 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/util/IndexScrutiny.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/util/IndexScrutiny.java
@@ -39,7 +39,22 @@ public class IndexScrutiny {
public static long scrutinizeIndex(Connection conn, String fullTableName, String fullIndexName)
throws SQLException {
PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
- PTable ptable = pconn.getTable(new PTableKey(pconn.getTenantId(), fullTableName));
+ String schemaName = SchemaUtil.getSchemaNameFromFullName(fullTableName);
+ String tableName = SchemaUtil.getTableNameFromFullName(fullTableName);
+ String indexName = SchemaUtil.getTableNameFromFullName(fullIndexName);
+ PTable ptable;
+ PTable pindex;
+ if (schemaName != null && !schemaName.isEmpty()) {
+ ptable =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), SchemaUtil.normalizeIdentifier(schemaName)
+ + "." + SchemaUtil.normalizeIdentifier(tableName)));
+ pindex =
+ pconn.getTable(new PTableKey(pconn.getTenantId(), SchemaUtil.normalizeIdentifier(schemaName)
+ + "." + SchemaUtil.normalizeIdentifier(indexName)));
+ } else {
+ ptable = pconn.getTable(new PTableKey(pconn.getTenantId(), fullTableName));
+ pindex = pconn.getTable(new PTableKey(pconn.getTenantId(), fullIndexName));
+ }
int tableColumnOffset = 0;
List<PColumn> tableColumns = ptable.getColumns();
List<PColumn> tablePKColumns = ptable.getPKColumns();
@@ -48,7 +63,6 @@ public class IndexScrutiny {
tableColumns = tableColumns.subList(tableColumnOffset, tableColumns.size());
tablePKColumns = tablePKColumns.subList(tableColumnOffset, tablePKColumns.size());
}
- PTable pindex = pconn.getTable(new PTableKey(pconn.getTenantId(), fullIndexName));
List<PColumn> indexColumns = pindex.getColumns();
int indexColumnOffset = 0;
if (pindex.getBucketNum() != null) {
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java b/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
index 6541dd7338..69a191b5c2 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/util/TestUtil.java
@@ -78,6 +78,7 @@ import org.apache.hadoop.hbase.client.CoprocessorDescriptorBuilder;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionLocator;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
@@ -1694,4 +1695,97 @@ public class TestUtil {
return sb.toString();
}
+ public static void splitTable(String url, String tableName, int nSplits,
+ long sleepBetweenSplitsMs) {
+ try (
+ PhoenixConnection phoenixConn =
+ DriverManager.getConnection(url).unwrap(PhoenixConnection.class);
+ Admin admin = phoenixConn.getQueryServices().getAdmin()) {
+ TableName hTableName = TableName.valueOf(tableName);
+ int splitCount = 0;
+ while (splitCount < nSplits) {
+ Thread.sleep(sleepBetweenSplitsMs);
+ try {
+ int regionsBefore = admin.getRegions(hTableName).size();
+ admin.split(hTableName);
+ int retries = 0;
+ while (retries < 50) {
+ int regionsAfter = admin.getRegions(hTableName).size();
+ if (regionsAfter >= regionsBefore * 2) {
+ LOGGER.info("Split {} completed, regions: {} -> {}", splitCount + 1, regionsBefore,
+ regionsAfter);
+ break;
+ }
+ retries++;
+ Thread.sleep(100);
+ }
+ splitCount++;
+ } catch (Exception e) {
+ LOGGER.warn("Exception during split: " + e.getMessage());
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.error("Error in split thread", e);
+ }
+ }
+
+ /**
+ * Merges table regions in groups of nMerges consecutive regions.
+ * @param url connection URL.
+ * @param tableName name of the table.
+ * @param nMerges number of consecutive regions to merge together in each group.
+ * @param sleepBetweenMergesMs time to sleep between merge operations.
+ */
+ public static void mergeTableRegions(String url, String tableName, int nMerges,
+ long sleepBetweenMergesMs) {
+ if (nMerges < 2) {
+ throw new IllegalArgumentException("nMerges must be at least 2");
+ }
+ try (
+ PhoenixConnection phoenixConn =
+ DriverManager.getConnection(url).unwrap(PhoenixConnection.class);
+ Admin admin = phoenixConn.getQueryServices().getAdmin()) {
+ TableName hTableName = TableName.valueOf(tableName);
+ List<RegionInfo> regions = admin.getRegions(hTableName);
+ if (regions.size() < 2) {
+ return;
+ }
+ int regionIndex = 0;
+ while (regionIndex < regions.size()) {
+ int remainingRegions = regions.size() - regionIndex;
+ int groupSize = Math.min(nMerges, remainingRegions);
+ if (groupSize < 2) {
+ break;
+ }
+ byte[][] regionsToMerge = new byte[groupSize][];
+ for (int i = 0; i < groupSize; i++) {
+ regionsToMerge[i] = regions.get(regionIndex + i).getRegionName();
+ }
+ try {
+ int regionsBefore = admin.getRegions(hTableName).size();
+ admin.mergeRegionsAsync(regionsToMerge, false).get();
+ int retries = 0;
+ while (retries < 50) {
+ List<RegionInfo> currentRegions = admin.getRegions(hTableName);
+ if (currentRegions.size() <= regionsBefore - groupSize + 1) {
+ LOGGER.info("Merge completed, regions: {} -> {}", regionsBefore,
+ currentRegions.size());
+ break;
+ }
+ retries++;
+ Thread.sleep(100);
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Exception during merge: " + e.getMessage(), e);
+ }
+ regionIndex += groupSize;
+ if (regionIndex < regions.size()) {
+ Thread.sleep(sleepBetweenMergesMs);
+ }
+ }
+ } catch (Exception e) {
+ LOGGER.error("Error in mergeTableRegions", e);
+ }
+ }
+
}
diff --git a/pom.xml b/pom.xml
index 3c27ab4f91..ae2d2c6d79 100644
--- a/pom.xml
+++ b/pom.xml
@@ -115,6 +115,7 @@
<jcip-annotations.version>1.0-1</jcip-annotations.version>
<jsr305.version>2.0.1</jsr305.version>
<snappy.version>0.5</snappy.version>
+ <snappy-java.version>1.1.10.8</snappy-java.version>
<commons-codec.version>1.16.0</commons-codec.version>
<htrace.version>3.1.0-incubating</htrace.version>
<jodatime.version>2.10.5</jodatime.version>
@@ -1253,6 +1254,11 @@
<artifactId>snappy</artifactId>
<version>${snappy.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.xerial.snappy</groupId>
+ <artifactId>snappy-java</artifactId>
+ <version>${snappy-java.version}</version>
+ </dependency>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>