当前位置: 首页 > 知识库问答 >
问题:

SpringBoot、GraphQL性能泄漏

田骁
2023-03-14

给出了一个SpringBoot应用程序,它托管一个graphQL服务器。它使用了许多数据加载器,它们本身的性能似乎很好,但我注意到与它们相关(或介于两者之间)的一个非常大的性能泄漏,我无法清楚地缩小范围,但可以测量它。

  1. 一些(UI)客户端从服务调用graphQL API查询,这将触发提取元素(x

示例日志如下所示:

2020-06-19T18:25:14.196Z [http-nio-80-exec-10] ~ Shopping ~ INFO  ~ It took |> PT0.095S <| for 'orders query with filter:[OrderFilter(col=createdAt, operator=BETWEEN, value=2020-01-05T00:00:00Z AND 2020-05-31T00:00:00Z)]'
2020-06-19T18:25:18.686Z [DefaultDispatcher-worker-6] ~ Shopping ~ INFO  ~ It took |> PT0.001S <| for 'orderKpiDataLoader' (PT0.000000095S on average for #10476 executions)
2020-06-19T18:25:23.229Z [DefaultDispatcher-worker-19] ~ Shopping ~ INFO  ~ Start 'priceForOrderReferences'
2020-06-19T18:25:24.840Z [DefaultDispatcher-worker-41] ~ Shopping ~ WARN  ~ It took |> PT1.613S <| for 'orderDepositDataLoader' (PT0.00015397S on average for #10476 executions)

日志的情况说明:

  1. 18:25:14.196:调用带有过滤器的订单查询并返回95ms#10476元素
  2. 4.49 S:它的所有字段“orderKpi”都从它们的DataLoader返回。创建和返回它们需要1ms。
  3. 4.54 S:“orderKpi”的字段“priceForOrderReena”加载到它自己的DataLoader上。创建和返回它们需要1.613S。
  • 调用数据加载器之间的时间间隔会发生什么?(4.49秒
  • 我尝试将数据加载器设置为单例,因为我认为创建对象需要很多时间——这不是问题所在
  • 我试图通过覆盖批处理大小来引入手动批处理-它稍微减少了时间,但增加了总体运行时间(不同的批处理大小对不同的元素大小起不同的作用)
  • 由于批处理减少了时间(很少),我假设性能泄漏来自于“收集”元素键以生成列表并将其传递给数据加载器。我还手动测量了每个项目的平均性能泄漏时间,每个元素似乎总是约1ms。我没有发现任何代码部分是如何收集的,也没有找到如何自己重写它的

新信息:

  • 我尝试了不同的数据加载器库,例如:
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-kickstart-spring-boot-starter-tools</artifactId>
    <version>7.0.1</version>
</dependency>

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>6.0.2</version>
</dependency>

并且还禁用了缓存-仍然是相同的结果。我放了更多的日志(当然要小心,这样它们本身不会降低性能),我的新线索是,graphql库本身就是问题所在,而不是数据加载器。在一个例子中,我删除了一个字段的数据加载器——在调用它之前花费了相同的时间!

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>7.0.1</version>
</dependency>
<dependency>
    <groupId>com.apollographql.federation</groupId>
    <artifactId>federation-graphql-java-support</artifactId>
    <version>0.4.1</version>
</dependency>
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>playground-spring-boot-starter</artifactId>
    <version>7.0.1</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-extended-scalars</artifactId>
    <version>1.0.1</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>java-dataloader</artifactId>
    <version>2.2.3</version>
</dependency>

所有DataLoader都实现了这个抽象:

import com.smark.shopping.utils.FunctionPerformance
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.future.future
import org.dataloader.DataLoader

private val dataLoaderSingleScopeIO = CoroutineScope(Dispatchers.IO)

interface DataLoaderEntryInterface<T, R> {
    val key: String
    val dataLoader: DataLoader<T, R>
}

abstract class DataLoaderEntryAbstract<T, R>(
    override val key: String,
    private val loader: suspend (List<T>) -> List<R>
) : DataLoaderEntryInterface<T, R> {

    private val performance = FunctionPerformance()

    override val dataLoader: DataLoader<T, R>
        get() = DataLoader.newDataLoader { ids ->
            dataLoaderSingleScopeIO.future {
                performance.executeMeasuringSuspend(key, ids.size) { loader(ids) }
            }
        }
}

@Component
class DataLoaderOrderDeposit(private val orderTotalPriceService: OrderTotalPriceService) : DataLoaderEntryAbstract<Int, Int>(
    key = ORDER_DEPOSIT_DATA_LOADER,
    loader = { orderReferences -> orderTotalPriceService.priceForOrderReferences(orderReferences).map { it.deposit } }
)
@Component
class OrderResolver : GraphQLResolver<ShopOrder> {

     fun kpi(shopOrder: ShopOrder, dfe: DataFetchingEnvironment): CompletionStage<OrderKpi> =
        DataLoaderFuture<OrderKpi>(dfe, ORDER_KPI_DATA_LOADER).loadBy(shopOrder)
}

@Component
class OrderKpiResolver : GraphQLResolver<OrderKpi> {

    fun deposit(orderKpi: OrderKpi, dfe: DataFetchingEnvironment): CompletionStage<Int> =
        dfe.getDataLoader<Int, Int>(ORDER_DEPOSIT_DATA_LOADER).load(orderKpi.orderReference)
}
@Component
class CustomGraphQLContextBuilder(
    private val dataLoadersSummelsarium: DataLoadersSummelsarium
) : GraphQLServletContextBuilder {

    override fun build(req: HttpServletRequest, response: HttpServletResponse): GraphQLContext =
        DefaultGraphQLServletContext.createServletContext(buildDataLoaderRegistry(), null)
                .with(req)
                .with(response)
                .build()

    override fun build(session: Session, request: HandshakeRequest): GraphQLContext =
        DefaultGraphQLWebSocketContext.createWebSocketContext(buildDataLoaderRegistry(), null)
                .with(session)
                .with(request)
                .build()

    override fun build(): GraphQLContext = DefaultGraphQLContext(buildDataLoaderRegistry(), null)

    private fun buildDataLoaderRegistry(): DataLoaderRegistry =
        DataLoaderRegistry().apply {
            dataLoadersSummelsarium.dataLoaders().forEach { register(it.key, it.dataLoader) }
        }
}

共有1个答案

戚星腾
2023-03-14

我自己的解决方案:

调用DataLoaders之间的时间会发生什么?(那些4.49S

我仍然不确定到底是什么减慢了速度,但这似乎是graphql java依赖性的问题。在graphql java脚本实现中执行类似查询时,延迟约为60ms。

我怎样才能减少它?

  1. 我找到了一篇关于graphQL性能的好文章,在这篇文章中,我采纳了在顶层提前加载更多数据的建议。通过这种方式,还可以创建包含更多数据的自定义类,并可以减少较低级别上的冗余存储库路径
  2. 为了避免数据加载器在执行函数之前等待“收集”密钥,您可以选择某些路径来展望未来,并自己在更高级别上执行加载器
  3. 围绕不同数据加载程序的批处理大小进行游戏

如果你想简化答案,可以是:“如果你想快速,不要深入”。

我仍然对其他解决方案持开放态度。

 类似资料:
  • JMeter可以在java应用程序中检测线程泄漏吗?也就是说,可以通过JVisualvm观察线程泄漏,但是JMeter没有任何插件等可以检测Java应用程序中的线程泄漏。如果没有,那么有没有其他性能测试工具可以检测java应用程序中的线程泄漏,为什么Jeter不能这样做?

  • 本文向大家介绍详解Android性能优化之内存泄漏,包括了详解Android性能优化之内存泄漏的使用技巧和注意事项,需要的朋友参考一下 综述 内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存。那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么

  • 我在spring引导应用程序中面临内存泄漏,在使用Eclipse MAT进行堆转储分析之后,它指出了spring boot执行器中潜在的内存:精确地说是使用InMemoryAuditEventRepository(审计事件)。 禁用此内存审核事件的最佳方法是什么: 删除spring-boot-acturtor依赖项 management.endpoints.enabled-by-default=t

  • 我正在尝试运行这个简单的类,并且对于每个cicle,我正在计算java进程线程的数量。 ps huH p pid wc-l。 对于每个cicle,线程数是availableProcessors()数的增加。 当ExecutorService在其他线程中运行时,garbace收集器似乎不会释放僵尸线程。 如果我创建了一个静态ExecutorService,它就不会发生

  • 使用REST API和Spring启动Web测试客户端,我可以轻松地从返回的JSON中取回解析的对象,如下所示: 使用graphql,json被包装在如下数据属性中: 所以它不能和 因为JSON解析器以“数据”而不是“data.person”开头。 如何实现直接解析JSON结果并返回Person对象?

  • 本文向大家介绍Android性能优化之利用Rxlifecycle解决RxJava内存泄漏详解,包括了Android性能优化之利用Rxlifecycle解决RxJava内存泄漏详解的使用技巧和注意事项,需要的朋友参考一下 前言: 其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所