From e483f7c776ed53015d08b78fc33729892dc2143c Mon Sep 17 00:00:00 2001 From: Sivabalan Narayanan Date: Fri, 3 Dec 2021 07:20:21 -0500 Subject: [PATCH] [HUDI-2902] Fixing populate meta fields with Hfile writers and Disabling virtual keys by default for metadata table (#4194) --- .../hudi/io/HoodieSortedMergeHandle.java | 3 +- .../io/storage/HoodieFileWriterFactory.java | 2 +- .../hudi/io/storage/HoodieHFileWriter.java | 14 +- .../storage/TestHoodieHFileReaderWriter.java | 55 ++++-- .../exampleSchemaWithMetaFields.avsc | 66 ++++++++ .../functional/TestHoodieBackedMetadata.java | 157 ++++++++++++++++++ .../functional/TestHoodieMetadataBase.java | 2 +- .../common/config/HoodieMetadataConfig.java | 12 +- .../HoodieMetadataMergedLogRecordReader.java | 4 +- ...sModeWithMultipleWriters.COPY_ON_WRITE.zip | Bin 2592485 -> 2592484 bytes ...sModeWithMultipleWriters.MERGE_ON_READ.zip | Bin 3015940 -> 3015939 bytes 11 files changed, 287 insertions(+), 28 deletions(-) create mode 100644 hudi-client/hudi-client-common/src/test/resources/exampleSchemaWithMetaFields.avsc diff --git a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieSortedMergeHandle.java b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieSortedMergeHandle.java index 606e63a34..533611df2 100644 --- a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieSortedMergeHandle.java +++ b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/HoodieSortedMergeHandle.java @@ -27,6 +27,7 @@ import org.apache.hudi.common.util.Option; import org.apache.hudi.config.HoodieWriteConfig; import org.apache.hudi.exception.HoodieUpsertException; import org.apache.hudi.keygen.BaseKeyGenerator; +import org.apache.hudi.keygen.KeyGenUtils; import org.apache.hudi.table.HoodieTable; import org.apache.avro.generic.GenericRecord; @@ -72,7 +73,7 @@ public class HoodieSortedMergeHandle ext */ @Override public void write(GenericRecord oldRecord) { - String key = oldRecord.get(HoodieRecord.RECORD_KEY_METADATA_FIELD).toString(); + String key = KeyGenUtils.getRecordKeyFromGenericRecord(oldRecord, keyGeneratorOpt); // To maintain overall sorted order across updates and inserts, write any new inserts whose keys are less than // the oldRecord's key. diff --git a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieFileWriterFactory.java b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieFileWriterFactory.java index c6d4540f2..0b6afd4d2 100644 --- a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieFileWriterFactory.java +++ b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieFileWriterFactory.java @@ -89,7 +89,7 @@ public class HoodieFileWriterFactory { config.getHFileCompressionAlgorithm(), config.getHFileBlockSize(), config.getHFileMaxFileSize(), PREFETCH_ON_OPEN, CACHE_DATA_IN_L1, DROP_BEHIND_CACHE_COMPACTION, filter, HFILE_COMPARATOR); - return new HoodieHFileWriter<>(instantTime, path, hfileConfig, schema, taskContextSupplier); + return new HoodieHFileWriter<>(instantTime, path, hfileConfig, schema, taskContextSupplier, config.populateMetaFields()); } private static HoodieFileWriter newOrcFileWriter( diff --git a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieHFileWriter.java b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieHFileWriter.java index d7e01c50c..a719bcb8f 100644 --- a/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieHFileWriter.java +++ b/hudi-client/hudi-client-common/src/main/java/org/apache/hudi/io/storage/HoodieHFileWriter.java @@ -62,6 +62,7 @@ public class HoodieHFileWriter populateMetaFieldsAndTestAvroWithMeta() { + return Arrays.stream(new Boolean[][] { + {true, true}, + {false, true}, + {true, false}, + {false, false} + }).map(Arguments::of); + } + + private HoodieHFileWriter createHFileWriter(Schema avroSchema, boolean populateMetaFields) throws Exception { BloomFilter filter = BloomFilterFactory.createBloomFilter(1000, 0.00001, -1, BloomFilterTypeCode.SIMPLE.name()); Configuration conf = new Configuration(); TaskContextSupplier mockTaskContextSupplier = Mockito.mock(TaskContextSupplier.class); + Supplier partitionSupplier = Mockito.mock(Supplier.class); + when(mockTaskContextSupplier.getPartitionIdSupplier()).thenReturn(partitionSupplier); + when(partitionSupplier.get()).thenReturn(10); String instantTime = "000"; HoodieHFileConfig hoodieHFileConfig = new HoodieHFileConfig(conf, Compression.Algorithm.GZ, 1024 * 1024, 120 * 1024 * 1024, PREFETCH_ON_OPEN, CACHE_DATA_IN_L1, DROP_BEHIND_CACHE_COMPACTION, filter, HFILE_COMPARATOR); - return new HoodieHFileWriter(instantTime, filePath, hoodieHFileConfig, avroSchema, mockTaskContextSupplier); + return new HoodieHFileWriter(instantTime, filePath, hoodieHFileConfig, avroSchema, mockTaskContextSupplier, populateMetaFields); } - @Test - public void testWriteReadHFile() throws Exception { - Schema avroSchema = getSchemaFromResource(TestHoodieOrcReaderWriter.class, "/exampleSchema.avsc"); - HoodieHFileWriter writer = createHFileWriter(avroSchema); + @ParameterizedTest + @MethodSource("populateMetaFieldsAndTestAvroWithMeta") + public void testWriteReadHFile(boolean populateMetaFields, boolean testAvroWithMeta) throws Exception { + Schema avroSchema = getSchemaFromResource(TestHoodieOrcReaderWriter.class, "/exampleSchemaWithMetaFields.avsc"); + HoodieHFileWriter writer = createHFileWriter(avroSchema, populateMetaFields); List keys = new ArrayList<>(); Map recordMap = new HashMap<>(); for (int i = 0; i < 100; i++) { @@ -97,7 +121,13 @@ public class TestHoodieHFileReaderWriter { keys.add(key); record.put("time", Integer.toString(RANDOM.nextInt())); record.put("number", i); - writer.writeAvro(key, record); + if (testAvroWithMeta) { + writer.writeAvroWithMetadata(record, new HoodieRecord(new HoodieKey((String) record.get("_row_key"), + Integer.toString((Integer) record.get("number"))), new EmptyHoodieRecordPayload())); // payload does not matter. GenericRecord passed in is what matters + // only HoodieKey will be looked up from the 2nd arg(HoodieRecord). + } else { + writer.writeAvro(key, record); + } recordMap.put(key, record); } writer.close(); @@ -109,8 +139,8 @@ public class TestHoodieHFileReaderWriter { records.forEach(entry -> assertEquals(entry.getSecond(), recordMap.get(entry.getFirst()))); hoodieHFileReader.close(); - for (int i = 0; i < 20; i++) { - int randomRowstoFetch = 5 + RANDOM.nextInt(50); + for (int i = 0; i < 2; i++) { + int randomRowstoFetch = 5 + RANDOM.nextInt(10); Set rowsToFetch = getRandomKeys(randomRowstoFetch, keys); List rowsList = new ArrayList<>(rowsToFetch); Collections.sort(rowsList); @@ -119,6 +149,11 @@ public class TestHoodieHFileReaderWriter { assertEquals(result.size(), randomRowstoFetch); result.forEach(entry -> { assertEquals(entry.getSecond(), recordMap.get(entry.getFirst())); + if (populateMetaFields && testAvroWithMeta) { + assertNotNull(entry.getSecond().get(HoodieRecord.RECORD_KEY_METADATA_FIELD)); + } else { + assertNull(entry.getSecond().get(HoodieRecord.RECORD_KEY_METADATA_FIELD)); + } }); hoodieHFileReader.close(); } diff --git a/hudi-client/hudi-client-common/src/test/resources/exampleSchemaWithMetaFields.avsc b/hudi-client/hudi-client-common/src/test/resources/exampleSchemaWithMetaFields.avsc new file mode 100644 index 000000000..c3fa82207 --- /dev/null +++ b/hudi-client/hudi-client-common/src/test/resources/exampleSchemaWithMetaFields.avsc @@ -0,0 +1,66 @@ +/* + * 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. + */ +{ + "namespace": "example.schema", + "type": "record", + "name": "trip", + "fields": [ + { + "name": "_hoodie_commit_time", + "type": ["null","string"], + "default":null + }, + { + "name": "_hoodie_commit_seqno", + "type": ["null","string"], + "default":null + }, + { + "name": "_hoodie_record_key", + "type": ["null","string"], + "default":null + }, + { + "name": "_hoodie_partition_path", + "type": ["null","string"], + "default":null + }, + { + "name": "_hoodie_file_name", + "type": ["null","string"], + "default":null + }, + { + "name": "_hoodie_operation", + "type": ["null","string"], + "default":null + }, + { + "name": "_row_key", + "type": "string" + }, + { + "name": "time", + "type": "string" + }, + { + "name": "number", + "type": ["int", "null"] + } + ] +} diff --git a/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieBackedMetadata.java b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieBackedMetadata.java index 73b78118b..ed245da3b 100644 --- a/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieBackedMetadata.java +++ b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieBackedMetadata.java @@ -18,6 +18,7 @@ package org.apache.hudi.client.functional; +import org.apache.hudi.avro.model.HoodieMetadataRecord; import org.apache.hudi.client.SparkRDDWriteClient; import org.apache.hudi.client.WriteStatus; import org.apache.hudi.client.common.HoodieSparkEngineContext; @@ -29,6 +30,8 @@ import org.apache.hudi.common.fs.ConsistencyGuardConfig; import org.apache.hudi.common.fs.FSUtils; import org.apache.hudi.common.metrics.Registry; import org.apache.hudi.common.model.FileSlice; +import org.apache.hudi.common.model.HoodieBaseFile; +import org.apache.hudi.common.model.HoodieCleaningPolicy; import org.apache.hudi.common.model.HoodieCommitMetadata; import org.apache.hudi.common.model.HoodieFailedWritesCleaningPolicy; import org.apache.hudi.common.model.HoodieFileFormat; @@ -41,6 +44,7 @@ import org.apache.hudi.common.model.WriteConcurrencyMode; import org.apache.hudi.common.table.HoodieTableConfig; import org.apache.hudi.common.table.HoodieTableMetaClient; import org.apache.hudi.common.table.HoodieTableVersion; +import org.apache.hudi.common.table.marker.MarkerType; import org.apache.hudi.common.table.timeline.HoodieActiveTimeline; import org.apache.hudi.common.table.timeline.HoodieInstant; import org.apache.hudi.common.table.timeline.HoodieTimeline; @@ -60,12 +64,18 @@ import org.apache.hudi.config.HoodieIndexConfig; import org.apache.hudi.config.HoodieLockConfig; import org.apache.hudi.config.HoodieStorageConfig; import org.apache.hudi.config.HoodieWriteConfig; +import org.apache.hudi.config.metrics.HoodieMetricsConfig; +import org.apache.hudi.config.metrics.HoodieMetricsGraphiteConfig; +import org.apache.hudi.config.metrics.HoodieMetricsJmxConfig; import org.apache.hudi.exception.HoodieMetadataException; import org.apache.hudi.index.HoodieIndex; +import org.apache.hudi.io.storage.HoodieHFileReader; import org.apache.hudi.metadata.FileSystemBackedTableMetadata; import org.apache.hudi.metadata.HoodieBackedTableMetadataWriter; import org.apache.hudi.metadata.HoodieMetadataMetrics; +import org.apache.hudi.metadata.HoodieMetadataPayload; import org.apache.hudi.metadata.HoodieTableMetadata; +import org.apache.hudi.metadata.HoodieTableMetadataKeyGenerator; import org.apache.hudi.metadata.MetadataPartitionType; import org.apache.hudi.metadata.SparkHoodieBackedTableMetadataWriter; import org.apache.hudi.table.HoodieSparkTable; @@ -75,9 +85,13 @@ import org.apache.hudi.table.upgrade.SparkUpgradeDowngradeHelper; import org.apache.hudi.table.upgrade.UpgradeDowngrade; import org.apache.hudi.testutils.MetadataMergeWriteStatus; +import org.apache.avro.generic.GenericRecord; +import org.apache.avro.generic.IndexedRecord; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hbase.io.hfile.CacheConfig; +import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.util.Time; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; @@ -120,10 +134,12 @@ import static org.apache.hudi.common.model.WriteOperationType.DELETE; import static org.apache.hudi.common.model.WriteOperationType.INSERT; import static org.apache.hudi.common.model.WriteOperationType.UPSERT; import static org.apache.hudi.common.testutils.HoodieTestDataGenerator.TRIP_EXAMPLE_SCHEMA; +import static org.apache.hudi.metadata.HoodieTableMetadata.METADATA_TABLE_NAME_SUFFIX; import static org.apache.hudi.testutils.Assertions.assertNoWriteErrors; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -317,6 +333,7 @@ public class TestHoodieBackedMetadata extends TestHoodieMetadataBase { /** * Tests that table services in data table won't trigger table services in metadata table. + * * @throws Exception */ @Test @@ -346,6 +363,56 @@ public class TestHoodieBackedMetadata extends TestHoodieMetadataBase { assertEquals(tableMetadata.getLatestCompactionTime().get(), "0000004001"); } + + /** + * Tests that virtual key configs are honored in base files after compaction in metadata table. + * + * @throws Exception + */ + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testVirtualKeysInBaseFiles(boolean populateMetaFields) throws Exception { + HoodieTableType tableType = MERGE_ON_READ; + init(tableType, false); + writeConfig = getWriteConfigBuilder(true, true, false) + .withMetadataConfig(HoodieMetadataConfig.newBuilder() + .enable(true) + .enableFullScan(true) + .enableMetrics(false) + .withPopulateMetaFields(populateMetaFields) + .withMaxNumDeltaCommitsBeforeCompaction(2) + .build()).build(); + initWriteConfigAndMetatableWriter(writeConfig, true); + + doWriteOperation(testTable, "0000001", INSERT); + doClean(testTable, "0000003", Arrays.asList("0000001")); + // this should have triggered compaction in metadata table + doWriteOperation(testTable, "0000004", UPSERT); + + HoodieTableMetadata tableMetadata = metadata(writeConfig, context); + assertTrue(tableMetadata.getLatestCompactionTime().isPresent()); + assertEquals(tableMetadata.getLatestCompactionTime().get(), "0000004001"); + + HoodieTableMetaClient metadataMetaClient = HoodieTableMetaClient.builder().setConf(hadoopConf).setBasePath(metadataTableBasePath).build(); + HoodieWriteConfig metadataTableWriteConfig = getMetadataWriteConfig(writeConfig); + metadataMetaClient.reloadActiveTimeline(); + + HoodieTable table = HoodieSparkTable.create(metadataTableWriteConfig, context, metadataMetaClient); + table.getHoodieView().sync(); + List fileSlices = table.getSliceView().getLatestFileSlices("files").collect(Collectors.toList()); + HoodieBaseFile baseFile = fileSlices.get(0).getBaseFile().get(); + HoodieHFileReader hoodieHFileReader = new HoodieHFileReader(context.getHadoopConf().get(), new Path(baseFile.getPath()), + new CacheConfig(context.getHadoopConf().get())); + List> records = hoodieHFileReader.readAllRecords(); + records.forEach(entry -> { + if (populateMetaFields) { + assertNotNull(((GenericRecord) entry.getSecond()).get(HoodieRecord.RECORD_KEY_METADATA_FIELD)); + } else { + assertNull(((GenericRecord) entry.getSecond()).get(HoodieRecord.RECORD_KEY_METADATA_FIELD)); + } + }); + } + /** * Test rollback of various table operations sync to Metadata Table correctly. */ @@ -586,6 +653,7 @@ public class TestHoodieBackedMetadata extends TestHoodieMetadataBase { * Tests the metadata payload spurious deletes. * Lets say a commit was applied to metadata table, and later was explicitly got rolledback. Due to spark task failures, there could be more files in rollback * metadata when compared to the original commit metadata. When payload consistency check is enabled, it will throw exception. If not, it will succeed. + * * @throws Exception */ @ParameterizedTest @@ -1308,6 +1376,95 @@ public class TestHoodieBackedMetadata extends TestHoodieMetadataBase { } } + /** + * Fetching WriteConfig for metadata table from Data table's writeConfig is not trivial and the method is not public in source code. so, for now, + * using this method which mimics source code. + * @param writeConfig + * @return + */ + private HoodieWriteConfig getMetadataWriteConfig(HoodieWriteConfig writeConfig) { + int parallelism = writeConfig.getMetadataInsertParallelism(); + + int minCommitsToKeep = Math.max(writeConfig.getMetadataMinCommitsToKeep(), writeConfig.getMinCommitsToKeep()); + int maxCommitsToKeep = Math.max(writeConfig.getMetadataMaxCommitsToKeep(), writeConfig.getMaxCommitsToKeep()); + + // Create the write config for the metadata table by borrowing options from the main write config. + HoodieWriteConfig.Builder builder = HoodieWriteConfig.newBuilder() + .withTimelineLayoutVersion(TimelineLayoutVersion.CURR_VERSION) + .withConsistencyGuardConfig(ConsistencyGuardConfig.newBuilder() + .withConsistencyCheckEnabled(writeConfig.getConsistencyGuardConfig().isConsistencyCheckEnabled()) + .withInitialConsistencyCheckIntervalMs(writeConfig.getConsistencyGuardConfig().getInitialConsistencyCheckIntervalMs()) + .withMaxConsistencyCheckIntervalMs(writeConfig.getConsistencyGuardConfig().getMaxConsistencyCheckIntervalMs()) + .withMaxConsistencyChecks(writeConfig.getConsistencyGuardConfig().getMaxConsistencyChecks()) + .build()) + .withWriteConcurrencyMode(WriteConcurrencyMode.SINGLE_WRITER) + .withMetadataConfig(HoodieMetadataConfig.newBuilder().enable(false).withFileListingParallelism(writeConfig.getFileListingParallelism()).build()) + .withAutoCommit(true) + .withAvroSchemaValidate(true) + .withEmbeddedTimelineServerEnabled(false) + .withMarkersType(MarkerType.DIRECT.name()) + .withRollbackUsingMarkers(false) + .withPath(HoodieTableMetadata.getMetadataTableBasePath(writeConfig.getBasePath())) + .withSchema(HoodieMetadataRecord.getClassSchema().toString()) + .forTable(writeConfig.getTableName() + METADATA_TABLE_NAME_SUFFIX) + .withCompactionConfig(HoodieCompactionConfig.newBuilder() + .withAsyncClean(writeConfig.isMetadataAsyncClean()) + // we will trigger cleaning manually, to control the instant times + .withAutoClean(false) + .withCleanerParallelism(parallelism) + .withCleanerPolicy(HoodieCleaningPolicy.KEEP_LATEST_COMMITS) + .withFailedWritesCleaningPolicy(HoodieFailedWritesCleaningPolicy.LAZY) + .retainCommits(writeConfig.getMetadataCleanerCommitsRetained()) + .archiveCommitsWith(minCommitsToKeep, maxCommitsToKeep) + // we will trigger compaction manually, to control the instant times + .withInlineCompaction(false) + .withMaxNumDeltaCommitsBeforeCompaction(writeConfig.getMetadataCompactDeltaCommitMax()).build()) + .withParallelism(parallelism, parallelism) + .withDeleteParallelism(parallelism) + .withRollbackParallelism(parallelism) + .withFinalizeWriteParallelism(parallelism) + .withAllowMultiWriteOnSameInstant(true) + .withKeyGenerator(HoodieTableMetadataKeyGenerator.class.getCanonicalName()) + .withPopulateMetaFields(writeConfig.getMetadataConfig().populateMetaFields()); + + // RecordKey properties are needed for the metadata table records + final Properties properties = new Properties(); + properties.put(HoodieTableConfig.RECORDKEY_FIELDS.key(), HoodieMetadataPayload.SCHEMA_FIELD_ID_KEY); + properties.put("hoodie.datasource.write.recordkey.field", HoodieMetadataPayload.SCHEMA_FIELD_ID_KEY); + builder.withProperties(properties); + + if (writeConfig.isMetricsOn()) { + builder.withMetricsConfig(HoodieMetricsConfig.newBuilder() + .withReporterType(writeConfig.getMetricsReporterType().toString()) + .withExecutorMetrics(writeConfig.isExecutorMetricsEnabled()) + .on(true).build()); + switch (writeConfig.getMetricsReporterType()) { + case GRAPHITE: + builder.withMetricsGraphiteConfig(HoodieMetricsGraphiteConfig.newBuilder() + .onGraphitePort(writeConfig.getGraphiteServerPort()) + .toGraphiteHost(writeConfig.getGraphiteServerHost()) + .usePrefix(writeConfig.getGraphiteMetricPrefix()).build()); + break; + case JMX: + builder.withMetricsJmxConfig(HoodieMetricsJmxConfig.newBuilder() + .onJmxPort(writeConfig.getJmxPort()) + .toJmxHost(writeConfig.getJmxHost()) + .build()); + break; + case DATADOG: + case PROMETHEUS: + case PROMETHEUS_PUSHGATEWAY: + case CONSOLE: + case INMEMORY: + case CLOUDWATCH: + break; + default: + throw new HoodieMetadataException("Unsupported Metrics Reporter type " + writeConfig.getMetricsReporterType()); + } + } + return builder.build(); + } + private void doPreBootstrapOperations(HoodieTestTable testTable) throws Exception { doPreBootstrapOperations(testTable, "0000001", "0000002"); } diff --git a/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieMetadataBase.java b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieMetadataBase.java index 0a45d31c8..25dfd292d 100644 --- a/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieMetadataBase.java +++ b/hudi-client/hudi-spark-client/src/test/java/org/apache/hudi/client/functional/TestHoodieMetadataBase.java @@ -292,7 +292,7 @@ public class TestHoodieMetadataBase extends HoodieClientTestHarness { .enable(useFileListingMetadata) .enableFullScan(enableFullScan) .enableMetrics(enableMetrics) - .withPopulateMetaFields(false) + .withPopulateMetaFields(HoodieMetadataConfig.POPULATE_META_FIELDS.defaultValue()) .ignoreSpuriousDeletes(validateMetadataPayloadConsistency) .build()) .withMetricsConfig(HoodieMetricsConfig.newBuilder().on(enableMetrics) diff --git a/hudi-common/src/main/java/org/apache/hudi/common/config/HoodieMetadataConfig.java b/hudi-common/src/main/java/org/apache/hudi/common/config/HoodieMetadataConfig.java index 810e475e5..51791c945 100644 --- a/hudi-common/src/main/java/org/apache/hudi/common/config/HoodieMetadataConfig.java +++ b/hudi-common/src/main/java/org/apache/hudi/common/config/HoodieMetadataConfig.java @@ -118,23 +118,15 @@ public final class HoodieMetadataConfig extends HoodieConfig { .sinceVersion("0.7.0") .withDocumentation("Parallelism to use, when listing the table on lake storage."); - public static final ConfigProperty ENABLE_INLINE_READING = ConfigProperty - .key(METADATA_PREFIX + ".enable.inline.reading") - .defaultValue(true) - .sinceVersion("0.10.0") - .withDocumentation("Enable inline reading of Log files. By default log block contents are read as byte[] using regular input stream and records " - + "are deserialized from it. Enabling this will read each log block as an inline file and read records from the same. For instance, " - + "for HFileDataBlock, a inline file will be read using HFileReader."); - public static final ConfigProperty ENABLE_FULL_SCAN_LOG_FILES = ConfigProperty .key(METADATA_PREFIX + ".enable.full.scan.log.files") .defaultValue(true) .sinceVersion("0.10.0") .withDocumentation("Enable full scanning of log files while reading log records. If disabled, hudi does look up of only interested entries."); - public static final ConfigProperty POPULATE_META_FIELDS = ConfigProperty + public static final ConfigProperty POPULATE_META_FIELDS = ConfigProperty .key(METADATA_PREFIX + ".populate.meta.fields") - .defaultValue("false") + .defaultValue(true) .sinceVersion("0.10.0") .withDocumentation("When enabled, populates all meta fields. When disabled, no meta fields are populated."); diff --git a/hudi-common/src/main/java/org/apache/hudi/metadata/HoodieMetadataMergedLogRecordReader.java b/hudi-common/src/main/java/org/apache/hudi/metadata/HoodieMetadataMergedLogRecordReader.java index 2c9ca39fd..e635eeaaa 100644 --- a/hudi-common/src/main/java/org/apache/hudi/metadata/HoodieMetadataMergedLogRecordReader.java +++ b/hudi-common/src/main/java/org/apache/hudi/metadata/HoodieMetadataMergedLogRecordReader.java @@ -28,6 +28,8 @@ import org.apache.avro.Schema; import org.apache.avro.generic.GenericRecord; import org.apache.avro.generic.IndexedRecord; import org.apache.hadoop.fs.FileSystem; + +import org.apache.hudi.common.config.HoodieMetadataConfig; import org.apache.hudi.common.table.HoodieTableConfig; import org.apache.hudi.common.util.SpillableMapUtils; import org.apache.log4j.LogManager; @@ -142,7 +144,7 @@ public class HoodieMetadataMergedLogRecordReader extends HoodieMergedLogRecordSc */ public static class Builder extends HoodieMergedLogRecordScanner.Builder { private Set mergeKeyFilter = Collections.emptySet(); - private boolean enableFullScan; + private boolean enableFullScan = HoodieMetadataConfig.ENABLE_FULL_SCAN_LOG_FILES.defaultValue(); private boolean enableInlineReading; @Override diff --git a/hudi-utilities/src/test/resources/fixtures/testUpsertsContinuousModeWithMultipleWriters.COPY_ON_WRITE.zip b/hudi-utilities/src/test/resources/fixtures/testUpsertsContinuousModeWithMultipleWriters.COPY_ON_WRITE.zip index 42c0301b310feb65d7eeca166a6cc60dbca40e8f..299b070bee34ac21e9b4a8426141e71e10a1b682 100644 GIT binary patch delta 5525 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 adPpKFVp37=r0sVwm5#JqJPjn!E&m78>9W=U delta 5441 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 F{{g1JW+ngt diff --git a/hudi-utilities/src/test/resources/fixtures/testUpsertsContinuousModeWithMultipleWriters.MERGE_ON_READ.zip b/hudi-utilities/src/test/resources/fixtures/testUpsertsContinuousModeWithMultipleWriters.MERGE_ON_READ.zip index 8e57b1d45f38a68ee2f2be7bf558868b96d5ca38..d80439d20d3dfc8c61a89a6fa850a1ef11f9fc45 100644 GIT binary patch delta 7711 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 delta 7488 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 N^7)rZNF2k4`Cq2>{i^@~