Improving Performance and Flexibility of Content Listings Using Criteria API

36
Improving Performance and Flexibility of Content Listings Using Criteria API Nils Breunese

Transcript of Improving Performance and Flexibility of Content Listings Using Criteria API

Page 1: Improving Performance and Flexibility of Content Listings Using Criteria API

Improving Performance and Flexibility of Content Listings Using Criteria API Nils Breunese

Page 2: Improving Performance and Flexibility of Content Listings Using Criteria API

Public Broadcaster since 1926 The Netherlands

Page 3: Improving Performance and Flexibility of Content Listings Using Criteria API

Online since 1994 Open-source CMS released in 1997

Page 4: Improving Performance and Flexibility of Content Listings Using Criteria API

Using Magnolia since 2010 Still migrating websites

Page 5: Improving Performance and Flexibility of Content Listings Using Criteria API

Tens of thousands of pages Multiple sites like that

Page 6: Improving Performance and Flexibility of Content Listings Using Criteria API

Overview pages Lots of them

Page 7: Improving Performance and Flexibility of Content Listings Using Criteria API

Thanks for the warning… Even 10 seconds would be way too long

WARN info.magnolia.module.cache.filter.CacheFilter -- The following URL took longer than 10 seconds (63969 ms) to render. This might cause timeout exceptions on other requests to the same URI.

Page 8: Improving Performance and Flexibility of Content Listings Using Criteria API

Overview models Standard Templating Kit

Page 9: Improving Performance and Flexibility of Content Listings Using Criteria API

Tracking back from the template newsOverview.ftl

(...) [#assign pager = model.pager] [#assign newsList = cmsfn.asContentMapList(pager.pageItems)!] (...)

Page 10: Improving Performance and Flexibility of Content Listings Using Criteria API

Constructing the pager AbstractItemListModel

public STKPager getPager() throws RepositoryException { (...) return new STKPager(currentPageLink, getItems(), content); }

Page 11: Improving Performance and Flexibility of Content Listings Using Criteria API

Four step pipeline AbstractItemListModel

public Collection<Node> getItems() throws RepositoryException { List<Node> itemsList = search(); this.filter(itemsList); this.sort(itemsList); itemsList = this.shrink(itemsList); return itemsList;}

1

23

4

Page 12: Improving Performance and Flexibility of Content Listings Using Criteria API

Step 1a: Constructing the query TemplateCategoryUtil

public static List<Node> getContentListByTemplateNames(...) { (...) StringBuffer sql = new StringBuffer( "select * from nt:base where jcr:path like '" + path + "/%'"); (...add 'mgnl:template=' clauses...) (...add 'ORDER BY' clauses...) return getWrappedNodesFromQuery(sql.toString(), repository, maxResultSize); } maxResultSize == Integer.MAX_VALUE

Page 13: Improving Performance and Flexibility of Content Listings Using Criteria API

Step 1b: Executing the query TemplateCategoryUtil

public static List<Node> getContentListByTemplateNames(...) { (...) NodeIterator items = QueryUtil.search( repository, sql.toString(), Query.SQL, NodeTypes.Content.NAME); }

Page 14: Improving Performance and Flexibility of Content Listings Using Criteria API

Step 2: Filtering the item list STKDateContentUtil

public static void filterDateContentList(...) { CollectionUtils.filter(itemsList, new Predicate() { @Override public boolean evaluate(Object object) { (...) return date.after(minDate) && date.before(maxDate); } });}

Page 15: Improving Performance and Flexibility of Content Listings Using Criteria API

Step 3: Time to sort STKDateContentUtil

public static void sortDateContentList(...) { Collections.sort(itemsList, new Comparator<Node>() { @Override public int compare(Node c1, Node c2) { (...) if (StringUtils.equals(sortDirection, ASCENDING)) { return date2.compareTo(date1); } return date1.compareTo(date2); } });}

Page 16: Improving Performance and Flexibility of Content Listings Using Criteria API

Step 4: Shrinking the list STKTemplatingFunctions

public List<Node> cutList(List<Node> itemsList, final int maxResults) { if (itemsList.size() > maxResults) { return itemsList.subList(0, maxResults); } return itemsList;}

NewsOverviewModel passes Integer.MAX_VALUE, so shrink does effectively nothing in this case

Page 17: Improving Performance and Flexibility of Content Listings Using Criteria API

Step 5: Get the items from the pager STKPager

public Collection getPageItems() { Collection subList = items; int offset = getOffset(); if (count > 0) { int limit = maxResultsPerPage + offset; if (items.size() < limit) { limit = count; } subList = ((List) items).subList(offset, limit); } return subList;}

maxResultsPerPage is typically something like 20

Page 18: Improving Performance and Flexibility of Content Listings Using Criteria API

When this becomes a problem We have multiple sites like this

select * from nt:base where jcr:path like '/siteX/news/%' AND

mgnl:template = 'standard-templating-kit:pages/stkNews'

20000 pages under website:/siteX/news

Four step pipeline returns STKPager with 20000 items (page nodes)

[#assign model.pager]

[#assign newsList = cmsfn.asContentMapList(pager.pageItems)!]

STKPager returns list with 20 page nodes

19980 Node objects created, but not rendered

Page 19: Improving Performance and Flexibility of Content Listings Using Criteria API

A query could do all steps at once JCR queries are pretty flexible

Page 20: Improving Performance and Flexibility of Content Listings Using Criteria API

Everything in a single JCR query Only 20 nodes returned

SELECT * FROM nt:base WHERE jcr:path LIKE '/siteX/news/%' AND

mgnl:template = 'standard-templating-kit:pages/stkNews'

AND jcr:created < cast('2016-06-07T00:00:00.000Z' AS DATE)

ORDER BY date ASCENDING

LIMIT 20 OFFSET 20

Search

Filter

Sort

Paging

Page 21: Improving Performance and Flexibility of Content Listings Using Criteria API

Criteria API For those familiar with Hibernate/JPA

Criteria criteria = JCRCriteriaFactory.createCriteria() .setBasePath("/siteX/news") .add(Restrictions.eq( "@mgnl:template", "standard-templating-kit:pages/stkNews")) .add(Restrictions.betweenDates("@jcr:created", minDate, maxDate)) .addOrder(Order.asc("date")) .setPaging(20, 1); ResultIterator<...> items = criteria.execute(session).getItems();

SortPaging

Filter

Search

Page 22: Improving Performance and Flexibility of Content Listings Using Criteria API

Criteria API for Magnolia CMS Magnolia module by Openmind

Page 23: Improving Performance and Flexibility of Content Listings Using Criteria API

jcr-criteria https://github.com/vpro/jcr-criteria

Page 24: Improving Performance and Flexibility of Content Listings Using Criteria API

Custom pager Only a single page worth of items

public class VtkPager<T> extends STKPager { private final List<? extends T> items; private final int pageSize; private final int count; (...) @Override public List<? extends T> getPageItems() { return items; } }

Page 25: Improving Performance and Flexibility of Content Listings Using Criteria API

Use it in your model classes VtkContentListModel (vpro)

public abstract class VtkContentListModel ... { protected final VtkPager<ContentMap> pager; @Override public String execute() { pager = createPager(); return super.execute(); } protected abstract VtkPager<T> createPager(); (...) }

Page 26: Improving Performance and Flexibility of Content Listings Using Criteria API

Concrete Example VtkNewsOverviewModel (vpro)

@Overrideprotected VtkPager<Node> createPager() { (...) AdvancedResult result = JCRCriteriaFactory.createCriteria() .setBasePath(path) .add(Restrictions.in("@mgnl:template", templates)) .add(Restrictions.betweenDates("@jcr:created", minDate, maxDate)) .addOrder(Order.asc("date")) .setPaging(itemsPerPage, pageNumberStartingFromOne) .execute(session);

List<Node> items = new ArrayList<>(); for (AdvancedResultItem item : result.getItems()) { items.add(item.getJCRNode()); } int count = result.getTotalSize(); return new VtkPager<>(link, items, content, itemsPerPage, count); }

Page 27: Improving Performance and Flexibility of Content Listings Using Criteria API

Still this. Was it all for nothing? :o(

WARN info.magnolia.module.cache.filter.CacheFilter -- The following URL took longer than 10 seconds (63969 ms) to render. This might cause timeout exceptions on other requests to the same URI.

Page 28: Improving Performance and Flexibility of Content Listings Using Criteria API

Example VtkNewsOverviewModel (vpro)

@Overrideprotected VtkPager<Node> createPager() { (...) AdvancedResult result = JCRCriteriaFactory.createCriteria() .setBasePath(path) .add(Restrictions.in("@mgnl:template", templates)) .add(Restrictions.betweenDates("@jcr:created", minDate, maxDate)) .addOrder(Order.asc("date")) .setPaging(itemsPerPage, pageNumberStartingFromOne) .execute(session);

List<Node> items = new ArrayList<>(); for (AdvancedResultItem item : result.getItems()) { items.add(item.getJCRNode()); } int count = result.getTotalSize(); return new VtkPager<>(link, items, content, itemsPerPage, count); }

This call takes 10-60+ seconds!

Page 29: Improving Performance and Flexibility of Content Listings Using Criteria API

AdvancedResultImpl (jcr-criteria)

@Overridepublic int getTotalSize() { if (totalResults == null) { int queryTotalSize = -1; try { // jcrQueryResult instanceof JackrabbitQueryResult) { Method m = jcrQueryResult.getClass().getMethod("getTotalSize"); queryTotalSize = (int) m.invoke(jcrQueryResult); } catch (InvocationTargetException | IllegalAccessException e) { LOG.error(e.getMessage(), e); } catch (NoSuchMethodException e) { } if (queryTotalSize == -1 && (itemsPerPage == 0 || applyLocalPaging)) { try { totalResults = (int) jcrQueryResult.getNodes().getSize(); } catch (RepositoryException e) { // ignore, the standard total size will be returned } } if (queryTotalSize == -1) { totalResults = queryCounter.getAsInt(); } else { totalResults = queryTotalSize; } } return totalResults; }

We end up here

Page 30: Improving Performance and Flexibility of Content Listings Using Criteria API

jackrabbit-core 2.8.0

protected void getResults(long size) throws RepositoryException { (...) result = executeQuery(maxResultSize); // Lucene query (...) // Doesn’t use result.getSize(), call collectScoreNodes(...) }

private void collectScoreNodes(...) { while (collector.size() < maxResults) { ScoreNode[] sn = hits.nextScoreNodes(); (...) // check access if (isAccessGranted(sn)) { collector.add(sn); } else { invalid++; } }} QueryResultImpl

Page 31: Improving Performance and Flexibility of Content Listings Using Criteria API

It used to be fast! https://issues.apache.org/jira/browse/JCR-3858

Page 32: Improving Performance and Flexibility of Content Listings Using Criteria API

jackrabbit-core 2.10.0+

protected void getResults(long size) throws RepositoryException { (...) if (sizeEstimate) { numResults = result.getSize(); // Use count from Lucene } else { // do things the Jackrabbit 2.8.0 way (...) } (...) }

QueryResultImpl

Page 33: Improving Performance and Flexibility of Content Listings Using Criteria API

Enable Jackrabbit’s 'sizeEstimate' Jackrabbit 2.10+

<SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex"> (...) <param name="sizeEstimate" value="true"/></SearchIndex>

Page 34: Improving Performance and Flexibility of Content Listings Using Criteria API

Rendering times down to 1-2 seconds Bingo

Page 35: Improving Performance and Flexibility of Content Listings Using Criteria API

Time for questions

Anyone?

Page 36: Improving Performance and Flexibility of Content Listings Using Criteria API

Feel free to contact me

Nils [email protected]@vpro.nl