Spring Data JPA:高效查询大型数据集的数据库
Spring Data JPA: Efficiently Query The Database for A Large Dataset
我编写了一个应用程序来收集大量评论。对于每条评论,我都会存储评论本身 Review_Table(User_Id, Trail_Id, Rating),用户名 (Id, Username, UserLink) 和之前在代码中构建的 Trail (Id , ...60 个其他属性)
for(Element card: reviewCards){
String userName = card.select("expression").text();
String userLink = card.select("expression").attr("href");
String userRatingString = card.select("expression").attr("aria-label");
Double userRating;
if(userRatingString.equals("NaN Stars")){
userRating = 0.0;
}else {
userRating = Double.parseDouble(userRatingString.replaceAll("[^0-9.]", ""));
}
User u;
Rating r;
//probably this is the bottleneck
if(userService.getByUserLink(userLink)!=null){
u = new User(userName, userLink, new HashSet<Rating>());
r = Rating.builder()
.user(u)
.userRating(userRating)
.trail(t)
.build();
}else {
u = userService.getByUserLink(userLink);
r = Rating.builder()
.user(u)
.userRating(userRating)
.trail(t)
.build();
}
i = i +1;
ratingSet.add(r);
userSet.add(u);
}
saveToDb(userSet, t, link, ratingSet);
savedEntities = savedEntities + 1;
log.info(savedEntities + " Saved Entities");
}
该代码适用于中小型数据集,但我遇到了大型数据集的巨大瓶颈。假设我有 13K 用户实体已经存储在 PostgresDB 中,另一批 8500 条评论将被删除,我必须检查每条评论是否已经存储了该评论的用户。这要花很长时间
我尝试在 Postgres 中对 UserLink 属性进行定义和索引,但速度根本没有提高
我试图将所有存储在 Db 中的用户收集到一个集合中,并使用 contains 方法检查特定用户是否已经存在于集合中(通过这种方式我认为我可以绕过 8k 写入和读取的数据库瓶颈,但这是一种冒险的方式,因为如果 db table 中的用户太多,我会遇到内存溢出)。同样,速度没有提高
在这一点上我没有任何其他改进的想法
好吧,如果不在循环中单独查询每个用户,您肯定会受益。您可以做的是仅针对 UserLink
或 UserName
进行查询和缓存,这意味着仅获取并缓存其中一个的完整集合,因为这似乎是您在 if-else
中需要区分的内容.
您实际上可以使用 Spring Data JPA @Query either directly or even with Spring Data JPA Projections to query subset of fields if needed and cache & use them for the lookup. If you think the users could run into millions or billions then you could think of using a distributed cache like Apache Ignite 查询单个字段,您的集合可以在其中轻松扩展。
顺便说一下,if-else
好像是倒过来的,不是吗?
接下来,您不要像上面的代码暗示的那样单独存储每条评论。可以分批写。另外,由于您使用的是 Postgres,因此可以使用 Postgres CopyManager provided by Postgres for bulk data transfer by using it with Spring Data Custom repositories。因此,您可以按照设定的时间表(每 x 分钟)继续在本地写入新的 text/csv 文件,并使用它将批处理的 text/csv 写入 table(在那之后 x 分钟)和删除文件。这真的很快。
另一种选择是编写一个结合上述内容的存储过程并在自定义存储库中再次调用它。
请让我知道你喜欢哪一个..
更新(2022 年 1 月 12 日):
我错过的另一项是当您查询 UserLink
或 UserName
时,您可以使用 Postgres 支持的一种非常有效的 select 查询形式,而不是使用像这样的 IN 子句下面,
@Select("select u from user u where u.userLink = ANY('{:userLinks}'::varchar[])", nativeQuery = true)
List<Users> getUsersByLinks(@Param("userLinks") String[] userLinks);
我编写了一个应用程序来收集大量评论。对于每条评论,我都会存储评论本身 Review_Table(User_Id, Trail_Id, Rating),用户名 (Id, Username, UserLink) 和之前在代码中构建的 Trail (Id , ...60 个其他属性)
for(Element card: reviewCards){
String userName = card.select("expression").text();
String userLink = card.select("expression").attr("href");
String userRatingString = card.select("expression").attr("aria-label");
Double userRating;
if(userRatingString.equals("NaN Stars")){
userRating = 0.0;
}else {
userRating = Double.parseDouble(userRatingString.replaceAll("[^0-9.]", ""));
}
User u;
Rating r;
//probably this is the bottleneck
if(userService.getByUserLink(userLink)!=null){
u = new User(userName, userLink, new HashSet<Rating>());
r = Rating.builder()
.user(u)
.userRating(userRating)
.trail(t)
.build();
}else {
u = userService.getByUserLink(userLink);
r = Rating.builder()
.user(u)
.userRating(userRating)
.trail(t)
.build();
}
i = i +1;
ratingSet.add(r);
userSet.add(u);
}
saveToDb(userSet, t, link, ratingSet);
savedEntities = savedEntities + 1;
log.info(savedEntities + " Saved Entities");
}
该代码适用于中小型数据集,但我遇到了大型数据集的巨大瓶颈。假设我有 13K 用户实体已经存储在 PostgresDB 中,另一批 8500 条评论将被删除,我必须检查每条评论是否已经存储了该评论的用户。这要花很长时间
我尝试在 Postgres 中对 UserLink 属性进行定义和索引,但速度根本没有提高
我试图将所有存储在 Db 中的用户收集到一个集合中,并使用 contains 方法检查特定用户是否已经存在于集合中(通过这种方式我认为我可以绕过 8k 写入和读取的数据库瓶颈,但这是一种冒险的方式,因为如果 db table 中的用户太多,我会遇到内存溢出)。同样,速度没有提高
在这一点上我没有任何其他改进的想法
好吧,如果不在循环中单独查询每个用户,您肯定会受益。您可以做的是仅针对 UserLink
或 UserName
进行查询和缓存,这意味着仅获取并缓存其中一个的完整集合,因为这似乎是您在 if-else
中需要区分的内容.
您实际上可以使用 Spring Data JPA @Query either directly or even with Spring Data JPA Projections to query subset of fields if needed and cache & use them for the lookup. If you think the users could run into millions or billions then you could think of using a distributed cache like Apache Ignite 查询单个字段,您的集合可以在其中轻松扩展。
顺便说一下,if-else
好像是倒过来的,不是吗?
接下来,您不要像上面的代码暗示的那样单独存储每条评论。可以分批写。另外,由于您使用的是 Postgres,因此可以使用 Postgres CopyManager provided by Postgres for bulk data transfer by using it with Spring Data Custom repositories。因此,您可以按照设定的时间表(每 x 分钟)继续在本地写入新的 text/csv 文件,并使用它将批处理的 text/csv 写入 table(在那之后 x 分钟)和删除文件。这真的很快。
另一种选择是编写一个结合上述内容的存储过程并在自定义存储库中再次调用它。
请让我知道你喜欢哪一个..
更新(2022 年 1 月 12 日):
我错过的另一项是当您查询 UserLink
或 UserName
时,您可以使用 Postgres 支持的一种非常有效的 select 查询形式,而不是使用像这样的 IN 子句下面,
@Select("select u from user u where u.userLink = ANY('{:userLinks}'::varchar[])", nativeQuery = true)
List<Users> getUsersByLinks(@Param("userLinks") String[] userLinks);