java.lang.Object org.apache.derby.impl.store.raw.data.ReclaimSpaceHelperThis class helps a BaseDataFactory reclaims unused space. Space needs to be reclaimed in the following cases:
We can implement a lot of optimization if we know that btree does not overflow. However, since that is not the case and Raw Store cannot tell if it is dealing with a btree page or a heap page, they all have to be treated gingerly. E.g., in heap page, once a head row is deleted (via a delete operation or via a rollback of insert), all the long rows and long columns can be reclaimed - in fact, most of the head row can be removed and reclaimed, only a row stub needs to remain for locking purposes. But in the btree, a deleted row still needs to contain the key values so it cannot be cleaned up until the row is purged.
Row with long columns or long row is deleted
When Access purge a committed deleted row, the purge operation will see if the row has overflowed row pieces or if it has long columns. If it has, then all the long columns and row pieces are purged before the head row piece can be purged. When a row is purged from an overflow page and it is the only row on the page, then the page is deallocated in the same transaction. Note that non-overflow pages are removed by Access but overflow pages are removed by Raw Store. Note that page removal is done in the same transaction and not post commit. This is, in general, dangerous because if the transaction does not commit for a long time, uncommit deallocated page slows down page allocation for this container. However, we know that access only purges committed delete row in access post commit processing so we know the transaction will tend to commit relatively fast. The alternative is to queue up a post commit ReclaimSpace.PAGE to reclaim the page after the purge commits. In order to do that, the time stamp of the page must also be remembered because post commit work may be queued more than once, but in this case, it can only be done once. Also, doing the page deallocation post commit adds to the overall cost and tends to fill up the post commit queue.
This approach is simple but has the drawback that the entire long row and all the long columns are logged in the purge operation. The alternative is more complicated, we can remember all the long columns on the head row piece and where the row chain starts and clean them up during post commit. During post commit, because the head row piece is already purged, there is no need to log the long column or the long rows, just wipe the page or just reuse the page if that is the only thing on the page. The problem with this approach is that we need to make sure the purging of the head row does indeed commit (the transaction may commit but the purging may be rolled back due to savepoint). So, we need to find the head row in the post commit and only when we cannot find it can we be sure that the purge is committed. However, in cases where the page can reuse its record Id (namely in btree), a new row may reuse the same recordId. In that case, the post commit can purge the long columns or the rest of the row piece only if the head piece no longer points to it. Because of the complexity of this latter approach, the first simple approach is used. However, if the performance due to extra logging becomes unbearble, we can consider implementing the second approach.
Insertion of a row with long column or long row is rolled back.
Insertion can be rolled back with either delete or purge. If the row is rolled back with purge, then all the overflow columns pieces and row pieces are also rolled back with purge. When a row is purged from an overflow page and it is the only row on the page, then a post commit ReclaimSpace.PAGE work is queued by Raw Store to reclaim that page.
If the row is rolled back with delete, then all the overflow columns pieces and row pieces are also rolled back with delete. Access will purge the deleted row in due time, see above.
Row is updated and the head row or some row pieces shrunk
Every page that an update operation touches will see if the record on that page has any reserve space. It it does, and if the reserve space plus the record size exceed the mininum record size, then a post commit ROW_RESERVE work will be queued to reclaim all unnecessary row reserved space for the entire row.
Row is updated and old long columns are orphaned
The ground rule is, whether a column is a long column or not before an update has nothing to do with whether a column will be a long column or not after the update. In other words, update can turn a non-long column into a long column, or it can turn a long column into a non-long column, or a long column can be updated to another long column and a non-long column can be updated to a non-long column. The last case - update of a non-long column to another non-long column - is only of concern if it shrinks the row piece it is on (see above).
So update can be looked at as 2 separate problems: A) a column is a long column before the update and the update will "orphaned" it. B) a column is a long column after the update and the rollback of the update will "orphaned" it if it is rolled back with a delete. This section deals with problem A, next section deals with problem B.
Update specifies a set of columns to be updated. If a row piece contains one or more columns to be updated, those columns are examined to see if they are actually long column chains. If they are, then after the update, those long column chains will be orphaned. So before the update happens, a post commit ReclaimSpace.COLUMN_CHAIN work is queued which contains the head rows id, the column number, the location of the first piece of the column chain, and the time stamp of the first page of the column chain.
If the update transaction commits, the post commit work will walk the row until it finds the column number (note that it may not be on the page where the update happened because of subsequent row splitting), and if it doesn't point to the head of the column chain, we know the update operation has indeed committed (versus rolled back by a savepoint). If a piece of the the column chain takes up an entire page, then the entire page can be reclaimed without first purging the row because the column chain is already orphaned.
We need to page time stamp of the first page of the column chain because if the post commit ReclaimSpace.COLUMN_CHAIN is queued more than once, as can happen in repeated rollback to savepoint, then after the first time the column is reclaimed, the pages in the column chain can be reused. Therefore, we cannot reclaim the column chain again. Since there is no back pointer from the column chain to the head row, we need the timestamp to tell us if that column chain has already been touched (reclaimed) or not.
Row is updated with new long columns and update is rolled back.
When the update is rolled back, the new long columns, which got there by insertion, got rolled back either by delete or by purge. If they were rolled back with delete, then they will be orphaned and need to be cleaned up with post abort work. Therefore, insertion of long columns due to update must be rolled back with purge.
This is safe because the moment the rollback of the head row piece happens, the new long column is orphaned anyway and nobody will be able to get to it. Since we don't attempt to share long column pages, we know that nobody else could be on the page and it is safe to deallocate the page.
Row is updated with new long row piece and update is rolled back.
When the update is rolled back, the new long row piece, which got there by insertion, got rolled back either by delete or by purge. Like update with new long row, they should be rolled back with purge. However, there is a problem in that the insert log record does not contain the head row handle. It is possible that another long row emanating from the same head page overflows to this page. That row may since have been deleted and is now in the middle of a purge, but the purge has not commit. To the code that is rolling back the insert (caused by the update that split off a new row piece) the overflow page looks empty. If it went ahead and deallocate the page, then the transaction which purged the row piece on this page won't be able to roll back. For this reason, the rollback to insert of a long row piece due to update must be rolled back with delete. Furthermore, there is no easy way to lodge a post termination work to reclaim this deleted row piece so it will be lost forever.
RESOLVE: need to log the head row's handle in the insert log record, i.e., any insert due to update of long row or column piece should have the head row's handle on it so that when the insert is rolled back with purge, and there is no more row on the page, it can file a post commit to reclaim the page safely. The post commit reclaim page needs to lock the head row and latch the head page to make sure the entire row chain is stable.
Method from org.apache.derby.impl.store.raw.data.ReclaimSpaceHelper Summary:
reclaimSpace Methods from java.lang.Object:
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait Method from org.apache.derby.impl.store.raw.data.ReclaimSpaceHelper Detail:
public static int reclaimSpace(BaseDataFileFactory dataFactory,
ReclaimSpace work) throws StandardException
Reclaim space based on work.