年中アイス

いろいろつらつら

データに対する時間がたくさんある

ちょっとハマったので、整理というか殴り書き。

一つのデータにも様々な時間が関わります。

スマートフォンアプリから、WebAPIを経由して、DBおよび検索エンジンに至るまでのデータに対する時間

- ユーザが作成した時間
- アップロードした時間
[↑スマートフォン端末側]
-------------------
[↓サーバ側]
- APIが受け付けた時間

- DBMasterに記録された時間
- DBMasterで参照可能になった時間

- DBSlaveに記録された時間
- DBSlaveで参照可能になった時間

- 検索エンジンに反映された時間
- 検索エンジンで検索可能になった時間

これらの時間を取り違えると、挙動がおかしくなります。

スマートフォンの時間、サーバの時間

大きな点として、スマートフォンと、サーバシステムでの時間の扱いがあります。

基本的にスマートフォン側の時間は、ずれることもあることや、基本的にサーバ側からは操作不可で、それを考慮する必要があります。また、スマートフォンはオフライン時に操作してそれを後から送信するといったユースケースがあるため、一概にサーバ到達時刻に置き換えることができません。

もちろん端末の時間がずれていたら別なので、その対処も必要です。日記などの時刻の場合は、ユーザが意図的に変更していても問題ないですが、特定のイベントに関することや、厳密にその時間に行ったことを証拠とするケースでは端末とサーバシステム側の時刻の整合性が必要です。

サーバ側では、基本的に複数台で同一の時間になるようにNTPで調節します。そして、システム的に記録した時間が重要になってきます。例えば、APIで受け付けた時間を、ユーザが作成した時間と取り違えると、実際にユーザが作成したは時間はいつなのか、消えて無くなります。

DB上に記録された時間も同様です。DBに記録された時間はユーザが作成した時間とは異なりますし、APIが受け付けた時間とも異なります(少なくとも数ミリ秒は)

また、DB上に記録された時間で厄介なのがトランザクションです。MySQLの場合、NOW()で、その時間はMySQL側の現在時刻に合わせることはできますが、トランザクションが完了するまで、別の接続からはそれを見ることはできません。つまりデータは存在する(予定)ですが、その時点では見えない状態です。 つまり、他の接続からは、この時間からこの時間の間に作られたデータを参照しても、ある時点では存在しないことになっていたが、後から存在していたことになった。という状況が生まれます。

単純に、1分前から今までに作成されたデータを参照していたら、参照インターバルをまたぐデータ作成・更新があると抜けるケースが出てきます。

さらに検索エンジンへの反映も、時間差は生まれるので、同様にDB上での時間と検索エンジンで検索できるようになった時間がずれるので、同様の問題が発生します。

秒以下も重要

また、これらの時間を秒単位で記録している場合、ミリ秒での前後関係の問題が出てきます。

12:00:00に作成されたデータが、12:00:00に参照しても取得できなかった場合に、それが遅延によるものなのか、そもそも順番が違うのかが判断できません。

  • 参照が12:00:00.001に来て、作成が12:00:00.999に行われていたのか(作成前の参照なので正常)
  • 作成が12:00:00.001に来て、参照が12:00:00.999に行われていたのか(何かしら遅延があったのか)

APサーバの場合、1台の中であれば、ログファイルの順序で判断できるケースもありますが、基本的に複数台で構成されるため、それらをマージすると秒以下の順序は無くなります。

他にも

タイムゾーンサマータイムなどが時間では大きな落とし穴になります。 あまりまとまってないのと、それでどうするのかというところは書けてないですが、とりあえず殴り書き。

GRANTで起こすDB接続障害

MySQLの権限追加でGRANT文を使った時に、DB接続障害を起こす失敗の仕方です。

GRANTSは、MySQLユーザに対しての権限を付与する構文です。

MySQL :: MySQL 5.6 Reference Manual :: 13.7.1.4 GRANT Syntax

よく紹介されているのは、CREATE USERを使ってユーザを作ってから、GRANTで権限を付与する流れです。 ただ、GRANTで、うっかりホスト名を厳密にすると、DBに接続できなくなります。

何が起きるか

試していきます。2つのホストを使います。試す環境の準備は省略します。*1

  • 192.168.50.11 mysql serverが稼働中
  • 192.168.50.12 mysqlクライアントの接続元

まずrootユーザで接続し、DBを作成、ユーザを作って権限を付与します

CREATE DATABASE mydb;
CREATE USER 'myuser' IDENTIFIED BY 'mypassword';
GRANT SELECT ON mydb.* TO 'myuser';

192.168.50.12から以下のように接続できます。

$ mysql -umyuser -h192.168.50.11 -p
mysql>

rootユーザで、以下のコマンドでUPDATE権限を付ける際に、ホストの指定(192.168.50.%)を追加します。

GRANT UPDATE ON mydb.* TO 'myuser'@'192.168.50.%';

そうすると、前述の接続コマンドで接続しても、以下のように接続できません

$ mysql -umyuser -h192.168.50.11 -p
Enter password:
ERROR 1045 (28000): Access denied for user 'myuser'@'192.168.50.12' (using password: YES)

なぜ?

この事象がなぜ発生するかというと、理由は簡単です。以下のようにユーザ一覧を見るとはっきりします

SELECT User,Host,Password FROM mysql.user WHERE User = 'myuser';
+--------+--------------+-------------------------------------------+
| User   | Host         | Password                                  |
+--------+--------------+-------------------------------------------+
| myuser | %            | *FABE5482D5AADF36D028AC443D117BE1180B9725 |
| myuser | 192.168.50.% |                                           |
+--------+--------------+-------------------------------------------+
2 rows in set (0.00 sec)

そうです。'myuser'と 'myuser'@'192.168.50.%'は、別扱いなのです。考えてみれば当たり前です。

気をつけること

気をつけないといけないのは、GRANTは、存在しないユーザとホストの組み合わせを指定すると、そのユーザを作成することです。*2 さらに、パスワード指定がない場合に、空パスワードでユーザを作成することです。 そのため、この場合は、厳密になったHostに該当する場合に、今まで使えていたパスワードと一致しないので接続に失敗します。

DBにつながらない。運用中のサービスだと恐ろしいですね。

最初から厳密に、ユーザと接続元を定義している場合は発生しにくいかなと思います。最初つけていなくて、あとからうっかりユーザは一緒だけど接続元だけで権限を分けよう!という気持ちになると、やったあとにひどいことになります。

*1:たまたま手元にあったvagrant環境はMySQL 5.6.19でした。

*2:ここではHostだけ変えてますが、ユーザ名を間違えると、そのユーザが作成されます

午後休のススメ

よくある?だらっとした休み

有給休暇をとる際に、1日とっても寝て起きたら昼過ぎで、少しだらっとしてるとすぐ夕方になってしまい、休み取った感がなくなってしまうことがあります。

そこで午後休

そこでオススメなのが、午後休です。朝はいつも通り出勤、仕事をして、昼過ぎたら自由になれます。午前中は働いているので、頭、体はウォームアップできていて、行動しやすいのです。半分で仕事が終わるので、やることを取捨選択して片付ける意識も働きやすいです。

動き出しやすさが良いところ

そのまま、少し離れたところに遅めのランチに行くもよし、買い物に行くもよし、カフェで本を読んでもいい。1日休みを取るよりは、比較的有意義な時間の使い方ができるかなーと思います。動きだすまでのハードルを下げられます。有給休暇も半分の消費です。

もちろん、あらかじめ予定があって丸ごと休む、1日寝るんだという方は、この限りではありません。