Share
Embed
Kaigi_on_Rails_2022_Talk-hogelog.pdf
hogelog
October 22, 2022
Transcript
33 slides
-
自己紹介 2 @hogelog (Sunao Komuro) - STORES 株式会社 CTO室 -
2021年6月入社 - サービス開発、基盤領域、マネージャーなど を経て、現在はいちバックエンドエンジニア -
Railsのセッションストア 5 - `rails new myapp` しただけでセッションが利用可能 - デフォルトセッションストアはCookieStore 1
│ class CountController < ApplicationController 2 │ def show 3 │ session[:count] = session[:count].to_i + 1 4 │ render plain: "hello: #{session[:count]}" 5 │ end 6 │ end -
CookieStoreの嬉しくないところ 7 - セッションの実体がクライアントにある - セッション無効化などの制御も難しい - セキュリティ的にもサーバサイドに置くことがベストプラクティス - >
However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Rails Guides <https://guides.rubyonrails.org/security.html> - > The best practice is to use a database based session OWASP Cheet Sheet Series: Ruby on Rails Cheet Sheet <https://cheatsheetseries.owasp.org/cheatsheets/Ruby_on_Rails_Cheat_Sheet.html#sessions> - セッションをサーバサイドに切り替えよう -
要件 9 - 対象サービス: STORES(ネットショップ) - 2012年8月リリース - ほぼモノリシックRailsアプリケーション -
セッション数は1000万オーダー - セッション期限 - ログインユーザ: 2週間 - 非ログインユーザ: セッションクッキー(すぐ揮発) - セッションのリセットはしたくない -
技術選定 10 - ストレージ: Amazon MemoryDB for Redis - Redis
プロトコルを喋る Aurora 的な存在 - セッションストア: redis-rails (redis-actionpack) gem - 技術選定の理由は STORES Product Blog にて - https://product.st.inc/entry/2022/07/07/142350 - https://product.st.inc/entry/2022/07/07/142350 -
移行戦略 11 - セッションは維持したまま - ダブルライトによる段階的な移行 1. クッキーのみ 2. ダブルライト
(Write: クッキー&Redis, Read: クッキー) 3. ダブルライト (Write: クッキー&Redis, Read: Redis) 4. Redisのみ -
必要な部品 12 - ダブルライト (Write: クッキー&Redis, Read: クッキー) - ほぼ
CookieStore だが書き込みは RedisStore にも実行 - ダブルライト (Write: クッキー&Redis, Read: Redis) - ほぼ RedisStore だが書き込みは CookieStore にも実行 -
ダブルライト (Write: クッキー & Redis, Read: クッキー) 13 - `class
CookieToRedis < ActionDispatch::Session::CookieStore` - delete_session, commit_session でダブルライト 1 │ class CookieToRedis < ActionDispatch::Session::CookieStore 2 │ def initialize(app, options = {}) 3 │ super(app, options[:cookie_store]) 4 │ @redis_store = ActionDispatch::Session::RedisStore.new(app, options[:redis_store]) 5 │ end 6 │ 7 │ def delete_session(req, session_id, options) 8 │ super 9 │ @redis_store = delete_session(req, session_id, options) 10 │ end 11 │ 12 │ def commit_session(req, res) 13 │ super 14 │ @redis_store.commit_session(req, res) 15 │ end 16 │ end 17 │ 18 │ Rails.application.config.session_store CookieToRedis, { 19 │ cookie_store: { key: "cookie_id" }, 20 │ redis_store: { key: "redis_id", servers: ..., expire_after: 2.weeks }, 21 │ } -
ダブルライト (Write: クッキー & Redis, Read: Redis) 14 - `class
RedisToCookie < ActionDispatch::Session::RedisStore` - delete_session, commit_session でダブルライト 1 │ class RedisToCookie < ActionDispatch::Session::RedisStore 2 │ def initialize(app, options = {}) 3 │ super(app, options[:redis_store]) 4 │ @cookie_store = ActionDispatch::Session::CookieStore.new(app, options[:cookie_store]) 5 │ end 6 │ 7 │ def delete_session(req, session_id, options) 8 │ super 9 │ @cookie_store = delete_session(req, session_id, options) 10 │ end 11 │ 12 │ def commit_session(req, res) 13 │ super 14 │ @cookie_store.commit_session(req, res) 15 │ end 16 │ end 17 │ 18 │ Rails.application.config.session_store RedisToCookie, { 19 │ cookie_store: { key: "cookie_id" }, 20 │ redis_store: { key: "redis_id", servers: ..., expire_after: 2.weeks }, 21 │ } -
なぜキーの増加が止まらなかったのか(その1) 17 - maxmemory-policy デフォルト値 - ElastiCache Redis: volatile-lru -
MemoryDB: noeviction - 適切な maxmemory-policy が設定されていなかった -
MemoryDB 設定の調整 18 - maxmemory-policy の設定 - MemoryDB のキーが揮発しメモリに余裕が -
しかし、しばらくすると揮発しなくなる - キーが増え続けメモリが溢れる💣💥 -
なぜキーの増加が止まらなかったのか(その2) 19 - 調査 - TTLなしキー(揮発しないキー)でいっぱいになっていた - Redis monitor コマンドで調査
- ダブルライト実装、rack、redis-rails の精査 - 原因 - CookieToRedisダブルライト実装の不備 - STORES のセッション期限仕様 - redis-rails gem の仕様(不具合?) -
CookieToRedisダブルライト実装の不備 20 - CookieToRedisはRedisStoreに対しcookie_store設定値で書き込 んでいた 373 │ def commit_session(req, res)
374 │ session = req.get_header RACK_SESSION 375 │ options = session.options … 388 │ if not data = write_session(req, session_id, session_data, options) rack-2.2.3.1/lib/rack/session/abstract/id.rb -
STORES のセッション期限仕様 21 - セッション期限 - ログインユーザ: 2週間 - 非ログインユーザ:
セッションクッキー(すぐ揮発) - CookieStoreではログインセッション期限は2週間に伸ばしている がデフォルトは`expire_after: nil` Rails.application.config.session_store CookieToRedis, { cookie_store: { key: "cookie_id" }, redis_store: { key: "redis_id", servers: ..., expire_after: 2.weeks }, } -
RedisStore における `expire_after: nil` の挙動 23 - セッションキーはセッションクッキーに書き込まれる - ブラウザ終了などによりすぐに揮発する
- セッション内容はRedisにTTLなしで書き込まれる - TTLなしのキーは揮発しない - どこからも読まれない揮発しないRedisキーが生まれる -
非ログインユーザの揮発しないセッション内容がMemoryDBに残り続けていた 24 - 非ログインユーザのセッションを `expire_after: nil` で書き込み - セッションキーはセッションクッキーなのですぐ揮発 -
セッション内容はTTLなしでMemoryDBに蓄積される - TTLなしの揮発しないキーでメモリが溢れる💣💥 -
ダブルライト実装の修正 26 - CookieStore も `expire_after: 2.weeks` に統一 - CookieStore非ログインユーザのセッション期限も2週間に
Rails.application.config.session_store CookieToRedis, { cookie_store: { key: "cookie_id", expire_after: 2.weeks }, redis_store: { key: "redis_id", servers: ..., expire_after: 2.weeks }, } -
redis-rails gem 32 - 人気の gem だが開発は活発ではない - `expire_after: nil`
仕様(不具合?)のIssueも反応なし - STORES 及び世界中で広く使われているgem - Issue を投げるだけではない、より積極的なコントリビュートの必 要性 - @hogelog の今後の活躍にご期待ください