我在 AWS 上有一个 5 节点 cassandra (3.11) 集群。 机器规格
Model vCPU Memory (GiB) Instance Storage (GB) Networking Bandwidth (Gbps)
i3.large 2 15.25 1 x 475 NVMe SSD Up to 10
对于我的密钥空间,复制因子 = 2。
我需要将车辆的移动记录存储在表中。
这些列非常简单 -vehicleId、lat、lng、timestamp
我们拥有 10 万辆车辆,并且拥有每辆车辆 10 年的历史数据。
总数据量约为20亿行。
我需要读取 ASC 中给定车辆的所有数据,以便对其进行一些处理。
之前我们的表结构是
CREATE TABLE vehicle_movement (
vehicle_id int,
timestamp timestamp,
lat double,
lng double,
PRIMARY KEY (vehicle_id, timestamp)
) WITH CLUSTERING ORDER BY (timestamp ASC)
Where vehicle_id was PARTITION KEY.
AND the query was
SELECT lat, lng, timestamp from vehicle_movement where vehicle_id = xyz
读取查询的性能相当慢(~5秒)。我们发现,由于每个vehicle_id单独作为分区键是不够的,因为我们每辆车有300K+记录,这可能会导致很大的分区。
所以我们稍微改变了我们的模式
以下是我更新的表格
CREATE TABLE vehicle_movement (
vehicle_id int,
year int,
timestamp timestamp,
lat double,
lng double,
PRIMARY KEY ((vehicle_id, year), timestamp)
) WITH CLUSTERING ORDER BY (timestamp ASC)
Here vehicle_id and year are composite partition keys
which ensures that each partition may not contain more than 10k-15k records.
但是由于我的问题陈述仍然相同 - 按 ASC 顺序获取给定车辆的所有记录, 我进一步将查询拆分为多个
Eg
SELECT lat, lng, timestamp from vehicle_movement where vehicle_id = xyz and year IN (2018, 2019)
SELECT lat, lng, timestamp from vehicle_movement where vehicle_id = xyz and year IN (2020, 2021)
SELECT lat, lng, timestamp from vehicle_movement where vehicle_id = xyz and year IN (2022, 2023)
与之前相比,这种方法减少了延迟,但仍然不能令人满意(与之前的 5 秒相比,延迟为 2 到 2.5 秒)。
我的客户端是基于spring的java应用程序,其中我使用spring-data-cassandra。
我执行这些多个查询并累积所有结果。
对于读取,我使用了consistency = LOCAL_ONE(读取速度最快)
我尝试了查询的串行和并行执行,但总体延迟仍然或多或少相同。
所以我的疑问是:
编辑2
我对我的架构进行了一些反规范化,它显着改善了我的读取延迟。
CREATE TYPE movement_point (
lat DOUBLE,
lng DOUBLE,
ts TIMESTAMP
);
CREATE TABLE vehicle_movement_denorm (
vehicle_id INT,
year INT,
points LIST<FROZEN<movement_point>>, // stored in ASC order
count INT, // As there is no built in function for count
PRIMARY KEY (vehicle_id, year)
) WITH CLUSTERING ORDER BY (year ASC);
幸运的是,对于每辆车和年份,我们有 7000-8000 条记录,这些记录很好地融入冻结列表,同时维持秩序。
读取数据时,一次调用就查询了3-4年的数据。 所以 2-3 个调用来获取我的数据。
SELECT points from vehicle_movement_denorm where vehicle_id = xyz and year in (2020, 2021, 2022)
SELECT points from vehicle_movement_denorm where vehicle_id = xyz and year in (2023, 2024)
这种方法给我带来了 200-400 毫秒的读取延迟,这非常好。 这种非规范化方法是否良好且可扩展?
首先,是的 - 如果你有一个相当短的列表(7000 条记录并不短,但它在合理的范围内。如果它可以变得更大甚至无限,你就会遇到大问题)并且你永远不需要修改 - 只需读取旧的历史值,那么冻结列表确实是一个不错的选择。
尽管我很惊讶使用冻结列表而不是分区可以大大减少延迟。从分区读取连续的、排序的数据不应该比读取冻结列表慢多少。也许由于某种原因,原始读取进行了更多的分页,并且您的页面较小和/或您的网络延迟较高(?),这导致分页速度较慢?
原则上,获得低延迟的最佳方法是使问题更加并行化,而不是更少。在最新的解决方案中,您从单个分区读取所有数据,因此从单个节点(或最多 3 个节点)读取所有数据。在 ScyllaDB 中,情况更糟 - 集群的 10 个 CPU 中只有 1 或 3 个 CPU。我认为如果使用 (vehicle_id,year) 一个复合分区键(年份将是分区键的一部分,而不是集群键),那么您将获得更低的延迟,并且您将并行执行不同年份的所有 SELECT。其中每一个都将被发送到不同的 CPU,利用更多集群的处理能力,并更快地完成(更低的延迟)。