Grid with lazy loading: very high number of calls into the data-provider

Hello,

I’m using com.vaadin.flow.component.grid.Grid with a lazy loading data provider:

dataProvider = DataProvider.fromCallbacks(
				// First callback fetches items based on a query
				query -> {
					// The index of the first item to load
					int offset = query.getOffset();

					// The number of items to load
					int limit = query.getLimit();

					List<Data> data_list = Data.fetchItems(offset, limit);

					return data_list.stream();
				},
				// Second callback fetches the number of items for a query
				query -> Data.getItemCount());

Essentially the grid works fine, but I see a lot of calls into the data-provider when scrolling. When scrolling forward, that is understandable, since the grid needs the next set of items to display. When scrolling back however, the grid also queries the data-provider for items that are already in the list. This was actually a surprise, since I see no need for this at all.

Ultimately, my goal is to minimize the number of calls into the data-provider since querying that data is an “expensive” operation. Additionally … my application will be running in the cloud. Hence I’d like to understand what parts of the code run client-side vs. server-side, in order to minimize the traffic between browser and server.

Any help/info would be very welcome.

Regards.

Do you mean that it fetches items that are currently shown, or items that have been fetched previously?

Grid actively discards items that are out of view to avoid leaking memory. Without this, memory usage could grow quite high if a user scrolls through a long data set since there would eventually be an object in memory for every row in the database.

Hello Leif,

The grid indeed does not fetch any items currently shown. The behaviour I see is that when scrolling up (essentially displaying items that have already been loaded previously) data is queried again. This corresponds to the behaviour you are describing.

I do understand the reasoning (saving memory), but will result in a lot of calls between browser and server/back-office. I would have expected some kind of caching to prevent calling into the back-office too much (typically these are “expensive” operations).

Vaadin cannot know what degree of caching would be appropriate in any specific use case. For that reason, the components only cache items based on what’s currently displayed.

In this way, the application developer has the freedom to do whatever caching they deem necessary in their own DataProvider implementation.

That is indeed a fair approach. Thanks a lot for the feedback!

Just one more thing I’m trying to understand … I want to minimize the traffic between browser and server (Tomcat servlet container). Is it correct the that DataProvider resides on the server and that the grid will execute AJAX calls to that data-provider to get chunks of data?

All your regular Java code runs on the server. In the case of Grid, there’s some client-side logic that keeps track of where the user is scrolled and then it does AJAX requests to the corresponding server-side logic. To reduce the number of requests, the logic always fetches at least one page of items at the same time. The default page size is 50.

You can use the setPageSize(int) method of Grid to configure a different page size. Higher numbers means fewer requests as the user scrolls, but at the same time also more memory usage (since there’s also a server-side cache for all items that the client-side knows about) and higher latency (since each response transmits more data and thus takes longer to complete).

Thanks!

Leif Åstrand:
All your regular Java code runs on the server. In the case of Grid, there’s some client-side logic that keeps track of where the user is scrolled and then it does AJAX requests to the corresponding server-side logic. To reduce the number of requests, the logic always fetches at least one page of items at the same time. The default page size is 50.

You can use the setPageSize(int) method of Grid to configure a different page size. Higher numbers means fewer requests as the user scrolls, but at the same time also more memory usage (since there’s also a server-side cache for all items that the client-side knows about) and higher latency (since each response transmits more data and thus takes longer to complete).

Why grid always try to fetch minimum 50 rows? We set only 8 in page size setPageSize(8) because there is only one generated column with item (component) with height of 120px, so there is no need to fetch more than 2 x 8 items at most, but first call fetch with limit 8 and offset 0, and second one right after that fetch with limit 42 and offset 8 (that makes that 50 items). As a result of that we have very slow loading. Can this be fixed somehow?

See https://github.com/vaadin/vaadin-grid-flow/issues/430#issuecomment-472808497 for a discussion and some partial workarounds related to the situation with having lots of rows despite setting a low page size.

Leif Åstrand:
See https://github.com/vaadin/vaadin-grid-flow/issues/430#issuecomment-472808497 for a discussion and some partial workarounds related to the situation with having lots of rows despite setting a low page size.

Thank you for answer but nothing helped. This is not new issue, we had already this problem before. Why is so hard to implement some fix for this?

I’m getting java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 error. I’ve two rows like below

The Description column is rendered as a ComboBox when edited and if there is only only matching value, it throws error

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:653) ~[na:1.8.0_131]

	at java.util.ArrayList.get(ArrayList.java:429) ~[na:1.8.0_131]

	at com.vaadin.flow.data.provider.DataCommunicator.lambda$getJsonItems$3(DataCommunicator.java:615) ~[flow-data-2.2.2.jar:2.2.2]

	at java.util.stream.IntPipeline$4$1.accept(IntPipeline.java:250) ~[na:1.8.0_131]

	at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110) ~[na:1.8.0_131]

	at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693) ~[na:1.8.0_131]

	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_131]

	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_131]

	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_131]

	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_131]

	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) ~[na:1.8.0_131]

	at com.vaadin.flow.data.provider.DataCommunicator.getJsonItems(DataCommunicator.java:617) ~[flow-data-2.2.2.jar:2.2.2]

	at com.vaadin.flow.data.provider.DataCommunicator.collectChangesToSend(DataCommunicator.java:560) ~[flow-data-2.2.2.jar:2.2.2]

	at com.vaadin.flow.data.provider.DataCommunicator.flush(DataCommunicator.java:477) ~[flow-data-2.2.2.jar:2.2.2]

	at com.vaadin.flow.data.provider.DataCommunicator.lambda$requestFlush$2f364bb9$1(DataCommunicator.java:425) ~[flow-data-2.2.2.jar:2.2.2]

	at com.vaadin.flow.internal.StateTree.lambda$runExecutionsBeforeClientResponse$1(StateTree.java:368) ~[flow-server-2.2.2.jar:2.2.2]

	at java.util.ArrayList.forEach(ArrayList.java:1249) ~[na:1.8.0_131]

	at com.vaadin.flow.internal.StateTree.runExecutionsBeforeClientResponse(StateTree.java:365) ~[flow-server-2.2.2.jar:2.2.2]

	at com.vaadin.flow.server.communication.UidlWriter.encodeChanges(UidlWriter.java:411) ~[flow-server-2.2.2.jar:2.2.2]

	at com.vaadin.flow.server.communication.UidlWriter.createUidl(UidlWriter.java:187) ~[flow-server-2.2.2.jar:2.2.2]

	at com.vaadin.flow.server.communication.UidlRequestHandler.writeUidl(UidlRequestHandler.java:121) ~[flow-server-2.2.2.jar:2.2.2]

	at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:90) ~[flow-server-2.2.2.jar:2.2.2]

	at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40) ~[flow-server-2.2.2.jar:2.2.2]

	at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1545) ~[flow-server-2.2.2.jar:2.2.2]

	at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:247) [flow-server-2.2.2.jar:2.2.2]

	at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:120) [vaadin-spring-12.2.0.jar:na]

	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:352) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.springframework.web.servlet.mvc.ServletForwardingController.handleRequestInternal(ServletForwardingController.java:141) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:177) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:52) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE]

	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131]

	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131]

	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.27.jar:9.0.27]

	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]


The dataprovider used here is

CallbackDataProvider<String, String> descDataProvider
                = DataProvider.fromFilteringCallbacks(query -> {
            String filter = query.getFilter().orElse("");
            Stream<String> propertyBoxStream = datastore.query(Product.TARGET)
                    .restrict(query.getLimit(), query.getOffset())
                    .stream(Product.DESCRIPTION)
                    .filter(s -> StringUtils.startsWithIgnoreCase(s,filter));
                           // s -> s.startsWith(filter)
            return propertyBoxStream;
        }, query -> {
            String filter = query.getFilter().orElse("");
            long propertyBoxStream = datastore.query(Product.TARGET)
                    .stream(Product.DESCRIPTION)
            .filter(s -> StringUtils.startsWithIgnoreCase(s,filter))
                    .count();
            return (int) propertyBoxStream;
        });
		```
		
I don't know what is wrong here. Might be something to do with the filter ? Please help.