yumの設定ファイルを確認する

概要

yumのグローバル設定やリポジトリ設定ファイルなどの各種設定ファイルをおさらいします。対象バージョンとしてCentOS6系に存在するファイルを元に調査をしていきます。
なお本記事は参考サイトとして挙げているサイトの情報要約していますので正確な情報を求めている方は参考サイト(特に公式の情報)に目を通すことを推奨します。

まず/etc/に存在するyum関連のファイルを確認してみます

始めに実環境に存在する yum に関連する設定ファイルと思われるものを確認します。
簡単に /etc/ 配下を調べると下記のようなファイルやディレクトリを発見しました。

  • /etc/yum.conf ファイル
  • /etc/yum.repos.d/ ディレクトリ
  • /etc/yum/ ディレクトリ

本記事ではこれらについてそれぞれどういう役割を持っているのかをまとめることを目的とします。

/etc/yum.conf ファイルの設定

それではまず /etc/yum.conf ファイルを確認してみましょう。
調査環境では下記のようなファイルが定義されていました。ひとつひとつ見ていきます。

[main]
cachedir=/var/cache/yum/$basearch/$releasever
keepcache=0
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
installonly_limit=5
bugtracker_url=http://bugs.centos.org/set_project.php?project_id=19&ref=http://bugs.centos.org/bug_report_page.php?category=yum
distroverpkg=centos-release

cachedir

Yumがキャッシュとデータベースを格納するディレクトリへの絶対パス< $basearch と $releasever という変数を利用しています。Yum変数のリストを確認しましょう。
すると $basearch はシステムのベースアーキテクチャを参照できます。最近のマシンですと64ビットが多いと思いますので x86_64 という変数に解決されます。
また $releasever は Red Hat のリリースバージョンを参照できるとのことですね。同じファイルの distroverpkg=centos-release の値を元にバージョンを取得します。
ここでは centos-release パッケージを元にバージョンを取得しているということになります。
centos-release パッケージのバージョンを調べてみます。

yum info centos-release
...
Installed Packages
Name        : centos-release
Arch        : x86_64
Version     : 6
...

すると Version 6 という記述があるので、cachedirに関しては /var/cache/yum/x86_64/6 というディレクトリに解決されていることがわかりました。
ローカルキャッシュはこのディレクトリに保存されることになります。

keepcache

インストールに成功した後にヘッダーとパッケージのキャッシュを保持するかどうかをコントロールします。
デフォルトではキャッシュをしない設定になっています。これはパッケージの移り変わりが早いので常に最新の情報を参照してほしいということかなと感じます。
0 – キャッシュを保持しません。デフォルト設定です
1 – キャッシュを保持します

debuglevel

1から10までの整数値を取ります。文字通りdebugのレベルをコントロールします。0を設定した場合はデバッグ出力を無効にします。
こちらはログファイルへの出力ではなく、yumコマンド実行時の標準出力の内容が対象になります。

logfile

ログ出力を行うファイルへの絶対パスです。
出力される内容には日時とアクション(どのパッケージが インストール/削除/更新 された)という情報が書き込まれています。

exactarch

OSがサポートするアーキテクチャを考慮してパッケージの更新を行うかどうかをコントロールします。
アーキテクチャを入れない場合、例えばi386のパッケージを更新するためにi686パッケージをインストールするなどの挙動を許可することになります。
0 – 正しいアーキテクチャを考慮に入れません
1 – 正しいアーキテクチャを考慮に入れます。デフォルト設定です

obsoletes

更新実行時に obsoletes処理ロジックを有効にするか無効にするかをコントロールします。

obsoletes処理と言われても何のことだかピンとこないですよね。
例えば、あるパッケージAがもともとパッケージBに依存していたとしましょう。
パッケージAが更新されパッケージBに依存しなくなったとします。このときにパッケージBを obsoletes 扱いにするというような言い方をします。
するとパッケージAを更新する際に、パッケージBは obsoletes 扱いですよ、という記述があれば更新時に依存していたパッケージBを排除します。
というようなロジックを適用するかどうか、という設定項目になります。
0 – obsoletes処理ロジックを無効にします
1 – obsoletes処理ロジックを有効にします。デフォルト設定です

gpgcheck

GPG署名の確認をコントロールします。
GPG署名に関しては こちら のページに書いてあります。
GPGというツールを利用してパッケージに対して署名したり、パッケージの署名を検証することでパッケージが改ざんされていないことを確認することができます。
この項目は yum.conf に設定された項目を各リポジトリごとの .repo ファイルで設定をオーバーライドすることができます。
0 – 全リポジトリの全パッケージ上でのGPG署名確認を無効にします
1 – 全リポジトリの全パッケージ上でのGPG署名確認を有効にします。デフォルト設定です

plugins

Yumのプラグインについては こちら にまとまっています。
インストールされたプラグインは /etc/yum/pluginconf.d/ のディレクトリに設定ファイルを持ち、この項目で設定されたプラグインの有効/無効の設定を各種プラグインの設定ファイルでオーバーライドすることができます。
0 – Yumのプラグインを無効にします
1 – Yumのプラグインを有効にします。デフォルト設定です

installonly_limit

単一のパッケージに同時にインストール可能なバージョンの最大数を表す整数を設定します。
複数のバージョンをインストールすることがあるのか?と思いますが、カーネルパッケージなんかではそういうことがあるようで、2以下に設定してしまうことは推奨されていません。

bugtracker_url

こちらに関しては参考ページには記載はありませんでしたが、centosのディストリにてバグを管理しているページへのリンクを記載しているようです。

上記に見てきたようにこの [main] セクションを定義することでyumのグローバル設定を定義できます。
またファイル内にリポジトリ用のセクション [repository] を作成して各リポジトリの設定を定義することもできますが、これは推奨されません。
各リポジトリの設定は /etc/yum.repos.d/ ディレクトリ内に .repoファイル を作成することで定義することが推奨されます。
この方が各リポジトリと設定がどこに集約されているかということがファイルを見るだけで読み取れ、管理が容易に行えるからです。

/etc/yum.repo.d/*.repo ファイルの設定

こちらも設定ファイルを参考に一つ一つ確認していきましょう。
デフォルトでは下記のようなファイルが存在するようですが、一つ例に取ってみていきます。

CentOS-Base.repo
CentOS-Debuginfo.repo
CentOS-Media.repo
CentOS-Vault.repo
CentOS-fasttrack.repo

CentOS-Base.repo を見ていきましょう

[base]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6

#released updates
[updates]
name=CentOS-$releasever - Updates
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6

#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6

#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6

#contrib - packages by Centos Users
[contrib]
name=CentOS-$releasever - Contrib
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=contrib&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/contrib/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6

いろいろなセクション(=リポジトリ)を持っていることが見て取れます。
それぞれを見ていきます。

セクション名

まずセクション名称はidとして機能するため重複することのない文字列でなければなりません。おそらく任意に定義できるでしょう。

name

nameはリポジトリの名称で人間が読み取れる形式で記述します。

baseurl または mirrorlist

リポジトリのrepodataディレクトリがあるディレクトリへのURLです。
centosのディストリビューションでは baseurl か mirrorlist のどちらかがあれば問題ないようです。

試しに mirrorurl に記載のある変数を解決したページ http://mirrorlist.centos.org/?release=6&arch=x86_64&repo=contrib&infra= を叩いてみると
以下のようなリポジトリのミラーリストが返却されました。

http://mirror.fairway.ne.jp/centos/6.9/contrib/x86_64/
http://ftp.iij.ad.jp/pub/linux/centos/6.9/contrib/x86_64/
http://www.ftp.ne.jp/Linux/packages/CentOS/6.9/contrib/x86_64/
http://ftp.riken.jp/Linux/centos/6.9/contrib/x86_64/
http://ftp.jaist.ac.jp/pub/Linux/CentOS/6.9/contrib/x86_64/
http://ftp.nara.wide.ad.jp/pub/Linux/centos/6.9/contrib/x86_64/
http://ftp.yz.yamagata-u.ac.jp/pub/linux/centos/6.9/contrib/x86_64/
http://mirror.vodien.com/centos/6.9/contrib/x86_64/
http://mirror.vastspace.net/centos/6.9/contrib/x86_64/
http://mirror.nus.edu.sg/centos/6.9/contrib/x86_64/

ユーザのアクセス元IPや負荷状況に応じてこの中のどれかを利用する。ということですね。

gpgcheck

こちらは先程のグローバル設定項目に出てきましたね。
gpgcheckを実行するかどうかの設定です。先に説明したようにこの項目は各種リポジトリの設定項目でオーバーライドすることができます。

gpgkey

こちらは先程のグローバール設定の項目でも取り上げましたが、GnuPGキーファイルを指す項目です。
このリポジトリのパッケージは開発者によりGnuPG秘密鍵により署名されています。
インストールするパッケージが改ざんされていないことをローカルに保存されている公開鍵 file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6 を用いて検証するということになります。

/etc/yum ディレクトリのファイル

こちらのディレクトリにはグローバル設定やリポジトリ設定以外のファイルが存在します。
具体的にはプラグイン設定、パッケージの保護設定、変数の定義などが存在するのでそれぞれの設定を確認してみます。

/etc/yum/pluginconf.d のファイルの設定

こちらのディレクトリにはプラグインごとの設定ファイルが存在しているようです。
fastestmirror というプラグインを例にとって /etc/yum/pluginconf.d/fastestmirror.conf というファイルを見てみます。

[main]
enabled=1
verbose=0
always_print_best_host = true
socket_timeout=3
hostfilepath=timedhosts.txt
maxhostfileage=10
maxthreads=15

定義としては見たところプラグイン固有の設定がされているようです。
共通項目としては enabled くらいのものだと思うので説明は割愛します。

/etc/yum/protected.d のファイルの設定

こちらのディレクトリにはパッケージが削除されないように保護を行う設定を行うことができます。
デフォルトの設定では保護のためのファイルは存在していませんでしたが、こちらに保護対象のパッケージを定義することで yum erase を行う際にチェックが入るようになります。

/etc/yum/vars のファイルの設定

こちらは確認したところリポジトリのmirrorurlなどの設定で使用されていた $infra 変数の定義を行っていました。
同様にして変数名をファイル名にし、ファイルの中身を変数値に展開するように様々な変数の定義を行うようです。

cat /etc/yum/vars/infra
stock

まとめ

以上で設定ファイルの全体概要が見えてきたのではないかと思います。
リポジトリ上でのパッケージ解決や内部実装などについても今後取り上げていければと思います。

参考

Red Hat Enterprise Linux 導入ガイド
mainオプションの設定
repositoryオプションの設定
What is the difference between base URL and mirrorlist in Yum?


mysqlのネクストキーロックと挿入インテンションギャップロックのデッドロックを確認する

概要

先日mysqlを利用したアプリケーションにおいてデッドロックが発生しました。
あちゃぱーと思いつつもせっかくなので自分の中で消化しきれいなかった部分をこれを機に再確認してみることに。

この記事ではmysqlのデフォルトの分離レベル(Repeatable Read)においての レコードロック / ネクストキーロック / ギャップロック / 挿入インテンションギャップロック というハイカラな単語と結びつけながら自分なりに解釈したものを解説します。
と、赤字で書きましたが、始めに詫びを入れておきます。
ロックの解釈はドキュメントを読むだけで詳細に把握するのは非常に難しく、もしかしたら間違っていることを言っているかもしれません。そしたら本当に申し訳ありません。

解決したい問題

はじめに問題を共有したほうがやる気もでるしわかりやすい。
というわけで具体的に問題をものすごく単純にしたテーブルを用意しました。
こちらをつかって問題を再確認したいと思います。

テーブル

はい。めちゃくちゃ単純化しました。主キーとさらにインデックスを付けた属性だけが存在するテーブルです。わかりやすい。

CREATE TABLE test (
  id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  secondaryId bigint(20) unsigned NOT NULL,
  PRIMARY KEY (id),
  KEY idxSecondaryId (secondaryId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

データ

データを用意します。一応ギャップロックとかいう話も出てくるのでギャップが存在するように飛び飛びのデータを入れてみます。これまたわかりやすい。

INSERT INTO test (id, secondaryId) VALUES (10, 10), (20, 20), (30, 30), (40, 40);

デッドロック

さてここで用意ができましたので、問題を再現してみたいと思います。
ここでトランザクション1(Trx1)とトランザクション2(Trx2)という二人のお友達に協力してもらおうと思います。
上から順に時系列で記載します。左側にどちらのトランザクションが実施したかという情報を記載します。

Trx1: begin;
Trx2: begin;
Trx1: DELETE FROM test WHERE secondaryId = 80;
Trx2: DELETE FROM test WHERE secondaryId = 90;
Trx1: INSERT INTO test VALUES (80, 80); // ここでTrx1がロックが取得できずに待ち状態になります
Trx2: INSERT INTO test VALUES (90, 90); // ここでTrx2もロックを取得できずに待ち状態となり、Trx1とのデッドロック状態となります

ということで

これがどういう仕組でデッドロックするか理解している方、これ以上この記事を見る必要はないです。

さておき、問題が確認できました。
実際にはアプリケーションで特定のキーによって管理されたデータを一回削除して再度データを入れ直す、というような処理を実装していました。
データ構造は全く違いますが、問題の本質はこれと一緒になります。
さて、この状況で中で一体何が起こっているのか見ていきましょう。

基本

まずここに上げる公式マニュアルを合わせてくまなく目を通しましょう。
合わせてよくまとまっているサイトなどもあると思うので、嘘か本当か見極めつつ検索して参考にしましょう。
なお本記事では各種ロックがどういうものかという基本的なことは一切説明しません。

さらに言葉の定義をちゃんとイメージできた方がいいと思うのでいくつかここで補足します。

トランザクションとロック

「ロックした」とかいう表現が意外と通じるのであるのであたかも「ロック」というのが動作とか状態のように解釈されがちだと感じますが、自分なりに解釈した結果ですと「ロック」とはアクセスする権利とイメージするとしっくり来ると思います。

例えばあるトランザクションのsql実行が別のトランザクションによって待ち状態になったとしましょう。
この時まさに「ロックした」といってもいいんですが、もっと正確に言うと「ロックを取得しようとしたが取得できずに待ち状態になった」というのが正しいです。

つまりロックというのは単なるアクセス権なのです。
ものすごく単純にいうと、この辺の話はトランザクションという登場人物がいて、レコードを操作する際にロック(アクセス権)を確保したり開放したりして自分の作業の影響範囲を定義しているのです。
でロックが先に取得されていたら、ロックが解放されるまで終わるまで待つ。ロックが開放されたら自分がロックを取得してレコードを操作する。シンプルにそれだけの話です。

ロックとは

でロックにも3つほど種類が存在します。冒頭にも挙げた レコードロック / ギャップロック / ネクストキーロック です。
マニュアルを読むとなるほどこれらの性質はなんとなくわかります。ですが どういうときにどのロックが使用されるのか というところがいまいちマニュアルだけだと分からない。
というわけでここが本記事の肝になります。

調査

というわけでじゃあ具体的にどういうことが起こっているの?という調査をします。

調査環境は osx で MariaDB を使用します。バージョンは下記。

MariaDB [test]> select version();
+-----------------+
| version()       |
+-----------------+
| 10.1.21-MariaDB |
+-----------------+

そして状態を確認するために下記のコマンドを実施しておきます。
これによってトランザクションの状態を出力する際にロックの詳細も一緒に出力してくれるようになります。

set global innodb_status_output_locks = ON;

どういうロックがかかっているのか

では先の例に沿って実行していきます。下記のdelete文を発行した時点で各トランザクションがどういうロックの取得を行っているのか確認してみます。

Trx1: begin;
Trx1: DELETE FROM test WHERE secondaryId = 80;

この時点で下記のコマンドによってトランザクションの状態を確認します。
モニターテーブルを作成してそちらを参照しても良いです。

show engine innodb status\G

するといろいろ出力されるのですが、トランザクションのロック取得状況を表す箇所を抜粋します。

---TRANSACTION 82897, ACTIVE 101 sec
2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 899, OS thread handle 0x700010b45000, query id 4569881 localhost root cleaning up
Trx #rec lock waits 7 #table lock waits 0
Trx total rec lock wait time 28 SEC
Trx total table lock wait time 0 SEC
TABLE LOCK table test.test trx id 82897 lock mode IX lock hold time 101 wait time before grant 0
RECORD LOCKS space id 5931 page no 4 n bits 72 index idxSecondaryId of table test.test trx table locks 1 total table locks 1  trx id 82897 lock_mode X lock hold time 101 wait time before grant 0

確認すると

  1. テーブルに対するインテンション排他ロック
  2. idxSecondaryIdインデックスに対する排他ネクストキーロック

が取得されているようです。テーブルに対するインテンションなロックはこの場合にはデッドロックを引き起こさないので無視します。
では続いてトランザクション2にも同じ手順に沿って実施してもらいましょう。

Trx2: begin;
Trx2: DELETE FROM test WHERE secondaryId = 90;

どうように show engin innodb status を実施すると

---TRANSACTION 82898, ACTIVE 1 sec
2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 945, OS thread handle 0x700010e57000, query id 4569884 localhost root cleaning up
Trx #rec lock waits 2 #table lock waits 0
Trx total rec lock wait time 0 SEC
Trx total table lock wait time 0 SEC
TABLE LOCK table test.test trx id 82898 lock mode IX lock hold time 1 wait time before grant 0
RECORD LOCKS space id 5931 page no 4 n bits 72 index idxSecondaryId of table test.test trx table locks 1 total table locks 2  trx id 82898 lock_mode X lock hold time 1 wait time before grant 0

トランザクション1と同様のロックを取得していることが確認できます。

ここで一つ注目したいのはこれら2つのステートメントの実施はロック待ち状態が発生することなく実施することができました。
つまり delete文によって取得した2つの異なるインデックス値に対するネクストキーロックの範囲は重なっていない ということがわかりました。

ネクストキーロックとは何なのだろう

これに気づいた時結構驚きました。
だってネクストキーロックって、指定の値に対してレコードロックとさらにその前のインデックスとのギャップを含むと説明があるではありませんか。
テストデータの末尾の idxSeconaryId は 40 になっています。
すなわち Trx1 の delete文は (40, 80] の範囲のネクストキーロックを取得し、 Trx2 の delete文は (40, 90] の範囲のネクストキーロックを取得するのではないかと考えました。

わかりやすいように図を用意しましょう。力作です。
横軸が idxSecondaryId のインデックスの並びで、ボックスが取得しようとしているロックの範囲を表しています。

fig1

しかしながら双方のロックが取得しているということは、この仮定が誤っているという結論になります。
この解釈は後で述べることにして、とりあえず先に進みます。

デッドロックの確認

ではロック取得待ちを引き起こすトランザクション1のステートメントを実施してみましょう

Trx1: INSERT INTO test VALUES (80, 80); // ここでTrx1がロックが取得できずに待ち状態になります

ここでロック取得待ちが発生します。トランザクション1のロック取得状態を確認してみましょう。

---TRANSACTION 82897, ACTIVE 2059 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1
MySQL thread id 899, OS thread handle 0x700010b45000, query id 4569893 localhost root update
INSERT INTO test VALUES (80, 80)
Trx #rec lock waits 8 #table lock waits 0
Trx total rec lock wait time 28 SEC
Trx total table lock wait time 0 SEC
------- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 5931 page no 4 n bits 72 index idxSecondaryId of table test.test trx table locks 1 total table locks 2  trx id 82897 lock_mode X insert intention waiting lock hold time 2 wait time before grant 0
------------------
TABLE LOCK table test.test trx id 82897 lock mode IX lock hold time 2059 wait time before grant 0
RECORD LOCKS space id 5931 page no 4 n bits 72 index idxSecondaryId of table test.test trx table locks 1 total table locks 2  trx id 82897 lock_mode X lock hold time 2059 wait time before grant 0
RECORD LOCKS space id 5931 page no 4 n bits 72 index idxSecondaryId of table test.test trx table locks 1 total table locks 2  trx id 82897 lock_mode X insert intention waiting lock hold time 2 wait time before grant 0

一つロックが増えていますね。これは挿入インテンションギャップロックというやつです。
そしてこれがトランザクション2の取得している idxSecondaryId = 90 に対するネクストキーロックによりブロックされているという状況になります。

そして最後にトランザクション2の方でも insert を実行してみましょう。

Trx2: INSERT INTO test VALUES (90, 90); // ここでTrx2もロックを取得できずに待ち状態となり、Trx1とのデッドロック状態となります
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

デッドロックが起こったことが確認できました。

考察

状況を整理して客観的に見たときに、下記のような条件が成り立っています。

  1. 2つのトランザクションがはじめにdelete文により取得したネクストキーロックはお互いのロック領域に重なっていない
  2. その後発行するinsert文が取得しようとする挿入インテンションギャップロックはお互いのネクストキーロックに重なっている

逆にこれが成り立つような状況を想像できればネクストキーロック、挿入インテンションギャップロックが取得しようとするロックの範囲を推測できます。

で自分なりに解釈した結論が下記のようになります

ネクストキーロックについて

ネクストキーロックはインデックス値の前のギャップも取得することもあるが、ギャップを取得しないこともある。つまりこの場合はレコードロックの範囲と等しい
これは具体的には Trx1 の発行する delete文は idxSecondaryId = 80 のレコードロックの範囲に等しく、 Trx2 の発行する delete文は idxSecondaryId = 90 のレコードロックの範囲に等しい

挿入インテンションギャップロックについて

挿入インテンションギャップロックが取得しようとするギャップは、挿入するレコードが該当するギャップに等しい範囲が対象となる。つまりこの場合は末尾のレコードである idxSecondaryId = 40 以降に存在する無限のギャップが対象となる
これは具体的には Trx1, Trx2 の発行する insert文の idxSecondaryId がそれぞれ 80, 90であり、Trx1, Trx2ともに idxSecondaryId = 40 以降のギャップロックを取得しようとする

わかりにくいと思うので図にします。これまた会心の出来です。
矢印はレコードロックを表しています。

fig2

こんな形になるんではなかろうかと。
記事に記載されていること以外にも色々試行錯誤したのですが、すべての結果を説明できるので自分としてはこんな形であろうと解釈しました。
(本当は実装レベルで把握できれば良いんだろうけれども。)

対応

今回の対応としては削除時に主キーのレコードロックのみを取得して削除するような実装に変更しました。

参考

InnoDB のレコード、ギャップ、およびネクストキーロック
InnoDB のロックモード
InnoDB のさまざまな SQL ステートメントで設定されたロック


時間が立つとGoogle APIのOAuth認証に失敗する

概要

Googleが提供するツール。便利ですよね。
公式ではよくG Suitesと呼ばれているスプレッドシートや、ドキュメントなどなどのことを指します。
アプリケーションからG Suitesを操作するためにはOauth認証を利用するのですが、今回はその際時間が立つと認証時にエラーが出てアプリケーションからの操作ができなくなる問題に遭遇したのでまとめておきます。

エラー詳細

今回のアプリケーションはG Suitesの操作をアプリケーションで実行しています。
挙動としてはまずOAuthクライアントを作成したユーザで 認証を行い、アクセストークンを取得します。
その後アクセストークンを用いてG Suitesの操作を行うようなごくごくシンプルなものです。

当初はアプリケーションが正常に動作するのですが、一定時間が経過した上で再度実行するとG Suitesの操作を行う際にトークンが正常でないエラーが発生するような現象が起こりました。

もう少し細かい実装について述べましょう。
今回はgoogle側で用意してくれているjavaのライブラリを使用しています。
かなりライブラリが充実しているので普通に考えてこれを使わない手はありません。

認証には公式ガイドに沿った典型的な実装だと思います。

GoogleAuthorizationCodeFlow を利用して認証しアクセストークンを取得します。
また認証した情報は FileDataStoreFactory を用いてローカルディスクに保存します。
一度認証した後はセッションを発行して、二度目以降は保存したトークンを用いてアプリケーションの処理を行います。

原因

一定時間が経過すると再度実行できなくなるということから、おそらくトークンが期限切れになった際にトークンのリフレッシュができていないんじゃないかと当たりをつけました。
ちょうどはじめに返却されるアクセストークンの生存期間が1時間でしたので、期限が切れたタイミングで実施するとやはり、エラーが再現することが確認できました。
更に認証時に返却される情報をよくよく確認してみると、リフレッシュトークンが返却されていません。
なのでFileDataStoreにはアクセストークンなどは保存はされているんですが、当然リフレッシュトークンがないのでアクセストークンの期限が切れた瞬間に再発行ができずにエラーとなっているようです。

今回はリフレッシュトークンが空っぽいという点に気づくのに時間がかかりました。
あとはデバッグする際にはアプリケーションを再起動して確認していたので、初回のアクセストークンが切れる前に再度アクセストークンが新しいものに変わるんですよね。なのでエラーが出るまでに時間がかかるので気づきにくかったです。

調べてみますとこちらに情報があり、リフレッシュトークンは初回の認証した時点でしか返却されないそうです。
なんですが approval_promt=force を指定することで毎回認可ダイアログに飛び、それによってリフレッシュトークンが返却されるようになります。

いろいろ試してみたところ、途中から認可ダイアログが表示されなくなるユーザ(この場合リフレッシュトークンが帰ってこなくなります)もいれば、ずっと認可ダイアログが開くユーザもおり、正直なところ何を基準にそうなっているのかわかりませんでした。

参考URLのようにパラメータを指定することで強制的に認証ダイアログを表示するなり、シークレットをリセットするなりするのが対応としては良さそうです。

参考

https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token