Skip to content

Commit

Permalink
⚡ GraphAppsUpdater updates gql app cache entries only if app def docu…
Browse files Browse the repository at this point in the history
…ment is actually updated. Cache entries are not all invalidated on delete /gql-apps requests since GraphAppsUpdater will remove deleted apps
  • Loading branch information
ujibang committed Aug 27, 2024
1 parent b6a40c2 commit 4a1677f
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ private String appURI(HttpServerExchange exchange) {
}

private GraphQLApp gqlApp(String appURI) throws GraphQLAppDefNotFoundException, GraphQLIllegalAppDefinitionException {
return AppDefinitionLoadingCache.get(appURI);
return AppDefinitionLoadingCache.getLoading(appURI);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package org.restheart.graphql.cache;

import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.restheart.graphql.GraphQLAppDefNotFoundException;
import org.restheart.graphql.GraphQLIllegalAppDefinitionException;
import org.restheart.graphql.models.GraphQLApp;
Expand Down Expand Up @@ -49,7 +50,34 @@ public static void setup(String _db, String _collection, MongoClient mclient){
mongoClient = mclient;
}

public static GraphQLApp loadAppDefinition(String appURI) throws GraphQLIllegalAppDefinitionException, GraphQLAppDefNotFoundException {
/**
* @param appURI
* @param etag the etag of cached gql app
* @return true if the app definition appURI has been updated, i.e. has a different _etag
*/
public static boolean isUpdated(String appURI, BsonValue etag) {
var uriOrNameCond = array()
.add(document().put(APP_URI_FIELD, appURI))
.add(document().put(APP_NAME_FIELD, appURI));

var conditions = array()
.add(document().put("$or", uriOrNameCond))
.add(document().put(APP_ENABLED_FIELD, true));

var findArg = document().put("$and", conditions);

var gqlApp = mongoClient.getDatabase(appDB).getCollection(appCollection, BsonDocument.class).find(findArg.get()).first();

if (gqlApp != null) {
var newEtag = gqlApp.get("_etag");
LOGGER.trace("oldEtag {}, newEtag {}", etag, newEtag);
return etag == null || newEtag == null || !etag.equals(newEtag);
} else {
return true; // app has been deleted
}
}

public static GraphQLApp load(String appURI) throws GraphQLIllegalAppDefinitionException, GraphQLAppDefNotFoundException {
LOGGER.trace("Loading GQL App Definition {} from db", appURI);

var uriOrNameCond = array()
Expand All @@ -62,10 +90,10 @@ public static GraphQLApp loadAppDefinition(String appURI) throws GraphQLIllegalA

var findArg = document().put("$and", conditions);

var appDefinition = mongoClient.getDatabase(appDB).getCollection(appCollection, BsonDocument.class).find(findArg.get()).first();
var gqlApp = mongoClient.getDatabase(appDB).getCollection(appCollection, BsonDocument.class).find(findArg.get()).first();

if (appDefinition != null) {
return AppBuilder.build(appDefinition);
if (gqlApp != null) {
return AppBuilder.build(gqlApp);
} else {
throw new GraphQLAppDefNotFoundException("GQL App Definition for uri " + appURI + " not found. ");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class AppDefinitionLoadingCache implements Provider<LoadingCache> {
private static final LoadingCache<String, GraphQLApp> CACHE = CacheFactory.createLocalLoadingCache(MAX_CACHE_SIZE, Cache.EXPIRE_POLICY.NEVER, 0,
appURI -> {
try {
return AppDefinitionLoader.loadAppDefinition(appURI);
return AppDefinitionLoader.load(appURI);
} catch (GraphQLAppDefNotFoundException e) {
return null;
} catch (GraphQLIllegalAppDefinitionException e) {
Expand All @@ -51,7 +51,7 @@ public static LoadingCache<String, GraphQLApp> getCache() {
return CACHE;
}

public static GraphQLApp get(String appURI) throws GraphQLAppDefNotFoundException, GraphQLIllegalAppDefinitionException {
public static GraphQLApp getLoading(String appURI) throws GraphQLAppDefNotFoundException, GraphQLIllegalAppDefinitionException {
var _app = CACHE.get(appURI);

if (_app != null && _app.isPresent()){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/
package org.restheart.graphql.initializers;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -72,12 +73,17 @@ public void init() {
}

private void revalidateCacheEntries() {
this.gqlAppDefCache.asMap().entrySet().stream()
// work on a copy of the synchronized cache map, to avoid blocking it
final var _cacheMap = new HashMap<>(this.gqlAppDefCache.asMap());

_cacheMap.entrySet().stream()
.filter(entry -> entry.getValue().isPresent())
// filter out not updated documents (same etag than cached entry)
.filter(entry -> AppDefinitionLoader.isUpdated(entry.getKey(), entry.getValue().get().getEtag()))
.map(entry -> entry.getKey())
.forEach(appUri -> {
try {
var appDef = AppDefinitionLoader.loadAppDefinition(appUri);
var appDef = AppDefinitionLoader.load(appUri);
this.gqlAppDefCache.put(appUri, appDef);
LOGGER.debug("gql cache entry {} updated", appUri);
} catch (GraphQLAppDefNotFoundException e) {
Expand All @@ -86,7 +92,7 @@ private void revalidateCacheEntries() {
} catch (GraphQLIllegalAppDefinitionException e) {
this.gqlAppDefCache.invalidate(appUri);
LOGGER.warn("gql cache entry {} removed {} due to illegal definition", appUri, e);
} catch (Exception e) {
} catch (Throwable e) {
this.gqlAppDefCache.invalidate(appUri);
LOGGER.warn("error updaring gql cache entry {}", appUri, e);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,27 @@
*/
package org.restheart.graphql.interceptors;

import java.util.Map;

import org.restheart.configuration.Configuration;
import org.restheart.configuration.ConfigurationException;
import org.restheart.exchange.MongoRequest;
import org.restheart.exchange.MongoResponse;
import org.restheart.graphql.GraphQLIllegalAppDefinitionException;
import org.restheart.graphql.GraphQLService;
import org.restheart.graphql.cache.AppDefinitionLoadingCache;
import org.restheart.graphql.models.builder.AppBuilder;
import org.restheart.plugins.Inject;
import static org.restheart.plugins.InterceptPoint.RESPONSE;
import org.restheart.plugins.MongoInterceptor;
import org.restheart.plugins.OnInit;
import org.restheart.plugins.RegisterPlugin;
import org.restheart.utils.HttpStatus;

import java.util.Map;

import org.bson.BsonDocument;
import org.restheart.graphql.cache.AppDefinitionLoader;
import org.restheart.graphql.cache.AppDefinitionLoadingCache;

import com.mongodb.client.MongoClient;

import static org.restheart.plugins.InterceptPoint.RESPONSE;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mongodb.client.MongoClient;

@RegisterPlugin(name="graphAppDefinitionPatchChecker",
description = "checks GraphQL application definitions on PATCH requests",
interceptPoint = RESPONSE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,23 @@
*/
package org.restheart.graphql.interceptors;

import java.util.Map;

import org.restheart.configuration.Configuration;
import org.restheart.configuration.ConfigurationException;
import org.restheart.exchange.MongoRequest;
import org.restheart.exchange.MongoResponse;
import org.restheart.graphql.GraphQLIllegalAppDefinitionException;
import org.restheart.graphql.GraphQLService;
import org.restheart.graphql.cache.AppDefinitionLoadingCache;
import org.restheart.graphql.models.builder.AppBuilder;
import org.restheart.plugins.Inject;
import static org.restheart.plugins.InterceptPoint.REQUEST_AFTER_AUTH;
import org.restheart.plugins.MongoInterceptor;
import org.restheart.plugins.OnInit;
import org.restheart.plugins.RegisterPlugin;
import org.restheart.utils.BsonUtils;
import org.restheart.utils.HttpStatus;

import java.util.Map;

import org.restheart.graphql.cache.AppDefinitionLoadingCache;
import static org.restheart.plugins.InterceptPoint.REQUEST_AFTER_AUTH;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down
20 changes: 18 additions & 2 deletions graphql/src/main/java/org/restheart/graphql/models/GraphQLApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class GraphQLApp {
private String schema;
private Map<String, TypeMapping> objectsMappings;
private GraphQLSchema executableSchema;
private BsonValue etag;

public static Builder newBuilder() {
return new Builder();
Expand All @@ -57,11 +58,12 @@ public static Builder newBuilder() {
public GraphQLApp() {
}

public GraphQLApp(AppDescriptor descriptor, String schema, Map<String, TypeMapping> objectsMappings, GraphQLSchema executableSchema) {
public GraphQLApp(AppDescriptor descriptor, String schema, Map<String, TypeMapping> objectsMappings, GraphQLSchema executableSchema, BsonValue etag) {
this.descriptor = descriptor;
this.schema = schema;
this.objectsMappings = objectsMappings;
this.executableSchema = executableSchema;
this.etag = etag;
}

public AppDescriptor getDescriptor() {
Expand Down Expand Up @@ -96,13 +98,22 @@ public void setExecutableSchema(GraphQLSchema executableSchema) {
this.executableSchema = executableSchema;
}

public BsonValue getEtag() {
return this.etag;
}

public void setEtag(BsonValue etag) {
this.etag = etag;
}

public static class Builder {
private AppDescriptor descriptor;
private String schema;
private Map<String, TypeMapping> objectsMappings;
private Map<String, Map<String, Object>> enumsMappings;
private Map<String, Map<String, Predicate>> unionMappings;
private Map<String, Map<String, Predicate>> interfacesMappings;
private BsonValue etag;

private Builder() {
}
Expand Down Expand Up @@ -137,6 +148,11 @@ public Builder interfacesMappings(Map<String, Map<String, Predicate>> mappings)
return this;
}

public Builder etag(BsonValue etag) {
this.etag = etag;
return this;
}

public GraphQLApp build() throws IllegalStateException {
if (this.descriptor == null) {
throw new IllegalStateException("app descriptor must be not null!");
Expand Down Expand Up @@ -232,7 +248,7 @@ public GraphQLApp build() throws IllegalStateException {

var execSchema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);

return new GraphQLApp(this.descriptor, this.schema, this.objectsMappings, execSchema);
return new GraphQLApp(this.descriptor, this.schema, this.objectsMappings, execSchema, this.etag);
} catch (SchemaProblem schemaProblem) {
var errorMSg = schemaProblem.getMessage() != null
? "Invalid GraphQL schema: " + schemaProblem.getMessage()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import org.bson.BsonDocument;
import org.bson.BsonInvalidOperationException;
import org.bson.BsonNull;
import org.restheart.graphql.GraphQLIllegalAppDefinitionException;
import org.restheart.graphql.models.AppDescriptor;
import org.restheart.graphql.models.GraphQLApp;
Expand Down Expand Up @@ -72,8 +73,6 @@ public static final GraphQLApp build(BsonDocument appDef) throws GraphQLIllegalA
throw new GraphQLIllegalAppDefinitionException(errorMSg, schemaProblem);
}



if (appDef.containsKey("mappings")) {
if (appDef.get("mappings").isDocument()) {
var mappings = appDef.getDocument("mappings");
Expand Down Expand Up @@ -106,11 +105,13 @@ public static final GraphQLApp build(BsonDocument appDef) throws GraphQLIllegalA
});

try {
return GraphQLApp.newBuilder().appDescriptor(descriptor).schema(schema)
return GraphQLApp.newBuilder().appDescriptor(descriptor)
.schema(schema)
.objectsMappings(objectsMappings)
.enumsMappings(enumsMappings)
.unionMappings(unionsMappings)
.interfacesMappings(interfacesMappings)
.etag(appDef.get("_etag", BsonNull.VALUE))
.build();
} catch (IllegalStateException | IllegalArgumentException e) {
throw new GraphQLIllegalAppDefinitionException(e.getMessage(), e);
Expand Down

0 comments on commit 4a1677f

Please sign in to comment.