2

Vaadin 14. CallbackDataProvider. When service is slow and answer time is long then Grid connected to CallbackDataProvider is freezes with all UI. Some example:

import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.provider.CallbackDataProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteAlias;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

public class GridWithSortableColumnsView extends VerticalLayout {

    public GridWithSortableColumnsView() {
        addClassName("dialogwithgrid-view");
        setSizeFull();

        // prepare Data
        List<GridRow> gridRows = new LinkedList<>();
        for (int i = 0; i < 10000; i++) {
            gridRows.add(new GridRow(i, RandomStringUtils.randomAlphanumeric(10)));
        }

        CallbackDataProvider<GridRow, Void> dataProvider = new CallbackDataProvider<>(
                query -> {

                    // Slow service answer is here
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    Notification.show("Offset=" + query.getOffset() + " Limit=" + query.getLimit());
                    return gridRows.stream()
                            .sorted((o1, o2) -> StringUtils.compare(o1.getCol05(), o2.getCol05()))
                            .skip(query.getOffset())
                            .limit(query.getLimit());
                },
                query -> gridRows.size());

        Grid<GridRow> grid = new Grid<>();
        grid.setPageSize(10);
        grid.setSizeFull();
        grid.addColumn(GridRow::getId).setHeader("Id");
        grid.addColumn(GridRow::getCol05).setHeader("col05");
        grid.setDataProvider(dataProvider);

        add(grid);
    }


    @EqualsAndHashCode(onlyExplicitlyIncluded = true)
    @AllArgsConstructor
    @Getter
    @Setter
    public static class GridRow {
        @EqualsAndHashCode.Include
        private int id;
        private String col05;
    }

}

Session locked and all UI components freezes while scrolling this Grid. How it can be more faster and async? I think about caching inside own DataProvider extends CallbackDataProvider, but it will filled after the Grid receive empty data on first request and may be used only after some time with refreshing DataProvider again. It is not useful for me. In my real project fetched data changes frequently and has amount of items, up to 100000 items for one Grid, so i need to use CallbackDataProvider with limited request size.

lanmaster
  • 330
  • 2
  • 16
  • The question is: why is your service slow? – Simon Martinelli Nov 12 '21 at 13:51
  • It is a database queries. Sometimes it is slow and there is no possibility to speed up it. Database is external project. – lanmaster Nov 13 '21 at 12:18
  • Maybe i will try to inject some async callback inside own DataProvider. But there is situation when at first Grid data request it receive Stream with empty entities and after getting correct data to cache i will manually invoke refresh on same offset and limit for in-cached data. Twice refresh will occur. – lanmaster Nov 13 '21 at 12:27

2 Answers2

2

Vaadin 23:

In Vaadin 23 there is now a new feature, which allows you to define executor for DataCommunicator to use for asynchronous operation with Grid. If the value is null, DataCommunicator will operate synchronously and this is the default. If executor is defined, the fetch callback is run by executor and Grid is updated using UI#access(..). This requires Push to be enabled.

grid.getDataCommunicator().enablePushUpdates(Executors.newCachedThreadPool());

Vaadin 14:

One can attempt to reduce query time with caches. You can either use some generic cache like ehcache, or integrate cache in your data provider. It is application specific which is better for you, global or local cache.

If the query is still taking that long, then I would propose alternative approach for your UI. Instead of using callback data provider, use Grid with in memory data provider, but do not load whole data to the data provider at once. Instead create a paged view. Query new data when user clicks e.g. "next"/"previous", etc. buttons. And update the UI using UI#access method async when query completes.

STaefi
  • 4,297
  • 1
  • 25
  • 43
Tatu Lund
  • 9,949
  • 1
  • 12
  • 26
  • Paging is not useful for our clients. They needs to get info in large Grids with sorting, filtering and dynamic update data. Only possible i think we add one more hackish layer as cache. I don't know how good it will be, some problems i already see. By the way - didn't Vaadin think of this problem before? It is very annoying problem. Solve it if you can, please. – lanmaster Nov 12 '21 at 13:28
  • If i understand right, Grid ask DataCommunicator for data, it ask data from DataProvider. DataCommunicator wait for DataProvider in blocking mode. Why didn't split it with two steps? First is fetching data from DataProvider and second is callback on data ready for refresh UI? It will be non-blocking architecture. – lanmaster Nov 12 '21 at 13:44
  • 1
    I inspected a code of the current implementation and made local mod for non-blocking version that worked reasonably well. I think it is technically possible, yes. It also opens lot of possibilities for corner case bugs, so I would assume that current choice has been made in favor of simplicity and maintainability. – Tatu Lund Nov 15 '21 at 10:48
  • It is very good news. Can you share with me this mod? Maybe i can use it in my project? We develop heavy application for 24/7 operators work. One page contains 5 grids with auto reload data. With about 40 operators sql query is slow down and i think DataCommunicators increasing its blocking periods. UI is frequently freezes. Fully async approach is very needed for heavy UI applications. – lanmaster Nov 15 '21 at 18:00
  • Or maybe you point me how to create a data layer that can provide fast answers for DataProvider? Most problem is that i cannot know which sort options will be used for next Grid Query. And i cannot prepare fast data for it. In some cases i must invoke direct query execution with blocking again. – lanmaster Nov 15 '21 at 18:09
  • 1
    Here it is as gist, note this not simple to extend, so I just tried mine as monkey patch https://gist.github.com/TatuLund/a97e01702705d2a53a67c114e514d8cd – Tatu Lund Nov 15 '21 at 18:41
  • I put DataCommunicator.java in classpath and it works perfectly for first look. Only small hack i was need to make: scrollToEnd() must be invoked after Grid refresh has been finished. – lanmaster Nov 16 '21 at 07:22
  • 2
    I naturally was not able to test it against all possible regressions. There is feature request ticket here, feel free to comment and thumb up it https://github.com/vaadin/flow-components/issues/1066 – Tatu Lund Nov 16 '21 at 10:10
1

You could use an additional database / searchengine that is specialized for searching/filtering and that can deliver the data for your grid very fast. Elasticsearch is a good choice for that use case.

The searchengine would be just for your grid. For all other use cases (input forms, processing, ...) you further use your current database.

Of course you have to implement an update mechanism. Whenever your data changes in your current database you have to inform the Elasticsearch system and update the data there.