blog

Speeding up a Django view

Step by step insights into cutting the email view's TTFB by 70%

Introduction

Buttondown is a Django app for sending emails, mostly beloved by technical users who like its REST API and Markdown support. Like most other email platforms, it also hosts archives of sent emails, and these archives process a millions of pageviews every month in traffic.

These archive pages are classical Django detail views: here is an example of one. You can see from the URL that the contract is pretty simple: given a username and a slug, return the email. Simple enough!

I noticed that Google Search Console was dinging Buttondown for a slow TTFB: Buttondown's archive pages were averaging around 1.2s, and the guidance is to be below one second. These views are pretty uncomplicated, so I dove in with Django Debug Toolbar (ol' faithful) to see what was happening:

Nine queries! Yikes! DDT is kind enough to provide all of the information you really need: what queries are being run, which ones are repeated, and in what order. Let's dive in, shall we?

Removing unnecessary joins

This is obvious when you say it out loud: the easiest queries to optimize are the ones that you can straight up remove because they're entirely unnecessary. In this case, I had two such queries:

  1. A query on monetization_stripeaccount
  2. A query on monetization_stripe_payment_link

Both of these pull data from Buttondown's mirrored store of Stripe data, and are entirely unnecessary on this page: we don't surface the payment link directly to users on this route, nor do we need information about the author's Stripe account. So we remove them from the serializer that was pulling them in:

class PublicNewsletterSerializer(serializers.ModelSerializer):
-     stripe_account = serializers.SerializerMethodField()
-     stripe_payment_link = serializers.SerializerMethodField()

...

-    def get_stripe_payment_link(self, obj: Newsletter) -> str:
-        if not obj.stripe_payment_link:
-            return ""
-        return obj.stripe_payment_link.data["url"]

-    def get_stripe_account(self, obj: Newsletter) -> str:
-        if not obj.stripe_account:
-            return ""
-        return obj.stripe_account.account_id

...

    class Meta:
        fields = (
            ...
-            "stripe_account",
-            "stripe_payment_link",
            ...
        )

Excellent! We're already down to seven queries, as confirmed by DDT:

Caching the object on the view

DDT helpfully informs us that some of these queries are duplicated: we grab the email, newsletter, and account twice. This is because of some janky logic I have in the permissions-checking for a given email: we show a slightly different version of the email depending on who's logged in (a premium subscriber may see different data compared to an unpaid one, for instance, or the author may be able to see posts that aren't published yet.)

Rather than fetching the email twice, then, let's just cache the email the first time it's fetched:

@method_decorator(xframe_options_exempt, name="dispatch")
class EmailView(TemplateView):
    def get_template_names(self) -> List[str]:
-        return determine_template_path(self.request, self.get_object())
+        return determine_template_path(self.request, self.email)

    def get_username(self) -> str:
        if hasattr(self.request, "newsletter_for_subdomain"):
@@ -89,7 +89,8 @@ def get_object(self) -> Optional[Email]:
        email_slug = self.kwargs.get("slug")
        username = self.get_username()

-        return find_email(email_id or email_slug or "", username)
+        self.email = find_email(email_id or email_slug or "", username)
+        return self.email

    def get_context_data(self, **kwargs: Any) -> Dict:
        context = super().get_context_data(**kwargs)

That brings us down to a mere four queries:

Denorming with select_related

If you look at those top three queries, it sounds fairly obvious what they're doing:

  1. Grab the email for a given slug and username.
  2. Grab the newsletter for that email.
  3. Grab the account for that newsletter.

These are all part of the same object, so we can use Django's select_related method to pull in those foreign keys as part of the initial query.

-            return emails.get(**kwargs)
+            return emails.select_related("newsletter__owning_account").get(**kwargs)

And with that one-liner, we're down to 385ms across only two queries:

Using defer

We're down to just two queries, but one of them is fairly large — 300ms for a simple join on an index seems awfully large, right?

Well, here's a fun bit of esoterica about Buttondown's implementation: Email objects are large. They contain a bunch of different cached versions of themselves, like:

  • How the email should look as a 'teaser'
  • How the email should look on email
  • How the email should look in Markdown

and so on, to make sending emails as quick as possible. But — especially for larger emails — these add up in size, and the simple act of serializing them from Postgres to Python is non-trivially slow.

This is compounded by the fact that Django's ORM defaults to the equivalent of a select * — if you fetch a row, you're getting all of the columns included in that row.

So, as a final touch, we use Django's defer command which allows us to selectively exclude certain fields that we know we don't need (in this case, it's various versions of the email that are rendered for other views):

- return emails.select_related("newsletter__owning_account").get(**kwargs)
+ return emails.select_related("newsletter__owning_account").defer(
+    "rendered_html_for_email",
+    "rendered_html_for_web",
+    "body",
+ ).get(**kwargs)

That saves us another 100ms, bringing us down to around 300ms!

Conclusion

We started with a view that took 1050ms across nine queries and we ended up with one that took 300ms across two queries. Pretty excellent for an hour's work and ~thirty lines of code, considering this view gets hit a few million times a month.

It bears repeating that none of the techniques described above are particularly novel. None required extensive tooling beyond django-debug-toolbar; exactly zero rocket surgery was performed in the course of this optimization.

And yet! This code stuck around, unoptimized, for quite some time. There is so much performance work out there in so many applications that looks like the above: twenty-dollar bills, sitting there crumpled on the ground, waiting to be picked up.

To wit, there are other things I could probably do to get this endpoint even faster:

  • That second query is checking to see if the newsletter being fetched has any "metadata fields" associated with it. Metadata fields are a bit of an edge case feature, and only around 15% of newsletters use them; I could add a field like Newsletter.has_metadata_fields and check that before making the query, to eliminate it for the 85% of newsletters who don't use it at all.
  • The primary query to pull out an email and a newsletter would be slightly faster (~30ms) if I queried the newsletter by ID instead of username. I could keep a cache mapping usernames to IDs to take advantage of that.

And all of those are good ideas for different days — but I'm a sucker for performance work that ends up with negative lines of code added.

What customers say about Buttondown

I made the transition from MailerLite and I have no regrets. I also like that Buttondown focuses on the essentials by design and keeps me grounded and centered on what really matters.
Arthur Cendrier
Author, Thoughtful Inquiry
Overall, Buttondown has been terrific to work with and I recommend them for anybody who's thinking of starting a newsletter or moving over like I did.
Andy Magnusson
Customer Engineering Leader
Wanna know how good Buttondown as a product experience is? I upgraded to Basic before sending the first email, and then upgraded again two days later.
Zak El Fassi
Founder, Zaigood Labs
Mailchimp lost me due to their inferior product and the nightmarish merry-go-around experience with their overseas support team. Buttondown won me over with their superior product and second to none customer service.
Sav Tripodi
CEO, Sanico Software
Your support is amazing and I deeply appreciate how available and helpful you are. I LOVE being able to turn tracking pixels off. I didn't even realize this was an option when I signed up and am SO HAPPY to not track people.
Andrea Mignolo
Method & Matter
I'm also impressed with how responsive you are, and how you directly answer customers. Makes it really clear that signing up for your service was a good decision.
Nicole Tietz-Sokolskaya
Software engineer and writer
Very happy with Buttondown, works smoothly, it's very configurable and I love the minimalist design of the UI. It makes me focus on my writing. Plus, I'm super happy to support independent software and I should mention - the support I receive whenever I have a question is warm and quick :)
Martina Pugliese
Data scientist and storyteller
I just tested the RSS to Email feature for one of my blogs and it was incredibly easy to set up. It took me about 30 mins to figure out the same feature in Mailchimp.
Nicolas Bernadowitsch
Blogger
This long weekend I fulfilled a long-standing promise to myself to switch my RSS-to-email provider from Mailchimp to Buttondown, and it’s been such a great experience. It’s cheaper, more flexible, less cluttered, and it’s run by Justin Duke who is just delightful and answered a bunch of my questions over the weekend (even though I asked him to please not!).
Rian van der Merwe
Director of Product at PagerDuty
I've been wrangling half a dozen tools to get my stuff up and running recently, almost all of which had some hiccup. Buttondown had zero. It did everything I expected and needed the first time.
Catherine Cusick
Self-Employed FAQ
I, like almost everyone else I've seen talk about Buttondown, am IMMENSELY happy and impressed with your customer service. It turns out we can have nice things, which is really refreshing.
Ed Yong
Staff writer, The Atlantic
Email makes the world go ‘round, and Buttondown is how I manage it all for my keyboard projects.
Tim van Damme
Founder, MVKB
It's a truth, that should be more universally acknowledged, that Buttondown is the best newsletter software. Simple, does exactly what it sets out to do, and reasonably priced.
Noel Welsh
Founder, Inner Product
Buttondown is the perfect fit for my headless newsletter use case. And I contacted support with some specific requests and Justin responded within 30 minutes with great answers and a nice pinch of charm.
Sam Roberts
Software engineer, Tamr
Hands down the easiest way to run a newsletter - and the free version is generous!
Javeed Khatree
SEO expert
With API and Markdown support, you can build workflows that make it so easy to write.
Westley Winks
Peace Corps
I’ve never enjoyed writing newsletters as much as I do with Buttondown.
Kevin Lewis
You Got This!
Buttondown remains the easiest thing I use regularly, and I am grateful for that.
Casey
Journalist
It's a humble app doing a common job but with end users in mind.
Si Jobling
Engineering Manager
Buttondown has been an amazing experience for me. The service is constantly being improved and customer service is the best. My newsletter with Buttondown has grown from a fairly small list to over 15,000 subscribers, and it hasn't broken a sweat yet.
Cassidy Williams
CTO, Contenda
I switched over to Buttondown from Mailchimp because of the difficulty I had with Mailchimp's campaigns, so Buttondown's easy and user-friendly system has been a genuine breath of fresh air.
Jessi Eoin
Illustrator + Comic Artist
You’ve truly built a great product that I feel good about using (vs a monopoly from our tech overlords).
Rachel
2030 Camp
I love how personal Buttondown feels, especially compared to Mailchimp, Convertkit, and services like that.
Simen Strøm Braaten
Designer
This product has been exactly what I’ve needed!
Nathan Bird
Podcast host, Chattanooga Civics
It's already so refreshing compared to the mega companies.
Casey Watts
Author, Debugging Your Brain
Definitely will be using for the foreseeable future. It’s a great service and I feel well cared for. Thank you!
Phoebe Sinclair
Author
I’m a sucker for elegant UI and I really love your site, but above that I think your product has so much value for so many different people. I’m not a coder, I’m only familiar with the bare basics, but I was able to figure out and utilise Buttondown quickly.
Claudia Nathan
Founder, The Repository
The killer feature for me: Buttondown will take an RSS feed then automatically slurp up the content (in their words) and then send it to our subscribers. Job done. They seem like a good company too, so I’d say this is a winner.
Andy Bell
Founder, Set Studio
As a recent user of Buttondown, they are super on the ball. A week ago I discovered a security vulnerability and reported it on Friday afternoon. They acknowledged and fixed it in under two hours. On a Friday night! Talk about going above and beyond for your users!
Predrag Gruevski
Principal Engineer, Kensho
Well may I just say your support experience is already approximately 1 billion times better than ConvertKit. Excited to be switching!
Michael J. Metts
Author, Writing is Designing
Privacy focused sending and sign up form; lets me focus on writing - editor is "just" markdown; simple, elegant design template looks like a blog post; the founder is amazing - he's helped with every question I've had, even outside of Buttondown.
Joe Masilotti
Founder, RailsDevs
We need more nice and professional services like yours on the web.
Tobias Horvath
Designer and developer
No one is paying me to say this, but I love @buttondown so far for my lil newsletter. It’s so smart, simple, and attractive (and to my knowledge, not actively anti-trans!). Customer service is also legitimately excellent.
Julie Kliegman
Copy chief, Sports Illustrated
I love it! It lets me breathe, not compete as I write with other writers.
Devin Kate Pope
Writer and editor
It’s a pleasure working with you. Thank you! (And what a contrast with Mailchimp, where I spent two weeks and a dozen of emails trying find out why our form goes down sometimes (only sometimes), and never really got a real answer.)
Anton Sotkov
Software Engineer, IA
Buttondown exemplifies how I wish most software worked, and I hope to achieve a similar thing with the software I develop in the future.
Matt Favero
Software engineer
It feels incomparably good to be able to email just like a guy named Justin when you have a @buttondown question 15 minutes before you’re about to blast a Geistlist email. (Not a guarantee but wow this guy is human-level good.)
Jacob Ford
Designer About Town
Enter Buttondown, Justin Duke’s lovely little newsletter tool. It’s small, elegant, and integrates well. And it is also eminently affordable.
Will Buckingham
Author
Your settings page is a joy to use and everything about Buttondown makes me happy.
Gareth Jelley
Magazine editor
have been on Buttondown for ~18 months and I can't recommend it enough.
Elizabeth Minkel
Podcast host
You really do make ALL other customer service look terrible by comparison.
Chris Mead
Improv teacher
There is a caring person on the other side of this software, which is one of the things I like the most about Buttondown.
Keith Calder
Film & TV Producer
I’d also like to add that @buttondown is an absolute joy to use. Hats off, Justin!
Elliot Jay Stocks
Creative Director, Google Fonts
Shoutout to @buttondown and @jmduke for building an amazing bootstrapped product for newsletters, all while being very open to feedback and connecting directly with customers 🙏 Easily one of the most enjoyable product experiences I've had.
Den Delimarsky
Head of Ecosystem, Netlify
if you are looking for "newsletter tool for hackers" i tentatively believe the answer is @buttondown full api, compose in markdown, good docs for setting up domain auth, simple subscribe form HTML that you style yourself (or not)
Brian David Hall
Author, Your Website Sucks
I really like @buttondown as a blogging platform, it has the simplicity of Substack but the corporate culture is less toxic.
Chad Loder
Extremism researcher
I worked with @buttondown and asked for some new payment support beyond the supporter single tier / pay-what-you-want options. Justin was great and built it in just a couple days.
Dan Hon
Author & consultant
I write nonfiction and I use @buttondown buttondown.email/Changeset - indie, GREAT personal customer support, very nice default styling, all the options I want including ones to protect my readers' privacy
Sumana Harihareswara
Open source maintainer
I use @buttondown because it does exactly what I need (manage subscribers and send markdown emails), not more and not less 👍 As a bonus it's made by an indie dev which I love!
Max Stober
Founder, GraphCDN
If you’re considering running an email newsletter, or if you already run one and are considering a change of provider, I highly recommend @buttondownemail. Super-easy app, very fair pricing with a generous free tier, and exemplary support. 💯
Peter Gasston
Technologist and speaker
imo @buttondown is easily one of the best-designed services i’ve used in recent years, if you have a substack you should really consider switching!
Kabir Goel
Engineer, Cal
Thanks for getting me excited about email newsletters again.
Garrick van Buren
I'm very thrilled that I can just write in Markdown without having to deal with email builders and all that crap.
Parham Doustdar
Thanks again for all the help! You’ve really turned something super complex into something super easy – sending new issues is as simple as firing off a text message.
Kartik Chaturvedi
Thanks for creating a simple way for people who want to, like, put words in a hole and have it sent to people... I am just thankful that something just nice and human exists on the internet.
Emmanuel Quartey
I tried 3 other newsletter services today and I felt like wanting to rip my hair out. They were all painfully slow. I'm so glad I found Buttondown.
Mohamed Elbadwihi
I’ve found Buttondown to be a great fit for my workflow and have been delighted by all of Justin’s thoughtful features and improvements to the product.
Michael Lee
Like seriously, so many lovely little easter eggs in one could-be-boring service.
Alexandra Muck
I just switched over from Tinyletter and I'm really excited to have found a place to host my tiny newsletter that doesn't seem like it's assuming everyone sending newsletters is an email marketer / growth hacker.
Tessa Alexanian
I'm in love with the simplicity of Buttondown.
Ekfan
I’ve used similar tools in the past and Buttondown is by far the simplest to use and most promising.
Fabrizio Rinaldi
Thank you for creating such a simple and brilliant tool. I’ve just signed up and the experience has been smooth and painless (the docs are great too!)
Oliver Holms
As a developer who has hated every email system I've ever used this is so nice.
Drew Hornbein
I wish I still wrote a newsletter just so I could use buttondown again. It’s like that.
Steven Kornweiss

Ready for a better newsletter?

See why thousands of businesses and creators use Buttondown to send millions of emails every day.

Best practices, news, and more. Every month.

See why thousands of businesses and creators use Buttondown to send millions of emails every day.

Have a question? We’re here to help.

We're email experts who are always available to answer your questions.