Skip to content

[lerna-util-sequence] (Cassandra) SequenceStore での処理が失敗した場合正常に採番できない場合がある #49

@tksugimoto

Description

@tksugimoto

概要

Akka Typed対応時に以下の問題が発覚。

Cassandraを使った採番機能において、Cassandraへの永続化失敗時に 1.重複した番号を採番する OR 2.採番できなくなる 可能性がある不具合。
※本事象はCassandraへの永続化に失敗、つまり、過半数ノードが停止する複合障害が発生しCassandraが停止した場合にのみ発生する。

1. 重複した番号を採番する

「次に採番する値の決定 → 予約(予約番号の永続化) → 採番」 の順序で処理を行うが、予約で永続化が失敗した場合に予約はできていないが既に決定した次に採番する値を採番値として発行してしまうため、アプリケーションを再起動し永続化済情報から予約番号を再取得した際に
重複した採番を行ってしまう。

2. 採番できなくなる

MAX値に達して番号をリセットした情報をCassandraに永続化する処理で失敗すると、処理状態が「リセット中」で留まり、
一切のコマンドを受け付けなくなり採番ができなくなってしまう。

これらの問題への対策として、採番処理の状態管理と各状態の処理の見直しを行った。

  • 予約するケース
    • 変更前: 採番可能 → 予約中(値が枯渇している場合は採番を保留) → 採番可能
    • 変更後: 採番可能 → 採番値枯渇(採番を保留) → 採番可能
  • リセットするケース
    • 変更前: 採番可能 → リセット中(値が枯渇しているため採番を保留) → 採番可能
    • 変更後: 採番可能 → 採番値枯渇(採番を保留) → 採番可能

影響範囲

条件を満たす事象が起きた該当 seqId のみであり、その他の seqId には影響しない。

1. 重複した番号を採番する

発生条件

以下のand条件

  • (Cassandra)SequenceStore への予約要求に失敗 (SequenceStore.ReservationFailed)
    • Cassandra 不調 ( write-consistency = QUORUM & 複数台構成推奨のため 2台以上が同時に止まる場合)
  • 予約要求に失敗後、採番された番号を含む値の予約が成功する前にアプリが再起動
    • graceful/障害問わない

原因

予約失敗時に reserving -> ready へ遷移するが ready 内部で 採番の可否判断に maxReservedValue を使用していないため
(= ready ならば必ず maxReservedValue 以下であるという暗黙の前提があるコードになっている)。

lerna-app-library/SequenceFactoryWorker.scala at v2.0.0 · lerna-stack/lerna-app-library

  private[this] def ready(implicit sequenceContext: SequenceContext): Behavior[Command] =
    Behaviors.receiveMessage[Command] {
      case msg: GenerateSequence =>
        if (msg.sequenceSubId === sequenceSubId) {
          import sequenceContext._

          if (nextValue <= maxSequenceValue) {
            msg.replyTo ! SequenceGenerated(nextValue, sequenceSubId)
            logger.debug("SequenceGenerated when ready: {}", nextValue)
          } else {
            stashBuffer.stash(msg)
          }

lerna-app-library/SequenceFactoryWorker.scala at v2.0.0 · lerna-stack/lerna-app-library

      case WrappedSequenceStoreResponse(SequenceStore.ReservationFailed) =>
        // 即座にリトライしたところで予約できる見込みは薄いケースがあるので、
        // クライアントから再び採番を要求されたときにリトライする方針とする
        stashBuffer.unstashAll(ready)

2. 採番できなくなる(常に Future.failed となる)

発生条件

以下のand条件

  • 採番値が上限値を超え、初期値へのリセットが試みられる
  • (Cassandra)SequenceStore への予約要求に失敗 (SequenceStore.ReservationFailed)
    • Cassandra 不調 ( write-consistency = QUORUM & 複数台構成推奨のため 2台以上が同時に止まる場合)

原因

def resetting: Behavior[Command] での SequenceStore.ReservationFailed の handling 漏れ

lerna-app-library/SequenceFactoryWorker.scala at v2.0.0 · lerna-stack/lerna-app-library

    case WrappedSequenceStoreResponse(SequenceStore.ReservationFailed)          => Behaviors.unhandled // FIXME

v1.0.0 の時点で存在したため Akka typed 対応のPR (#29) ではそのままとした

関連する議論

修正を試みたが新たな問題が見つかり取り下げた PR

修正版の PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions