Repository navigation
Support database-scoped tablet insertion in table sessions #19237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e5a2715
7ab0db7
e8a15c6
2f49f14
b092a12
f53ab9a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,7 +70,15 @@ | |
| boolean enableRPCCompression, | ||
| int connectionTimeoutInMs, | ||
| Map<String, TEndPoint> deviceIdToEndpoint, | ||
| Map<IDeviceID, TEndPoint> tabletModelDeviceIdToEndpoint, | ||
| Map<IDeviceID, TEndPoint> tableModelDeviceIdToEndpoint, | ||
| INodeSupplier nodeSupplier) | ||
| throws IoTDBConnectionException; | ||
|
|
||
| void openWithTableModelDatabaseCache( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This adds an internal cache-sharing hook to Session newSession = constructNewSession();
newSession.openWithTableModelDatabaseCache(...);
session = newSession;
Separately, the new abstract methods here and the new |
||
| boolean enableRPCCompression, | ||
|
Check warning on line 78 in iotdb-client/isession/src/main/java/org/apache/iotdb/isession/ISession.java
|
||
| int connectionTimeoutInMs, | ||
| Map<String, TEndPoint> deviceIdToEndpoint, | ||
| Map<String, Map<IDeviceID, TEndPoint>> tableModelDeviceIdToEndpoint, | ||
| INodeSupplier nodeSupplier) | ||
| throws IoTDBConnectionException; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -173,12 +173,13 @@ | |
| private static final double CONVERT_THRESHOLD = 0.5; | ||
| private static final double SAMPLE_PROPORTION = 0.05; | ||
| private static final int MIN_RECORDS_SIZE = 40; | ||
| private static final String UNSPECIFIED_DATABASE_KEY = ""; | ||
|
|
||
| @SuppressWarnings("squid:S3077") // Non-primitive fields should not be "volatile" | ||
| protected volatile Map<String, TEndPoint> deviceIdToEndpoint; | ||
|
|
||
| @SuppressWarnings("squid:S3077") // Non-primitive fields should not be "volatile" | ||
| protected volatile Map<IDeviceID, TEndPoint> tableModelDeviceIdToEndpoint; | ||
| protected volatile Map<String, Map<IDeviceID, TEndPoint>> tableModelDeviceIdToEndpoint; | ||
|
|
||
| @SuppressWarnings("squid:S3077") // Non-primitive fields should not be "volatile" | ||
| protected volatile Map<TEndPoint, SessionConnection> endPointToSessionConnection; | ||
|
|
@@ -666,6 +667,28 @@ | |
| Map<IDeviceID, TEndPoint> tableModelDeviceIdToEndpoint, | ||
| INodeSupplier nodesSupplier) | ||
| throws IoTDBConnectionException { | ||
| Map<String, Map<IDeviceID, TEndPoint>> databaseToDeviceIdToEndpoint = new ConcurrentHashMap<>(); | ||
| if (tableModelDeviceIdToEndpoint != null) { | ||
| databaseToDeviceIdToEndpoint.put( | ||
| resolveTableModelRouteDatabase(null), tableModelDeviceIdToEndpoint); | ||
| } | ||
| openWithTableModelDatabaseCache( | ||
| enableThriftRpcCompaction, | ||
| connectionTimeoutInMs, | ||
| deviceIdToEndpoint, | ||
| databaseToDeviceIdToEndpoint, | ||
| nodesSupplier); | ||
| } | ||
|
|
||
| /** Opens this Session with a database-aware table-model route cache for internal callers. */ | ||
| @Override | ||
| public synchronized void openWithTableModelDatabaseCache( | ||
| boolean enableThriftRpcCompaction, | ||
| int connectionTimeoutInMs, | ||
| Map<String, TEndPoint> deviceIdToEndpoint, | ||
| Map<String, Map<IDeviceID, TEndPoint>> tableModelDeviceIdToEndpoint, | ||
| INodeSupplier nodesSupplier) | ||
| throws IoTDBConnectionException { | ||
| if (!isClosed) { | ||
| return; | ||
| } | ||
|
|
@@ -1420,12 +1443,20 @@ | |
| } | ||
| } | ||
|
|
||
| private SessionConnection getSessionConnection(IDeviceID deviceId) | ||
| private Map<IDeviceID, TEndPoint> getTableModelDatabaseRoutes(String routeDatabase) { | ||
| Map<IDeviceID, TEndPoint> databaseToEndpoint = tableModelDeviceIdToEndpoint.get(routeDatabase); | ||
| return databaseToEndpoint == null | ||
| ? tableModelDeviceIdToEndpoint.computeIfAbsent( | ||
| routeDatabase, ignored -> new ConcurrentHashMap<>()) | ||
| : databaseToEndpoint; | ||
| } | ||
|
|
||
| private SessionConnection getSessionConnection( | ||
| Map<IDeviceID, TEndPoint> databaseToEndpoint, IDeviceID deviceId) | ||
| throws IoTDBConnectionException { | ||
| TEndPoint endPoint; | ||
| if (enableRedirection | ||
| && tableModelDeviceIdToEndpoint != null | ||
| && (endPoint = tableModelDeviceIdToEndpoint.get(deviceId)) != null | ||
| && (endPoint = databaseToEndpoint.get(deviceId)) != null | ||
| && endPointToSessionConnection.containsKey(endPoint)) { | ||
| return endPointToSessionConnection.get(endPoint); | ||
| } else { | ||
|
|
@@ -1465,12 +1496,13 @@ | |
| } | ||
| } | ||
| if (tableModelDeviceIdToEndpoint != null && !tableModelDeviceIdToEndpoint.isEmpty()) { | ||
| for (Iterator<Entry<IDeviceID, TEndPoint>> it = | ||
| tableModelDeviceIdToEndpoint.entrySet().iterator(); | ||
| it.hasNext(); ) { | ||
| Entry<IDeviceID, TEndPoint> entry = it.next(); | ||
| if (entry.getValue().equals(endPoint)) { | ||
| it.remove(); | ||
| for (Map<IDeviceID, TEndPoint> deviceIdToEndpoint : tableModelDeviceIdToEndpoint.values()) { | ||
|
Check warning on line 1499 in iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
|
||
| for (Iterator<Entry<IDeviceID, TEndPoint>> it = deviceIdToEndpoint.entrySet().iterator(); | ||
| it.hasNext(); ) { | ||
| Entry<IDeviceID, TEndPoint> entry = it.next(); | ||
| if (Objects.equals(entry.getValue(), endPoint)) { | ||
| it.remove(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -1503,15 +1535,15 @@ | |
| } | ||
| } | ||
|
|
||
| private void handleRedirection(IDeviceID deviceId, TEndPoint endpoint) { | ||
| private void handleRedirection( | ||
| Map<IDeviceID, TEndPoint> databaseToEndpoint, IDeviceID deviceId, TEndPoint endpoint) { | ||
| if (enableRedirection) { | ||
| // no need to redirection | ||
| if (UrlUtils.isWildcardAddress(endpoint.ip)) { | ||
| return; | ||
| } | ||
| if (!tableModelDeviceIdToEndpoint.containsKey(deviceId) | ||
| || !tableModelDeviceIdToEndpoint.get(deviceId).equals(endpoint)) { | ||
| tableModelDeviceIdToEndpoint.put(deviceId, endpoint); | ||
| if (!endpoint.equals(databaseToEndpoint.get(deviceId))) { | ||
| databaseToEndpoint.put(deviceId, endpoint); | ||
| } | ||
| SessionConnection connection = | ||
| endPointToSessionConnection.computeIfAbsent( | ||
|
|
@@ -1524,11 +1556,16 @@ | |
| } | ||
| }); | ||
| if (connection == null) { | ||
| tableModelDeviceIdToEndpoint.remove(deviceId); | ||
| databaseToEndpoint.remove(deviceId, endpoint); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private String resolveTableModelRouteDatabase(String database) { | ||
| String routeDatabase = database == null ? this.database : database; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: the route-cache key is the raw string, while the server lower-cases it, so |
||
| return routeDatabase == null ? UNSPECIFIED_DATABASE_KEY : routeDatabase; | ||
| } | ||
|
|
||
| private void handleQueryRedirection(TEndPoint endPoint) throws IoTDBConnectionException { | ||
| if (enableQueryRedirection) { | ||
| AtomicReference<IoTDBConnectionException> exceptionReference = new AtomicReference<>(); | ||
|
|
@@ -2907,11 +2944,24 @@ | |
| */ | ||
| public void insertRelationalTablet(Tablet tablet) | ||
| throws IoTDBConnectionException, StatementExecutionException { | ||
| insertRelationalTablet(tablet, null); | ||
| } | ||
|
|
||
| /** | ||
| * Inserts a relational {@link Tablet} into the specified database for this invocation only. | ||
| * | ||
| * @param tablet data batch | ||
| * @param database target database; if null, use the current database of this session | ||
| */ | ||
| public void insertRelationalTablet(Tablet tablet, String database) | ||
| throws IoTDBConnectionException, StatementExecutionException { | ||
|
Check warning on line 2957 in iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
|
||
| if (tablet.getRowSize() == 0) { | ||
| return; | ||
| } | ||
| // Capture the effective database before grouping so a later USE cannot change cached routes. | ||
| String routeDatabase = resolveTableModelRouteDatabase(database); | ||
| if (enableRedirection) { | ||
| insertRelationalTabletWithLeaderCache(tablet); | ||
| insertRelationalTabletWithLeaderCache(tablet, database, routeDatabase); | ||
| } else { | ||
| TSInsertTabletReq request = genTSInsertTabletReq(tablet, false, false); | ||
| if (request == null) { | ||
|
|
@@ -2923,24 +2973,28 @@ | |
| return; | ||
| } | ||
| request.setWriteToTable(true); | ||
| setRelationalRequestDatabase(request, database); | ||
| try { | ||
| getDefaultSessionConnection().insertTablet(request); | ||
| } catch (RedirectException ignored) { | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void insertRelationalTabletWithLeaderCache(Tablet tablet) | ||
| private void insertRelationalTabletWithLeaderCache( | ||
| Tablet tablet, String database, String routeDatabase) | ||
| throws IoTDBConnectionException, StatementExecutionException { | ||
| Map<SessionConnection, Tablet> relationalTabletGroup = new HashMap<>(); | ||
| if (tableModelDeviceIdToEndpoint.isEmpty()) { | ||
| Map<IDeviceID, TEndPoint> databaseToEndpoint = getTableModelDatabaseRoutes(routeDatabase); | ||
| if (databaseToEndpoint.isEmpty()) { | ||
| relationalTabletGroup.put(getDefaultSessionConnection(), tablet); | ||
| } else if (SessionUtils.isTabletContainsSingleDevice(tablet)) { | ||
| relationalTabletGroup.put(getSessionConnection(tablet.getDeviceID(0)), tablet); | ||
| relationalTabletGroup.put( | ||
| getSessionConnection(databaseToEndpoint, tablet.getDeviceID(0)), tablet); | ||
| } else { | ||
| for (int i = 0; i < tablet.getRowSize(); i++) { | ||
| IDeviceID iDeviceID = tablet.getDeviceID(i); | ||
| final SessionConnection connection = getSessionConnection(iDeviceID); | ||
| final SessionConnection connection = getSessionConnection(databaseToEndpoint, iDeviceID); | ||
| int finalI = i; | ||
| relationalTabletGroup.compute( | ||
| connection, | ||
|
|
@@ -2975,13 +3029,16 @@ | |
| } | ||
| } | ||
| if (relationalTabletGroup.size() == 1) { | ||
| insertRelationalTabletOnce(relationalTabletGroup); | ||
| insertRelationalTabletOnce(relationalTabletGroup, database, databaseToEndpoint); | ||
| } else { | ||
| insertRelationalTabletByGroup(relationalTabletGroup); | ||
| insertRelationalTabletByGroup(relationalTabletGroup, database, databaseToEndpoint); | ||
| } | ||
| } | ||
|
|
||
| private void insertRelationalTabletOnce(Map<SessionConnection, Tablet> relationalTabletGroup) | ||
| private void insertRelationalTabletOnce( | ||
|
Check failure on line 3038 in iotdb-client/session/src/main/java/org/apache/iotdb/session/Session.java
|
||
| Map<SessionConnection, Tablet> relationalTabletGroup, | ||
| String database, | ||
| Map<IDeviceID, TEndPoint> databaseToEndpoint) | ||
| throws IoTDBConnectionException, StatementExecutionException { | ||
| Map.Entry<SessionConnection, Tablet> entry = relationalTabletGroup.entrySet().iterator().next(); | ||
| SessionConnection connection = entry.getKey(); | ||
|
|
@@ -2996,6 +3053,7 @@ | |
| return; | ||
| } | ||
| request.setWriteToTable(true); | ||
| setRelationalRequestDatabase(request, database); | ||
| try { | ||
| connection.insertTablet(request); | ||
| } catch (RedirectException e) { | ||
|
|
@@ -3006,7 +3064,8 @@ | |
| endPointMap.put(tablet.getDeviceID(i), endPointList.get(i)); | ||
| } | ||
| } | ||
| endPointMap.forEach(this::handleRedirection); | ||
| endPointMap.forEach( | ||
| (deviceId, endpoint) -> handleRedirection(databaseToEndpoint, deviceId, endpoint)); | ||
| } catch (IoTDBConnectionException e) { | ||
| if (endPointToSessionConnection != null && endPointToSessionConnection.size() > 1) { | ||
| // remove the broken session | ||
|
|
@@ -3024,7 +3083,10 @@ | |
| @SuppressWarnings({ | ||
| "squid:S3776" | ||
| }) // ignore Cognitive Complexity of methods should not be too high | ||
| private void insertRelationalTabletByGroup(Map<SessionConnection, Tablet> relationalTabletGroup) | ||
| private void insertRelationalTabletByGroup( | ||
| Map<SessionConnection, Tablet> relationalTabletGroup, | ||
| String database, | ||
| Map<IDeviceID, TEndPoint> databaseToEndpoint) | ||
| throws IoTDBConnectionException, StatementExecutionException { | ||
| List<CompletableFuture<Void>> completableFutures = | ||
| relationalTabletGroup.entrySet().stream() | ||
|
|
@@ -3044,6 +3106,7 @@ | |
| return; | ||
| } | ||
| request.setWriteToTable(true); | ||
| setRelationalRequestDatabase(request, database); | ||
| InsertConsumer<TSInsertTabletReq> insertConsumer = | ||
| SessionConnection::insertTablet; | ||
| try { | ||
|
|
@@ -3056,7 +3119,9 @@ | |
| endPointMap.put(subTablet.getDeviceID(i), endPointList.get(i)); | ||
| } | ||
| } | ||
| endPointMap.forEach(this::handleRedirection); | ||
| endPointMap.forEach( | ||
| (deviceId, endpoint) -> | ||
| handleRedirection(databaseToEndpoint, deviceId, endpoint)); | ||
| } catch (StatementExecutionException e) { | ||
| throw new CompletionException(e); | ||
| } catch (IoTDBConnectionException e) { | ||
|
|
@@ -3101,6 +3166,12 @@ | |
| } | ||
| } | ||
|
|
||
| private static void setRelationalRequestDatabase(TSInsertTabletReq request, String database) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: the null check is redundant. The generated |
||
| if (database != null) { | ||
| request.setDatabase(database); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * insert the aligned timeseries data of a device. For each timestamp, the number of measurements | ||
| * is the same. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please assert the status code here, not just the exception type.
""currently returns 301, the generic failure code, so this assertion would still pass if the call failed for an unrelated reason.Missing cases:
"root.xxx","ROOT.xxx"and"Root.xxx"while both tree databaseroot.xxxand table databasexxxexist. Assert the error code, and assert that no device appears underroot.xxx.**(see the comment onStatementGenerator).insertRelationalTabletByGroupwith a database set. The new unit test uses a single device. In the IT, each explicit-database insert starts with an empty route map for that database, so it always goes to the default connection. ExtendingSessionCacheLeaderTestwith a multi-device tablet whose devices have different cached leaders, written to an explicit database that differs from the session's, would cover the grouping, the redirect bookkeeping and the per-group requests carrying the database.