Skip to content

HIVE-29694#6577

Open
InvisibleProgrammer wants to merge 7 commits into
apache:masterfrom
InvisibleProgrammer:HIVE-29694
Open

HIVE-29694#6577
InvisibleProgrammer wants to merge 7 commits into
apache:masterfrom
InvisibleProgrammer:HIVE-29694

Conversation

@InvisibleProgrammer

@InvisibleProgrammer InvisibleProgrammer commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Details in the corresponding Jira: https://issues.apache.org/jira/browse/HIVE-29694

Notes about the implementation:
MetaStoreDirectSql.getColumnDescriptor runs multiple queries to get a reusable column descriptor.
I leave the possibility to reduce the amount of queries run as a future improvement as theoretically it would be possible to find a single query that covers all the candidates but practically Hive support so many HMS databases and their feature set is significant.

What changes were proposed in this pull request?

The problem came from replication when addPartition added a lot of extra records in case the table had schema evolution.
In this change, we try to re-use existing schema info based on the following algorithm:

  • Take the latest 10 partition
  • Filter out partitions when the column count matches with the column count of the partition that we currently want to add.
  • Check the names and types of the candidates in order. If there is a math, we can reuse that info. If not, we can still add a new record.

Why are the changes needed?

We got a customer with huge tables (with hundreds of thousands of partitions) with schema evolution between partitions. Their HMS became bloated.

Does this PR introduce any user-facing change?

No

How was this patch tested?

Added new test class, TestHMSColumnDescriptorReuse.
Ran it with all the supported databases.

ACID_TXN_CLEANER_INTERVAL("metastore.acid.txn.cleaner.interval",
"hive.metastore.acid.txn.cleaner.interval", 10, TimeUnit.SECONDS,
"Time interval describing how often aborted and committed txns are cleaned."),
ADD_PARTITION_REUSE_EXISTING_COLUMN_DESCRIPTORS("metastore.add.partition.reuse.existing.column.descriptors",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is an optimization for large tables, for small tables, it shouldn't introduce too much overhead, should we set the default to true?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code got a lot of modifications since your last review. What do you think, should we still turn it true, by default?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still make it true by default

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few thoughts on simplifying the config name (it seems verbose) and the functionality, default value:

  • the word 'existing' is already implied by 'reuse' since one can only reuse existing descriptors, so you could remove 'existing'.
  • would this functionality only apply for explicit ADD PARTITION commands or would it also apply for MSCK REPAIR which also adds partitions under the covers that are missing in the metastore but are present in the filesystem. Once MSCK REPAIR adds partitions, then subsequent schema evolutions of those partitions should ideally also be reusing the column descriptors. But if this is not currently supported or tested, it is ok to file a separate ticket for followup.
  • from an end user perspective, ADD COLUMNS and CHANGE COLUMN are also actions that trigger this duplicate column descriptors, so it may not be accurate to say 'add.partition' . Considering all these, we could simplify it to metastore.partition.reuse.column.descriptors. WDYT ?
  • About the default value, since this is changing the generated SQL, it would be better to give it some more time for testing - not just functionally but also performance with order of 100K partitions. Metastore sql changes have significant impact and is in some sense a behavior change, so would be good to wait for further testing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metastore.partition.reuse.column.descriptors sounds fine. Thank you.

@dengzhhu653

Copy link
Copy Markdown
Member

I have moved convertToMPart to the TableStoreImpl, so we don't need create an extra interface or pass the RawStore


List<Long> latestColumnDescriptorIds = new ArrayList<>();
for (Object cdId : sqlResult) {
latestColumnDescriptorIds.add(extractLongValue(cdId));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dengzhhu653 : I introduced `extractLongValue as the type of CD_ID is number in Oracle and executeWithArray returns with BigDecimal instead of Long.

Do we have an existing reusable code to handle the situation?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use MetastoreDirectSqlUtils.extractSqlLong instead

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.


@Override
protected MColumnDescriptor getSqlResult() throws MetaException {
if (MetastoreConf.getBoolVar(conf, ADD_PARTITION_REUSE_EXISTING_COLUMN_DESCRIPTORS)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you check this boolean property in the else if branch with if (cols.equals(tableSchema)) {?
else if (MetastoreConf.getBoolVar(conf, ADD_PARTITION_REUSE_EXISTING_COLUMN_DESCRIPTORS) {

// that's the easy and relatively fast-check since does not require
// round-tripe to the database
List<FieldSchema> tableSchema = mt.getSd() != null && mt.getSd().getCD() != null && mt.getSd()
.getCD()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: code format, change the mt.getSd() .getCD() .getCols() != null to one line

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would violate the 120 long rule of the static code analyzer. However, I reformatted that statement to get better readability.


} finally {
if (query != null) {
query.closeAll();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't need to close the query any more, the TransactionHandler could take care of resource releasing

break;
}
}
return candidate;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could return a mistaken candidate even if !cols.get(i).equals(col)

// Please note! In case you modify any of those methods, run TestHMSColumnDescriptorReuse with all the supported databases
List<Long> cdCandidates = findTheLatestColumnDescriptors(tblId);

cdCandidates = filterCandidatesByColumnCount(cdCandidates, cols.size());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return early if cdCandidates is null

}

private List<Long> filterCandidatesByColumnCount(List<Long> cdCandidates, int size) throws MetaException {
Query<?> query = null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the cdCandidates might be null

import org.apache.hadoop.hive.metastore.utils.MetaStoreUtils;
import org.apache.hive.common.util.BloomFilter;
import org.datanucleus.store.rdbms.query.ForwardQueryResult;
import org.jetbrains.annotations.Nullable;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unnecessary import

JOIN "SDS" s ON s."SD_ID" = p."SD_ID"
WHERE p."TBL_ID" = ?
GROUP BY s."CD_ID"
ORDER BY MAX(p."PART_ID") DESC

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be we can add a limit here for saving time, e.g, limit 20, most recent schemas have the biggest match usually

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot add a limit clause that is compatible with all the supported databases. However, I can add a limit to the executeWithArray call so that it will only read the first 20 records.

return convertToMStorageDescriptor(sd, mcd);
}

@VisibleForTesting

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since we don't use this annotation, can we remove it?

@amansinha100 amansinha100 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working in this PR. I mainly had comments about the config and related functionality.

ACID_TXN_CLEANER_INTERVAL("metastore.acid.txn.cleaner.interval",
"hive.metastore.acid.txn.cleaner.interval", 10, TimeUnit.SECONDS,
"Time interval describing how often aborted and committed txns are cleaned."),
ADD_PARTITION_REUSE_EXISTING_COLUMN_DESCRIPTORS("metastore.add.partition.reuse.existing.column.descriptors",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few thoughts on simplifying the config name (it seems verbose) and the functionality, default value:

  • the word 'existing' is already implied by 'reuse' since one can only reuse existing descriptors, so you could remove 'existing'.
  • would this functionality only apply for explicit ADD PARTITION commands or would it also apply for MSCK REPAIR which also adds partitions under the covers that are missing in the metastore but are present in the filesystem. Once MSCK REPAIR adds partitions, then subsequent schema evolutions of those partitions should ideally also be reusing the column descriptors. But if this is not currently supported or tested, it is ok to file a separate ticket for followup.
  • from an end user perspective, ADD COLUMNS and CHANGE COLUMN are also actions that trigger this duplicate column descriptors, so it may not be accurate to say 'add.partition' . Considering all these, we could simplify it to metastore.partition.reuse.column.descriptors. WDYT ?
  • About the default value, since this is changing the generated SQL, it would be better to give it some more time for testing - not just functionally but also performance with order of 100K partitions. Metastore sql changes have significant impact and is in some sense a behavior change, so would be good to wait for further testing.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an opt-in mechanism to reuse existing HMS column descriptors when adding partitions (to reduce metadata bloat during partition-level schema evolution, especially in replication scenarios), plus targeted direct-SQL support and a new unit test to validate the behavior.

Changes:

  • Add a new metastore config (metastore.partition.reuse.column.descriptors) and wire partition conversion to reuse matching column descriptors when enabled.
  • Implement DirectSQL lookup logic to find and reuse a suitable existing MColumnDescriptor based on recent partitions.
  • Add TestHMSColumnDescriptorReuse to exercise reuse behavior across schema evolution cases.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestHMSColumnDescriptorReuse.java Adds unit tests validating descriptor reuse across schema evolution and replication-like partition adds.
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metastore/impl/TableStoreImpl.java Routes partition SD conversion through a reusable column-descriptor lookup (when enabled).
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetastoreDirectSqlUtils.java Adjusts SQL string extraction to handle Oracle/DataNucleus empty-string workaround values.
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetaStoreDirectSql.java Adds DirectSQL implementation for finding a reusable column descriptor from recent partitions.
standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java Introduces the new configuration flag for enabling descriptor reuse (and reorders one existing conf var).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

private List<Long> findTheLatestColumnDescriptors(long tblId) throws MetaException {
final int limit = 20;
Comment on lines +663 to +670
Object[] row = rows.get(i);
FieldSchema col = new FieldSchema(
extractSqlString(row[0]),
extractSqlClob(row[1]),
extractSqlString(row[2]));
if (!cols.get(i).equals(col)) {
break;
}
@dengzhhu653

Copy link
Copy Markdown
Member

Pushed a minor refactor to address the code-format from Copilot and Sonar and restart the CI test.

@sonarqubecloud

sonarqubecloud Bot commented Jul 9, 2026

Copy link
Copy Markdown

@DanielZhu58

Copy link
Copy Markdown
Contributor

I’m generally supportive of this PR.
The approach of fixing this on the add-partition/import path is better than only optimizing the read path later. By trying to reuse an existing column descriptor before creating a new CD_ID, this can prevent metadata bloat.
I also support that the change is guarded by a new config.

A couple of things I think we might need to pay attention later:
We only checks a limited set of recent CD_IDs, so we should document that clearly.
We should also test it across all supported metastore DBs, (does it work with oracle?) and consider a batch-level cache later to reduce repeated DirectSQL lookups during large imports.
We can do the a possible follow-up dedup tool later.

Overall, I think this is a good and working fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants