Skip to main content

Tech Stack

Gumroad is built on a modern, scalable stack designed to handle high-traffic e-commerce operations:

Backend

  • Rails 7.1.6 - Main application framework
  • Ruby 3.4.3 - Programming language
  • MySQL 8.0.x - Primary relational database with Makara for read replicas
  • MongoDB - Document store for specific use cases
  • Redis - Caching and session management
  • Sidekiq 7.2 - Background job processing with Sidekiq Pro features
  • Elasticsearch 7.11.2 - Full-text search and analytics

Frontend

  • React 18.1.0 - UI component library
  • TypeScript 5.5.0 - Type-safe JavaScript
  • Inertia.js 3.15 - Modern monolith SPA framework
  • Tailwind CSS 4.1.14 - Utility-first CSS framework
  • Shakapacker 8.0 - Webpack integration for Rails

Infrastructure

  • Docker - Local development environment
  • Puma 6.4.2 - Web server
  • AnyCable 1.5 - WebSocket server for real-time features
  • Nomad - Production deployment orchestration

Application Structure

Directory Layout

The app/modules/ directory is legacy. Use concerns in the appropriate directory instead (e.g., app/models/concerns/).

Key Architectural Patterns

Complex business logic is encapsulated in service objects located in app/services/. These objects follow the single responsibility principle and make the codebase more maintainable.Example structure:
Presenters in app/presenters/ handle view-layer data preparation, keeping controllers thin and views clean.
Gumroad uses Pundit for authorization. Policies are defined in app/policies/ and enforce permissions at the controller level.

Database Architecture

MySQL with Makara

The application uses MySQL 8.0.x with the Makara gem for database connection management:
Makara automatically routes read queries to replicas and write queries to the primary database, improving performance and scalability.

Connection Pooling

The database pool size is configurable via DB_POOL_SIZE environment variable. Sidekiq workers use larger pool sizes to handle concurrent job processing.

Schema Management

Database migrations are managed through Rails migrations:

Background Job Processing

Sidekiq Configuration

Gumroad uses Sidekiq with Pro features for reliable background job processing:

Queue Priorities

Jobs are organized by priority:
  1. critical - Receipt and purchase emails (time-sensitive)
  2. default - Standard background jobs
  3. low - Non-urgent batch operations
  4. mongo - Legacy queue for one-time scripts
When creating new jobs, choose the lowest priority queue that meets your requirements. Most jobs should use the low queue unless they’re time-sensitive.

Job Naming Convention

All new Sidekiq job classes must end with “Job”:

Search with Elasticsearch

Elasticsearch 7.11.2 powers search functionality across products, users, and sales data.

Reindexing

After initial setup or schema changes, reindex all data:
You must reindex Elasticsearch after setup, otherwise you’ll see index_not_found_exception errors.

Caching Strategy

Redis is used for:
  • Session storage
  • Action caching
  • Fragment caching
  • Sidekiq job queues
  • Rate limiting
The Redis connection is configured globally:

Real-time Features

WebSocket functionality is powered by:
  • AnyCable - Scalable WebSocket server
  • Action Cable - Rails WebSocket framework
Configuration in config/anycable.yml defines connection settings.

Code Organization Principles

1

Separate concerns

Business logic lives in service objects, not controllers or models.
2

Use conventions

Follow Rails conventions and Gumroad-specific patterns documented in CONTRIBUTING.md.
3

Keep controllers thin

Controllers should primarily route requests and delegate to service objects or models.
4

Prefer composition

Use service objects and modules rather than deeply nested inheritance.

Performance Considerations

Database Queries

  • Use database indexes appropriately
  • Avoid N+1 queries with eager loading
  • Use includes and joins strategically
  • Monitor query performance with Active Record Query Trace (development)

Background Jobs

  • Keep jobs idempotent when possible
  • Use lock: :until_executed for deduplication
  • Avoid on_conflict: :replace due to performance impact
  • Monitor job queue latency

Caching

  • Use action caching for expensive operations
  • Implement fragment caching for partial views
  • Set appropriate cache expiration times

Development Tools

  • Pry - Enhanced IRB console with debugging
  • Rubocop - Ruby code linter
  • ESLint - JavaScript/TypeScript linter
  • Bullet - N+1 query detection (development)
  • Rack Mini Profiler - Performance profiling

Next Steps

Authentication

Learn about Devise and OAuth implementation

Testing

Understand the testing strategy with RSpec

Deployment

Deploy changes to staging and production

Contributing

Read the contribution guidelines