From cc8036529061bd10f68756b65dc6fce3525108b9 Mon Sep 17 00:00:00 2001 From: Mervyn McCreight Date: Wed, 5 Feb 2025 16:10:07 +0100 Subject: [PATCH] Cache document parsing result --- kgraphql/build.gradle.kts | 6 ++++ .../kgraphql/RequestCachingBenchmark.kt | 2 +- .../apurebase/kgraphql/function/Memoize.kt | 5 ++-- .../kgraphql/request/CachingDocumentParser.kt | 30 ------------------- .../com/apurebase/kgraphql/request/Parser.kt | 7 +---- .../kgraphql/schema/DefaultSchema.kt | 23 ++++++++++++-- 6 files changed, 31 insertions(+), 42 deletions(-) delete mode 100644 kgraphql/src/main/kotlin/com/apurebase/kgraphql/request/CachingDocumentParser.kt diff --git a/kgraphql/build.gradle.kts b/kgraphql/build.gradle.kts index 292746ab..12fc16aa 100644 --- a/kgraphql/build.gradle.kts +++ b/kgraphql/build.gradle.kts @@ -24,6 +24,12 @@ benchmark { jmhVersion = "1.37" } } + + configurations { + register("requestCachingBenchmark") { + include("com.apurebase.kgraphql.RequestCachingBenchmark") + } + } } dependencies { diff --git a/kgraphql/src/jvm/kotlin/com/apurebase/kgraphql/RequestCachingBenchmark.kt b/kgraphql/src/jvm/kotlin/com/apurebase/kgraphql/RequestCachingBenchmark.kt index 0327bf28..60a4aba1 100644 --- a/kgraphql/src/jvm/kotlin/com/apurebase/kgraphql/RequestCachingBenchmark.kt +++ b/kgraphql/src/jvm/kotlin/com/apurebase/kgraphql/RequestCachingBenchmark.kt @@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit @State(Scope.Benchmark) @Warmup(iterations = 10) @Measurement(iterations = 5) -@OutputTimeUnit(TimeUnit.MILLISECONDS) +@OutputTimeUnit(TimeUnit.SECONDS) open class RequestCachingBenchmark { @Param("true", "false") diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/function/Memoize.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/function/Memoize.kt index 8e2d2fe9..fbd6b1f8 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/function/Memoize.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/function/Memoize.kt @@ -3,13 +3,12 @@ package com.apurebase.kgraphql.function import com.github.benmanes.caffeine.cache.Caffeine import com.sksamuel.aedile.core.asLoadingCache import kotlinx.coroutines.CoroutineScope -import kotlin.coroutines.EmptyCoroutineContext -fun (suspend (X) -> Y).memoized(scope: CoroutineScope = CoroutineScope(EmptyCoroutineContext), memorySize: Long): suspend (X) -> Y { +internal fun (suspend (X) -> Y).memoized(scope: CoroutineScope, memorySize: Long): suspend (X) -> Y { val cache = Caffeine .newBuilder() .maximumSize(memorySize) .asLoadingCache(scope) { this(it) } - return { cache.get(it) } + return cache::get } diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/request/CachingDocumentParser.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/request/CachingDocumentParser.kt deleted file mode 100644 index aff95e1b..00000000 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/request/CachingDocumentParser.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.apurebase.kgraphql.request - -import com.apurebase.kgraphql.schema.model.ast.DocumentNode -import com.github.benmanes.caffeine.cache.Cache -import com.github.benmanes.caffeine.cache.Caffeine - -class CachingDocumentParser(cacheMaximumSize: Long = 1000L) { - private val cache: Cache = Caffeine.newBuilder().maximumSize(cacheMaximumSize).build() - - fun parseDocument(input: String): DocumentNode { - val result = cache.get(input) { - val parser = Parser(input) - try { - Result.Success(parser.parseDocument()) - } catch (e: Exception) { - Result.Exception(e) - } - } - - return when (result) { - is Result.Success -> result.document - is Result.Exception -> throw result.exception - } - } - - private sealed class Result { - class Success(val document: DocumentNode) : Result() - class Exception(val exception: kotlin.Exception) : Result() - } -} diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/request/Parser.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/request/Parser.kt index cf456dc3..b235d553 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/request/Parser.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/request/Parser.kt @@ -45,7 +45,7 @@ import com.apurebase.kgraphql.schema.model.ast.TypeNode import com.apurebase.kgraphql.schema.model.ast.ValueNode import com.apurebase.kgraphql.schema.model.ast.VariableDefinitionNode -open class Parser { +internal class Parser { private val options: Options private val lexer: Lexer @@ -56,11 +56,6 @@ open class Parser { constructor(source: String, options: Options? = null) : this(Source(source), options) - constructor(lexer: Lexer, options: Options? = null) { - this.options = options ?: Options() - this.lexer = lexer - } - /** * Converts a name lex token into a name parse node. */ diff --git a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt index caf99b98..7b28141e 100644 --- a/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt +++ b/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/DefaultSchema.kt @@ -3,6 +3,7 @@ package com.apurebase.kgraphql.schema import com.apurebase.kgraphql.Context import com.apurebase.kgraphql.ValidationException import com.apurebase.kgraphql.configuration.SchemaConfiguration +import com.apurebase.kgraphql.function.memoized import com.apurebase.kgraphql.request.Introspection import com.apurebase.kgraphql.request.Parser import com.apurebase.kgraphql.request.VariablesJson @@ -14,18 +15,21 @@ import com.apurebase.kgraphql.schema.execution.Executor.Parallel import com.apurebase.kgraphql.schema.execution.ParallelRequestExecutor import com.apurebase.kgraphql.schema.execution.RequestExecutor import com.apurebase.kgraphql.schema.introspection.__Schema +import com.apurebase.kgraphql.schema.model.ast.DocumentNode import com.apurebase.kgraphql.schema.model.ast.NameNode import com.apurebase.kgraphql.schema.structure.LookupSchema import com.apurebase.kgraphql.schema.structure.RequestInterpreter import com.apurebase.kgraphql.schema.structure.SchemaModel import com.apurebase.kgraphql.schema.structure.Type +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope +import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass class DefaultSchema( override val configuration: SchemaConfiguration, internal val model: SchemaModel -) : Schema, __Schema by model, LookupSchema { +) : Schema, __Schema by model, LookupSchema, CoroutineScope { companion object { val OPERATION_NAME_PARAM = NameNode("operationName", null) @@ -40,6 +44,16 @@ class DefaultSchema( private val requestInterpreter: RequestInterpreter = RequestInterpreter(model) + private val parseRequest: (suspend (String) -> DocumentNode) = + if (configuration.useCachingDocumentParser) { + ::parseDocument.memoized( + this, + configuration.documentParserCacheMaximumSize + ) + } else { + ::parseDocument + } + override suspend fun execute( request: String, variables: String?, @@ -55,7 +69,7 @@ class DefaultSchema( ?.let { VariablesJson.Defined(configuration.objectMapper, variables) } ?: VariablesJson.Empty() - val document = Parser(request).parseDocument() + val document = parseRequest(request) val executor = options.executor?.let(this@DefaultSchema::getExecutor) ?: defaultRequestExecutor @@ -73,4 +87,9 @@ class DefaultSchema( override fun inputTypeByKClass(kClass: KClass<*>): Type? = model.inputTypes[kClass] override fun findTypeByName(name: String): Type? = model.allTypesByName[name] + + @Suppress("RedundantSuspendModifier") + private suspend fun parseDocument(input: String): DocumentNode = Parser(input).parseDocument() + + override val coroutineContext: CoroutineContext = configuration.coroutineDispatcher }