How to reduce a large internal view state / what is in the internal view state?

  • I'm building an account search page that lets users select accounts and then pass them to other pages to perform various actions on them. Unfortunately users are reporting hitting the view state limit. Which leads me to a broader question: what is in in the internal view state and how can I reduce it?

    I've already made everything transient I can, and when I look at the view state inspector my controller only uses a modest 15kb, but the "Internal" component of the view state is upwards of 90kb. Does anyone have any tips for how to reduce the "Internal" component of the view state?

    Some additional details. The page is not using any components. The page has only one form on it. The controller does reference a couple other classes. The controller does not store any state that can be regenerated in code on the postback.

    EDIT: Uploaded a gist with a thoroughly edited version of the code. The code will likely not compile initially but should give some idea of the design.

    EDIT2: Here's a screenshot of what the page looks like from a related post on the UX stackexchange Account Filter Page

    EDIT3: Here's a screenshot with a large result set loaded. If I group these results I'll exceed the view state limit. Result set size is 500 records and 1 grouping when the limit is reached. gigantic internal view state

    I think for anything beyond the ideas http://www.salesforce.com/us/developer/docs/pages/Content/pages_best_practices_performance.htm you may need to provide a code sample.

    Yes sample code of your page would be good. In the meantime, the components do generate a lot of internal view state, especially if you put them in an or table.

    Do you have multiple sections on your page which get sent to and from the backend for different reasons? You can now have multiple ``s which reduces the amount sent back and forth as it can use multiple view states from what I understand. Alternatively, could you leverage javascript remoting instead?

    Good point by LaceySnr.Javascript remoting will surely resolve view state issue.

    WRT , I think there's a little confusion. Multiple form tags used to be a problem as that resulted in multiple copies of the viewstate. Single view state went GA in Summer 12 and fixed this so that multiple forms use a single copy of the view state. Bottom line is one form or many forms should result in the same size viewstate.

    Thanks for all the thoughts so far, to the best of my knowledge I'm following all the best practices on this one. Putting up a bounty of +50 if someone can give an authoritative answer on this one. I've added a gist with an edited version of the code.

    @AndrewFawcett sample code is there, any thoughts?

    Anyone out there have friends in R&D, product management, or tier 3? Gauging by the number of votes on this question the community would be a lot better off by clarifying this murky area of visual force.

    I am thinking it is the amount of outputPanel's, just writing some test code to see if my hunch is correct. If it proves fruitful will post and answer and if so, I think there maybe some ways to avoid using them looking at what your trying to achieve.

    This post is an awesome post for somebody looking into building nested data structures on visual force pages. Limiting the output data would be certainly reduce the view state size. I was taken back by impact of visual force components on a repeat loop. I had a problem like this in my previous project where we had a file upload feature causing the problem and I was keeping the uploaded file in memory. So i had to insert the file as attachment and clear the property as null. So Lessons learnt 1. Use ajax to lazy load 2. Avoid multiple repeat loops Buyan

  • I needed to reproduce this in a smaller more focused example to prove my hunch. Then further down you'll see how I've approached a solution that hopefully doesnt impact your desire to structure the data as you currently have it. Yet does more work in the viewstate in order to simplify the VF markup.

    Research. It looks like it is the use of nested apex:repeat and apex:outputPanel's, they internally appear to use quite a lot of state. In the reproduction below, I found around half of my view state allocated to internal. And I didn't even take my example further by using apex:outputField (which I also suspect to be quite heavy). So with this in mind first, my reproduction controller and page are shown below. Followed by one possible way I considered to reduce the VF components on the page a little more but get the same output.

    public with sharing class ViewStateTestController {
    
    public List<Row> rows {get;set;}
    
    private static final Integer scaler = 5;
    
    public class Row
    {
        public Boolean isGrouping {get;set;}
        public Boolean selected {get;set;}
        public String name {get;set;}
        public String data1 {get;set;}
        public String data2 {get;set;}
        public String data3 {get;set;}
        public List<Row> children {get;set;}        
    }
    
    public ViewStateTestController()
    {
        rows = new List<Row>();
        for(Integer group1Idx=0; group1Idx<scaler; group1Idx++)     
        {
            // Add group for level 2 data
            Row group1Row = new Row();
            group1Row.isGrouping = true;
            group1Row.Name = 'Level 1 Group';
            group1Row.children = new List<Row>();
            rows.add(group1Row);
            for(Integer group2Idx=0; group2Idx<scaler; group2Idx++)     
            {
                // Add some level 2 data
                for(Integer group2DataIdx=0; group2DataIdx<group2Idx+1; group2DataIdx++)
                {
                    Row group2DataRow = new Row();
                    group2DataRow.isGrouping = false;
                    group2DataRow.Name = 'Level 2 Data';
                    group2DataRow.data1 = 'Some Level 2 Data 1';
                    group2DataRow.data2 = 'Some Level 2 Data 2';
                    group2DataRow.data3 = 'Some Level 2 Data 3';                                
                    group1Row.children.add(group2DataRow);
                }
                // Add group for level 3 data
                Row group2Row = new Row();
                group2Row.isGrouping = true;
                group2Row.Name = 'Level 2 Group';
                group2Row.children = new List<Row>();
                group1Row.children.add(group2Row);
                for(Integer group3Idx=0; group3Idx<scaler; group3Idx++)     
                {
                    // Add some level 3 data
                    for(Integer group3DataIdx=0; group3DataIdx<group3Idx+1; group3DataIdx++)
                    {                   
                        Row group3DataRow = new Row();
                        group3DataRow.isGrouping = false;
                        group3DataRow.Name = 'Level 3 Data';
                        group3DataRow.data1 = 'Some Level 3 Data 1';
                        group3DataRow.data2 = 'Some Level 3 Data 2';
                        group3DataRow.data3 = 'Some Level 3 Data 3';                                
                        group2Row.children.add(group3DataRow);
                    }
                }           
            }           
        }            
    }
    }
    

    And the VF page...

    <apex:page controller="ViewStateTestController" sidebar="false">
    <style type="text/css">
        table { border-collapse:collapse; }
        table.myTable td, table.myTable th { border:1px solid black;padding:5px; }
        table.myTable td.padding { border:0px; }
    </style>
    <apex:form >
    <table class="myTable">
        <tr>
            <td><b>Select</b></td>
            <td colspan="3"><b>Name</b></td>
            <td><b>Data 1</b></td>
            <td><b>Data 2</b></td>
            <td><b>Data 3</b></td>
        </tr>
        <apex:repeat value="{!rows}" var="rowlevel1">
            <tr>
                <apex:outputPanel rendered="{!rowLevel1.isGrouping}">
                    <apex:outputPanel layout="none">
                        <td><input type="checkbox"/></td>
                    </apex:outputPanel>
                    <td colspan="6">{!rowlevel1.Name} ({!rowlevel1.children.size})</td>
                </apex:outputPanel>
            </tr>
            <apex:repeat value="{!rowlevel1.children}" var="rowlevel2">
                <tr>
                    <apex:outputPanel rendered="{!rowLevel2.isGrouping}">
                        <apex:outputPanel layout="none">
                            <td><input type="checkbox"/></td>
                        </apex:outputPanel>
                        <td class="padding">&nbsp;&nbsp;</td>
                        <td colspan="5">{!rowlevel2.Name} ({!rowlevel2.children.size})</td>
                    </apex:outputPanel>
                    <apex:outputPanel rendered="{!NOT(rowlevel2.isGrouping)}">
                        <td><apex:inputCheckbox value="{!rowlevel2.selected}"/></td>
                        <td class="padding">&nbsp;&nbsp;</td>
                        <td colspan="2">{!rowlevel2.name}</td>
                        <td>{!rowlevel2.data1}</td>
                        <td>{!rowlevel2.data2}</td>
                        <td>{!rowlevel2.data3}</td>
                    </apex:outputPanel>                 
                </tr>           
                <apex:repeat value="{!rowlevel2.children}" var="rowlevel3">
                    <tr>
                        <td><apex:inputCheckbox value="{!rowlevel3.selected}"/></td>
                        <td class="padding">&nbsp;&nbsp;</td>
                        <td class="padding">&nbsp;&nbsp;</td>
                        <td colspan="1">{!rowLevel3.name}</td>
                        <td>{!rowlevel3.data1}</td>
                        <td>{!rowlevel3.data2}</td>
                        <td>{!rowlevel3.data3}</td>
                    </tr>
                </apex:repeat>              
            </apex:repeat>
        </apex:repeat>
    </table>
    </apex:form>
    </apex:page>
    

    Results in this...

    enter image description here

    Proposed Solution. So my solution revolves around flattening the nested lists into one and providing the bindings to control the layout in a single apex:repeat with simplified markup within. The pattern should allow you to retain your existing structure just be sure to produce this view of the data before rerendering the page. Of course if you want to adapt your internal viewstate structure this would make things easier.

    I extended the above controller to add this...

    public List<ViewRow> viewRows {get;set;}
    
    public class ViewRow
    {
        public Row row {get;set;}
        public String name {get;set;}
        public Integer level {get;set;}
        public Integer colspan {get;set;}
    }
    
    private void makeViewRows(List<Row> rows, Integer level)
    {
        for(Row row : rows)
        {       
            ViewRow viewRow = new ViewRow();
            viewRow.row = row;
            viewRow.name = row.isGrouping ?
                String.format('{0} ({1})', new String[] { row.Name, String.valueOf(row.children.size()) }) : row.Name;
            viewRow.level = level;
            viewRow.colSpan = row.isGrouping ? 
                (level == 1 ? 6 : 5) :
                (level == 2 ? 2 : 1); 
            viewRows.add(viewRow);
            if(row.isGrouping)
                makeViewRows(row.children, level + 1); 
        }
    }
    

    Then add these lines to the bottom of the constructor to produce the new list.

    // Produce a flat list to binding to
    viewRows = new List<ViewRow>();
    makeViewRows(rows, 1);
    

    My Visualforce page now looks like this.

    <apex:page controller="ViewStateTestController" sidebar="false">
    <style type="text/css">
        table { border-collapse:collapse; }
        table.myTable td, table.myTable th { border:1px solid black;padding:5px; }
        table.myTable td.padding { border:0px; }
    </style>
    <apex:form >
    <table class="myTable">
        <tr>
            <td><b>Select</b></td>
            <td colspan="3"><b>Name</b></td>
            <td><b>Data 1</b></td>
            <td><b>Data 2</b></td>
            <td><b>Data 3</b></td>
        </tr>
        <apex:repeat value="{!viewrows}" var="viewrow">
            <tr>
                <td><apex:inputCheckbox value="{!viewrow.row.selected}"/></td>
                <td style="display:{!IF(viewrow.level>=2, 'table-cell', 'none')}" class="padding">&nbsp;&nbsp;</td>         
                <td style="display:{!IF(viewrow.level>=3, 'table-cell', 'none')}" class="padding">&nbsp;&nbsp;</td>         
                <td colspan="{!viewrow.colspan}">{!viewrow.name}</td>
                <td style="display:{!IF(viewrow.row.isGrouping, 'none', 'table-cell')}">{!viewrow.row.data1}</td>
                <td style="display:{!IF(viewrow.row.isGrouping, 'none', 'table-cell')}">{!viewrow.row.data2}</td>
                <td style="display:{!IF(viewrow.row.isGrouping, 'none', 'table-cell')}">{!viewrow.row.data3}</td>               
            </tr>
        </apex:repeat>
    </table>
    </apex:form>
    </apex:page>
    

    Results in the following reduction on internal viewstate from 5.22kb (48%) to 2.85kb (34%).

    enter image description here

    With the apex:inputCheckbox component commented out, it reduces to 1.27kb (20%).

    enter image description here

    Summary: So it seems using the VF components in large tables can have a more noticeable impact on your internal view state. The general feeling I get is that VF is best at form entry, but struggles with lots of VF components (be they read or write) on the page. And large tables certainly stress this a lot more, especially in nested situations like this, where the output needs to vary from row to row. Some suggestions then...

    • Try to avoid having nested VF component related repeats / panels / rendered logic in the table in the VF page. In this case it seems to help to predetermine some of the rendering aspects in the view state as I have done in my proposed solution above. BTW I did notice at least one outputPanel that just wrapped a checkbox, so while that might give a small gain, I think that could be safely removed from your current solution.
    • Given this example is largely output driven, with mostly a single VF checkbox component per row it might be best to consider dropping VF components all together for the rows. And outputting a standard HTML checkbox. Then using JavaScript Remoting or a parameterised apex:actionFunction to get the selected rows back to the controller?
    • Building on the above, for total control you might want to consider building the entire table HTML in your controller Apex logic yourself. This can be output with apex:outputText, setting escape="false". Take a look at this for a brief explanation.
    • Consider if you need the apex:form tag, sometimes this is just carried over, copy paste style as general tags needed. But if you are not accepting any input via the traditional VF input components then you don't need it and hence you don't need viewstate.
    • Finally of course consider ways to help the users reduce the number of records displayed via a paging solution perhaps.

    Update: 22nd Jan, added point relating to use of apex:form tag.

    Update: 21st Feb, added further insight as to relative weight of internal viewstate based on field type and use of fieldsets. Thanks Ralph!

    "Ran into this in a couple more cases. One interesting conclusion is that the weight of apex:outputField varies by the field type, with lookups not surprisingly added a lot more view state relative to test fields. Also found that looping over field sets as for the columns also generated a lot of extra view state as opposed to hard-coded columns"

    Updated: 25th July 2013

    This answer from sfdcfox, represents another option (so long as you take into account the advice above as well). Basically he proposes using Dynamic Visualforce. For trees with variable depths this could be an option.

    Andrew, this is some amazing research, and sheds a lot of light into the mysterious "Internal" view state and I'm sure will help a lot of people who run into this down the line. Thank you!!

    also just to note: `style="display:{!IF(viewrow.level>=2, 'table-cell', 'none')}"` throws an error in saving HTML in the IDE. Pretty much try with any APEX output inside of style-tags in HTML. Thanks for the solution! Trying to implement some of this stuff right now.

    Thanks for the update Jordan, I didn't notice that when I wrote it, maybe different IDE version? Hope it helps you out. You also prompted me to add a further point around apex:form tag above.

    @AndrewFawcett fyi, ran into this in a couple more cases. One interesting conclusion is that the weight of apex:outputField varies by the field type, with lookups not surprisingly added a lot more view state relative to test fields. Also found that looping over field sets as for the columns also generated a lot of extra view state as opposed to hard-coded columns

    the form tag...

License under CC-BY-SA with attribution


Content dated before 7/24/2021 11:53 AM