Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
rubocop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- run: bundle exec rubocop

rspec:
runs-on: ubuntu-latest
services:
redis:
image: redis:7
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- run: bundle exec rspec --tag ~system
env:
RACK_ENV: test
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
10 changes: 10 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
AllCops:
NewCops: enable
Exclude:
- '.gems/**/*'
- '.direnv/**/*'
- 'vendor/**/*'
- '.bundle/**/*'


Style/Documentation:
Enabled: false

Metrics/MethodLength:
Enabled: false

Metrics/BlockLength:
Exclude:
- 'spec/**/*'

Lint/UnusedMethodArgument:
Enabled: false
8 changes: 6 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ ruby '4.0.3'
gem 'activerecord'
gem 'pg'
gem 'puma'
gem 'rake'
gem 'redis'
gem 'sinatra'
gem 'sinatra-activerecord'

group :development do
gem 'rubocop'
group :development, :test do
gem 'rack-test'
gem 'rspec'
gem 'ruby-lsp'
gem 'solargraph'
gem 'standard', '>= 1.35.1'
end
63 changes: 57 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ GEM
observer (0.1.2)
open3 (0.2.1)
ostruct (0.6.3)
parallel (2.1.0)
parallel (1.28.0)
parser (3.3.11.1)
ast (~> 2.4.1)
racc
Expand All @@ -68,7 +68,10 @@ GEM
rack-session (2.1.2)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rainbow (3.1.1)
rake (13.4.2)
rbs (4.0.2)
logger
prism (>= 1.6.0)
Expand All @@ -81,11 +84,24 @@ GEM
reverse_markdown (3.0.2)
nokogiri
rexml (3.4.4)
rubocop (1.86.2)
rspec (3.13.2)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.6)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.8)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.7)
rubocop (1.84.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (>= 1.10)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
Expand All @@ -95,6 +111,10 @@ GEM
rubocop-ast (1.49.1)
parser (>= 3.3.7.2)
prism (~> 1.7)
rubocop-performance (1.26.1)
lint_roller (~> 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.47.1, < 2.0)
ruby-lsp (0.26.9)
language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0)
Expand All @@ -108,6 +128,9 @@ GEM
rack-protection (= 4.2.1)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
sinatra-activerecord (2.0.28)
activerecord (>= 4.1)
sinatra (>= 1.0)
solargraph (0.59.0)
ast (~> 2.4.3)
backport (~> 1.2)
Expand All @@ -131,6 +154,18 @@ GEM
yard (~> 0.9, >= 0.9.24)
yard-activesupport-concern (~> 0.0)
yard-solargraph (~> 0.1)
standard (1.54.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.84.0)
standard-custom (~> 1.0.0)
standard-performance (~> 1.8)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.9.0)
lint_roller (~> 1.1)
rubocop-performance (~> 1.26.0)
thor (1.5.0)
tilt (2.7.0)
timeout (0.6.1)
Expand All @@ -154,11 +189,15 @@ DEPENDENCIES
activerecord
pg
puma
rack-test
rake
redis
rubocop
rspec
ruby-lsp
sinatra
sinatra-activerecord
solargraph
standard (>= 1.35.1)

CHECKSUMS
activemodel (8.1.3) sha256=90c05cbe4cef3649b8f79f13016191ea94c4525ce4a5c0fb7ef909c4b91c8219
Expand Down Expand Up @@ -188,7 +227,7 @@ CHECKSUMS
observer (0.1.2) sha256=d8a3107131ba661138d748e7be3dbafc0d82e732fffba9fccb3d7829880950ac
open3 (0.2.1) sha256=8e2d7d2113526351201438c1aa35c8139f0141c9e8913baa007c898973bf3952
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99
pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d
Expand All @@ -198,20 +237,32 @@ CHECKSUMS
rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2
rack-protection (4.2.1) sha256=cf6e2842df8c55f5e4d1a4be015e603e19e9bc3a7178bae58949ccbb58558bac
rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
rbs (4.0.2) sha256=af75671e66cd03434cc546622741ebf83f6197ec4328375805306330bf78ef25
redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae
redis-client (0.29.0) sha256=0c65bf1f8f6dca22063ddb085c0bb2054feef6f03a84869f4161b18a9a15bea3
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
reverse_markdown (3.0.2) sha256=818ebb92ce39dbb1a291690dd1ec9a6d62530d4725296b17e9c8f668f9a5b8af
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
rubocop (1.86.2) sha256=bb2e97f635eda42c448f2588f4a6ff78f221b8bdfdf65b1e9b07fbd57521b45d
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
rubocop (1.84.2) sha256=5692cea54168f3dc8cb79a6fe95c5424b7ea893c707ad7a4307b0585e88dbf5f
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
ruby-lsp (0.26.9) sha256=33a01c001c00a76b4e821efc04ed7572983430f31ca5d6f3e343d0b6ccab4129
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080
sinatra-activerecord (2.0.28) sha256=99f352c2dfa244d02b4f877efbe00135360b758390b8bb7bc2d4d91171c93811
solargraph (0.59.0) sha256=229845d4ce99b6259e3d7fd9c8238f9cd74c3056e59a4a16fb1525d6a790dad3
standard (1.54.0) sha256=7a4b08f83d9893083c8f03bc486f0feeb6a84d48233b40829c03ef4767ea0100
standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b
standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
tilt (2.7.0) sha256=0d5b9ba69f6a36490c64b0eee9f6e9aad517e20dcc848800a06eb116f08c6ab3
timeout (0.6.1) sha256=78f57368a7e7bbadec56971f78a3f5ecbcfb59b7fcbb0a3ed6ddc08a5094accb
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Ruby developers who want to go beyond CRUD and learn how to handle high traffic,
| Problem | Description | Difficulty |
|---------|-------------|------------|
| [Cache Stampede](lib/ruby_at_scale/cache_stampede/) | Prevent 1000 concurrent requests from hammering your DB when cache expires | Medium |
| [Rate Limiter](lib/ruby_at_scale/rate_limiter/) | Atomic sliding window rate limiting under high concurrency | Medium |

## How it works

Expand All @@ -23,7 +24,13 @@ Ruby developers who want to go beyond CRUD and learn how to handle high traffic,

```bash
bundle install
bin/setup
```

Update `config/database.yml` with your PostgreSQL credentials, then:

```bash
rake db:create
rake db:migrate
```

## Run
Expand All @@ -35,8 +42,12 @@ bin/server
## Test

```bash
# Cache Stampede
test/ruby_at_scale/cache_stampede_test
# Unit/Integration specs
rspec --tag ~system

# System tests (interactive, requires running PostgreSQL and Redis)
spec/system/bin/cache_stampede_test
spec/system/bin/rate_limiter_test
```

## Dependencies
Expand Down
6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

ENV['RACK_ENV'] ||= 'production'

require_relative 'lib/ruby_at_scale'
require 'sinatra/activerecord/rake'
31 changes: 0 additions & 31 deletions bin/setup

This file was deleted.

14 changes: 2 additions & 12 deletions config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,5 @@

require_relative 'lib/ruby_at_scale'

get '/report' do
content_type :json

result = RubyAtScale::CacheStampede.cache.fetch('expensive_report', ttl: 60) do
RubyAtScale::CacheStampede.redis.incr('stampede:query_count')
RubyAtScale::CacheStampede.expensive_query
end

{ pid: Process.pid, result_size: result.to_s.length }.to_json
end

run Sinatra::Application
map('/report') { run RubyAtScale::Controllers::ReportsController }
map('/limited') { run RubyAtScale::Controllers::RateLimitController }
15 changes: 11 additions & 4 deletions config/database.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
development:
test:
adapter: postgresql
host: localhost
database: ruby_at_scale
username: <%= ENV.fetch('PGUSER', 'postgres') %>
password: <%= ENV.fetch('PGPASSWORD', '') %>
database: ruby_at_scale_test
username: postgres
password:

production:
adapter: postgresql
host: localhost
database: ruby_at_scale_production
username: postgres
password:
2 changes: 2 additions & 0 deletions config/puma.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

environment ENV.fetch('RACK_ENV', 'production')

workers 4
threads_count = 5
threads threads_count, threads_count
Expand Down
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

map('/report') { run RubyAtScale::Controllers::ReportsController }
map('/limited') { run RubyAtScale::Controllers::RateLimitController }
11 changes: 11 additions & 0 deletions db/migrate/20260520000001_create_events.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

class CreateEvents < ActiveRecord::Migration[7.1]
def change
create_table :events do |t|
t.string :event_type
t.decimal :amount, precision: 10, scale: 2
t.timestamps
end
end
end
25 changes: 25 additions & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.1].define(version: 20_260_520_000_001) do
# These are extensions that must be enabled in order to support this database
enable_extension 'pg_catalog.plpgsql'

create_table 'events', force: :cascade do |t|
t.decimal 'amount', precision: 10, scale: 2
t.datetime 'created_at', null: false
t.string 'event_type'
t.datetime 'updated_at', null: false
end
end
Loading
Loading