Let's admit it: the speed of this blog sucks. I've been trying to ignore it for a while, because I was thinking my code was perfect ๐ but it got too bad to ignore. Load times were taking up to 4 seconds, and it was embarrassing.
In this blog post, I'd like to take you through that journey and speak on the lessons I learned along the way.
4 seconds? That's an eternity!
I know, and I agree. So I had to ask Gemini1 about it2.
Me: "Why is my Flask app slow?"
Gemini: "Because you made it that way. Have you tried profiling it?"
Me: "Profiling? You mean analyze its performance to identify bottlenecks and areas for optimization? Why would I want to do that?" ๐ค
Gemini: ๐
Me: ๐ "Oh yeah, you right. I'll get right to it."
So I downloaded a profiler using pip
called Flask-Profiler
, only to eventually find out that there is a built-in profiler in Flask ๐คฆโโ๏ธ๐พ. I eventually figure out that my website takes 4 seconds to load โ on localhost3! So I turned to my AI genie once more:
Me: "Gemini, help! The profiler said my app takes 4 seconds. How long should it take for my website to load on localhost?"
Gemini: ... ๐ค ... "It should take your website 300 - 500 milliseconds at most to load. You must really suck at making websites."
Me: ๐ "Thanks for the compliment. So what can I do to make it faster, wise guy?"
Gemini: ... ๐ค ... "Have you tried the Flask-Caching
library for Python?"
Me: ๐ฏ "What's that?"
Gemini: "Flask-Caching is a Flask extension that adds caching to your application. It stores the results of functions, like database queries or rendered HTML pages, in a fast storage system. The next time a request is made for the same content, it's served directly from the cache instead of being regenerated, which significantly speeds up your application and reduces the load on your server."
Me: ๐คฏ
I rush to download the library and create two caches in my Flask app, a memory cache for storing relatively constant data in my computer's memory, and a file cache for storing data like rendered HTML pages on my file system. Now with all confidence, I restart the server and refresh the home page. I take a look at my results on the qutebrowser's devtools.
Me: "What? It's still taking 1 second to load. I mean, that's a significant increase, down to a quarter of the initial time taken, but why still so slow? ๐ฉ"
Gemini: ... ๐ค ... "If you are using the Flask-Caching library and it's still that slow, the situation is worse than I thought. At this point, it's not Python's fault, it's not Flask's fault, it's not Flask-Caching's fault, it's completely on you! You may want to take a look at what you've coded."
Me: "Fine, how do I measure durations of functions in Python?"
Gemini: ... ๐ค ... "You could use the time.perf_counter()
from the time
library. It has ..."
Me: (Stops prompt.) Say less. ๐ค
I go down the rabbit hole of my own code ... only to eventually find the culprits, each of them taking at least 0.91 seconds to load:
def index(page=1):
try:
page = int(page)
db = Database(cache=DataCache()
if Config.env.data_cache_enabled
else None)
posts = db.posts.previews.get(page=page)
if len(posts) == 0 and page > 1:
raise RuntimeError(f"No posts for page {page}.")
return render_template("index.html",
title=Config.SITE_TITLE,
cache_hash=Config.env.cache_hash,
heading=Config.SITE_HEADING,
subheading=Config.SITE_SUBHEADING,
base_url=Config.BASE_URL,
og_title=Config.OG_TITLE,
og_image=Config.OG_IMAGE,
og_url=Config.BASE_URL,
header=Header(),
footer=Footer(page_count=db.posts.page_count,
rss_visible=True),
posts=posts,
tags=db.posts.tags.get(), # <- CULPRIT #1
history=db.posts.history.get()) # <- CULPRIT #2
except RuntimeError:
abort(404)
The db.posts.tags.get()
and db.posts.history.get())
functions were to blame! The ironic thing about it is that my blog doesn't even display them on any page. They were features I was planning to add to the early design of my blog but I later decided to keep it simple and leave them out of the webpage. Unfortunately I forgot to take the queries out.
What's worse is that they both would keep getting slower and slower and dragging the page load longer and longer with every new post, because the functions had to go through every blog post file.
After deleting those functions from the file, pages loads were way faster, ranging from 500 - 800 milliseconds for the home page and 200 - 300 milliseconds for a blog post. These are not the most ideal of speeds but it's way better than 4 seconds!
Bottom line
I've learned a couple lessons:
- Don't just assume, measure. That's the only way to keeping your website fast. You never know what could be hiding in your code.
- Caching can't save you. Find out the root cause to issues.
- Don't look down on SSGs. I used to think that they were somewhat redundant and unnecessary because they create a whole bunch of static files that need to be diffed later. Now I know they have their place. In fact, save yourself the stress and use one!4
-
I used to use ChatGPT more often, but now they are asking me to sign in before I use it at all. I don't want to give everyone my information (just in case of a data breach), so I use Google's AI, Gemini. Wise decision? ๐คทโโ๏ธ๐พย โฉ
-
Of course, this is an exaggerated (partly fictional) take on the AI ๐.ย โฉ
-
For those who are unaware, localhost doesn't go through the Internet at all. It means the website was slow not including the time it takes to get from the server to the browser. TLDR: it was crawling.ย โฉ
-
I will stick to using Flask and caching since I like pain โ I'm a masochist ๐.ย โฉ