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

希望 Halo 提供内存索引功能提高数据的获取速度 #5058

Closed
guqing opened this issue Dec 18, 2023 · 2 comments · Fixed by #5121
Closed

希望 Halo 提供内存索引功能提高数据的获取速度 #5058

guqing opened this issue Dec 18, 2023 · 2 comments · Fixed by #5121
Assignees
Labels
area/core Issues or PRs related to the Halo Core kind/feature Categorizes issue or PR as related to a new feature.
Milestone

Comments

@guqing
Copy link
Member

guqing commented Dec 18, 2023

你当前使用的版本

2.11.0

描述一下此特性

目前 Halo 分页条件查询数据会有大批量无效数据被加载到内存中,在数据量多的情况下获取数据需要的时间和对内存的消耗变的很大

用例验证

创建一个 Moment 自定义模型,有以下这些属性

 public static class Spec {
        private String title;

        private String slug;

        private String content;

        private String raw;

        private List<String> tags;

        private String author;

        private boolean isPrivate;
    }

创建内容大小同为 37kb 的数据 2000 条

查询条件 1

通过 labelSelector 查询条件获取 10 条数据(实际会将该 Moment 自定义模型的所有数据查询到 Halo 然后过滤 10 条)

labelSelector=moment.halo.run/is-private=false&labelSelector=moment.halo.run/archive-month=28
Pasted image 20231206122624

Heap 使用量在查询时增加了约 1200M
查询耗时:20.120986512 seconds

查询条件 2

labelSelector=moment.halo.run/is-private=false
Pasted image 20231206123419

耗时:22.709311531 seconds,所以对于没有索引时无论查询条件都会出现相同的内存占用情况

用例 2

在单条数据大小不变的情况下增加到 4000 条,使用以下查询条件

labelSelector=moment.halo.run/is-private=false
Pasted image 20231206143057

耗时:58.147185776 seconds
可以看出随着数据量的增加,查询耗时和内存占用了都在上涨

在内存中建立索引

而在内存中建立对 metadata.name 与查询条件的索引后在 2k 和 4k 数据的情况下相同的查询条件的内存占用情况基本一致,如下:
(根据条件从索引筛选出所需的 metadata.name 后通过数据库的类似 where name in() 查询来批量拿取数据)
Pasted image 20231206143953

耗时:0.023571471 seconds

总结

由于索引提供了精准定位数据行主键的能力,有两点优于目前 ReactiveExtensionClient.list 方法:

  1. list 查询依旧可以通过主键索引从数据库获取数据
  2. 减少了不必要的数据传输,查询条件可以过滤掉那些不需要的数据且分页可以减少获取数据数量从而提高了效率

因此随着目前数据量日益增长的情况下,有必要先提供一个建立内存索引并根据查询条件使用索引来获取数据的功能以减少查询时的内存占用和缩短查询时间

附加信息

/kind feature
/area core
/assign

@f2c-ci-robot f2c-ci-robot bot added kind/feature Categorizes issue or PR as related to a new feature. area/core Issues or PRs related to the Halo Core labels Dec 18, 2023
@xinkeng0
Copy link
Contributor

  1. 分页时,如果没有任何查询条件且无需排序的情况下,也许可以先skip take之后再Deserialize JSON

    public <E extends Extension> Flux<E> list(Class<E> type, Predicate<E> predicate,
    Comparator<E> comparator) {
    var scheme = schemeManager.get(type);
    var prefix = ExtensionStoreUtil.buildStoreNamePrefix(scheme);
    return client.listByNamePrefix(prefix)
    .map(extensionStore -> converter.convertFrom(type, extensionStore))
    .filter(predicate == null ? Predicates.isTrue() : predicate)
    .sort(comparator == null ? Comparator.naturalOrder() : comparator);
    }
    @Override
    public <E extends Extension> Mono<ListResult<E>> list(Class<E> type, Predicate<E> predicate,
    Comparator<E> comparator, int page, int size) {
    var extensions = list(type, predicate, comparator);
    var totalMono = extensions.count();
    if (page > 0) {
    extensions = extensions.skip(((long) (page - 1)) * (long) size);
    }
    if (size > 0) {
    extensions = extensions.take(size);
    }
    return extensions.collectList().zipWith(totalMono)

  2. 鉴于Plugin的自定义模型定义不可知,用JSON存储,查询怎么样?

Type H2 Mysql PostgreSQL
JSON Column
JSON Query

@JohnNiang
Copy link
Member

Hi @gengxiaoxiaoxin ,感谢提供思路!

  1. 由于 Predicate 和 Comparator 都需要拿到 Extension 之后才能进行判断和排序,故无法做到先 skip 后 Deserialize JSON。当前 Issue 要解决的应该就是这个问题,我们需要提前为所有数据建立索引,重构搜索和排序方法,实现只需要根据索引就可以判断并排序,这样就只需要拿到部分数据并 Deserialize JSON。
  2. 如果我们需要自己实现索引的话,如果还用 JSON 存储的话,可能有些多余。同时,不同的数据库的 JSON 查询的方式可能不同。

f2c-ci-robot bot pushed a commit that referenced this issue Jan 19, 2024
#### What type of PR is this?
/kind feature
/area core
/milestone 2.12.x

#### What this PR does / why we need it:
新增自定义模型索引机制

默认为所有的自定义模型都添加了以下索引:
- metadata.name
- metadata.labels
- metadata.creationTimestamp
- metadata.deletionTimestamp

**how to test it?**
1. 测试应用的启动和停止
2. 测试 Reconciler 被正确执行,如创建文章发布文章,测试删除文章的某个 label 数据启动后能被 PostReconciler 恢复(即Reconciler 被正确执行)
3. 测试自定义模型自动生成的 list APIs
	1. 能根据 labels 正确过滤数据和分页
	2. 能根据 creationTimestamp 正确排序
	3. 测试插件启用后也能正确使用 list APIs 根据 labels 过滤数据和 creationTimestamp 排序
4. 能正确删除数据(则表示 GcReconciler 使用索引正确)
5. 测试在插件中为自定义模型注册索引
```java
public class DemoPlugin extension BasePlugin {
    private final SchemeManager schemeManager;

    public MomentsPlugin(PluginContext pluginContext, SchemeManager schemeManager) {
        super(pluginContext);
        this.schemeManager = schemeManager;
    }

    @OverRide
    public void start() {
        schemeManager.register(Moment.class, indexSpecs -> {
            indexSpecs.add(new IndexSpec()
                .setName("spec.tags")
                .setIndexFunc(multiValueAttribute(Moment.class, moment -> {
                    var tags = moment.getSpec().getTags();
                    return tags == null ? Set.of() : tags;
                }))
            );
            indexSpecs.add(new IndexSpec()
                .setName("spec.owner")
                .setIndexFunc(simpleAttribute(Moment.class,
                    moment -> moment.getSpec().getOwner())
                )
            );
            indexSpecs.add(new IndexSpec()
                .setName("spec.releaseTime")
                .setIndexFunc(simpleAttribute(Moment.class, moment -> {
                    var releaseTime = moment.getSpec().getReleaseTime();
                    return releaseTime == null ? null : releaseTime.toString();
                }))
            );

            indexSpecs.add(new IndexSpec()
                .setName("spec.visible")
                .setIndexFunc(simpleAttribute(Moment.class, moment -> {
                    var visible = moment.getSpec().getVisible();
                    return visible == null ? null : visible.toString();
                }))
            );
        });
    }

    @OverRide
    public void stop() {
        // unregister scheme 即可,不需要手动删除索引
    }
}
```
可以正确在自动生成的 list APIs 使用 fieldSelector 来过滤 `spec.slug` 和排序,可以自己添加其他的 indexSpec 测试
6. 测试唯一索引并添加重复数据,期望无法添加进去

#### Which issue(s) this PR fixes:
Fixes #5058

#### Does this PR introduce a user-facing change?
```release-note
新增自定义模型索引机制
```
@ruibaby ruibaby added this to the 2.12.0 milestone Jan 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/core Issues or PRs related to the Halo Core kind/feature Categorizes issue or PR as related to a new feature.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants