Repository: pinpoint-apm/pinpoint
Issue: Allow collector and web to fail fast when required tables for span/stat compatibility versions do not exist
Labels: enhancement

With the introduction of span/stat v2 tables new configuration options were added to ensure smooth transition from v1 -> v2.
The relevant configuration options are as follows:

**Collector** - _pinpoint-collector.properties_

```
collector.experimental.span.format.compatibility.version=v1|v2|dualWrite
collector.experimental.stat.format.compatibility.version=v1|v2|dualWrite
```

**Web** - _pinpoint-web.properties_

```
web.experimental.span.format.compatibility.version=v1|v2|compatibilityMode|dualRead
web.experimental.stat.format.compatibility.version=v1|v2|compatibilityMode
```

As it is currently, if the option is set without having the necessary table (for example setting `collector.experimental.span.format.compatibility.version=v2` without actually having the table `TraceV2`), the error is thrown late during runtime after collector startup. This could be especially undesirable for the collector as it could cause data losses.

It would be better to simply stop the collector from starting up correctly at all, and let developers know about it before traffic starts flowing in.
Current plan is to stop the collector/web from starting up under the following circumstances:
- v1 - Fail if `Traces` or `AgentStat` tables are missing
- v2 - Fail if `TraceV2` or `AgentStatV2` tables are missing
- dualWrite/dualRead/compatibilityMode - Fail if either of the v1, v2 tables is missing.


Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/collector/src/main/java/com/navercorp/pinpoint/collector/dao/hbase/HbaseTraceDaoFactory.java b/collector/src/main/java/com/navercorp/pinpoint/collector/dao/hbase/HbaseTraceDaoFactory.java
index 20189d724b50..1d0856a78669 100644
--- a/collector/src/main/java/com/navercorp/pinpoint/collector/dao/hbase/HbaseTraceDaoFactory.java
+++ b/collector/src/main/java/com/navercorp/pinpoint/collector/dao/hbase/HbaseTraceDaoFactory.java
@@ -1,6 +1,9 @@
 package com.navercorp.pinpoint.collector.dao.hbase;
 
 import com.navercorp.pinpoint.collector.dao.TraceDao;
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import org.apache.hadoop.hbase.TableName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.FactoryBean;
@@ -25,6 +28,9 @@ public class HbaseTraceDaoFactory implements FactoryBean<TraceDao> {
     @Qualifier("hbaseTraceDaoV2")
     private TraceDao v2;
 
+    @Autowired
+    private HBaseAdminTemplate adminTemplate;
+
     @Value("#{pinpoint_collector_properties['collector.experimental.span.format.compatibility.version'] ?: 'v1'}")
     private String mode = "v1";
 
@@ -33,17 +39,38 @@ public TraceDao getObject() throws Exception {
 
         logger.info("TraceDao Compatibility {}", mode);
 
+        final TableName v1TableName = HBaseTables.TRACES;
+        final TableName v2TableName = HBaseTables.TRACE_V2;
+
         if (mode.equalsIgnoreCase("v1")) {
-            return v1;
+            if (this.adminTemplate.tableExists(v1TableName)) {
+                return v1;
+            } else {
+                logger.error("TraceDao configured for v1, but {} table does not exist", v1TableName);
+                throw new IllegalStateException(v1TableName + " table does not exist");
+            }
         }
         else if (mode.equalsIgnoreCase("v2")) {
-            return v2;
+            if (this.adminTemplate.tableExists(v2TableName)) {
+                return v2;
+            } else {
+                logger.error("TraceDao configured for v2, but {} table does not exist", v2TableName);
+                throw new IllegalStateException(v2TableName + " table does not exist");
+            }
         }
         else if(mode.equalsIgnoreCase("dualWrite")) {
-            return new DualWriteHbaseTraceDao(v1, v2);
+            boolean v1TableExists = this.adminTemplate.tableExists(v1TableName);
+            boolean v2TableExists = this.adminTemplate.tableExists(v2TableName);
+            if (v1TableExists && v2TableExists) {
+                return new DualWriteHbaseTraceDao(v1, v2);
+            } else {
+                logger.error("TraceDao configured for dualWrite, but {} and {} tables do not exist", v1TableName, v2TableName);
+                throw new IllegalStateException(v1TableName + ", " + v2TableName + " tables do not exist");
+            }
+        }
+        else {
+            throw new IllegalStateException("Unknown TraceDao configuration : " + mode);
         }
-
-        return v1;
     }
 
     @Override
diff --git a/collector/src/main/java/com/navercorp/pinpoint/collector/handler/AgentStatHandlerFactory.java b/collector/src/main/java/com/navercorp/pinpoint/collector/handler/AgentStatHandlerFactory.java
index 746d128ec17c..679f7766ef53 100644
--- a/collector/src/main/java/com/navercorp/pinpoint/collector/handler/AgentStatHandlerFactory.java
+++ b/collector/src/main/java/com/navercorp/pinpoint/collector/handler/AgentStatHandlerFactory.java
@@ -16,6 +16,9 @@
 
 package com.navercorp.pinpoint.collector.handler;
 
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import org.apache.hadoop.hbase.TableName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.FactoryBean;
@@ -41,20 +44,45 @@ public class AgentStatHandlerFactory implements FactoryBean<Handler> {
     @Qualifier("agentStatHandlerV2")
     private AgentStatHandlerV2 v2;
 
+    @Autowired
+    private HBaseAdminTemplate adminTemplate;
+
     @Value("#{pinpoint_collector_properties['collector.experimental.stat.format.compatibility.version'] ?: 'v0'}")
     private String mode = "v1";
 
     @Override
     public Handler getObject() throws Exception {
         logger.info("AgentStatHandler Mode {}", mode);
+
+        final TableName v1TableName = HBaseTables.AGENT_STAT;
+        final TableName v2TableName = HBaseTables.AGENT_STAT_VER2;
+
         if (mode.equalsIgnoreCase("v1")) {
-            return v1;
+            if (this.adminTemplate.tableExists(v1TableName)) {
+                return v1;
+            } else {
+                logger.error("AgentStatHandler configured for v1, but {} table does not exist", v1TableName);
+                throw new IllegalStateException(v1TableName + " table does not exist");
+            }
         } else if (mode.equalsIgnoreCase("v2")) {
-            return v2;
+            if (this.adminTemplate.tableExists(v2TableName)) {
+                return v2;
+            } else {
+                logger.error("AgentStatHandler configured for v2, but {} table does not exist", v2TableName);
+                throw new IllegalStateException(v2TableName + " table does not exist");
+            }
         } else if (mode.equalsIgnoreCase("dualWrite")) {
-            return new DualAgentStatHandler(v1, v2);
+            boolean v1TableExists = this.adminTemplate.tableExists(v1TableName);
+            boolean v2TableExists = this.adminTemplate.tableExists(v2TableName);
+            if (v1TableExists && v2TableExists) {
+                return new DualAgentStatHandler(v1, v2);
+            } else {
+                logger.error("AgentStatHandler configured for dualWrite, but {} and {} tables do not exist", v1TableName, v2TableName);
+                throw new IllegalStateException(v1TableName + ", " + v2TableName + " tables do not exist");
+            }
+        } else {
+            throw new IllegalStateException("Unknown AgentStatHandler configuration : " + mode);
         }
-        return v1;
     }
 
     @Override
diff --git a/commons-hbase/src/main/java/com/navercorp/pinpoint/common/hbase/HBaseAdminTemplate.java b/commons-hbase/src/main/java/com/navercorp/pinpoint/common/hbase/HBaseAdminTemplate.java
index 3bef590cdb0f..1294c329cc54 100644
--- a/commons-hbase/src/main/java/com/navercorp/pinpoint/common/hbase/HBaseAdminTemplate.java
+++ b/commons-hbase/src/main/java/com/navercorp/pinpoint/common/hbase/HBaseAdminTemplate.java
@@ -54,20 +54,19 @@ public boolean createTableIfNotExist(HTableDescriptor htd) {
         }
     }
 
-    public boolean tableExists(String tableName) {
+    public boolean tableExists(TableName tableName) {
         try {
-            return admin.tableExists(TableName.valueOf(tableName));
+            return admin.tableExists(tableName);
         } catch (IOException e) {
             throw new HbaseSystemException(e);
         }
     }
 
-    public boolean dropTableIfExist(String tableName) {
-        TableName tn = TableName.valueOf(tableName);
+    public boolean dropTableIfExist(TableName tableName) {
         try {
-            if (admin.tableExists(tn)) {
-                this.admin.disableTable(tn);
-                this.admin.deleteTable(tn);
+            if (admin.tableExists(tableName)) {
+                this.admin.disableTable(tableName);
+                this.admin.deleteTable(tableName);
                 return true;
             }
             return false;
@@ -76,11 +75,10 @@ public boolean dropTableIfExist(String tableName) {
         }
     }
 
-    public void dropTable(String tableName) {
-        TableName tn = TableName.valueOf(tableName);
+    public void dropTable(TableName tableName) {
         try {
-            this.admin.disableTable(tn);
-            this.admin.deleteTable(tn);
+            this.admin.disableTable(tableName);
+            this.admin.deleteTable(tableName);
         } catch (IOException e) {
             throw new HbaseSystemException(e);
         }
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/HbaseTraceDaoFactory.java b/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/HbaseTraceDaoFactory.java
index 097cb10f00de..703881b47eac 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/HbaseTraceDaoFactory.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/HbaseTraceDaoFactory.java
@@ -1,6 +1,9 @@
 package com.navercorp.pinpoint.web.dao.hbase;
 
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
 import com.navercorp.pinpoint.web.dao.TraceDao;
+import org.apache.hadoop.hbase.TableName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.FactoryBean;
@@ -9,6 +12,8 @@
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Repository;
 
+import java.io.IOException;
+
 /**
  * TraceDao Factory for compatibility
  * @author Woonduk Kang(emeroad)
@@ -26,6 +31,8 @@ public class HbaseTraceDaoFactory implements FactoryBean<TraceDao> {
     @Qualifier("hbaseTraceDaoV2")
     private TraceDao v2;
 
+    @Autowired
+    private HBaseAdminTemplate adminTemplate;
 
     @Value("#{pinpointWebProps['web.experimental.span.format.compatibility.version'] ?: 'v1'}")
     private String mode = "v1";
@@ -35,19 +42,48 @@ public TraceDao getObject() throws Exception {
 
         logger.info("TraceDao Compatibility {}", mode);
 
+        final TableName v1TableName = HBaseTables.TRACES;
+        final TableName v2TableName = HBaseTables.TRACE_V2;
+
         if (mode.equalsIgnoreCase("v1")) {
-            return v1;
+            if (this.adminTemplate.tableExists(v1TableName)) {
+                return v1;
+            } else {
+                logger.error("TraceDao configured for v1, but {} table does not exist", v1TableName);
+                throw new IllegalStateException(v1TableName + " table does not exist");
+            }
         }
         else if (mode.equalsIgnoreCase("v2")) {
-            return v2;
+            if (this.adminTemplate.tableExists(v2TableName)) {
+                return v2;
+            } else {
+                logger.error("TraceDao configured for v2, but {} table does not exist", v2TableName);
+                throw new IllegalStateException(v2TableName + " table does not exist");
+            }
         }
         else if (mode.equalsIgnoreCase("compatibilityMode")) {
-            return new HbaseTraceCompatibilityDao(v2, v1);
+            boolean v1TableExists = this.adminTemplate.tableExists(v1TableName);
+            boolean v2TableExists = this.adminTemplate.tableExists(v2TableName);
+            if (v1TableExists && v2TableExists) {
+                return new HbaseTraceCompatibilityDao(v2, v1);
+            } else {
+                logger.error("TraceDao configured for compatibilityMode, but {} and {} tables do not exist", v1TableName, v2TableName);
+                throw new IllegalStateException(v1TableName + ", " + v2TableName + " tables do not exist");
+            }
         }
         else if (mode.equalsIgnoreCase("dualRead")) {
-            return new HbaseDualReadDao(v2, v1);
+            boolean v1TableExists = this.adminTemplate.tableExists(v1TableName);
+            boolean v2TableExists = this.adminTemplate.tableExists(v2TableName);
+            if (v1TableExists && v2TableExists) {
+                return new HbaseDualReadDao(v2, v1);
+            } else {
+                logger.error("TraceDao configured for dualRead, but {} and {} tables do not exist", v1TableName, v2TableName);
+                throw new IllegalStateException(v1TableName + ", " + v2TableName + " tables do not exist");
+            }
+        }
+        else {
+            throw new IllegalStateException("Unknown TraceDao configuration : " + mode);
         }
-        return v1;
     }
 
     @Override
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/stat/AgentStatDaoFactory.java b/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/stat/AgentStatDaoFactory.java
index 6751738f6a59..a50e285cbaee 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/stat/AgentStatDaoFactory.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/stat/AgentStatDaoFactory.java
@@ -16,6 +16,8 @@
 
 package com.navercorp.pinpoint.web.dao.hbase.stat;
 
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
 import com.navercorp.pinpoint.common.server.bo.stat.ActiveTraceBo;
 import com.navercorp.pinpoint.common.server.bo.stat.AgentStatDataPoint;
 import com.navercorp.pinpoint.common.server.bo.stat.CpuLoadBo;
@@ -29,6 +31,7 @@
 import com.navercorp.pinpoint.web.dao.stat.JvmGcDao;
 import com.navercorp.pinpoint.web.dao.stat.JvmGcDetailedDao;
 import com.navercorp.pinpoint.web.dao.stat.TransactionDao;
+import org.apache.hadoop.hbase.TableName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.FactoryBean;
@@ -47,19 +50,44 @@ abstract class AgentStatDaoFactory<T extends AgentStatDataPoint, D extends Agent
     protected D v1;
     protected D v2;
 
+    @Autowired
+    private HBaseAdminTemplate adminTemplate;
+
     @Value("#{pinpointWebProps['web.experimental.stat.format.compatibility.version'] ?: 'v1'}")
     private String mode = "v1";
 
     D getDao() throws Exception {
         logger.info("AgentStatDao Compatibility {}", mode);
+
+        final TableName v1TableName = HBaseTables.AGENT_STAT;
+        final TableName v2TableName = HBaseTables.AGENT_STAT_VER2;
+
         if (mode.equalsIgnoreCase("v1")) {
-            return v1;
+            if (this.adminTemplate.tableExists(v1TableName)) {
+                return v1;
+            } else {
+                logger.error("AgentStatDao configured for v1, but {} table does not exist", v1TableName);
+                throw new IllegalStateException(v1TableName + " table does not exist");
+            }
         } else if (mode.equalsIgnoreCase("v2")) {
-            return v2;
+            if (this.adminTemplate.tableExists(v2TableName)) {
+                return v2;
+            } else {
+                logger.error("AgentStatDao configured for v2, but {} table does not exist", v2TableName);
+                throw new IllegalStateException(v2TableName + " table does not exist");
+            }
         } else if (mode.equalsIgnoreCase("compatibilityMode")) {
-            return getCompatibilityDao(this.v1, this.v2);
+            boolean v1TableExists = this.adminTemplate.tableExists(v1TableName);
+            boolean v2TableExists = this.adminTemplate.tableExists(v2TableName);
+            if (v1TableExists && v2TableExists) {
+                return getCompatibilityDao(this.v1, this.v2);
+            } else {
+                logger.error("AgentStatDao configured for compatibilityMode, but {} and {} tables do not exist", v1TableName, v2TableName);
+                throw new IllegalStateException(v1TableName + ", " + v2TableName + " tables do not exist");
+            }
+        } else {
+            throw new IllegalStateException("Unknown AgentStatDao configuration : " + mode);
         }
-        return v1;
     }
 
     abstract D getCompatibilityDao(D v1, D v2);
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/stat/SampledAgentStatDaoFactory.java b/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/stat/SampledAgentStatDaoFactory.java
index 0ccabe2cf2c8..5d70785f4a02 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/stat/SampledAgentStatDaoFactory.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/stat/SampledAgentStatDaoFactory.java
@@ -16,6 +16,8 @@
 
 package com.navercorp.pinpoint.web.dao.hbase.stat;
 
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
 import com.navercorp.pinpoint.web.dao.SampledAgentStatDao;
 import com.navercorp.pinpoint.web.dao.hbase.stat.compatibility.HbaseSampledAgentStatDualReadDao;
 import com.navercorp.pinpoint.web.dao.stat.SampledActiveTraceDao;
@@ -29,6 +31,7 @@
 import com.navercorp.pinpoint.web.vo.stat.SampledJvmGc;
 import com.navercorp.pinpoint.web.vo.stat.SampledJvmGcDetailed;
 import com.navercorp.pinpoint.web.vo.stat.SampledTransaction;
+import org.apache.hadoop.hbase.TableName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.FactoryBean;
@@ -47,19 +50,44 @@ abstract class SampledAgentStatDaoFactory<S extends SampledAgentStatDataPoint, D
     protected D v1;
     protected D v2;
 
+    @Autowired
+    private HBaseAdminTemplate adminTemplate;
+
     @Value("#{pinpointWebProps['web.experimental.stat.format.compatibility.version'] ?: 'v1'}")
     private String mode = "v1";
 
     D getDao() throws Exception {
         logger.info("SampledAgentStatDao Compatibility {}", mode);
+
+        final TableName v1TableName = HBaseTables.AGENT_STAT;
+        final TableName v2TableName = HBaseTables.AGENT_STAT_VER2;
+
         if (mode.equalsIgnoreCase("v1")) {
-            return v1;
+            if (this.adminTemplate.tableExists(v1TableName)) {
+                return v1;
+            } else {
+                logger.error("SampledAgentStatDao configured for v1, but {} table does not exist", v1TableName);
+                throw new IllegalStateException(v1TableName + " table does not exist");
+            }
         } else if (mode.equalsIgnoreCase("v2")) {
-            return v2;
+            if (this.adminTemplate.tableExists(v2TableName)) {
+                return v2;
+            } else {
+                logger.error("SampledAgentStatDao configured for v2, but {} table does not exist", v2TableName);
+                throw new IllegalStateException(v2TableName + " table does not exist");
+            }
         } else if (mode.equalsIgnoreCase("compatibilityMode")) {
-            return getCompatibilityDao(this.v1, this.v2);
+            boolean v1TableExists = this.adminTemplate.tableExists(v1TableName);
+            boolean v2TableExists = this.adminTemplate.tableExists(v2TableName);
+            if (v1TableExists && v2TableExists) {
+                return getCompatibilityDao(this.v1, this.v2);
+            } else {
+                logger.error("SampledAgentStatDao configured for compatibilityMode, but {} and {} tables do not exist", v1TableName, v2TableName);
+                throw new IllegalStateException(v1TableName + ", " + v2TableName + " tables do not exist");
+            }
+        } else {
+            throw new IllegalStateException("Unknown SampledAgentStatDao configuration : " + mode);
         }
-        return v1;
     }
 
     abstract D getCompatibilityDao(D v1, D v2);
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/service/stat/LegacyAgentStatChartServiceFactory.java b/web/src/main/java/com/navercorp/pinpoint/web/service/stat/LegacyAgentStatChartServiceFactory.java
index 90649f57755f..95eaba293bc0 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/service/stat/LegacyAgentStatChartServiceFactory.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/service/stat/LegacyAgentStatChartServiceFactory.java
@@ -16,6 +16,9 @@
 
 package com.navercorp.pinpoint.web.service.stat;
 
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import org.apache.hadoop.hbase.TableName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.FactoryBean;
@@ -48,17 +51,42 @@ public class LegacyAgentStatChartServiceFactory implements FactoryBean<LegacyAge
     @Qualifier("legacyAgentStatChartCompatibilityService")
     private LegacyAgentStatChartService compatibility;
 
+    @Autowired
+    private HBaseAdminTemplate adminTemplate;
+
     @Override
     public LegacyAgentStatChartService getObject() throws Exception {
         logger.info("LegacyAgentStatService Compatibility {}", mode);
+
+        final TableName v1TableName = HBaseTables.AGENT_STAT;
+        final TableName v2TableName = HBaseTables.AGENT_STAT_VER2;
+
         if (mode.equalsIgnoreCase("v1")) {
-            return v1;
+            if (this.adminTemplate.tableExists(v1TableName)) {
+                return v1;
+            } else {
+                logger.error("LegacyAgentStatService configured for v1, but {} table does not exist", v1TableName);
+                throw new IllegalStateException(v1TableName + " table does not exist");
+            }
         } else if (mode.equalsIgnoreCase("v2")) {
-            return v2;
+            if (this.adminTemplate.tableExists(v2TableName)) {
+                return v2;
+            } else {
+                logger.error("LegacyAgentStatService configured for v2, but {} table does not exist", v2TableName);
+                throw new IllegalStateException(v2TableName + " table does not exist");
+            }
         } else if (mode.equalsIgnoreCase("compatibilityMode")) {
-            return compatibility;
+            boolean v1TableExists = this.adminTemplate.tableExists(v1TableName);
+            boolean v2TableExists = this.adminTemplate.tableExists(v2TableName);
+            if (v1TableExists && v2TableExists) {
+                return compatibility;
+            } else {
+                logger.error("LegacyAgentStatService configured for compatibilityMode, but {} and {} tables do not exist", v1TableName, v2TableName);
+                throw new IllegalStateException(v1TableName + ", " + v2TableName + " tables do not exist");
+            }
+        } else {
+            throw new IllegalStateException("Unknown LegacyAgentStatService configuration : " + mode);
         }
-        return v1;
     }
 
     @Override


Test patch:
diff --git a/collector/src/test/java/com/navercorp/pinpoint/collector/dao/hbase/HbaseTraceDaoFactoryTest.java b/collector/src/test/java/com/navercorp/pinpoint/collector/dao/hbase/HbaseTraceDaoFactoryTest.java
new file mode 100644
index 000000000000..ead314980c13
--- /dev/null
+++ b/collector/src/test/java/com/navercorp/pinpoint/collector/dao/hbase/HbaseTraceDaoFactoryTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016 Naver Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.collector.dao.hbase;
+
+import com.navercorp.pinpoint.collector.dao.TraceDao;
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author HyunGil Jeong
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class HbaseTraceDaoFactoryTest {
+
+    @Mock
+    private TraceDao v1;
+
+    @Mock
+    private TraceDao v2;
+
+    @Mock
+    private HBaseAdminTemplate adminTemplate;
+
+    @InjectMocks
+    private final HbaseTraceDaoFactory traceDaoFactory = new HbaseTraceDaoFactory();
+
+    @Test
+    public void v1Mode_v1TableExists() throws Exception {
+        // Given
+        final TraceDao expectedDao = v1;
+        final String mode = "v1";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(true);
+        // When
+        TraceDao actualDao = traceDaoFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v1Mode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v1";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void v2Mode_v2TableExists() throws Exception {
+        // Given
+        final TraceDao expectedDao = v2;
+        final String mode = "v2";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(true);
+        // When
+        TraceDao actualDao = traceDaoFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v2Mode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v2";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void dualWriteMode_bothTablesExist() throws Exception {
+        // Given
+        final Class<? extends TraceDao> expectedDaoClass = DualWriteHbaseTraceDao.class;
+        final String mode = "dualWrite";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(true);
+        // When
+        TraceDao actualDao = traceDaoFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedDaoClass, actualDao.getClass());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualWriteMode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "dualWrite";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(true);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualWriteMode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "dualWrite";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualWriteMode_bothTablesDoNotExist() throws Exception {
+        // Given
+        final String mode = "dualWrite";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+}
diff --git a/collector/src/test/java/com/navercorp/pinpoint/collector/handler/AgentStatHandlerFactoryTest.java b/collector/src/test/java/com/navercorp/pinpoint/collector/handler/AgentStatHandlerFactoryTest.java
new file mode 100644
index 000000000000..7422d6c79cdc
--- /dev/null
+++ b/collector/src/test/java/com/navercorp/pinpoint/collector/handler/AgentStatHandlerFactoryTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2016 Naver Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.collector.handler;
+
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author HyunGil Jeong
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AgentStatHandlerFactoryTest {
+
+    @Mock
+    private AgentStatHandler v1;
+
+    @Mock
+    private AgentStatHandlerV2 v2;
+
+    @Mock
+    private HBaseAdminTemplate adminTemplate;
+
+    @InjectMocks
+    private final AgentStatHandlerFactory handlerFactory = new AgentStatHandlerFactory();
+
+    @Test
+    public void v1Mode_v1TableExists() throws Exception {
+        // Given
+        final Handler expectedHandler = v1;
+        final String mode = "v1";
+        ReflectionTestUtils.setField(handlerFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        // When
+        Handler actualHandler = handlerFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedHandler, actualHandler);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v1Mode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v1";
+        ReflectionTestUtils.setField(handlerFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        // When
+        handlerFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void v2Mode_v2TableExists() throws Exception {
+        // Given
+        final Handler expectedHandler = v2;
+        final String mode = "v2";
+        ReflectionTestUtils.setField(handlerFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        Handler actualHandler = handlerFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedHandler, actualHandler);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v2Mode_v2TablesDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v2";
+        ReflectionTestUtils.setField(handlerFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        handlerFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void dualWriteMode_bothTablesExist() throws Exception {
+        // Given
+        final Class<? extends Handler> expectedHandlerClass = DualAgentStatHandler.class;
+        final String mode = "dualWrite";
+        ReflectionTestUtils.setField(handlerFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        Handler actualHandler = handlerFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedHandlerClass, actualHandler.getClass());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualWriteMode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "dualWrite";
+        ReflectionTestUtils.setField(handlerFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        handlerFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualWriteMode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "dualWrite";
+        ReflectionTestUtils.setField(handlerFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        handlerFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualWriteMode_bothTablesDoNotExist() throws Exception {
+        // Given
+        final String mode = "dualWrite";
+        ReflectionTestUtils.setField(handlerFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        handlerFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+}
diff --git a/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/HbaseTraceDaoFactoryTest.java b/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/HbaseTraceDaoFactoryTest.java
new file mode 100644
index 000000000000..e142d634c52b
--- /dev/null
+++ b/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/HbaseTraceDaoFactoryTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2016 Naver Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.web.dao.hbase;
+
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import com.navercorp.pinpoint.web.dao.TraceDao;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author HyunGil Jeong
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class HbaseTraceDaoFactoryTest {
+
+    @Mock
+    private TraceDao v1;
+
+    @Mock
+    private TraceDao v2;
+
+    @Mock
+    private HBaseAdminTemplate adminTemplate;
+
+    @InjectMocks
+    private final HbaseTraceDaoFactory traceDaoFactory = new HbaseTraceDaoFactory();
+
+    @Test
+    public void v1Mode_v1TableExists() throws Exception {
+        // Given
+        final TraceDao expectedDao = v1;
+        final String mode = "v1";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(true);
+        // When
+        TraceDao actualDao = traceDaoFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v1Mode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v1";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void v2Mode_v2TableExists() throws Exception {
+        // Given
+        final TraceDao expectedDao = v2;
+        final String mode = "v2";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(true);
+        // When
+        TraceDao actualDao = traceDaoFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v2Mode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v2";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void compatibilityMode_bothTablesExist() throws Exception {
+        // Given
+        final Class<? extends TraceDao> expectedTraceDaoClass = HbaseTraceCompatibilityDao.class;
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(true);
+        // When
+        TraceDao actualDao = traceDaoFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedTraceDaoClass, actualDao.getClass());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(true);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_bothTablesDoNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void dualReadMode_bothTablesExist() throws Exception {
+        // Given
+        final Class<? extends TraceDao> expectedTraceDaoClass = HbaseDualReadDao.class;
+        final String mode = "dualRead";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(true);
+        // When
+        TraceDao actualDao = traceDaoFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedTraceDaoClass, actualDao.getClass());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualReadMode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "dualRead";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(true);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualReadMode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "dualRead";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void dualReadMode_bothTablesDoNotExist() throws Exception {
+        // Given
+        final String mode = "dualRead";
+        ReflectionTestUtils.setField(traceDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.TRACES)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.TRACE_V2)).thenReturn(false);
+        // When
+        traceDaoFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+}
diff --git a/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/stat/AgentStatDaoFactoryTest.java b/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/stat/AgentStatDaoFactoryTest.java
new file mode 100644
index 000000000000..0e02cdac1bdf
--- /dev/null
+++ b/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/stat/AgentStatDaoFactoryTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2016 Naver Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.web.dao.hbase.stat;
+
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import com.navercorp.pinpoint.common.server.bo.stat.AgentStatDataPoint;
+import com.navercorp.pinpoint.web.dao.hbase.stat.compatibility.HbaseAgentStatDualReadDao;
+import com.navercorp.pinpoint.web.dao.stat.AgentStatDao;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author HyunGil Jeong
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AgentStatDaoFactoryTest {
+
+    @Mock
+    private AgentStatDao<AgentStatDataPoint> v1;
+
+    @Mock
+    private AgentStatDao<AgentStatDataPoint> v2;
+
+    @Mock
+    private HBaseAdminTemplate adminTemplate;
+
+    @InjectMocks
+    private TestAgentStatDaoFactory agentStatDaoFactory = new TestAgentStatDaoFactory();
+
+    private final HbaseAgentStatDualReadDao<AgentStatDataPoint> compatibilityDao = new TestAgentStatDualReadDao(v1, v2);
+
+    private static class TestAgentStatDualReadDao extends HbaseAgentStatDualReadDao<AgentStatDataPoint> {
+        public TestAgentStatDualReadDao(AgentStatDao<AgentStatDataPoint> master, AgentStatDao<AgentStatDataPoint> slave) {
+            super(master, slave);
+        }
+    }
+
+    private class TestAgentStatDaoFactory extends AgentStatDaoFactory<AgentStatDataPoint, AgentStatDao<AgentStatDataPoint>> {
+        @Override
+        HbaseAgentStatDualReadDao<AgentStatDataPoint> getCompatibilityDao(
+                AgentStatDao<AgentStatDataPoint> v1,
+                AgentStatDao<AgentStatDataPoint> v2) {
+            return compatibilityDao;
+        }
+    }
+
+    @Test
+    public void v1Mode_v1TableExists() throws Exception {
+        // Given
+        final AgentStatDao<AgentStatDataPoint> expectedDao = v1;
+        final String mode = "v1";
+        ReflectionTestUtils.setField(agentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        // When
+        AgentStatDao<AgentStatDataPoint> actualDao = agentStatDaoFactory.getDao();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v1Mode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v1";
+        ReflectionTestUtils.setField(agentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        // When
+        agentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void v2Mode_v2TableExists() throws Exception {
+        // Given
+        final AgentStatDao<AgentStatDataPoint> expectedDao = v2;
+        final String mode = "v2";
+        ReflectionTestUtils.setField(agentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        AgentStatDao<AgentStatDataPoint> actualDao = agentStatDaoFactory.getDao();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v2Mode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v2";
+        ReflectionTestUtils.setField(agentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        agentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void compatibilityMode_bothTablesExist() throws Exception {
+        // Given
+        final HbaseAgentStatDualReadDao<AgentStatDataPoint> expectedDao = compatibilityDao;
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(agentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        AgentStatDao<AgentStatDataPoint> actualDao = agentStatDaoFactory.getDao();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(agentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        agentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(agentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        agentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_bothTablesDoNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(agentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        agentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+}
diff --git a/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/stat/SampledAgentStatDaoFactoryTest.java b/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/stat/SampledAgentStatDaoFactoryTest.java
new file mode 100644
index 000000000000..15fd7ea53f93
--- /dev/null
+++ b/web/src/test/java/com/navercorp/pinpoint/web/dao/hbase/stat/SampledAgentStatDaoFactoryTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2016 Naver Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.web.dao.hbase.stat;
+
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import com.navercorp.pinpoint.web.dao.SampledAgentStatDao;
+import com.navercorp.pinpoint.web.dao.hbase.stat.compatibility.HbaseSampledAgentStatDualReadDao;
+import com.navercorp.pinpoint.web.vo.stat.SampledAgentStatDataPoint;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author HyunGil Jeong
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class SampledAgentStatDaoFactoryTest {
+
+    @Mock
+    private SampledAgentStatDao<SampledAgentStatDataPoint> v1;
+
+    @Mock
+    private SampledAgentStatDao<SampledAgentStatDataPoint> v2;
+
+    @Mock
+    private HBaseAdminTemplate adminTemplate;
+
+    @InjectMocks
+    private TestSampledAgentStatDaoFactory sampledAgentStatDaoFactory = new TestSampledAgentStatDaoFactory();
+
+    private final HbaseSampledAgentStatDualReadDao<SampledAgentStatDataPoint> compatibilityDao = new TestSampledAgentStatDualReadDao(v1, v2);
+
+    private static class TestSampledAgentStatDualReadDao extends HbaseSampledAgentStatDualReadDao<SampledAgentStatDataPoint> {
+        public TestSampledAgentStatDualReadDao(SampledAgentStatDao<SampledAgentStatDataPoint> master, SampledAgentStatDao<SampledAgentStatDataPoint> slave) {
+            super(master, slave);
+        }
+    }
+
+    private class TestSampledAgentStatDaoFactory extends SampledAgentStatDaoFactory<SampledAgentStatDataPoint, SampledAgentStatDao<SampledAgentStatDataPoint>> {
+        @Override
+        HbaseSampledAgentStatDualReadDao<SampledAgentStatDataPoint> getCompatibilityDao(
+                SampledAgentStatDao<SampledAgentStatDataPoint> v1,
+                SampledAgentStatDao<SampledAgentStatDataPoint> v2) {
+            return compatibilityDao;
+        }
+    }
+
+    @Test
+    public void v1Mode_v1TableExists() throws Exception {
+        // Given
+        final SampledAgentStatDao<SampledAgentStatDataPoint> expectedDao = v1;
+        final String mode = "v1";
+        ReflectionTestUtils.setField(sampledAgentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        // When
+        SampledAgentStatDao<SampledAgentStatDataPoint> actualDao = sampledAgentStatDaoFactory.getDao();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v1Mode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v1";
+        ReflectionTestUtils.setField(sampledAgentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        // When
+        sampledAgentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void v2Mode_v2TableExists() throws Exception {
+        // Given
+        final SampledAgentStatDao<SampledAgentStatDataPoint> expectedDao = v2;
+        final String mode = "v2";
+        ReflectionTestUtils.setField(sampledAgentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        SampledAgentStatDao<SampledAgentStatDataPoint> actualDao = sampledAgentStatDaoFactory.getDao();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v2Mode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v2";
+        ReflectionTestUtils.setField(sampledAgentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        sampledAgentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void compatibiltyMode_bothTablesExist() throws Exception {
+        // Given
+        final HbaseSampledAgentStatDualReadDao<SampledAgentStatDataPoint> expectedDao = compatibilityDao;
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(sampledAgentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        SampledAgentStatDao<SampledAgentStatDataPoint> actualDao = sampledAgentStatDaoFactory.getDao();
+        // Then
+        Assert.assertEquals(expectedDao, actualDao);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(sampledAgentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        sampledAgentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(sampledAgentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        sampledAgentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_bothTablesDoNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(sampledAgentStatDaoFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        sampledAgentStatDaoFactory.getDao();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+}
diff --git a/web/src/test/java/com/navercorp/pinpoint/web/service/stat/LegacyAgentStatChartServiceFactoryTest.java b/web/src/test/java/com/navercorp/pinpoint/web/service/stat/LegacyAgentStatChartServiceFactoryTest.java
new file mode 100644
index 000000000000..a8a35545b0e1
--- /dev/null
+++ b/web/src/test/java/com/navercorp/pinpoint/web/service/stat/LegacyAgentStatChartServiceFactoryTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2016 Naver Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.navercorp.pinpoint.web.service.stat;
+
+import com.navercorp.pinpoint.common.hbase.HBaseAdminTemplate;
+import com.navercorp.pinpoint.common.hbase.HBaseTables;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * @author HyunGil Jeong
+ */
+@Deprecated
+@RunWith(MockitoJUnitRunner.class)
+public class LegacyAgentStatChartServiceFactoryTest {
+
+    @Mock
+    private LegacyAgentStatChartService v1;
+
+    @Mock
+    private LegacyAgentStatChartService v2;
+
+    @Mock
+    private LegacyAgentStatChartService compatibility;
+
+    @Mock
+    private HBaseAdminTemplate adminTemplate;
+
+    @InjectMocks
+    private LegacyAgentStatChartServiceFactory serviceFactory = new LegacyAgentStatChartServiceFactory();
+
+    @Test
+    public void v1Mode_v1TableExists() throws Exception {
+        // Given
+        final LegacyAgentStatChartService expectedService = v1;
+        final String mode = "v1";
+        ReflectionTestUtils.setField(serviceFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        // When
+        LegacyAgentStatChartService actualService = serviceFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedService, actualService);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v1Mode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v1";
+        ReflectionTestUtils.setField(serviceFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        // When
+        serviceFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void v2Mode_v2TableExists() throws Exception {
+        // Given
+        final LegacyAgentStatChartService expectedService = v2;
+        final String mode = "v2";
+        ReflectionTestUtils.setField(serviceFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        LegacyAgentStatChartService actualService = serviceFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedService, actualService);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void v2Mode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "v2";
+        ReflectionTestUtils.setField(serviceFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        serviceFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test
+    public void compatibilityMode_bothTablesExist() throws Exception {
+        // Given
+        final LegacyAgentStatChartService expectedService = compatibility;
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(serviceFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        LegacyAgentStatChartService actualService = serviceFactory.getObject();
+        // Then
+        Assert.assertEquals(expectedService, actualService);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_v1TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(serviceFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(true);
+        // When
+        serviceFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_v2TableDoesNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(serviceFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(true);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        serviceFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void compatibilityMode_bothTablesDoNotExist() throws Exception {
+        // Given
+        final String mode = "compatibilityMode";
+        ReflectionTestUtils.setField(serviceFactory, "mode", mode);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT)).thenReturn(false);
+        when(adminTemplate.tableExists(HBaseTables.AGENT_STAT_VER2)).thenReturn(false);
+        // When
+        serviceFactory.getObject();
+        // Then
+        Assert.fail("Should have thrown IllegalStateException.");
+    }
+}