π Performant Web Applications
Python enables you to rapidly built backend applications using frameworks like Django, Flask, and FastAPI, but these can struggle to scale to intensive loads and high numbers of requests. Fortunately, there are some useful optimisation tricks, to do with database queuing, caching, and async processing.
Here's an overview of some strategies for each framework, along with relevant code examples.
Django Optimisation
Database queries and indexing: Use select_related()
for forward ForeignKey
relationships to reduce the number of database queries. Use prefetch_related()
for many-to-many and reverse ForeignKey
relationships:
# Without optimisation
books = Book.objects.all()
for book in books:
print(book.author.name) # Each iteration hits the database
# With select_related
books = Book.objects.select_related('author').all()
for book in books:
print(book.author.name) # No additional database queries
Also, consider adding indexes to your models for fields that are frequently queried:
class MyModel(models.Model):
name = models.CharField(max_length=100, db_index=True)
Caching: Use Djangoβs cache framework to cache views, query sets, or template fragments:
from django.core.cache import cache
def my_view(request):
if cache.get('my_key'):
return cache.get('my_key')
else:
result = expensive_query()
cache.set('my_key', result, 60) # Cache for 60 seconds
return result
And consider template fragment caching to cache parts of templates that are expensive to render:
{% load cache %}
{% cache 600 sidebar %}
... expensive calculations here ...
{% endcache %}
Middleware, query, and static file optimisation: Reduce middleware classes that are not necessary for your project and consider using Django Debug Toolbar to identify and optimise slow queries. GZipMiddleware can compresses responses (for browsers that support gzip
), while WhiteNoise or a similar tool can serve static files directly from WSGI and apply cache control headers.
Flask Optimisation
Database queries: You can use SQLAlchemy or another ORM and apply similar strategies to those seen in the Django section above. Optimise queries by joining tables only when necessary and filtering queries as much as possible:
from myapp import db
from myapp.models import User, Post
posts = Post.query.join(User).filter(Post.user_id == User.id).all()
Caching: Use flask_caching
to cache views or data:
from flask_caching import Cache
cache = Cache(config={'CACHE_TYPE': 'simple'})
app = Flask(__name__)
cache.init_app(app)
@app.route('/')
@cache.cached(timeout=50)
def index():
return expensive_function()
Asynchronous views: Try using Flaskβs built-in support for async views to handle I/O-bound operations efficiently.
@app.route('/async')
async def async_view():
await asyncio.sleep(1) # Simulate async I/O operation
return 'This is an async view'
Profiling and monitoring: You can use Flask extensions like Flask-DebugToolbar to monitor performance bottlenecks.
Use efficient WSGI servers: Deploy Flask applications using efficient WSGI servers like Gunicorn or uWSGI instead of the built-in Flask server for production environments.
FastAPI Optimisation
Asynchronous and concurrent handling: FastAPI is built to be asynchronous, so use async and await for I/O-bound operations, including database operations:
from fastapi import FastAPI
import httpx
app = FastAPI()
@app.get("/async")
async def read_async():
async with httpx.AsyncClient() as client:
response = await client.get('https://example.com')
return response.text
Database queries: Again, use packages like TortoiseORM or SQLAlchemy with async support to optimise database interactions. Implement strategies similar to those in Django and Flask for prefetching and selecting related data asynchronously.
Dependency injection for caching: Use FastAPI's dependency injection system for efficient caching mechanisms across your app. Implement background tasks for operations that can be processed asynchronously.
Other Optimisation Tips
- Profile your application to identify bottlenecks using tools seen earlier.
- Serve static files efficiently using a CDN.
- Optimise front-end assets (e.g. minified CSS/JS, image compression) to reduce load times.
- Use something like Celery for background tasks, to prevent blocking web requests for operations like sending emails or processing data.
- Use persistent database connections and connection pooling.