spring-projects / spring-data-relational

Spring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.
https://spring.io/projects/spring-data-jdbc
Apache License 2.0
767 stars 345 forks source link

Spring Data Jdbc findAll low performance for native application #1683

Closed Ferioney closed 11 months ago

Ferioney commented 11 months ago

Hi,

I faced a performance issue with Spring Data Jdbc with native application. It isn't easy to prepare a demo cause I found this issue in the production code. But I will try to explain.

Let's say I have a simple Spring Data repository:

public interface DemoJdbcRepository extends CrudRepository<DemoEntity, Long> {
        @Query("select d.* from demo_table d where d.status in (:statuses)")
        List<DemoEntity> getDemoByStatusId(@Param("statuses") Set<Integer> statuses);
}

In demo_table I have 600,000 records for searched statuses. For Java applications, this method takes an average of 5 sec. When I switched to the native application, this method took more than 100 sec. I prepared a flame graph for native application: perf-native-spring-data

Could you please explain if it is expected behavior for native? and maybe we can improve the performance of Spring Data somehow

odrotbohm commented 11 months ago

TypeAndPath spending so much time in equals(…) looks suspicious, especially the fact that it's apparently using MethodHandles under the covers. TAP is a Java record, which means that we don't control how it's implemented. I'll forward this to the GraalVM folks.

mp911de commented 11 months ago

Here's the related GraalVM issue: https://github.com/oracle/graal/issues/4348

Ferioney commented 11 months ago

thanks for the answers!

I have changed TypeAndPath from record to class:

    static class TypeAndPath {

        private final TypeInformation<?> type;
        private final String path;

        private TypeAndPath(TypeInformation<?> type, String path) {
            this.type = type;
            this.path = path;
        }

        public TypeInformation<?> type() {
            return type;
        }

        public String path() {
            return path;
        }

        public static TypeAndPath of(TypeInformation<?> type, String path) {
            return new TypeAndPath(type, path);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            TypeAndPath that = (TypeAndPath) o;
            return Objects.equals(type, that.type) && Objects.equals(path, that.path);
        }

        @Override
        public int hashCode() {
            return Objects.hash(type, path);
        }
    }

and run test again (the same condition as in first comment). I see a huge performance increase (from 100sed to 11 sec):

I prepared flame graph as well: perf-native-spring-data-tar-class

odrotbohm commented 11 months ago

Thanks for the input, that's very helpful. Can you clarify whether you saw the original decreased performance on a GraalVM for JDK 17 or 21?

Ferioney commented 11 months ago

Originally we found it in 17. Then I tried 21 (I want to try with G1) but results were almost the same

odrotbohm commented 11 months ago

Would you mind giving the latest snapshots (3.1 or 3.2) a try? I've resolved spring-data-commons#2997, which should also solve the bottleneck in EntityCallbackDiscoverer.

Ferioney commented 11 months ago

@odrotbohm thank you so much!

I tried spring-data-commons:3.1.7-SNAPSHOT and got time decrease to ~ 8 sec.

Flame graph: perf-native-data-SNAPSHOT

odrotbohm commented 11 months ago

Alright, I consider this fixed then for now. We're going to continue investigating how to reduce the overhead introduced by the PersistentPropertyPath creation, but that doesn't seem to particularly affect the native scenario.