【问题标题】:Grails batch update postgres performanceGrails 批量更新 postgres 性能
【发布时间】:2023-04-01 19:30:01
【问题描述】:

我正在尝试使用 Grails 和 json 对 postgres 数据库进行一次大的事务更新。我有一个压缩的 json 文件,我正在从中读取属性并相应地更新数据库。

好吧,我正在从文件中读取 json

new FileInputStream("/home/export.gz")

将其传递给解压缩流并创建 json 解析器

GZIPInputStream gzipInputStream = new GZIPInputStream(is)
JsonParser jp = new JsonFactory().createParser(gzipInputStream);

正在解析 json 字段...

    while (jp.nextToken() != null) {
        if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
            if (jp.nextToken() == JsonToken.FIELD_NAME) {
              ...
            }
        } 
    }

像这样从数据库中加载每一行

private <C> C getById(Class<C> clazz, def id) {
    Criteria criteria = sessionFactory.getCurrentSession().createCriteria(clazz).setCacheMode(CacheMode.IGNORE)
    criteria.add(Restrictions.idEq(id))
    return (C) criteria.uniqueResult()
}

获取行,设置字段,刷新每一行

private void flushAndEvict(def o) {
    sessionFactory.getCurrentSession().flush()
    sessionFactory.getCurrentSession().evict(o)
    o.discard()
}

问题是这种方法随着时间的推移变得越来越慢。 开始时更新 500 行/秒,更新约 4000 行,速度为 100 行/秒,到更新 30 000 行时,速度为 10 行/秒,50 000 更新约 5 行/秒。

监控性能我发现内存一切正常,gc 峰值不会超过 5% 的 cpu 时间。

CPU 正在全功率运行,100%,50%(挂墙时间)睡眠,42% SocketInputStream.read(byte[], int, int)。

我看到更多的是套接字读/写速度变慢,从 10 kb/s 开始,会话结束时为 5kb/s。

我试图了解这里的瓶颈以及如何提高性能。什么会导致这种速度下降?

【问题讨论】:

  • 我不明白您为什么说要进行大笔交易并且要刷新您保存的每个域。你在做任何交易吗?
  • @juandiegoh 井数据需要保持一致,我不能部分更新。要么全有,要么全无。
  • 据我所见,您并没有进行事务处理,您是否尝试过失败并正在回滚?还是必须从数据库中删除才能重新开始?

标签: json performance postgresql grails


【解决方案1】:

我建议你每 500 或 1000 次刷新一次(你应该检查你的时间并选择一个),然后你应该清除你的会话。这样您就不会再有任何延迟,您的时间将变得线性。

def BATCH_SIZE = 1000 // or 500

def myListOfJsonsParsed = [] //...

// ...
def count = 0
myListOfJsonsParsed.each {
    def o = getById(..., it.id)
    // update o fields
    o.save()
    count++
    if(count >= BATCH_SIZE) {
        count = 0
        flushAndClear()
    }
}
flushAndClear()
// ...

private void flushAndClear() {
    sessionFactory.getCurrentSession().flush()
    sessionFactory.getCurrentSession().clear()
}

private <C> C getById(Class<C> clazz, def id) {
    Criteria criteria = sessionFactory.getCurrentSession().createCriteria(clazz).setCacheMode(CacheMode.IGNORE)
    criteria.add(Restrictions.idEq(id))
    return (C) criteria.uniqueResult()
}

这不会是一个大事务,这将更新每个 BATCH_SIZE 元素,如果你的 json 真的很大,这就是这样做的方法。另一方面,如果您想进行大量交易,则根本不应该使用刷新。

@Transactional
public void updateDatabase(def myListOfJsonsParsed) {
    myListOfJsonsParsed.each {
        def o = getById(..., it.id)
        o.save()
    }
}

但如果 myListOfJsonsParsed 有很多元素,我不会推荐它。

【讨论】:

  • 是的,btach flush 有帮助。我也将大多数 grails 的 findById 方法替换为 get() 方法。这样,速度显着提高。但是我仍然不明白为什么速度会变慢,为什么它或多或少不是恒定的。
  • 原因是休眠会话一直被填满而没有被清除。这是因为您的工作只使用一个会话,因此您必须手动清除它。如果这是很多控制器调用,这不会成为问题,因为 grails 可以处理每个请求的会话。
  • 我在每批之后都这样做private flushAndEvictList(List&lt;Object&gt; batch) { if(batch.size() &gt; 0) { sessionFactory.getCurrentSession().flush() sessionFactory.getCurrentSession().clear() for(Object o : batch) { sessionFactory.getCurrentSession().evict(o) o.discard() } batch.clear() } } 这还不够吗?内存不是问题,我已监控,可用的内存很多,gc 时间不到线程时间的 5%。
猜你喜欢
  • 2019-02-26
  • 1970-01-01
  • 2020-06-03
  • 2013-05-19
  • 2018-10-11
  • 1970-01-01
  • 2019-08-17
  • 1970-01-01
  • 2014-03-12
相关资源
最近更新 更多