Skip to content
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

Support read and write lock in table level to reduce lock competition #3775

Merged
merged 50 commits into from
Jan 13, 2021

Conversation

caiconghui
Copy link
Contributor

This PR is to reduce lock competition by supporting read and write lock in table level. When we modify or read table's meta, we don't need to get db lock, just get table write or read lock. And when we get db lock, that means meta directly in db cannot be modified by other thread. Db lock only protect meta in Database class, while table lock protect meta in Table class.

@caiconghui
Copy link
Contributor Author

caiconghui commented Jun 4, 2020

ref #3743

Copy link
Contributor

@morningman morningman left a comment

Choose a reason for hiding this comment

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

Hi @caiconghui , first of all, this change is worth it.

But I don't think this is a good way to achieve it. In the previous implementation, it was not a good design to expose the database lock outside, and there have been multiple deadlocks caused by lock nesting problems.

When the lock granularity is refined to the table level, this way of exposing locks will bring more error-proneness. And so many changes to locks in one PR are difficult to review.

I suggest to refer to the implementation of kudu MetadataGroupLock to abstract the operation of the lock and then gradually modify the relevant code.

@morningman morningman added the api-review Categorizes an issue or PR as actively needing an API review. label Jun 5, 2020
@morningman morningman self-assigned this Jun 5, 2020
@morningman morningman added the area/catalog Issues or PRs related to catalog management label Jun 5, 2020
@kangkaisen
Copy link
Contributor

I just reviewed a little, and submitted review accidentally.

@kangkaisen
Copy link
Contributor

Hi @caiconghui , first of all, this change is worth it.

But I don't think this is a good way to achieve it. In the previous implementation, it was not a good design to expose the database lock outside, and there have been multiple deadlocks caused by lock nesting problems.

When the lock granularity is refined to the table level, this way of exposing locks will bring more error-proneness. And so many changes to locks in one PR are difficult to review.

I suggest to refer to the implementation of kudu MetadataGroupLock to abstract the operation of the lock and then gradually modify the relevant code.

+1.

@caiconghui
Copy link
Contributor Author

caiconghui commented Jun 16, 2020

It is hard to review the changed files at one time, so I classify the modified files into different groups. Please Pay more attention to Alter, Catalog, Load theme, which could be quite error prone. And now I don't change httpv2 code, because it is not used now, so we can modify it later.
Alter
1 Alter.java
2 AlterHandler.java
3 AlterJobV2.java
4 MaterializedViewHandler.java
5 RollupJob.java
6 RollupJobV2.java
7 SchemaChangeHandler.java
8 SchemaChangeJob.java
9 SchemaChangeJobV2.java

Stmt

10 DescribeStmt.java
11 ExportStmt.java
12 ShowDataStmt.java
13 ShowPartitionsStmt.java
14 InsertStmt.java
15 QueryStmt.java
16 SelectStmt.java
17 SetOperationStmt.java
18 WithClause.java
19 StmtExecutor.java
20 ShowExecutor.java

Backup

21 BackupHandler.java
22 BackupJob.java
23 RestoreJob.java

Catalog

24 Catalog.java
25 CatalogRecycleBin.java

Colocate

26 ColocateTableIndex.java
27 ColocateTableBalancer.java

Meta

28 Database.java
29 MetadataViewer.java
30 OlapTable.java
31 Table.java
32 InfoSchemaDb.java
33 MetaLockUtils.java

MetaManager

34 TabletStatMgr.java
35 DynamicPartitionScheduler.java
36 TabletChecker.java
37 TabletSchedCtx.java
38 TabletScheduler.java

Proc

39 EsPartitionsProcDir.java
40 EsShardProcDir.java
41 IndexInfoProcDir.java
42 IndicesProcDir.java
43 PartitionsProcDir.java
44 StatisticProcDir.java
45 TablesProcDir.java
46 TabletsProcDir.java

Check

47 CheckConsistencyJob.java
48 ConsistencyChecker.java

Rest

49 GetDdlStmtAction.java
50 MigrationAction.java
51 RowCountAction.java
52 StorageTypeCheckAction.java
53 TableQueryPlanAction.java
54 TableRowCountAction.java
55 TableSchemaAction.java
56 ShowDataAction.java
57 MetaService.java

Load
58 BrokerFileGroup.java
59 DeleteHandler.java
60 DeleteJob.java
61 ExportJob.java
62 Load.java
63 LoadChecker.java
64 BrokerLoadJob.java
65 LoadManager.java
66 KafkaRoutineLoadJob.java
67 RoutineLoadJob.java
68 BulkLoadJob.java
69 LoadJob.java
70 SparkLoadJob.java

System

71 ReportHandler.java
72 SystemInfoService.java
73 ConnectProcessor.java

Task

74 HadoopLoadPendingTask.java
75 LoadEtlTask.java
76 MiniLoadPendingTask.java
77 StreamLoadTask.java
78 SparkLoadPendingTask.java

TransactionMgr

79 DatabaseTransactionMgr.java
80 GlobalTransactionMgr.java
81 PublishVersionDaemon.java

Rpc

82 FrontendServiceImpl.java
83 MasterImpl.java

Test
84 SchemaChangeJobV2Test.java
85 RollupJobV2Test.java
86 DatabaseTest.java
87 AlterTest.java
88 DemoTest.java
89 AnotherDemoTest.java
90 MetaLockUtilsTets.java
91 InfoSchemaDbTest.java
92 TableTest.java
93 DeleteHandlerTest.java
94 StreamLoadPlannerTest.java
95 StreamLoadScanNodeTest.java
96 DatabaseTransactionMgrTest.java
97 GlobalTransactionMgrTest.java
98 StmtExecutorTest.java
99 SparkLoadJobTest.java

Looking forwards to lively discussions.

@kangkaisen @morningman @wangbo

@caiconghui caiconghui force-pushed the table_lock branch 2 times, most recently from 2c247dc to 5c76212 Compare September 24, 2020 14:58
@caiconghui caiconghui force-pushed the table_lock branch 2 times, most recently from f6a0178 to 2300dce Compare October 22, 2020 13:21
@caiconghui caiconghui force-pushed the table_lock branch 3 times, most recently from 0aed452 to d8f4bcd Compare November 10, 2020 03:34
@caiconghui
Copy link
Contributor Author

caiconghui commented Nov 10, 2020

Main Modification and Some Lock rule to discuss.

  1. For meta data viewing, just simple use table read lock to replace database read lock.

  2. For meta reading, we don't pursue strong consistency, our goal is to ensure not dead lock and thread safe, such as db.getTable do not need db read lock.

  3. In order to escape dead lock, we keep db lock -> table lock -> other lock, for same meta level, we must sort meta by unique id before lock meta list.

  4. Although db lock to table lock modification is large and difficult to review, but I think the heavy work is Meta modification, which in Alter, Load, Catalog theme, and logic in meta viewing is clear and less error prone.

  5. When change property in one meta, we need to change meta lock, such as load, we only need lock table, but when we change property both in meta and its parent meta, such rename operation, we must lock both db and table

  6. Finally. reduce unneeded lock, such as lock the same meta twice in one operation, reduce the lock scope to reduce the lock competition.

@caiconghui caiconghui force-pushed the table_lock branch 2 times, most recently from 304942c to 45ef112 Compare November 19, 2020 09:48
db.writeUnlock();
}
String tableName = stmt.getTableName().getTbl();
OlapTable olapTable = (OlapTable) db.getTableOrThrowException(tableName, TableType.OLAP);
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing if (olapTable.getState() != OlapTableState.NORMAL) { check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

processDropMaterializedView has check table state operation

}
} finally {
db.writeUnlock();
switch (table.getType()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can just lock the table outside the switch, avoid call lock/unlock everywhere inside the processAlterOlapTable and processAlterExternalTable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

some function need db lock and table lock but some function only need table lock, what't more, If the nesting function is too deep, it is easy to repeat locking, if we lock small scope for modificaion, which will be more clear although we may write many lock code

break;
} else if (alterClause instanceof ColumnRenameClause) {
Catalog.getCurrentCatalog().renameColumn(db, table, (ColumnRenameClause) alterClause);
db.writeLock();
Copy link
Contributor

Choose a reason for hiding this comment

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

Better put these locks inside the renameTable method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

break;
} else {
Preconditions.checkState(false);
table.writeLock();
Copy link
Contributor

Choose a reason for hiding this comment

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

Put lock inside the method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

}
}
}

private void processRename(Database db, Table table, List<AlterClause> alterClauses) throws DdlException {
for (AlterClause alterClause : alterClauses) {
if (alterClause instanceof TableRenameClause) {
Catalog.getCurrentCatalog().renameTable(db, table, (TableRenameClause) alterClause);
db.writeLock();
Copy link
Contributor

Choose a reason for hiding this comment

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

Put lock inside the method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -14,6 +14,22 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// Licensed to the Apache Software Foundation (ASF) under one
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this license is different?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is an incorrect modification.

}

lock(dbs);
List<Table> tables = Lists.newArrayList(tableMap.values());
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you sure this can get table list in order?
I think it is more safe and clear to change tableMap to tableList, and sort the tableList explicitly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, tableMap is a treeMap which is used to for previous analysis, the treeMap will keep table id in In ascending order,

} else {
LOG.warn("failed to update backend report version, db {} does not exist", dbId);
}
atomicLong.set(newReportVersion);
Copy link
Contributor

Choose a reason for hiding this comment

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

In original implementation, we use db locks to ensure mutual exclusion and order.
So here we may still need to add table locks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

atomicLong is AtomicLong,so I think it is meanless for add table lock or db lock?

Map<String, EtlPartitionConf> etlPartitions = createEtlPartitions();
Preconditions.checkNotNull(etlPartitions);
taskConf.setEtlPartitions(etlPartitions);
EtlTaskConf taskConf = new EtlTaskConf();
Copy link
Contributor

Choose a reason for hiding this comment

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

Table's lock should be held to call method like createEtlPartitions()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

add table lock in createEtlPartitions

continue;
}
OlapTable olapTable = (OlapTable) table;
olapTable.readLock();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it more safe to lock all tables at once outside the for loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the final result is same if the table is dropped before loop or in loop, we just make sure that it is thread safe, and we has already add committed txn check if user want to drop table during loading data

@caiconghui caiconghui force-pushed the table_lock branch 2 times, most recently from 6144347 to 1485ab0 Compare December 2, 2020 07:41
@@ -265,17 +265,16 @@ private void executeDynamicPartition() {
String tableName;
boolean skipAddPartition = false;
OlapTable olapTable;
db.readLock();
olapTable = (OlapTable) db.getTable(tableId);
Copy link
Contributor

Choose a reason for hiding this comment

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

When drop a table from database, a db lock will be held;
Why not get a db read lock here when get a table from database.

Copy link
Contributor Author

@caiconghui caiconghui Dec 4, 2020

Choose a reason for hiding this comment

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

@wangbo
because when drop table, we need to prevent that other thread drop table or create table too. but for get table, we just need to ensure that the get table operation is thread safe, and the final result is ok, we don't purse the strict consistency here for better performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another consideration is to avoid deadlock, all lock sequence is db lock -> table lock -> other lock, and if we has get table lock and sometimes need to get table again from db, there may cause dead lock, so db get table operation not get db read lock anymore

Copy link
Contributor

Choose a reason for hiding this comment

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

I get, you mean Performance is ahead of NPE.
For the second point about lock sequence .
I think it should become programming specifications for Doris.

1 If try to lock multiple tables or dbs, it should use getXXXInOrderMethod.
2 If try to lock different type locks, lock sequence should be  guaranteed.
3 The appearance of nested locks of the same type should be avoided.

Copy link
Contributor

@morningman morningman left a comment

Choose a reason for hiding this comment

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

LGTM

@caiconghui caiconghui added the approved Indicates a PR has been approved by one committer. label Jan 12, 2021
@caiconghui caiconghui merged commit f773003 into apache:master Jan 13, 2021
EmmyMiao87 pushed a commit to EmmyMiao87/incubator-doris that referenced this pull request Jan 26, 2021
…apache#3775)

This PR is to reduce lock competition by supporting read and write lock in table level. When we modify or read table's meta, we don't need to get database lock, just get table write or read lock. And when we get database lock, that means meta directly in db cannot be modified by other thread. Database lock only protect meta in Database class, while table lock protect meta in Table class.

Change-Id: Icec6f39f58708950c786059edaeb474a8aa0e324
@yangzhg yangzhg mentioned this pull request Feb 9, 2021
@caiconghui caiconghui deleted the table_lock branch August 23, 2023 03:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-review Categorizes an issue or PR as actively needing an API review. approved Indicates a PR has been approved by one committer. area/catalog Issues or PRs related to catalog management
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants