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

app/
├── business/           # Business logic layer
├── controllers/        # HTTP request handlers
├── javascript/         # React components and TypeScript
├── jobs/              # Background job definitions
├── models/            # ActiveRecord models
├── policies/          # Pundit authorization policies
├── presenters/        # View layer data preparation
├── services/          # Service objects for complex operations
├── sidekiq/           # Sidekiq worker classes
└── views/             # ERB templates and layouts
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:
class UserBalanceStatsService
  def initialize(user)
    @user = user
  end

  def fetch
    # Complex calculation logic
  end
end
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:
development:
  adapter: mysql2_makara
  encoding: utf8mb4
  collation: utf8mb4_unicode_ci
  pool: <%= ENV["DB_POOL_SIZE"].presence || 5 %>
  makara:
    id: mysql
    connections:
      - role: primary
        host: <%= ENV["DATABASE_HOST"] %>
      - role: replica
        host: <%= ENV["DATABASE_HOST"] %>
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:
bin/rails db:migrate
bin/rails db:rollback
bin/rails db:prepare  # Setup database

Background Job Processing

Sidekiq Configuration

Gumroad uses Sidekiq with Pro features for reliable background job processing:
Sidekiq.configure_server do |config|
  config.redis = { url: "redis://#{ENV['SIDEKIQ_REDIS_HOST']}" }
  config.super_fetch!          # Sidekiq Pro feature
  config.reliable_scheduler!   # Sidekiq Pro feature
end

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”:
class ProcessBacklogJob < ApplicationJob
  queue_as :low
  
  def perform(user_id)
    # Job logic
  end
end

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:
# In Rails console
DevTools.delete_all_indices_and_reindex_all
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:
$redis = Redis.new(url: "redis://#{ENV['REDIS_HOST']}")

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

Build docs developers (and LLMs) love