Replacing a page with Copy Single Page API fails with OptimisticLockException

I have been playing around with the new Copy Single Page API.

When using it to replace an existing page using the existing_page destination, I am frequently encountering the following error: javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1.

I would say that the error occurs around 50% of the time. Simply catching this error and then retrying the same operation works as a workaround, and so far the second try has always succeeded.

I have never seen the error coming up when using the parent_page destination though.

1 Like

What’s the HTTP status code you get on this? Is it at 409 (conflict) like you’d expect? When I see these occur in other REST APIs (e.g., /content during page content updates or page deletions) they annoyingly show up as 500 errors.

The status code is 500.

1 Like

@candid - can you share your POST body? Also, anything special about the source page or destination page that makes it more likely to throw an error? No luck reproducing on our end yet.

Interestingly, sometimes the error message is javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1, while other times it is com.atlassian.confluence.core.persistence.confluence.StaleObjectStateException: javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 (notice the different prefix).

Here is an example request:

{
  "copyAttachments" : true,
  "copyPermissions" : false,
  "copyProperties" : false,
  "copyLabels" : false,
  "copyCustomContents" : false,
  "destination" : {
    "type" : "existing_page",
    "value" : "1472889087"
  },
  "pageTitle" : "Version (v23) of Attachment includes",
  "body" : {
    "storage" : {
      "value" : "<ac:image ac:align=\"center\" ac:layout=\"center\" ac:original-height=\"1440\" ac:original-width=\"1080\"><ri:attachment ri:filename=\"a.jpg\" ri:version-at-save=\"1\" /></ac:image><p /><ac:image ac:align=\"center\" ac:layout=\"center\" ac:original-height=\"480\" ac:original-width=\"480\"><ri:attachment ri:filename=\"a_2.jpg\" ri:version-at-save=\"1\" /></ac:image><p />",
      "representation" : "storage"
    },
    "raw" : null
  }
}

Note that we are using this endpoint only to copy attachments. So the source page has attachments, and the body specified in the copy request is equal to the body of the destination page (since we don’t want to change the page, we only want to copy the attachments). Not sure these things are related to the problem.

It might also be noteworthy that the destination page was created just seconds before this call using a copy operation with type parent_page.

Right now when I was testing, the error occurred about once in every 5 copy operations.

1 Like

When we have seen these errors in the past it was due to rapidly/concurrently modifying aspects of a single page’s children. For example, concurrently either trying to delete a single page’s child pages, or renaming a single page’s child pages. We ended up implementing a retry scheme when we encountered 500 errors containing the OptimisticLockException string, but but for follow-on reasons that I forget the details of, we ended up abandoning attempts to use concurrent updates and switched to making them serially only. Perhaps this new API is similarly copying attachments concurrently and running into a similar issue?

We are not making any concurrent requests, but of course it could be that after copying the page, something in still happening in the Confluence backend in background, which sometimes is still running when we run the second copy operation, and that causes the error.

Sorry, I meant to imply that perhaps the copy page API itself is copying the attachments concurrently, running into this error internally. I also agree that it’s possible that modifying the same page again too soon after the first call is encountering a background conflict. Pure speculation on my part, though.

In case anyone else experiences this, the error messages that we are seeing now have slightly changed:

javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

It happens every couple of times we try to replace a page using the POST /rest/api/content/{contentId}/copy, but one customer has also reported that in some cases it happens during PUT /rest/api/content/{contentId}/child/attachment/{attachmentId} and DELETE /rest/api/content/{id} (deleting an attachment).

What is common to these cases is that we are working with a page that has been created just seconds before. We are using the Copy Single Page API with copyAttachments to replace the page that we have just created (actually we don’t really replace it, we keep the body the same but use that API to copy all the attachments from another page over), and then we use the update and delete endpoints to rename some of the attachments and to delete some others.

I suspect that after the page is created, Confluence runs some background tasks with that page or its attachments. Maybe sometimes it happens that such a background task does an operation while we are making an API call, which creates a conflict that leads to this error.

Anyways, we implemented a workaround that retries a couple of times in case it receives an error that contains the string javax.persistence.OptimisticLockException. For the Copy API, this workaround has been working reliably for almost a year now. For the update/delete attachment API, we will see whether the workaround also solves the problem for our customer.

1 Like

We are seeing yet another error message that is probably related:

com.atlassian.confluence.api.service.exceptions.ConflictException: Object of class [com.atlassian.confluence.pages.Page] with identifier [698515582]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.atlassian.confluence.pages.Page#698515582]

We will add this to our retry workaround and see whether it works.