(对不起,这是TL,博士,但我很绝望,想要彻底!)
我们正在将服务从AWS转移到GCP,并从DynamoDB切换到云扳手作为后端数据存储。
数据存储(扳手)包含web服务查询用户的数据。在生产负载中,查询的数据在1%到10%之间。我有一个简单的多线程Java测试客户机来查询我们的服务,只要过去1分钟的平均吞吐量不断增加,就会不断添加新的线程。
我的测试客户端运行在GCE (64 CPU)上,当使用DynamoDB数据源时,当我们的服务自动扩展到配置的pod节点计数时,我可以获得最多3700个线程,平均通过50k req/s。每1000次请求,每个线程从Dynamo读取100个散列(命中率10%)。
现在,我需要切换我的Java客户端,以查询10%请求中使用的数据。我的查询一般如下:
SELECT A, B, C FROM data_table LIMIT 250 OFFSET XXX理论上,我希望每个线程选择独特的行块。我使用偏移量从一个唯一的位置开始每个线程读取,一旦每个记录块被用完,我将偏移量增加到startingOffset + totalRows,并选择另一个数据块。
我意识到这个查询可能不会转换到每个实现,但是这个概念应该是正确的,即每个线程都可以在线程生命周期内为唯一的数据集查询扳手。
我尝试通过c3p0连接池和标准的DriverManager.getConnection()路由使用jdbc。我使用了min/max会话配置以及numChannels,但似乎没有什么能帮助我将其扩展。TBH,我仍然不明白会话和频道之间的相关性。
我还尝试了本机SpannerDB客户机的singleUseReadOnlyTransaction()、batchReadOnlyTransaction()和最近的txn.partitionQuery()。
由于partitionQuery()感觉很像DynamoDB代码,这似乎是正确的方向,但是由于我的查询(基于https://cloud.google.com/spanner/docs/reads中的“并行读取数据”示例)有一个限制子句,所以我得到了错误:
com.google.api.gax.rpc.InvalidArgumentException: com.google.cloud.spanner.SpannerException: INVALID_ARGUMENT: io.grpc.StatusRuntimeException: INVALID_ARGUMENT: Query是不可根分区的,因为它在根上没有DistributedUnion。请运行“解释查询计划详细信息”。
删除限制子句可以忽略这一点,但是查询需要花费很长时间!
因此,问题是,如果partitionQuery()路由是正确的,我如何进行具有“分页”限制的并行查询?如果这不是最好的路由,那么我应该使用什么来获得每个线程具有唯一数据集的最佳并行读取吞吐量?
编辑基于下面的评论由克努特奥拉夫Loite,分区或批处理查询不是正确的方法,所以我回到一个单一的使用只读查询。
下面是我创建spannerDbClient的代码:
RetrySettings retrySettings = RetrySettings.newBuilder()
.setInitialRpcTimeout(Duration.ofSeconds(SPANNER_INITIAL_TIMEOUT_RETRY_SECONDS))
.setMaxRpcTimeout(Duration.ofSeconds(SPANNER_MAX_TIMEOUT_RETRY_SECONDS))
.setMaxAttempts(SPANNER_MAX_RETRY_ATTEMPTS)
.setTotalTimeout(Duration.ofSeconds(SPANNER_TOTAL_TIMEOUT_RETRY_SECONDS))
.build();
SpannerOptions.Builder builder = SpannerOptions.newBuilder()
.setSessionPoolOption(SessionPoolOptions.newBuilder()
.setFailIfPoolExhausted()
.setMinSessions(SPANNER_MIN_SESSIONS)
.setMaxSessions(SPANNER_MAX_SESSIONS)
.build()
)
.setNumChannels(SPANNER_NUM_CHANNELS);
if (credentials != null) {
builder.setCredentials(credentials);
}
builder.getSpannerStubSettingsBuilder()
.executeSqlSettings()
.setRetryableCodes(StatusCode.Code.DEADLINE_EXCEEDED, StatusCode.Code.UNAVAILABLE)
.setRetrySettings(retrySettings);
spanner = builder.build().getService();
databaseId = DatabaseId.of(
projectName,
instanceName,
databaseName
);
spannerDbClient = spanner.getDatabaseClient(databaseId);下面是我执行实际查询的方法:
List<Entry> entry = new ArrayList<>();
try (ResultSet resultSet = spannerDbClient
.singleUseReadOnlyTransaction(TimestampBound.ofMaxStaleness(5, TimeUnit.SECONDS))
.executeQuery(Statement.newBuilder(String.format("SELECT * from %s LIMIT %d OFFSET %d", tableName, limit, offset)).build())) {
while (resultSet.next()) {
entry.add(getEntryFromResultSet(resultSet));
}
}我添加了计时器代码,显示了查询的时间,这是50个线程的样子。这是使用共享spannerDbClient实例与maxSession=50、minSession=50、numChannels=4 (默认):
--> [0h:00m:00s] Throughput: Total 0, Interval 0 (0 req/s), 0/0 threads reporting
[tId:099][00:00:00.335] Spanner query, LIMIT 250 OFFSET 99000
[tId:146][00:00:00.382] Spanner query, LIMIT 250 OFFSET 146000
[tId:140][00:00:00.445] Spanner query, LIMIT 250 OFFSET 140000
[tId:104][00:00:00.494] Spanner query, LIMIT 250 OFFSET 104000
[tId:152][00:00:00.363] Spanner query, LIMIT 250 OFFSET 152000
[tId:149][00:00:00.643] Spanner query, LIMIT 250 OFFSET 149000
[tId:143][00:00:00.748] Spanner query, LIMIT 250 OFFSET 143000
[tId:163][00:00:00.682] Spanner query, LIMIT 250 OFFSET 163000
[tId:155][00:00:00.799] Spanner query, LIMIT 250 OFFSET 155000
[tId:166][00:00:00.872] Spanner query, LIMIT 250 OFFSET 166000
[tId:250][00:00:00.870] Spanner query, LIMIT 250 OFFSET 250000
[tId:267][00:00:01.319] Spanner query, LIMIT 250 OFFSET 267000
[tId:229][00:00:01.917] Spanner query, LIMIT 250 OFFSET 229000
[tId:234][00:00:02.256] Spanner query, LIMIT 250 OFFSET 234000
[tId:316][00:00:02.401] Spanner query, LIMIT 250 OFFSET 316000
[tId:246][00:00:02.844] Spanner query, LIMIT 250 OFFSET 246000
[tId:312][00:00:02.989] Spanner query, LIMIT 250 OFFSET 312000
[tId:176][00:00:03.497] Spanner query, LIMIT 250 OFFSET 176000
[tId:330][00:00:03.140] Spanner query, LIMIT 250 OFFSET 330000
[tId:254][00:00:03.879] Spanner query, LIMIT 250 OFFSET 254000
[tId:361][00:00:03.816] Spanner query, LIMIT 250 OFFSET 361000
[tId:418][00:00:03.635] Spanner query, LIMIT 250 OFFSET 418000
[tId:243][00:00:04.503] Spanner query, LIMIT 250 OFFSET 243000
[tId:414][00:00:04.006] Spanner query, LIMIT 250 OFFSET 414000
[tId:324][00:00:04.457] Spanner query, LIMIT 250 OFFSET 324000
[tId:498][00:00:03.865] Spanner query, LIMIT 250 OFFSET 498000
[tId:252][00:00:04.945] Spanner query, LIMIT 250 OFFSET 252000
[tId:494][00:00:04.211] Spanner query, LIMIT 250 OFFSET 494000
[tId:444][00:00:04.780] Spanner query, LIMIT 250 OFFSET 444000
[tId:422][00:00:04.951] Spanner query, LIMIT 250 OFFSET 422000
[tId:397][00:00:05.234] Spanner query, LIMIT 250 OFFSET 397000
[tId:420][00:00:05.106] Spanner query, LIMIT 250 OFFSET 420000
[tId:236][00:00:05.985] Spanner query, LIMIT 250 OFFSET 236000
[tId:406][00:00:05.429] Spanner query, LIMIT 250 OFFSET 406000
[tId:449][00:00:05.291] Spanner query, LIMIT 250 OFFSET 449000
[tId:437][00:00:05.929] Spanner query, LIMIT 250 OFFSET 437000
[tId:341][00:00:06.611] Spanner query, LIMIT 250 OFFSET 341000
[tId:475][00:00:06.223] Spanner query, LIMIT 250 OFFSET 475000
[tId:490][00:00:06.186] Spanner query, LIMIT 250 OFFSET 490000
[tId:416][00:00:06.460] Spanner query, LIMIT 250 OFFSET 416000
[tId:328][00:00:07.446] Spanner query, LIMIT 250 OFFSET 328000
[tId:322][00:00:07.679] Spanner query, LIMIT 250 OFFSET 322000
[tId:158][00:00:09.357] Spanner query, LIMIT 250 OFFSET 158000
[tId:496][00:00:08.183] Spanner query, LIMIT 250 OFFSET 496000
[tId:256][00:00:09.250] Spanner query, LIMIT 250 OFFSET 256000
--> [0h:00m:10s] Throughput: Total 9848, Interval +9848 (984 req/s), 44/50 threads reporting
[tId:492][00:00:08.646] Spanner query, LIMIT 250 OFFSET 492000
[tId:390][00:00:09.810] Spanner query, LIMIT 250 OFFSET 390000
[tId:366][00:00:10.142] Spanner query, LIMIT 250 OFFSET 366000
[tId:320][00:00:10.451] Spanner query, LIMIT 250 OFFSET 320000
[tId:318][00:00:10.619] Spanner query, LIMIT 250 OFFSET 318000
--> [0h:00m:20s] Throughput: Total 56051, Interval +46203 (4620 req/s), 50/50 threads reporting
--> [0h:00m:30s] Throughput: Total 102172, Interval +46121 (4612 req/s), 50/50 threads reporting 请注意,不管偏移量如何,查询时间只会增加,初始扳手查询在开始报告结果之前返回所有50个线程的数据需要10到20秒。如果我把限制提高到1000,那么所有50个线程都需要2分钟的时间才能从扳手那里得到结果。
将其与DynamoDb等效(除非限制为1000)进行比较,其中所有查询在不到1秒内返回,所有50个线程在显示10秒状态更新之前都在报告结果:
--> [0h:00m:00s] Throughput: Total 0, Interval 0 (0 req/s), 0/0 threads reporting
[tId:045] Dynamo query, LIMIT 1000 [00:00:00.851]
[tId:138] Dynamo query, LIMIT 1000 [00:00:00.463]
[tId:183] Dynamo query, LIMIT 1000 [00:00:00.121]
[tId:122] Dynamo query, LIMIT 1000 [00:00:00.576]
[tId:095] Dynamo query, LIMIT 1000 [00:00:00.708]
[tId:072] Dynamo query, LIMIT 1000 [00:00:00.778]
[tId:115] Dynamo query, LIMIT 1000 [00:00:00.619]
[tId:166] Dynamo query, LIMIT 1000 [00:00:00.296]
[tId:058] Dynamo query, LIMIT 1000 [00:00:00.814]
[tId:179] Dynamo query, LIMIT 1000 [00:00:00.242]
[tId:081] Dynamo query, LIMIT 1000 [00:00:00.745]
[tId:106] Dynamo query, LIMIT 1000 [00:00:00.671]
[tId:162] Dynamo query, LIMIT 1000 [00:00:00.348]
[tId:035] Dynamo query, LIMIT 1000 [00:00:00.889]
[tId:134] Dynamo query, LIMIT 1000 [00:00:00.513]
[tId:187] Dynamo query, LIMIT 1000 [00:00:00.090]
[tId:158] Dynamo query, LIMIT 1000 [00:00:00.405]
[tId:191] Dynamo query, LIMIT 1000 [00:00:00.095]
[tId:195] Dynamo query, LIMIT 1000 [00:00:00.096]
[tId:199] Dynamo query, LIMIT 1000 [00:00:00.144]
[tId:203] Dynamo query, LIMIT 1000 [00:00:00.112]
[tId:291] Dynamo query, LIMIT 1000 [00:00:00.102]
[tId:303] Dynamo query, LIMIT 1000 [00:00:00.094]
[tId:312] Dynamo query, LIMIT 1000 [00:00:00.101]
[tId:318] Dynamo query, LIMIT 1000 [00:00:00.075]
[tId:322] Dynamo query, LIMIT 1000 [00:00:00.086]
[tId:326] Dynamo query, LIMIT 1000 [00:00:00.096]
[tId:330] Dynamo query, LIMIT 1000 [00:00:00.085]
[tId:334] Dynamo query, LIMIT 1000 [00:00:00.114]
[tId:342] Dynamo query, LIMIT 1000 [00:00:00.096]
[tId:391] Dynamo query, LIMIT 1000 [00:00:00.081]
[tId:395] Dynamo query, LIMIT 1000 [00:00:00.088]
[tId:406] Dynamo query, LIMIT 1000 [00:00:00.088]
[tId:415] Dynamo query, LIMIT 1000 [00:00:00.078]
[tId:421] Dynamo query, LIMIT 1000 [00:00:00.089]
[tId:425] Dynamo query, LIMIT 1000 [00:00:00.068]
[tId:429] Dynamo query, LIMIT 1000 [00:00:00.088]
[tId:433] Dynamo query, LIMIT 1000 [00:00:00.105]
[tId:437] Dynamo query, LIMIT 1000 [00:00:00.092]
[tId:461] Dynamo query, LIMIT 1000 [00:00:00.110]
[tId:483] Dynamo query, LIMIT 1000 [00:00:00.071]
[tId:491] Dynamo query, LIMIT 1000 [00:00:00.078]
[tId:495] Dynamo query, LIMIT 1000 [00:00:00.075]
[tId:503] Dynamo query, LIMIT 1000 [00:00:00.064]
[tId:499] Dynamo query, LIMIT 1000 [00:00:00.108]
[tId:514] Dynamo query, LIMIT 1000 [00:00:00.163]
[tId:518] Dynamo query, LIMIT 1000 [00:00:00.135]
[tId:529] Dynamo query, LIMIT 1000 [00:00:00.163]
[tId:533] Dynamo query, LIMIT 1000 [00:00:00.079]
[tId:541] Dynamo query, LIMIT 1000 [00:00:00.060]
--> [0h:00m:10s] Throughput: Total 24316, Interval +24316 (2431 req/s), 50/50 threads reporting
--> [0h:00m:20s] Throughput: Total 64416, Interval +40100 (4010 req/s), 50/50 threads reporting 我是不是在配置中遗漏了什么?如果我让它自动放大,性能问题就会大大放大。
发布于 2021-03-26 08:00:12
根据其他信息编辑:
正如[医] Voulgaris Panagiotis Voulgaris在下面指出的,我不认为这个问题与客户端配置有关,而是与查询本身有关。查询似乎相当慢,特别是对于较高的OFFSET值。我尝试使用一个大约有1,000,000行的表,对于OFFSET值为900,000,一个查询运行4-5秒。当您进行扩展时,问题会变得更糟,原因可能是您使用了许多需要很长时间的并行查询来压倒后端,而不是因为客户端配置错误。
最好的方法是重写查询,根据主键值选择一系列行,而不是使用LIMIT x OFFSET y构造。这样,您的查询就会如下所示:
SELECT A, B, C
FROM data_table
WHERE A >= x AND A < (x+250)这显然不能保证如果您的键列包含值之间的空白,那么每个分区中就会有确切的250行。在这种情况下,您还可以稍微增加+250值,以获得合理的分区。
如果由于键值是完全随机的值(或分布不均匀),所以不可能这样做,那么我认为以下查询将比当前的查询更有效:
SELECT A, B, C
FROM data_table
WHERE A >= (
SELECT ANY_VALUE(A)
FROM data_table
GROUP BY A
LIMIT 1 OFFSET y
)
ORDER BY A
LIMIT 250在这种情况下,我不太清楚你的最终目标是什么,而在具体问题上,这就产生了不同的影响:
...if partitionQuery()路由是正确的(?)
BatchReadOnlyTransaction和partitionQuery()路由用于在单个时间点读取大型数据集。例如,当您想要创建表中所有数据的转储时,可能会出现这种情况。扳手将为您划分查询,并返回分区列表。然后,每个分区可以由单独的线程(甚至是单独的VM)处理。这可以说是自动替换查询的LIMIT 250 OFFSET xxxx部分,因为Spanner根据表中的实际数据创建不同的分区。
但是,如果您的最终目标是模拟生产负载,那么BatchReadOnlyTransaction不是要遵循的路线。
如果您要做的是高效地查询数据集,那么您应该确保使用单用只读事务进行查询。这就是您已经在使用本机客户端所做的事情。此外,只要连接处于自动提交模式(),JDBC驱动程序也将自动使用查询的单用途只读事务。如果关闭自动提交,则在执行查询时,驱动程序将自动启动事务。
关于会议和渠道:
关于(示例)查询:如上所述,我不太清楚这是一个测试设置,还是一个实际的生产示例。但是,我希望查询包含一个显式的ORDER BY子句,以确保按预期顺序返回数据,并且ORDER BY子句显然应该使用索引列。
最后:在每个查询上,后端响应缓慢是否会导致问题?还是后端基本上处于闲置状态,客户端是否无法真正增加查询?
发布于 2021-03-26 18:38:22
我怀疑为了产生准确的结果
SELECT A, B, C FROM data_table LIMIT 250 OFFSET XXX后端需要获取250 + XXX行,然后跳过其中的XXX行。因此,如果XXX非常大,这可能是一个非常昂贵的查询,需要扫描大量的data_table。
相反地限制表键是否有意义?类似于:
SELECT A, B, C FROM data_table WHERE TableKey1 > 'key_restriction' LIMIT 250;这种类型的查询最多只能读取250行。
独立地理解这样的查询对于您的生产工作负载有多有代表性是很好的。您能解释您在生产中所期望的查询类型吗?
https://stackoverflow.com/questions/66811901
复制相似问题