4

Issue Explanation

I have a big performance issue with my REST API. I can't find any help on Google, so I ask my question here.

I can target the slow query, but it is not due to the query itself, because when I run it directly into Compass, it takes 0ms.

I think the issue is due to Java, when it convert the query result into POJO, but how to improve it?

Project Settings and code

Pom.xml

        <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.1.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com</groupId>
        <artifactId>myproject</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>myproject</name>
        <description>myproject API</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
    
            <dependency>
                <groupId>com.newrelic.agent.java</groupId>
                <artifactId>newrelic-java</artifactId>
                <version>5.12.1</version>
                <scope>provided</scope>
                <type>zip</type>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-mongodb</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-rest</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-mail</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-oauth2-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-core</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>com.google.auth</groupId>
                <artifactId>google-auth-library-oauth2-http</artifactId>
                <version>0.16.1</version>
            </dependency>
            <dependency>
                <groupId>com.google.firebase</groupId>
                <artifactId>firebase-admin</artifactId>
                <version>6.9.0</version>
            </dependency>
            <dependency>
                <groupId>io.swagger</groupId>
                <artifactId>swagger-annotations</artifactId>
                <version>1.5.14</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>4.1.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>4.1.2</version>
            </dependency>
    
            <dependency>
                <groupId>org.passay</groupId>
                <artifactId>passay</artifactId>
                <version>1.6.0</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>3.0.2</version>
                    <executions>
                        <execution>
                            <id>unpack-newrelic</id>
                            <phase>package</phase>
                            <goals>
                                <goal>unpack-dependencies</goal>
                            </goals>
                            <configuration>
                                <includeGroupIds>com.newrelic.agent.java</includeGroupIds>
                                <includeArtifactIds>newrelic-java</includeArtifactIds>
                                <overWriteReleases>false</overWriteReleases>
                                <overWriteSnapshots>false</overWriteSnapshots>
                                <overWriteIfNewer>true</overWriteIfNewer>
                                <outputDirectory>${project.basedir}</outputDirectory>
                                <destFileName>newrelic</destFileName>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>

Item.java

    /*
     *  Copyright (C) 2020
     *  Produced by Wuidev
     */
    package com.myproject.api.model;
    
    import lombok.Data;
    import org.springframework.data.annotation.Id;
    
    @Data
    public class Item {
    
        @Id
        private String id;
    
    }

MenuChoice.java

    /*
     *  Copyright (C) 2020
     *  Produced by Wuidev
     */
    package com.myproject.api.model;
    
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import org.springframework.data.mongodb.core.index.Indexed;
    import org.springframework.data.mongodb.core.mapping.DBRef;
    
    import java.util.List;
    
    @Data
    @EqualsAndHashCode(callSuper = false)
    public class MenuChoice extends Item {
    
    //    private String customerId;
        @DBRef
        private Customer customer;
    
        @Indexed
        @DBRef
        private Menu menu;
    //    private String menuId;
        private List<MenuItemChoice> choices;
        private String comment;
        private String timeSlot;
    
    }

MenuChoiceRepository.java

package com.myproject.api.dao;

import com.myproject.api.model.Menu;
import com.myproject.api.model.MenuChoice;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

/**
 * The interface Menu choice repository.
 */
public interface MenuChoiceRepository extends MongoRepository<MenuChoice, String> {

    
    /**
     * Find all by menu list.
     *
     * @param menu the menu
     * @return the list
     */
    List<MenuChoice> findAllByMenu(Menu menu);

}

DefaultMenuChoiceService


@Component
public class DefaultMenuChoiceService extends AbstractDefaultItemService<MenuChoice> implements MenuChoiceService {

    @Override
    public List<MenuChoice> getChoicesForMenu(final Menu m) {
        final long startTime = System.currentTimeMillis();
        if(m == null) {
            return Collections.emptyList();
        }
        final List<MenuChoice> choices = menuChoiceRepository.findAllByMenu(m);
//        final List<MenuChoice> choices = menuChoiceRepository.findAllByMenu_Id(m.getId());
        LOG.info("getChoicesForMenu total = {}", (System.currentTimeMillis() - startTime));
        return choices;
    }


}

Results

I have set the mongoTemplate logging level to DEBUG, this is the query :

find using query: { "menu" : { "$java" : { "$ref" : "menu", "$id" : "foo" } } } fields: Document{{}} for class: class com.myproject.api.model.MenuChoice in collection: menuChoice

In local env

Total MenuChoices Documents: 22

Number of returned MenuChoice Documents: 1

getChoicesForMenu total = 561

In Prod env

Total MenuChoices Documents: 341

Number of returned MenuChoice Documents: 27

getChoicesForMenu total = 27149

Tried solutions

  1. Add an Index on the menu field in MenuChoice collection
  2. Upgrade Spring boot version
  3. I have tried turning it off and on again

But nothing has changed.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
vwuilbea
  • 43
  • 4

1 Answers1

0

I suspect your problem lies with @DBRef. I never used it but based on the documentation, your query will also retrieve Customers from a different collection, the same goes for Menu (i.e. +2 queries for each MenuChoice, not sure how SpringJPA handles it but since MongoDB is NO-SQL I suspect these queries are distinct).

More details at MongoDB - is DBREF necessary?

The reason why the query works so fast in Compass is that these additional queries are not performed whereas SpringJPA also performs the additional queries so you have a complete POJO.

When it comes to improvements you have the following options:

  1. Use foreign keys for Menu and Customer and retrieve them as required.
  2. Move Customer and Menu into the same collection thus no referencing required.
  3. If you wish to use references then MongoDB is a bad fit, consider using relational DBs like Postgre, MySQL, MSSQL, OracleSQL.
  • arf, I had foreign keys before, as your first solution, but after reading this [post](https://stackoverflow.com/questions/45636064/mongodb-with-java-foreign-key) I have changed to DBRef. But you are probably right, I am too formatted with relational DBs, I need to forget that. Do you know a secured solution to apply your first solution without lost my Prod data ? Maybe with a second field "menuId" (as you can see in comment line), and a script to populate this field ? Thanks a lot for your fast answer ! – vwuilbea Jul 07 '20 at 16:34
  • I think the best approach is to first reintroduce foreign keys I think you need to first update the DAO with an additional setter something like: void setCustomer(Customer in){ this.customer=in; foreginKey=in.getId(); } after deploying this to prod will ensure that data created while you run the script will remain constant. After deploying this version to prod you could run a script to perform cleanup and finally remove the old Members. At least this is how I would tackle it. – Mate Szilard Jul 07 '20 at 16:44