@Override
  public void put(@NotNull K key,
                  @Nullable Set<VItem> values) throws IOException {
    int keyHash = keyDescriptor.getHashCode(key);
    int adjustedKeyHash = HashUtils.adjustHash(keyHash);

    DataExternalizerEx.KnownSizeRecordWriter keyWriter = keyDescriptor.writerFor(key);
    int keySize = keyWriter.recordSize();
    int keyRecordSize = keySize + Integer.BYTES;

    if (values == null) {
      ChunkedAppendOnlyLog.LogChunk chunk = keyValuesLog.append(keyRecordSize, /*reserveNextChunkIdField: */ false);
      chunk.append(
        buffer -> {
          putHeader(buffer, keySize, /*empty: */ true);
          return keyWriter.write(buffer.slice(Integer.BYTES, keySize)
                                   .order(buffer.order()));
        },
        keyRecordSize
      );
      synchronized (keyHashToChunkIdMap) {
        int foundRecordId = lookupRecordId(key, adjustedKeyHash);
        if (foundRecordId != DurableIntToMultiIntMap.NO_VALUE) {
          keyHashToChunkIdMap.remove(adjustedKeyHash, foundRecordId);
        }
        else {
          // nothing -- key didn't exist before
          // MAYBE RC: skip appending <null> record to the keyValuesLog if there wasn't a non-null record before?
        }
      }
      return;
    }

    //FIXME RC: look up for already existing chunk first!

    //TODO RC: how to guess reasonable initial chunkSize?
    int chunkSize = Math.max(keyRecordSize, 512);
    ChunkedAppendOnlyLog.LogChunk chunk = keyValuesLog.append(chunkSize, /*reserveNextChunkIdField: */ true);
    chunk.append(
      buffer -> {
        putHeader(buffer, keySize, /*empty: */false);
        return keyWriter.write(
          buffer.slice(Integer.BYTES, keySize)
            .order(buffer.order())
        );
      },
      keyRecordSize
    );

    ItemsImpl items = new ItemsImpl(chunk);
    for (VItem item : values) {
      items.append(item);
    }

    int newRecordId = convertChunkIdToStoredId(chunk.id());
    synchronized (keyHashToChunkIdMap) {
      int foundRecordId = lookupRecordId(key, adjustedKeyHash);
      if (foundRecordId != DurableIntToMultiIntMap.NO_VALUE) {
        keyHashToChunkIdMap.replace(adjustedKeyHash, foundRecordId, newRecordId);
      }
      else {
        keyHashToChunkIdMap.put(adjustedKeyHash, newRecordId);
      }
    }
  }
@Override
  public void put(@NotNull K key,
                  @Nullable Set<VItem> values)