diff --git a/pom.xml b/pom.xml
index 83e23cb..73733c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,12 +4,11 @@
   <modelVersion>4.0.0</modelVersion>  
   <groupId>com.aliyun.openservices</groupId>  
   <artifactId>ots-public</artifactId>  
-  <version>2.2.5</version>  
+  <version>2.2.10</version>
   <packaging>jar</packaging>  
   <name>Aliyun Open Services SDK for Java</name>  
   <url>http://www.aliyun.com</url>  
   <description>Aliyun Open Services SDK for Java Copyright (C) Alibaba Cloud Computing All rights reserved. 版权所有 （C）阿里云计算有限公司 http://www.aliyun.com</description>  
-
   <licenses>
     <license>
       <name>The Apache Software License, Version 2.0</name>
@@ -27,19 +26,16 @@
       <email>aliyunsdk@aliyun.com</email>
     </developer>
   </developers>
-
   <distributionManagement>
-    <snapshotRepository>
-      <id>sonatype-nexus-snapshots</id>
-      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
-    </snapshotRepository>
-    <repository>
-      <id>sonatype-nexus-staging</id>
-      <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
-    </repository>
+        <snapshotRepository>
+            <id>sonatype-nexus-snapshots</id>
+            <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
+        </snapshotRepository>
+        <repository>
+            <id>sonatype-nexus-staging</id>
+            <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+        </repository>
   </distributionManagement>
-
-
   <dependencies> 
     <dependency> 
       <groupId>junit</groupId>  
@@ -55,7 +51,7 @@
     <dependency> 
       <groupId>org.apache.httpcomponents</groupId>  
       <artifactId>httpasyncclient</artifactId>  
-      <version>4.0.2</version> 
+      <version>4.1</version>
     </dependency>  
     <dependency> 
       <groupId>joda-time</groupId>  
@@ -186,6 +182,8 @@
               <exclude>org.apache.logging.log4j:log4j-core:jar:</exclude>  
               <exclude>org.apache.logging.log4j:log4j-slf4j-impl:jar:</exclude>  
               <exclude>org.slf4j:slf4j-api:jar:</exclude> 
+              <exclude>com.lmax:disruptor:jar:</exclude>
+              <exclude>commons-codec:commons-codec:jar:</exclude>
             </excludes> 
           </artifactSet>  
           <relocations> 
@@ -227,6 +225,31 @@
           </execution> 
         </executions> 
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-gpg-plugin</artifactId>
+        <version>1.5</version>
+        <executions>
+          <execution>
+            <id>sign-artifacts</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>sign</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.sonatype.plugins</groupId>
+        <artifactId>nexus-staging-maven-plugin</artifactId>
+        <version>1.6.3</version>
+        <extensions>true</extensions>
+        <configuration>
+        	<serverId>sonatype-nexus-staging</serverId>
+        	<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
+        	<autoReleaseAfterClose>true</autoReleaseAfterClose>
+        </configuration>
+      </plugin>
     </plugins> 
   </build>  
 </project>
diff --git a/src/main/java/com/aliyun/openservices/ots/ClientConfiguration.java b/src/main/java/com/aliyun/openservices/ots/ClientConfiguration.java
index 36c0ac1..f966ff8 100755
--- a/src/main/java/com/aliyun/openservices/ots/ClientConfiguration.java
+++ b/src/main/java/com/aliyun/openservices/ots/ClientConfiguration.java
@@ -23,8 +23,11 @@ public class ClientConfiguration {
     private int ioThreadCount = AVAILABLE_PROCESSORS;
     private int maxConnections = 300;
     private int socketTimeoutInMillisecond = 15 * 1000;
+    // default value = 5 000, but will be reset to default value if > 15 000
+    private int idleConnectionClosePeriodInMillisecond = 5 * 1000;
     private int connectionTimeoutInMillisecond = 15 * 1000;
     private int retryThreadCount = 1;
+    private int selectInterval = 1000;
     
     
     /**
@@ -258,4 +261,24 @@ public void setRetryThreadCount(int retryThreadCount) {
         this.retryThreadCount = retryThreadCount;
     }
 
+    /**
+     * 设置底层异步IO层被唤醒去检查连接或请求超时的时间间隔，这个值若比设置的 connectionTimeout 或 socketTimeout 大，将导致实际超时时间比设置的阈值更高。
+     *
+     * @param selectInterval
+     */
+    public void setSelectInterval(int selectInterval) {
+        this.selectInterval = selectInterval;
+    }
+
+    public int getSelectInterval() {
+        return this.selectInterval;
+    }
+
+    public int getIdleConnectionClosePeriodInMillisecond() {
+        return idleConnectionClosePeriodInMillisecond;
+    }
+
+    public void setIdleConnectionClosePeriodInMillisecond(int idleConnectionClosePeriodInMillisecond) {
+        this.idleConnectionClosePeriodInMillisecond = idleConnectionClosePeriodInMillisecond;
+    }
 }
diff --git a/src/main/java/com/aliyun/openservices/ots/DefaultOTSWriter.java b/src/main/java/com/aliyun/openservices/ots/DefaultOTSWriter.java
index 0885e46..b07a3dc 100644
--- a/src/main/java/com/aliyun/openservices/ots/DefaultOTSWriter.java
+++ b/src/main/java/com/aliyun/openservices/ots/DefaultOTSWriter.java
@@ -35,8 +35,6 @@ public class DefaultOTSWriter implements OTSWriter {
 
     private Timer flushTimer;
 
-    private ReentrantLock lock;
-
     private Disruptor<RowChangeEvent> disruptor;
 
     private RingBuffer<RowChangeEvent> ringBuffer;
@@ -55,7 +53,6 @@ public DefaultOTSWriter(OTSAsync ots, String tableName, WriterConfig config, OTS
         this.callback = callback;
         this.executor = executor;
         flushTimer = new Timer();
-        lock = new ReentrantLock();
 
         initialize();
     }
@@ -119,12 +116,12 @@ public void addRowChange(RowChange rowChange) {
         }
     }
 
-    private void addSignal(ReentrantLock lock, Condition condition) {
+    private void addSignal(CountDownLatch latch) {
         while (true) {
             try {
                 long sequence = ringBuffer.tryNext();
                 RowChangeEvent event = ringBuffer.get(sequence);
-                event.setValue(lock, condition);
+                event.setValue(latch);
                 ringBuffer.publish(sequence);
                 return;
             } catch (InsufficientCapacityException e) {
@@ -167,23 +164,20 @@ public WriterConfig getWriterConfig() {
         return writerConfig;
     }
 
-    private void triggerFlush() {
-        Condition cond = lock.newCondition();
-        addSignal(lock, cond);
+    private CountDownLatch triggerFlush() {
+        CountDownLatch latch = new CountDownLatch(1);
+        addSignal(latch);
+        return latch;
     }
 
     @Override
     public synchronized void flush() throws ClientException {
         logger.debug("trigger flush and waiting.");
-        Condition cond = lock.newCondition();
-        lock.lock();
+        CountDownLatch latch = triggerFlush();
         try {
-            addSignal(lock, cond);
-            cond.await();
+            latch.await();
         } catch (InterruptedException e) {
             throw new ClientException(e);
-        } finally {
-            lock.unlock();
         }
         logger.debug("user trigger flush finished.");
     }
diff --git a/src/main/java/com/aliyun/openservices/ots/comm/AsyncServiceClient.java b/src/main/java/com/aliyun/openservices/ots/comm/AsyncServiceClient.java
index 3eff427..52f64c3 100755
--- a/src/main/java/com/aliyun/openservices/ots/comm/AsyncServiceClient.java
+++ b/src/main/java/com/aliyun/openservices/ots/comm/AsyncServiceClient.java
@@ -31,6 +31,7 @@ public AsyncServiceClient(ClientConfiguration config) {
         super(config);
         try {
             IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
+                    .setSelectInterval(config.getSelectInterval())
                     .setIoThreadCount(config.getIoThreadCount()).build();
             ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(
                     ioReactorConfig);
@@ -40,18 +41,11 @@ public AsyncServiceClient(ClientConfiguration config) {
             cm.setDefaultMaxPerRoute(config.getMaxConnections());
             httpClient = new HttpFactory().createHttpAsyncClient(config, cm);
 
-            /*
-             * socketTimeout的值限制了closeIdleConnections执行的周期。
-             * 如果周期相对socketTimeout的值过长，有可能一个请求分配到一个即将socketTimeout的连接，
-             * 在请求发送之前即抛出SocketTimeoutException。
-             * 现在让closeIdleConnections的执行周期为socketTimeout / 2.5。
-             */
-            long closePeriod = 5000;
-            if (config.getSocketTimeoutInMillisecond() > 0) {
-                closePeriod = (long) (config.getSocketTimeoutInMillisecond() / 2.5);
-            }
-            closePeriod = closePeriod < 5000 ? closePeriod : 5000;
-            connEvictor = new IdleConnectionEvictor(cm, closePeriod);
+            // idleConnectionClosePeriod support use defined value, but should <= 15000,
+            // which will be reset to default value (5 000) if > 15 000
+            long idleConnectionClosePeriod = config.getIdleConnectionClosePeriodInMillisecond();
+            connEvictor = new IdleConnectionEvictor(cm,
+                    idleConnectionClosePeriod <= 15 * 1000 ? idleConnectionClosePeriod : 5 * 1000);
             httpClient.start();
             connEvictor.start();
         } catch (IOReactorException ex) {
diff --git a/src/main/java/com/aliyun/openservices/ots/internal/writer/RowChangeEvent.java b/src/main/java/com/aliyun/openservices/ots/internal/writer/RowChangeEvent.java
index e9067ba..cc0fb96 100644
--- a/src/main/java/com/aliyun/openservices/ots/internal/writer/RowChangeEvent.java
+++ b/src/main/java/com/aliyun/openservices/ots/internal/writer/RowChangeEvent.java
@@ -3,6 +3,7 @@
 import com.aliyun.openservices.ots.model.RowChange;
 import com.lmax.disruptor.EventFactory;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -13,8 +14,7 @@ public static enum EventType {
 
     public EventType type;
     public RowChange rowChange;
-    public ReentrantLock lock;
-    public Condition condition;
+    public CountDownLatch latch;
 
     private RowChangeEvent() {
 
@@ -25,10 +25,9 @@ public void setValue(RowChange rowChange) {
         this.rowChange = rowChange;
     }
 
-    public void setValue(ReentrantLock lock, Condition condition) {
+    public void setValue(CountDownLatch latch) {
         this.type = EventType.FLUSH;
-        this.lock = lock;
-        this.condition = condition;
+        this.latch = latch;
     }
 
     public static class RowChangeEventFactory implements EventFactory<RowChangeEvent> {
diff --git a/src/main/java/com/aliyun/openservices/ots/internal/writer/RowChangeEventHandler.java b/src/main/java/com/aliyun/openservices/ots/internal/writer/RowChangeEventHandler.java
index 25d7d64..7629eec 100644
--- a/src/main/java/com/aliyun/openservices/ots/internal/writer/RowChangeEventHandler.java
+++ b/src/main/java/com/aliyun/openservices/ots/internal/writer/RowChangeEventHandler.java
@@ -9,6 +9,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -42,8 +43,7 @@ public void onEvent(RowChangeEvent rowChangeEvent, long sequence, boolean endOfB
         BatchWriteRowRequest request = null;
 
         boolean shouldWaitFlush = false;
-        ReentrantLock lock = null;
-        Condition flushCond = null;
+        CountDownLatch latch = null;
 
         if (rowChangeEvent.type == RowChangeEvent.EventType.FLUSH) {
             logger.debug("FlushSignal with QueueSize: {}", buffer.getTotalRowsCount());
@@ -53,8 +53,7 @@ public void onEvent(RowChangeEvent rowChangeEvent, long sequence, boolean endOfB
             }
 
             shouldWaitFlush = true;
-            lock = rowChangeEvent.lock;
-            flushCond = rowChangeEvent.condition;
+            latch = rowChangeEvent.latch;
         } else {
             final RowChange rowChange = rowChangeEvent.rowChange;
             boolean succeed = buffer.appendRowChange(rowChange);
@@ -92,12 +91,7 @@ public void run() {
 
         if (shouldWaitFlush) {
             waitFlush();
-            lock.lock();
-            try {
-                flushCond.signal();
-            } finally {
-                lock.unlock();
-            }
+            latch.countDown();
         }
     }
 
diff --git a/src/test/java/com/aliyun/openservices/ots/OTSResponseHandlerTest.java b/src/test/java/com/aliyun/openservices/ots/OTSResponseHandlerTest.java
new file mode 100755
index 0000000..c8794b4
--- /dev/null
+++ b/src/test/java/com/aliyun/openservices/ots/OTSResponseHandlerTest.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (C) Alibaba Cloud Computing
+ * All rights reserved.
+ * 
+ * 版权所有 （C）阿里云计算有限公司
+ */
+
+package com.aliyun.openservices.ots;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.http.entity.BufferedHttpEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.message.BasicHttpResponse;
+import org.junit.Test;
+
+import com.aliyun.openservices.ots.auth.ServiceCredentials;
+import com.aliyun.openservices.ots.comm.ResponseMessage;
+import com.aliyun.openservices.ots.utils.DateUtil;
+import com.aliyun.openservices.ots.ClientException;
+import com.aliyun.openservices.ots.internal.OTSValidationResponseHandler;
+
+@SuppressWarnings("deprecation")
+public class OTSResponseHandlerTest {
+    @Test
+    public void testValidationResponseHandler() throws UnsupportedEncodingException{
+        ServiceCredentials cred =
+                new ServiceCredentials("****", "******");
+        OTSValidationResponseHandler h =
+                new OTSValidationResponseHandler(cred, "CreateTable");
+
+        ResponseMessage responseData = createTestResponseData();
+        responseData.getHeadersMap().remove("x-ots-date");
+
+        try{
+            h.handle(responseData);
+            fail("Expected exception not thrown.");
+        } catch(Exception e){
+            assertTrue(e instanceof ClientException);
+        }
+        
+        responseData = createTestResponseData();
+        responseData.getHeadersMap().put("x-ots-date", DateUtil.formatRfc822Date(new Date()));
+
+        try{
+            h.handle(responseData);
+            fail("Expected exception not thrown.");
+        } catch(Exception e){
+            assertTrue(e instanceof ClientException);
+        }
+
+        responseData = createTestResponseData();
+
+        // response expired.
+        try{
+            h.handle(responseData);
+            fail("Expected exception not thrown.");
+        } catch(Exception e){
+            assertTrue(e instanceof ClientException);
+        }
+
+        responseData = createTestResponseData();
+        h.setResponseExpireMinutes(Integer.MAX_VALUE);
+        try {
+            h.handle(responseData);
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail("Should not throw exception.");
+        }
+    }
+
+    private ResponseMessage createTestResponseData() throws UnsupportedEncodingException {
+        ResponseMessage responseData = new ResponseMessage(new BasicHttpResponse(null, 200, null));
+        String uri = "http://service.ots.aliyun.com/CreateTable";
+
+        Map<String, String> headers = responseData.getHeadersMap();
+        headers.put("Server", "AliyunOTS");
+        headers.put("Authorization", "OTS rr0yssqhctx2lz9r4o29uu8i:JJQMflH/Vs38p/7k8bzuJkEaT3s=");
+        headers.put("x-ots-contentmd5", "1B2M2Y8AsgTpgAmY7PhCfg==");
+        headers.put("x-ots-contenttype", "text");
+        headers.put("x-ots-hostid", "MTAuMjMwLjIwMS4yNQ==");
+        headers.put("x-ots-requestid", "1624561984-1373978089964071");
+        headers.put("x-ots-date", "Fri, 03 Feb 2012 06:42:22 GMT");
+        headers.put("Content-Length", "216");
+        
+        String content = "";
+        responseData.getResponse().setEntity(new StringEntity(content));
+        return responseData;
+    }
+
+}
diff --git a/src/test/java/com/aliyun/openservices/ots/integration/OTSPerformanceTest.java b/src/test/java/com/aliyun/openservices/ots/integration/OTSPerformanceTest.java
new file mode 100644
index 0000000..d80564d
--- /dev/null
+++ b/src/test/java/com/aliyun/openservices/ots/integration/OTSPerformanceTest.java
@@ -0,0 +1,1187 @@
+/**
+ * Copyright (C) Alibaba Cloud Computing
+ * All rights reserved.
+ * 
+ * 版权所有 （C）阿里云计算有限公司
+ */
+
+package com.aliyun.openservices.ots.integration;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.aliyun.openservices.ots.ClientConfiguration;
+import com.aliyun.openservices.ots.ClientException;
+import com.aliyun.openservices.ots.OTS;
+import com.aliyun.openservices.ots.OTSAsync;
+import com.aliyun.openservices.ots.OTSException;
+import com.aliyun.openservices.ots.OTSServiceConfiguration;
+import com.aliyun.openservices.ots.internal.OTSCallback;
+import com.aliyun.openservices.ots.model.BatchGetRowRequest;
+import com.aliyun.openservices.ots.model.BatchGetRowResult;
+import com.aliyun.openservices.ots.model.BatchWriteRowRequest;
+import com.aliyun.openservices.ots.model.BatchWriteRowResult;
+import com.aliyun.openservices.ots.model.CapacityUnit;
+import com.aliyun.openservices.ots.model.ColumnValue;
+import com.aliyun.openservices.ots.model.CreateTableRequest;
+import com.aliyun.openservices.ots.model.DescribeTableRequest;
+import com.aliyun.openservices.ots.model.DescribeTableResult;
+import com.aliyun.openservices.ots.model.GetRangeRequest;
+import com.aliyun.openservices.ots.model.GetRangeResult;
+import com.aliyun.openservices.ots.model.GetRowRequest;
+import com.aliyun.openservices.ots.model.GetRowResult;
+import com.aliyun.openservices.ots.model.MultiRowQueryCriteria;
+import com.aliyun.openservices.ots.model.OTSContext;
+import com.aliyun.openservices.ots.model.PrimaryKeyType;
+import com.aliyun.openservices.ots.model.PrimaryKeyValue;
+import com.aliyun.openservices.ots.model.PutRowRequest;
+import com.aliyun.openservices.ots.model.PutRowResult;
+import com.aliyun.openservices.ots.model.RangeRowQueryCriteria;
+import com.aliyun.openservices.ots.model.ReservedThroughputChange;
+import com.aliyun.openservices.ots.model.Row;
+import com.aliyun.openservices.ots.model.RowPrimaryKey;
+import com.aliyun.openservices.ots.model.RowPutChange;
+import com.aliyun.openservices.ots.model.RowUpdateChange;
+import com.aliyun.openservices.ots.model.SingleRowQueryCriteria;
+import com.aliyun.openservices.ots.model.TableMeta;
+import com.aliyun.openservices.ots.model.UpdateTableRequest;
+import com.aliyun.openservices.ots.model.BatchGetRowResult.RowStatus;
+import com.aliyun.openservices.ots.utils.ServiceSettings;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/*
+ * 测试环境为24核机器。
+ */
+public class OTSPerformanceTest {
+
+    private final static int ROW_COUNT = 20000000;
+    private static long[] latency = new long[ROW_COUNT];
+    private static String tableName = "java_sdk_perf_test";
+    private static int MILLISECONDS_UNTIL_TABLE_READY = 30 * 1000;
+    private static AtomicLong errCount = new AtomicLong();
+
+    public static void main(String[] args) throws InterruptedException {
+//         testPutRowAsync();
+         testGetRowAsync();
+//         testGetRangeAsync();
+//         testBatchWriteRowAsync();
+//         testBatchGetRowAsync();
+//         testPutRow();
+//         testGetRow();
+//         testGetRange();
+//         testBatchWriteRow();
+//         testBatchGetRow();
+//        test();
+    }
+
+    private static List<OTSAsync> getOTSAsyncList(int clientCount,
+            int ioThreadCountPerClient, int connectionsPerClient) {
+        ClientConfiguration config = new ClientConfiguration();
+        config.setMaxConnections(connectionsPerClient);
+        config.setIoThreadCount(ioThreadCountPerClient);
+        config.setSocketTimeoutInMillisecond(8000);
+        OTSServiceConfiguration serviceConfig = new OTSServiceConfiguration();
+        serviceConfig.setEnableResponseValidation(false);
+        List<OTSAsync> otsList = new ArrayList<OTSAsync>();
+        for (int i = 0; i < clientCount; i++) {
+            ExecutorService pool = Executors.newFixedThreadPool(2);
+            OTSAsync ots = OTSClientFactory.createOTSClientAsync(
+                    ServiceSettings.load(), config, serviceConfig, pool);
+            otsList.add(ots);
+        }
+        return otsList;
+    }
+
+    private static List<OTS> getOTSList(int clientCount,
+            int ioThreadCountPerClient, int connectionsPerClient) {
+        ClientConfiguration config = new ClientConfiguration();
+        config.setMaxConnections(connectionsPerClient);
+        config.setIoThreadCount(ioThreadCountPerClient);
+        config.setSocketTimeoutInMillisecond(10000);
+        OTSServiceConfiguration serviceConfig = new OTSServiceConfiguration();
+        serviceConfig.setEnableResponseValidation(false);
+        List<OTS> otsList = new ArrayList<OTS>();
+        for (int i = 0; i < clientCount; i++) {
+            OTS ots = OTSClientFactory.createOTSClient(ServiceSettings.load(),
+                    config, serviceConfig);
+            otsList.add(ots);
+        }
+        return otsList;
+    }
+    
+    public static void test() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 1;
+        List<OTSAsync> otsList = getOTSAsyncList(clientCount, 6, 30);
+        int producerThreadCount = 1;
+        int rowPerThread = ROW_COUNT / producerThreadCount;
+        final int totalRequest = rowPerThread * producerThreadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        final AtomicLong sendCount = new AtomicLong();
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < producerThreadCount; i++) {
+            final OTSAsync ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i++) {
+                        long send = sendCount.incrementAndGet();
+                        if (totalRequest - latch.getCount() < send - 200) {
+                            try {
+                                Thread.sleep(10);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                        putRowAsync(ots, i, latch);
+                        if (errCount.get() > 0) {
+                            break;
+                        }
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test GetRowAsync:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+    
+    public static void testGetRowAsync() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 9;
+        List<OTSAsync> otsList = getOTSAsyncList(clientCount, 4, 100);
+        int producerThreadCount = 36;
+        int rowPerThread = ROW_COUNT / producerThreadCount;
+        final int totalRequest = rowPerThread * producerThreadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        final AtomicLong sendCount = new AtomicLong();
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < producerThreadCount; i++) {
+            final OTSAsync ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i++) {
+                        long send = sendCount.incrementAndGet();
+                        if (totalRequest - latch.getCount() < send - 1200) {
+                            try {
+                                Thread.sleep(10);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                        getRowAsync(ots, i, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test GetRowAsync:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    public static void testPutRowAsync() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 4;
+        List<OTSAsync> otsList = getOTSAsyncList(clientCount, 6, 200);
+        int producerThreadCount = 8;
+        int rowPerThread = ROW_COUNT / producerThreadCount;
+        final int totalRequest = rowPerThread * producerThreadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        final AtomicLong sendCount = new AtomicLong();
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < producerThreadCount; i++) {
+            final OTSAsync ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i++) {
+                        long send = sendCount.incrementAndGet();
+                        if (totalRequest - latch.getCount() < send - 1500) {
+                            try {
+                                Thread.sleep(1);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                        putRowAsync(ots, i, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test PutRowAsync:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    public static void testGetRangeAsync() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 4;
+        List<OTSAsync> otsList = getOTSAsyncList(clientCount, 6, 200);
+        int producerThreadCount = 20;
+        int rowPerThread = ROW_COUNT / producerThreadCount;
+        final int totalRequest = rowPerThread * producerThreadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        final AtomicLong sendCount = new AtomicLong();
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < producerThreadCount; i++) {
+            final OTSAsync ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i++) {
+                        int tmp = (i % 61439) + 4096; // [4096, 65534]
+                        String startPk = Integer.toHexString(tmp);
+                        String endPk = Integer.toHexString(tmp + 1);
+                        long send = sendCount.incrementAndGet();
+                        if (totalRequest - latch.getCount() < send - 800) {
+                            try {
+                                Thread.sleep(5);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                        getRangeAsync(ots, i, startPk, endPk, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test GetRangeAsync:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    private static void testBatchGetRowAsync() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 4;
+        List<OTSAsync> otsList = getOTSAsyncList(clientCount, 6, 300);
+        int producerThreadCount = 20;
+        final int rowsPerRequest = 10;
+        int rowPerThread = ROW_COUNT / producerThreadCount;
+        final int totalRequest = rowPerThread / rowsPerRequest
+                * producerThreadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        final AtomicLong sendCount = new AtomicLong();
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < producerThreadCount; i++) {
+            final OTSAsync ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i += rowsPerRequest) {
+                        long send = sendCount.incrementAndGet();
+                        if (totalRequest - latch.getCount() < send - 1000) {
+                            try {
+                                Thread.sleep(1);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                        batchGetRowAsync(ots, i, rowsPerRequest, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test BatchGetRowAsync:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    private static void testBatchWriteRowAsync() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 4;
+        List<OTSAsync> otsList = getOTSAsyncList(clientCount, 6, 100);
+        int producerThreadCount = 20;
+        final int rowsPerRequest = 10;
+        int rowPerThread = ROW_COUNT / producerThreadCount;
+        final int totalRequest = rowPerThread / rowsPerRequest
+                * producerThreadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        final AtomicLong sendCount = new AtomicLong();
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < producerThreadCount; i++) {
+            final OTSAsync ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i += rowsPerRequest) {
+                        long send = sendCount.incrementAndGet();
+                        if (totalRequest - latch.getCount() < send - 300) {
+                            try {
+                                Thread.sleep(1);
+                            } catch (InterruptedException e) {
+                                e.printStackTrace();
+                            }
+                        }
+                        batchWriteRowAsync(ots, i, rowsPerRequest, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test BatchWriteRowAsync:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    public static void testGetRow() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 1;
+        List<OTS> otsList = getOTSList(clientCount, 24, 100);
+        int threadCount = 100;
+        int rowPerThread = ROW_COUNT / threadCount;
+        int totalRequest = rowPerThread * threadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < threadCount; i++) {
+            final OTS ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i++) {
+                        long timeSend = System.currentTimeMillis();
+                        getRow(ots, i, latch);
+                        if (errCount.get() > 0) {
+                            System.out.println(timeSend);
+                            break;
+                        }
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test GetRow:");
+        Summary(ROW_COUNT, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    public static void testPutRow() throws InterruptedException {
+        int clientCount = 4;
+        List<OTS> otsList = getOTSList(clientCount, 6, 150);
+        int threadCount = 400;
+        int rowPerThread = ROW_COUNT / threadCount;
+        int totalRequest = rowPerThread * threadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < threadCount; i++) {
+            final OTS ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i++) {
+                        putRow(ots, i, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test PutRow:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    public static void testGetRange() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 4;
+        List<OTS> otsList = getOTSList(clientCount, 6, 150);
+        int threadCount = 400;
+        int rowPerThread = ROW_COUNT / threadCount;
+        final int totalRequest = rowPerThread * threadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < threadCount; i++) {
+            final OTS ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i++) {
+                        int tmp = (i % 61439) + 4096; // [4096, 65534]
+                        String startPk = Integer.toHexString(tmp);
+                        String endPk = Integer.toHexString(tmp + 1);
+                        getRange(ots, i, startPk, endPk, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test GetRange:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    private static void testBatchGetRow() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 4;
+        List<OTS> otsList = getOTSList(clientCount, 6, 150);
+        int threadCount = 400;
+        final int rowsPerRequest = 10;
+        int rowPerThread = ROW_COUNT / threadCount;
+        int totalRequest = rowPerThread / rowsPerRequest * threadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < threadCount; i++) {
+            final OTS ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i += rowsPerRequest) {
+                        batchGetRow(ots, i, rowsPerRequest, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test BatchGetRow:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    private static void testBatchWriteRow() throws InterruptedException {
+        errCount.set(0);
+        int clientCount = 4;
+        List<OTS> otsList = getOTSList(clientCount, 6, 150);
+        int threadCount = 400;
+        final int rowsPerRequest = 10;
+        int rowPerThread = ROW_COUNT / threadCount;
+        int totalRequest = rowPerThread / rowsPerRequest * threadCount;
+        final CountDownLatch latch = new CountDownLatch(totalRequest);
+        long time0 = System.currentTimeMillis();
+        for (int i = 0; i < threadCount; i++) {
+            final OTS ots = otsList.get(i % clientCount);
+            final int start = i * rowPerThread;
+            final int end = start + rowPerThread;
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = start; i < end; i += rowsPerRequest) {
+                        batchWriteRow(ots, i, rowsPerRequest, latch);
+                    }
+                }
+            };
+            Thread thread = new Thread(runnable);
+            thread.start();
+        }
+        latch.await();
+        long time1 = System.currentTimeMillis();
+        System.out.println("Test BatchWriteRow:");
+        Summary(totalRequest, time1 - time0);
+        for (int i = 0; i < clientCount; i++) {
+            otsList.get(i).shutdown();
+        }
+    }
+
+    private static void Summary(int totalReq, long timeSpan) {
+        long latencySum = 0;
+        Arrays.sort(latency, 0, totalReq);
+        for (int i = 0; i < totalReq; i++) {
+            latencySum += latency[i];
+        }
+        double latencyAver = latencySum / totalReq;
+        System.out.println("Total Request:" + totalReq);
+        System.out.println("Error Count:" + errCount.get());
+        System.out.println("qps:" + (totalReq * (long) 1000 / timeSpan));
+        System.out.println("average latency: " + latencyAver);
+        System.out.println("Top 1 latency: " + latency[totalReq - 1]);
+        System.out.println("Top " + totalReq / 100 + " latency: "
+                + latency[totalReq - totalReq / 100]);
+        System.out.println("Top " + totalReq / 10 + " latency: "
+                + latency[totalReq - totalReq / 10]);
+        System.out.println("Top " + totalReq / 5 + " latency: "
+                + latency[totalReq - totalReq / 5]);
+        System.out.println("Top " + totalReq / 2 + " latency: "
+                + latency[totalReq - totalReq / 2]);
+        System.out.println();
+    }
+
+    private static void getRow(OTS ots, int pk, CountDownLatch latch) {
+        GetRowRequest request = new GetRowRequest();
+        SingleRowQueryCriteria rowQueryCriteria = new SingleRowQueryCriteria(
+                tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk1",
+                PrimaryKeyValue.fromString(toHexAndReverse(pk)));
+        pks.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromString("" + pk));
+        pks.addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromLong(pk));
+        rowQueryCriteria.setPrimaryKey(pks);
+        String[] columnNames = new String[10];
+        for (int i = 1; i <= 10; i++) {
+            columnNames[i - 1] = "col" + i;
+        }
+        rowQueryCriteria.addColumnsToGet(columnNames);
+        request.setRowQueryCriteria(rowQueryCriteria);
+        try {
+            long timeSend = System.currentTimeMillis();
+            ots.getRow(request);
+            latency[pk] = System.currentTimeMillis() - timeSend;
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (ClientException ex) {
+            System.out.println(ex.getCause());
+            ex.printStackTrace();
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (OTSException ex) {
+            System.out.println(ex.getErrorCode());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        }
+    }
+
+    private static void getRowAsync(OTSAsync ots, final int pk,
+            final CountDownLatch latch) {
+        GetRowRequest request = new GetRowRequest();
+        SingleRowQueryCriteria rowQueryCriteria = new SingleRowQueryCriteria(
+                tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk1",
+                PrimaryKeyValue.fromString(toHexAndReverse(pk)));
+        pks.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromString("" + pk));
+        pks.addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromLong(pk));
+        rowQueryCriteria.setPrimaryKey(pks);
+        String[] columnNames = new String[10];
+        for (int i = 1; i <= 10; i++) {
+            columnNames[i - 1] = "col" + i;
+        }
+        rowQueryCriteria.addColumnsToGet(columnNames);
+        request.setRowQueryCriteria(rowQueryCriteria);
+        final long timeSend = System.currentTimeMillis();
+        ots.getRow(request, new OTSCallback<GetRowRequest, GetRowResult>() {
+
+            @Override
+            public void onCompleted(
+                    OTSContext<GetRowRequest, GetRowResult> otsContext) {
+                Row row = otsContext.getOTSResult().getRow();
+                for (int i = 1; i <= 10; i++) {
+                    String expect = "helloworld" + pk + i;
+                    ColumnValue columnValue = row.getColumns().get("col" + i);
+                    if (columnValue == null) {
+                        System.out.println("pk:" + pk + ". Column col" + i + " does not exist.");
+                        continue;
+                    }
+                    String value = columnValue.asString();
+                    if (!value.equals(expect)) {
+                        errCount.incrementAndGet();
+                        System.out.println("Data Error!");
+                    }
+                }
+                latency[pk] = System.currentTimeMillis() - timeSend;
+                if (latch != null) {
+                    latch.countDown();
+                }
+            }
+
+            @Override
+            public void onFailed(
+                    OTSContext<GetRowRequest, GetRowResult> otsContext,
+                    OTSException ex) {
+                System.out.println(ex.getErrorCode());
+                ex.printStackTrace();
+                errCount.incrementAndGet();
+                if (latch != null) {
+                    latch.countDown();
+                }
+            }
+
+            @Override
+            public void onFailed(
+                    OTSContext<GetRowRequest, GetRowResult> otsContext,
+                    ClientException ex) {
+                System.out.println(ex.getMessage());
+                System.out.println(timeSend);
+                System.out.println(System.currentTimeMillis());
+                errCount.incrementAndGet();
+                if (latch != null) {
+                    latch.countDown();
+                }
+            }
+        });
+    }
+
+    private static void putRow(OTS ots, int pk, CountDownLatch latch) {
+        PutRowRequest request = new PutRowRequest();
+        RowPutChange rowChange = new RowPutChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk1",
+                PrimaryKeyValue.fromString(toHexAndReverse(pk)));
+        pks.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromString("" + pk));
+        pks.addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromLong(pk));
+        rowChange.setPrimaryKey(pks);
+        for (int i = 1; i <= 10; i++) {
+            rowChange.addAttributeColumn("col" + i,
+                    ColumnValue.fromString("helloworld" + pk + i));
+        }
+        request.setRowChange(rowChange);
+        try {
+            long timeSend = System.currentTimeMillis();
+            ots.putRow(request);
+            latency[pk] = System.currentTimeMillis() - timeSend;
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (ClientException ex) {
+            System.out.println(ex.getCause());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (OTSException ex) {
+            System.out.println(ex.getErrorCode());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        }
+    }
+
+    private static void putRowAsync(OTSAsync ots, final int pk,
+            final CountDownLatch latch) {
+        PutRowRequest request = new PutRowRequest();
+        RowPutChange rowChange = new RowPutChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk1",
+                PrimaryKeyValue.fromString(toHexAndReverse(pk)));
+        pks.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromString("" + pk));
+        pks.addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromLong(pk));
+        rowChange.setPrimaryKey(pks);
+        for (int i = 1; i <= 10; i++) {
+            rowChange.addAttributeColumn("col" + i,
+                    ColumnValue.fromString("helloworld" + pk + i));
+        }
+        request.setRowChange(rowChange);
+        final long timeSend = System.currentTimeMillis();
+        ots.putRow(request, new OTSCallback<PutRowRequest, PutRowResult>() {
+            @Override
+            public void onCompleted(
+                    OTSContext<PutRowRequest, PutRowResult> otsContext) {
+                latency[pk] = System.currentTimeMillis() - timeSend;
+                if (latch != null) {
+                    latch.countDown();
+                }
+            }
+
+            @Override
+            public void onFailed(
+                    OTSContext<PutRowRequest, PutRowResult> otsContext,
+                    OTSException ex) {
+                System.out.println(ex.getErrorCode());
+                errCount.incrementAndGet();
+                if (latch != null) {
+                    latch.countDown();
+                }
+            }
+
+            @Override
+            public void onFailed(
+                    OTSContext<PutRowRequest, PutRowResult> otsContext,
+                    ClientException ex) {
+                System.out.println(ex.getCause());
+                errCount.incrementAndGet();
+                if (latch != null) {
+                    latch.countDown();
+                }
+            }
+        });
+    }
+
+    private static void getRange(OTS ots, int id, String startPk, String endPk,
+            CountDownLatch latch) {
+        GetRangeRequest getRangeRequest = new GetRangeRequest();
+        RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(
+                tableName);
+        RowPrimaryKey start = new RowPrimaryKey();
+        RowPrimaryKey end = new RowPrimaryKey();
+        start.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString(startPk));
+        start.addPrimaryKeyColumn("pk2", PrimaryKeyValue.INF_MIN);
+        start.addPrimaryKeyColumn("pk3", PrimaryKeyValue.INF_MIN);
+        end.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString(endPk));
+        end.addPrimaryKeyColumn("pk2", PrimaryKeyValue.INF_MAX);
+        end.addPrimaryKeyColumn("pk3", PrimaryKeyValue.INF_MAX);
+        rangeRowQueryCriteria.setInclusiveStartPrimaryKey(start);
+        rangeRowQueryCriteria.setExclusiveEndPrimaryKey(end);
+        rangeRowQueryCriteria.setLimit(10);
+        rangeRowQueryCriteria
+                .addColumnsToGet(new String[] { "col1", "col2", "col3", "col4",
+                        "col5", "col6", "col7", "col8", "col9", "col10" });
+        getRangeRequest.setRangeRowQueryCriteria(rangeRowQueryCriteria);
+        try {
+            long timeSend = System.currentTimeMillis();
+            ots.getRange(getRangeRequest);
+            latency[id] = System.currentTimeMillis() - timeSend;
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (ClientException ex) {
+            System.out.println(ex.getCause());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (OTSException ex) {
+            System.out.println(ex.getErrorCode());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        }
+    }
+
+    private static void getRangeAsync(OTSAsync ots, final int id,
+            String startPk, String endPk, final CountDownLatch latch) {
+        GetRangeRequest getRangeRequest = new GetRangeRequest();
+        RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(
+                tableName);
+        RowPrimaryKey start = new RowPrimaryKey();
+        RowPrimaryKey end = new RowPrimaryKey();
+        start.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString(startPk));
+        start.addPrimaryKeyColumn("pk2", PrimaryKeyValue.INF_MIN);
+        start.addPrimaryKeyColumn("pk3", PrimaryKeyValue.INF_MIN);
+        end.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString(endPk));
+        end.addPrimaryKeyColumn("pk2", PrimaryKeyValue.INF_MAX);
+        end.addPrimaryKeyColumn("pk3", PrimaryKeyValue.INF_MAX);
+        rangeRowQueryCriteria.setInclusiveStartPrimaryKey(start);
+        rangeRowQueryCriteria.setExclusiveEndPrimaryKey(end);
+        rangeRowQueryCriteria.setLimit(10);
+        rangeRowQueryCriteria
+                .addColumnsToGet(new String[] { "col1", "col2", "col3", "col4",
+                        "col5", "col6", "col7", "col8", "col9", "col10" });
+        getRangeRequest.setRangeRowQueryCriteria(rangeRowQueryCriteria);
+        final long timeSend = System.currentTimeMillis();
+        ots.getRange(getRangeRequest,
+                new OTSCallback<GetRangeRequest, GetRangeResult>() {
+
+                    @Override
+                    public void onCompleted(
+                            OTSContext<GetRangeRequest, GetRangeResult> otsContext) {
+
+                        latency[id] = System.currentTimeMillis() - timeSend;
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<GetRangeRequest, GetRangeResult> otsContext,
+                            OTSException ex) {
+                        System.out.println(ex.getErrorCode());
+                        // ex.printStackTrace();
+                        errCount.incrementAndGet();
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<GetRangeRequest, GetRangeResult> otsContext,
+                            ClientException ex) {
+                        System.out.println(ex.getCause());
+                        errCount.incrementAndGet();
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+                    }
+                });
+    }
+
+    private static void batchGetRow(OTS ots, int start, int num,
+            CountDownLatch latch) {
+        BatchGetRowRequest batchGetRowRequest = new BatchGetRowRequest();
+        MultiRowQueryCriteria criteria = new MultiRowQueryCriteria(tableName);
+        String[] columnNames = new String[10];
+        for (int i = 1; i <= 10; i++) {
+            columnNames[i - 1] = "col" + i;
+        }
+        criteria.addColumnsToGet(columnNames);
+        for (int pk = start; pk < start + num; pk++) {
+            RowPrimaryKey pks = new RowPrimaryKey();
+            pks.addPrimaryKeyColumn("pk1",
+                    PrimaryKeyValue.fromString(toHexAndReverse(start)));
+            pks.addPrimaryKeyColumn("pk2",
+                    PrimaryKeyValue.fromString("" + start));
+            pks.addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromLong(pk));
+            criteria.addRow(pks);
+        }
+        batchGetRowRequest.addMultiRowQueryCriteria(criteria);
+        int id = start / num;
+        try {
+            long timeSend = System.currentTimeMillis();
+            ots.batchGetRow(batchGetRowRequest);
+            latency[id] = System.currentTimeMillis() - timeSend;
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (ClientException ex) {
+            System.out.println(ex.getCause());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (OTSException ex) {
+            System.out.println(ex.getErrorCode());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        }
+    }
+
+    private static void batchGetRowAsync(OTSAsync ots, int start, int num,
+            final CountDownLatch latch) {
+        BatchGetRowRequest batchGetRowRequest = new BatchGetRowRequest();
+        MultiRowQueryCriteria criteria = new MultiRowQueryCriteria(tableName);
+        String[] columnNames = new String[10];
+        for (int i = 1; i <= 10; i++) {
+            columnNames[i - 1] = "col" + i;
+        }
+        criteria.addColumnsToGet(columnNames);
+        for (int pk = start; pk < start + num; pk++) {
+            RowPrimaryKey pks = new RowPrimaryKey();
+            pks.addPrimaryKeyColumn("pk1",
+                    PrimaryKeyValue.fromString(toHexAndReverse(pk)));
+            pks.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromString("" + pk));
+            pks.addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromLong(pk));
+            criteria.addRow(pks);
+        }
+        batchGetRowRequest.addMultiRowQueryCriteria(criteria);
+        final long timeSend = System.currentTimeMillis();
+        final int id = start / num;
+        ots.batchGetRow(batchGetRowRequest,
+                new OTSCallback<BatchGetRowRequest, BatchGetRowResult>() {
+
+                    @Override
+                    public void onCompleted(
+                            OTSContext<BatchGetRowRequest, BatchGetRowResult> otsContext) {
+                        BatchGetRowResult result = otsContext.getOTSResult();
+                        List<RowStatus> statues = result
+                                .getBatchGetRowStatus(tableName);
+                        for (RowStatus status : statues) {
+                            if (!status.isSucceed()) {
+                                System.out.println(status.getError()
+                                        .getMessage());
+                            }
+                        }
+                        latency[id] = System.currentTimeMillis() - timeSend;
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<BatchGetRowRequest, BatchGetRowResult> otsContext,
+                            OTSException ex) {
+                        System.out.println(ex.getErrorCode());
+                        ex.printStackTrace();
+                        errCount.incrementAndGet();
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<BatchGetRowRequest, BatchGetRowResult> otsContext,
+                            ClientException ex) {
+                        System.out.println(ex.getCause());
+                        errCount.incrementAndGet();
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+                    }
+                });
+    }
+
+    private static void batchWriteRow(OTS ots, int start, int num,
+            CountDownLatch latch) {
+        BatchWriteRowRequest batchWriteRowRequest = new BatchWriteRowRequest();
+        for (int pk = start; pk < start + num; pk++) {
+            // RowPutChange rowPutChange = new RowPutChange(tableName);
+            RowUpdateChange rowUpdateChange = new RowUpdateChange(tableName);
+            RowPrimaryKey pks = new RowPrimaryKey();
+            pks.addPrimaryKeyColumn("pk1",
+                    PrimaryKeyValue.fromString(toHexAndReverse(start)));
+            pks.addPrimaryKeyColumn("pk2",
+                    PrimaryKeyValue.fromString("" + start));
+            pks.addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromLong(pk));
+            // rowPutChange.setPrimaryKey(pks);
+            rowUpdateChange.setPrimaryKey(pks);
+            for (int i = 1; i <= 10; i++) {
+                // rowPutChange.addAttributeColumn("col" + i,
+                // ColumnValue.fromString("helloworld" + pk + i));
+                rowUpdateChange.addAttributeColumn("col" + i,
+                        ColumnValue.fromString("helloworld" + pk + i));
+            }
+            batchWriteRowRequest.addRowUpdateChange(rowUpdateChange);
+            // batchWriteRowRequest.addRowPutChange(rowPutChange);
+        }
+        int id = start / num;
+        try {
+            long timeSend = System.currentTimeMillis();
+            ots.batchWriteRow(batchWriteRowRequest);
+            latency[id] = System.currentTimeMillis() - timeSend;
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (ClientException ex) {
+            System.out.println(ex.getCause());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        } catch (OTSException ex) {
+            System.out.println(ex.getErrorCode());
+            errCount.incrementAndGet();
+            if (latch != null) {
+                latch.countDown();
+            }
+        }
+    }
+
+    private static void batchWriteRowAsync(OTSAsync ots, int start, int num,
+            final CountDownLatch latch) {
+        BatchWriteRowRequest batchWriteRowRequest = new BatchWriteRowRequest();
+        for (int pk = start; pk < start + num; pk++) {
+            // RowPutChange rowPutChange = new RowPutChange(tableName);
+            RowUpdateChange rowUpdateChange = new RowUpdateChange(tableName);
+            RowPrimaryKey pks = new RowPrimaryKey();
+            pks.addPrimaryKeyColumn("pk1",
+                    PrimaryKeyValue.fromString(toHexAndReverse(start)));
+            pks.addPrimaryKeyColumn("pk2",
+                    PrimaryKeyValue.fromString("" + start));
+            pks.addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromLong(pk));
+            // rowPutChange.setPrimaryKey(pks);
+            rowUpdateChange.setPrimaryKey(pks);
+            for (int i = 1; i <= 10; i++) {
+                // rowPutChange.addAttributeColumn("col" + i,
+                // ColumnValue.fromString("helloworld" + pk + i));
+                rowUpdateChange.addAttributeColumn("col" + i,
+                        ColumnValue.fromString("helloworld" + pk + i));
+            }
+            batchWriteRowRequest.addRowUpdateChange(rowUpdateChange);
+            // batchWriteRowRequest.addRowPutChange(rowPutChange);
+        }
+        final long timeSend = System.currentTimeMillis();
+        final int id = start / num;
+        ots.batchWriteRow(batchWriteRowRequest,
+                new OTSCallback<BatchWriteRowRequest, BatchWriteRowResult>() {
+
+                    @Override
+                    public void onCompleted(
+                            OTSContext<BatchWriteRowRequest, BatchWriteRowResult> otsContext) {
+                        BatchWriteRowResult result = otsContext.getOTSResult();
+
+                        List<BatchWriteRowResult.RowStatus> statues = result
+                                .getUpdateRowStatus(tableName);
+                        for (BatchWriteRowResult.RowStatus status : statues) {
+                            if (!status.isSucceed()) {
+                                System.out.println(status.getError()
+                                        .getMessage());
+                            }
+                        }
+                        latency[id] = System.currentTimeMillis() - timeSend;
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<BatchWriteRowRequest, BatchWriteRowResult> otsContext,
+                            OTSException ex) {
+                        System.out.println(ex.getErrorCode());
+                        ex.printStackTrace();
+                        errCount.incrementAndGet();
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<BatchWriteRowRequest, BatchWriteRowResult> otsContext,
+                            ClientException ex) {
+                        System.out.println(ex.getCause());
+                        errCount.incrementAndGet();
+                        if (latch != null) {
+                            latch.countDown();
+                        }
+                    }
+                });
+    }
+
+    private static TableMeta getTestTableMeta() {
+        TableMeta tableMeta = new TableMeta(tableName);
+        tableMeta.addPrimaryKeyColumn("pk1", PrimaryKeyType.STRING);
+        tableMeta.addPrimaryKeyColumn("pk2", PrimaryKeyType.STRING);
+        tableMeta.addPrimaryKeyColumn("pk3", PrimaryKeyType.INTEGER);
+        return tableMeta;
+    }
+
+    private static CapacityUnit getTestTableCapacityUnit() {
+        CapacityUnit capacityUnit = new CapacityUnit();
+        capacityUnit.setReadCapacityUnit(5000);
+        capacityUnit.setWriteCapacityUnit(5000);
+        return capacityUnit;
+    }
+
+    private static void updateTable(OTS ots, int readCu, int writeCu) {
+        UpdateTableRequest updateTableCapacityRequest = new UpdateTableRequest();
+        updateTableCapacityRequest.setTableName(tableName);
+        ReservedThroughputChange capacityChange = new ReservedThroughputChange();
+        if (readCu != -1) {
+            capacityChange.setReadCapacityUnit(readCu);
+        }
+        if (writeCu != -1) {
+            capacityChange.setWriteCapacityUnit(writeCu);
+        }
+        updateTableCapacityRequest.setReservedThroughputChange(capacityChange);
+        ots.updateTable(updateTableCapacityRequest);
+    }
+
+    private static void describeTable(OTS ots) {
+        DescribeTableRequest describeTableRequest = new DescribeTableRequest();
+        describeTableRequest.setTableName(tableName);
+        DescribeTableResult result = ots.describeTable(describeTableRequest);
+        TableMeta tableMeta = result.getTableMeta();
+        System.out.println(tableMeta.getTableName());
+        for (Entry<String, PrimaryKeyType> entry : tableMeta.getPrimaryKey()
+                .entrySet()) {
+            System.out.println(entry.getKey() + ":" + entry.getValue());
+        }
+
+        System.out.println("Read:"
+                + result.getReservedThroughputDetails().getCapacityUnit()
+                        .getReadCapacityUnit());
+        System.out.println("Write:"
+                + result.getReservedThroughputDetails().getCapacityUnit()
+                        .getWriteCapacityUnit());
+    }
+
+    private static void createTable() {
+        ClientConfiguration config = new ClientConfiguration();
+        config.setMaxConnections(1);
+        config.setIoThreadCount(1);
+        OTS ots = OTSClientFactory.createOTSClient(ServiceSettings.load(),
+                config);
+        try {
+            TableMeta tableMeta = getTestTableMeta();
+            CapacityUnit tableCU = getTestTableCapacityUnit();
+            CreateTableRequest createTableRequest = new CreateTableRequest();
+            createTableRequest.setTableMeta(tableMeta);
+            createTableRequest.setReservedThroughput(tableCU);
+            ots.createTable(createTableRequest);
+            Thread.sleep(MILLISECONDS_UNTIL_TABLE_READY);
+        } catch (OTSException ex) {
+            if (!ex.getErrorCode().equals("OTSObjectAlreadyExist")) {
+                System.out.println(ex.getRequestId());
+                ex.printStackTrace();
+            }
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (InterruptedException ex) {
+            ex.printStackTrace();
+        } finally {
+            describeTable(ots);
+            ots.shutdown();
+        }
+    }
+
+    private static String toHexAndReverse(int pk) {
+        StringBuffer stringBuffer = new StringBuffer(Integer.toHexString(pk));
+        return stringBuffer.reverse().toString();
+    }
+
+}
diff --git a/src/test/java/com/aliyun/openservices/ots/integration/OTSStressTest.java b/src/test/java/com/aliyun/openservices/ots/integration/OTSStressTest.java
new file mode 100755
index 0000000..f8cc6e2
--- /dev/null
+++ b/src/test/java/com/aliyun/openservices/ots/integration/OTSStressTest.java
@@ -0,0 +1,368 @@
+/**
+ * Copyright (C) Alibaba Cloud Computing
+ * All rights reserved.
+ * 
+ * 版权所有 （C）阿里云计算有限公司
+ */
+
+package com.aliyun.openservices.ots.integration;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import com.aliyun.openservices.ots.model.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.aliyun.openservices.ots.ClientConfiguration;
+import com.aliyun.openservices.ots.ClientException;
+import com.aliyun.openservices.ots.OTS;
+import com.aliyun.openservices.ots.OTSErrorCode;
+import com.aliyun.openservices.ots.OTSException;
+import com.aliyun.openservices.ots.utils.ServiceSettings;
+
+public class OTSStressTest {
+    private String tableName = "ots_stress_test_table";
+    private static final int MAX_RETRIES = 3;
+    private static final int MILLISECONDS_UNTIL_TABLE_READY = 10 * 1000;
+    private static final OTS ots = OTSClientFactory.createOTSClient(
+            ServiceSettings.load(), new ClientConfiguration());
+    private static final Logger LOG = Logger.getLogger(OTSStressTest.class.getName());
+
+    private static class Summary {
+        private int requests = 0;
+        private int failures = 0;
+        //        private StringBuilder message = new StringBuilder();
+
+        public Summary() {}
+
+        public synchronized void incrementRequests(){ requests++; }
+
+        public synchronized void incrementFailures(){ failures++; }
+
+        //        public synchronized void addMessage(String msg){ this.message.append(msg); }
+
+        public int getRequests() { return requests; }
+
+        public int getFailures() { return failures; }
+    }
+
+    private static interface OTSMethodWrapper {
+        void run() throws OTSException, ClientException;
+    }
+
+    private static void retryableCallOTSMethod(OTSMethodWrapper method)
+            throws OTSException, ClientException{
+        int retries = 0;
+        while(retries++ < MAX_RETRIES){
+            try {
+                method.run();
+            } catch (OTSException e) {
+                if (!shouldRetry(e.getErrorCode())){
+                    throw e;
+                }
+            } catch (ClientException e) {
+                throw e;
+            }
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                throw new ClientException(e);
+            }
+        }
+    }
+    
+    private static boolean shouldRetry(String errorCode){
+        return OTSErrorCode.ROW_OPERATION_CONFLICT.equals(errorCode);
+    }
+
+    @Before
+    public void setup() throws Exception {
+        LOG.info("Instance: " + ServiceSettings.load().getOTSInstanceName());
+
+        ListTableResult r = ots.listTable();
+
+        for (String table: r.getTableNames()) {
+            DeleteTableRequest deleteTableRequest = new DeleteTableRequest(table);
+            ots.deleteTable(deleteTableRequest);
+
+            LOG.info("Delete table: " + table);
+            Thread.sleep(1000);
+        }
+    }
+
+    @Test
+    public void testConcurrentPutRow() throws Exception{
+        final int CONCURRENCIES = 2;
+        final int INTERVAL = 900; // in milliseconds
+        final int MAX_OCCURS = 10;
+
+        // create
+        TableMeta tableMeta = getTestTableMeta();
+        CapacityUnit tableCU = getTestTableCapacityUnit();
+        tableMeta.addPrimaryKeyColumn("flag", PrimaryKeyType.STRING);
+
+        CreateTableRequest createTableRequest = new CreateTableRequest();
+        createTableRequest.setTableMeta(tableMeta);
+        createTableRequest.setReservedThroughput(tableCU);
+        ots.createTable(createTableRequest);
+        Thread.sleep(MILLISECONDS_UNTIL_TABLE_READY); // Wait for the table is ready
+
+        final Summary sum = new Summary();
+
+        Runnable putDataRun = new Runnable(){
+            public void run(){
+
+                for(int i = 0; i < MAX_OCCURS; i++){
+
+                    System.out.println("Loop " + i + " on the thread " + Thread.currentThread().getName());
+
+                    final String flag = Thread.currentThread().getName() + ":" + new Date().getTime();
+                    System.out.println(flag);
+
+                    // Put data
+                    try {
+                        sum.incrementRequests();
+                        retryableCallOTSMethod(new OTSMethodWrapper() {
+                            @Override
+                            public void run() throws OTSException, ClientException {
+                                RowPutChange rowPutChange = new RowPutChange(tableName);
+                                RowPrimaryKey pks = new RowPrimaryKey();
+                                pks.addPrimaryKeyColumn("groupid", PrimaryKeyValue.fromLong(1));
+                                pks.addPrimaryKeyColumn("name", PrimaryKeyValue.fromString("张三"));
+                                pks.addPrimaryKeyColumn("flag", PrimaryKeyValue.fromString(flag));
+                                rowPutChange.setPrimaryKey(pks);
+                                rowPutChange.addAttributeColumn("address", ColumnValue.fromString("北京某地"));
+                                rowPutChange.addAttributeColumn("level", ColumnValue.fromLong(1));
+                                rowPutChange.addAttributeColumn("dmax", ColumnValue.fromDouble(Double.MAX_VALUE));
+                                rowPutChange.addAttributeColumn("dmin", ColumnValue.fromDouble(-Double.MAX_VALUE));
+
+                                PutRowRequest putRowRequest = new PutRowRequest();
+                                putRowRequest.setRowChange(rowPutChange);
+                                ots.putRow(putRowRequest);
+                            }
+                        });
+                    } catch (OTSException e) {
+                        sum.incrementFailures();
+                        System.err.println(flag + "[PUT] " + e.getMessage() + "\tRequestID: " + e.getRequestId() + ", HostID:" + e.getHostId());
+                    } catch (ClientException e) {
+                        sum.incrementFailures();
+                        System.err.println("[PUT] " + e.getMessage());
+                    }
+
+                    try {
+                        Thread.sleep(INTERVAL / 3);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                        fail(e.getMessage());
+                    }
+
+                    // Get data
+                    SingleRowQueryCriteria criteria = new SingleRowQueryCriteria(tableName);
+                    RowPrimaryKey pks = new RowPrimaryKey();
+                    pks.addPrimaryKeyColumn("groupid", PrimaryKeyValue.fromLong(1));
+                    pks.addPrimaryKeyColumn("name", PrimaryKeyValue.fromString("张三"));
+                    pks.addPrimaryKeyColumn("flag", PrimaryKeyValue.fromString(flag));
+                    criteria.setPrimaryKey(pks);
+                    try {
+                        sum.incrementRequests();
+                        GetRowRequest getRowRequest = new GetRowRequest();
+                        getRowRequest.setRowQueryCriteria(criteria);
+                        Row row = ots.getRow(getRowRequest).getRow();
+                        assertTrue(row != null);
+                    } catch (OTSException e) {
+                        sum.incrementFailures();
+                        System.err.println(flag + "[GET] " + e.getMessage() + "\tRequestID: " + e.getRequestId() + ", HostID:" + e.getHostId());
+                    } catch (ClientException e) {
+                        sum.incrementFailures();
+                        System.err.println("[GET] " + e.getMessage());
+                    }
+
+                    try {
+                        Thread.sleep(INTERVAL / 3);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                        fail(e.getMessage());
+                    }
+
+                    // Delete data
+                    try {
+                        sum.incrementRequests();
+                        retryableCallOTSMethod(new OTSMethodWrapper() {
+
+                            @Override
+                            public void run() throws OTSException, ClientException {
+                                RowDeleteChange rowDelChange = new RowDeleteChange(tableName);
+                                RowPrimaryKey pks = new RowPrimaryKey();
+                                pks.addPrimaryKeyColumn("groupid", PrimaryKeyValue.fromLong(1));
+                                pks.addPrimaryKeyColumn("name", PrimaryKeyValue.fromString("张三"));
+                                pks.addPrimaryKeyColumn("flag", PrimaryKeyValue.fromString(flag));
+                                rowDelChange.setPrimaryKey(pks);
+
+                                DeleteRowRequest deleteRowRequest = new DeleteRowRequest();
+                                deleteRowRequest.setRowChange(rowDelChange);
+                                ots.deleteRow(deleteRowRequest);
+                            }
+                        });
+                    } catch (OTSException e) {
+                        sum.incrementFailures();
+                        System.err.println(flag + "[DELELE] " + e.getMessage() + "\tRequestID: " + e.getRequestId() + ", HostID:" + e.getHostId());
+                    } catch (ClientException e) {
+                        sum.incrementFailures();
+                        System.err.println("[DELELE] " + e.getMessage());
+                    }
+
+                    try {
+                        Thread.sleep(INTERVAL / 3);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                        fail(e.getMessage());
+                    }
+                }
+            }
+        };
+
+        // Make threads.
+        Thread threads[] = new Thread[CONCURRENCIES];
+        for(int i = 0; i < CONCURRENCIES; i++){
+            threads[i] = new Thread(putDataRun);
+            threads[i].setName("ots_thread_" + i);
+        }
+
+        // Start threads.
+        for(int i = 0; i < CONCURRENCIES; i++){
+            threads[i].start();
+        }
+
+        // Wait for threads to complete.
+        for(int i = 0; i < CONCURRENCIES; i++){
+            threads[i].join();
+        }
+
+        // Print summaries
+        System.out.println("Requests in total: " + Integer.toString(sum.getRequests()));
+        if (sum.getFailures() > 0){
+            System.out.println("Failures: " + Integer.toString(sum.getFailures()));
+            fail("Failure rate: " + Double.toString((sum.getFailures() * 100) / sum.getRequests()) + "%");
+        } else {
+            System.out.println("All requests succeeded.");
+        }
+    }
+
+    @Test
+    public void testGetRowBenchmark() throws Exception {
+        final int CONCURRENCIES = 4;
+        final int MAX_OCCURS = 100;
+        final int ROW_COUNT = 10;
+
+        System.out.println("Java version: " + System.getProperty("java.version"));
+
+        ServiceSettings ss = ServiceSettings.load();
+        ClientConfiguration config = new ClientConfiguration();
+        config.setMaxConnections(CONCURRENCIES);
+        if (ss.getProxyHost() != null && !ss.getProxyHost().isEmpty()) {
+            config.setProxyHost(ss.getProxyHost());
+            config.setProxyPort(ss.getProxyPort());
+        }
+        final OTS ots = OTSClientFactory.createOTSClient(
+                ServiceSettings.load(), config);
+        // create
+        TableMeta tableMeta = getTestTableMeta();
+        CapacityUnit tableCU = getTestTableCapacityUnit();
+        CreateTableRequest createTableRequest = new CreateTableRequest();
+        createTableRequest.setTableMeta(tableMeta);
+        createTableRequest.setReservedThroughput(tableCU);
+        ots.createTable(createTableRequest);
+        Thread.sleep(MILLISECONDS_UNTIL_TABLE_READY); // Wait for the table is ready
+
+        // prepare data
+        for(int i = 0; i < ROW_COUNT; i++) {
+            RowPutChange rowPutChange = new RowPutChange(tableName);
+            RowPrimaryKey pks = new RowPrimaryKey();
+            pks.addPrimaryKeyColumn("groupid", PrimaryKeyValue.fromLong(1));
+            pks.addPrimaryKeyColumn("name", PrimaryKeyValue.fromString("张三" + String.valueOf(i)));
+            rowPutChange.setPrimaryKey(pks);
+            rowPutChange.addAttributeColumn("address", ColumnValue.fromString("北京某地"));
+            rowPutChange.addAttributeColumn("level", ColumnValue.fromLong(1));
+
+            PutRowRequest putRowRequest = new PutRowRequest();
+            putRowRequest.setRowChange(rowPutChange);
+            ots.putRow(putRowRequest);
+        }
+
+        // get data
+        Runnable getDataFunc = new Runnable() {
+
+            @Override
+            public void run() {
+                final String threadName = Thread.currentThread().getName();
+                System.out.println("The thread '" + threadName + "'started.");
+                long startTimeMillis = System.currentTimeMillis();
+
+                for(int i = 0; i < MAX_OCCURS; i++) {
+                    RangeIteratorParameter criteria = new RangeIteratorParameter(tableName);
+                    RowPrimaryKey inclusiveStartPrimaryKey = new RowPrimaryKey();
+                    RowPrimaryKey exclusiveEndPrimaryKey = new RowPrimaryKey();
+                    criteria.setInclusiveStartPrimaryKey(inclusiveStartPrimaryKey);
+                    criteria.setExclusiveEndPrimaryKey(exclusiveEndPrimaryKey);
+                    inclusiveStartPrimaryKey.addPrimaryKeyColumn("groupid", PrimaryKeyValue.fromLong(1));
+                    inclusiveStartPrimaryKey.addPrimaryKeyColumn("name", PrimaryKeyValue.INF_MIN);
+
+                    exclusiveEndPrimaryKey.addPrimaryKeyColumn("groupid", PrimaryKeyValue.fromLong(1));
+                    exclusiveEndPrimaryKey.addPrimaryKeyColumn("name", PrimaryKeyValue.INF_MAX);
+
+                    try {
+                        List<Row> rows = new ArrayList<Row>();
+                        Iterator<Row> iter = ots.createRangeIterator(criteria);
+                        while (iter.hasNext()) {
+                            rows.add(iter.next());
+                        }
+                        assertEquals(ROW_COUNT, rows.size());
+                    } catch (OTSException e) {
+                        System.err.println(threadName + "[GET] OTSException: " + e.getMessage() +
+                                "\tRequestID: " + e.getRequestId() + ", HostID:" + e.getHostId());
+                    } catch (ClientException e) {
+                        System.err.println(threadName + "[GET] ClientException: " + e.getMessage());
+                    } catch (Exception e) {
+                        System.err.println(threadName + "[GET] Exception: " + e.getMessage());
+                    }
+                }
+                long endTimeMillis = System.currentTimeMillis();
+                long averageMillis = (endTimeMillis - startTimeMillis) / MAX_OCCURS;
+                System.out.println("Average time on " + threadName + ": "
+                        + String.valueOf(averageMillis) + "ms");
+            }
+        };
+
+        // Make threads.
+        ExecutorService executor = Executors.newCachedThreadPool();
+        for(int i = 0; i < CONCURRENCIES; i++) {
+            executor.submit(getDataFunc);
+        }
+        executor.shutdown();
+        while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
+        }
+    }
+
+    private TableMeta getTestTableMeta() {
+        TableMeta tableMeta = new TableMeta(tableName);
+        tableMeta.addPrimaryKeyColumn("groupid", PrimaryKeyType.INTEGER);
+        tableMeta.addPrimaryKeyColumn("name", PrimaryKeyType.STRING);
+        return tableMeta;
+    }
+    
+    private CapacityUnit getTestTableCapacityUnit() {
+        CapacityUnit capacityUnit = new CapacityUnit();
+        capacityUnit.setReadCapacityUnit(5000);
+        capacityUnit.setWriteCapacityUnit(5000);
+        return capacityUnit;
+    }
+}
diff --git a/src/test/java/com/aliyun/openservices/ots/smoketest/BatchWriteRowStress.java b/src/test/java/com/aliyun/openservices/ots/smoketest/BatchWriteRowStress.java
new file mode 100644
index 0000000..310161d
--- /dev/null
+++ b/src/test/java/com/aliyun/openservices/ots/smoketest/BatchWriteRowStress.java
@@ -0,0 +1,111 @@
+package com.aliyun.openservices.ots.smoketest;
+
+import com.aliyun.openservices.ots.*;
+import com.aliyun.openservices.ots.internal.OTSRetryStrategy;
+import com.aliyun.openservices.ots.model.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BatchWriteRowStress {
+
+    private static final String TABLE_NAME = "HelloWorld";
+
+    public static class BatchWriteRowThread implements Runnable {
+        private OTS ots;
+
+        public BatchWriteRowThread(OTS ots) {
+            this.ots = ots;
+        }
+
+
+        @Override
+        public void run() {
+            while (true) {
+            //for (int d = 0; d < 2; d++) {
+                try {
+                    BatchWriteRowRequest request = new BatchWriteRowRequest();
+                    for (int i = 0; i < 120; i++) {
+                        RowPutChange rowChange = new RowPutChange(TABLE_NAME);
+                        RowPrimaryKey primaryKey = new RowPrimaryKey();
+                        primaryKey.addPrimaryKeyColumn("PK0", PrimaryKeyValue.fromLong(10));
+                        primaryKey.addPrimaryKeyColumn("PK2", PrimaryKeyValue.fromLong(99));
+                        primaryKey.addPrimaryKeyColumn("PK3", PrimaryKeyValue.fromLong(99));
+                        rowChange.setPrimaryKey(primaryKey);
+                        for (int j = 11; j < 25; j++) {
+                            rowChange.addAttributeColumn("Column" + j, ColumnValue.fromLong(j));
+                        }
+                        request.addRowPutChange(rowChange);
+                    }
+                    ots.batchWriteRow(request);
+                } catch (OTSException e) {
+                    System.out.println(e.getMessage());
+                }
+            }
+        }
+    }
+
+    public static void createTable(OTS ots) {
+        CreateTableRequest request = new CreateTableRequest();
+        TableMeta tableMeta = new TableMeta(TABLE_NAME);
+        tableMeta.addPrimaryKeyColumn("PK0", PrimaryKeyType.INTEGER);
+        request.setTableMeta(tableMeta);
+        request.setReservedThroughput(new CapacityUnit(1000000, 10000000));
+        ots.createTable(request);
+    }
+
+    public static void main(String[] args) throws Exception {
+        ClientConfiguration cc = new ClientConfiguration();
+        OTSServiceConfiguration sc = new OTSServiceConfiguration();
+        sc.setEnableResponseValidation(false);
+        cc.setIoThreadCount(1);
+        cc.setMaxConnections(1);
+        OTSRetryStrategy retryStrategy = new OTSRetryStrategy() {
+
+            @Override
+            public boolean shouldRetry(String action, Exception ex, int retries) {
+                return false;
+            }
+
+            @Override
+            public long getPauseDelay(String action, Exception ex, int retries) {
+                return 10;
+            }
+
+        };
+        sc.setRetryStrategy(retryStrategy);
+
+        OTS ots = null;
+
+        if (false) {
+            ots = new OTSClient("http://10.101.166.28:8888",
+                    "accessid_for_zzf_test", "accesskey_for_zzf_testt", "hellotest",
+                    cc, sc);
+        } else {
+            ots = new OTSClient("http://10.101.168.147:9090",
+                    "accessid_for_zzf_test", "accesskey_for_zzf_testt", "hellotest",
+                    cc, sc);
+        }
+
+        //createTable(ots);
+
+        try {
+
+            List<Thread> threads = new ArrayList<Thread>();
+            for (int i = 0; i < 1; i++) {
+                threads.add(new Thread(new BatchWriteRowThread(ots)));
+            }
+
+            for (Thread t : threads) {
+                t.start();
+            }
+
+            for (Thread t : threads) {
+                t.join();
+            }
+
+        }finally {
+            ots.shutdown();
+        }
+    }
+}
diff --git a/src/test/java/com/aliyun/openservices/ots/smoketest/MTThread.java b/src/test/java/com/aliyun/openservices/ots/smoketest/MTThread.java
new file mode 100755
index 0000000..8c94f6a
--- /dev/null
+++ b/src/test/java/com/aliyun/openservices/ots/smoketest/MTThread.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) Alibaba Cloud Computing
+ * All rights reserved.
+ * 
+ * 版权所有 （C）阿里云计算有限公司
+ */
+
+package com.aliyun.openservices.ots.smoketest;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import com.aliyun.openservices.ots.utils.DateUtil;
+
+import static com.aliyun.openservices.ots.utils.HttpUtil.*;
+
+public class MTThread {
+    static class Test implements Runnable {
+
+        @Override
+        public void run() {
+            // while (true) {
+            System.out.println("Start");
+            try {
+                for (int i = 0; i < 10; i++) {
+                    Map<String, String> headers = new HashMap<String, String>();
+                    headers.put("Hello1", "World");
+                    headers.put("Hello2", "World");
+                    headers.put("Hello3", "World");
+                    headers.put("Hello4", "World");
+                    headers.put("Hello5", "World");
+                    headers.put("Hello6", "World");
+                    headers.put("Hello7", "World");
+                    headers.put("Hello8", "World");
+                    headers.put("Hello9", "World");
+                    headers.put("Hello10", "World");
+                    
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            System.out.println("End");
+        }
+        // }
+
+    }
+
+    public static void main(String[] args) throws InterruptedException, UnsupportedEncodingException {
+        long total = 1000000;
+        long time = 8903;
+        double qps = 1.0 * total / (time * 1.0 / 1000.0);
+        System.out.println(qps);
+        
+    }
+}
diff --git a/src/test/java/com/aliyun/openservices/ots/smoketest/SmokeTest.java b/src/test/java/com/aliyun/openservices/ots/smoketest/SmokeTest.java
new file mode 100755
index 0000000..ba6b3ec
--- /dev/null
+++ b/src/test/java/com/aliyun/openservices/ots/smoketest/SmokeTest.java
@@ -0,0 +1,353 @@
+/**
+ * Copyright (C) Alibaba Cloud Computing
+ * All rights reserved.
+ * 
+ * 版权所有 （C）阿里云计算有限公司
+ */
+
+package com.aliyun.openservices.ots.smoketest;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Date;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import com.aliyun.openservices.ots.ClientConfiguration;
+import com.aliyun.openservices.ots.ClientException;
+import com.aliyun.openservices.ots.ServiceException;
+import com.aliyun.openservices.ots.OTS;
+import com.aliyun.openservices.ots.OTSClient;
+import com.aliyun.openservices.ots.OTSException;
+import com.aliyun.openservices.ots.OTSServiceConfiguration;
+import com.aliyun.openservices.ots.internal.OTSCallback;
+import com.aliyun.openservices.ots.model.BatchGetRowRequest;
+import com.aliyun.openservices.ots.model.BatchGetRowResult;
+import com.aliyun.openservices.ots.model.BatchGetRowResult.RowStatus;
+import com.aliyun.openservices.ots.model.BatchWriteRowRequest;
+import com.aliyun.openservices.ots.model.BatchWriteRowResult;
+import com.aliyun.openservices.ots.model.DeleteRowResult;
+import com.aliyun.openservices.ots.model.DeleteTableRequest;
+import com.aliyun.openservices.ots.model.DeleteTableResult;
+import com.aliyun.openservices.ots.model.RangeIteratorParameter;
+import com.aliyun.openservices.ots.model.ReservedThroughputChange;
+import com.aliyun.openservices.ots.model.CapacityUnit;
+import com.aliyun.openservices.ots.model.ColumnValue;
+import com.aliyun.openservices.ots.model.CreateTableRequest;
+import com.aliyun.openservices.ots.model.DeleteRowRequest;
+import com.aliyun.openservices.ots.model.DescribeTableRequest;
+import com.aliyun.openservices.ots.model.DescribeTableResult;
+import com.aliyun.openservices.ots.model.GetRangeRequest;
+import com.aliyun.openservices.ots.model.GetRangeResult;
+import com.aliyun.openservices.ots.model.GetRowRequest;
+import com.aliyun.openservices.ots.model.GetRowResult;
+import com.aliyun.openservices.ots.model.ListTableResult;
+import com.aliyun.openservices.ots.model.MultiRowQueryCriteria;
+import com.aliyun.openservices.ots.model.OTSContext;
+import com.aliyun.openservices.ots.model.OTSFuture;
+import com.aliyun.openservices.ots.model.OTSResult;
+import com.aliyun.openservices.ots.model.RowPrimaryKey;
+import com.aliyun.openservices.ots.model.PrimaryKeyType;
+import com.aliyun.openservices.ots.model.PrimaryKeyValue;
+import com.aliyun.openservices.ots.model.PutRowRequest;
+import com.aliyun.openservices.ots.model.PutRowResult;
+import com.aliyun.openservices.ots.model.RangeRowQueryCriteria;
+import com.aliyun.openservices.ots.model.Row;
+import com.aliyun.openservices.ots.model.RowDeleteChange;
+import com.aliyun.openservices.ots.model.RowPutChange;
+import com.aliyun.openservices.ots.model.RowUpdateChange;
+import com.aliyun.openservices.ots.model.SingleRowQueryCriteria;
+import com.aliyun.openservices.ots.model.TableMeta;
+import com.aliyun.openservices.ots.model.UpdateRowRequest;
+import com.aliyun.openservices.ots.model.UpdateRowResult;
+import com.aliyun.openservices.ots.model.UpdateTableRequest;
+import com.aliyun.openservices.ots.model.UpdateTableResult;
+import com.aliyun.openservices.ots.utils.ServiceSettings;
+
+public class SmokeTest {
+    private static OTS ots;
+    private static String endpoint;
+    private static String accessId;
+    private static String accessKey;
+    private static String instanceName;
+
+    public static void main(String[] args) throws Exception {
+        ServiceSettings ss = ServiceSettings.load();
+        endpoint = ss.getOTSEndpoint();
+        accessId = ss.getOTSAccessKeyId();
+        accessKey = ss.getOTSAccessKeySecret();
+        instanceName = ss.getOTSInstanceName();
+        OTSServiceConfiguration osc = new OTSServiceConfiguration();
+        osc.setEnableRequestCompression(false);
+        osc.setEnableResponseCompression(false);
+        ExecutorService pool = Executors.newFixedThreadPool(4);
+        ClientConfiguration config = new ClientConfiguration();
+        ots = new OTSClient(endpoint, accessId, accessKey, instanceName,
+                config, osc);
+        String tableName = "sdk_test";
+//         createTable(tableName);
+//         putRow(tableName, "aaa");
+//         deleteTable(tableName);
+//        listTable();
+//        describeTable(tableName);
+        // updateTable(tableName, 5000, 5000);
+        // deleteTable(tableName);
+//        updateRow(tableName, "aaa");
+       
+//        putRow(tableName, "bbb");
+//        putRow(tableName, "ccc");
+//        getRow(tableName, "aaa");
+//        deleteRow(tableName, "aaa");
+//        batchGetRow(tableName);
+//        batchWriteRow(tableName);
+        getRange(tableName);
+//        getRangeByIterator(tableName);
+        ots.shutdown();
+    }
+
+    private static void listTable() {
+        ListTableResult result = ots.listTable();
+        for (String tableName : result.getTableNames()) {
+            System.out.println(tableName);
+        }
+    }
+
+    private static void createTable(String tableName) {
+        CreateTableRequest request = new CreateTableRequest();
+        TableMeta tableMeta = new TableMeta(tableName);
+        tableMeta.addPrimaryKeyColumn("pk", PrimaryKeyType.STRING);
+        CapacityUnit capacityUnit = new CapacityUnit();
+        capacityUnit.setReadCapacityUnit(1000);
+        capacityUnit.setWriteCapacityUnit(1000);
+        request.setTableMeta(tableMeta);
+        request.setReservedThroughput(capacityUnit);
+        try {
+            ots.createTable(request);
+        } catch (OTSException ex) {
+            System.out.println(ex.getErrorCode());
+            ex.printStackTrace();
+        }
+    }
+
+    private static void describeTable(String tableName) {
+        DescribeTableRequest describeTableRequest = new DescribeTableRequest();
+        describeTableRequest.setTableName(tableName);
+        DescribeTableResult result = ots.describeTable(describeTableRequest);
+        TableMeta tableMeta = result.getTableMeta();
+        System.out.println(tableMeta.getTableName());
+        for (Entry<String, PrimaryKeyType> entry : tableMeta.getPrimaryKey()
+                .entrySet()) {
+            System.out.println(entry.getKey() + ":" + entry.getValue());
+        }
+
+        System.out.println("Read:"
+                + result.getReservedThroughputDetails().getCapacityUnit()
+                        .getReadCapacityUnit());
+        System.out.println("Write:"
+                + result.getReservedThroughputDetails().getCapacityUnit()
+                        .getWriteCapacityUnit());
+    }
+
+    private static void updateTable(String tableName, int readCu, int writeCu) {
+        UpdateTableRequest updateTableCapacityRequest = new UpdateTableRequest();
+        updateTableCapacityRequest.setTableName(tableName);
+        ReservedThroughputChange capacityChange = new ReservedThroughputChange();
+        if (readCu != -1) {
+            capacityChange.setReadCapacityUnit(readCu);
+        }
+        if (writeCu != -1) {
+            capacityChange.setWriteCapacityUnit(writeCu);
+        }
+        updateTableCapacityRequest.setReservedThroughputChange(capacityChange);
+        UpdateTableResult result = ots.updateTable(updateTableCapacityRequest);
+
+        System.out.println("LastDecreaseTime: "
+                + result.getReservedThroughputDetails().getLastDecreaseTime());
+        System.out.println("LastIncreaseTime: "
+                + result.getReservedThroughputDetails().getLastIncreaseTime());
+        System.out.println("DecreaseCount: "
+                + result.getReservedThroughputDetails()
+                        .getNumberOfDecreasesToday());
+    }
+
+    private static void deleteTable(String tableName) {
+        DeleteTableRequest deleteTableRequest = new DeleteTableRequest();
+        deleteTableRequest.setTableName(tableName);
+        ots.deleteTable(deleteTableRequest);
+    }
+
+    private static void getRow(String tableName, String pk) {
+        GetRowRequest getRowRequest = new GetRowRequest();
+        SingleRowQueryCriteria rowQueryCriteria = new SingleRowQueryCriteria(
+                tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowQueryCriteria.setPrimaryKey(pks);
+        getRowRequest.setRowQueryCriteria(rowQueryCriteria);
+        GetRowResult result = ots.getRow(getRowRequest);
+        Row row = result.getRow();
+        for (Entry<String, ColumnValue> entry : row.getColumns().entrySet()) {
+            System.out.println(entry.getKey() + ":" + entry.getValue());
+        }
+        System.out.println("ReadCapacity:"
+                + result.getConsumedCapacity().getCapacityUnit()
+                        .getReadCapacityUnit());
+        System.out.println("WriteCapacity:"
+                + result.getConsumedCapacity().getCapacityUnit()
+                        .getWriteCapacityUnit());
+    }
+
+    private static void putRow(String tableName, String pk) {
+        PutRowRequest request = new PutRowRequest();
+        RowPutChange rowChange = new RowPutChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        request.setRowChange(rowChange);
+        long timeSt = new Date().getTime();
+        try {
+            PutRowResult result = ots.putRow(request);
+            System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                    .getReadCapacityUnit());
+            System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                    .getWriteCapacityUnit());
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            System.out.println(ex.getMessage());
+            System.out.println(new Date().getTime() - timeSt);
+        }
+    }
+
+    private static void updateRow(String tableName, String pk) {
+        UpdateRowRequest updateRowRequest = new UpdateRowRequest();
+        RowUpdateChange rowChange = new RowUpdateChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        rowChange.addAttributeColumn("col0", ColumnValue.fromBoolean(true));
+        updateRowRequest.setRowChange(rowChange);
+        UpdateRowResult result = ots.updateRow(updateRowRequest);
+        System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                .getReadCapacityUnit());
+        System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                .getWriteCapacityUnit());
+    }
+
+    private static void deleteRow(String tableName, String pk) {
+        DeleteRowRequest deleteRowRequest = new DeleteRowRequest();
+        RowDeleteChange rowChange = new RowDeleteChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        deleteRowRequest.setRowChange(rowChange);
+        DeleteRowResult result = ots.deleteRow(deleteRowRequest);
+        System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                .getReadCapacityUnit());
+        System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                .getWriteCapacityUnit());
+    }
+
+    private static void batchGetRow(String tableName) {
+        BatchGetRowRequest batchGetRowRequest = new BatchGetRowRequest();
+        MultiRowQueryCriteria criteria = new MultiRowQueryCriteria(tableName);
+        criteria.addColumnsToGet("col");
+        RowPrimaryKey pk1 = new RowPrimaryKey();
+        pk1.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString("a"));
+        criteria.addRow(pk1);
+        batchGetRowRequest.addMultiRowQueryCriteria(criteria);
+        BatchGetRowResult result = ots.batchGetRow(batchGetRowRequest);
+        List<RowStatus> statues = result.getBatchGetRowStatus(tableName);
+        for (RowStatus status : statues) {
+            System.out.println(status.isSucceed());
+            System.out.println(status.getConsumedCapacity().getCapacityUnit()
+                    .getReadCapacityUnit());
+            System.out.println(status.getConsumedCapacity().getCapacityUnit()
+                    .getWriteCapacityUnit());
+            if (!status.isSucceed()) {
+                System.out.println(status.getError().getMessage());
+            }
+        }
+    }
+
+    private static void batchWriteRow(String tableName) {
+        BatchWriteRowRequest batchWriteRowRequest = new BatchWriteRowRequest();
+        RowPutChange rowPutChange = new RowPutChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString("ee"));
+        rowPutChange.setPrimaryKey(pks);
+        rowPutChange.addAttributeColumn("COL1", ColumnValue.fromLong(999));
+        for (int j = 0; j < 10; j++) {
+            rowPutChange.addAttributeColumn("COL2" + j,
+                    ColumnValue.fromString("" + j));
+        }
+        batchWriteRowRequest.addRowPutChange(rowPutChange);
+        BatchWriteRowResult result = ots.batchWriteRow(batchWriteRowRequest);
+        List<BatchWriteRowResult.RowStatus> statues = result
+                .getPutRowStatus(tableName);
+        for (BatchWriteRowResult.RowStatus status : statues) {
+            System.out.println(status.isSucceed());
+            System.out.println(status.getConsumedCapacity().getCapacityUnit()
+                    .getReadCapacityUnit());
+            System.out.println(status.getConsumedCapacity().getCapacityUnit()
+                    .getWriteCapacityUnit());
+            if (!status.isSucceed()) {
+                System.out.println(status.getError().getMessage());
+            }
+        }
+    }
+
+    private static void getRange(String tableName) {
+        GetRangeRequest getRangeRequest = new GetRangeRequest();
+        RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(
+                tableName);
+        RowPrimaryKey start = new RowPrimaryKey();
+        RowPrimaryKey end = new RowPrimaryKey();
+
+        start.addPrimaryKeyColumn("pk", PrimaryKeyValue.INF_MIN);
+        end.addPrimaryKeyColumn("pk", PrimaryKeyValue.INF_MAX);
+
+        rangeRowQueryCriteria.setInclusiveStartPrimaryKey(start);
+        rangeRowQueryCriteria.setExclusiveEndPrimaryKey(end);
+
+        rangeRowQueryCriteria.setLimit(-1);
+        rangeRowQueryCriteria.addColumnsToGet(new String[] {  "pk" });
+
+        getRangeRequest.setRangeRowQueryCriteria(rangeRowQueryCriteria);
+        GetRangeResult result = ots.getRange(getRangeRequest);
+        System.out.println("NextStartKey:" + result.getNextStartPrimaryKey());
+
+        for (Row row : result.getRows()) {
+            for (Entry<String, ColumnValue> entry : row.getColumns().entrySet()) {
+                System.out.println(entry.getKey() + ":" + entry.getValue());
+            }
+        }
+    }
+
+    private static void getRangeByIterator(String tableName) {
+        RangeIteratorParameter parameter = new RangeIteratorParameter(tableName);
+        RowPrimaryKey start = new RowPrimaryKey();
+        RowPrimaryKey end = new RowPrimaryKey();
+
+        start.addPrimaryKeyColumn("pk", PrimaryKeyValue.INF_MIN);
+        end.addPrimaryKeyColumn("pk", PrimaryKeyValue.INF_MAX);
+
+        parameter.setInclusiveStartPrimaryKey(start);
+        parameter.setExclusiveEndPrimaryKey(end);
+
+        Iterator<Row> rows = ots.createRangeIterator(parameter);
+        List<Row> result = new ArrayList<Row>();
+        while (rows.hasNext()) {
+            result.add(rows.next());
+        }
+
+        System.out.println(result.size());
+    }
+}
diff --git a/src/test/java/com/aliyun/openservices/ots/smoketest/SmokeTestForAsync.java b/src/test/java/com/aliyun/openservices/ots/smoketest/SmokeTestForAsync.java
new file mode 100755
index 0000000..47b6beb
--- /dev/null
+++ b/src/test/java/com/aliyun/openservices/ots/smoketest/SmokeTestForAsync.java
@@ -0,0 +1,848 @@
+/**
+ * Copyright (C) Alibaba Cloud Computing
+ * All rights reserved.
+ * 
+ * 版权所有 （C）阿里云计算有限公司
+ */
+
+package com.aliyun.openservices.ots.smoketest;
+
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Date;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.aliyun.openservices.ots.ClientConfiguration;
+import com.aliyun.openservices.ots.ClientException;
+import com.aliyun.openservices.ots.OTSAsync;
+import com.aliyun.openservices.ots.OTSClientAsync;
+import com.aliyun.openservices.ots.OTSException;
+import com.aliyun.openservices.ots.OTSServiceConfiguration;
+import com.aliyun.openservices.ots.internal.OTSCallback;
+import com.aliyun.openservices.ots.model.BatchGetRowRequest;
+import com.aliyun.openservices.ots.model.BatchGetRowResult;
+import com.aliyun.openservices.ots.model.BatchGetRowResult.RowStatus;
+import com.aliyun.openservices.ots.model.BatchWriteRowRequest;
+import com.aliyun.openservices.ots.model.BatchWriteRowResult;
+import com.aliyun.openservices.ots.model.CreateTableResult;
+import com.aliyun.openservices.ots.model.DeleteRowResult;
+import com.aliyun.openservices.ots.model.DeleteTableRequest;
+import com.aliyun.openservices.ots.model.DeleteTableResult;
+import com.aliyun.openservices.ots.model.ReservedThroughputChange;
+import com.aliyun.openservices.ots.model.CapacityUnit;
+import com.aliyun.openservices.ots.model.ColumnValue;
+import com.aliyun.openservices.ots.model.CreateTableRequest;
+import com.aliyun.openservices.ots.model.DeleteRowRequest;
+import com.aliyun.openservices.ots.model.DescribeTableRequest;
+import com.aliyun.openservices.ots.model.DescribeTableResult;
+import com.aliyun.openservices.ots.model.GetRangeRequest;
+import com.aliyun.openservices.ots.model.GetRangeResult;
+import com.aliyun.openservices.ots.model.GetRowRequest;
+import com.aliyun.openservices.ots.model.GetRowResult;
+import com.aliyun.openservices.ots.model.ListTableRequest;
+import com.aliyun.openservices.ots.model.ListTableResult;
+import com.aliyun.openservices.ots.model.MultiRowQueryCriteria;
+import com.aliyun.openservices.ots.model.OTSContext;
+import com.aliyun.openservices.ots.model.OTSFuture;
+import com.aliyun.openservices.ots.model.RowPrimaryKey;
+import com.aliyun.openservices.ots.model.PrimaryKeyType;
+import com.aliyun.openservices.ots.model.PrimaryKeyValue;
+import com.aliyun.openservices.ots.model.PutRowRequest;
+import com.aliyun.openservices.ots.model.PutRowResult;
+import com.aliyun.openservices.ots.model.RangeRowQueryCriteria;
+import com.aliyun.openservices.ots.model.Row;
+import com.aliyun.openservices.ots.model.RowDeleteChange;
+import com.aliyun.openservices.ots.model.RowPutChange;
+import com.aliyun.openservices.ots.model.RowUpdateChange;
+import com.aliyun.openservices.ots.model.SingleRowQueryCriteria;
+import com.aliyun.openservices.ots.model.TableMeta;
+import com.aliyun.openservices.ots.model.UpdateRowRequest;
+import com.aliyun.openservices.ots.model.UpdateRowResult;
+import com.aliyun.openservices.ots.model.UpdateTableRequest;
+import com.aliyun.openservices.ots.model.UpdateTableResult;
+import com.aliyun.openservices.ots.utils.ServiceSettings;
+
+public class SmokeTestForAsync {
+    private static OTSAsync ots;
+    private static String endpoint;
+    private static String accessId;
+    private static String accessKey;
+    private static String instanceName;
+    private static CountDownLatch latch;
+    private static AtomicLong count;
+
+    public static void main(String[] args) throws Exception {
+        ServiceSettings ss = ServiceSettings.load();
+        endpoint = ss.getOTSEndpoint();
+        accessId = ss.getOTSAccessKeyId();
+        accessKey = ss.getOTSAccessKeySecret();
+        instanceName = ss.getOTSInstanceName();
+        OTSServiceConfiguration osc = new OTSServiceConfiguration();
+        osc.setEnableRequestCompression(false);
+        osc.setEnableResponseCompression(false);
+        ExecutorService pool = Executors.newFixedThreadPool(2);
+        ClientConfiguration config = new ClientConfiguration();
+        config.setMaxConnections(30);
+        ots = new OTSClientAsync(endpoint, accessId, accessKey, instanceName,
+                config, osc, pool);
+        count = new AtomicLong();
+         createTable("sdk_test");
+         listTable();
+        // createTable("test_why");
+//        updateTable("test_why", 50000, 50000);
+//        latch = new CountDownLatch(100);
+//        describeTable("test_why");
+//        long time0 = System.currentTimeMillis();
+//        for (int i = 0; i < 100; i++) {
+//            putRow("test_why", "" + i);
+//        }
+//        latch.await();
+//        System.out.println(System.currentTimeMillis() - time0);
+//        System.out.println("done");
+//        ots.shutdown();
+    }
+
+    private static OTSFuture<ListTableResult> listTable() {
+        OTSFuture<ListTableResult> future = ots
+                .listTable(new OTSCallback<ListTableRequest, ListTableResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<ListTableRequest, ListTableResult> otsContext) {
+                        ListTableResult result = otsContext.getOTSResult();
+                        for (String tableName : result.getTableNames()) {
+                            System.out.println(tableName);
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<ListTableRequest, ListTableResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+                        ex.printStackTrace();
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<ListTableRequest, ListTableResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+                        ex.printStackTrace();
+                    }
+                });
+        return future;
+    }
+
+    private static void listTableWithoutCallback() {
+        try {
+            OTSFuture<ListTableResult> future = ots.listTable();
+            ListTableResult result = future.get();
+            for (String tableName : result.getTableNames()) {
+                System.out.println(tableName);
+            }
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void createTable(String tableName) {
+        CreateTableRequest request = new CreateTableRequest();
+        TableMeta tableMeta = new TableMeta(tableName);
+        tableMeta.addPrimaryKeyColumn("pk", PrimaryKeyType.STRING);
+        CapacityUnit capacityUnit = new CapacityUnit();
+        capacityUnit.setReadCapacityUnit(5000);
+        capacityUnit.setWriteCapacityUnit(5000);
+        request.setTableMeta(tableMeta);
+        request.setReservedThroughput(capacityUnit);
+        OTSFuture<CreateTableResult> future = ots.createTable(request,
+                new OTSCallback<CreateTableRequest, CreateTableResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<CreateTableRequest, CreateTableResult> otsContext) {
+                        OTSFuture<ListTableResult> future = ots.listTable();
+                        ListTableResult result = future.get();
+                        for (String tableName : result.getTableNames()) {
+                            System.out.println(tableName);
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<CreateTableRequest, CreateTableResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+                        System.out.println("failed");
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<CreateTableRequest, CreateTableResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+        try {
+            future.get();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    private static void createTableWithoutCallback(String tableName) {
+        CreateTableRequest request = new CreateTableRequest();
+        TableMeta tableMeta = new TableMeta(tableName);
+        tableMeta.addPrimaryKeyColumn("pk", PrimaryKeyType.STRING);
+        CapacityUnit capacityUnit = new CapacityUnit();
+        capacityUnit.setReadCapacityUnit(1000);
+        capacityUnit.setWriteCapacityUnit(1000);
+        request.setTableMeta(tableMeta);
+        request.setReservedThroughput(capacityUnit);
+        try {
+            OTSFuture<CreateTableResult> future = ots.createTable(request);
+            CreateTableResult result = future.get();
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void describeTable(String tableName) {
+        DescribeTableRequest describeTableRequest = new DescribeTableRequest();
+        describeTableRequest.setTableName(tableName);
+        OTSFuture<DescribeTableResult> future = ots.describeTable(
+                describeTableRequest,
+                new OTSCallback<DescribeTableRequest, DescribeTableResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<DescribeTableRequest, DescribeTableResult> otsContext) {
+                        DescribeTableResult result = otsContext.getOTSResult();
+                        TableMeta tableMeta = result.getTableMeta();
+                        System.out.println(tableMeta.getTableName());
+                        for (Entry<String, PrimaryKeyType> entry : tableMeta
+                                .getPrimaryKey().entrySet()) {
+                            System.out.println(entry.getKey() + ":"
+                                    + entry.getValue());
+                        }
+                        System.out.println("Read:"
+                                + result.getReservedThroughputDetails()
+                                        .getCapacityUnit()
+                                        .getReadCapacityUnit());
+                        System.out.println("Write:"
+                                + result.getReservedThroughputDetails()
+                                        .getCapacityUnit()
+                                        .getWriteCapacityUnit());
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<DescribeTableRequest, DescribeTableResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<DescribeTableRequest, DescribeTableResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+    }
+
+    private static void describeTableWithoutCallback(String tableName) {
+        DescribeTableRequest describeTableRequest = new DescribeTableRequest();
+        describeTableRequest.setTableName(tableName);
+        try {
+            OTSFuture<DescribeTableResult> future = ots
+                    .describeTable(describeTableRequest);
+            DescribeTableResult result = future.get();
+            TableMeta tableMeta = result.getTableMeta();
+            System.out.println(tableMeta.getTableName());
+            for (Entry<String, PrimaryKeyType> entry : tableMeta
+                    .getPrimaryKey().entrySet()) {
+                System.out.println(entry.getKey() + ":" + entry.getValue());
+            }
+            System.out.println("Read:"
+                    + result.getReservedThroughputDetails().getCapacityUnit()
+                            .getReadCapacityUnit());
+            System.out.println("Write:"
+                    + result.getReservedThroughputDetails().getCapacityUnit()
+                            .getWriteCapacityUnit());
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void updateTable(String tableName, int readCu, int writeCu) {
+        UpdateTableRequest updateTableCapacityRequest = new UpdateTableRequest();
+        updateTableCapacityRequest.setTableName(tableName);
+        ReservedThroughputChange capacityChange = new ReservedThroughputChange();
+        if (readCu != -1) {
+            capacityChange.setReadCapacityUnit(readCu);
+        }
+        if (writeCu != -1) {
+            capacityChange.setWriteCapacityUnit(writeCu);
+        }
+        updateTableCapacityRequest.setReservedThroughputChange(capacityChange);
+        OTSFuture<UpdateTableResult> future = ots.updateTable(
+                updateTableCapacityRequest,
+                new OTSCallback<UpdateTableRequest, UpdateTableResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<UpdateTableRequest, UpdateTableResult> otsContext) {
+                        // TODO Auto-generated method stub
+                        UpdateTableResult result = otsContext.getOTSResult();
+                        System.out.println("LastDecreaseTime: "
+                                + result.getReservedThroughputDetails()
+                                        .getLastDecreaseTime());
+                        System.out.println("LastIncreaseTime: "
+                                + result.getReservedThroughputDetails()
+                                        .getLastIncreaseTime());
+                        System.out.println("DecreaseCount: "
+                                + result.getReservedThroughputDetails()
+                                        .getNumberOfDecreasesToday());
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<UpdateTableRequest, UpdateTableResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+                        ex.printStackTrace();
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<UpdateTableRequest, UpdateTableResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+                        ex.printStackTrace();
+
+                    }
+                });
+    }
+
+    private static void updateTableWithoutCallback(String tableName,
+            int readCu, int writeCu) {
+        UpdateTableRequest updateTableCapacityRequest = new UpdateTableRequest();
+        updateTableCapacityRequest.setTableName(tableName);
+        ReservedThroughputChange capacityChange = new ReservedThroughputChange();
+        if (readCu != -1) {
+            capacityChange.setReadCapacityUnit(readCu);
+        }
+        if (writeCu != -1) {
+            capacityChange.setWriteCapacityUnit(writeCu);
+        }
+        updateTableCapacityRequest.setReservedThroughputChange(capacityChange);
+        try {
+            OTSFuture<UpdateTableResult> future = ots
+                    .updateTable(updateTableCapacityRequest);
+            UpdateTableResult result = future.get();
+            System.out.println("LastDecreaseTime: "
+                    + result.getReservedThroughputDetails()
+                            .getLastDecreaseTime());
+            System.out.println("LastIncreaseTime: "
+                    + result.getReservedThroughputDetails()
+                            .getLastIncreaseTime());
+            System.out.println("DecreaseCount: "
+                    + result.getReservedThroughputDetails()
+                            .getNumberOfDecreasesToday());
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void deleteTable(String tableName) {
+        DeleteTableRequest deleteTableRequest = new DeleteTableRequest();
+        deleteTableRequest.setTableName(tableName);
+        OTSFuture<DeleteTableResult> future = ots.deleteTable(
+                deleteTableRequest,
+                new OTSCallback<DeleteTableRequest, DeleteTableResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<DeleteTableRequest, DeleteTableResult> otsContext) {
+                        System.out.println(otsContext.getOTSRequest()
+                                .getTableName());
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<DeleteTableRequest, DeleteTableResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<DeleteTableRequest, DeleteTableResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+    }
+
+    private static void deleteTableWithoutCallback(String tableName) {
+        DeleteTableRequest deleteTableRequest = new DeleteTableRequest();
+        deleteTableRequest.setTableName(tableName);
+        try {
+            OTSFuture<DeleteTableResult> future = ots
+                    .deleteTable(deleteTableRequest);
+            DeleteTableResult result = future.get();
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void getRow(String tableName, String pk) {
+        GetRowRequest getRowRequest = new GetRowRequest();
+        SingleRowQueryCriteria rowQueryCriteria = new SingleRowQueryCriteria(
+                tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowQueryCriteria.setPrimaryKey(pks);
+        getRowRequest.setRowQueryCriteria(rowQueryCriteria);
+
+        OTSFuture<GetRowResult> future = ots.getRow(getRowRequest,
+                new OTSCallback<GetRowRequest, GetRowResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<GetRowRequest, GetRowResult> otsContext) {
+                        GetRowResult result = otsContext.getOTSResult();
+                        // Row row = result.getRow();
+                        // for (Entry<String, ColumnValue> entry : row
+                        // .getColumns().entrySet()) {
+                        // System.out.println(entry.getKey() + ":"
+                        // + entry.getValue());
+                        // }
+                        // System.out.println("ReadCapacity:"
+                        // + result.getConsumedCapacity()
+                        // .getCapacityUnit()
+                        // .getReadCapacityUnit());
+                        // System.out.println("WriteCapacity:"
+                        // + result.getConsumedCapacity()
+                        // .getCapacityUnit()
+                        // .getWriteCapacityUnit());
+                        latch.countDown();
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<GetRowRequest, GetRowResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+                        System.out.println(ex.getErrorCode());
+                        latch.countDown();
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<GetRowRequest, GetRowResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+                        System.out.println(ex.getErrorCode());
+                        latch.countDown();
+                    }
+                });
+    }
+
+    private static void getRowWithoutCallback(String tableName, String pk) {
+        GetRowRequest getRowRequest = new GetRowRequest();
+        SingleRowQueryCriteria rowQueryCriteria = new SingleRowQueryCriteria(
+                tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowQueryCriteria.setPrimaryKey(pks);
+        getRowRequest.setRowQueryCriteria(rowQueryCriteria);
+        try {
+            OTSFuture<GetRowResult> future = ots.getRow(getRowRequest);
+            GetRowResult result = future.get();
+            Row row = result.getRow();
+            for (Entry<String, ColumnValue> entry : row.getColumns().entrySet()) {
+                System.out.println(entry.getKey() + ":" + entry.getValue());
+            }
+            System.out.println("ReadCapacity:"
+                    + result.getConsumedCapacity().getCapacityUnit()
+                            .getReadCapacityUnit());
+            System.out.println("WriteCapacity:"
+                    + result.getConsumedCapacity().getCapacityUnit()
+                            .getWriteCapacityUnit());
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void putRow(String tableName, String pk) {
+        PutRowRequest request = new PutRowRequest();
+        RowPutChange rowChange = new RowPutChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        request.setRowChange(rowChange);
+        final long timeSt = new Date().getTime();
+        OTSFuture<PutRowResult> future = ots.putRow(request,
+                new OTSCallback<PutRowRequest, PutRowResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<PutRowRequest, PutRowResult> otsContext) {
+                        PutRowResult result = otsContext.getOTSResult();
+                        latch.countDown();
+                        // System.out.println(System.currentTimeMillis());
+                        // System.out.println(result.getConsumedCapacity()
+                        // .getCapacityUnit().getReadCapacityUnit());
+                        // System.out.println(result.getConsumedCapacity()
+                        // .getCapacityUnit().getWriteCapacityUnit());
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<PutRowRequest, PutRowResult> otsContext,
+                            OTSException ex) {
+                        latch.countDown();
+                        System.out.println(ex.getErrorCode());
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<PutRowRequest, PutRowResult> otsContext,
+                            ClientException ex) {
+                        latch.countDown();
+                        System.out.println(ex.getErrorCode());
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+    }
+
+    private static void putRowWithoutCallback(String tableName, String pk) {
+        PutRowRequest request = new PutRowRequest();
+        RowPutChange rowChange = new RowPutChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        request.setRowChange(rowChange);
+        try {
+            OTSFuture<PutRowResult> future = ots.putRow(request);
+            PutRowResult result = future.get();
+            System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                    .getReadCapacityUnit());
+            System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                    .getWriteCapacityUnit());
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void updateRow(String tableName, String pk) {
+        UpdateRowRequest updateRowRequest = new UpdateRowRequest();
+        RowUpdateChange rowChange = new RowUpdateChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        rowChange.addAttributeColumn("col0", ColumnValue.fromBoolean(true));
+        updateRowRequest.setRowChange(rowChange);
+        OTSFuture<UpdateRowResult> future = ots.updateRow(updateRowRequest,
+                new OTSCallback<UpdateRowRequest, UpdateRowResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<UpdateRowRequest, UpdateRowResult> otsContext) {
+                        UpdateRowResult result = otsContext.getOTSResult();
+                        System.out.println(result.getConsumedCapacity()
+                                .getCapacityUnit().getReadCapacityUnit());
+                        System.out.println(result.getConsumedCapacity()
+                                .getCapacityUnit().getWriteCapacityUnit());
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<UpdateRowRequest, UpdateRowResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<UpdateRowRequest, UpdateRowResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+    }
+
+    private static void updateRowWithoutCallback(String tableName, String pk) {
+        UpdateRowRequest updateRowRequest = new UpdateRowRequest();
+        RowUpdateChange rowChange = new RowUpdateChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        rowChange.addAttributeColumn("col0", ColumnValue.fromBoolean(true));
+        updateRowRequest.setRowChange(rowChange);
+        try {
+            OTSFuture<UpdateRowResult> future = ots.updateRow(updateRowRequest);
+            UpdateRowResult result = future.get();
+            System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                    .getReadCapacityUnit());
+            System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                    .getWriteCapacityUnit());
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void deleteRow(String tableName, String pk) {
+        DeleteRowRequest deleteRowRequest = new DeleteRowRequest();
+        RowDeleteChange rowChange = new RowDeleteChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        deleteRowRequest.setRowChange(rowChange);
+        OTSFuture<DeleteRowResult> future = ots.deleteRow(deleteRowRequest,
+                new OTSCallback<DeleteRowRequest, DeleteRowResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<DeleteRowRequest, DeleteRowResult> otsContext) {
+                        DeleteRowResult result = otsContext.getOTSResult();
+                        System.out.println(result.getConsumedCapacity()
+                                .getCapacityUnit().getReadCapacityUnit());
+                        System.out.println(result.getConsumedCapacity()
+                                .getCapacityUnit().getWriteCapacityUnit());
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<DeleteRowRequest, DeleteRowResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<DeleteRowRequest, DeleteRowResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+    }
+
+    private static void deleteRowWithoutCallback(String tableName, String pk) {
+        DeleteRowRequest deleteRowRequest = new DeleteRowRequest();
+        RowDeleteChange rowChange = new RowDeleteChange(tableName);
+        RowPrimaryKey pks = new RowPrimaryKey();
+        pks.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString(pk));
+        rowChange.setPrimaryKey(pks);
+        deleteRowRequest.setRowChange(rowChange);
+        try {
+            OTSFuture<DeleteRowResult> future = ots.deleteRow(deleteRowRequest);
+            DeleteRowResult result = future.get();
+            System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                    .getReadCapacityUnit());
+            System.out.println(result.getConsumedCapacity().getCapacityUnit()
+                    .getWriteCapacityUnit());
+        } catch (ClientException ex) {
+            ex.printStackTrace();
+        } catch (OTSException ex) {
+            System.out.println(ex.getRequestId());
+            System.out.println(ex.getErrorCode());
+        }
+    }
+
+    private static void batchGetRow(String tableName) {
+        BatchGetRowRequest batchGetRowRequest = new BatchGetRowRequest();
+        {
+            MultiRowQueryCriteria criteria = new MultiRowQueryCriteria(
+                    tableName);
+            criteria.addColumnsToGet("col");
+            RowPrimaryKey pk1 = new RowPrimaryKey();
+            pk1.addPrimaryKeyColumn("PK0", PrimaryKeyValue.fromString("a"));
+            pk1.addPrimaryKeyColumn("PK1", PrimaryKeyValue.fromString("a"));
+            criteria.addRow(pk1);
+            batchGetRowRequest.addMultiRowQueryCriteria(criteria);
+        }
+        OTSFuture<BatchGetRowResult> future = ots.batchGetRow(
+                batchGetRowRequest,
+                new OTSCallback<BatchGetRowRequest, BatchGetRowResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<BatchGetRowRequest, BatchGetRowResult> otsContext) {
+                        BatchGetRowResult result = otsContext.getOTSResult();
+                        List<RowStatus> statues = result
+                                .getBatchGetRowStatus("zzf");
+                        for (RowStatus status : statues) {
+                            System.out.println(status.isSucceed());
+                            System.out.println(status.getError().getMessage());
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<BatchGetRowRequest, BatchGetRowResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<BatchGetRowRequest, BatchGetRowResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+    }
+
+    private static void batchGetRowWithoutCallback() {
+
+    }
+
+    private static void batchWriteRow(final String tableName) {
+        BatchWriteRowRequest batchWriteRowRequest = new BatchWriteRowRequest();
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < 1100; i++) {
+            sb.append('c');
+        }
+        String longC = sb.toString();
+        for (int i = 0; i < 100; i++) {
+            RowPutChange rowPutChange = new RowPutChange(tableName);
+            RowPrimaryKey pks = new RowPrimaryKey();
+            pks.addPrimaryKeyColumn("PK0", PrimaryKeyValue.fromString("ee" + i));
+            pks.addPrimaryKeyColumn("PK1", PrimaryKeyValue.fromString("ee"));
+            rowPutChange.setPrimaryKey(pks);
+            rowPutChange.addAttributeColumn("COL1", ColumnValue.fromLong(999));
+            for (int j = 0; j < 10; j++) {
+                rowPutChange.addAttributeColumn("COL2" + j,
+                        ColumnValue.fromString(longC));
+            }
+            batchWriteRowRequest.addRowPutChange(rowPutChange);
+        }
+
+        OTSFuture<BatchWriteRowResult> future = ots.batchWriteRow(
+                batchWriteRowRequest,
+                new OTSCallback<BatchWriteRowRequest, BatchWriteRowResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<BatchWriteRowRequest, com.aliyun.openservices.ots.model.BatchWriteRowResult> otsContext) {
+                        BatchWriteRowResult result = otsContext.getOTSResult();
+                        List<BatchWriteRowResult.RowStatus> statues = result
+                                .getPutRowStatus(tableName);
+                        for (BatchWriteRowResult.RowStatus status : statues) {
+                            System.out.println(status.isSucceed());
+                            if (!status.isSucceed()) {
+                                System.out.println(status.getError()
+                                        .getMessage());
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<BatchWriteRowRequest, BatchWriteRowResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<BatchWriteRowRequest, BatchWriteRowResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+    }
+
+    private static void batchWriteRowWithoutCallback() {
+
+    }
+
+    private static void getRange(String tableName) {
+        GetRangeRequest getRangeRequest = new GetRangeRequest();
+        RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(
+                tableName);
+        RowPrimaryKey start = new RowPrimaryKey();
+        RowPrimaryKey end = new RowPrimaryKey();
+
+        start.addPrimaryKeyColumn("uid", PrimaryKeyValue.INF_MIN);
+        end.addPrimaryKeyColumn("uid", PrimaryKeyValue.INF_MAX);
+
+        rangeRowQueryCriteria.setInclusiveStartPrimaryKey(start);
+        rangeRowQueryCriteria.setExclusiveEndPrimaryKey(end);
+
+        rangeRowQueryCriteria.setLimit(10);
+        rangeRowQueryCriteria.addColumnsToGet(new String[] { "COL1", "uid" });
+
+        getRangeRequest.setRangeRowQueryCriteria(rangeRowQueryCriteria);
+        OTSFuture<GetRangeResult> future = ots.getRange(getRangeRequest,
+                new OTSCallback<GetRangeRequest, GetRangeResult>() {
+                    @Override
+                    public void onCompleted(
+                            OTSContext<GetRangeRequest, GetRangeResult> otsContext) {
+                        GetRangeResult result = otsContext.getOTSResult();
+                        System.out.println("NextStartKey:"
+                                + result.getNextStartPrimaryKey());
+                        for (Row row : result.getRows()) {
+                            for (Entry<String, ColumnValue> entry : row
+                                    .getColumns().entrySet()) {
+                                System.out.println(entry.getKey() + ":"
+                                        + entry.getValue());
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<GetRangeRequest, GetRangeResult> otsContext,
+                            OTSException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    @Override
+                    public void onFailed(
+                            OTSContext<GetRangeRequest, GetRangeResult> otsContext,
+                            ClientException ex) {
+                        // TODO Auto-generated method stub
+
+                    }
+                });
+        GetRangeResult result = future.get();
+    }
+
+}
diff --git a/src/test/java/com/aliyun/openservices/ots/smoketest/Test.java b/src/test/java/com/aliyun/openservices/ots/smoketest/Test.java
new file mode 100644
index 0000000..ac44c27
--- /dev/null
+++ b/src/test/java/com/aliyun/openservices/ots/smoketest/Test.java
@@ -0,0 +1,151 @@
+package com.aliyun.openservices.ots.smoketest;
+
+import com.aliyun.openservices.ots.ClientException;
+import com.aliyun.openservices.ots.OTS;
+import com.aliyun.openservices.ots.OTSAsync;
+import com.aliyun.openservices.ots.OTSException;
+import com.aliyun.openservices.ots.integration.OTSClientFactory;
+import com.aliyun.openservices.ots.internal.OTSCallback;
+import com.aliyun.openservices.ots.model.*;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+
+public class Test {
+
+
+    private static OTS ots = OTSClientFactory.createOTSClient();
+    private static OTSAsync otsAsync = OTSClientFactory.createOTSClientAsync();
+    private static String tableName = "iteratorTest";
+
+    private void createTable() {
+        TableMeta tableMeta = new TableMeta(tableName);
+        tableMeta.addPrimaryKeyColumn("pk", PrimaryKeyType.INTEGER);
+        CreateTableRequest createTableRequest = new CreateTableRequest(tableMeta);
+        createTableRequest.setReservedThroughput(new CapacityUnit(5000, 5000));
+        ots.createTable(createTableRequest);
+    }
+
+    private void prepareData(int n) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(n);
+        final Semaphore semaphore = new Semaphore(300);
+        for (int i = 0; i < n; i++) {
+            RowPrimaryKey rowPrimaryKey = new RowPrimaryKey();
+            rowPrimaryKey.addPrimaryKeyColumn("pk", PrimaryKeyValue.fromLong(i));
+            RowPutChange rowPutChange = new RowPutChange(tableName);
+            rowPutChange.setPrimaryKey(rowPrimaryKey);
+            rowPutChange.addAttributeColumn("col" + i, ColumnValue.fromLong(i));
+            if ((i % 1000) == 0) {
+                System.out.println(i);
+            }
+            semaphore.acquire();
+            otsAsync.putRow(new PutRowRequest(rowPutChange), new OTSCallback<PutRowRequest, PutRowResult>() {
+                @Override
+                public void onCompleted(OTSContext<PutRowRequest, PutRowResult> otsContext) {
+                    semaphore.release();
+                    latch.countDown();
+                }
+
+                @Override
+                public void onFailed(OTSContext<PutRowRequest, PutRowResult> otsContext, OTSException ex) {
+                    semaphore.release();
+                    latch.countDown();
+                    ex.printStackTrace();
+                }
+
+                @Override
+                public void onFailed(OTSContext<PutRowRequest, PutRowResult> otsContext, ClientException ex) {
+                    semaphore.release();
+                    latch.countDown();
+                    ex.printStackTrace();
+                }
+            });
+        }
+        latch.await();
+    }
+
+    private void rangeIterate(String colName) {
+        RangeIteratorParameter rangeIteratorParameter = new RangeIteratorParameter(tableName);
+        RowPrimaryKey rowPrimaryKey = new RowPrimaryKey();
+        rowPrimaryKey.addPrimaryKeyColumn("pk", PrimaryKeyValue.INF_MIN);
+        rangeIteratorParameter.setInclusiveStartPrimaryKey(rowPrimaryKey);
+        rowPrimaryKey = new RowPrimaryKey();
+        rowPrimaryKey.addPrimaryKeyColumn("pk", PrimaryKeyValue.INF_MAX);
+        rangeIteratorParameter.setExclusiveEndPrimaryKey(rowPrimaryKey);
+
+        rangeIteratorParameter.setColumnsToGet(Arrays.asList(colName));
+        Iterator<Row> iterator = ots.createRangeIterator(rangeIteratorParameter);
+        while (iterator.hasNext()) {
+            System.out.println(iterator.next());
+        }
+    }
+
+    private void testGetRange() throws InterruptedException {
+        RangeRowQueryCriteria criteria = new RangeRowQueryCriteria(tableName);
+        criteria.setInclusiveStartPrimaryKey(new RowPrimaryKey().addPrimaryKeyColumn("pk", PrimaryKeyValue.INF_MIN));
+        criteria.setExclusiveEndPrimaryKey(new RowPrimaryKey().addPrimaryKeyColumn("pk", PrimaryKeyValue.INF_MAX));
+        criteria.setColumnsToGet(Arrays.asList("col"));
+        GetRangeRequest request = new GetRangeRequest(criteria);
+
+        final Semaphore semaphore = new Semaphore(20);
+        for (int i = 0; i < 100000; i++) {
+            semaphore.acquire();
+            otsAsync.getRange(request, new OTSCallback<GetRangeRequest, GetRangeResult>() {
+                @Override
+                public void onCompleted(OTSContext<GetRangeRequest, GetRangeResult> otsContext) {
+                    semaphore.release();
+                    System.out.println(otsContext.getOTSResult().getConsumedCapacity().getCapacityUnit().getReadCapacityUnit());
+                }
+
+                @Override
+                public void onFailed(OTSContext<GetRangeRequest, GetRangeResult> otsContext, OTSException ex) {
+                    semaphore.release();
+                    ex.printStackTrace();
+                }
+
+                @Override
+                public void onFailed(OTSContext<GetRangeRequest, GetRangeResult> otsContext, ClientException ex) {
+                    semaphore.release();
+                    ex.printStackTrace();
+                }
+            });
+        }
+
+        /*
+        GetRangeResult result = ots.getRange(request);
+        System.out.println(result.getConsumedCapacity().getCapacityUnit().getReadCapacityUnit());
+        System.out.println(result.getRows());
+        System.out.println(result.getNextStartPrimaryKey());
+        */
+
+        SingleRowQueryCriteria singleRowQueryCriteria = new SingleRowQueryCriteria(tableName);
+        singleRowQueryCriteria.setPrimaryKey(new RowPrimaryKey().addPrimaryKeyColumn("pk", PrimaryKeyValue.fromLong(100000)));
+        GetRowRequest getRowRequest = new GetRowRequest(singleRowQueryCriteria);
+        GetRowResult getRowResult = ots.getRow(getRowRequest);
+        System.out.println(getRowResult.getRow());
+    }
+
+    private void test() throws InterruptedException {
+        createTable();
+        prepareData(100001);
+        testGetRange();
+        ListTableResult listTableResult = ots.listTable();
+        System.out.println(listTableResult.getTableNames());
+    }
+
+    public static void main(String[] args) throws InterruptedException {
+        Test test = new Test();
+        UpdateTableRequest updateTableRequest = new UpdateTableRequest(tableName);
+        ReservedThroughputChange reservedThroughputChange = new ReservedThroughputChange();
+        reservedThroughputChange.setReadCapacityUnit(100);
+        reservedThroughputChange.setWriteCapacityUnit(100);
+        updateTableRequest.setReservedThroughputChange(reservedThroughputChange);
+   //     ots.updateTable(updateTableRequest);
+        test.test();
+        ots.shutdown();
+        otsAsync.shutdown();
+    }
+
+}
