[wp-trac] [WordPress Trac] #34560: Server error after saving many revisions of a large page in editor & possible fix

WordPress Trac noreply at wordpress.org
Mon Nov 2 23:42:29 UTC 2015


#34560: Server error after saving many revisions of a large page in editor &
possible fix
-------------------------+------------------------------------
 Reporter:  pdfernhout   |      Owner:
     Type:  enhancement  |     Status:  new
 Priority:  normal       |  Milestone:  Awaiting Review
Component:  Editor       |    Version:  4.3.1
 Severity:  normal       |   Keywords:  efficiency scalability
  Focuses:               |
-------------------------+------------------------------------
 **Version:** This issue was seen under WordPress 4.3.1, but it likely
 applies to any modern version of WordPress, even as far back as
 [https://github.com/WordPress/WordPress/blob/2.9-branch/wp-admin/edit-
 form-advanced.php 2.9] or maybe earlier.

 **Expected Behavior:** A WordPress user should be able to happily create
 thousands of revisions of pages (or blog posts) containing megabytes of
 HTML text, limited only by the amount of server memory needed to contain
 the specific edited revision of the page or post (minus baseline memory
 needs). So, for example, if you have a 256MB PHP memory limit, you should
 be able in theory to edit pages of 100MB or more for thousands of
 revisions (ignoring non-memory constraints like CPU usage, database
 configuration, communication timeouts, or browser-side limitations).
 Certainly you should be able to edit a page or post of under 1MB for
 hundreds of revisions given 256MB of allowed PHP memory (or even just with
 a 32MB memory limit given few plugins). While editing in WordPress will
 have limits based on available memory or CPU use or communications
 timeouts and the memory demand of the current revision being edited, those
 editing limits should not be substantially affected by the number of
 revisions of a post previously saved or the sizes of previously saved
 revisions.

 **Exhibited Behavior:** Right now, you will unhappily see "500 server
 error" and "white screen of death" results eventually if you, say, edit a
 400K page for hundreds of revisions, even with a 256MB server memory
 limit. Here is an example error from a server with 256MB memory limit
 resulting from trying to edit a page after saving about 350 revisions of a
 page that grew to be 437,042 bytes in size:
 {{{
 [02-Nov-2015 14:40:42 UTC] PHP Fatal error:  Allowed memory size of
 268435456 bytes exhausted (tried to allocate 437043 bytes) in
 /usr/home/narrafirma/public_html/narrafirma.com/wp-includes/wp-db.php on
 line 1389
 }}}

 **Steps to reproduce:** Create a new WordPress page. Put 500K or so of
 text in it (or any other large amount, the smaller the amount, the more
 revisions needed to be saved). Save the page for hundreds of revisions.
 Failure will occur when the number of revisions times the post size
 exceeds the PHP available memory minus the core memory need of
 WordPress+plugins. To speed this failure process, reduce the memory
 available to PHP.

 **Likely cause:** The file edit-form-advanced.php has the following
 section of code which retrieves all revisions for a post using
 wp_get_post_revisions and then counts the result (twice) to display a
 count of revisions on the page. While the current approach has a certain
 elegant simplicity and is easy to understand, the approach is
 unfortunately computationally inefficient because retrieving all the
 revisions of a post just to count them potentially consumes a lot of
 memory, a lot of CPU time, and a lot of database bandwidth when there are
 many revisions of a post if some are of significant size.

 From [https://github.com/WordPress/WordPress/blob/4.3.1/wp-admin/edit-
 form-advanced.php edit-form-advanced.php (4.3.1)]:
 {{{
 $publish_callback_args = null;
 if ( post_type_supports($post_type, 'revisions') && 'auto-draft' !=
 $post->post_status ) {
         $revisions = wp_get_post_revisions( $post_ID );

         // We should aim to show the revisions metabox only when there are
 revisions.
         if ( count( $revisions ) > 1 ) {
                 reset( $revisions ); // Reset pointer for key()
                 $publish_callback_args = array( 'revisions_count' =>
 count( $revisions ), 'revision_id' => key( $revisions ) );
                 add_meta_box('revisionsdiv', __('Revisions'),
 'post_revisions_meta_box', null, 'normal', 'core');
         }
 }
 }}}
 **Suggested fix:** Retrieve the count of revisions in some other way with
 custom SQL. Alternatively, add an option in when retrieving revisions via
 "wp_get_post_revisions" (in "wp-includes/revision.php") to only retrieve
 the metadata for the revision (like size, timestamp, user, and so on) and
 use that option in this code. Incidentally, another minor optimization for
 this code fragment may be to store the count of revisions in the code
 above so the counting process is not done twice (depending perhaps on how
 count is implemented versus variable allocation costs in PHP).

 **Caveats:** I have only tested this error condition for a page, but I am
 assuming this issue will apply to blog posts as well (or any kind of posts
 with revisions). I am not sure what other implications there would be from
 such a change, like if the revisions' contents were needed elsewhere later
 for comparisons -- although such a situation could potentially be dealt
 with by selectively loading specific revision's contents as needed for
 diff comparisons, even if all the metadata might be needed up front.

 **Other potential benefits of the proposed fix:** Beyond avoiding server
 errors and white screens, making the proposed change may speed up opening
 long posts and pages for editing by reducing server load substantially.
 When editing even small posts with a few revisions, this change will still
 likely speed up the initial opening of the editor at least slightly.

 **Use cases:** An example situations of a large page with multiple
 revisions is where a team uses a WordPress page to track component version
 deployment status of a large system. Another use case is when a WordPress
 page grows into a book-length document through repeated editing by someone
 :-) who is also used to saving a lot to avoid losing work from browser
 crashes.

 **Additional suggestion for further code review:** All uses of
 "wp_get_post_revisions" and any similar functions, as well as all uses of
 "count", could be reviewed in the WordPress codebase looking for similar
 computational inefficiencies.

--
Ticket URL: <https://core.trac.wordpress.org/ticket/34560>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list