Performance questions

Since many of our clients are quite busy and have lots and lots of data - I thought I should ask about performance.

How is data loaded by the various components of the UI? For example, we’re using a Calendar control for our scheduling - how does it load data? Is it going to try to load all tens of thousands of records, or does it load as it needs to display? Ditto for the browse screens, lookup/picker fields, and so on.

Some of our clients have data back to 1994 and we intend to do a full load for them when the CUBA version is done, so…

Hi @jon.craig,

You always have control over what will be loaded in your application screens. Screen Dataloaders will define how data is loaded (and when). It is not the same for cuba screens and components like generic filter, for example.

I’ll share some tips related to performance based on my experience. It should help you in both cases (your screens and cuba screens and components).

  1. Enable debug for SQL queries for local development (tomcat/conf/logback.xml or app_home/logback.xml file, depending on which version you are using)
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
        ...
    <!-- End CUBA -->
    <logger name="eclipselink" level="WARN"/>
    <logger name="eclipselink.sql" level="DEBUG"/>

After enabling, inspect the screens that you think will need a better performance. That’s the way I started identifying most of the problems related to SQL performance (tips below).

  1. Create specific views for each need, loading only the required data. Avoid using the same view for browse and edit screens, for example. Edit screens almost always needs to load more data than browse screens.

  2. Use Screen instead of Dropdown for selecting entities with a lot of records. If you have already generated the screens, remove the generated collections and change the components in the form from lookupPickerField to pickerField. When using Dropdown, cuba will load all records and filter the results in memory based on user input.

  3. Enable entity statistics. Based on statistics data, cuba defines which UI component to use to select a given entity (screen or dropdown). When dropdown is used (default, when entity cache is not used) all the instances are loaded in memory when the screen is opened. This feature is very useful to control which component is used in generic filter.

  4. Consider using Entity Cache when items 3 and/or 4 is not an option (usability).

  5. Complex screens sometimes need to load a lot of data. Try to load data on demand when possible (when switching tabs, for example).

Hope it helps.

Regards,
Peterson.

4 Likes

Just to make item 6 of Peterson’s advice more clear: lazy tabs are a great way to reduce amount of data loaded to complex edit screens.

1 Like

Hmm - ok. What about the Calendar control? Does it simply load the entire database? If so… I’ll need to figure out a solution for that.

Neither Calendar nor any other UI components load data directly - they display what is loaded in data containers, normally using data loaders. For example, here in the screen XML descriptor you can see the JPQL query that is used to load calendar events. You can restrict the query however you want.

Ok - so how do data containers/data loaders handle data? Do they load in pages, or do they simply load everything in the database all at once? I’m talking about just a normal, Studio-generated screen - will that simply load the entire database, or does it do some kind of paging or similar for performance?

Data loaders just execute the query and also take into account their firstResult / maxResults properties (see XML attributes and corresponding methods of the CollectionLoader interface). These methods are used for paging by the RowsCount component which is the part of tables. So normally your browse screens load only one page at a time.

Calendar doesn’t control loading like table does, so you are right - it may cause performance problems. We will prepare an example of how to control the loader paging properties according to calendar switching events.

I’m thinking perhaps to control loading based on what’s viewed. So in week view, load only that week’s data; day view, only that day; month view, only that month. Hmmm.

Yes, there is no much sense in paging for calendar, we rather should modify the query and its parameters.

Just to follow-up with this, I implemented loading by displayed dates in the scheduler. In day view, it only loads data for that day; week view, the week; month view, the month. It works quite well and was extremely easy to set up.

Thanks for the discussion on this, @knstvk.

Below is the simplest solution for the week view and built-in navigation buttons.

Define the JPQL query with startDate / endDate parameters and the calendar component with navigationButtonsVisible="true":

<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="msg://caption"
        messagesPack="com.company.demo.web.screens">
    <data>
        <collection id="calendarEventsDc" class="com.company.demo.entity.CalendarEvent" view="_local">
            <loader id="calendarEventsDl">
                <query>
                    <![CDATA[select e from demo_CalendarEvent e
                    where e.startDate >= :startDate and e.endDate <= :endDate]]>
                </query>
            </loader>
        </collection>
    </data>
    <layout>
        <calendar id="calendar"
                  captionProperty="caption"
                  dataContainer="calendarEventsDc"
                  descriptionProperty="description"
                  endDateProperty="endDate"
                  height="100%"
                  navigationButtonsVisible="true"
                  startDateProperty="startDate"
                  stylenameProperty="stylename"
                  width="100%"/>
    </layout>
</window>

In the screen controller, modify the range in CalendarBackwardClickEvent / CalendarForwardClickEvent handlers and load events using current calendar range:

@UiController("demo_CalendarScreen")
@UiDescriptor("calendar-screen.xml")
public class CalendarScreen extends Screen {

    @Inject
    private Calendar<Date> calendar;
    @Inject
    private CollectionLoader<CalendarEvent> calendarEventsDl;

    @Subscribe
    public void onBeforeShow(BeforeShowEvent event) {
        loadEvents();
    }

    @Subscribe("calendar")
    public void onCalendarBackwardClick(Calendar.CalendarBackwardClickEvent<Date> event) {
        calendar.setStartDate(DateUtils.addWeeks(calendar.getStartDate(), -1));
        calendar.setEndDate(DateUtils.addWeeks(calendar.getEndDate(), -1));
        loadEvents();
    }

    @Subscribe("calendar")
    public void onCalendarForwardClick(Calendar.CalendarForwardClickEvent<Date> event) {
        calendar.setStartDate(DateUtils.addWeeks(calendar.getStartDate(), 1));
        calendar.setEndDate(DateUtils.addWeeks(calendar.getEndDate(), 1));
        loadEvents();
    }

    private void loadEvents() {
        calendarEventsDl.setParameter("startDate", calendar.getStartDate());
        calendarEventsDl.setParameter("endDate", calendar.getEndDate());
        calendarEventsDl.load();
    }
}
2 Likes

That’s fairly similar to what I did, except I’ve put up custom navigation buttons and such. It works great. :slight_smile:

1 Like