Skip to content

HIVE-30056: Iceberg: Add client side support for REST catalog server-side scan planning - #6789

Open
difin wants to merge 2 commits into
apache:masterfrom
difin:file_planning
Open

difin wants to merge 2 commits into
apache:masterfrom
difin:file_planning

Conversation

@difin

@difin difin commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Hive wires up Iceberg REST server-side scan planning: when scan-planning-mode=server, split planning can go through the REST catalog (POST /plan) instead of only the serialized table snapshot on Tez/MR workers. Adds a small RestCatalogScanPlanning helper for config and copying catalog settings into job conf, and updates the Iceberg storage handler / input path to reload a live REST table on executors where appropriate. Tests cover configuration, job propagation, and embedded REST planning behavior.

Why are the changes needed?

Iceberg REST catalogs can plan scans on the catalog service instead of on every client. That keeps planning close to authoritative metadata and catalog credentials, can reduce work on HiveServer2 and task containers, and lets the REST service apply catalog-side optimizations as Iceberg evolves.

Hive today mostly plans reads from a serialized table snapshot shipped in the job configuration, which fits local/Hadoop catalogs but does not use the REST server’s planning API. For deployments that already run a REST catalog with scan planning, operators expect scan-planning-mode=server to work for Hive queries on Tez/MR - not only for single-node Iceberg clients. This change closes that gap so Hive participates in the same server-side planning model as the rest of the Iceberg REST ecosystem.

Does this PR introduce any user-facing change?

Yes. For REST Iceberg catalogs, operators can set iceberg.catalog.<catalog>.scan-planning-mode=server (via hive-site.xml or session SET) alongside existing REST catalog properties. When enabled, read jobs that use the Iceberg input format can use server-side scan planning instead of local planning from the serialized snapshot. Default behavior is unchanged when the property is unset or not server. Hive and non-REST catalogs are unaffected.

How was this patch tested?

  • TestRestCatalogScanPlanning — Tests config keys, server-mode detection, job propagation gating and copying.
  • TestRestCatalogScanPlanningServerIT — Uses embedded REST catalog; Verifies that RESTTable / RESTTableScan and PlanTableScanRequest on planTasks() are used when file planning is on.
  • TestHiveIcebergServerSideScanPlanning — Checks that Hive only reloads a table from the REST catalog for split planning when server mode is on and it is safe to do so - not when planning stays local or when the job must use uncommitted metadata from the same transaction.
  • TestHiveIcebergServerSideScanPlanningServerIT — Tests that executor job conf with/without propagated catalog props and table reload over REST.

@difin difin changed the title File planning HIVE-30056: Iceberg: Add REST catalog server-side scan planning support Sep 15, 2026
@Aggarwal-Raghav

Copy link
Copy Markdown
Contributor

There were already discussions happening under HIVE-29599 for server side planning?

@difin

difin commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

There were already discussions happening under HIVE-29599 for server side planning?

This is different - this PR is about adding client-side support.

@difin difin changed the title HIVE-30056: Iceberg: Add REST catalog server-side scan planning support HIVE-30056: Iceberg: Add client side support for REST catalog server-side scan planning Sep 16, 2026

@tanishq-chugh tanishq-chugh 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.

Hi @difin
Checked out the PR, great improvement 🚀

*/
public static Table resolveTableForScanPlanning(Configuration conf, String tableIdentifier) {
if (shouldReloadForServerSideScanPlanning(conf)) {
Table table = Catalogs.loadTable(conf);

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.

I believe this will create a new RestCatalog Object/HttpClient on every call, curious if this can cause any connection leak?

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.

Yes - Catalogs.loadTable creates a new REST catalog (and HTTP client) on each call, and we don’t close it on this path today. That’s the same pattern as other Catalogs.loadTable usages in the handler. Here it runs once per getSplits during split planning, not per input split. A reload only happens when shouldReloadForServerSideScanPlanning is true.

"If this is set to true the URI for auth will have the default location masked with DEFAULT_TABLE_LOCATION"),
HIVE_ICEBERG_ALLOW_DATAFILES_IN_TABLE_LOCATION_ONLY("hive.iceberg.allow.datafiles.in.table.location.only", false,
"If this is set to true, then all the data files being read should be withing the table location"),
HIVE_ICEBERG_REST_SERVER_SIDE_SCAN_PLANNING_ENABLED(

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.

hive.iceberg.rest.scan-planning-mode {client, server} ?

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.

Renamed to hive.iceberg.rest.scan-planning-mode with client/server to mirror Iceberg REST catalog property naming; default client preserves prior opt-in behavior.

setCommonJobConf(jobConf);
configureOutputTableJobConf(tableDesc, jobConf);
if (tableDesc != null && tableDesc.getProperties() != null) {
String catalogName = tableDesc.getProperties().getProperty(InputFormatConfig.CATALOG_NAME);

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.

why not move helper into tableDesc? i.e. tableDesc.getCatalogName()?

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.

If we move this to If we move this ti tableDesc we will add Iceberg-specific code to hive-exec module.
How about tableDesc.getProperty(InputFormatConfig.CATALOG_NAME) instead?

Comment thread pom.xml Outdated
<!-- Upgrade roaringbit version whenever upgrading iceberg version -->
<iceberg.version>1.11.0</iceberg.version>
<!-- Jetty 12 for Iceberg REST catalog embedded-server tests (Hive uses jetty.version 9.x elsewhere). -->
<iceberg.rest.test.jetty.version>12.1.8</iceberg.rest.test.jetty.version>

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.

is it misalligned?

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.

Two Jetty stacks are required (9 for Hive, 12 for Iceberg REST embedded-server tests). Putting it in the root pom.xml was wrong since it's used only in one test module. Fixed.

import static org.mockito.Mockito.verify;

/** Embedded REST server tests for the Hive executor reload path for server-side scan planning. */
class TestHiveIcebergServerSideScanPlanningServerIT extends TestBaseWithRESTServer {

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.

don't we use HiveRESTCatalogServerExtension restCatalogExtension = getHiveRESTCatalogServerExtension() do decorate with additional fixtures?

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.

We use HiveRESTCatalogServerExtension for tests that hit the HMS-backed REST catalog servlet. These scan-planning Server ITs intentionally use Iceberg’s TestBaseWithRESTServer, an in-process Iceberg REST harness, not the HMS servlet, so we can spy RESTCatalogAdapter, drive HiveTableUtil.resolveTableForScanPlanning / job-conf propagation, and keep the unshaded Iceberg + Jetty 12 classpath this module needs. Different server, different layer under test.

import static org.mockito.Mockito.verify;

/** Embedded REST server tests for the Hive executor reload path for server-side scan planning. */
class TestHiveIcebergServerSideScanPlanningServerIT extends TestBaseWithRESTServer {

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.

is this a test framefork class or a test class?

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.

Test class. It’s a concrete JUnit tests. It uses the framework base TestBaseWithRESTServer (from iceberg-core tests); it isn’t a reusable fixture or extension itself.

*
* @see <a href="https://iceberg.apache.org/docs/latest/catalog-properties/">REST catalog properties</a>
*/
public final class RestCatalogScanPlanning {

@deniskuzZ deniskuzZ Sep 29, 2026 •

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.

should it be in util package or have util in the name?

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.

Moved this class to iceberg-handler (because it's not rest client specific) and added Util to the class name.

}

public static String catalogPropertyKey(String catalogName) {
return IcebergCatalogProperties.catalogPropertyConfigKey(

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 see you use mix of static imports, catalogPropertyKey in setScanPlanningMode

setScanPlanningMode(conf, catalogName, RESTCatalogProperties.ScanPlanningMode.fromString(mode));
}

public static RESTCatalogProperties.ScanPlanningMode getScanPlanningMode(

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.

naming seems to repeat class name

/**
* Resolves the catalog name from per-table {@code iceberg.catalog} or the session default catalog.
*/
public static String resolveCatalogName(Configuration conf, String catalogNameFromTable) {

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 seem to be a repeated functionality

* Called from {@code configureJobConf} so split generation sees catalog URI/type/scan-planning-mode
* even when job properties were not copied yet.
*/
public static void propagateCatalogPropertiesToJob(

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.

please check visibility modifiers in public methods if the actually need thos

* {@link HiveTableUtil#resolveTableForScanPlanning}. Embedded REST server coverage is in
* {@code TestHiveIcebergServerSideScanPlanningServerIT} in {@code itests/hive-iceberg-rest-server}.
*/
class TestHiveIcebergServerSideScanPlanning {

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.

does it test ServerSideScanPlanning? usesDeserializedTable

String tableIdentifier = conf.get(InputFormatConfig.TABLE_IDENTIFIER);
Table table = HiveTableUtil.resolveTableForScanPlanning(conf, tableIdentifier);
String executorTableId = tableIdentifier != null ? tableIdentifier : table.name();
if (tableIdentifier == null) {

@deniskuzZ deniskuzZ Sep 29, 2026 •

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 check tableIdentifier and set executorTableId, that is inconsistent.

});
String tableIdentifier = conf.get(InputFormatConfig.TABLE_IDENTIFIER);
Table table = HiveTableUtil.resolveTableForScanPlanning(conf, tableIdentifier);
String executorTableId = tableIdentifier != null ? tableIdentifier : table.name();

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.

maybe simply tableId ?

* uses the deserialized snapshot so uncommitted metadata is visible.
*/
public static Table resolveTableForScanPlanning(Configuration conf, String tableIdentifier) {
if (shouldReloadForServerSideScanPlanning(conf)) {

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.

what is this reload, isn't it expensive? what if we have multiple requests in same query/session?

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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.

5 participants