1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

セッション外部化で同時アクセス発生した際の挙動を動かしながら理解する

Last updated at Posted at 2024-11-08

わかったこと

同時アクセスが発生した場合、後にリクエストが終了したシーケンスのセッション情報で上書きされる。
理由はおそらく、リクエスト開始時にRedisからデータを取得してSessionオブジェクトを作成し、リクエスト終了時にセッション内容をcommitするため。

そして大きなポイントは2つ。
・Sessionオブジェクトは同時アクセスした同一セッションの処理同士で共有されない。
・Sessionオブジェクトの操作有無に関わらず、リクエスト終了時にセッション内容がcommitされる。

検証内容

今回はJavaアプリを使って検証をしています。

検証前提

フレームワーク・ライブラリ

SpringBoot、SpringSessionDataRedisを利用しています。

gradle.build
plugins {
    id 'org.springframework.boot' version '2.7.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    ...(省略)
}

sourceCompatibility = '18'
...(省略)

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.session:spring-session-data-redis'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    ...(省略)
}

セッションはRedisに格納、その他の設定はデフォルト(なのでリクエスト終了時にセッション内容コミット)。

application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.session.store-type=redis

アプリ仕様

下記のように4APIを用意。

@RestController
public class Controller {

    private static final Logger logger = LoggerFactory.getLogger(Controller.class);

    @PostMapping("/create-user-info")
    public String createUserInfo(HttpSession session){
        logger.info("[create-user-info] セッションにユーザー情報を入れます");
        UserInfo userInfo = getUserInfoMock();
        session.setAttribute("USER_INFO", userInfo);
        logger.info("[create-user-info] セッションにユーザー情報を入れました(id=99,name=taro,role=ADMIN)");
        return "ok";
    }

    @PostMapping("/change-role")
    public String changeRole(HttpSession session) throws Exception{
        logger.info("[change-role] ユーザー情報のロールを変更します");
        UserInfo userInfo = (UserInfo) session.getAttribute("USER_INFO");
        userInfo.setRole("CLIENT");
        session.setAttribute("USER_INFO", userInfo);
        logger.info("[change-role] ユーザー情報のロールを変更しました(role=CLIENT)");

        logger.info("[change-role] 待機を開始します");
        Thread.sleep(10 * 1000);
        logger.info("[change-role] 待機が終了しました");
        return "ok";
    }

    @PostMapping("/change-name")
    public String changeName(HttpSession session) throws Exception{
        logger.info("[change-name] ユーザー情報の名前を変更します");
        UserInfo userInfo = (UserInfo) session.getAttribute("USER_INFO");
        userInfo.setName("new taro");
        session.setAttribute("USER_INFO", userInfo);
        logger.info("[change-name] ユーザー情報のロールを変更しました(name=new taro)");
        logger.info("[change-name] 待機を開始します");
        Thread.sleep(10 * 1000);
        logger.info("[change-name] 待機が終了しました");
        return "ok";
    }

    @GetMapping("/user-info")
    public UserInfo getUserInfo(HttpSession session) throws Exception{
        logger.info("[user-info] 待機を開始します");
        Thread.sleep(10 * 1000);
        logger.info("[user-info] 待機が終了しました");
        return (UserInfo) session.getAttribute("USER_INFO");
    }

    private UserInfo getUserInfoMock(){
        return new UserInfo(99L, "taro", "ADMIN");
    }
}

API仕様は下記

  • 情報初期化(create-user-info)
    • id=99,name=taro,role=ADMIN
  • 名前変更(change-name)
    • nameをnew taroに変更
  • ロール変更(change-role)
    • roleをCLIENTに変更
  • 情報確認(user-info)
    • セッション情報を参照し返却

利用コマンド

curl
# 情報初期化
curl -XPOST http://localhost:8080/create-user-info -H 'Cookie: SESSION=NWQ1NmQzZGYtMmFhZi00OTVmLTljNTQtNTUxMzk0M2YxZTNl' -v

# 名前変更
curl -XPOST http://localhost:8080/change-role -H 'Cookie: SESSION=NWQ1NmQzZGYtMmFhZi00OTVmLTljNTQtNTUxMzk0M2YxZTNl' -v

# ロール変更
curl -XPOST http://localhost:8080/change-name -H 'Cookie: SESSION=NWQ1NmQzZGYtMmFhZi00OTVmLTljNTQtNTUxMzk0M2YxZTNl' -v

# 情報確認
curl http://localhost:8080/user-info -H 'Cookie: SESSION=NWQ1NmQzZGYtMmFhZi00OTVmLTljNTQtNTUxMzk0M2YxZTNl' -v

検証内容

並列リクエスト(あるリクエストの処理中に別のリクエストが開始して終了する)

下記のような処理パターン。ある処理中(changeName)に別のリクエスト(changeRole)が開始して終了している。

この時、下記のように名前だけ変更される。最後にリクエストが終了したchangeNameの更新が反映され、同時実行されたchangeRoleの変更は破棄されている。

項目 Before After
id 99 99
name taro new taro
role ADMIN ADMIN

この結果から後にリクエストが終了したデータで上書きされ、同時アクセスによりデータ整合性が損なわれるケースがあることが分かる。

並列リクエスト(あるリクエストの処理中に別のリクエストが開始、開始順に終了する)

この時、下記のように名前だけ変更される。最後にリクエストが終了したchangeRoleの更新が反映され、最初のchangeNameの変更は破棄されている。

項目 Before After
id 99 99
name taro taro
role ADMIN CLIENT

この結果からchangeRoleでgetAttributeしたデータは、changeNameの変更を反映していないことが分かる。

並列リクエスト(あるリクエストの処理中に別のリクエストが開始、開始順に終了する、後続処理はセッション操作をしない)

この時、下記のように何も変更されない。最後のSessionオブジェクト操作を伴わないshowInfoの取得情報が反映され、changeNameの変更は破棄されている。

項目 Before After
id 99 99
name taro taro
role ADMIN ADMIN

この結果から、Sessionオブジェクトの操作有無に関わらず、セッション情報はcommitされて上書きされることが分かる。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?