'How can I further lower TTFB for my Spring application webpages running on Google App Engine?

I am trying to improve the speed at which pages are loading on my site and it seems the issue is with the TTFB. The backend is using Java with Spring framework.

Most of the pages on the website have a TTFB of >1000ms, this time also fluctuates and occasionally will as much as 5000ms.

I have made database indexes for all of the commonly used SQL queries in the back end as well upgrading the instance class running on Google App Engine. This has had a positive impact but the overall performance is still lacking. (The server hosting is in the nearest location to me)

Additional I observed that the TTFB is roughly 200ms longer than the time it takes for the code to run and return the model in the controller. In some cases there are a lot of attributes being added to the model so it's possible html building is taking considerable time but I wonder if there is an issue with how Spring is configured.

From what I have read online a TTFB of more than 600ms is consider sub optimal so I think there is room for more improvement. Are there any known performance issues with Spring on Google App Engine which I should explore further? Do you have suggestion for anything else I should try?

Below is the code from one of the slower controllers in cases that helps in identify any obvious inefficiencies.

Thanks for any help!

    public String requestPrivateCompanyProfile(Model model,
                                           @PathVariable String companyName,
                                          @RequestParam(required = false) String alertboxMessage,
                                           @RequestParam(required = false) String openSubmenu) {

        User user = sessionService.getUser();
        Ecosystem ecosystem = sessionService.getEcosystem();

        model.addAttribute("user", user);
        model.addAttribute("ecosystem", ecosystem);
        model.addAttribute("canSeeAdminButton", accessLevelService.isUserHasManagmentAccessLevel(ecosystem, user));
        model.addAttribute("userPic", cloudStorageService.getProfilePic(user));
        model.addAttribute("ecoNavLogo", cloudStorageService.getEcosystemLogo(ecosystem.getName(), true));
        model.addAttribute("defaultCompanyLogo",cloudStorageService.getDefaultCompanyLogo());

        // Pass on the alertboxMessage and openSubmenu parameters, if there are any
        if (alertboxMessage != null) model.addAttribute("alertboxMessage", alertboxMessage);
        if (openSubmenu != null) model.addAttribute("openSubmenu", openSubmenu);

        // If user is not registered in the current ecosystem, redirect to index
        if (ecoUserDataRepository.findByKeyEcosystemAndKeyUser(ecosystem, user) == null) return "redirect:/";

        // If the company doesn't exist, redirect to /companies
        Company company = companyRepository.findByEcosystemAndName(ecosystem, companyName);
        if (company == null) return "redirect:/companies";

        //checks the user has permission to view this page
        if (!accessLevelService.isCanSeePrivateCompanyProfile(ecosystem, company, user)) return "redirect:/companies";

        // Store the company user data along with the profile pics of each employee
        List<CompanyProfileEmployeeDto> employeeDtos = new ArrayList<>();
        List<CompanyUserData> cuds = companyUserDataRepository.findAllByKeyCompany(company);
        for (CompanyUserData cud : cuds) {
            User employee = cud.getKey().getUser();
            String profPic = cloudStorageService.getProfilePic(employee);

            if (employee == company.getOwner()) {
                employeeDtos.add(0, new CompanyProfileEmployeeDto(cud, profPic));
            } else {
                employeeDtos.add(new CompanyProfileEmployeeDto(cud, profPic));
            }
        }

        //Get all the ecosystem users to enable search for new team members
        EcoUserData[] ecoUserData = ecoUserDataRepository.findAllEcoUsers(ecosystem).toArray(new EcoUserData[0]);

        //Get tags used by the company
        String ecoSystemTags = ecosystem.getEcosystemTags() == null ? "" : ecosystem.getEcosystemTags();
        String companyTagsString = company.getTags() == null ? "" : company.getTags();
        String[] companyTags = company.getTags() == null ? null : company.getTags().replace("#", "").split(";");
        List<String> unusedTags = TagsService.getUnusedTags(companyTagsString, ecoSystemTags);

        //get goals and order them for the private company
        List<CompanyGoal> companyGoalAchieved = companyGoalRepository.findCompanyGoalByAchievedAndCompany(false, company);
        List<CompanyGoal> companyNotGoalAchieved = companyGoalRepository.findCompanyGoalByAchievedAndCompany(true, company);
        Collections.reverse(companyGoalAchieved);
        Collections.reverse(companyNotGoalAchieved);
        //this makes achieved goals the first in the list
        List<CompanyGoal> companyGoalsOrdered = new ArrayList<>(companyGoalAchieved);
        companyGoalsOrdered.addAll(companyNotGoalAchieved);

        //get the shared docs for a specific private company
        List<CompanySharedDocs> companySharedDocs = companySharedDocsRepository.findCompanySharedDocsByCompanyAndDocType(company, "shared");

        //get the admin documents for a specific private company
        List<CompanySharedDocs> companyAdminDocs = companySharedDocsRepository.findCompanySharedDocsByCompanyAndDocType(company, "admin");

        //get the admin documents for a specific private company
        List<CompanySharedDocs> resourceLinks = companySharedDocsRepository.findCompanySharedDocsByCompanyAndDocType(company, "resource");

        //gets shared notes for the company sorts the list so the recently edited notes are first in the list
        List<CompanySharedNote> companySharedNotes = companySharedNoteRepository.findCompanySharedNoteByCompany(company);
        companySharedNotes.sort(Comparator.comparing(CompanySharedNote::getLastEdited).reversed());

        //finds all kpi associated with a company and saves them into a list, each kpi in the list then has its kpiEntries sorted by date
        List<KeyPerformanceIndicator> companyKpis = keyPerformanceIndicatorRepository.findAllByCompany(company);
        Collections.reverse(companyKpis);
        for (KeyPerformanceIndicator kpi: companyKpis){
            kpi.sortKpiEntriesByDate();
        }

        //checks if the user just created a new kpi and if they did add attribute to change frontend display
        model.addAttribute("showKpiTab", sessionService.getAttribute("showKpiTab"));
        sessionService.removeAttribute("showKpiTab");

        model.addAttribute("companyKpis", companyKpis);
        model.addAttribute("resourceLinks", resourceLinks);
        model.addAttribute("isOwner", company.getOwner() == user);
        model.addAttribute("canDeleteCompanyProfile", accessLevelService.isDeleteCompanyProfile(ecosystem, company, user));
        model.addAttribute("canSeePrivateCompanyProfile", true);
        model.addAttribute("canEditCompanyProfile", accessLevelService.isCanEditCompanyProfile(ecosystem, company, user));
        model.addAttribute("companyAdminDocs", companyAdminDocs);
        model.addAttribute("companySharedNotes", companySharedNotes);
        model.addAttribute("companyGoals", companyGoalsOrdered);
        model.addAttribute("companySharedDocs", companySharedDocs);
        model.addAttribute("company", company);
        model.addAttribute("empDtos", employeeDtos);
        model.addAttribute("ecoUserData", ecoUserData);
        model.addAttribute("companyTags", companyTags);
        model.addAttribute("unusedTags", unusedTags);
        model.addAttribute("canUploadAdminDocs", ecoUserDataRepository.findByKeyEcosystemAndKeyUser(ecosystem, user).getAccessLevels().isCanUploadAdminDocs());
        model.addAttribute("companyNameList", companyUserDataRepository.findAllCompanyNameByUserAndEcosystem(user, ecosystem));

        return "ecosystem/company-profile/private-company-profile";
    }


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source