Igor Almeida
Igor Almeida

Reputation: 41

Caching for Neo4j user-defined procedures

I am current running a comparative experiment for some algorithms running on top of a relational database (PostgreSQL) and a graph one (Neo4j). I implemented my algorithm as a user-defined procedure for Neo4j, but it doesn't look like it performs any caching out-of-box. Is there a way to configure caching for user-defined procedures in Neo4j?

Thanks

Upvotes: 1

Views: 75

Answers (1)

Frank Pavageau
Frank Pavageau

Reputation: 11715

You'll have to implement the caching yourself, if it's relevant for your use case and you have something to cache: probably something not related to the transaction, so no nodes or relationships; Neo4j ids are tricky, since they can be reused, so it's probably best to only cache them for a short duration, or not at all. Application-level ids would be fine, as would beans composed of strings or scalar types.

Suppose you have this procedure defined:

public class MyProcedure {
    @Context
    public GraphDatabaseService db;

    @Procedure
    public Stream<MyBean> doSomething(@Name("uuid") String uuid) {
         int count = 0;
         // ...
         return Stream.of(new MyBean(count));
    }

    public static class MyBean {
        public int count;

        public MyBean(int count) {
            this.count = count;
        }
    }
}

You can add some simple caching using a ConcurrentMap:

public class MyProcedure {
    private static final ConcurrentMap<String, Collection<MyBean>> CACHE =
            new ConcurrentHashMap<>();

    @Context
    public GraphDatabaseService db;

    @Procedure
    public Stream<MyBean> doSomething(@Name("uuid") String uuid) {
         Collection<MyBean> result = CACHE.computeIfAbsent(uuid,
                 k -> doSomethingCacheable(k).collect(Collectors.toList()));
         return result.stream();
    }

    private Stream<MyBean> doSomethingCacheable(String uuid) {
         int count = 0;
         // ...
         return Stream.of(new MyBean(count));
    }

    public static class MyBean {
        // ...
    }
}

Note that you can't cache a Stream as it can only be consumed once, so you have to consume it yourself by collecting into an ArrayList (you can also move the collect inside the method, change the return type to Collection<MyBean> and use a method reference). If the procedure takes more than one argument, you'll need to create a proper class for the composite key (immutable if possible, with correct equals and hashCode implementation). The restrictions that apply to cacheable values also apply to the keys.

This is an eternal, unbounded cache. If you need more features (expiration, maximum size), I suggest you use a real cache implementation, such as Guava's Cache (or LoadingCache) or Ben Manes' Caffeine.

Upvotes: 1

Related Questions