Shinya Yoshida created HBASE-26238:
--------------------------------------

             Summary: OOME in VerifyReplication for the table contains rows 
with 10M+ cells
                 Key: HBASE-26238
                 URL: https://issues.apache.org/jira/browse/HBASE-26238
             Project: HBase
          Issue Type: Bug
            Reporter: Shinya Yoshida
            Assignee: Shinya Yoshida


We have a table that contains rows with 10M+ cells.

When we run VerifyReplication for that table, we got OOME.
VerifyReplication cannot be complete without OOME even though we provide 31GB 
heap for each mapper despite of RS can handle such get request.

{code:java}
org.apache.hadoop.mapred.YarnChild: Error running child : 
java.lang.OutOfMemoryError
        at 
java.lang.AbstractStringBuilder.hugeCapacity(AbstractStringBuilder.java:161)
        at 
java.lang.AbstractStringBuilder.newCapacity(AbstractStringBuilder.java:155)
        at 
java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:125)
        at 
java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
        at java.lang.StringBuilder.append(StringBuilder.java:136)
        at org.apache.hadoop.hbase.client.Result.compareResults(Result.java:844)
        at 
org.apache.hadoop.hbase.mapreduce.replication.VerifyReplication$Verifier.map(VerifyReplication.java:184)
        at 
org.apache.hadoop.hbase.mapreduce.replication.VerifyReplication$Verifier.map(VerifyReplication.java:95)
        at org.apache.hadoop.mapreduce.Mapper.run(Mapper.java:146)
        at org.apache.hadoop.mapred.MapTask.runNewMapper(MapTask.java:787)
        at org.apache.hadoop.mapred.MapTask.run(MapTask.java:341)
        at org.apache.hadoop.mapred.YarnChild$2.run(YarnChild.java:164)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:422)
        at 
org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1657)
        at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:158)
{code}

The interesting thing is always failing at 
AbstractStringBuilder.hugeCapacity(AbstractStringBuilder.java:161) in 
Result.compareResults.
This is an application-side OOME and caused by the max size of Java array, so 
we cannot avoid this error whatever heap size we use.

{code:java}
    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }
{code}

When we see Result.compareResults, it generates a string representation of all 
cells in 2 results for the exception message.
This could be a very large string despite cells in the result could consist 
from multiple byte arrays and are well optimized in the heap.
(Repeated rowkeys in string, 4 chars for 1 byte if it cannot be represented by 
ascii char, long timestamp(8 bytes) vs string timestamp(13 bytes so far), and 
so on.)

{code:java}
  public static void compareResults(Result res1, Result res2)
      throws Exception {
    if (res2 == null) {
      throw new Exception("There wasn't enough rows, we stopped at "
          + Bytes.toStringBinary(res1.getRow()));
    }
    if (res1.size() != res2.size()) {
      throw new Exception("This row doesn't have the same number of KVs: "
          + res1.toString() + " compared to " + res2.toString());
    }
    Cell[] ourKVs = res1.rawCells();
    Cell[] replicatedKVs = res2.rawCells();
    for (int i = 0; i < res1.size(); i++) {
      if (!ourKVs[i].equals(replicatedKVs[i]) ||
          !CellUtil.matchingValue(ourKVs[i], replicatedKVs[i]) ||
          !CellUtil.matchingTags(ourKVs[i], replicatedKVs[i])) {
        throw new Exception("This result was different: "
            + res1.toString() + " compared to " + res2.toString());
      }
    }
  }
{code}

In VerifyReplication, the exception thrown is never used.
So this message is useless and a white elephant for VerifyReplication and us.
Can we provide a version that produces lightweight message (or returns boolean 
instead of exception, but I think it's confusing similar method that returns 
boolean and another throws exception)?
(Such hot row can have mutations often and be considered as inconsistent due to 
timing issues, so difficult to avoid this OOME by just a luck)

{code:java}
          try {
            Result.compareResults(value, currentCompareRowInPeerTable);
            context.getCounter(Counters.GOODROWS).increment(1);
            if (verbose) {
              LOG.info("Good row key: " + delimiter
                  + Bytes.toStringBinary(value.getRow()) + delimiter);
            }
          } catch (Exception e) {
            logFailRowAndIncreaseCounter(context, 
Counters.CONTENT_DIFFERENT_ROWS, value);
          }



        try {
          Result sourceResult = sourceTable.get(new Get(row.getRow()));
          Result replicatedResult = replicatedTable.get(new Get(row.getRow()));
          Result.compareResults(sourceResult, replicatedResult);
          if (!sourceResult.isEmpty()) {
            context.getCounter(Counters.GOODROWS).increment(1);
            if (verbose) {
              LOG.info("Good row key (with recompare): " + delimiter + 
Bytes.toStringBinary(row.getRow())
              + delimiter);
            }
          }
          return;
        } catch (Exception e) {
          LOG.error("recompare fail after sleep, rowkey=" + delimiter +
              Bytes.toStringBinary(row.getRow()) + delimiter);
        }
{code}

VerifyReplication with the patch that changes the message to include rowkey 
only can handle our table without OOME and evel smaller heap.



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

Reply via email to