Repository navigation
Application Map
Home · アプリの仕組み · 負荷試験の観点 · 計測記録
最初に押さえるのは「CSVを更新する主催者」と「ランキングを見る参加者」が同時に動き、その閲覧が請求にも影響すること。 ランキングは読み取り専用の処理ではない。ここを理解すると、DB・ロック・キャッシュの改善を考えやすくなる。
このページは仕様書・アプリの初期実装・DBスキーマを使った実装の地図。 負荷試験の内部実装は参照しない。未計測の改善案は仮説として扱う。
| 目安 | 見るもの | 答えられるようにする問い |
|---|---|---|
| 5分 | このページの構成図・データ表 | 誰が、何を更新し、誰がそれを読むか? |
| 5分 | アプリの仕組み と 負荷試験の観点 | 正しく動くとは何か?更新前後に何が変わるか? |
| 10分 | Goの Run → 認証・テナント判定 → ランキング |
1リクエストがどのDB・ロックを通るか? |
| 10分 | CSV入稿 → 請求 → 初期化 | 書き込みと読み取りの整合性をどこで守っているか? |
全言語の実装を順に読む必要はない。現在稼働しているGoで、重要な1リクエストを入口から返却まで追う。 読んだ結果は「SQL何回・何行読む・どのロック・どんな副作用」の4項目でメモするとよい。
flowchart LR
B[ブラウザー・APIクライアント] --> N[nginx: ホスト名とパスで転送]
N -->|認証| A[blackauth: JWT発行]
N -->|API| G[Goアプリ: JWT・役割・テナント判定]
N -->|静的ファイル| S[public]
G --> M[MySQL: テナント・ID・閲覧履歴]
G --> T[テナント別SQLite: 参加者・大会・スコア]
G --> L[テナント単位のファイルロック]
I[初期SQLiteデータ] -->|initializeで復元| T
MySQLとSQLiteが分かれていても、ランキングと請求は両方を使う。 Redisはサーバーに導入されているが、このGo初期実装の中心経路には登場しない。
| 保存先 | データ | 役割 | 更新する操作 |
|---|---|---|---|
| MySQL | tenant |
ホスト名から利用団体を特定 | テナント作成 |
| MySQL | id_generator |
ID発行 | 参加者・大会・CSV行などの追加 |
| MySQL | visit_history |
誰がどの大会のランキングを見たか | ランキング閲覧 |
| テナント別SQLite | player |
参加者名・失格状態 | 追加・失格 |
| テナント別SQLite | competition |
大会名・終了時刻 | 作成・終了 |
| テナント別SQLite | player_score |
入稿スコアとCSV行順 | CSV再入稿で置き換え |
DBスキーマは 管理DB と
テナントDB。
初期の player_score は主キー以外の索引がない。検索条件に合う索引は候補になるが、書き込みコストと実際の実行計画も確認する。
| 操作 | 初期実装の処理の流れ | 最初に疑う点 |
|---|---|---|
| ランキング閲覧 | 認証・失格確認 → 大会確認 → MySQLへ閲覧履歴追加 → テナントロック → 大会のスコア取得 → 参加者を個別取得 → Goで重複除去・ソート → 最大100件 | 閲覧ごとの書き込み、全件取得、参加者取得の繰り返し、ロック待ち |
| CSV入稿 | 大会状態確認 → ロック → CSVを読む → 行ごとに参加者確認・MySQLでID発行 → 既存スコア削除 → 行ごとにSQLiteへINSERT | 行数に比例するDB往復、長いロック区間、トランザクション・一括処理の余地 |
| 参加者の戦績 | 参加者確認 → 大会一覧 → ロック → 大会ごとに当人の最新CSV行取得 → 大会情報取得 | 大会数に比例する問い合わせ、適切な索引 |
| 請求 | 大会ごとにMySQLの閲覧履歴を集約 → ロック → SQLiteのスコア登録者取得 → 重複を除いて課金区分を計算 | DBを跨ぐ集約の繰り返し、終了済み大会の再計算 |
これはコードから読み取れる処理量の候補。まだCPU・SQL・待ち時間の順位は計測していない。 ファイルロック待ちはCPUプロファイルだけでは見落とし得るため、HTTP時間とSQL時間も合わせて見る。
| 関係 | 保つべきこと |
|---|---|
| CSV入稿 → ランキング・戦績 | 最新のCSVを反映。同一参加者の重複行は最後の行を採用。入れ替え途中の不整合を出さない |
| ランキング → 請求 | 閲覧履歴の副作用を維持。レスポンスをキャッシュしても、必要な閲覧記録は省かない |
| 同点 → 順位 | CSVの行順で順序が決まる。ページ境界まで同じ順序を保つ |
| 大会終了 → 入稿・請求 | 終了後の入稿を拒否し、請求に終了状態を反映する |
| 失格 → 参加者API | 当人の参加者向けアクセスを拒否。表示用のキャッシュだけで認可を済ませない |
| initialize → 新しい保存形式・キャッシュ | 自分が追加した保存先も初期状態に戻す。古い値を次の実行へ持ち越さない |
請求の初期実装は、終了時刻より後に初めて閲覧した参加者を閲覧課金から除外し、 スコア登録者を閲覧者と二重に数えない。キャッシュ対象はこうした状態境界を確認して決める。
specification.md のランキング節には「一番大きい値」という記述もあるが、同じ仕様書の入稿節は重複行の最後を採用すると定め、
初期実装も最後の行を採用する。ここは再実装前に重複・同点の例で挙動を確認する。最大値への単純な置き換えは避ける。
対象は 初期Go実装。関数名で検索すると辿りやすい。
| 問い | 読む関数 |
|---|---|
| どのURLがどの処理へ行くか? | Run |
| JWT・ホスト名・失格をどう判定するか? |
parseViewer, retrieveTenantRowFromHeader, authorizePlayer
|
| SQLiteの開き方とロック単位は? |
connectToTenantDB, flockByTenantID
|
| ランキングと書き込みの関係は? |
competitionRankingHandler, competitionScoreHandler
|
| 戦績は何回DBを読むか? | playerHandler |
| 請求をどこで繰り返し計算するか? |
billingReportByCompetition, billingHandler, tenantsBillingHandler
|
| ID発行と初期化は? |
dispenseID, initializeHandler, webapp/sql/init.sh
|
次の1回は変更せず計測し、ランキング・CSV入稿・請求のどこに時間が使われるかを決める。 その後は1つの仮説につき1つの変更を行い、同じ条件で再計測する。
| 仮説 | 確かめる観測 | 最初に試す変更の方向 |
|---|---|---|
| スコア検索が大量の行を読む | 該当APIの累積時間、SQLiteの実行計画・読取行数 | 検索条件と並び順に合う索引 |
| CSVの行単位処理が重い | 入稿の所要時間、MySQLのID発行回数、SQLiteのINSERT処理 | 検証を保った一括処理・トランザクション化 |
| ランキングのN+1とソートが重い | CPUプロファイル、参加者参照の回数、HTTP累積時間 | 一括取得・必要な列の取得・計算結果の再利用 |
| 終了済み請求を繰り返し計算している | 請求APIの時間、同じ大会の集約の繰り返し | 終了時の確定値保存と初期化対応 |
| ロックが複数APIをまとめて止める | CPU使用率に比べHTTP待ちが長い、入稿中だけ閲覧が遅い | 整合性を守りつつロック範囲・更新方法を見直す |
最初から全DB移行や全面キャッシュを行うと、効果と不具合の原因が分かりにくい。 小さい変更で根拠を作り、伸びない・正しさが崩れる変更は戻す。 実験の根拠・実装・判定はIssueへ、時刻・変更・スコアは 計測記録 へ残す。