package com.xiaomi.infra.galaxy.sds.client;
import com.xiaomi.infra.galaxy.sds.thrift.Datum;
import com.xiaomi.infra.galaxy.sds.thrift.ErrorCode;
import com.xiaomi.infra.galaxy.sds.thrift.ScanRequest;
import com.xiaomi.infra.galaxy.sds.thrift.ScanResult;
import com.xiaomi.infra.galaxy.sds.thrift.TableService.Iface;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
public class TableScanner implements Iterable<Map<String, Datum>> {
private final Iface tableClient;
private final ScanRequest scan;
public TableScanner(Iface tableClient, ScanRequest scan) {
this.tableClient = tableClient;
this.scan = scan;
}
@Override
public Iterator<Map<String, Datum>> iterator() {
return new RecordIterator(tableClient, scan.deepCopy());
}
class RecordIterator implements Iterator<Map<String, Datum>> {
private final Iface tableClient;
private final ScanRequest scan;
private boolean finished = false;
private int retry = 0;
private final static int MAX_RETRY = 256;
private Iterator<Map<String, Datum>> bufferIterator = null;
private ScanResult lastResult = null;
private ThreadLocal<Long> lastPauseTime = new ThreadLocal<Long>() {
public Long initialValue() {
return 0l;
}
};
public RecordIterator(Iface tableClient, ScanRequest scan) {
this.tableClient = tableClient;
this.scan = scan;
}
@Override
public boolean hasNext() {
if (bufferIterator != null && bufferIterator.hasNext()) {
return true;
} else {
if (finished) {
return false;
} else {
if (retry > 0) {
// continue the last unfinished scan request
if (lastResult != null && lastResult.isThrottled()) {
// throttle scan qps quota
long pauseTime = ThrottleUtils.getPauseTime(ErrorCode.THROUGHPUT_EXCEED, retry);
ThrottleUtils.sleepPauseTime(pauseTime);
lastPauseTime.set(pauseTime < 0 ? 0 : pauseTime);
}
} else {
// start a new scan request
assert retry == 0;
long pauseTime = ThrottleUtils.getPauseTime(lastPauseTime.get());
ThrottleUtils.sleepPauseTime(pauseTime);
lastPauseTime.set(pauseTime < 0 ? 0 : pauseTime);
}
ScanResult result = null;
try {
result = tableClient.scan(scan);
} catch (Throwable e) {
throw new RuntimeException("Scan request " + scan + " failed", e);
}
if (result.getRecords() != null) {
List<Map<String, Datum>> buffer = result.getRecords();
bufferIterator = buffer.iterator();
}
if (result.getNextStartKey() == null || result.getNextStartKey().isEmpty()) {
// finish the whole scan request
finished = true;
} else {
if (result.getRecordsSize() < scan.getLimit() && result.isThrottled()) {
// two possible cases: qps quota exceeds or scan limit is too large
retry++;
if (retry > MAX_RETRY) {
throw new RuntimeException("Scan request " + scan + " failed with "
+ retry + " retries");
}
} else {
// finish the current sub scan request
retry = 0;
}
lastResult = result;
scan.setStartKey(result.getNextStartKey());
}
return hasNext();
}
}
}
@Override
public Map<String, Datum> next() throws NoSuchElementException {
if (hasNext()) {
return bufferIterator.next();
} else {
throw new NoSuchElementException("Scanner reaches the end");
}
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove is not supported");
}
}
}