Tuning Wicket Session Size
I love Wicket, and I love Google App Engine. Using both in combination has enabled us to build a really nice performance appraisal software really quickly, and i could do it with rather clean code (which now enables me to bring on board junior developers really fast), and minimal worry about deploying and hosting it (GAE is dirt cheap, and super comfortable when it comes to 1-click deployments, monitoring, etc).
However, Wicket uses the session to store lots of data (the page representation to be precise). Most of that data is little chunks of 32 bytes here, 56 bytes there. But they
can add up. And if you do it wrong, you can even end up with a megabyte here, a megabyte there. The memory limits on GAE may be a bit radical (1MB is
not really that much), but
even on a normal application server, you don't want each visitor to cling to tens of megabytes. So have a read and see if you can tune your pages.
The basics
There are quite a few things you can do to improve session size. Let's start with the absolute basics. Wicket experts can smile and skip this section.
Use LoadableDetachable Models
Don't be an idiot like me who skipped the most important chapter in the Wicket book. You MUST make sure your domain model (e.g. your user, or your project, or your tweet, or whatevery your model consists of) and its attributes do NOT get permanently stored into the component tree in any way.
The easiest way to stuff up is this:
Label name= new Label("name", person.getName());
Yes, this is WRONG. It may only cost you 50 bytes, but nonetheless. What about this:
Label name= new Label("description", project.getDescription());
Same problem. But maybe the project description contains 20K instead of 50 bytes now! And these 20K stay with you until the page gets removed from the pagemap. Until then, the memory is wasted and clogging up your session. Look up LDMs and use those. Then all you keep in the session is the ID to the project or person. IF the page has to be re-rendered, the LDM will simply reload the data from the page, show it, and throw it away again. I'm not going to explain LDMs here, that's been done often enough. :-)
Say after me: "Final is evil". (Use a Profiler for your excorcism!)
The most common error besides using the wrong models, is the use of the word "final". You might have used final to be able to access your domain object from inside an AJAX action. But this final actually means the variable now is a instance variable (instead of a local variable) and does NOT get garbage collected until the component does. And since the component does NOT get garbage collected, your domain object is now an undead zombie. Useless, but wasting memory. So, get rid of finals. (How to access the domain object then from within said AJAX method? See LDMs above)
Even if you didn't use final on the model object directly, you might have some indirect reference chain clinging on to large objects, preventing them from being detached at the end of the cycle. The only sure way to find out is to use a profiler. Try JProfiler, for instance, it's great, and there's a 30 day eval. Click around, then take a memory snapshot, and locate your page:
Take a snapshot:
Locate the page to analyse:
Double click the entry, and pick "show larges objects":
Now drill into the tree to see who's been a naughty boy:
Look out for AttributeAppenders
I use them a LOT, because it is sooo easy to change the UI with them. If condition A is met, append the class A to make my label go red. Else append the class B to make the label go green.
However, each appender costs memory! The obvious optimisation is to not create new AttributeAppenders all over the place, but to hardwire them outside the loop, and use them inside.
This works like a charm for AttributeAppenders that add a class, or something else that's not different for each row. You can't use this approach to append an employee's name to their image using a title, for instance. See Pro-Tips if you need something like that.
UrlCompressingWebRequestProcessor seems to use more memory than I want it to
I came across this hint somewhere:
I do want to report a couple of settings that seemed to make a difference:
getMarkupSettings().setCompressWhitespace(true);
This almost cut page sizes in half for large HTML pages (having lots of
repeater indenting etc), and improved performance.
Also:
@Override
protected IRequestCycleProcessor newRequestCycleProcessor() {
return new UrlCompressingWebRequestProcessor();
}
This setting can help reduce the size of HTML in cases where you have a
large-sized repeater containing hyperlinks.
- Peter.
The first one is a nice little improvement: When you don't gzip, the difference is huge, and even if you gzip everything on the way out, the removal of whitespace still makes pages a little smaller (in my case, pages are now 22k instead of 24k when reaching the client). Not a lot smaller, but it's basically for free, so use it.
The second hint however had a massive drawback in my case, there was suddenly 20K or so used for some other stuff that had minimal effect. I disabled it again. Maybe it works for you, just make sure you analyse your memory.
Intermediate advice
For example, we may want to display display 100 or more rows containing plenty of employee account data. And not just a name, but
lots of data per row: First name, surname, title, description, links to the staff profile, to their manager, images of their reports, and some ajax links
to change a person's settings. Like in this screenshot, for instance:
Sure, we can use pagination, but pagination is limiting the usefulness of such screens, so we want a big pagination window of at least 100 rows, preferrably 300.
Avoid setVisible()!
I am using this pattern so often. If condition A is met, show label A. If not, use setVisible(false) to hide it. Combine that with Enclosures, and you know why I an code really fast in Wicket. I love it.
Trouble is, while the hidden component doesn't show up in the markup, it's still part of the component tree! This may not be such a problem if we're talking labels (56 bytes). But a link that contains an image and a label, and possibly an AttributeAppender, comes at a cost of at least 200 bytes. When showing 100 rows, and the label maybe only shows in 10 percent of the cases, that's 90x200 bytes going to waste. It doesn't seem like you can tell Wicket to not keep this in the component tree. But if you are sure you don't want to change the visibility later on, you can do a little hack like this:
if (currentUser.isHR() || currentUser.isAdmin()) {
AjaxLink showAdminActions = new ShowAdminActionsAjaxLink("showAdminActions", userModel, nestedAdminActions);
showAdminActions.setOutputMarkupId(true);
listItem.add(showAdminActions);
}
else{
Label blankLabel = new Label("showAdminActions");
blankLabel.setVisible(false);
listItem.add(blankLabel);
}
This saved me a lot of memory in some heavy data tables
Clean up regularly
Webmarkupcontainers are expensive. if your panels or pages have grown organically over a couple of months, there's a fair chance you have containers that are not strictly needed anymore, or are only in place to make things look neat and organised. Get rid of them, save those 100 bytes.
1998 called...
...and asked if you're still using Word files to do your performance reviews?
Small Improvements is a user friendly
system to manage performance reviews online .
If you're still using Word files at your job for performance
reviews,
why not tell your HR manager or boss that
there are easier ways these days?
Happy customers:
"Our performance management system Small Improvements pretty much takes care of itself, which is a dream come true :)"
Consider using replaceWith()
In the above example of the staff list, you can see I have 4 (but sometimes up to 8) AJAX links. I love AJAX links, but of course having 8 is a lot. Also, while they are super-convenient for the enduser, they are of course not used all the time. In my case, I decided that I rather want a longer pagination size, than show the AJAX links all the time. So I put them into a separate panel, and do NOT show them all the time. There is now a "show actions" AJAX link. when the employee clicks it, the panel with the 8 links get loaded on demand (look up replaceWith()!). It takes the end user a second longer to access my nice 8 AJAX links. But I saved 1K per row. Meaning I could increase pagination from 100 rows to 200 rows :-)
Maybe you don't need IndicatingAjaxLink
IndicatingAjaxLinks (from the extensions package) are great because they show the enduser that something is happening. But also consume 80 Bytes more than a regular AjaxLink. So if you have actions that are rarely used, or frequently used by experienced staff, etc, use the non-indicating link. If you can swap 5 of those, you save 400 bytes. If that's happening in 50 rows, you have just made a difference.
Failed optimisation
I am using UUIDs for my domain object IDs. Call me paranoid, but it feels just great to know I will never run into issues when migrating my data. However, UUIDs are long, and in the LDMs they take up quite some memory. So I thought I'd be smart and optimise my Strings into char[] arrays. The profiler did show an improvement, however on GAE the serialisation is way smarter than that, so it actually was a memory increase. So, don't bother trying this one. :-)
Anyway, do get creative about tuning stuff! Almost everything I tried did pay off, this is the rare case where it didn't work. And it didn't cost me more
than an hour to find out either.
Size based eviction policy
The default pagemapsize-based eviction policy evicts pages from your pagemap once it becomes too crowded, and uses a first in first out approach. Fair enough if your pages are all about the same size. But if most of your pages are small, and only a handful are large, this eviction policy forces you to use a pretty small number (I kept using 4), so you don't crash your GAE application in case someone view 5 heavy pages in a row (It really gives you one of the most ugly screens ever, and staff hate ugly error screens). While 4 is safe, it also severely restricts your employee' ability to use the back-button.
So when I saw the great examples in the upcoming Wicket 1.5 release about how to write a session-size based eviction policy, I hacked one up for 1.4 as well. It is not as clean, because the eviction happens for the current pagemap only, while the session size check takes into account ALL page maps. So this may, in worst cases, force your current page map to be a size of 1, while another pagemap has eaten most of your memory. Also, memory size calculation is not acurate at all. But it's a best effort approach that works for me, I have since increased the number of pages I store to up to 7, which is a lot better for back-button support.
Careful, like so many things we do, this is just a hack based on other peoples' code, and I will gladly throw it away once I can get Wicket 1.5 working on GAE:
package com.praisemanager.web.util;
import com.praisemanager.domain.SystemSettings;
import com.praisemanager.web.PraiseSession;
import org.apache.wicket.*;
import org.apache.wicket.protocol.http.WebRequestCycle;
import org.apache.wicket.session.pagemap.IPageMapEntry;
import org.apache.wicket.session.pagemap.IPageMapEvictionStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PageNumberAndMemoryEvictionStrategy implements IPageMapEvictionStrategy
{
private static final long serialVersionUID = 1L;
private static Logger log = LoggerFactory.getLogger(PageNumberAndMemoryEvictionStrategy.class);
private int maxVersions;
private int maxBytesPerPagemap;
public PageNumberAndMemoryEvictionStrategy(int maxVersions, int maxBytesPerPagemap)
{
this.maxBytesPerPagemap = maxBytesPerPagemap;
if (maxVersions < 1)
{
throw new IllegalArgumentException("Value for maxVersions must be >= 1");
}
this.maxVersions = maxVersions;
}
public void evict(final IPageMap pageMap)
{
if (pageMap instanceof AccessStackPageMap)
{
synchronized (Session.get())
{
AccessStackPageMap accessPM = (AccessStackPageMap)pageMap;
// Do we need to evict under this strategy?
int currentVersions = accessPM.getVersions();
SystemSettings systemSettings = ((PraiseSession) RequestCycle.get().getSession()).getSystemSettings();
if (systemSettings!=null) {
maxVersions=systemSettings.maxPageVersions;
maxBytesPerPagemap=systemSettings.maxBytesPerPagemap;
}
log.info("eviction required in map "+accessPM.getName()+"? "+currentVersions+" available, "+maxVersions+" is max");
if (currentVersions > maxVersions)
{
cleanOne(pageMap, accessPM);
}
cleanForBytesRecursive(pageMap, accessPM);
}
}
}
private void cleanForBytesRecursive(IPageMap pageMap, AccessStackPageMap accessPM) {
Session session = WebRequestCycle.get().getSession();
// this is a hack! we should be checking the current pagemap, not the session as a whole. we are only cleaning the current pagemap after all
long storeCurrentSize=session.getSizeInBytes();
//long storeCurrentSize = Objects.sizeof(session);
if (storeCurrentSize > maxBytesPerPagemap)
{
log.info("pagetable is too big: "+storeCurrentSize+" vs "+ maxBytesPerPagemap +" allowed. Evicting a page.");
if (accessPM.getVersions()==1){
log.info("... not really, since this is the last version we have, not evicting it.");
return;
}
cleanOne(pageMap, accessPM);
long storeCurrentSizeAfterClean=session.getSizeInBytes();
// recurse until enough space is cleaned, but only if the recent clean made any difference. If not, stop recursing to avoid dead loop
if (storeCurrentSize!=storeCurrentSizeAfterClean) {
cleanForBytesRecursive(pageMap, accessPM);
}
else {
log.info("whoa, cleaning made no difference, so we interrupt the recursion right away at "+storeCurrentSize);
}
}
else {
log.info("pagetable is right sized :"+storeCurrentSize+" vs "+ maxBytesPerPagemap +" allowed. Done here");
}
}
private void cleanOne(IPageMap pageMap, AccessStackPageMap accessPM) {
// Remove oldest entry from access stack
final AccessStackPageMap.Access oldestAccess = accessPM.getAccessStack()
.remove(0);
final IPageMapEntry oldestEntry = pageMap.getEntry(oldestAccess.getId());
// If entry is a page (cannot be null if we're evicting)
if (oldestEntry instanceof Page)
{
Page page = (Page)oldestEntry;
// If there is more than one version of this page
if (page.getVersions() > 1)
{
// expire the oldest version
page.expireOldestVersion();
}
else
{
// expire whole page
accessPM.removeEntry(page);
}
}
else
{
// If oldestEntry is not an instance of Page, then it is
// some
// custom, defined IPageMapEntry class and cannot
// contain
// versioning information, so we just remove the entry.
if (oldestEntry != null)
{
accessPM.removeEntry(oldestEntry);
}
}
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return "[LeastRecentlyAccessedEvictionStrategy maxVersions = " + maxVersions + "]";
}
}
Advanced tips and hacks
If you're really, really desparate, because your tuning has not reduced the sizes significantly, then you may need start some serious brain surgery. Don't do this if you can avoid it!!
Dedicated components
I realised that Images took up a lot of memory for me. No matter how I tried (using AttributeAppenders on Labels even, how bizarre!) I couldn't get under 200 bytes or so.
In my desparation, I read about overriding onComponentTag(), and subsequently created this ugly beast, which serves me really well though. It consumes only 56 bytes, and seeing how many images I use, that's a massive memory saver! :-) Check out how I even fill in the title attribute here in the case of users. that would be super expensive to do with AttributeAppenders.
public class StaticImage extends WebComponent {
private static final long serialVersionUID = 1L;
public StaticImage(String id, IModel model) {
super(id, model);
}
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
checkComponentTag(tag, "img");
IModel> iModel = getDefaultModel();
Object o = iModel.getObject();
if (o instanceof User) {
User user = (User) o;
tag.put("src", user.getLogoUrl());
tag.put("title", user.getFullName());
}
else if (o instanceof Project) {
Project project = (Project) o;
tag.put("src", project.getLogoUrl());
}
else {
if (o!=null)
tag.put("src", o.toString());
else {
int t=0;
}
}
}
}
I am also using similar (but nicer) constructs for Links to Projects, etc. But you can probably figure it out by yourself now how I do that.
Go retro, go servlet!
One of the awesome things in wicket is how you can decorate your html in the html file without touching the Java code. But this comes at a price: Each label, each image, each everything costs memory. So in some cases, it may make sense to combine several labels into one. For instance, if you are displaying simple data, you may not need 10 labels sitting next to each other independently. One label could render the content of the 10 of them. You'd save 9 labels, and that's at least 56 bytes each! If you have 200 rows, you can save 9x56x200 bytes, and this is huge!
Here's an example of how I do it in one particular case, by overriding the "onComponentTagBody()"-method, and by outputting raw HTML here.
Things you lose: You cannot easily decorate things. You lose the ability to modify those spans from Wicket code. Since we removed these from the component tree, changing their state isn't captured anywhere, and you can't restore their state e.g. when using the back button. So make sure your usecase fits!!
public AppraisalDetailsLabel(String id, UserModel userDetachableModel) {
super(id);
this.userDetachableModel = userDetachableModel;
}
protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
super.onComponentTagBody(markupStream, openTag);
User user=userDetachableModel.getUser();
String s=
"<span class="userName">"+user.getFullName()+"</span>\n" +
"<span class="bossX" style="display: none;">"+ bossX +"</span>\n" +
"<span class="bossY" style="display: none;">"+ bossY +"</span>\n" +
"<span class="selfX" style="display: none;">"+ selfX +"</span>\n" +
"<span class="selfY" style="display: none;">"+ selfY +"</span>\n" +
"<span class="userLogoSrc" style="display: none;">"+user.getLogoUrl()+"</span>\n" +
"<span class="uuid" style="display: none;">"+user.getId()+"</span>\n" +
"<span class="appraisalId" style="display: none;">"+ appraisalId +"</span>";
getResponse().write(s);
}
Note how in the above example I do not yet escape the user.getFullName() -- this is bad practice. An employee could change their name into something that attacks your other employees with JavaScript! As mentioned above, use Strings.escape(...), just like the Wicket framework does it automatically for you in its core components.
Summing it up, onComponent() and onComponentTag() are ugly, but they saved my ass, so...
A simple and pretty performance review tool
Is your employee review tool ugly and clunky? Let your HR staff know about Small
Improvements then! Our solution is affordable, pretty and web-based. Learn more about our approach to performance review software.
![]()
Feedback
Did the advice help you? Do you see issues with my suggestions? Have you got any further ideas? I am happy to incorporate your feedback, or simply to display it as a comment below. Every post will be answered. Also, make sure to check out our performance appraisal application in case your company does performance reviews, but you're not entirely convinced you are using the right software! :)
blog comments powered by Disqus