DynamoDB for Scalable Data Storage: Managing User Progress in a Fitness App

DynamoDB for Scalable Data Storage: Managing User Progress in a Fitness App

Managing and scaling dynamic user data, such as progress logs and exercise details, is crucial in fitness-tracking applications. Amazon DynamoDB, with its serverless architecture and high throughput, is a perfect match for this requirement. It supports low-latency reads and writes, handles high concurrency, and allows schema flexibility.

This article delves into the specific DynamoDB setup in your fitness app, leveraging configurations directly from your project files. It explains the schema design, integration with Lambda for data processing and handling of high-concurrency use cases.


Why DynamoDB Is Ideal for Fitness Apps

Fitness apps generate diverse, dynamic datasets, such as:

  • User-Specific Progress: Logs of workouts and aggregated metrics.
  • Flexibility Needs: Allowing new workout types or progress metrics without predefined schemas.
  • High Concurrency: Supporting simultaneous updates as users interact with the app in real time.

DynamoDB offers:

  • Pay-Per-Request Billing: Cost-efficient scaling for unpredictable workloads.
  • Global Secondary Indexes (GSIs): Efficient queries on secondary attributes like exercise types or dates.
  • Streams for Event Processing: Real-time change notifications for downstream processing.


DynamoDB Table Schema for Fitness Tracking

Your project uses two tables, each tailored to specific fitness app data:

1. Raw Data Table

This table stores detailed workout logs for each user with the following attributes:

Primary Key:

  • Partition Key: user
  • Sort Key: date

Global Secondary Index:

  • Name: exercise-date-index
  • Partition Key: exercise
  • Sort Key: date

2. Aggregated Data Table

This table stores summary metrics for user progress:

Primary Key:

  • Partition Key: user
  • Sort Key: exercise_name


Terraform Configuration for DynamoDB

Below is the actual configuration from your project:

resource "aws_dynamodb_table" "raw_data" {
  name         = "${var.environment}_raw_data"
  billing_mode = "PAY_PER_REQUEST"

  attribute {
    name = "user"
    type = "S"
  }

  attribute {
    name = "date"
    type = "S"
  }

  attribute {
    name = "exercise"
    type = "S"
  }

  hash_key  = "user"
  range_key = "date"

  global_secondary_index {
    name            = "exercise-date-index"
    hash_key        = "exercise"
    range_key       = "date"
    projection_type = "ALL"
  }

  stream_enabled   = true
  stream_view_type = "NEW_AND_OLD_IMAGES"

  tags = var.tags
}

resource "aws_dynamodb_table" "aggregates" {
  name         = "${var.environment}_aggregated"
  billing_mode = "PAY_PER_REQUEST"

  attribute {
    name = "user"
    type = "S"
  }

  attribute {
    name = "exercise_name"
    type = "S"
  }

  hash_key = "user"
  range_key = "exercise_name"

  tags = var.tags
}        

Lambda Integration for Data Processing

Updating User Progress

The following Lambda function demonstrates how to record detailed workout data in the raw_data table:

import json
import boto3

def handler(event, context):
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('raw_data')

    body = json.loads(event['body'])
    user = body['user']
    date = body['date']
    exercise = body['exercise']
    details = body['details']

    # Save the data
    table.put_item(
        Item={
            'user': user,
            'date': date,
            'exercise': exercise,
            'details': details
        }
    )

    return {
        'statusCode': 200,
        'body': json.dumps({'message': 'Workout logged successfully!'})
    }        

Querying Aggregated Data

This Lambda function retrieves summary metrics from the aggregates table:

def handler(event, context):
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('aggregates')

    user = event['queryStringParameters']['user']

    response = table.query(
        KeyConditionExpression="user = :user",
        ExpressionAttributeValues={":user": user}
    )

    return {
        'statusCode': 200,
        'body': json.dumps(response['Items'])
    }        

Handling High Concurrency

Strategies for Scalability

  1. On-Demand Billing: Automatically scales based on traffic without manual adjustments.
  2. Indexes for Optimized Queries: The GSI on the raw_data table enables efficient queries on exercise and date.
  3. DynamoDB Streams: Captures changes to tables in real-time for downstream analytics or notifications.


Visuals

DynamoDB Console

Article content
Article content

Example Query Results

Example output from a query in the AWS console:

"user","date","exercise","exercise_reps","exercise_volumes","raw_exercises","total_volume"
"todd@bernsonfamily.com","2025-01-14","DAILY_SUMMARY","{""situps"":{""N"":""150""},""deadlift"":{""N"":""90""},""muscleups"":{""N"":""487""},""pullups"":{""N"":""60""}}","{""situps"":{""N"":""20250""},""deadlift"":{""N"":""13950""},""muscleups"":{""N"":""7305""},""pullups"":{""N"":""12120""}}","[{""M"":{""name"":{""S"":""deadlift""},""weight"":{""N"":""155""},""reps"":{""N"":""90""}}},{""M"":{""name"":{""S"":""muscleups""},""weight"":{""N"":""15""},""reps"":{""N"":""487""}}},{""M"":{""name"":{""S"":""pullups""},""weight"":{""N"":""202""},""reps"":{""N"":""60""}}},{""M"":{""name"":{""S"":""situps""},""weight"":{""N"":""135""},""reps"":{""N"":""150""}}}]","53625"        

Amazon DynamoDB forms the backbone of the data storage layer for your fitness tracking app. By using the schema and Terraform configurations you’ve provided, this architecture supports low-latency, scalable operations, enabling seamless user experiences even under high concurrency. With additional features like DynamoDB Streams and GSIs, your setup is both robust and future-proof.

To view or add a comment, sign in

More articles by Todd Bernson

Others also viewed

Explore content categories