In the previous post, we discussed the basics of Redis and its key use cases. I hope you're now familiar with what Redis is and how it can be helpful in various scenarios. As promised, today we'll explore a real-world example of how to implement Redis in a Django application for a ticketing system.
Use Case: High-Demand Ticketing System
Imagine you're building a ticketing system where users can book tickets for events like concerts or flights. Let's say we have a high demand of booking, how can we make sure that no two person's are booking the same ticket.
In most ticketing systems, when you try to book a ticket, you are given a specific window of time (e.g., 5 minutes) in which the ticket is reserved for you. During this time, the ticket is neither available for other users nor confirmed as booked yet. If another user visits the booking page, they should see that the ticket is already reserved, not available.
Now, let's think about how we can handle this in real time. Should we store the reservation in the database for 5 minutes to mark the ticket as "reserved"? While this might work, there are a couple of issues:
Database Overhead: Continuously writing and updating the database for every reservation request may not be optimal, especially if the system has a high volume of users.
Handling Expiry: After 5 minutes, we need to automatically reset the ticket status back to "Available" if it's not booked. Setting up a cron job to check and update ticket statuses might be one solution, but it's a bit of an overkill and introduces complexity.
How Redis Solves This Problem
Redis, with its ability to handle high-frequency reads and writes in real-time, can help solve both of these problems efficiently. Redis provides a feature called TTL (Time-To-Live), which automatically expires keys after a set duration.
Here's how Redis can work for our ticketing system:
- When a user reserves a ticket, we store the reservation in Redis with a TTL of 5 minutes.
- If the reservation is not confirmed within that time, Redis will automatically delete the key, making the ticket available again.
Here is the flow,
When user needs to book a ticket, we get all the available tickets (not booked) and took all the tickets (may be, ticketId) from Redis and show this accordingly.
This way, we don't need to constantly update the database. Redis acts as a high-performance, low-latency cache for ticket reservations.
But, what happens if two users try to reserve the same ticket at the same time? π€
The answer is that Redis guarantees atomic operations. This means that if two users attempt to reserve the same ticket simultaneously, Redis will ensure that only one succeeds, preventing both users from booking the same ticket.
Setting Up Redis in Django
Before diving into the implementation, ensure you've set up Redis in your Django project. You can follow this excellent tutorial on caching in Django with Redis by Mehedi Khan to configure Redis as your cache backend.
Once Redis is up and running in your project, we'll proceed with implementing the ticket reservation logic.
Implementing Ticket Reservation Logic
Assuming you've set up Redis, let's implement the core logic for reserving tickets.
First, let's define the Ticket
model:
class Ticket(models.Model):
STATUS_CHOICES = [
('available', 'Available'),
('booked', 'Booked'),
]
price = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=10,
choices=STATUS_CHOICES, default='available')
# Add more fields as necessary...
When a user tries to book a ticket, we need to reserve it for them in Redis:
from django.core.cache import cache
from django.http import HttpResponse
from .models import Ticket
def reserve_ticket_in_redis(ticket_id, timeout=60*5):
reservation_key = f"ticket_reservation_{ticket_id}"
# Check if the ticket is already reserved
if cache.get(reservation_key):
return False
# If not reserved, set the reservation key with TTL
cache.set(reservation_key, "reserved", timeout=timeout)
return True
def book_ticket(request, ticket_id):
ticket = Ticket.objects.get(id=ticket_id)
# Check if the ticket is already booked in the database
if ticket.status == "booked":
return HttpResponse("Sorry, this ticket is already booked.")
# Attempt to reserve the ticket in Redis
if not reserve_ticket_in_redis(ticket_id):
return HttpResponse("This ticket is already reserved by another user. Please try again later.")
return HttpResponse(f"Ticket reserved successfully for 5 minutes. Please complete your booking.")
When users view a particular event, we need to show them a list of available tickets that are neither booked nor reserved. We can check both the ticket status and Redis cache for this:
from django.core.cache import cache
from django.shortcuts import get_object_or_404
def event_details(request, id):
event = get_object_or_404(Event, pk=id)
all_tickets = event.tickets.all()
available_tickets = []
for ticket in all_tickets:
# Skip tickets that are booked or reserved in Redis
if ticket.status == "booked" or cache.get(f"ticket_reservation_{ticket.id}"):
continue # Don't show this ticket to the user
# Add ticket to available list if not booked or reserved
available_tickets.append(ticket)
return render(request, 'event_details.html', {'event': event, 'available_tickets': available_tickets})
Explanation
Redis Reservation: The
reserve_ticket_in_redis
function attempts to reserve the ticket in Redis usingcache.set()
andcache.get()
, which ensures the key is only set if it doesn't already exist.Atomic Operations: This ensures that if two users try to reserve the same ticket at the same time, only one will succeed.
Conclusion
In this post, weβve explored how Redis can be used in a Django ticketing system to manage ticket reservations efficiently. By utilizing Redisβ TTL feature, we can reserve tickets for a set period (e.g., 5 minutes), and automatically make them available again if not confirmed. This reduces the load on the database and avoids race conditions by ensuring atomic operations for concurrent reservations.
Let me know if you have any questions or need further clarification!
Happy Designing! ππ»
Top comments (0)