10 Best Practices for Keeping a Python Web App Running Flawlessly
Here are 10 best practices I employ to keep Django and Flask web applications running in tip top shape.
1. Use managed services
I have a strong opinion on this one. If you do not have an in-house development team available to support your web app, you should be using managed services, such as Heroku or AWS Elastic Beanstalk.
Yes, you could save money by running your web service and database on a single raw server. But who is going to patch the operating system? Who is going to ensure backups are in place? Who is going to log in and reboot the server when something goes wrong? The great thing about managed services is when something goes wrong with that portion of the service, someone else is going to get involved quickly and get it working again.
Backups, security, and your deployment process are critical capabilities that must be managed. Why not take advantage of Heroku and AWS engineers and focus on code rather than servers?
2. Leverage caching
Caching is a very effective way to make an app fast and resilient. I recommend a layered caching strategy with:
- Static assets (images, CSS, javascript) cached through a CDN such as Cloudflare
- Expensive database queries and page fragments cached with Redis
Use Whitenoise to generate unique static asset IDs, which can then be cached forever. If you change the file then a new ID will be generated.
Caching should be applied where needed and not broadly, as it can introduce a lot of user issues if overdone.
3. Eliminate unnecessary bots and spiders
The majority of search spiders are leaches that hog resources and drag down a web site’s performance.
Many of them use your site in an abusive way. While a typical user may click through 3 to 4 pages of results in 5 seconds, a bot will quickly go from page 1 to 50 in 7 seconds. Identify bots with a logging tool such as Papertrail, then block those that provide no benefit to you via robots.txt.
# Example robots.txt User-agent: Baiduspider User-agent: Yandex User-agent: Sogou web spider User-agent: seoscanners.net Disallow: /
A web application firewall can help with this as well. But I have seen the best results from simple robots.txt updates.
4. Monitor for errors
Utilize Django’s built-in error reporting feature, or even better, integrate Sentry. This will provide detailed insight into every error, which can then be patched to make your app more resilient.
5. Investigate performance issues continuously
Identify the root cause of performance issues and resolve them methodically.
It’s easy to brush off temporary errors or timeouts as a server glitch, hosting issue, or ‘gremlins’. But there is almost always a specific reason something went wrong. It could very likely happen again if you do not fix it.
Left unchecked, these glitches combine to cause major problems or unexpected downtime. But when fixed often, the app gets stronger and stronger.
One effective way to improve stability is by setting low timeouts across your services. For example, if you request an API service and expect the result to always take less than 3 seconds, then set a timeout for 3 seconds. Then, when that API has issues it will not impact your site.
r = requests.get(url, timeout=3)
You should set low timeouts with gunicorn as well, as it will make your app recover faster from errors and reduce the chance of cascading timeouts:
web: gunicorn my.wsgi:application --timeout 10
6. Update dependencies often
With dependencies there is a temptation to apply the old ‘if it ain’t broke, don’t fix it’ mentality. But dependency updates bring security patches, performance, and capability improvements. Letting dependencies go out of date can lead to a stale web app that requires major surgery to bring it current.
I recommend updating 2 to 3 dependencies per month at most, and patching all insecure dependencies within 48 hours.
7. Add tests
Your app will be tested one way or another. Better to test a change prior to it being live, rather than letting users see an error and hearing about it that way.
A solid test suite takes the guess work out of changes.
8. Implement features in small batches
One of my favorite books, The DevOps Handbook, covers this well. It’s much better to push small feature updates and changes than try to push a very large update. When we’re pushing small feature updates and dependency updates often, we can clearly see what causes issues and fix it quickly.
This kind of development is best supported by a quality managed service with seamless updates that can be pushed live in the middle of the day.
9. Write clean code
Readable, PEP 8 compliant code reduces complexity. One of the best ways to make an app more maintainable is to focus on Uncle Bob’s Clean Code methodologies, which include:
- Write small functions
- Name those functions so that their intention is clear
- Test everything
10. Focus on security at every level
You must constantly focus on security to keep your web app available and running well. This means:
- Utilize two-factor authentication for every backend service login
- Set strong passwords
- Properly handle web forms to protect against Cross-Site Request Forgery
- Sanitize user input to protect against SQL injection attacks
- Update insecure dependencies quickly