MySQL InnoDB AUTO_INCREMENTの上限値周りでの振る舞いを検証

AUTO_INCREMENTの値が型の上限値に達した際の振る舞いが気になったので、いくつか調べてみました。
AUTO_INCREMENTのドキュメントでは情報が足りませんでした。

例えば、一意性さえ確保できれば良いテーブルのidが上限に達した際に、1から順に再度振り直したくなるようなことがあるかもしれません。(アンチパターンの香り)

影響範囲の調査とか調整とかの工数を考えると、明らかにやるべきでない操作ですが、やむを得ない事情がある場合だってあるかもしれません(弊社には今の所ありません。良かった)。そんなときのために、知見をメモしておきます。

前提

  • UNSIGNED INT 型の上限値: 4294967295
  • UNSIGNED INT型のidカラムにAUTO_INCREMENTを指定

検証

idが上限値に達したテーブルに対し、さらにINSERTを実施する

iddata
10hoge
11hoge
4294967294hoge
4294967295hoge
INSERT INTO `id_test_table` (`id`, `data`) VALUES (NULL, 'hoge');
#1062 - '4294967295' は索引 'PRIMARY' で重複しています。

重複エラーが出て、テーブルにデータは入りません。

idを指定してINSERTする

INSERT INTO `id_test_table` (`id`, `data`) VALUES (1, 'hoge');
iddata
1hoge
10hoge
11hoge
4294967294hoge
4294967295hoge

重複が無ければ、問題なくINSERTできます。

この状態で再度INSERTした場合、次に割り当てられるidはなんでしょうか。

INSERT INTO `id_test_table` (`id`, `data`) VALUES (NULL, 'hoge');
#1062 - '4294967295' は索引 'PRIMARY' で重複しています。

AUTO_INCREMENTの値がリセットされるわけではないため、都合よく「2」などにはならず、当然のように重複エラーとなります。
こんな操作で「2」が入ったら、それこそバグの原因になってしまうので幸い。

AUTO_INCREMENTの値をリセットする

それでは、今度はテーブルのauto incrementをリセットしてみましょう。

ALTER TABLE tbl_name AUTO_INCREMENT = value; でリセットできます。

ALTER TABLE id_test_table AUTO_INCREMENT = 2;

テーブル情報から、現状のauto incrementの値を確認します。

SELECT auto_increment FROM information_schema.tables WHERE table_name = 'id_test_table';
auto_increment	4294967295

ダメみたいですね。

データを一部消してAUTO_INCREMENTの値をリセットする

レコードを一部DELETEしました。

iddata
1hoge
10hoge
11hoge

この状態で、auto incrementの値をリセットしてみます。

ALTER TABLE id_test_table AUTO_INCREMENT = 15;
SELECT auto_increment FROM information_schema.tables WHERE table_name = 'id_test_table';
auto_increment	15

この場合は無事「15」になりました。

INSERT INTO `id_test_table` (`id`, `data`) VALUES (NULL, 'hoge');
iddata
1hoge
10hoge
11hoge
15hoge

問題なくINSERTされます。

「指定したidの値より大きい値を持つレコードがある場合、変更できない」という仕様のようです。

15より低い値で入れてみると、やはり更新されません。

ALTER TABLE id_test_table AUTO_INCREMENT = 10;
SELECT auto_increment FROM information_schema.tables WHERE table_name = 'id_test_table';
auto_increment	16

冒頭の「idの採番を1から振り直す」なんてことをしたい場合には、指定したい値以上のidを持つレコードを削除してやる必要がありそうです。とんでもねえなおい。

実際に「idの採番を1から振り直す」ようなケースでは、テーブルにid:1~上限値までのデータが全て残っているような状態は珍しいのではないでしょうか。

つまるところ、
「指定したい値以上のidを持つレコードを削除」は
「テーブルのレコードを全て削除」とほぼ同じ意味になりそうです。

とんでもねえなおい。

結論

テーブルの設計段階で型を十分に検討するべき。

参考

Win10 Hyper-V Dockerインストール

Win10のHyper-Vを使ってDocker Desktop for Windowsを動かすことを目指す回。

事前準備

Hyper-Vを有効化する必要があるが、下記の公式ドキュメントを参照されたい。

Windows 10 での Hyper-V の有効化 | Microsoft Docs

インストール

下記の公式ページよりインストーラーをダウンロードした。

Docker Desktop for Mac and Windows | Docker

ボタンの表記が「Download for Windows (stable) 」となっているので、そのまま押下。

Docker Desktop Installer.exeが取得できたので、実行してインストール。

デスクトップのショートカットはいらないかも……
インストールが完了した後、PCの再起動

さっくりとインストール完了。ちょろすぎる。

動作確認

おもむろにWindows PowerShellを起動。

バージョン確認

2020/07/28の最新?

> docker version
Client: Docker Engine - Community
 Version:           19.03.12
 API version:       1.40
 Go version:        go1.13.10
 Git commit:        48a66213fe
 Built:             Mon Jun 22 15:43:18 2020
 OS/Arch:           windows/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.12
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.10
  Git commit:       48a66213fe
  Built:            Mon Jun 22 15:49:27 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Hello world

Dockerが自動でイメージの取得まで済ませてくれるらしい。便利。

> docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:451ce787d12369c5df2a32c85e5a03d52cbcef6eb3586dd03075f3034f10adcd
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

チュートリアル?

Docker Desktopを起動してみると、チュートリアルが始まった。

右上のセッティグとかも見てみたけど、まだ良くわからなかった

画面のボタンをぽちぽち押すと画面右側のコンソールで処理が走るというチュートリアルだったが、正直何が起きているのかよくわからないままに進んでいく感はあった(この時点で細かく説明するよりは全体の流れをさっと見せる方が良いと思うので、悪い点ではない)。


途中、ファイアウォールの警告が出たので[OK]を押下
イメージの保存とシェアをするステップ
アカウントを持っていなかったので新規登録
チュートリアル画面のUI
イメージの保存まで完了すると
[View in Browser]ボタンの押下を促される
ブラウザでこのようなページが開かれる
先程のボタンポチポチよりも重めのイントロダクションとチュートリアルだ
Docker Desktopを見ると、コンテナリストが表示されている
(hello-worldをやった形跡がある)

以上の流れで、Docker Desktop for Windowsをインストールしてチュートリアルに到達するところまでができた。

ひとまずここまで。

AWS EC2 不具合インスタンスの不具合調査備忘録

Auto Scalingが設定してあるインスタンスで深刻な不具合が発生すると、設定によっては、調査中に勝手にインスタンスがTerminateされてしまうことがあります。

これを回避する方法のメモ。乱文。

正しいやり方かはわからないけれど、現状のインスタンス規模なら適当。

前提

インスタンス数: 2台
Auto Scaling設定あり(=重篤な不具合を検知すると自動で代替インスタンスが立ち上がる)

インスタンスのスタンバイ状態を活用する

  1. インスタンスの内部をいじる場合は、まずインスタンスを「スタンバイ状態」にしておきます。
    スタンバイ状態にすれば、ロードバランサーがアクセス異常を検知しなくなるので、好きなだけゆっくりと調査ができます。
    1. Auto Scaling > [インスタンス]タブ
    2. 対象のインスタンスを選択し、 [操作] > [スタンバイに設定]
      • ※スタンバイ状態にすると、ロードバランサーはそのインスタンスにリクエストを投げなくなります(=インスタンスとしては稼働しているが、いないものとして扱われる)。
    3. このとき、代替インスタンスを自動で立ち上げるかを選択できます。
      • 立ててもいいですが、調査対応後の動作確認をする際に、当該インスタンス以外をすべてスタンバイに切り替えるので、可能な限り立ち上げない方が良いかもしれません。
      • ※スタンバイ状態に切り変えると、Auto Scalingの「希望インスタンス数」の値は自動で変動し、意図しないインスタンスの増減を防止します。
  2. 調査後、問題なさそうになったら、スタンバイ状態のインスタンスを「実行中」に設定します。
  3. 当該インスタンスの「ライフサイクル」が「実行中」に切り替わるのを待ちます。
  4. その後、調査対象のインスタンス以外のインスタンスを「スタンバイ」に設定し、動作検証を行いたいインスタンスのみがアクセスの対象となるようにします。
  5. 動作確認します。
  6. 問題なく稼働するようであれば、インスタンスのイメージを取得して保存します。
  7. 適当なインスタンス数になるよう調整したりなんだりします。
    • インスタンスの希望台数を適性に設定し直し、不要なインスタンスを終了させていく。
    • ※スタンバイ状態にしたままインスタンスがTerminateされると、Auto Scalingの[インスタンス]タブにゾンビ項目みたいに残ったりする?かもしれない。多分インスタンスが終了しても即座に紐付けが消失しないせい。
      (インスタンス一覧から消えればさすがに反映されると思われる)

CloudWatchのログを見る

CloudWatchにログを吐き出す設定をしていれば、ログが利用できるはずです。

  1. CloudWatch > 左側メニュー[ロググループ]
  2. ドメイン名などで検索をかけ、表示するログを絞ります。
  3. ログを開くと、インスタンスごとにファイルがわかれているので、問題が生じているインスタンスのIDで検索をかけてログファイルを特定します。
  4. ログを見ます。
    • ログの最下部にあるpauseを押せば、ログの自動更新を一時停止できます。
    • 時間を絞って表示することもできてまあまあ便利。

インスタンスのログを見る

Apache : /var/log/httpd/error_log とか。
nginx : /var/log/nginx/error_log とか。

参考

PHP コード内で PHPUnit を呼び出す(実行する)

PHPUnitを実行するPHPスクリプトを書きたい日がありました。
まあまあ時間が溶けたので備忘録です。

結論

このように書けばOK。

exec('/path/to/phpunit /path/to/tests');

参考: php – How to trigger unit testing from Laravel Artisan – Stack Overflow

特定のテスト単体を実行したい場合はこのように書けばOK。

exec('/path/to/phpunit /path/to/target /path/to/tests');

関数ごとの出力例をざっくり

記述法は結論の通り。

PHPには外部プログラムの実行ができる関数が色々あるので、出力結果を見比べてみました。

下記のようなコードで出力を確認しました。

$result = exec('/path/to/phpunit/ /path/to/tests');
var_dump(gettype($result));
var_dump($result);

exec()

成功時
var_dump(gettype($result));
string(6) "string"
var_dump($result);
string(24) "OK (1 test, 1 assertion)"
エラー発生時
var_dump(gettype($result));
 string(6) "string" 
var_dump($result);
 string(52) "Tests: 75, Assertions: 3650, Errors: 4, Failures: 8." 

エラー発生時に ERROR! の文言が出ないが、成功時はシンプルかつ OK と出るので良さそう。


shell_exec()

成功時
var_dump(gettype($result));
string(6) "string"
var_dump($result);
string(194) "PHPUnit 5.7.27 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 231 ms, Memory: 14.00MB OK (1 test, 1 assertion) "
エラー発生時
var_dump(gettype($result));
string(6) "string"
var_dump($result);
string(108797) "PHPUnit 5.7.27 by Sebastian Bergmann and contributors. (中略) ERRORS! Tests: 75, Assertions: 3650, Errors: 4, Failures: 8. "

OKERROR! の文言は出るものの、出力が長大で微妙……


system()

成功時
system()の出力(勝手に出る)
PHPUnit 5.7.27 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 229 ms, Memory: 14.00MB OK (1 test, 1 assertion)
var_dump(gettype($result));
string(6) "string"
var_dump($result); 
string(24) "OK (1 test, 1 assertion)"
エラー発生時
system()の出力(勝手に出る)
PHPUnit 5.7.27 by Sebastian Bergmann and contributors. (中略) ERRORS! Tests: 75, Assertions: 3650, Errors: 4, Failures: 8.
var_dump(gettype($result));
string(6) "string"
var_dump($result);
string(52) "Tests: 75, Assertions: 3650, Errors: 4, Failures: 8."

system()は実行結果をすべて出力するため、 var_dump(gettype($result)); の出力内容の前にPHPUnitの実行結果がドカドカ出てきてしまい、見にくかった。

var_dump($result);の出力結果は exec() と同じ。見やすくて良い。

ということで

PHPUnitを外部から実行し、出力内容を利用して成否判定するようなことができるようになりました。

system() は実行結果を出力するところまで行うため、PHPUnitの実行結果(最後の行)だけ見たい場合には、 exec()を使うのが良さそう。

参考

Vagrant CentOS6 PHP5.6 → 7.2 バージョンアップ

ローカル開発環境のPHPが5.6だったので、バージョンを上げてみようと思いました。

CentOSにPHP7.2入れたい
 →Remiというプロジェクトのリポジトリを使うとyumで入れられるらしい
  →Remiを利用するためにはEPELリポジトリのインストールも必須要件

手順

root権限を持つユーザで作業します。

EPELリポジトリのインストール

# yum -y install epel-release

えーなんか出た……

[root@localhost ~]# yum -y install epel-release
Geladene Plugins: fastestmirror
Einrichten des Installationsprozess
Loading mirror speeds from cached hostfile
Could not get metalink https://mirrors.fedoraproject.org/metalink?repo=epel-6&arch=x86_64 error was
14: problem making ssl connection
 * base: ftp-srv2.kddilabs.jp
 * epel: ftp.riken.jp
 * extras: ftp-srv2.kddilabs.jp
 * remi-safe: ftp.riken.jp
 * updates: ftp-srv2.kddilabs.jp
Paket epel-release-6-8.noarch ist bereits in der neusten Version installiert.
Nichts zu tun

yumの設定を書き換えて対処。
baseurl のコメントアウトを外して、 mirrorlist をコメントアウト。
参考: yumがepelのmirrorにつながんねーよと怒ってる時の対処法 – Qiita

# vim /etc/yum.repos.d/epel.repo
[epel]
name=Extra Packages for Enterprise Linux 6 - $basearch
#baseurl=http://download.fedoraproject.org/pub/epel/6/$basearch
mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-6&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6

[epel]
name=Extra Packages for Enterprise Linux 6 - $basearch
baseurl=http://download.fedoraproject.org/pub/epel/6/$basearch
#mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-6&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6

再実行

# yum -y install epel-release
[root@localhost ~]# yum -y install epel-release
Geladene Plugins: fastestmirror
Einrichten des Installationsprozess
Loading mirror speeds from cached hostfile
 * base: ftp-srv2.kddilabs.jp
 * extras: ftp-srv2.kddilabs.jp
 * remi-safe: ftp.riken.jp
 * updates: ftp-srv2.kddilabs.jp
epel                                                         | 4.7 kB     00:00
epel/primary_db                                              | 6.1 MB     00:02
Paket epel-release-6-8.noarch ist bereits in der neusten Version installiert.
Nichts zu tun

ヨシ!

Remiリポジトリのインストール

# yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
[root@localhost ~]# yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
Geladene Plugins: fastestmirror
Einrichten des Installationsprozess
remi-release-7.rpm                                                                                               |  20 kB     00:00
Untersuche /var/tmp/yum-root-KfXqbk/remi-release-7.rpm: remi-release-7.7-2.el7.remi.noarch
Markiere /var/tmp/yum-root-KfXqbk/remi-release-7.rpm als eine Aktualisierung für remi-release-6.9-2.el6.remi.noarch
Loading mirror speeds from cached hostfile
 * base: ftp-srv2.kddilabs.jp
 * extras: ftp-srv2.kddilabs.jp
 * remi-safe: ftp.riken.jp
 * updates: ftp-srv2.kddilabs.jp
Löse Abhängigkeiten auf
--> Führe Transaktionsprüfung aus
---> Package remi-release.noarch 0:6.9-2.el6.remi will be aktualisiert
---> Package remi-release.noarch 0:7.7-2.el7.remi will be an update
--> Verarbeite Abhängigkeiten: epel-release = 7 für Paket: remi-release-7.7-2.el7.remi.noarch
--> Abhängigkeitsauflösung beendet
Fehler: Package: remi-release-7.7-2.el7.remi.noarch (/remi-release-7)
            Requires: epel-release = 7
            Installiert: epel-release-6-8.noarch (installed)
                epel-release = 6-8
 Sie können versuchen --skip-broken zu benutzen, um das Problem zu umgehen.
 You could try running: rpm -Va --nofiles --nodigest

ヨシ!

Remiのインストール状況を確認

# ls -l /etc/yum.repos.d/
[root@localhost ~]# ls -l /etc/yum.repos.d/
insgesamt 64
-rw-r--r--. 1 root root 1991 23. Okt 2014  CentOS-Base.repo
-rw-r--r--. 1 root root  647 23. Okt 2014  CentOS-Debuginfo.repo
-rw-r--r--. 1 root root  289 23. Okt 2014  CentOS-fasttrack.repo
-rw-r--r--. 1 root root  630 23. Okt 2014  CentOS-Media.repo
-rw-r--r--. 1 root root 5394 23. Okt 2014  CentOS-Vault.repo
-rw-r--r--  1 root root  957 22. Jun 18:47 epel.repo
-rw-r--r--. 1 root root 1056  5. Nov 2012  epel-testing.repo
-rw-r--r--  1 root root 1209  2. Dez 2013  mysql-community.repo
-rw-r--r--  1 root root 1060  2. Dez 2013  mysql-community-source.repo
-rw-r--r--. 1 root root  456 16. Jan 2018  remi-php54.repo
-rw-r--r--. 1 root root 1314 16. Jan 2018  remi-php70.repo
-rw-r--r--. 1 root root 1314 16. Jan 2018  remi-php71.repo
-rw-r--r--. 1 root root 1314 16. Jan 2018  remi-php72.repo
-rw-r--r--. 1 root root 2605 16. Jan 2018  remi.repo
-rw-r--r--. 1 root root  750 16. Jan 2018  remi-safe.repo

epel、remiともにインストールできていることがわかる。
特に、remi-php72.repoが取得できていることが確認できる。

ここまでが下準備。

古いPHPのアンインストール

インストール済のPHP関連のパッケージ一覧を表示

# yum info installed php*

ずらずらっと色々出てくる。

アンインストール

# yum remove php*
~~(php関連パッケージたち)~~

========================================================
Remove       93 Package(s)

Installed size: 95 M
Ist dies in Ordnung? [j/N] :j

~~(削除中)~~

Komplett!
[root@localhost ~]#

消えていることを確認

[root@localhost ~]# php -v
-bash: /usr/bin/php: Datei oder Verzeichnis nicht gefunden

新しいPHPのインストール

# yum install --disablerepo=* --enablerepo=epel,remi,remi-safe,remi-php72 php

インストール完了後、バージョン確認。

[root@localhost ~]# php -v
PHP 7.2.31 (cli) (built: Jun  9 2020 08:25:54) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

<関係ないですが、ここで言語設定をドイツ語から英語に変更しました>

phpMyAdminのインストール

# yum install -y --enablerepo=remi-php72 phpMyAdmin

なんか言われた。

Error: Package: phpMyAdmin-4.0.10.20-1.el6.noarch (epel)
           Requires: php(language) < 7
           Installed: php-common-7.2.31-2.el6.remi.x86_64 (@remi-php72)
               php(language) = 7.2.31
           Available: php-common-5.3.3-49.el6.x86_64 (base)
               php(language) = 5.3.3
           Available: php-common-5.3.3-50.el6_10.x86_64 (updates)
               php(language) = 5.3.3
           Available: php-common-7.2.31-1.el6.remi.x86_64 (remi-php72)
               php(language) = 7.2.31
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest

phpMyAdminが入らないのはアカン。

対処します。
参考: centos – centOS6.8にphpMyAdminをyumでインストールしたさいのエラー – スタック・オーバーフロー

remiリポジトリのアップデート

# yum --disablerepo=* --enablerepo=remi,remi-php72 update

remiのみを指定して再インストール

# yum --disablerepo=* --enablerepo=remi,remi-php72 install phpMyAdmin

ずらずらっと処理が走って Complete! したのでOK。

Apacheの再起動

# service httpd restart

phpMyAdminが無事開けたのでOK

と思いきや、いざlocalを開いてみたらまたエラー

やれやれ

Notice: Undefined variable: u_id in (ファイルのパス) on line 78

Notice: Undefined index: action in (ファイルのパス) on line 11

Warning: require_once(hogefuga.php): failed to open stream: No such file or directory in (ファイルのパス) on line 4

Fatal error: require_once(): Failed opening required 'hogefuga.php' (include_path='.:/usr/share/pear:/usr/share/php') in (ファイルのパス) on line 4

.htaccessを確認

<IfModule mod_php5.c>
 php_value include_path "hogehogehogehogehohgeohgoehogehogheogehoge"
 php_value error_reporting 6135
 php_value mysql.connect_timeout 1
</IfModule>

あーーーーーーー!

対処

php_value include_path "hogehogehogehogehohgeohgoehogehogheogehoge"
php_value error_reporting 6135
php_value mysql.connect_timeout 1

一般的に、<IfModule>使うのはよろしくないという言説も見かけるので削除。

再びのApache再起動

# service httpd restart

きちんとページが表示されたのでヨシ!

参考