Global Trend Radar
Web: qiita.com US web_search 2026-05-01 06:53

【図解】キャッシュ戦略って何?キャッシュを使ったクエリチューニングって何? - Qiita

元記事を開く →

分析結果

カテゴリ
IT
重要度
57
トレンドスコア
21
要約
【図解】キャッシュ戦略って何?キャッシュを使ったクエリチューニングって何? #Go - Qiita 59 いいねしたユーザー一覧へ移動 43 X(Twitter)でシェアする Facebookでシェアする はてなブックマークに追加する more_horiz 記事を削除する close 一度削除した記事は復旧できません。 この記事の編集中の下書きも削除されます。 削除してよろしいですか? キャンセル 削除する delete info この
キーワード
【図解】キャッシュ戦略って何?キャッシュを使ったクエリチューニングって何? #Go - Qiita 59 いいねしたユーザー一覧へ移動 43 X(Twitter)でシェアする Facebookでシェアする はてなブックマークに追加する more_horiz 記事を削除する close 一度削除した記事は復旧できません。 この記事の編集中の下書きも削除されます。 削除してよろしいですか? キャンセル 削除する delete info この記事は最終更新日から1年以上が経過しています。 @ WebEngrChild 【図解】キャッシュ戦略って何?キャッシュを使ったクエリチューニングって何? Go Memcached RDS ElastiCache Terraform 59 最終更新日 2023年08月09日 投稿日 2023年08月04日 基礎知識編 Webシステム開発においてキャッシュの理解と設計は非常に重要です。 キャッシュはサーバーの負荷を軽減し、レスポンス時間を大幅に短縮する役割を果たします。頻繁にアクセスされるデータや計算結果を一時的に保管しておくことで、同じリクエストが来たときに再計算や再取得をせずにすみます。 前半の"基礎知識編"では、主にキャッシュ戦略やAWSが提供しているインメモリのキャッシュサービスについてまとめ、記事の後半の"ハンズオン編では、"MemCached"を使ったクエリチューニングを実践していきます。 キャッシュ戦略種別 キャッシュ戦略は、システムの目的や構成、負荷の性質に応じて選択することが重要です。主に、「ローカル or リモート」、「リード or ライト」、「インライン or アサイド」の三つの軸で戦略が分類されます。それぞれの軸での選択がパフォーマンスや一貫性に大きな影響を及ぼします。 ローカル or リモート ローカルキャッシュ 一般にはブラウザのようなクライアント側にデータを一時的に保存する方法を指します。これにより、データがローカルに存在すればサーバーへの再度のアクセスを避けることができ、アプリケーションのパフォーマンスが向上します。 リモートキャッシュ 一つ以上の専用のキャッシュサーバーにデータを保持します。リモートキャッシュは一般的には複数のクライアント間でのデータ共有に適しており、大規模なシステムでよく使われます。 リード or ライト リードキャッシュ(読み込み時にキャッシュ) データを読み取る際にキャッシュを利用する戦略です。データソースからのデータを初めて読み取る際には時間がかかりますが、その後のアクセスはキャッシュされたデータを利用するため、大幅に読み取り時間が短縮されます。頻繁に同じデータを読み取るアプリケーションやサービスにおいて特に有効です。 ライトキャッシュ(書き込み時にキャッシュ) データを書き込む際にキャッシュを利用する戦略です。データをデータソースに直接書き込む代わりに、一旦キャッシュにデータを保存します。そして、キャッシュからデータソースへの書き込みはバックグラウンドで効率的に行われます。これにより、アプリケーションは迅速にレスポンスを返すことが可能となり、特に書き込み操作が頻繁に行われるアプリケーションで有効です。 インライン or アサイド インラインキャッシュ データを取得するために呼び出すサービスからは透過的で、キャッシュ自身が上流のデータソースからデータを取得する作業を行います。例えば、AmazonのCloudFrontはインラインキャッシュとして機能し、ユーザーがリクエストを送ると、最も近いエッジロケーションからコンテンツを迅速に配信します。 アサイドキャッシュ キャッシュはデータソースから独立して更新されます。これにより、キャッシュとデータソース間で一貫性を維持するための追加のロジックが必要となります。 Amazon Web Services (AWS)が提供するAWS Elastic Cacheは、インメモリデータストアとキャッシュサービスを提供します。これにより、アプリケーションからのデータアクセス時間が大幅に短縮され、パフォーマンスが向上します。大量のリードトラフィックを処理するWebサーバー、スケール可能なリアルタイム分析、高速なトランザクション処理、レイテンシに敏感なユースケースに特に適しています。 MemCached MemCacheは、AWS Elastic Cacheのサポートするキャッシュエンジンの一つで、シンプルなキー値ストアとして動作します。また、マルチスレッドに対応しているため、マルチコアプロセッサの能力を最大限に活用できます。単純なデータ構造を持つデータや小規模なデータセットに最適で、メモリ効率が非常に良いという特徴があります。 Redis RedisもAWS Elastic Cacheのサポートエンジンで、より高度なデータ構造やトランザクションをサポートしており、さらに、リスト/ハッシュ/セット/ソート済みセットなどのリッチなデータ型にも対応しています。しかし、Redisはシングルスレッドの設計を採用しているため、一度に1つのコマンドしか実行できないという制限があります。大規模で複雑なデータ構造を持つデータセットや高いパフォーマンス要求のあるアプリケーションに適しています。 ハンズオン編 ここからは実際のコードでキャッシュを使ったパフォーマンス・チューニングのデモを行ってみます。 本記事ではローカル環境でDockerを使って動作確認を行います。キャッシュサーバーとしては、 memcached を利用します。 なお、今回は リード・アサイド戦略 を用いたケースを想定します。つまり、初回読取り時(キャッシュがヒットしない場合)はDBからデータを取得し、次回以降のリクエストの場合は、キャッシュから取得します。図式化すると以下の通りです。 なお、本記事作成時における、筆者のローカルマシンの環境は以下になります。 項目 内容 PC M1 MacBook Pro(14インチ、2021) OS MacOS Monterey IDE(統合開発環境) GoLand サンプルAPI概要 本ハンズオンでのユースケース 非常に遅いクエリを発行するAPIをキャッシュを用いてパフォーマンス改善する といったケースを想定しています。 ここでは、Goで書かれたAPI(Frameworkは Gin )をサンプルとして用います。そして、このアプリケーションには2つのエンドポイントが用意されております。 (1)スロークエリAPI(path:/db/1) 1つ目は、リクエストが発生するとDB(MySQL)に問い合わせを行います。ただし、 SELECT id, value, SLEEP(10) FROM customers WHERE id = ? とあるように、クエリが完了するまでに10秒を有するという非常に遅い処理です。 コードの詳細はこちら(main.go) main.go // ex. http://localhost:8080/db/1 r . GET ( "/db/:id" , func ( c * gin . Context ) { paramId := c . Param ( "id" ) var result Customer // 10秒遅延させるクエリの実行 err := db . QueryRow ( "SELECT id, value, SLEEP(10) FROM customers WHERE id = ?" , paramId ) . Scan ( & result . ID , & result . Value , & result . SleepResult ) if err != nil { c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ()}) return } c . JSON ( http . StatusOK , result ) }) (2)キャッシュを活用したAPI(path:/cache/1) 2つ目は、リクエストが発生すると、まずキャッシュサーバーに値を確認し、存在しなければ、DBから値を取得しキャッシュサーバーに保存した上で結果を返却します。2回目以降のリクエストに関しては、キャッシュサーバーから値を取得する形になります。 コードの詳細はこちら(main.go) main.go // ex. http://localhost:8080/cache/1 r . GET ( "/cache/:id" , func ( c * gin . Context ) { paramId := c . Param ( "id" ) // キャッシュから取得 item , err := mc . Get ( paramId ) // キャッシュがない場合 if err == memcache . ErrCacheMiss { var result Customer // 10秒遅延させるクエリの実行 err := db . QueryRow ( "SELECT id, value, SLEEP(10) FROM customers WHERE id = ?" , paramId ) . Scan ( & result . ID , & result . Value , & result . SleepResult ) if err != nil { c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ()}) return } // キャッシュ登録 resultBytes , err := json . Marshal ( result ) if err != nil { c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ()}) return } item = & memcache . Item { Key : paramId , Value : resultBytes , } if err := mc . Set ( item ); err != nil { c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ()}) return } c . JSON ( http . StatusOK , result ) return } if err != nil { c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ()}) return } // キャッシュ結果をレスポンス用に加工する result := Customer {} if err = json . Unmarshal ( item . Value , & result ); err != nil { c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ()}) return } c . JSON ( http . StatusOK , result ) }) ローカル編 インフラ構成図は以下の通りです。 ここから、ローカルマシン上でキャッシュを使ったクエリチューニングを実際に体感してみましょう。 まずはじめに、以下のRepositoryのクローンからお願いします。その後、 README.md を参考に環境構築をお願いします。 Terminal # クローン $ git clone [email protected]:WebEngrChild/go-rds-memcached.git # Docker起動 $ docker compose up -d # API起動 $ docker compose exec app go run main.go スロークエリAPIの測定 キャッシュサーバーを用いたパフォーマンスチューニングをする上でも、"現状分析"は必ず行うべきです。実装前後でどのような改善ができたのかを定量的に計測することで施策の良し悪しを振り返るための良い物差しになるからです。 それでは、上記で説明した一つ目の スロークエリAPI にリクエストを投げてみます。レスポンスが返却されるまで、10秒程度のタイムラグが発生するはずです。 Terminal > which ab /usr/sbin/ab > curl http://localhost:8080/db/1 { "id" :1, "value" : "Initial Value" , "sleepResult" :0 } Apache Benchを使った負荷テスト 次に、 Apache Bench を使ってAPIのパフォーマンスを計測してみます。なお、Macの場合はデフォルトでインストールされています。本ツールの詳細は以下の記事を参考にしてみてください。 Terminal > ab -n 30 -c 30 http://localhost:8080/db/1 Benchmarking localhost ( be patient ) ... # 一部割愛 # レイテンシの分布(パーセンタイル情報) Percentage of the requests served within a certain time ( ms ) 50% 10079 66% 10083 75% 10085 80% 10087 90% 10088 95% 10089 98% 10089 99% 10089 100% 10089 ( longest request ) ここで、-nオプションは合計のリクエスト数を指定、-cオプションは並列リクエストの数を指定しています。ここでは、30並列で30回のリクエストを実行しています。実行結果から、レイテンシの分布からも各パーセンタイルで10秒程度遅延が発生していることがわかります。 スロークエリの確認 ローカル環境で利用しているMySQLイメージはデフォルトでスロークエリログを出力します。また、 docker-compose.yml でスロークエリをローカルマシン側にマッピングしています。 コードの詳細はこちら(docker-compose.yml) docker-compose.yml mysql : container_name : mysql build : .docker/mysql/ volumes : - .docker/mysql/init:/docker-entrypoint-initdb.d - .docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf - .docker/mysql/log:/var/log/mysql # ここでログをマッピング environment : - MYSQL_ROOT_PASSWORD=${DB_PASS} ports : - " 3306:3306" networks : sample_go_network : ログを確認すると、いくつか出力されていることがわかります。 Query_time でクエリ実行に10秒程度かかっている事がわかります。 slow.log # Time: 2023-07-22T02:32:36.702832Z # User@Host: root[root] @ [192.168.192.4] Id: 24 # Query_time: 10.007020 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 1 SET timestamp=1689993146; SELECT id, value, SLEEP(10) FROM customers WHERE id = '1'; キャッシュを使ったチューニング後の計測 Terminal # 1回目は同じく10秒程度かかる > curl http://localhost:8080/cache/1 { "id" :1, "value" : "Initial Value" , "sleepResult" :0 } # 2回目以降は遅延の発生はしない > curl http://localhost:8080/cache/1 { "id" :1, "value" : "Initial Value" , "sleepResult" :0 } 1回目のリクエストは、 MemCached に保存される前であるため、同様に10秒程度遅延が発生します。しかし、2回目以降は保存されたデータを用いるため、レスポンスの遅延は発生しません。 Terminal > ab -n 30 -c 30 http://localhost:8080/cache/1 Benchmarking localhost ( be patient ) ... # 一部割愛 # レイテンシの分布(パーセンタイル情報) Percentage of the requests served within a certain time ( ms ) 50% 22 66% 22 75% 24 80% 25 90% 26 95% 26 98% 10047 99% 10047 100% 10047 ( longest request ) Apache Bench でもレスポンス遅延が大幅に改善している事が確認できます。また、スロークエリも、初回以外は出力されていない事がわかります。 AWS編 インフラ構成図 注意点 利用するAWSの各リソースは課金対象となるものが多く含まれています。個人利用される方は特に注意してください。発生した費用に関して一切責任を負いかねます。 事前設定 既に設定済みの方は不必要ですが、本記事ではルートに近い IAMユーザー の準備と AWS CLI のインストールが必要になります。 Terraform はDocker上で起動するため、インストールは不要です。 AWS CLIのインストール rootに近い権限を持つIAMユーザーの作成 また、以下のRepositoryのクローンからお願いします。その後、 README.md を参考に環境構築をお願いします。 Terminal # クローン $ git clone [email protected]:WebEngrChild/go-rds-memcached.git # Docker起動 $ docker compose up -d # API起動 $ docker compose exec app go run main.go Terraformコード紹介 本記事では、AWSのリソース構築にTerraformを利用しています。詳細の説明は省略しますが参考までにコードを掲載しておきます。 (1)main.tf:provider定義 main.tf provider "aws" { region = "ap-northeast-1" } (2)variables.tf:プロジェクト名などの変数定義 variables.tf variable "project" { type = string default = "go-api" } variable "environment" { type = string default = "dev" } variable "cidr_blocks" { description = "List of CIDR blocks" type = list ( string ) default = [ "<ご自身のグローバルIPを設定してください>/32" ] } (3)data.tf:ECR, SSM, AMIといった既存リソースの取得 data.tf # ------------------------------------------------------------# # Existing ECR # ------------------------------------------------------------# data "aws_ecr_repository" "existing" { name = "go-dev-repo" } # ------------------------------------------------------------# # Existing SSM Parameter Store # ------------------------------------------------------------# data "aws_ssm_parameter" "existing" { name = "/env" } # ------------------------------------------------------------# # Existing ECR # ------------------------------------------------------------# data "aws_ecr_repository" "existing" { name = "go-dev-repo" } # ------------------------------------------------------------# # Existing SSM Parameter Store # ------------------------------------------------------------# data "aws_ssm_parameter" "existing" { name = "/env" } # ------------------------------------------------------------# # Latest EC2 AMI # ------------------------------------------------------------# data "aws_ssm_parameter" "ami" { name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" } data.tf # 最新のAMIイメージを取得 data "aws_ssm_parameter" "ami" { name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" } (3)vpc.tf:VPC, subnetといったネットワーク構築 vpc.tf # ------------------------------------------------------------# # local variables # ------------------------------------------------------------# locals { zones = [ "1a" , "1c" , "1d" ] public_cidrs = [ "10.0.1.0/24" , "10.0.2.0/24" , "10.0.3.0/24" ] pri

類似記事(ベクトル近傍)