java - Spring Boot,GraphQL性能泄漏

标签 java kotlin graphql

给出的是一个 SpringBoot 应用程序,该应用程序托管一个 graphQL服务器。它使用了许多DataLoaders,它们似乎表现良好,但是我注意到非常大的性能泄漏与(或介于两者之间)有关,我无法明确缩小范围,但可以对其进行衡量。
问题

  • 某些(UI)客户端从服务中调用graphQL API的查询,这将触发对x元素(x > 10_000)的获取。
  • GraphQLQueryResolver在SpringBoot服务中调用,该服务获取x元素。
  • 调用字段 getter 的函数,这些函数从加载的CompletionStage<T>返回DataLoader<K,T>
  • 需要很长时间的东西
  • 用批处理键调用
  • DataLoader<K,T>实现类,并返回结果。

  • 示例日志如下所示:
    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)
    
    日志情况的说明:
  • 18:25:14.196 :调用“带过滤器的订单查询”并在95ms#10476元素中返回
  • + 4.49 S :其所有字段“orderKpi”均从其DataLoader返回。创建和返回它们花费了1ms。
  • + 4.54 :“orderKpi”的“priceForOrderReferences”字段已通过其自己的DataLoader加载。创建并返回它们花费了1.613S。

  • 解决问题
  • 在两次调用DataLoader的时间之间会发生什么? (那些+ 4.49S和+ 4.54S)
  • 如何减少它?

  • 我所做的事情和我的假设
  • 我试图使DataLoaders单例,因为我认为对象创建需要花费很多时间-这不是问题。
  • 我试图通过覆盖批大小来引入手动批处理-它减少了一些时间,但是增加了整体运行时间(各种批处理大小对于各种元素大小而言都不同)
  • 因为批处理减少了时间(非常少),所以我认为性能泄漏来自“收集”元素Keys以使其成为List并将它们传递给DataLoader。
    我还手动测量了每个项目的平均性能泄漏时间,每个元素似乎总是〜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库本身就是问题,而不是DataLoader。在一种情况下,我删除了一个字段的DataLoader-直到调用它花了相同的时间!
    统计资料
    Spring
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    
    我的graphQL依赖项
    <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) }
            }
    }
    

    最佳答案

    我自己的解决方案:

    What happens in the time between DataLoaders are being called? (those +4.49S & +4.54S)


    我仍然不确定到底是什么使它变慢,但这似乎是graphql- java 依赖性的问题。在graphql java-script 实现中执行类似查询时,时间延迟约为60ms。

    How can I reduce it?


  • 我找到了good article concerning graphQL performance,在那里我建议提前在顶层加载更多数据。这样,还可以创建自定义类,以容纳更多数据,并可以减少较低级别上的冗余存储库路径。
  • 为避免在执行功能之前等待DataLoader进行“收集”键的时间,您可以选择look-ahead的某些路径并亲自执行加载器-在更高级别上。
  • 播放不同DataLoader的批处理大小

  • 如果您要简化答案,则可能是:“如果您想变得更快,请不要深入”。

    我仍然愿意接受其他解决方案。

    关于java - Spring Boot,GraphQL性能泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62475354/

    相关文章:

    java - 如何设置文本末尾的插入符号位置?

    import - Kotlin.math 包未导入

    java - 使用 Java 实现 Shopify 的 GraphQL

    graphql - 如何在 insomnia.rest 中发送带有 Graphql 突变的文件?

    equals - 我可以将运算符添加到现有的类中吗?

    java - Vert.x - 使用 DataInputStreams 的 GraphQL 订阅

    java - 如何删除索引左侧的所有元素?

    java - 使用通配符创建新的通用对象

    java - 从 JNI 代码检查 Java 类中是否存在可选字段或方法

    android - callback.onResult() 没有在 android 的 PageKeyedDataSource<Int, Item>() 中向适配器返回值