diff --git a/Dockerfile b/Dockerfile index ac0a3bb..fc3f85b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.8.12-slim-buster -# YOUR COMMANDS HERE -# .... -# .... +WORKDIR /TelegramAI +COPY . . +RUN pip install -r requirements.txt CMD ["python3", "app.py"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ac0069 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# AWS Ex1 - Provision the TelegramAI app in AWS + +## Background + +Your goal is to provision the TelegramAI chat app as a scalable, micro-services architecture in AWS. + +Here is a high level diagram of the architecture: + +![](botaws2.png) + +## Scaling the app + +When end-users send a message via Telegram app (1-black), the messages are served by the Bot service (run as a Docker container in the Telegram Bot EC2 instance). +The Bot service **doesn't** download the video from YouTube itself, otherwise, all it does is sending a "job" to an SQS queue (2-black), and return the end-user a message like "your video is being downloaded...". +So, the Bot service is a very lightweight app that can serve hundreds requests per seconds. +In the other side, there are Worker service (run as a Docker container in the Worker EC2 instance) **consumes** jobs from the SQS queue (3-black) and does the hard work - to download the video from YouTube and upload it to S3 (4-black). When the Worker done with current job, it asks the SQS queue if it has another job for him. As long as there are jobs pending in the queue, a free Worker will consume and perform the job. In such way the Bot service pushes jobs to the SQS queue, making it "full", while the Worker service consumes jobs from the queue, making it "empty". + +But what if the rate in which the Bot service is pushing jobs to the queue is much higher than the rate the Worker completing jobs? In such case the queue will overflow... +To solve that, we will create multiple workers that together consume jobs from the queue. How many workers? we will deploy a dynamic model **that auto-scale the number of workers** depending on the number of messages in the queue. +When there are a lot of jobs in the queue, the autoscaler will provision many workers. +When there are only a few jobs in the queue, the autoscaler will provision fewer workers. +The Workers are part of an AutoScaling group, which is scaled in and out by a custom metric that the Metric Sender service (run as a Docker container as well, on the same VM as the Bot service) writes to CloudWatch every 1 minute (1-blue). CloudWatch will trigger an autoscale event (2-blue) when needed, which results in provisioning of another Worker instance, or terminate a redundant Worker instance (3-blue). + +The metric sent to CloudWatch can be called `BacklogPerInstance`, as it represents the number of jobs in the queue (jobs that was not consumed yet) per Worker instance. +For example, assuming you have 5 workers up and running, and 100 messages in the queue, thus `BacklogPerInstance` equals 20, since each Worker instance has to consume ~20 messages to get the queue empty. For more information, read [here](https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-using-sqs-queue.html). + +### The TelegramAI repo structure + +The repository structure is divided into services - each service under its own directory, while all services are sharing common files (`config.json` and `utils.py`) under the root directory of the repo. + +1. `bot/app.py` - The Telegram bot code, similar to what you've implemented in the previous exercise. But this time, the bot doesn't download the videos itself, but sends a "job" to an SQS queue. +2. `worker/app.py` - The Worker service continuously reads messages from the SQS queue and process them, which means download the video from YouTube and store it in a dedicated S3 bucket. +3. `metric-sender/app.py` - The Metric Sender service calculates the `backlog_per_instance` metric and send it to CloudWatch. + +Each service has its own `Dockerfile` under the service's directory. +## Diagram of the lambada architecture: + +![](botaws3.png) diff --git a/app.py b/app.py index 45e12d7..dc7f997 100644 --- a/app.py +++ b/app.py @@ -45,8 +45,10 @@ def download_user_photo(self, quality=0): file_info = self.bot.get_file(self.current_msg.photo[quality].file_id) data = self.bot.download_file(file_info.file_path) + with open(file_info.file_path,'wb') as p: + p.write(data) + - # TODO save `data` as a photo in `file_info.file_path` path def handle_message(self, message): """Bot Main message handler""" @@ -61,14 +63,22 @@ def handle_message(self, message): class YoutubeBot(Bot): - pass + def handle_message(self, message): + if self.is_current_msg_photo(): + self.download_user_photo(quality=3) + return + + video = search_download_youtube_video(message.text) + self.send_text(video[0].get("url")) if __name__ == '__main__': with open('.telegramToken') as f: _token = f.read() +<<<<<<< HEAD + my_bot = YoutubeBot(_token) +======= my_bot = Bot(_token) +>>>>>>> upstream/main my_bot.start() - - diff --git a/botaws2.png b/botaws2.png new file mode 100644 index 0000000..53b6339 Binary files /dev/null and b/botaws2.png differ diff --git a/botaws3.png b/botaws3.png new file mode 100644 index 0000000..4329d18 Binary files /dev/null and b/botaws3.png differ