Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catch ConnectionError with Django-Haystack and ElasticSearch

I'm currently using django-haystack and elasticsearch in a project and all works as expected when elasticsearch is running.

Haystack Settings:

HAYSTACK_CONNECTIONS = {
'default': {
    'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
    'URL': 'http://127.0.0.1:9200/',
    'INDEX_NAME': 'haystack',
},
}

HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

I'm using RealtimeSignalProcessor for a realtime index update.

The problem comes when elasticsearch is down, because trying to add/update any object gives us the following error:

ConnectionError(('Connection aborted.', error(111, 'Connection refused'))) caused by: ProtocolError(('Connection aborted.', error(111, 'Connection refused')))

Is there a way of catch/manage that error?

It would be useful in production environment in order to allow users to add/update objects without crashing, when elasticsearch is down.

Thanks in advance.

like image 415
g-abello Avatar asked Apr 01 '26 00:04

g-abello


2 Answers

I suggest you subclass ElasticSearchBackend and wrap the update, remove and clear methods around a decorator that captures the exceptions. That way you keep elasticsearch features, but you are able to override the behaviour of them.

I use to wrap them with a decorator, a mute-error one:

def mute_error(f):      
    def error_wrapper(*args, **kwargs):  
        try:  
            return f(*args, **kwargs)  
        except:
            print('Connection Error')    

    return error_wrapper

Then add it to your project, configure the HAYSTACK_BACKEND:

HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://127.0.0.1:9200/',
        'INDEX_NAME': 'haystack',
    },
    'robust_elasticsearch':{
        'ENGINE': 'YOURAPP.backend.RobustElasticSearchEngine',
        'URL': 'http://127.0.0.1:9200/',
        'INDEX_NAME': 'haystack',
    }
}

Have a look at django-haystack documentation. You should also create a subclass of BaseEngine, this is not properly documented.

Here it is the code:

from django.utils.decorators import method_decorator
from haystack.backends.elasticsearch_backend import ElasticsearchSearchBackend, ElasticsearchSearchEngine
from haystack.backends import BaseEngine
from haystack.backends import log_query
from urllib3.exceptions import ProtocolError, ConnectionError


class RobustElasticSearchBackend(ElasticsearchSearchBackend):
    """A robust backend that doesn't crash when no connection is available"""

    def mute_error(f):

        def error_wrapper(self, *args, **kwargs):
            try:
                return f(self, *args, **kwargs)
            except TransportError:
                self.log.warn('Connection Error: elasticsearch communication error') 
        return error_wrapper

    def __init__(self, connectionalias, **options):
        super(RobustElasticSearchBackend, self).__init__(connectionalias, **options)

    @mute_error
    def update(self, indexer, iterable, commit=True):
        super(RobustElasticSearchBackend, self).update(indexer, iterable, commit)

    @mute_error
    def remove(self, obj, commit=True):
        super(RobustElasticSearchBackend, self).remove(obj, commit)

    @mute_error
    def clear(self, models=[], commit=True):
        super(RobustElasticSearchBackend, self).clear(models, commit)

class RobustElasticSearchEngine(ElasticsearchSearchEngine):
    backend = RobustElasticSearchBackend

We are just overriding the engine, and not the SearchQuery subclass, since the one provided by elasticsearch class by default is enough for us, by now.

like image 100
miceno Avatar answered Apr 03 '26 13:04

miceno


To make this work for my setup I had to

import elasticsearch

and edit to:

def mute_error(f):

    def error_wrapper(self, *args, **kwargs):
        try:
            return f(self, *args, **kwargs)
        except elasticsearch.TransportError:
            self.log.warn('Connection Error: elasticsearch communication error')
    return error_wrapper
like image 43
phil Avatar answered Apr 03 '26 13:04

phil



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!