Repository: translate/pootle

Issue:
Elasticsearch TM pings too much
Elasticsearch is involved way too much and way too early.  If you set logging for Elasticsearch into `DEBUG` then you will see that we ping the Elasticsearch server often.  We do want to ping the server to know its alive and take appropriate action e.g. disable localTM, but we don't want to do that all the time.  At the moment I believe each query might actually perform a ping.  I suspect actually that for each unit we have 4 queries, one each for `external-translations` and `translation` and another two to ping each of those. And would suspect we may ping each time we import the TM modules.



Discussion:

Resolving patch:
diff --git a/pootle/apps/pootle_store/models.py b/pootle/apps/pootle_store/models.py
--- a/pootle/apps/pootle_store/models.py
+++ b/pootle/apps/pootle_store/models.py
@@ -56,6 +56,15 @@
                    parse_pootle_revision)
 
 
+TM_BROKER = None
+
+
+def get_tm_broker():
+    global TM_BROKER
+    if TM_BROKER is None:
+        TM_BROKER = SearchBroker()
+    return TM_BROKER
+
 #
 # Store States
 #
@@ -215,9 +224,6 @@ def stringcount(string):
         return 1
 
 
-TMServer = SearchBroker()
-
-
 class UnitManager(models.Manager):
 
     def get_queryset(self):
@@ -915,10 +921,11 @@ def update_tmserver(self):
                 'email_md5': md5(self.submitted_by.email).hexdigest(),
             })
 
-        TMServer.update(self.store.translation_project.language.code, obj)
+        get_tm_broker().update(self.store.translation_project.language.code,
+                               obj)
 
     def get_tm_suggestions(self):
-        return TMServer.search(self)
+        return get_tm_broker().search(self)
 
 ##################### TranslationUnit ############################
 
diff --git a/pootle/checks.py b/pootle/checks.py
--- a/pootle/checks.py
+++ b/pootle/checks.py
@@ -274,16 +274,48 @@ def check_settings(app_configs=None, **kwargs):
                     id="pootle.C008",
                 ))
             elif settings.POOTLE_TM_SERVER[server]['INDEX_NAME'] in tm_indexes:
-                errors.append(checks.Warning(
+                errors.append(checks.Critical(
                     _("Duplicate '%s' INDEX_NAME in POOTLE_TM_SERVER.",
                       settings.POOTLE_TM_SERVER[server]['INDEX_NAME']),
                     hint=_("Set different INDEX_NAME for all servers in "
                            "POOTLE_TM_SERVER."),
-                    id="pootle.W018",
+                    id="pootle.C009",
                 ))
             else:
                 tm_indexes.append(settings.POOTLE_TM_SERVER[server]['INDEX_NAME'])
 
+            if 'ENGINE' not in settings.POOTLE_TM_SERVER[server]:
+                errors.append(checks.Critical(
+                    _("POOTLE_TM_SERVER['%s'] has no ENGINE.", server),
+                    hint=_("Set a ENGINE for POOTLE_TM_SERVER['%s'].",
+                           server),
+                    id="pootle.C010",
+                ))
+
+            if 'HOST' not in settings.POOTLE_TM_SERVER[server]:
+                errors.append(checks.Critical(
+                    _("POOTLE_TM_SERVER['%s'] has no HOST.", server),
+                    hint=_("Set a HOST for POOTLE_TM_SERVER['%s'].",
+                           server),
+                    id="pootle.C011",
+                ))
+
+            if 'PORT' not in settings.POOTLE_TM_SERVER[server]:
+                errors.append(checks.Critical(
+                    _("POOTLE_TM_SERVER['%s'] has no PORT.", server),
+                    hint=_("Set a PORT for POOTLE_TM_SERVER['%s'].",
+                           server),
+                    id="pootle.C012",
+                ))
+
+            if 'MIN_SCORE' not in settings.POOTLE_TM_SERVER[server]:
+                errors.append(checks.Critical(
+                    _("POOTLE_TM_SERVER['%s'] has no MIN_SCORE.", server),
+                    hint=_("Set a MIN_SCORE for POOTLE_TM_SERVER['%s'].",
+                           server),
+                    id="pootle.C013",
+                ))
+
             if ('WEIGHT' in settings.POOTLE_TM_SERVER[server] and
                 not (0.0 <= settings.POOTLE_TM_SERVER[server]['WEIGHT'] <= 1.0)):
 
diff --git a/pootle/core/search/backends/elasticsearch.py b/pootle/core/search/backends/elasticsearch.py
--- a/pootle/core/search/backends/elasticsearch.py
+++ b/pootle/core/search/backends/elasticsearch.py
@@ -9,58 +9,66 @@
 
 from __future__ import absolute_import
 
+import logging
+
+
 __all__ = ('ElasticSearchBackend',)
 
 try:
     from elasticsearch import Elasticsearch
-    from elasticsearch.exceptions import ConnectionError
+    from elasticsearch.exceptions import ElasticsearchException
 except:
     Elasticsearch = None
 
 from ..base import SearchBackend
 
 
+logger = logging.getLogger(__name__)
+
+
 class ElasticSearchBackend(SearchBackend):
     def __init__(self, config_name):
         super(ElasticSearchBackend, self).__init__(config_name)
         self._es = self._get_es_server()
         self._create_index_if_missing()
-        if self._es is not None:
-            self.weight = min(max(self._settings.get('WEIGHT', self.weight),
-                                  0.0), 1.0)
-
-    def _server_setup_and_alive(self):
-        if self._es is None:
-            return False
-        try:
-            return self._es.ping()
-        except ConnectionError:
-            return False
+        self.weight = min(max(self._settings.get('WEIGHT', self.weight),
+                              0.0), 1.0)
 
     def _get_es_server(self):
-        if self._settings is None or Elasticsearch is None:
-            return None
         return Elasticsearch([
             {'host': self._settings['HOST'],
              'port': self._settings['PORT']},
         ])
 
     def _create_index_if_missing(self):
-        if self._server_setup_and_alive():
+        try:
             if not self._es.indices.exists(self._settings['INDEX_NAME']):
                 self._es.indices.create(self._settings['INDEX_NAME'])
+        except ElasticsearchException as e:
+            self._log_error(e)
 
     def _is_valuable_hit(self, unit, hit):
         return str(unit.id) != hit['_id']
 
-    def search(self, unit):
-        if not self._server_setup_and_alive():
-            return []
+    def _es_call(self, cmd, *args, **kwargs):
+        try:
+            return getattr(self._es, cmd)(*args, **kwargs)
+        except ElasticsearchException as e:
+            self._log_error(e)
+            return None
+
+    def _log_error(self, e):
+        logger.error("Elasticsearch error for server(%s:%s): %s"
+                     % (self._settings.get("HOST"),
+                        self._settings.get("PORT"),
+                        e))
 
+    def search(self, unit):
         counter = {}
         res = []
         language = unit.store.translation_project.language.code
-        es_res = self._es.search(
+        es_res = self._es_call(
+            "search",
             index=self._settings['INDEX_NAME'],
             doc_type=language,
             body={
@@ -75,6 +83,10 @@ def search(self, unit):
             }
         )
 
+        if es_res is None:
+            # ElasticsearchException - eg ConnectionError.
+            return []
+
         for hit in es_res['hits']['hits']:
             if self._is_valuable_hit(unit, hit):
                 translation_pair = hit['_source']['source'] + hit['_source']['target']
@@ -103,10 +115,10 @@ def search(self, unit):
         return res
 
     def update(self, language, obj):
-        if self._server_setup_and_alive():
-            self._es.index(
-                index=self._settings['INDEX_NAME'],
-                doc_type=language,
-                body=obj,
-                id=obj['id']
-            )
+        self._es_call(
+            "index",
+            index=self._settings['INDEX_NAME'],
+            doc_type=language,
+            body=obj,
+            id=obj['id']
+        )


Test patch: