@Override
  public int writeToRecord(int recordId,
                           @NotNull ByteBufferWriter writer,
                           int expectedRecordSizeHint,
                           boolean leaveRedirectOnRecordRelocation) throws IOException {
    //insert new record?
    if (!isValidRecordId(recordId)) {
      ByteBuffer temp = acquireTemporaryBuffer(expectedRecordSizeHint);
      try {
        ByteBuffer bufferWithData = writer.write(temp);
        bufferWithData.flip();

        int recordLength = bufferWithData.limit();
        checkLengthHardLimit(recordLength);
        if (recordLength > maxCapacityForPageSize) {
          throw new IllegalStateException(
            "recordLength(=" + recordLength + ") > maxCapacityForPageSize(=" + maxCapacityForPageSize + ") -- can't fit");
        }

        int capacity = bufferWithData.capacity();
        //Don't check capacity right here -- let allocation strategy first decide how to deal with capacity > MAX
        int requestedRecordCapacity = allocationStrategy.capacity(
          recordLength,
          capacity
        );

        if (requestedRecordCapacity < recordLength) {
          throw new IllegalStateException(
            "Allocation strategy " + allocationStrategy + "(" + recordLength + ", " + capacity + ")" +
            " returns " + requestedRecordCapacity + " < length(=" + recordLength + ")");
        }

        return writeToNewlyAllocatedRecord(bufferWithData, requestedRecordCapacity);
      }
      finally {
        releaseTemporaryBuffer(temp);
      }
    }

    //already existent record
    int currentRecordId = recordId;
    for (int i = 0; i < MAX_REDIRECTS; i++) {
      long recordOffset = idToOffset(currentRecordId);
      int offsetOnPage = storage.toOffsetInPage(recordOffset);
      Page page = storage.pageByOffset(recordOffset);
      ByteBuffer buffer = page.rawPageBuffer();
      RecordLayout recordLayout = RecordLayout.recordLayout(buffer, offsetOnPage);
      byte recordType = recordLayout.recordType();
      if (recordType == RecordLayout.RECORD_TYPE_MOVED) {
        int redirectToId = recordLayout.redirectToId(buffer, offsetOnPage);
        checkRedirectToId(recordId, currentRecordId, redirectToId);
        currentRecordId = redirectToId;
        continue;//hope redirect chains are not too long...
      }
      if (recordType != RecordLayout.RECORD_TYPE_ACTUAL) {
        throw new AssertionError("RecordType(" + recordType + ") should not appear in the chain: " +
                                 "it is either not implemented yet, or all wrong");
      }
      int recordCapacity = recordLayout.capacity(buffer, offsetOnPage);
      int recordActualLength = recordLayout.length(buffer, offsetOnPage);
      //TODO RC: consider 'expectedRecordSizeHint' here? I.e. if expectedRecordSizeHint>record.capacity -> allocate heap buffer
      //         of the size asked, copy actual record content into it?
      int recordPayloadOffset = offsetOnPage + recordLayout.headerSize();
      ByteBuffer recordContent = buffer.slice(recordPayloadOffset, recordCapacity)
        .limit(recordActualLength)
        .order(buffer.order());

      ByteBuffer newRecordContent = writer.write(recordContent);
      if (newRecordContent == null) {
        //returned null means writer decides to skip write -> just return current recordId
        return currentRecordId;
      }

      if (newRecordContent != recordContent) {//writer decides to allocate new buffer for content:
        newRecordContent.flip();
        int newRecordLength = newRecordContent.remaining();
        if (newRecordLength <= recordCapacity) {
          //RC: really, in this case writer should just write data right in the 'recordContent'
          //    buffer, not allocate the new buffer -- but ok, we could deal with it:
          recordLayout.putRecord(buffer, offsetOnPage,
                                 recordCapacity, newRecordLength, NULL_ID, newRecordContent);

          totalLiveRecordsPayloadBytes.addAndGet(newRecordLength - recordActualLength);
        }
        else {//current record is too small for new content -> relocate to a new place
          int newRecordCapacity = allocationStrategy.capacity(newRecordLength, newRecordContent.capacity());
          int newRecordId = writeToNewlyAllocatedRecord(newRecordContent, newRecordCapacity);

          RecordLayout.MovedRecord movedRecordLayout = RecordLayout.MovedRecord.INSTANCE;
          //mark current record as either 'moved' or 'deleted'
          int redirectToId = leaveRedirectOnRecordRelocation ? newRecordId : NULL_ID;
          //Total space occupied by record must remain constant, but record capacity should be
          // changed since MovedRecord has another headerSize than Small|LargeRecord
          int movedRecordCapacity = recordLayout.fullRecordSize(recordCapacity) - movedRecordLayout.headerSize();
          movedRecordLayout.putRecord(buffer, offsetOnPage, movedRecordCapacity, 0, redirectToId, null);

          totalLiveRecordsPayloadBytes.addAndGet(-recordActualLength);
          totalLiveRecordsCapacityBytes.addAndGet(-recordCapacity);
          if (leaveRedirectOnRecordRelocation) {
            recordsRelocated.incrementAndGet();
          }
          else {
            recordsDeleted.incrementAndGet();
          }

          return newRecordId;
        }
      }
      else {//if newRecordContent is null or == recordContent -> changes are already written by writer into the recordContent,
        // we only need to adjust record header:
        recordContent.flip();
        int newRecordLength = recordContent.remaining();
        assert (newRecordLength <= recordCapacity) : newRecordLength + " > " + recordCapacity +
                                                     ": can't be, since recordContent.capacity()==recordCapacity!";
        recordLayout.putLength(buffer, offsetOnPage, newRecordLength);


        totalLiveRecordsPayloadBytes.addAndGet(newRecordLength - recordActualLength);
      }
      return currentRecordId;
    }
    throw new IOException("record[" + recordId + "].redirectTo chain is too long (>=" + MAX_REDIRECTS + "): circular reference?");
  }
@Override
  public int writeToRecord(int recordId,
                           @NotNull ByteBufferWriter writer,
                           int expectedRecordSizeHint,
                           boolean leaveRedirectOnRecordRelocation)