Optimize relational tablet batch inserts#18132
Conversation
|
I found a few issues that should be addressed before merging:
In InsertTablets.validateTableSchema, the call to validateTableSchema passes allowCreateTable=false. This makes ITableSession.insertTablets(List) fail when inserting into a new table, while the existing single-tablet insert path can auto-create the table. The current integration tests all create the tables first, so this behavior difference is not covered. Please either align this with the single-tablet path or clearly document and test that batch tablet insertion requires the table to exist.
Session.insertRelationalTablets always sends the request through getDefaultSessionConnection().insertTablets(request) and ignores RedirectException. The existing insertRelationalTablet path uses the table-model leader cache and groups rows/tablets by endpoint when redirection is enabled. In a cluster, this new path can keep sending requests to the default node and will not refresh tableModelDeviceIdToEndpoint, which may offset the intended performance gain. Please consider reusing or extending the existing relational tablet redirection logic for the batch path.
recordMergeTabletsCost is called from finally, so failed RPCs or execution failures still contribute to the auto-disable decision. This can permanently disable merge due to failure-path latency rather than actual merge overhead. It would be safer to record the cost only after a successful insert, or explicitly exclude failed attempts from the heuristic. Question: Is insertTablets intentionally not supposed to support auto table creation? If yes, the API documentation and tests should make that clear. |
|
Thanks for addressing the previous review comments. I re-checked the latest revision and there is still one blocking issue. High: the new table-model The current batch path catches This is also visible in the current CI failure, and it hits the new/related tests directly:
Could you please make the batch |
| final Tablet subTablet = | ||
| subTabletsByConnection | ||
| .computeIfAbsent(connection, ignored -> new LinkedHashMap<>()) | ||
| .computeIfAbsent(deviceID, ignored -> createEmptyRelationalTabletLike(tablet)); |
There was a problem hiding this comment.
High: This creates one sub-Tablet per distinct device, while createEmptyRelationalTabletLike allocates every sub-tablet with the original tablet full rowSize at line 2974. An R-row tablet with D devices therefore allocates O(D * R * columns) storage, reaching O(R^2) when each row belongs to a different device. This also happens with an empty leader cache: all devices map to the default connection, but the inner map still creates D full-capacity tablets. This can exhaust the client heap before any RPC. Could we group rows once per endpoint, or pre-count rows per device and allocate exact capacities? A many-device stress test would also help.
| final int lastIndex = mergedTablets.size() - 1; | ||
| final RelationalTabletWithColumnIndexMap candidate = mergedTablets.get(lastIndex); | ||
| if (canMergeRelationalTablets(candidate, relationalTablet)) { | ||
| mergedTablets.set(lastIndex, mergeRelationalTablets(candidate, relationalTablet.tablet)); |
There was a problem hiding this comment.
Medium: Every successful merge replaces the accumulated candidate with a newly allocated tablet and recopies all previously accumulated rows at lines 3213-3224; constructing the wrapper then rescans those timestamps. Merging N compatible tablets is therefore quadratic in copying and allocation, which affects the exact workload this optimization targets. The adaptive cost check runs only after the expensive call and after ten samples, so it cannot protect the first large batch. Could we collect a compatible run and allocate and copy once, or impose an explicit merge-work cap? Please add a scaling test with a large mergeable list.
| accessControl.checkCanInsertIntoTable( | ||
| context.getSession().getUserName(), | ||
| new QualifiedObjectName( | ||
| unQualifyDatabaseName(insertStatement.getDatabase()), |
There was a problem hiding this comment.
Medium: Although insertStatement is the InsertTablets AST wrapper, its inherited getDatabase method dynamically dispatches getInnerTreeStatement to the wrapped InsertMultiTabletsStatement. That statement getDatabaseName method scans every child, and AnalyzeUtils.getDatabaseName may invoke it twice. Calling it inside this N-tablet permission loop makes validation O(N^2) before schema work begins. Please resolve and unqualify the database once before the loop; repeated checks for the same table can also be deduplicated.
|
|
||
| private void handleRelationalTabletsRedirection( | ||
| final RedirectException redirectException, final List<IDeviceID> deviceIDs) { | ||
| final List<TEndPoint> endPointList = redirectException.getEndPointList(); |
There was a problem hiding this comment.
Medium: The batch redirection path cannot update the table leader cache as currently wired. TableModelPlanner.setRedirectInfo emits redirect metadata only for InsertTabletStatement, not the new InsertMultiTabletsStatement. In addition, SessionConnection.insertTablets uses verifySuccessWithRedirectionForMultiDevices, which creates a RedirectException with deviceEndPointMap keyed from request prefix paths (table names), while this handler reads only endPointList. Batch-only workloads therefore never learn or repair per-device leader routes and keep going through the default or stale DataNode. Please define redirect metadata aligned with the batch devices and add a test that verifies cache updates; successful REDIRECTION_RECOMMEND writes should not be retried.
Description
This PR optimizes relational insertTablets handling in the Java Session client.
Tests