PORTFOLIO
Back to writing

Django

Keeping APIs Non-Blocking: Celery, Redis, and Async Score Calculations

March 10th, 20262 min read

Daily Dogfights is a golf scoring platform with GHIN/USGA handicap API integration, a Django REST backend consumed by both a Next.js frontend and a mobile app. The obvious first implementation calculates a player's updated handicap and notifies their group the moment a score is submitted, synchronously, inside the request.

That works fine with light traffic. It falls apart on a Sunday afternoon when half the app's users finish their round within the same hour and submit scores at once, handicap math plus an external API call plus notification fan-out, all blocking the response.

The fix was moving score calculation and notifications off the request and response cycle entirely with Celery, using Redis as the broker. The API's job becomes: validate the score, write it, queue a job, return immediately. The handicap recalculation, the GHIN and USGA lookups, and the push notifications all happen in a worker, independent of how many people are submitting scores at the same time.

tasks.py
@shared_task
def recalculate_handicap(player_id, round_id):
player = Player.objects.get(id=player_id)
handicap = fetch_ghin_handicap(player.ghin_number)
player.handicap_index = handicap
player.save(update_fields=["handicap_index"])
notify_group.delay(round_id)

The main API stayed fast and predictable regardless of submission bursts, and the async layer gave room to add retries around the flakier external API calls without making the user wait for them.