diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java
index a9962535df9b1fd69d3b1baa70338a5fc2d35722..a77d22cabfe3b9248f0b59831069d35bba2289ad 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java
@@ -29,6 +29,7 @@ public enum EndpointApi {
     BMATOR,
     ES_CORE_API,
     ES_USER_API,
+    ES_SUBSCRIPTION_API,
     MONIT_API,
     UNDEFINED
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java
index 0069509b23bf392292f150e0c02da79d8203433a..c7c2be009eaf5a31e62898f08a9f9fe93051a642 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java
@@ -25,18 +25,12 @@ package org.duniter.core.client.model.local;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.google.common.base.Joiner;
-import javafx.beans.Observable;
-import org.duniter.core.beans.Bean;
-import org.duniter.core.beans.ObservableBean;
 import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.bma.NetworkPeering;
 import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
 import org.duniter.core.util.http.InetAddressUtils;
 
-import java.beans.PropertyChangeListener;
-import java.beans.PropertyChangeListenerProxy;
-import java.beans.PropertyChangeSupport;
 import java.io.Serializable;
 import java.util.StringJoiner;
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java
index 1a8881907ebcd372466353ded7a5d542bb3752c7..417d5b3e2ae0e988a9f473a103e81d2fb00c929e 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java
@@ -26,7 +26,6 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Charsets;
 import com.google.common.base.Joiner;
-import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.HttpClient;
@@ -66,7 +65,6 @@ import java.net.ConnectException;
 import java.net.SocketTimeoutException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
diff --git a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/util/DateUtils.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/DateUtils.java
similarity index 66%
rename from duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/util/DateUtils.java
rename to duniter4j-core-shared/src/main/java/org/duniter/core/util/DateUtils.java
index d6264c7074cfb3d04be8620e7818f2b14155cb56..a463c6dcc7253385ed4cb636cf94fe323bc868b6 100644
--- a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/util/DateUtils.java
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/DateUtils.java
@@ -1,36 +1,13 @@
-package org.duniter.elasticsearch.subscription.util;
-
-/*-
- * #%L
- * Duniter4j :: ElasticSearch Subscription plugin
- * %%
- * Copyright (C) 2014 - 2017 EIS
- * %%
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public
- * License along with this program.  If not, see
- * <http://www.gnu.org/licenses/gpl-3.0.html>.
- * #L%
- */
+package org.duniter.core.util;
 
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
 
 /**
- * Created by blavenie on 10/04/17.
+ * Created by blavenie on 13/09/17.
  */
 public class DateUtils {
-
     public static final long DAY_DURATION_IN_MILLIS = 24 * 60 * 60 * 1000;
 
 
@@ -63,14 +40,28 @@ public class DateUtils {
         return cal.getTime();
     }
 
+
+    public static Date nextHour() {
+        Calendar cal = new GregorianCalendar();
+        cal.setTimeInMillis(System.currentTimeMillis());
+        cal.add(Calendar.HOUR, 1);
+        cal.add(Calendar.HOUR_OF_DAY, 1);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+
     public static long delayBeforeHour(int hour) {
         return nextHour(hour).getTime() - System.currentTimeMillis();
     }
 
+    public static long delayBeforeNextHour() {
+        return nextHour().getTime() - System.currentTimeMillis();
+    }
+
     public static long delayBeforeDayAndHour(int dayOfTheWeek, int hour) {
         return nextDayAndHour(dayOfTheWeek, hour).getTime() - System.currentTimeMillis();
     }
 
-
 }
-
diff --git a/duniter4j-es-assembly/src/main/assembly/config/elasticsearch.yml b/duniter4j-es-assembly/src/main/assembly/config/elasticsearch.yml
index c132c2d18136bd7279fb1810c0c59fc06ac3b7a6..e9c1057ea192be356501dff2c8d3a594f05d57ec 100644
--- a/duniter4j-es-assembly/src/main/assembly/config/elasticsearch.yml
+++ b/duniter4j-es-assembly/src/main/assembly/config/elasticsearch.yml
@@ -154,6 +154,11 @@ duniter.data.sync.enable: true
 duniter.data.sync.host: g1.data.duniter.fr
 duniter.data.sync.port: 443
 
+#
+# Should maintain stats on data ?
+#
+duniter.data.stats.enable: true
+
 # ---------------------------------- Duniter4j Mail module -----------------------
 #
 # Enable mail module ?
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java
index deaec741ac6a44be42ea9472afa44ee77e194a5c..8014ba23803de558db33efa2e634af3f38012b93 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java
@@ -35,19 +35,20 @@ import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.inject.Module;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.logging.Loggers;
 import org.elasticsearch.common.settings.Settings;
 
 import java.util.Collection;
 
 public class Plugin extends org.elasticsearch.plugins.Plugin {
 
-    private ESLogger log = ESLoggerFactory.getLogger(Plugin.class.getName());
+    private ESLogger logger;
 
     private boolean enable;
 
     @Inject public Plugin(Settings settings) {
         this.enable = settings.getAsBoolean("duniter.enabled", true);
-
+        this.logger = Loggers.getLogger(Plugin.class.getName(), settings, new String[0]);
     }
 
     @Override
@@ -57,7 +58,7 @@ public class Plugin extends org.elasticsearch.plugins.Plugin {
 
     @Override
     public String description() {
-        return "Duniter ElasticSearch Plugin";
+        return "Duniter Core Plugin";
     }
 
     @Inject
@@ -71,7 +72,7 @@ public class Plugin extends org.elasticsearch.plugins.Plugin {
     public Collection<Module> nodeModules() {
         Collection<Module> modules = Lists.newArrayList();
         if (!enable) {
-            log.warn(description() + " has been disabled.");
+            logger.warn(description() + " has been disabled.");
             return modules;
         }
         modules.add(new SecurityModule());
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java
index 554a7928d37e50158380993aa45158c1304494d2..578f3045ba112ace57ec6ceec6ac50180f728040 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java
@@ -24,13 +24,11 @@ package org.duniter.elasticsearch;
 
 import org.duniter.core.client.model.elasticsearch.Currency;
 import org.duniter.core.client.model.local.Peer;
-import org.duniter.elasticsearch.dao.BlockDao;
-import org.duniter.elasticsearch.dao.BlockStatDao;
-import org.duniter.elasticsearch.dao.PeerDao;
-import org.duniter.elasticsearch.dao.MovementDao;
+import org.duniter.elasticsearch.dao.*;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.service.BlockchainService;
 import org.duniter.elasticsearch.service.CurrencyService;
+import org.duniter.elasticsearch.service.DocStatService;
 import org.duniter.elasticsearch.service.PeerService;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
@@ -50,11 +48,12 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
     private final PluginSettings pluginSettings;
     private final ThreadPool threadPool;
     private final Injector injector;
-    private final static ESLogger logger = Loggers.getLogger("duniter.core");
+    private final ESLogger logger;
 
     @Inject
     public PluginInit(Settings settings, PluginSettings pluginSettings, ThreadPool threadPool, final Injector injector) {
         super(settings);
+        this.logger = Loggers.getLogger("duniter.core", settings, new String[0]);
         this.pluginSettings = pluginSettings;
         this.threadPool = threadPool;
         this.injector = injector;
@@ -62,13 +61,13 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
 
     @Override
     protected void doStart() {
-        threadPool.scheduleOnClusterHealthStatus(() -> {
+        threadPool.scheduleOnClusterReady(() -> {
             createIndices();
 
             // Waiting cluster back to GREEN or YELLOW state, before doAfterStart
-            threadPool.scheduleOnClusterHealthStatus(this::doAfterStart, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN);
+            threadPool.scheduleOnClusterReady(this::doAfterStart);
 
-        }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN);
+        });
     }
 
     @Override
@@ -93,6 +92,12 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
                     .deleteIndex()
                     .createIndexIfNotExists();
 
+            if (pluginSettings.enableDocStats()) {
+                injector.getInstance(DocStatService.class)
+                        .deleteIndex()
+                        .createIndexIfNotExists();
+            }
+
             if (logger.isInfoEnabled()) {
                 logger.info("Reloading indices [OK]");
             }
@@ -121,6 +126,11 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
             injector.getInstance(CurrencyService.class)
                     .createIndexIfNotExists();
 
+            if (pluginSettings.enableDocStats()) {
+                injector.getInstance(DocStatService.class)
+                        .createIndexIfNotExists();
+            }
+
             if (logger.isInfoEnabled()) {
                 logger.info("Checking indices [OK]");
             }
@@ -132,7 +142,6 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
         // Synchronize blockchain
         if (pluginSettings.enableBlockchain()) {
 
-
             Peer peer = pluginSettings.checkAndGetPeer();
 
             Currency currency;
@@ -147,7 +156,6 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
 
             final String currencyName = currency.getCurrencyName();
 
-
             // Add access security rules, for the currency indices
             injector.getInstance(RestSecurityController.class)
 
@@ -183,6 +191,12 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
                             currencyName,
                             MovementDao.TYPE);
 
+            /* TODO à décommenter quand les pending seront sauvegardés
+            injector.getInstance(DocStatService.class)
+            .registerIndex(currencyName,
+                            PendingRegistrationDao.TYPE);
+             */
+
             // If partial reload (from a block)
             if (pluginSettings.reloadBlockchainIndices() && pluginSettings.reloadBlockchainIndicesFrom() > 0) {
                 if (logger.isWarnEnabled()) {
@@ -200,7 +214,7 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
 
 
             // Wait end of currency index creation, then index blocks
-            threadPool.scheduleOnClusterHealthStatus(() -> {
+            threadPool.scheduleOnClusterReady(() -> {
 
                 try {
                     // Index blocks (and listen if new block appear)
@@ -215,14 +229,39 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
                     if (logger.isInfoEnabled()) {
                         logger.info(String.format("[%s] Indexing blockchain [OK]", currencyName));
                     }
+
                 } catch(Throwable e){
                     logger.error(String.format("[%s] Indexing blockchain error: %s", currencyName, e.getMessage()), e);
                     throw e;
                 }
 
-            }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN);
+            });
 
+        }
 
+        // If doc stats enable
+        if (pluginSettings.enableDocStats()) {
+
+            // Add access to docstat index
+            injector.getInstance(RestSecurityController.class)
+                    .allowIndexType(RestRequest.Method.GET,
+                            DocStatDao.INDEX,
+                            DocStatDao.TYPE)
+                    .allowPostSearchIndexType(
+                            DocStatDao.INDEX,
+                            DocStatDao.TYPE);
+
+            // Add index [currency/record] to stats
+            final DocStatService docStatService = injector
+                    .getInstance(DocStatService.class)
+                    .registerIndex(CurrencyService.INDEX, CurrencyService.RECORD_TYPE);
+
+            // Wait end of currency index creation, then index blocks
+            threadPool.scheduleOnClusterReady(docStatService::start);
         }
+
+        // Allow scroll search
+        injector.getInstance(RestSecurityController.class)
+                .allow(RestRequest.Method.POST, "^_search/scroll$");
     }
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
index 8590207ef8e960d8a69744894fd07e240a584467..2897d601d970c7d93cda9ff16e88685c72ff0100 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
@@ -150,6 +150,10 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
 
     }
 
+    public Settings getSettings() {
+        return settings;
+    }
+
     public String getClusterName() {
         return settings.get("cluster.name", "?");
     }
@@ -286,8 +290,8 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
         return settings.get("network.host", "locahost");
     }
 
-    public int getWebSocketPort()  {
-        return settings.getAsInt("duniter.ws.port", 81);
+    public String getWebSocketPort()  {
+        return settings.get("duniter.ws.port", "9400");
     }
 
     public boolean getWebSocketEnable()  {
@@ -298,6 +302,10 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
         return settings.getAsArray("duniter.ws.changes.listenSource", new String[]{"*"});
     }
 
+    public boolean enableDocStats() {
+        return settings.getAsBoolean("duniter.data.stats.enable", false);
+    }
+
     /* protected methods */
 
     protected void initI18n() throws IOException {
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClientImpl.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClientImpl.java
index 0432e2b98100e121664c1ae56913a05b3aa7b428..d94dbd84b9d2936c43b4af43667d12ee941a807d 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClientImpl.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClientImpl.java
@@ -236,6 +236,8 @@ public class Duniter4jClientImpl implements Duniter4jClient {
         try {
             SearchResponse response = searchRequest.execute().actionGet();
 
+            if (response.getHits().getTotalHits() == 0) return null;
+
             Map<String, Object> result = new HashMap<>();
             // Read query result
             SearchHit[] searchHits = response.getHits().getHits();
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractDao.java
index ee9332075a27f93a41c04a2b3117f5f0935d7447..055c72f480e9c302cb76412b8b5dc77a98b17bed 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractDao.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractDao.java
@@ -39,7 +39,8 @@ import org.elasticsearch.common.logging.Loggers;
 public abstract class AbstractDao implements Bean {
 
 
-    protected final ESLogger logger;
+    protected final String loggerName;
+    protected ESLogger logger;
 
     protected Duniter4jClient client;
     protected CryptoService cryptoService;
@@ -47,7 +48,7 @@ public abstract class AbstractDao implements Bean {
 
     public AbstractDao(String loggerName) {
         super();
-        this.logger = Loggers.getLogger(loggerName);
+        this.loggerName = loggerName;
     }
 
     @Inject
@@ -63,6 +64,7 @@ public abstract class AbstractDao implements Bean {
     @Inject
     public void setPluginSettings(PluginSettings pluginSettings) {
         this.pluginSettings = pluginSettings;
+        this.logger = Loggers.getLogger(loggerName, pluginSettings.getSettings(), new String[0]);
     }
 
     /* -- protected methods  -- */
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DaoModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DaoModule.java
index 5aad05486536584503c78b7c5194b7844cf73017..984cd0f98a9b5c4510484751e9b08520386b21f7 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DaoModule.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DaoModule.java
@@ -28,6 +28,7 @@ import org.duniter.core.client.dao.PeerDao;
 import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.client.Duniter4jClientImpl;
 import org.duniter.elasticsearch.dao.impl.BlockStatDaoImpl;
+import org.duniter.elasticsearch.dao.impl.DocStatDaoImpl;
 import org.duniter.elasticsearch.dao.impl.MovementDaoImpl;
 import org.duniter.elasticsearch.service.ServiceLocator;
 import org.elasticsearch.common.inject.AbstractModule;
@@ -37,13 +38,18 @@ public class DaoModule extends AbstractModule implements Module {
 
     @Override protected void configure() {
 
+        // Common instance
         bind(Duniter4jClient.class).to(Duniter4jClientImpl.class).asEagerSingleton();
+        bind(DocStatDao.class).to(DocStatDaoImpl.class).asEagerSingleton();
+
+        //
         bind(BlockStatDao.class).to(BlockStatDaoImpl.class).asEagerSingleton();
         bind(MovementDao.class).to(MovementDaoImpl.class).asEagerSingleton();
 
         bindWithLocator(BlockDao.class);
         bindWithLocator(PeerDao.class);
         bindWithLocator(CurrencyDao.class);
+
     }
 
     /* protected methods */
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DocStatDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DocStatDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec3083eee28157b77172c086acd99b509a3cf736
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DocStatDao.java
@@ -0,0 +1,19 @@
+package org.duniter.elasticsearch.dao;
+
+import org.duniter.elasticsearch.model.DocStat;
+import org.elasticsearch.action.index.IndexRequestBuilder;
+
+import javax.annotation.Nullable;
+
+/**
+ * Created by blavenie on 13/09/17.
+ */
+public interface DocStatDao extends IndexTypeDao<DocStatDao>{
+    String INDEX = "docstat";
+    String TYPE = "record";
+
+    long countDoc(String index, @Nullable String type);
+
+    IndexRequestBuilder prepareIndex(DocStat stat);
+
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/DocStatDaoImpl.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/DocStatDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..05caf5f836d11979546a67e9c87ca47b5d6ff9a5
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/DocStatDaoImpl.java
@@ -0,0 +1,163 @@
+package org.duniter.elasticsearch.dao.impl;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.dao.*;
+import org.duniter.elasticsearch.model.DocStat;
+import org.elasticsearch.action.index.IndexRequestBuilder;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public class DocStatDaoImpl extends AbstractIndexTypeDao<DocStatDao> implements DocStatDao {
+
+    private PluginSettings pluginSettings;
+
+    @Inject
+    public DocStatDaoImpl(PluginSettings pluginSettings) {
+        super(DocStatDao.INDEX, DocStatDao.TYPE);
+        this.pluginSettings = pluginSettings;
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+
+    @Override
+    public long countDoc(String index, String type) {
+        Preconditions.checkArgument(StringUtils.isNotBlank(index));
+
+        SearchRequestBuilder searchRequest = client.prepareSearch(index)
+                .setFetchSource(false)
+                .setSize(0);
+
+        // Set type if present
+        if (StringUtils.isNotBlank(type)) {
+            searchRequest.setTypes(type);
+        }
+
+        SearchResponse response = searchRequest.execute().actionGet();
+        return response.getHits().getTotalHits();
+    }
+
+    @Override
+    public IndexRequestBuilder prepareIndex(DocStat stat) {
+        Preconditions.checkNotNull(stat);
+        Preconditions.checkArgument(StringUtils.isNotBlank(stat.getIndex()));
+
+        // Make sure time has been set
+        if (stat.getTime() == 0) {
+            stat.setTime(System.currentTimeMillis()/1000);
+        }
+
+        try {
+            return client.prepareIndex(INDEX, TYPE)
+                    .setRefresh(false)
+                    .setSource(getObjectMapper().writeValueAsBytes(stat));
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(e);
+        }
+    }
+
+    @Override
+    protected void createIndex() throws JsonProcessingException {
+        logger.info(String.format("Creating index [%s]", INDEX));
+
+        client.admin().indices().prepareCreate(INDEX)
+            .setSettings(Settings.settingsBuilder()
+                .put("number_of_shards", 3)
+                .put("number_of_replicas", 1)
+                .build())
+            .addMapping(TYPE, createTypeMapping())
+            .execute().actionGet();
+    }
+
+    @Override
+    public XContentBuilder createTypeMapping() {
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder()
+                    .startObject()
+                    .startObject(TYPE)
+                    .startObject("properties")
+
+                    // index
+                    .startObject(DocStat.PROPERTY_INDEX)
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // indexType
+                    .startObject(DocStat.PROPERTY_INDEX_TYPE)
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // type
+                    .startObject(DocStat.PROPERTY_COUNT)
+                    .field("type", "long")
+                    .endObject()
+
+                    // time
+                    .startObject(DocStat.PROPERTY_TIME)
+                    .field("type", "integer")
+                    .endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException("Error while getting mapping for doc stat index: " + ioe.getMessage(), ioe);
+        }
+    }
+
+    /* -- protected method -- */
+
+    protected long countData(String index, String type) {
+
+        SearchRequestBuilder searchRequest = client.prepareSearch(index)
+                .setTypes(type)
+                .setFetchSource(false)
+                .setSize(0);
+
+        SearchResponse response = searchRequest.execute().actionGet();
+        return response.getHits().getTotalHits();
+    }
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/DocStat.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/DocStat.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b688b52b0e4159bd716a4d5d25475e13ec82aa7
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/DocStat.java
@@ -0,0 +1,79 @@
+package org.duniter.elasticsearch.model;
+
+/*
+ * #%L
+ * Duniter4j :: Core Client API
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import java.io.Serializable;
+
+/**
+ * Created by blavenie on 29/11/16.
+ */
+public class DocStat implements Serializable {
+
+    public static final String PROPERTY_INDEX = "index";
+    public static final String PROPERTY_INDEX_TYPE = "indexType";
+    public static final String PROPERTY_COUNT = "docCount";
+    public static final String PROPERTY_TIME = "type";
+
+    // Property copied from Block
+    private String index;
+    private String indexType;
+    private long count;
+    private long time = 0L;
+
+
+    public DocStat() {
+        super();
+    }
+
+    public String getIndex() {
+        return index;
+    }
+
+    public void setIndex(String index) {
+        this.index = index;
+    }
+
+    public String getIndexType() {
+        return indexType;
+    }
+
+    public void setIndexType(String indexType) {
+        this.indexType = indexType;
+    }
+
+    public long getCount() {
+        return count;
+    }
+
+    public void setCount(long count) {
+        this.count = count;
+    }
+
+    public long getTime() {
+        return time;
+    }
+
+    public void setTime(long time) {
+        this.time = time;
+    }
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchResponse.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..d959155a5d5b03b91bee6ce3bd97a12948227a9b
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchResponse.java
@@ -0,0 +1,83 @@
+package org.duniter.elasticsearch.model;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+/**
+ * Created by eis on 05/02/15.
+ */
+public class SearchResponse implements Serializable {
+
+    protected JsonNode node;
+
+    public SearchResponse(JsonNode response) {
+        this.node = response;
+    }
+
+    public Hits getHits() {
+        return new Hits(node.get("hits"));
+    }
+
+    public class Hits implements Iterator<Hit>{
+
+        protected JsonNode node;
+        private Iterator<JsonNode> hits;
+        Hits(JsonNode node) {
+            this.node = node;
+            this.hits = node == null ? null : node.get("hits").iterator();
+        }
+
+        public int getTotal() {
+            return node == null ? 0 : node.get("total").asInt(0);
+        }
+
+        public boolean hasNext() {
+            return hits != null && hits.hasNext();
+        }
+        public Hit next() {
+            return hits == null ? null : new Hit(hits.next());
+        }
+    }
+
+    public class Hit {
+
+        private JsonNode node;
+        Hit(JsonNode node) {
+            this.node = node;
+        }
+
+        public String getId() {
+            return node.get("_id").asText();
+        }
+
+        public JsonNode getSource() {
+            return node.get("_source");
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchResult.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchScrollResponse.java
similarity index 62%
rename from duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchResult.java
rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchScrollResponse.java
index 541bc634fe9ad0dbe33e8fd029a1528d979ae6af..0284bd22e473be8cf5bd1a1140bc896498fa7f4b 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchResult.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchScrollResponse.java
@@ -23,38 +23,18 @@ package org.duniter.elasticsearch.model;
  */
 
 
-import java.io.Serializable;
+import com.fasterxml.jackson.databind.JsonNode;
 
 /**
  * Created by eis on 05/02/15.
  */
-public class SearchResult implements Serializable {
+public class SearchScrollResponse extends SearchResponse {
 
-    private String id;
-    private String value;
-    private String type;
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
-    public String getType() {
-        return type;
-    }
-
-    public void setType(String type) {
-        this.type = type;
-    }
-
-    public String getValue() {
-        return value;
+    public SearchScrollResponse(JsonNode response) {
+        super(response);
     }
 
-    public void setValue(String value) {
-        this.value = value;
+    public String getScrollId() {
+        return node.get("_scroll_id").asText();
     }
 }
\ No newline at end of file
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityController.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityController.java
index 6da9bb84f7e1c5865047b168fba92cb0033ad9e0..6777a2268e3a5e8a09035deac4897c05ed557d03 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityController.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityController.java
@@ -51,18 +51,24 @@ public class RestSecurityController extends AbstractLifecycleComponent<RestSecur
         super(settings);
         this.enable = pluginSettings.enableSecurity();
         this.allowRulesByMethod = new HashMap<>();
+        if (!enable) {
+            log.warn("/!\\ Security has been disable using option [duniter.security.enable]. This is NOT recommended in production !");
+        }
     }
 
     public RestSecurityController allowIndexType(RestRequest.Method method, String index, String type) {
-        return allow(method, String.format("/%s/%s(/.*)?", index, type));
+        allow(method, String.format("/%s/%s(/.*)?", index, type));
+        return this;
     }
 
     public RestSecurityController allowPostSearchIndexType(String index, String type) {
-        return allow(RestRequest.Method.POST, String.format("/%s/%s/_search", index, type));
+        allow(RestRequest.Method.POST, String.format("/%s/%s/_search", index, type));
+        return this;
     }
 
     public RestSecurityController allowImageAttachment(String index, String type, String field) {
-        return allow(RestRequest.Method.GET, String.format("/%s/%s/[^/]+/_image/%s.*", index, type, field));
+        allow(RestRequest.Method.GET, String.format("/%s/%s/[^/]+/_image/%s.*", index, type, field));
+        return this;
     }
 
     public RestSecurityController allow(RestRequest.Method method, String regexPath) {
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
index d2b15d9527174d0940a7fc87095215998a831547..1c6b179a06ebb4d0f70f5d1ad8296e37fdc8bfd9 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
@@ -72,7 +72,7 @@ public abstract class AbstractService implements Bean {
 
     public AbstractService(String loggerName, Duniter4jClient client, PluginSettings pluginSettings, CryptoService cryptoService) {
         super();
-        this.logger = Loggers.getLogger(loggerName);
+        this.logger = Loggers.getLogger(loggerName, pluginSettings.getSettings(), new String[0]);
         this.client = client;
         this.pluginSettings = pluginSettings;
         this.cryptoService = cryptoService;
@@ -85,7 +85,7 @@ public abstract class AbstractService implements Bean {
     protected void setIsReady(boolean ready) {
         this.ready = ready;
     }
-    protected boolean isReady() {
+    public boolean isReady() {
         return this.ready;
     }
 
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java
index 6e144b822d45c80bce459d144814303b36899a6c..a8048d478332fafaf28f8bb10a88ca57aaae204b 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java
@@ -25,7 +25,9 @@ package org.duniter.elasticsearch.service;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.entity.StringEntity;
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.elasticsearch.Record;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.client.service.HttpService;
@@ -39,28 +41,29 @@ import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.exception.DuniterElasticsearchException;
 import org.duniter.elasticsearch.exception.InvalidFormatException;
 import org.duniter.elasticsearch.exception.InvalidSignatureException;
+import org.duniter.elasticsearch.model.SearchScrollResponse;
 import org.duniter.elasticsearch.model.SynchroResult;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.action.bulk.BulkItemResponse;
 import org.elasticsearch.action.bulk.BulkRequestBuilder;
 import org.elasticsearch.action.bulk.BulkResponse;
-import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.json.JsonXContent;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
 
 import java.io.IOException;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
 
 /**
  * Created by blavenie on 27/10/16.
  */
 public abstract class AbstractSynchroService extends AbstractService {
 
+    private static final String SCROLL_PARAM_VALUE = "1m";
+
     protected HttpService httpService;
 
     @Inject
@@ -71,42 +74,70 @@ public abstract class AbstractSynchroService extends AbstractService {
         super("duniter.network.p2p", client, settings,cryptoService);
         threadPool.scheduleOnStarted(() -> {
             httpService = serviceLocator.getHttpService();
+            setIsReady(true);
         });
     }
 
     /* -- protected methods -- */
 
-    protected Peer getPeerFromAPI(String filterApiName) {
+    protected Peer getPeerFromAPI(EndpointApi api) {
         // TODO : get peers from currency - use peering BMA API, and select peers with ESA (ES API)
-        Peer peer = Peer.newBuilder().setHost(pluginSettings.getDataSyncHost()).setPort(pluginSettings.getDataSyncPort()).build();
+        Peer peer = Peer.newBuilder()
+                .setHost(pluginSettings.getDataSyncHost())
+                .setPort(pluginSettings.getDataSyncPort())
+                .setApi(api.name())
+                .build();
+
         return peer;
     }
 
-    protected long importChangesRemap(SynchroResult result,
-                                      Peer peer,
-                                      String fromIndex, String fromType,
-                                      String toIndex, String toType,
-                                      long sinceTime) {
-        return doImportChanges(result, peer, fromIndex, fromType, toIndex, toType, Record.PROPERTY_ISSUER, Record.PROPERTY_TIME, sinceTime);
+    protected void safeSynchronizeIndex(Peer peer, String index, String type, long fromTime, SynchroResult result) {
+        safeSynchronizeIndexRemap(peer, index, type, index, type, Record.PROPERTY_ISSUER, Record.PROPERTY_TIME, fromTime, result);
     }
 
-    protected long importChanges(SynchroResult result, Peer peer, String index, String type, long sinceTime) {
-        return doImportChanges(result, peer, index, type, index, type, Record.PROPERTY_ISSUER, Record.PROPERTY_TIME, sinceTime);
+    protected void safeSynchronizeIndexRemap(Peer peer,
+                                             String fromIndex, String fromType,
+                                             String toIndex, String toType,
+                                             long fromTime,
+                                             SynchroResult result) {
+        safeSynchronizeIndexRemap(peer, fromIndex, fromType, toIndex, toType, Record.PROPERTY_ISSUER, Record.PROPERTY_TIME, fromTime, result);
     }
 
-    protected long importChanges(SynchroResult result, Peer peer, String index, String type,
-                                 String issuerFieldName, String versionFieldName, long sinceTime) {
-        return doImportChanges(result, peer, index, type, index, type, issuerFieldName, versionFieldName, sinceTime);
+    protected void safeSynchronizeIndexRemap(Peer peer,
+                                             String fromIndex, String fromType,
+                                             String toIndex, String toType,
+                                             String issuerFieldName, String versionFieldName,
+                                             long fromTime,
+                                             SynchroResult result) {
+        Preconditions.checkArgument(fromTime >= 0);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("[%s] [%s/%s] Synchronizing where [%s > %s]...", peer, toIndex, toType, versionFieldName, fromTime));
+        }
+
+        QueryBuilder fromQuery = createDefaultQuery(fromTime);
+        safeSynchronizeIndexRemap(peer, fromIndex, fromType, toIndex, toType, issuerFieldName, versionFieldName, fromQuery, result);
     }
 
-    /* -- private methods -- */
+    protected void safeSynchronizeIndex(Peer peer,
+                                        String index, String type,
+                                        QueryBuilder query,
+                                        SynchroResult result) {
+        Preconditions.checkNotNull(query);
 
-    private long doImportChanges(SynchroResult result,
-                                 Peer peer,
-                                 String fromIndex, String fromType,
-                                 String toIndex, String toType,
-                                 String issuerFieldName, String versionFieldName, long sinceTime) {
-        Preconditions.checkNotNull(result);
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("[%s] [%s/%s] Synchronizing using query [%s]...", peer, index, type, query.toString()));
+        }
+
+        safeSynchronizeIndexRemap(peer, index, type, index, type, Record.PROPERTY_ISSUER, Record.PROPERTY_TIME, query, result);
+    }
+
+    protected void safeSynchronizeIndexRemap(Peer peer,
+                                             String fromIndex, String fromType,
+                                             String toIndex, String toType,
+                                             String issuerFieldName, String versionFieldName,
+                                             QueryBuilder query,
+                                             SynchroResult result) {
         Preconditions.checkNotNull(peer);
         Preconditions.checkNotNull(fromIndex);
         Preconditions.checkNotNull(fromType);
@@ -114,89 +145,136 @@ public abstract class AbstractSynchroService extends AbstractService {
         Preconditions.checkNotNull(toType);
         Preconditions.checkNotNull(issuerFieldName);
         Preconditions.checkNotNull(versionFieldName);
+        Preconditions.checkNotNull(query);
+        Preconditions.checkNotNull(result);
+
+        try {
+            synchronizeIndexRemap(peer, fromIndex, fromType, toIndex, toType, issuerFieldName, versionFieldName, query, result);
+        }
+        catch(Exception e1) {
+            // Log the first error
+            if (logger.isDebugEnabled()) {
+                logger.error(e1.getMessage(), e1);
+            }
+            else {
+                logger.error(e1.getMessage());
+            }
+        }
+    }
+
+    private void synchronizeIndexRemap(Peer peer,
+                                       String fromIndex, String fromType,
+                                       String toIndex, String toType,
+                                       String issuerFieldName,
+                                       String versionFieldName,
+                                       QueryBuilder query,
+                                       SynchroResult result) {
+
+        if (!client.existsIndex(toIndex)) {
+           throw new TechnicalException(String.format("Unable to import changes. Index [%s] not exists", toIndex));
+        }
+
+        ObjectMapper objectMapper = getObjectMapper();
 
-        long offset = 0;
-        int size = pluginSettings.getIndexBulkSize() / 10;
+        long counter = 0;
         boolean stop = false;
+        String scrollId = null;
+        int total = 0;
         while(!stop) {
-            long currentRowCount = doImportChangesAtOffset(result, peer,
-                    fromIndex, fromType, toIndex, toType,
-                    issuerFieldName, versionFieldName, sinceTime,
-                    offset, size);
-            offset += currentRowCount;
-            stop = currentRowCount < size;
-        }
+            SearchScrollResponse response;
+            if (scrollId == null) {
+                HttpUriRequest request = createScrollRequest(peer, fromIndex, fromType, query);
+                response = executeAndParseRequest(peer, fromIndex, fromType, request);
+                if (response != null) {
+                    scrollId = response.getScrollId();
+                    total = response.getHits().getTotal();
+                    if (total > 0 && logger.isDebugEnabled()) {
+                        logger.debug(String.format("[%s] [%s/%s] %s docs to check...", peer, toIndex, toType, total));
+                    }
+                }
+            }
+            else {
+                HttpUriRequest request = createNextScrollRequest(peer, scrollId);
+                response =  executeAndParseRequest(peer, fromIndex, fromType, request);
+            }
 
-        return offset; // = total rows
+            if (response == null) {
+                stop = true;
+            }
+            else {
+                counter += fetchAndIndex(peer, toIndex, toType, issuerFieldName, versionFieldName, response, objectMapper, result);
+                stop = counter >= total;
+            }
+        }
     }
 
-    private long doImportChangesAtOffset(SynchroResult result, Peer peer,
-                                 String fromIndex, String fromType,
-                                 String toIndex, String toType,
-                                 String issuerFieldName, String versionFieldName,
-                                 long sinceTime,
-                                 long offset,
-                                 int size) {
+    private QueryBuilder createDefaultQuery(long fromTime) {
+
+        return QueryBuilders.boolQuery()
+                .should(QueryBuilders.rangeQuery("time").gte(fromTime));
+    }
 
+    private HttpPost createScrollRequest(Peer peer,
+                                         String fromIndex, String fromType,
+                                         QueryBuilder query) {
+        HttpPost httpPost = new HttpPost(httpService.getPath(peer, fromIndex, fromType, "_search?scroll=" + SCROLL_PARAM_VALUE));
+        httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
 
-        // Create the search query
-        BytesStreamOutput bos;
         try {
-            bos = new BytesStreamOutput();
+            // Query to String
+            BytesStreamOutput bos = new BytesStreamOutput();
             XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, bos);
-            builder.startObject()
-                    .startObject("query")
-                    // bool.should
-                    .startObject("bool")
-                    .startObject("should")
-                    // time > sinceDate
-                    .startObject("range")
-                    .startObject("time")
-                    .field("gte", sinceTime)
-                    .endObject()
-                    .endObject()
-                    .endObject()
-                    .endObject()
-                    // end: query
-                    .endObject()
-                    .field("from", offset)
-                    .field("size", size)
-                    .endObject();
+            query.toXContent(builder, null);
             builder.flush();
 
-        } catch(IOException e) {
-            throw new TechnicalException("Error while preparing default index analyzer: " + e.getMessage(), e);
-        }
+            // Sort on "_doc" - see https://www.elastic.co/guide/en/elasticsearch/reference/2.4/search-request-scroll.html
+            String content = String.format("{\"query\":%s,\"size\":%s, \"sort\": [\"_doc\"]}",
+                    bos.bytes().toUtf8(),
+                    pluginSettings.getIndexBulkSize());
+            httpPost.setEntity(new StringEntity(content, "UTF-8"));
 
-        // Execute query
-        JsonNode node;
-        try {
-            HttpPost httpPost = new HttpPost(httpService.getPath(peer, fromIndex, fromType, "_search"));
-            httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
-            httpPost.setEntity(new ByteArrayEntity(bos.bytes().array()));
             if (logger.isTraceEnabled()) {
-                logger.trace(String.format("[%s] [%s/%s] Sending POST request: %s", peer, fromIndex, fromType, new String(bos.bytes().array())));
+                logger.trace(String.format("[%s] [%s/%s] Sending POST scroll request: %s", peer, fromIndex, fromType, content));
             }
-            // Parse response
-            node = httpService.executeRequest(httpPost, JsonNode.class, String.class);
-        }
-        catch(HttpUnauthorizeException e) {
-            logger.error(String.format("[%s] [%s/%s] Unable to access (%s). Skipping data import.", peer, fromIndex, fromType, e.getMessage()));
-            return 0;
-        }
-        catch(TechnicalException e) {
-            throw new TechnicalException("Unable to parse search response", e);
-        }
-        catch(Exception e) {
-            throw new TechnicalException("Unable to parse search response", e);
+
+        } catch (IOException e) {
+            throw new TechnicalException("Error while preparing search query: " + e.getMessage(), e);
         }
 
-        node = node.get("hits");
-        int total = node == null ? 0 : node.get("total").asInt(0);
-        if (logger.isDebugEnabled() && offset == 0) {
-            logger.debug(String.format("[%s] [%s/%s] Rows to update: %s", peer, toIndex, toType, total));
+        return httpPost;
+    }
+
+    private HttpPost createNextScrollRequest(Peer peer,
+                                             String scrollId) {
+
+        HttpPost httpPost = new HttpPost(httpService.getPath(peer, "_search", "scroll"));
+        httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
+        httpPost.setEntity(new StringEntity(String.format("{\"scroll\": \"%s\", \"scroll_id\": \"%s\"}",
+                SCROLL_PARAM_VALUE,
+                scrollId), "UTF-8"));
+        return httpPost;
+    }
+
+    private SearchScrollResponse executeAndParseRequest(Peer peer, String fromIndex, String fromType, HttpUriRequest request) {
+        try {
+            // Execute query & parse response
+            JsonNode node = httpService.executeRequest(request, JsonNode.class, String.class);
+            return node == null ? null : new SearchScrollResponse(node);
+        } catch (HttpUnauthorizeException e) {
+            throw new TechnicalException(String.format("[%s] [%s/%s] Unable to access (%s).", peer, fromIndex, fromType, e.getMessage()), e);
+        } catch (TechnicalException e) {
+            throw new TechnicalException(String.format("[%s] [%s/%s] Unable to synchronize: %s", peer, fromIndex, fromType, e.getMessage()), e);
+        } catch (Exception e) {
+            throw new TechnicalException(String.format("[%s] [%s/%s] Unable to parse response: ", peer, fromIndex, fromType, e.getMessage()), e);
         }
+    }
 
+    private long fetchAndIndex(final Peer peer,
+                               String toIndex, String toType,
+                               String issuerFieldName, String versionFieldName,
+                               SearchScrollResponse response,
+                               final ObjectMapper objectMapper,
+                               SynchroResult result) {
         boolean debug = logger.isTraceEnabled();
 
         long counter = 0;
@@ -204,17 +282,19 @@ public abstract class AbstractSynchroService extends AbstractService {
         long insertHits = 0;
         long updateHits = 0;
         long invalidSignatureHits = 0;
-        ObjectMapper objectMapper = getObjectMapper();
 
-        if (offset < total) {
+        BulkRequestBuilder bulkRequest = client.prepareBulk();
+        bulkRequest.setRefresh(true);
 
-            BulkRequestBuilder bulkRequest = client.prepareBulk();
-            bulkRequest.setRefresh(true);
+        for (Iterator<SearchScrollResponse.Hit> hits = response.getHits(); hits.hasNext();){
+            SearchScrollResponse.Hit hit = hits.next();
+            String id = hit.getId();
+            JsonNode source = hit.getSource();
 
-            for (Iterator<JsonNode> hits = node.get("hits").iterator(); hits.hasNext();){
-                JsonNode hit = hits.next();
-                String id = hit.get("_id").asText();
-                JsonNode source = hit.get("_source");
+            if (source == null) {
+                logger.error("No source for doc " + id);
+            }
+            else {
                 counter++;
 
                 try {
@@ -227,14 +307,11 @@ public abstract class AbstractSynchroService extends AbstractService {
                         throw new InvalidFormatException(String.format("Invalid format: missing or null %s field.", versionFieldName));
                     }
 
-                    GetResponse existingDoc = client.prepareGet(toIndex, toType, id)
-                            .setFields(versionFieldName, issuerFieldName)
-                            .execute().actionGet();
-
-                    boolean doInsert = !existingDoc.isExists();
+                    Map<String, Object> existingFields = client.getFieldsById(toIndex, toType, id, versionFieldName, issuerFieldName);
+                    boolean exists = existingFields != null;
 
                     // Insert (new doc)
-                    if (doInsert) {
+                    if (!exists) {
 
                         if (debug) {
                             logger.trace(String.format("[%s] [%s/%s] insert _id=%s\n%s", peer, toIndex, toType, id, source.toString()));
@@ -244,7 +321,7 @@ public abstract class AbstractSynchroService extends AbstractService {
                         // Il semble que le format JSON ne soit pas le même que celui qui a été signé
                         try {
                             readAndVerifyIssuerSignature(source, issuerFieldName);
-                        } catch(InvalidSignatureException e) {
+                        } catch (InvalidSignatureException e) {
                             invalidSignatureHits++;
                             // FIXME: should enable this log (after issue #11 resolution)
                             //logger.warn(String.format("[%s] [%s/%s/%s] %s.\n%s", peer, toIndex, toType, id, e.getMessage(), source.toString()));
@@ -260,14 +337,14 @@ public abstract class AbstractSynchroService extends AbstractService {
                     else {
 
                         // Check same issuer
-                        String existingIssuer = (String)existingDoc.getFields().get(issuerFieldName).getValue();
+                        String existingIssuer = (String) existingFields.get(issuerFieldName);
                         if (!Objects.equals(issuer, existingIssuer)) {
                             throw new InvalidFormatException(String.format("Invalid document: not same [%s].", issuerFieldName));
                         }
 
                         // Check version
-                        Long existingVersion = ((Number)existingDoc.getFields().get(versionFieldName).getValue()).longValue();
-                        boolean doUpdate = (existingVersion == null || version > existingVersion);
+                        Number existingVersion = ((Number) existingFields.get(versionFieldName));
+                        boolean doUpdate = (existingVersion == null || version > existingVersion.longValue());
 
                         if (doUpdate) {
                             if (debug) {
@@ -278,7 +355,7 @@ public abstract class AbstractSynchroService extends AbstractService {
                             // Il semble que le format JSON ne soit pas le même que celui qui a été signé
                             try {
                                 readAndVerifyIssuerSignature(source, issuerFieldName);
-                            } catch(InvalidSignatureException e) {
+                            } catch (InvalidSignatureException e) {
                                 invalidSignatureHits++;
                                 // FIXME: should enable this log (after issue #11 resolution)
                                 //logger.warn(String.format("[%s] [%s/%s/%s] %s.\n%s", peer, toIndex, toType, id, e.getMessage(), source.toString()));
@@ -291,40 +368,37 @@ public abstract class AbstractSynchroService extends AbstractService {
                         }
                     }
 
-                }
-                catch (DuniterElasticsearchException e) {
+                } catch (DuniterElasticsearchException e) {
                     if (logger.isDebugEnabled()) {
                         logger.warn(String.format("[%s] [%s/%s/%s] %s. Skipping.\n%s", peer, toIndex, toType, id, e.getMessage(), source.toString()));
-                    }
-                    else {
+                    } else {
                         logger.warn(String.format("[%s] [%s/%s/%s] %s. Skipping.", peer, toIndex, toType, id, e.getMessage()));
                     }
                     // Skipping document (continue)
-                }
-                catch (Exception e) {
+                } catch (Exception e) {
                     logger.error(String.format("[%s] [%s/%s/%s] %s. Skipping.", peer, toIndex, toType, id, e.getMessage()), e);
                     // Skipping document (continue)
                 }
             }
+        }
 
-            if (bulkRequest.numberOfActions() > 0) {
+        if (bulkRequest.numberOfActions() > 0) {
 
-                // Flush the bulk if not empty
-                BulkResponse bulkResponse = bulkRequest.get();
-                Set<String> missingDocIds = new LinkedHashSet<>();
+            // Flush the bulk if not empty
+            BulkResponse bulkResponse = bulkRequest.get();
+            Set<String> missingDocIds = new LinkedHashSet<>();
 
-                // If failures, continue but save missing blocks
-                if (bulkResponse.hasFailures()) {
-                    // process failures by iterating through each bulk response item
-                    for (BulkItemResponse itemResponse : bulkResponse) {
-                        boolean skip = !itemResponse.isFailed()
-                                || missingDocIds.contains(itemResponse.getId());
-                        if (!skip) {
-                            if (debug) {
-                                logger.debug(String.format("[%s] [%s/%s] could not process _id=%s: %s. Skipping.", peer, toIndex, toType, itemResponse.getId(), itemResponse.getFailureMessage()));
-                            }
-                            missingDocIds.add(itemResponse.getId());
+            // If failures, continue but save missing blocks
+            if (bulkResponse.hasFailures()) {
+                // process failures by iterating through each bulk response item
+                for (BulkItemResponse itemResponse : bulkResponse) {
+                    boolean skip = !itemResponse.isFailed()
+                            || missingDocIds.contains(itemResponse.getId());
+                    if (!skip) {
+                        if (debug) {
+                            logger.debug(String.format("[%s] [%s/%s] could not process _id=%s: %s. Skipping.", peer, toIndex, toType, itemResponse.getId(), itemResponse.getFailureMessage()));
                         }
+                        missingDocIds.add(itemResponse.getId());
                     }
                 }
             }
@@ -337,4 +411,5 @@ public abstract class AbstractSynchroService extends AbstractService {
 
         return counter;
     }
+
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/DocStatService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/DocStatService.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f1cd917b60ec83f716a7d94888a0461b672f9bf
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/DocStatService.java
@@ -0,0 +1,190 @@
+package org.duniter.elasticsearch.service;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.duniter.core.util.DateUtils;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.dao.DocStatDao;
+import org.duniter.elasticsearch.model.DocStat;
+import org.duniter.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.action.bulk.BulkRequestBuilder;
+import org.elasticsearch.action.index.IndexRequestBuilder;
+import org.elasticsearch.common.inject.Inject;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Maintained stats on doc (count records)
+ * Created by Benoit on 30/03/2015.
+ */
+public class DocStatService extends AbstractService  {
+
+    private DocStatDao docStatDao;
+    private ThreadPool threadPool;
+    private List<StatDef> statDefs = new ArrayList<>();
+
+    public interface ComputeListener {
+       void onCompute(DocStat stat);
+    }
+
+    public class StatDef {
+        String index;
+        String type;
+        List<ComputeListener> listeners;
+        StatDef(String index, String type) {
+            this.index=index;
+            this.type=type;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return (obj instanceof StatDef) &&
+                    Objects.equals(((StatDef)obj).index, index) &&
+                    Objects.equals(((StatDef)obj).type, type);
+        }
+
+        public void addListener(ComputeListener listener) {
+            if (listeners == null) {
+                listeners = new ArrayList<>();
+            }
+            listeners.add(listener);
+        }
+    }
+
+    @Inject
+    public DocStatService(Duniter4jClient client, PluginSettings settings, ThreadPool threadPool,
+                          DocStatDao docStatDao){
+        super("duniter.data.stats", client, settings);
+        this.threadPool = threadPool;
+        this.docStatDao = docStatDao;
+        setIsReady(true);
+    }
+
+    public DocStatService createIndexIfNotExists() {
+        docStatDao.createIndexIfNotExists();
+        return this;
+    }
+
+    public DocStatService deleteIndex() {
+        docStatDao.deleteIndex();
+        return this;
+    }
+
+    public DocStatService registerIndex(String index, String type) {
+        return registerIndex(index, type, null);
+    }
+
+    public DocStatService registerIndex(String index, String type, ComputeListener listener) {
+        Preconditions.checkArgument(StringUtils.isNotBlank(index));
+        StatDef statDef = new StatDef(index, type);
+        if (!statDefs.contains(statDef)) {
+            statDefs.add(statDef);
+        }
+
+        if (listener != null) {
+            addListener(index, type, listener);
+        }
+
+        return this;
+    }
+
+    public DocStatService addListener(String index, String type, ComputeListener listener) {
+        Preconditions.checkArgument(StringUtils.isNotBlank(index));
+        Preconditions.checkNotNull(listener);
+
+        // Find the existsing def
+        StatDef spec = new StatDef(index, type);
+        StatDef statDef = statDefs.stream().filter(sd -> sd.equals(spec)).findFirst().get();
+        Preconditions.checkNotNull(statDef);
+
+        statDef.addListener(listener);
+        return this;
+    }
+
+    /**
+     * Start scheduling doc stats update
+     * @return
+     */
+    public DocStatService start() {
+        long delayBeforeNextHour = DateUtils.delayBeforeNextHour();
+
+        threadPool.scheduleAtFixedRate(
+                this::computeStats,
+                delayBeforeNextHour,
+                60 * 60 * 1000 /* every hour */,
+                TimeUnit.MILLISECONDS);
+        return this;
+    }
+
+    public void computeStats() {
+
+        // Skip if empty
+        if (CollectionUtils.isEmpty(statDefs)) return;
+
+        int bulkSize = pluginSettings.getIndexBulkSize();
+        long now = System.currentTimeMillis()/1000;
+        BulkRequestBuilder bulkRequest = client.prepareBulk();
+
+        DocStat stat = new DocStat();
+        stat.setTime(now);
+
+        int counter = 0;
+
+        for (StatDef statDef: statDefs) {
+            long count = docStatDao.countDoc(statDef.index, statDef.type);
+
+            // Update stat properties (resue existing obj)
+            stat.setIndex(statDef.index);
+            stat.setIndexType(statDef.type);
+            stat.setCount(count);
+
+            // Call compute listeners if any
+            if (CollectionUtils.isNotEmpty(statDef.listeners)) {
+                statDef.listeners.forEach(l -> l.onCompute(stat));
+            }
+
+            // Add insertion into bulk
+            IndexRequestBuilder request = docStatDao.prepareIndex(stat);
+            bulkRequest.add(request);
+            counter++;
+
+            // Flush the bulk if not empty
+            if ((counter % bulkSize) == 0) {
+                client.flushBulk(bulkRequest);
+                bulkRequest = client.prepareBulk();
+            }
+        }
+
+        // last flush
+        if ((counter % bulkSize) != 0) {
+            client.flushBulk(bulkRequest);
+        }
+    }
+
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/PeerService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/PeerService.java
index ba34bf5b40b52e895b54377f82e6e022583aed6e..070190a7f79d353fb70c54292a0d70b9f7ffd70f 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/PeerService.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/PeerService.java
@@ -24,6 +24,7 @@ package org.duniter.elasticsearch.service;
 
 
 import com.google.common.collect.ImmutableList;
+import org.duniter.core.client.dao.PeerDao;
 import org.duniter.core.client.model.bma.BlockchainParameters;
 import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.local.Peer;
@@ -46,14 +47,16 @@ public class PeerService extends AbstractService  {
     private org.duniter.core.client.service.bma.BlockchainRemoteService blockchainRemoteService;
     private org.duniter.core.client.service.local.NetworkService networkService;
     private org.duniter.core.client.service.local.PeerService delegate;
+    private PeerDao peerDao;
     private ThreadPool threadPool;
 
     @Inject
     public PeerService(Duniter4jClient client, PluginSettings settings, ThreadPool threadPool,
-                       CryptoService cryptoService,
+                       CryptoService cryptoService, PeerDao peerDao,
                        final ServiceLocator serviceLocator){
         super("duniter.network.peer", client, settings, cryptoService);
         this.threadPool = threadPool;
+        this.peerDao = peerDao;
         threadPool.scheduleOnStarted(() -> {
             this.blockchainRemoteService = serviceLocator.getBlockchainRemoteService();
             this.networkService = serviceLocator.getNetworkService();
@@ -132,4 +135,8 @@ public class PeerService extends AbstractService  {
                 peers -> logger.debug(String.format("[%s] Update peers: %s found", currencyName, CollectionUtils.size(peers))),
                 filterDef, sortDef, true /*autoreconnect*/, threadPool.scheduler());
     }
+
+    public long getMaxLastUpTime(String currencyId) {
+        return peerDao.getMaxLastUpTime(currencyId);
+    }
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
index 13a771ef032e568ad09da47a02803e75fb8c7761..f6b9397af00e5faa84ac2115c9836d3e80133ef2 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
@@ -49,6 +49,7 @@ public class ServiceModule extends AbstractModule implements Module {
         bind(ThreadPool.class).asEagerSingleton();
         bind(PluginInit.class).asEagerSingleton();
         bind(ChangeService.class).asEagerSingleton();
+        bind(DocStatService.class).asEagerSingleton();
 
         // blockchain indexation services
         bind(BlockchainService.class).asEagerSingleton();
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
index 1edd7eb0c155741580c838e86e11631b40e680e3..06689ebafa78951bde7f7be57a0cc5849633acf8 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
@@ -109,7 +109,7 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> {
     }
 
     /**
-     * Schedules an rest when cluster is ready
+     * Schedules an rest when cluster is ready AND has one of the expected health status
      *
      * @param job the rest to execute
      * @param expectedStatus expected health status, to run the job
@@ -118,6 +118,8 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> {
     public void scheduleOnClusterHealthStatus(Runnable job, ClusterHealthStatus... expectedStatus) {
         Preconditions.checkNotNull(job);
 
+        Preconditions.checkArgument(expectedStatus.length > 0);
+
         scheduleOnStarted(() -> {
             if (waitClusterHealthStatus(expectedStatus)) {
                 // continue
@@ -126,6 +128,17 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> {
         });
     }
 
+    /**
+     * Schedules an rest when cluster is ready
+     *
+     * @param job the rest to execute
+     * @param expectedStatus expected health status, to run the job
+     * @return a ScheduledFuture who's get will return when the task is complete and throw an exception if it is canceled
+     */
+    public void scheduleOnClusterReady(Runnable job) {
+        scheduleOnClusterHealthStatus(job, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN);
+    }
+
     /**
      * Schedules an rest that runs on the scheduler thread, when possible (0 delay).
      *
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java
index fdb89c30ff09eab321e1bbec868eb3870d8b1af5..8852808b05fcc0b52afe21d8a7084df5cdf4bb35 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java
@@ -39,23 +39,28 @@ package org.duniter.elasticsearch.websocket;
 */
 
 import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.Preconditions;
 import org.duniter.elasticsearch.PluginSettings;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.Loggers;
 import org.glassfish.tyrus.server.Server;
 
-import javax.websocket.DeploymentException;
+import java.net.BindException;
 import java.security.AccessController;
-import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.List;
 
 public class WebSocketServer {
 
+
     public static final String WS_PATH = "/ws";
     private final ESLogger log = Loggers.getLogger("duniter.ws");
+    private static final String PORT_RANGE_REGEXP = "[0-9]+-[0-9]+";
     private List<Class<?>> endPoints = new ArrayList<>();
 
     @Inject
@@ -83,32 +88,66 @@ public class WebSocketServer {
         return endPoints.toArray(new Class<?>[endPoints.size()]);
     }
 
-    private void startServer(String host, int port, Class<?>[] endPoints) {
-
-        final Server server = new Server(host, port, WS_PATH, null, endPoints) ;
-
-        try {
-            log.info(String.format("Starting Websocket server... [%s:%s%s]", host, port, WS_PATH));
-            AccessController.doPrivileged(new PrivilegedAction() {
-                @Override
-                public Object run() {
-                    try {
-                        // Tyrus tries to load the server code using reflection. In Elasticsearch 2.x Java
-                        // security manager is used which breaks the reflection code as it can't find the class.
-                        // This is a workaround for that
-                        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
-                        server.start();
-                        log.info("Websocket server started");
-                        return null;
-                    } catch (DeploymentException e) {
-                        throw new RuntimeException("Failed to start server", e);
+    private void startServer(String host, String portOrRange, Class<?>[] endPoints) {
+        Preconditions.checkNotNull(host);
+        Preconditions.checkNotNull(portOrRange);
+        Preconditions.checkArgument(portOrRange.matches(PORT_RANGE_REGEXP) || portOrRange.matches("[0-9]+"));
+
+        log.info(String.format("Starting Websocket server... {%s:%s}", host, portOrRange));
+
+        String[] rangeParts = portOrRange.split("-");
+        int port =  Integer.parseInt(rangeParts[0]);
+        int endPort = rangeParts.length == 1 ? port : Integer.parseInt(rangeParts[1]);
+
+        boolean started = false;
+        while (!started && port <= endPort) {
+
+            final Server server = new Server(host, port, WS_PATH, null, endPoints) ;
+            try {
+                AccessController.doPrivileged(new PrivilegedExceptionAction<Server>() {
+                    @Override
+                    public Server run() throws Exception {
+                            // Tyrus tries to load the server code using reflection. In Elasticsearch 2.x Java
+                            // security manager is used which breaks the reflection code as it can't find the class.
+                            // This is a workaround for that
+                            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+                            server.start();
+                            return server;
                     }
+                });
+                started = true;
+            }
+            catch (PrivilegedActionException e) {
+                // port already use: retry with a new port
+                if (isBindException(e)) {
+                    server.stop(); // destroy server
+                    port++;
                 }
-            });
-        } catch (Exception e) {
-            log.error("Failed to start Websocket server", e);
-            throw new TechnicalException(e);
+                else {
+                    throw new TechnicalException("Failed to start Websocket server", e);
+                }
+            }
+
+        }
+
+        if (started) {
+            log.info(String.format("Websocket server started {%s:%s} on path [%s]", host, port, WS_PATH));
+        }
+        else {
+            String error = String.format("Failed to start Websocket server. Could not bind address {%s:%s}", host, port);
+            log.error(error);
+            throw new TechnicalException(error);
         }
     }
 
+    /* -- protected method -- */
+
+    protected boolean isBindException(Throwable t) {
+
+        if (t instanceof BindException) return true;
+        if (t.getCause() != null){
+            return isBindException(t.getCause());
+        }
+        return false;
+    }
 }
diff --git a/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/DocStatServiceTest.java b/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/DocStatServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f9791ba45d092c56632f12eed17ccc47c20baf60
--- /dev/null
+++ b/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/DocStatServiceTest.java
@@ -0,0 +1,77 @@
+package org.duniter.elasticsearch.service;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import org.duniter.core.client.config.Configuration;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.elasticsearch.TestResource;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DocStatServiceTest {
+
+	private static final Logger log = LoggerFactory.getLogger(DocStatServiceTest.class);
+
+	@ClassRule
+	public static final TestResource resource = TestResource.create();
+
+    private CurrencyService currencyService;
+    private DocStatService service;
+    private Configuration config;
+    private Peer peer;
+
+    @Before
+    public void setUp() throws Exception {
+        currencyService = ServiceLocator.instance().getBean(CurrencyService.class);
+        service = ServiceLocator.instance().getBean(DocStatService.class);
+        config = Configuration.instance();
+        peer = new Peer.Builder()
+                .setHost(config.getNodeHost())
+                .setPort(config.getNodePort()).build();
+
+        // Waiting services started
+        while(!service.isReady() || !currencyService.isReady()) {
+            Thread.sleep(1000);
+        }
+
+        // Init the currency
+        currencyService.createIndexIfNotExists()
+                .indexCurrencyFromPeer(peer);
+
+        Thread.sleep(5000);
+    }
+
+    @Test
+    public void computeStats() throws Exception {
+
+        // Add new stats def
+        service.registerIndex(CurrencyService.INDEX, CurrencyService.RECORD_TYPE);
+
+        service.computeStats();
+
+    }
+}
diff --git a/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/PeerServiceTest.java b/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/PeerServiceTest.java
index 790a9d2e00d3a34b2b04b19c9c97e3f7b944669e..826d03f1ed8c5f73386eb9e22345e4448069364d 100644
--- a/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/PeerServiceTest.java
+++ b/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/PeerServiceTest.java
@@ -53,6 +53,7 @@ public class PeerServiceTest {
 
     private CurrencyService currencyService;
     private PeerService service;
+    private org.duniter.core.client.service.local.PeerService localService;
     private NetworkRemoteService remoteService;
     private Configuration config;
     private Peer peer;
@@ -62,6 +63,7 @@ public class PeerServiceTest {
         currencyService = ServiceLocator.instance().getBean(CurrencyService.class);
         service = ServiceLocator.instance().getBean(PeerService.class);
         remoteService = ServiceLocator.instance().getNetworkRemoteService();
+        localService = ServiceLocator.instance().getPeerService();
         config = Configuration.instance();
         peer = new Peer.Builder()
                 .setHost(config.getNodeHost())
@@ -101,7 +103,7 @@ public class PeerServiceTest {
         peer2.getStats().setLastUpTime(peer1.getStats().getLastUpTime() - 150); // Set UP just before the peer 1
 
         // Save peers
-        service.savePeers(peer1.getCurrency(), ImmutableList.of(peer1, peer2));
+        localService.save(peer1.getCurrency(), ImmutableList.of(peer1, peer2), false);
 
         // Try to read
         Long maxLastUpTime = service.getMaxLastUpTime(peer1.getCurrency());
diff --git a/duniter4j-es-subscription/pom.xml b/duniter4j-es-subscription/pom.xml
index 42f32e79e99d1c66096db3c54e0a67fa8ee380ac..4824be7532b9744e0f424b36a7539ea8a5f49843 100644
--- a/duniter4j-es-subscription/pom.xml
+++ b/duniter4j-es-subscription/pom.xml
@@ -52,8 +52,6 @@
       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
-
-    <!-- LOGGING DEPENDENCIES - SLF4J -->
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
@@ -70,14 +68,6 @@
       <optional>true</optional>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>log4j</artifactId>
-      <optional>true</optional>
-      <scope>test</scope>
-    </dependency>
-
-    <!-- JNA (need for OS shutdown hook) -->
     <dependency>
       <groupId>net.java.dev.jna</groupId>
       <artifactId>jna</artifactId>
diff --git a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/Plugin.java b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/Plugin.java
index 48b5b967e00f1cb37de8c6ed31d715bf8ad454b7..ffb163534dd63928888fd476e40366b74194ffd6 100644
--- a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/Plugin.java
+++ b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/Plugin.java
@@ -31,35 +31,37 @@ import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.inject.Module;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.logging.Loggers;
 import org.elasticsearch.common.settings.Settings;
 
 import java.util.Collection;
 
 public class Plugin extends org.elasticsearch.plugins.Plugin {
 
-    private ESLogger log = ESLoggerFactory.getLogger(Plugin.class.getName());
+    private ESLogger logger ;
 
     private boolean enable;
 
     @Inject public Plugin(Settings settings) {
         this.enable = settings.getAsBoolean("subscription.enabled", true);
+        this.logger = Loggers.getLogger(Plugin.class.getName(), settings, new String[0]);
     }
 
     @Override
     public String name() {
-        return "subscription";
+        return "duniter4j-es-subscription";
     }
 
     @Override
     public String description() {
-        return "ElasticSearch Gchange Plugin";
+        return "Duniter Subscription Plugin";
     }
 
     @Override
     public Collection<Module> nodeModules() {
         Collection<Module> modules = Lists.newArrayList();
         if (!enable) {
-            log.warn(description() + " has been disabled.");
+            logger .warn(description() + " has been disabled.");
             return modules;
         }
         modules.add(new DaoModule());
diff --git a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/PluginInit.java b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/PluginInit.java
index 3716c074dda509ccceed7431409250a0f44079f0..fab3a3063f13be14436deb5b5702d62e7624e4bf 100644
--- a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/PluginInit.java
+++ b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/PluginInit.java
@@ -48,11 +48,12 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
     private final PluginSettings pluginSettings;
     private final ThreadPool threadPool;
     private final Injector injector;
-    private final static ESLogger logger = Loggers.getLogger("duniter.subscription");
+    private final ESLogger logger;
 
     @Inject
     public PluginInit(Settings settings, PluginSettings pluginSettings, ThreadPool threadPool, final Injector injector) {
         super(settings);
+        this.logger = Loggers.getLogger("duniter.subscription", settings, new String[0]);
         this.pluginSettings = pluginSettings;
         this.threadPool = threadPool;
         this.injector = injector;
@@ -60,13 +61,12 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
 
     @Override
     protected void doStart() {
-        threadPool.scheduleOnClusterHealthStatus(() -> {
+        threadPool.scheduleOnClusterReady(() -> {
             createIndices();
 
             // Waiting cluster back to GREEN or YELLOW state, before synchronize
-            threadPool.scheduleOnClusterHealthStatus(this::doAfterStart,
-                    ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN);
-        }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN);
+            threadPool.scheduleOnClusterReady(this::doAfterStart);
+        });
     }
 
     @Override
diff --git a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/rest/execution/RestSubscriptionExecutionGetAction.java b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/rest/execution/RestSubscriptionExecutionGetAction.java
index dbd6ff10e179029d07425628f33386256ea54a13..f0f5771be6a1c4a07e14eac1041d014383528a87 100644
--- a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/rest/execution/RestSubscriptionExecutionGetAction.java
+++ b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/rest/execution/RestSubscriptionExecutionGetAction.java
@@ -32,6 +32,7 @@ public class RestSubscriptionExecutionGetAction {
     @Inject
     public RestSubscriptionExecutionGetAction(RestSecurityController securityController) {
         // Add security rule to enable access on /subscription/execution
+        // only on search POST request (need by synchro)
         securityController.allowPostSearchIndexType(SubscriptionIndexDao.INDEX, SubscriptionExecutionDao.TYPE);
     }
 
diff --git a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SubscriptionService.java b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SubscriptionService.java
index 86d613713d5a86641372e6e814fcbb41e5e5ba63..7e3b0b577899bce247c055db5b507a017fc893a5 100644
--- a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SubscriptionService.java
+++ b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SubscriptionService.java
@@ -32,6 +32,7 @@ import org.duniter.core.client.model.elasticsearch.Record;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
 import org.duniter.core.util.CollectionUtils;
+import org.duniter.core.util.DateUtils;
 import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
 import org.duniter.core.util.crypto.CryptoUtils;
@@ -42,7 +43,6 @@ import org.duniter.elasticsearch.subscription.dao.record.SubscriptionRecordDao;
 import org.duniter.elasticsearch.subscription.model.SubscriptionExecution;
 import org.duniter.elasticsearch.subscription.model.SubscriptionRecord;
 import org.duniter.elasticsearch.subscription.model.email.EmailSubscription;
-import org.duniter.elasticsearch.subscription.util.DateUtils;
 import org.duniter.elasticsearch.subscription.util.stringtemplate.DateRenderer;
 import org.duniter.elasticsearch.subscription.util.stringtemplate.StringRenderer;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
@@ -243,7 +243,7 @@ public class SubscriptionService extends AbstractService {
         }
 
         try {
-            EmailSubscription.Content content = objectMapper.readValue(jsonContent, EmailSubscription.Content.class);
+            EmailSubscription.Content content = getObjectMapper().readValue(jsonContent, EmailSubscription.Content.class);
             subscription.setContent(content);
         } catch(Exception e) {
             logger.error(String.format("Could not parse email subscription content [%s]: %s", jsonContent, e.getMessage()));
@@ -437,7 +437,7 @@ public class SubscriptionService extends AbstractService {
     private String toJson(Record record, boolean cleanHashAndSignature) {
         Preconditions.checkNotNull(record);
         try {
-            String json = objectMapper.writeValueAsString(record);
+            String json = getObjectMapper().writeValueAsString(record);
             if (cleanHashAndSignature) {
                 json = JacksonUtils.removeAttribute(json, Record.PROPERTY_SIGNATURE);
                 json = JacksonUtils.removeAttribute(json, Record.PROPERTY_HASH);
diff --git a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SynchroService.java b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SynchroService.java
index 0f33cdbf3e6891d93a6847ad0b89352847b6366b..6428e00736a59aaddc62b0abc741f867f7fa85e4 100644
--- a/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SynchroService.java
+++ b/duniter4j-es-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SynchroService.java
@@ -22,6 +22,7 @@ package org.duniter.elasticsearch.subscription.service;
  * #L%
  */
 
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.service.CryptoService;
 import org.duniter.elasticsearch.client.Duniter4jClient;
@@ -32,7 +33,6 @@ import org.duniter.elasticsearch.subscription.dao.SubscriptionIndexDao;
 import org.duniter.elasticsearch.subscription.dao.execution.SubscriptionExecutionDao;
 import org.duniter.elasticsearch.subscription.dao.record.SubscriptionRecordDao;
 import org.duniter.elasticsearch.subscription.model.Protocol;
-import org.duniter.elasticsearch.subscription.model.SubscriptionExecution;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.duniter.elasticsearch.user.PluginSettings;
 import org.elasticsearch.common.inject.Inject;
@@ -49,30 +49,33 @@ public class SynchroService extends AbstractSynchroService {
     }
 
     public void synchronize() {
-        logger.info("Synchronizing subscription data...");
-        Peer peer = getPeerFromAPI(Protocol.EMAIL_API);
+        logger.info("Starting subscription data synchronization...");
+
+        Peer peer = getPeerFromAPI(EndpointApi.ES_SUBSCRIPTION_API);
         synchronize(peer);
+
+        logger.info("Subscription data synchronization [OK]");
     }
 
     /* -- protected methods -- */
 
     protected void synchronize(Peer peer) {
-
-        long sinceTime = 0; // TODO: get last sync time from somewhere ? (e.g. a specific index)
-
-        logger.info(String.format("[%s] Synchronizing subscription data since %s...", peer.toString(), sinceTime));
-
         SynchroResult result = new SynchroResult();
-        long time = System.currentTimeMillis();
+        long now = System.currentTimeMillis();
 
-        importSubscriptionsChanges(result, peer, sinceTime);
+        long fromTime = 0; // TODO: get last sync time from somewhere ? (e.g. a specific index)
+        synchronizeSubscriptions(peer, fromTime, result);
 
-        long duration = System.currentTimeMillis() - time;
-        logger.info(String.format("[%s] Synchronizing subscription data since %s [OK] %s (in %s ms)", peer.toString(), sinceTime, result.toString(), duration));
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("[%s] Subscription data imported in %s ms: %s", peer, System.currentTimeMillis() - now, result.toString()));
+        }
     }
 
-    protected void importSubscriptionsChanges(SynchroResult result, Peer peer, long sinceTime) {
-        importChanges(result, peer, SubscriptionIndexDao.INDEX, SubscriptionExecutionDao.TYPE,  sinceTime);
-        importChanges(result, peer, SubscriptionIndexDao.INDEX, SubscriptionRecordDao.TYPE,  sinceTime);
+    protected void synchronizeSubscriptions(Peer peer, long fromTime, SynchroResult result) {
+        // Workaround to skip data older than june 2017
+        long executionFromTime = Math.max(fromTime, 1493743177);
+        safeSynchronizeIndex(peer, SubscriptionIndexDao.INDEX, SubscriptionExecutionDao.TYPE, executionFromTime, result);
+        
+        safeSynchronizeIndex(peer, SubscriptionIndexDao.INDEX, SubscriptionRecordDao.TYPE,  fromTime, result);
     }
 }
diff --git a/duniter4j-es-subscription/src/test/es-home/config/elasticsearch.yml b/duniter4j-es-subscription/src/test/es-home/config/elasticsearch.yml
index 1a8345b47b40d49e7aceea35dd1706dfcc2ea4b9..bb407837577dc136aceadad7a9f5109f986bc941 100644
--- a/duniter4j-es-subscription/src/test/es-home/config/elasticsearch.yml
+++ b/duniter4j-es-subscription/src/test/es-home/config/elasticsearch.yml
@@ -15,7 +15,7 @@
 # Use a descriptive name for your cluster:
 #
 # cluster.name: my-application
-cluster.name: duniter4j-subscription-TEST
+cluster.name: duniter4j-es-subscription-TEST
 #
 # ------------------------------------ Node ------------------------------------
 #
@@ -182,7 +182,7 @@ duniter.mail.admin: blavenie@EIS-DEV
 #
 # Websocket port (usefull for listen changes)
 #
-duniter.ws.port: 9400
+duniter.ws.port: 9400-9410
 
 # ---------------------------------- Duniter4j Subscription services ------------
 #
diff --git a/duniter4j-es-subscription/src/test/java/org/duniter/elasticsearch/subscription/service/SubscriptionServiceTest.java b/duniter4j-es-subscription/src/test/java/org/duniter/elasticsearch/subscription/service/SubscriptionServiceTest.java
index 7493b37471b347a72360e0b1ba63eba6239e5e22..8ed8c9ab4cae4ffcd7b23c5cf2d3eb91f6a0ceb3 100644
--- a/duniter4j-es-subscription/src/test/java/org/duniter/elasticsearch/subscription/service/SubscriptionServiceTest.java
+++ b/duniter4j-es-subscription/src/test/java/org/duniter/elasticsearch/subscription/service/SubscriptionServiceTest.java
@@ -106,6 +106,15 @@ public class SubscriptionServiceTest {
         Thread.sleep(10000);
     }
 
+    @Test
+    @Ignore
+    public void startNode() throws Exception {
+
+        while(true) {
+            Thread.sleep(10000);
+        }
+    }
+
     /* -- internal methods -- */
 
     protected Wallet createTestWallet() {
diff --git a/duniter4j-es-user/pom.xml b/duniter4j-es-user/pom.xml
index ad6fefc25afbd739fe33e7913318af9bc649270c..0f6607b5d53232d7e6065267b69340757424d891 100644
--- a/duniter4j-es-user/pom.xml
+++ b/duniter4j-es-user/pom.xml
@@ -31,6 +31,45 @@
             <artifactId>javax.websocket-api</artifactId>
             <scope>provided</scope>
         </dependency>
+
+        <!-- Unit test -->
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <optional>true</optional>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.java.dev.jna</groupId>
+            <artifactId>jna-platform</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>net.java.dev.jna</groupId>
+                    <artifactId>jna</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/Plugin.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/Plugin.java
index cb136e312edb52e623eca7778eae936096237b05..c649e733aa427a7eb16c27e723a60428893a2049 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/Plugin.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/Plugin.java
@@ -32,18 +32,20 @@ import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.inject.Module;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.ESLoggerFactory;
+import org.elasticsearch.common.logging.Loggers;
 import org.elasticsearch.common.settings.Settings;
 
 import java.util.Collection;
 
 public class Plugin extends org.elasticsearch.plugins.Plugin {
 
-    private ESLogger log = ESLoggerFactory.getLogger(Plugin.class.getName());
+    private ESLogger logger;
 
     private boolean enable;
 
     @Inject public Plugin(Settings settings) {
         this.enable = settings.getAsBoolean("duniter.user.enabled", true);
+        this.logger = Loggers.getLogger(Plugin.class.getName(), settings, new String[0]);
     }
 
     @Override
@@ -53,14 +55,14 @@ public class Plugin extends org.elasticsearch.plugins.Plugin {
 
     @Override
     public String description() {
-        return "Duniter ElasticSearch User Plugin";
+        return "Duniter User Plugin";
     }
 
     @Override
     public Collection<Module> nodeModules() {
         Collection<Module> modules = Lists.newArrayList();
         if (!enable) {
-            log.warn(description() + " has been disabled.");
+            logger.warn(description() + " has been disabled.");
             return modules;
         }
 
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java
index dd3953b1a1094fe50e03d87b31fa4d0650227a88..3dcfa2d3e36d83682a425d93b6d4714030714327 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java
@@ -23,7 +23,11 @@ package org.duniter.elasticsearch.user;
  */
 
 import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.service.DocStatService;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
+import org.duniter.elasticsearch.user.dao.page.RegistryCommentDao;
+import org.duniter.elasticsearch.user.dao.page.RegistryIndexDao;
+import org.duniter.elasticsearch.user.dao.page.RegistryRecordDao;
 import org.duniter.elasticsearch.user.model.UserEvent;
 import org.duniter.elasticsearch.user.service.*;
 import org.duniter.elasticsearch.user.model.UserEventCodes;
@@ -44,12 +48,13 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
     private final PluginSettings pluginSettings;
     private final ThreadPool threadPool;
     private final Injector injector;
-    private final static ESLogger logger = Loggers.getLogger("duniter.user");
+    private final ESLogger logger;
     private final String clusterName;
 
     @Inject
     public PluginInit(Settings settings, PluginSettings pluginSettings, ThreadPool threadPool, final Injector injector) {
         super(settings);
+        this.logger = Loggers.getLogger("duniter.user", settings, new String[0]);
         this.pluginSettings = pluginSettings;
         this.threadPool = threadPool;
         this.injector = injector;
@@ -58,13 +63,13 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
 
     @Override
     protected void doStart() {
-        threadPool.scheduleOnClusterHealthStatus(() -> {
+        threadPool.scheduleOnClusterReady(() -> {
             createIndices();
 
             // Waiting cluster back to GREEN or YELLOW state, before doAfterStart
-            threadPool.scheduleOnClusterHealthStatus(this::doAfterStart, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN);
+            threadPool.scheduleOnClusterReady(this::doAfterStart);
 
-        }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN);
+        });
     }
 
     @Override
@@ -142,11 +147,27 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
             }
         }
 
+        // Register stats on indices
+        if (pluginSettings.enableDocStats()) {
+            injector.getInstance(DocStatService.class)
+                    .registerIndex(UserService.INDEX, UserService.PROFILE_TYPE)
+                    .registerIndex(UserService.INDEX, UserService.SETTINGS_TYPE)
+                    .registerIndex(MessageService.INDEX, MessageService.INBOX_TYPE)
+                    .registerIndex(MessageService.INDEX, MessageService.OUTBOX_TYPE)
+                    .registerIndex(UserInvitationService.INDEX, UserInvitationService.CERTIFICATION_TYPE)
+                    .registerIndex(UserEventService.INDEX, UserEventService.EVENT_TYPE)
+                    .registerIndex(RegistryIndexDao.INDEX, RegistryRecordDao.TYPE)
+                    .registerIndex(RegistryIndexDao.INDEX, RegistryCommentDao.TYPE)
+                    .registerIndex(GroupService.INDEX, GroupService.RECORD_TYPE)
+                    .registerIndex(HistoryService.INDEX, HistoryService.DELETE_TYPE)
+            ;
+        }
+
     }
 
     protected void doAfterStart() {
+        // Synchronize
         if (pluginSettings.enableDataSync()) {
-            // Synchronize
             injector.getInstance(SynchroService.class).synchronize();
         }
 
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginSettings.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginSettings.java
index 7e54777d4a9c3d45a3a051d987a74ad593a08b54..ea88fbb8c2218776c35ada4094f03ce6e5c21a05 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginSettings.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginSettings.java
@@ -198,7 +198,7 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
         return "duniter4j-es-user-i18n";
     }
 
-    protected void initNodeKeyring() {
+    protected synchronized void initNodeKeyring() {
         if (this.nodeKeyPair != null) return;
         if (StringUtils.isNotBlank(getKeyringSalt()) &&
                 StringUtils.isNotBlank(getKeyringPassword())) {
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java
index 7b0a3fc1c3bd89ddb92c7882a7ff3853ce7e1dd9..e1aeaee0a0242af85c0bc3ce1b5061d6a78d15fd 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java
@@ -22,19 +22,19 @@ package org.duniter.elasticsearch.user.service;
  * #L%
  */
 
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.elasticsearch.Protocol;
-import org.duniter.core.client.model.elasticsearch.Record;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.service.CryptoService;
 import org.duniter.elasticsearch.client.Duniter4jClient;
-import org.duniter.elasticsearch.user.PluginSettings;
 import org.duniter.elasticsearch.model.SynchroResult;
-import org.duniter.elasticsearch.service.ServiceLocator;
 import org.duniter.elasticsearch.service.AbstractSynchroService;
+import org.duniter.elasticsearch.service.ServiceLocator;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
-import org.duniter.elasticsearch.user.model.Message;
-import org.elasticsearch.client.Client;
+import org.duniter.elasticsearch.user.PluginSettings;
 import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
 
 /**
  * Created by blavenie on 27/10/16.
@@ -48,61 +48,75 @@ public class SynchroService extends AbstractSynchroService {
     }
 
     public void synchronize() {
-        logger.info("Synchronizing user data...");
+        logger.info("Starting user data synchronization...");
 
-        Peer peer = getPeerFromAPI(Protocol.ES_API);
+        Peer peer = getPeerFromAPI(EndpointApi.ES_USER_API);
         synchronize(peer);
+
+        logger.info("User data synchronization [OK]");
     }
 
     /* -- protected methods -- */
 
 
     protected void synchronize(Peer peer) {
+        long now = System.currentTimeMillis();
+        SynchroResult result = new SynchroResult();
 
-        long sinceTime = 0; // ToDO: get last sync time from somewhere ? (e.g. a specific index)
+        long fromTime = 0; // TODO: get last sync time from somewhere ? (e.g. a specific index)
+        synchronize(peer, fromTime, result);
 
-        logger.info(String.format("[%s] Synchronizing user data since %s...", peer.toString(), sinceTime));
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("[%s] User data imported in %s ms: %s", peer, System.currentTimeMillis() - now, result.toString()));
+        }
 
-        SynchroResult result = new SynchroResult();
-        long time = System.currentTimeMillis();
+    }
 
-        importHistoryChanges(result, peer, sinceTime);
-        importUserChanges(result, peer, sinceTime);
-        importMessageChanges(result, peer, sinceTime);
-        importGroupChanges(result, peer, sinceTime);
-        importInvitationChanges(result, peer, sinceTime);
+    protected void synchronize(Peer peer, long fromTime, SynchroResult result) {
+        synchronizeHistory(peer, fromTime, result);
+        synchronizeUser(peer, fromTime, result);
+        synchronizeMessage(peer, fromTime, result);
+        synchronizeGroup(peer, fromTime, result);
+        synchronizeInvitation(peer, fromTime, result);
+    }
 
-        long duration = System.currentTimeMillis() - time;
-        logger.info(String.format("[%s] Synchronizing user data since %s [OK] %s (in %s ms)", peer.toString(), sinceTime, result.toString(), duration));
+    protected void synchronizeHistory(Peer peer, long fromTime, SynchroResult result) {
+        safeSynchronizeIndex(peer, HistoryService.INDEX, HistoryService.DELETE_TYPE, fromTime, result);
     }
 
-    protected void importHistoryChanges(SynchroResult result, Peer peer, long sinceTime) {
-        importChanges(result, peer, HistoryService.INDEX, HistoryService.DELETE_TYPE,  sinceTime);
+    protected void synchronizeUser(Peer peer, long fromTime, SynchroResult result) {
+        safeSynchronizeIndex(peer, UserService.INDEX, UserService.PROFILE_TYPE,  fromTime, result);
+        safeSynchronizeIndex(peer, UserService.INDEX, UserService.SETTINGS_TYPE,  fromTime, result);
     }
 
-    protected void importUserChanges(SynchroResult result, Peer peer, long sinceTime) {
-        importChanges(result, peer, UserService.INDEX, UserService.PROFILE_TYPE,  sinceTime);
-        importChanges(result, peer, UserService.INDEX, UserService.SETTINGS_TYPE,  sinceTime);
-        importChanges(result, peer, UserService.INDEX, UserEventService.EVENT_TYPE,  sinceTime);
+    protected void synchronizeMessage(Peer peer, long fromTime, SynchroResult result) {
+        safeSynchronizeIndex(peer, MessageService.INDEX, MessageService.INBOX_TYPE, fromTime, result);
+        safeSynchronizeIndex(peer, MessageService.INDEX, MessageService.OUTBOX_TYPE, fromTime, result);
+
+        // User events, that reference message index
+        synchronizeUserEventsOnReferenceIndex(peer, MessageService.INDEX, fromTime, result);
     }
 
-    protected void importMessageChanges(SynchroResult result, Peer peer, long sinceTime) {
-        // For compat
-        // TODO: remove this later
-        importChangesRemap(result, peer, MessageService.INDEX, MessageService.RECORD_TYPE,
-                MessageService.INDEX, MessageService.INBOX_TYPE,
-                sinceTime);
+    protected void synchronizeGroup(Peer peer, long fromTime, SynchroResult result) {
+        safeSynchronizeIndex(peer, GroupService.INDEX, GroupService.RECORD_TYPE, fromTime, result);
 
-        importChanges(result, peer, MessageService.INDEX, MessageService.INBOX_TYPE, sinceTime);
-        importChanges(result, peer, MessageService.INDEX, MessageService.OUTBOX_TYPE, sinceTime);
+        // User events, that reference invitation index
+        synchronizeUserEventsOnReferenceIndex(peer, GroupService.INDEX, fromTime, result);
     }
 
-    protected void importGroupChanges(SynchroResult result, Peer peer, long sinceTime) {
-        importChanges(result, peer, GroupService.INDEX, GroupService.RECORD_TYPE,  sinceTime);
+    protected void synchronizeInvitation(Peer peer, long fromTime, SynchroResult result) {
+        safeSynchronizeIndex(peer, UserInvitationService.INDEX, UserInvitationService.CERTIFICATION_TYPE, fromTime, result);
+
+        // User events, that reference invitation index
+        synchronizeUserEventsOnReferenceIndex(peer, UserInvitationService.INDEX, fromTime, result);
     }
 
-    protected void importInvitationChanges(SynchroResult result, Peer peer, long sinceTime) {
-        importChanges(result, peer, UserInvitationService.INDEX, UserInvitationService.CERTIFICATION_TYPE,  sinceTime);
+
+    protected void synchronizeUserEventsOnReferenceIndex(Peer peer, String referenceIndex, long fromTime, SynchroResult result) {
+
+        /*QueryBuilder query = QueryBuilders.boolQuery()
+                .filter(QueryBuilders.rangeQuery("time").gte(fromTime));
+        safeSynchronizeIndex(peer, UserService.INDEX, UserEventService.EVENT_TYPE, query, result);*/
     }
 
 }
diff --git a/duniter4j-es-user/src/test/es-home/config/elasticsearch.yml b/duniter4j-es-user/src/test/es-home/config/elasticsearch.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a4d7635529d94fe6412653d17a54016b72598de9
--- /dev/null
+++ b/duniter4j-es-user/src/test/es-home/config/elasticsearch.yml
@@ -0,0 +1,234 @@
+# ======================== Elasticsearch Configuration =========================
+#
+# NOTE: Elasticsearch comes with reasonable defaults for most settings.
+#       Before you set out to tweak and tune the configuration, make sure you
+#       understand what are you trying to accomplish and the consequences.
+#
+# The primary way of configuring a node is via this file. This template lists
+# the most important settings you may want to configure for a production cluster.
+#
+# Please see the documentation for further information on configuration options:
+# <http://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html>
+#
+# ---------------------------------- Cluster -----------------------------------
+#
+# Use a descriptive name for your cluster:
+#
+# cluster.name: my-application
+cluster.name: duniter4j-es-assembly-test-2
+#
+# ------------------------------------ Node ------------------------------------
+#
+# Use a descriptive name for the node:
+#
+# node.name: node-1
+#
+# Add custom attributes to the node:
+#
+# node.rack: r1
+#
+# ----------------------------------- Paths ------------------------------------
+#
+# Path to directory where to store the data (separate multiple locations by comma):
+#
+# path.data: /path/to/data
+#
+# Path to log files:
+#
+# path.logs: /path/to/logs
+#
+# ----------------------------------- Memory -----------------------------------
+#
+# Lock the memory on startup:
+#
+# bootstrap.mlockall: true
+#
+# Make sure that the `ES_HEAP_SIZE` environment variable is set to about half the memory
+# available on the system and that the owner of the process is allowed to use this limit.
+#
+# Elasticsearch performs poorly when the system is swapping the memory.
+#
+# ---------------------------------- Network -----------------------------------
+#
+# Set the bind address to a specific IP (IPv4 or IPv6):
+#
+# network.host: 192.168.233.118
+#
+# Set a custom port for HTTP:
+#
+# http.port: 9200-9300
+
+http.cors.allow-origin: "/.*/"
+http.cors.enabled: true
+
+# Internal transport layer
+#
+# transport.tcp.port: 9210-9220
+#
+# For more information, see the documentation at:
+# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html>
+#
+# --------------------------------- Discovery ----------------------------------
+#
+# Pass an initial list of hosts to perform discovery when new node is started:
+# The default list of hosts is ["127.0.0.1", "[::1]"]
+#
+# discovery.zen.ping.unicast.hosts: ["host1", "host2"]
+#discovery.zen.ping.unicast.hosts: ["127.0.0.1", ""]
+#
+# Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1):
+#
+# discovery.zen.minimum_master_nodes: 3
+#
+# For more information, see the documentation at:
+# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html>
+#
+# ---------------------------------- Gateway -----------------------------------
+#
+# Block initial recovery after a full cluster restart until N nodes are started:
+#
+# gateway.recover_after_nodes: 3
+#
+# For more information, see the documentation at:
+# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-gateway.html>
+#
+# ---------------------------------- Various -----------------------------------
+#
+# Disable starting multiple nodes on a single system:
+#
+# node.max_local_storage_nodes: 1
+#
+# Require explicit names when deleting indices:
+#
+# rest.destructive_requires_name: true
+
+security.manager.enabled: false
+
+#
+# ---------------------------------- Duniter4j ---------------------------------
+#
+# Disbale duniter4j plugin
+#
+# duniter.enabled: false
+#
+# Delete then create all indices at startup - DO NOT set to true in production
+#
+#duniter.indices.reload: true
+#
+# Default string analyzer
+#
+duniter.string.analyzer: french
+#
+# Enabling blockchain synchronization
+#
+duniter.blockchain.enable: false
+#
+# Force blockchain reload - WARNING: all user events will be resetted to 'unread'
+#
+#duniter.blockchain.reload: true
+#duniter.blockchain.reload.from: 50999
+#
+# Duniter node address
+#
+duniter.host: g1-test.duniter.org
+duniter.port: 10900
+#duniter.useSsl: true
+duniter4j.network.timeout: 10000
+#
+# ---------------------------------- Duniter4j security module -------------------
+#
+# Allow admin actions
+#
+duniter.keyring.salt: 'abc'
+duniter.keyring.password: 'def'
+#
+# Enable security - will restrict HTTP access to only Duniter4j known indices
+#
+duniter.security.enable: true
+#
+# Security token prefix (default: 'duniter-')
+#
+# duniter.auth.token.prefix: duniter-
+#
+# Token validity duration, in seconds (default: 600)
+#
+# duniter.auth.tokenValidityDuration: 3600  # = 1hour
+
+# ---------------------------------- Duniter4j P2P sync -------------------------
+#
+# Should synchronize data from an existing ES node ?
+#
+duniter.data.sync.enable: true
+#duniter.data.sync.enable: true
+duniter.data.sync.host: localhost
+duniter.data.sync.port: 9200
+
+#
+# Should maintain stats on data ?
+#
+duniter.data.stats.enable: true
+
+# ---------------------------------- Duniter4j Mail module -----------------------
+#
+# Enable mail module ?
+#
+duniter.mail.enable: false
+#
+# Mail: SMTP server configuration (host and port)
+#
+duniter.mail.smtp.host: localhost
+duniter.mail.smtp.port: 25
+#
+# Mail: SMTP server SSL security
+#
+#duniter.mail.smtp.ssl: true
+#duniter.mail.smtp.starttls: true
+#
+# Mail: SMTP server authentication
+#
+#duniter.mail.smtp.username:
+#duniter.mail.smtp.password:
+#
+# Mail: 'from' address
+#
+#duniter.mail.from: no-reply@domain.com
+duniter.mail.from: 'no-reply@duniter.fr'
+#
+# Mail: admin address
+#
+#duniter.mail.admin: user@domain.com
+#duniter.mail.admin: blavenie@EIS-DEV
+duniter.mail.admin: 'benoit.lavenier@e-is.pro'
+#
+# Mail: subject prefix
+#
+#duniter.mail.subject.prefix: '[Cesium+]'
+
+# ---------------------------------- Duniter4j Websocket server ----------------------
+#
+# Websocket port (usefull for listen changes)
+#
+duniter.ws.port: 9400
+
+# ---------------------------------- Duniter4j Subscription module -------------------
+#
+# Enable subscription module (Need to enable mail features)
+#
+duniter.subscription.enable: true
+#
+# Opions to DEBUG this features
+#
+#duniter.subscription.email.atStartup: false
+#duniter.subscription.email.debug: false
+#
+# Email subscription: Day of the week to trigger weekly (default: 2 = monday)
+#
+#duniter.subscription.email.dayOfWeek: 2
+#
+# Email subscription: Hour in day to trigger daily email subscription (default: 3 AM)
+#
+duniter.subscription.email.hourOfDay: 3
+#
+# Email subscription: URL to a Cesium site, for links in the email content (default: https://g1.duniter.fr)
+#
+#duniter.subscription.email.cesium.url: 'https://domain.com/cesium'
\ No newline at end of file
diff --git a/duniter4j-es-user/src/test/es-home/config/logging.yml b/duniter4j-es-user/src/test/es-home/config/logging.yml
new file mode 100644
index 0000000000000000000000000000000000000000..15cfa3e195cb46a62c7536f118d1684acfcc2ecf
--- /dev/null
+++ b/duniter4j-es-user/src/test/es-home/config/logging.yml
@@ -0,0 +1,97 @@
+# you can override this using by setting a system property, for example -Des.logger.level=DEBUG
+es.logger.level: INFO
+rootLogger: ${es.logger.level}, console, file
+logger:
+  # log rest execution errors for easier debugging
+  action: DEBUG
+
+  # deprecation logging, turn to DEBUG to see them
+  deprecation: INFO, deprecation_log_file
+
+  # reduce the logging for aws, too much is logged under the default INFO
+  com.amazonaws: WARN
+  # aws will try to do some sketchy JMX stuff, but its not needed.
+  com.amazonaws.jmx.SdkMBeanRegistrySupport: ERROR
+  com.amazonaws.metrics.AwsSdkMetrics: ERROR
+
+  org.apache.http: INFO
+
+  org.duniter: INFO
+
+  org.duniter.elasticsearch: DEBUG
+
+  duniter : DEBUG
+  duniter.network.p2p: TRACE
+
+  security: DEBUG
+
+  org.nuiton.i18n: WARN
+  org.nuiton.config: WARN
+
+  # gateway
+  #gateway: DEBUG
+  #index.gateway: DEBUG
+
+  # peer shard recovery
+  #indices.recovery: DEBUG
+
+  # discovery
+  #discovery: TRACE
+
+  index.search.slowlog: TRACE, index_search_slow_log_file
+  index.indexing.slowlog: TRACE, index_indexing_slow_log_file
+
+additivity:
+  index.search.slowlog: false
+  index.indexing.slowlog: false
+  deprecation: false
+
+appender:
+  console:
+    type: console
+    layout:
+      type: consolePattern
+      conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"
+
+  file:
+    type: dailyRollingFile
+    file: ${path.logs}/${cluster.name}.log
+    datePattern: "'.'yyyy-MM-dd"
+    layout:
+      type: pattern
+      conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %.10000m%n"
+
+  # Use the following log4j-extras RollingFileAppender to enable gzip compression of log files. 
+  # For more information see https://logging.apache.org/log4j/extras/apidocs/org/apache/log4j/rolling/RollingFileAppender.html
+  #file:
+    #type: extrasRollingFile
+    #file: ${path.logs}/${cluster.name}.log
+    #rollingPolicy: timeBased
+    #rollingPolicy.FileNamePattern: ${path.logs}/${cluster.name}.log.%d{yyyy-MM-dd}.gz
+    #layout:
+      #type: pattern
+      #conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"
+
+  deprecation_log_file:
+    type: dailyRollingFile
+    file: ${path.logs}/${cluster.name}_deprecation.log
+    datePattern: "'.'yyyy-MM-dd"
+    layout:
+      type: pattern
+      conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"
+
+  index_search_slow_log_file:
+    type: dailyRollingFile
+    file: ${path.logs}/${cluster.name}_index_search_slowlog.log
+    datePattern: "'.'yyyy-MM-dd"
+    layout:
+      type: pattern
+      conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"
+
+  index_indexing_slow_log_file:
+    type: dailyRollingFile
+    file: ${path.logs}/${cluster.name}_index_indexing_slowlog.log
+    datePattern: "'.'yyyy-MM-dd"
+    layout:
+      type: pattern
+      conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n"
diff --git a/duniter4j-es-user/src/test/es-home/plugins/duniter4j-es-core/plugin-descriptor.properties b/duniter4j-es-user/src/test/es-home/plugins/duniter4j-es-core/plugin-descriptor.properties
new file mode 100644
index 0000000000000000000000000000000000000000..1d55cb5948100ff93956598d1e02abdf0056594f
--- /dev/null
+++ b/duniter4j-es-user/src/test/es-home/plugins/duniter4j-es-core/plugin-descriptor.properties
@@ -0,0 +1,9 @@
+name=duniter4j-es-core
+description=Plugin for Duniter
+version=1.0
+site=false
+jvm=true
+classname=org.duniter.elasticsearch.Plugin
+java.version=1.8
+elasticsearch.version=2.4.5
+isolated=false
diff --git a/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestConfiguration.java b/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..29d8a0f4670bd204115c675bb0e2a95ce7354ef9
--- /dev/null
+++ b/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestConfiguration.java
@@ -0,0 +1,35 @@
+package org.duniter.elasticsearch.user;
+
+import org.duniter.core.exception.TechnicalException;
+import org.nuiton.config.ApplicationConfig;
+import org.nuiton.config.ArgumentsParserException;
+
+import static org.nuiton.i18n.I18n.t;
+
+/**
+ * Created by blavenie on 13/09/17.
+ */
+public class TestConfiguration {
+
+    private ApplicationConfig applicationConfig;
+
+    public TestConfiguration(String configFileName) {
+        applicationConfig = new ApplicationConfig();
+        applicationConfig.setConfigFileName(configFileName);
+
+        try {
+            applicationConfig.parse(new String[]{});
+
+        } catch (ArgumentsParserException e) {
+            throw new TechnicalException(t("duniter4j.config.parse.error"), e);
+        }
+    }
+
+    public String getDataSyncHost() {
+        return applicationConfig.getOption("duniter4j.data.sync.host");
+    }
+
+    public int getDataSyncPort() {
+        return applicationConfig.getOptionAsInt("duniter4j.data.sync.port");
+    }
+}
diff --git a/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestFixtures.java b/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestFixtures.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c27de6609be9011471b718442937691350b7ec9
--- /dev/null
+++ b/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestFixtures.java
@@ -0,0 +1,26 @@
+package org.duniter.elasticsearch.user;/*
+ * #%L
+ * UCoin Java Client :: ElasticSearch Indexer
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+public class TestFixtures extends org.duniter.core.test.TestFixtures {
+
+}
diff --git a/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestResource.java b/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..22822a6047c09a5ab83972de07740275ab56d485
--- /dev/null
+++ b/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/TestResource.java
@@ -0,0 +1,84 @@
+package org.duniter.elasticsearch.user;/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import org.apache.commons.io.FileUtils;
+import org.elasticsearch.bootstrap.Elasticsearch;
+import org.junit.runner.Description;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class TestResource extends org.duniter.core.test.TestResource {
+
+    private static final Logger log = LoggerFactory.getLogger(TestResource.class);
+
+    public static TestResource create() {
+        return new TestResource(null);
+    }
+    
+    public static TestResource create(String configName) {
+        return new TestResource(configName);
+    }
+
+    private TestFixtures fixtures = new TestFixtures();
+
+    private TestConfiguration testConfiguration;
+
+    protected TestResource(String configName) {
+        super(configName);
+    }
+    
+    protected void before(Description description) throws Throwable {
+        super.before(description);
+
+        // Prepare ES home
+        File esHomeDir = getResourceDirectory("es-home");
+
+        System.setProperty("es.path.home", esHomeDir.getCanonicalPath());
+
+        FileUtils.copyDirectory(new File("src/test/es-home"), esHomeDir);
+        FileUtils.copyDirectory(new File("target/classes"), new File(esHomeDir, "plugins/duniter4j-es-user"));
+
+        Elasticsearch.main(new String[]{"start"});
+
+        // Init a configuration
+        testConfiguration = new TestConfiguration(getConfigFileName());
+
+    }
+
+    public TestFixtures getFixtures() {
+        return fixtures;
+    }
+
+    public TestConfiguration getConfiguration() {
+        return testConfiguration;
+    }
+
+    /* -- protected method -- */
+
+    protected String getConfigFilesPrefix() {
+        return "duniter4j-es-user-test";
+    }
+
+}
diff --git a/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/service/SynchroServiceTest.java b/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/service/SynchroServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3340a5f4b2baa1d6bd99af66fab3310e9360285f
--- /dev/null
+++ b/duniter4j-es-user/src/test/java/org/duniter/elasticsearch/user/service/SynchroServiceTest.java
@@ -0,0 +1,58 @@
+package org.duniter.elasticsearch.user.service;
+
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.elasticsearch.model.SynchroResult;
+import org.duniter.elasticsearch.service.ServiceLocator;
+import org.duniter.elasticsearch.user.TestResource;
+import org.junit.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Created by blavenie on 13/09/17.
+ */
+public class SynchroServiceTest {
+
+    private static final Logger log = LoggerFactory.getLogger(SynchroServiceTest.class);
+
+    @ClassRule
+    public static final TestResource resource = TestResource.create();
+
+    private SynchroService service;
+    private Peer peer;
+
+    @Before
+    public void setUp() throws Exception {
+        service = ServiceLocator.instance().getBean(SynchroService.class);
+        peer = new Peer.Builder()
+                .setHost(resource.getConfiguration().getDataSyncHost())
+                .setPort(resource.getConfiguration().getDataSyncPort()).build();
+
+        while(!service.isReady()) {
+            Thread.sleep(1000);
+        }
+
+        Thread.sleep(5000);
+    }
+
+    @Test
+    public void synchronizeUser() throws Exception {
+
+        SynchroResult result = new SynchroResult();
+        long fromTime = 0L;
+
+        service.synchronizeUser(peer, fromTime, result);
+
+        Assert.assertTrue(result.getInserts() > 0);
+
+    }
+
+    @Test
+    @Ignore
+    public void startNode() throws Exception {
+
+        while(true) {
+            Thread.sleep(10000);
+        }
+    }
+}
diff --git a/duniter4j-es-user/src/test/resources/duniter4j-es-user-test.properties b/duniter4j-es-user/src/test/resources/duniter4j-es-user-test.properties
new file mode 100644
index 0000000000000000000000000000000000000000..71ab38ca3edad272060c306434e27a2873158570
--- /dev/null
+++ b/duniter4j-es-user/src/test/resources/duniter4j-es-user-test.properties
@@ -0,0 +1,5 @@
+# This is options only used by TestRessource.getTestConfig().
+# For ES config, see files inside 'src/test/es-home/config'
+
+duniter4j.data.sync.host=g1-test.data.duniter.fr
+duniter4j.data.sync.port=443
\ No newline at end of file
diff --git a/duniter4j-es-user/src/test/resources/log4j.properties b/duniter4j-es-user/src/test/resources/log4j.properties
new file mode 100644
index 0000000000000000000000000000000000000000..2712b72e0f06c247e8b96a4b1265f95105fda739
--- /dev/null
+++ b/duniter4j-es-user/src/test/resources/log4j.properties
@@ -0,0 +1,18 @@
+###
+# Global logging configuration
+log4j.rootLogger=ERROR, stdout
+
+# Console output
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p (%c:%L) - [%t] %m%n
+
+# duniter4j levels
+log4j.logger.org.duniter=INFO
+#log4j.logger.org.duniter=DEBUG
+log4j.logger.org.duniter.core=WARN
+log4j.logger.org.duniter.elasticsearch=DEBUG
+
+# Other frameworks levels
+log4j.logger.org.elasticsearch=INFO
+
diff --git a/duniter4j-es-user/src/test/resources/services/org.duniter.core.beans.Bean b/duniter4j-es-user/src/test/resources/services/org.duniter.core.beans.Bean
new file mode 100644
index 0000000000000000000000000000000000000000..6613b03e0d6f746558b3ac98f065d36d74cf3411
--- /dev/null
+++ b/duniter4j-es-user/src/test/resources/services/org.duniter.core.beans.Bean
@@ -0,0 +1,13 @@
+org.duniter.core.client.service.bma.BlockchainRemoteServiceImpl
+org.duniter.core.client.service.bma.NetworkRemoteServiceImpl
+org.duniter.core.client.service.bma.WotRemoteServiceImpl
+org.duniter.core.client.service.bma.TransactionRemoteServiceImpl
+org.duniter.core.service.Ed25519CryptoServiceImpl
+org.duniter.core.service.MailServiceImpl
+org.duniter.core.client.service.HttpServiceImpl
+org.duniter.core.client.service.DataContext
+org.duniter.core.client.service.local.PeerServiceImpl
+org.duniter.core.client.service.local.CurrencyServiceImpl
+org.duniter.elasticsearch.dao.impl.CurrencyDaoImpl
+org.duniter.elasticsearch.dao.impl.PeerDaoImpl
+org.duniter.elasticsearch.dao.impl.BlockDaoImpl
diff --git a/pom.xml b/pom.xml
index 97d2966314d0171b16114d273c7404d17fe0bcd7..9896ec4e44a7831550b35646ca168e79c9f4a478 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,6 +7,10 @@
   <packaging>pom</packaging>
   <name>Duniter4j : a Duniter Java Client API</name>
 
+  <prerequisites>
+    <maven>3.1.1</maven>
+  </prerequisites>
+
   <properties>
     <!-- source file encoding -->
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -279,7 +283,6 @@
         <artifactId>tyrus-container-grizzly-server</artifactId>
         <version>${tyrus.version}</version>
       </dependency>
-
     </dependencies>
   </dependencyManagement>
 
@@ -449,6 +452,29 @@
 
       </plugins>
     </pluginManagement>
+
+    <plugins>
+      <plugin>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>enforce-maven</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <requireMavenVersion>
+                  <version>3.1.1</version>
+                </requireMavenVersion>
+              </rules>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+
   </build>
 
   <!-- Repositories needed to find the dependencies -->