1

One of my webpages takes about 3 seconds to load locally, and 15 seconds to load when it's live on Heroku. I believe the problem is how many synchronous Google TTS (Text-To-Speech) API calls and synchronous database / Amazon S3 writes I make.

I think asynchronous coding would help, but I'm not entirely sure how to implement it. Here's what's happening in the view:

# views.py 

def convert_str_to_audio_info_if_necessary(audio_str):
    audio_info =  AudioInfo.objects.get_by_text(audio_str)
    if audio_info is None:
        audio_content = synthesize_text(audio_str) # returns audio file from Google
        # WAIT for the response to come back from Google's API
        new_audio = ContentFile(audio_content, 'audio.wav') # converts to file Python can read
        audio_info = AudioInfo.objects.create_problem_audio(text=audio_str, audio=new_audio, duration=get_audio_file_duration(new_audio))
        # WAIT for the audio file to be written to my S3 bucket
    return audio_info

def slow_loading_view(request):
    for i in range(100):
        audio_str = str(i)
        audio_info = convert_str_to_audio_info_if_necessary(audio_str)
        context[audio_str] = audio_info
    # Now I would like to pass this data in my context to use in the webpage
    return render(request, 'my_page.html', context)

And my in my model:

# models.py

class AudioInfoManager(models.Manager):

    def get_by_text(self, text):
        qs = self.get_queryset().filter(text=text) 
        if len(qs) == 0:
            return None
        return qs[0]

    def create_problem_audio(self, text, audio, duration):
        already_created_entry = self.get_by_text(text)
        if already_created_entry != None:
            return already_created_entry
        problem_audio = self.create(text=text, audio=audio, duration=duration)
        return problem_audio


class AudioInfo(models.Model):
    text            = models.TextField(unique=True)
    audio           = models.FileField(upload_to=upload_audio_info_path)
    duration        = models.FloatField()

    objects         = AudioInfoManager()

As you can see, there's a lot of waiting (idle time) going on in the view, so ideally i'd be able to 1) asynchronously send all the google API requests to generate the audio files, and then after I have all those audio files returned 2) asynchronously write those audio files to the database and S3 buckets, and then once they are all written, 3) query the database for their data and pass the data as context to render my webpage.

It seems like all the asynchronous libraries in Django such as Celery and Redis Queue only help with background tasks (tasks that aren't required to be completed before rendering the view's webpage, e.g. sending emails, writing data to a database that isn't needed in the webpage, etc.). Perhaps asyncio or Django Channels are solutions? Django is a synchronous framework, so I'm not sure whether I'm allowed to update the database asynchronously.

Any suggestions for what I should do?

user3562967
  • 151
  • 3
  • 12
  • Where is the text coming from? Is it different every time, or could it be pre computed? – Daniel Roseman Jan 03 '19 at 12:02
  • The text comes from user inputs. Sometimes it's the same as a previous input and sometimes it's different. In the case where it's the same, I simply retrieve an audio file associated with the text I've previously saved to the database. In the case where it's different I generate the audio file and save it to the database for future and current use. So for example if you enter "Hi my name is Bob.", and no one else has ever entered that, I'll have to generate the audio file and save it. But if the next user enters "Hi my name is Bob." I will already have that file ready to go. – user3562967 Jan 03 '19 at 12:04

1 Answers1

0

I agree, this does not seem like a use case for a task queue given the current implementation.

As you mentioned, the writes are indeed what's taking the most time.

There are a few different approaches and they can be used in combination:

  • in-mem cache to avoid re-computation (Redis, Memcached)
  • handle on the UI (one action to transform to audio, another action with loading bar during DB write)
  • async writes (gather audio files to write, send off, and communicate to user that writes in process); I'd reach for the asyncio library associated with the release of Python 3.7 if you're able to use newer releases of Django that support async.

UI-wise, could look something like adding multiple attachments in Gmail.

Zach Valenta
  • 1,783
  • 1
  • 20
  • 35
  • I saw someone try to do that here: https://stackoverflow.com/questions/44667242/python-asyncio-in-django-view There is a comment at bottom saying "Django is a synchronous framework so you can't use any async/await in the views because of there no loop or something like that." Not sure if that's right. I know Django Channels (which may be a solution as well) makes a big deal about using async_to_sync, especially for database updates – user3562967 Jan 03 '19 at 11:59
  • Good catch! I updated my answer to specify which versions of Django I had in mind. – Zach Valenta Jan 03 '19 at 14:14
  • Thanks! I'm trying out asyncio, and I'm getting an error message of "object SynthesizeSpeechResponse can't be used in 'await' expression". I don't have too much familiarity with asyncio, so it may be an error on my part as opposed to Google forbidding it. However, I'm starting to think that the database writes of the audio files are what takes up the majority of the time. Can asyncio be used to write the audio files to my database / S3 Bucket asynchronously? – user3562967 Jan 03 '19 at 20:08
  • Could you give an example of how you'd use asyncio to write to the database? I updated the question to include my AudioInfo model that I'm using to store the audio text, audio file, and audio duration. The upload_audio_info_path is a function that maps the file to my S3 bucket. – user3562967 Jan 04 '19 at 07:51
  • I'm wondering if I need to use a Thread Pool, as described in this post: https://stackoverflow.com/questions/39952799/python-3-5-async-await-with-real-code-example – user3562967 Jan 04 '19 at 08:59