Repository: gpodder/mygpo

Issue:
Slow podcast search with short query string
When searching for podcasts with a very short query string, the search takes a very long time, sometimes even times out, and most likely doesn't return any meaningful result.

Example: https://gpodder.net/search/?q=s

The search should return an empty result (and possibly an error message) if the query string is too short.


Discussion:
This could be implemented in `mygpo.search.index.search_podcasts()`, by checking the length of the (non-whitespace parts of the) query string, and returning an [empty result set](https://docs.djangoproject.com/en/dev/ref/models/querysets/#none) or raising an exception.

https://github.com/gpodder/mygpo/blob/52e7af4021d64a07bb26899aba8303abccd6767b/mygpo/search/index.py#L24-L46

Resolving patch:
diff --git a/mygpo/search/index.py b/mygpo/search/index.py
--- a/mygpo/search/index.py
+++ b/mygpo/search/index.py
@@ -23,6 +23,9 @@
 
 def search_podcasts(query):
     """ Search for podcasts according to 'query' """
+    if is_query_too_short(query):
+        logger.debug('Found no podcasts for "{query}". Query is too short', query=query)
+        return Podcast.objects.none()
 
     logger.debug('Searching for "{query}" podcasts"', query=query)
 
@@ -44,3 +47,7 @@ def search_podcasts(query):
     )
 
     return results
+
+
+def is_query_too_short(query):
+    return len(query.replace(" ", "")) <= settings.QUERY_LENGTH_CUTOFF
diff --git a/mygpo/settings.py b/mygpo/settings.py
--- a/mygpo/settings.py
+++ b/mygpo/settings.py
@@ -369,6 +369,11 @@ def get_intOrNone(name, default):
 
 SEARCH_CUTOFF = float(os.getenv('SEARCH_CUTOFF', 0.3))
 
+# Maximum non-whitespace length of search query
+# If length of query is shorter than QUERY_LENGTH_CUTOFF, no results
+# will be returned to avoid a server timeout due to too many possible
+# responses
+QUERY_LENGTH_CUTOFF = int(os.getenv('QUERY_LENGTH_CUTOFF', 3))
 
 ### Sentry
 


Test patch:
diff --git a/mygpo/search/tests.py b/mygpo/search/tests.py
--- a/mygpo/search/tests.py
+++ b/mygpo/search/tests.py
@@ -3,6 +3,7 @@
 
 from mygpo.podcasts.models import Podcast
 from django.contrib.postgres.search import SearchVector
+from django.test.utils import override_settings
 
 from .index import search_podcasts
 from .tasks import update_search_index
@@ -28,3 +29,28 @@ def test_search_podcast(self):
         # search for the podcast
         results = search_podcasts('awesome')
         self.assertEqual(results[0].id, podcast.id)
+
+    @override_settings(QUERY_LENGTH_CUTOFF=3)
+    def test_shortest_search_podcast(self):
+        """
+         Search for a podcast with query length smaller than 3
+         With QUERY_LENGTH_CUTOFF = 3 
+         Server would normally time out, however Podcasts exist for the given 
+         search term.
+        """
+        # create a podcast
+        podcast = Podcast(
+            id=uuid.uuid1(),
+            title='The Tricky Podcast',
+            description='The only podcast containing tricky messages.',
+        )
+        podcast.save()
+
+        # explicitly trigger a search index update
+        update_search_index()
+
+        results = search_podcasts('The')
+        self.assertEqual(len(results), 0)
+
+        results = search_podcasts('The Tricky')
+        self.assertEqual(results[0].id, podcast.id)