【MySQL】データベースのパーティショニングとはなんぞやという話

パーティショニングについての覚書。 一応 MySQL が前提。先に具体的な実体を書いたあと、それがパフォーマンス向上にどう寄与するのかを書きます。

パーティショニング is 何

簡単に言えば、データベースのテーブルを物理的に分割することです。テーブルのデータが分割されてしまっても、外からは論理的(透過的)に単一のテーブルとして見えます。なので、以下の図のようにクライアントや DSL レイヤはそれを意識する必要はありません。p1 や p2 は分割されたパーティションを表しています。

f:id:norikone:20181006193802p:plain:w400

この透過性は MySQL サーバによって提供されています。また、ストレージエンジンは分割された各パーティションを独立したテーブルのように扱います。これを図にすると次のような感じになります。

f:id:norikone:20181006193810p:plain:w450

このため、例えばパーティショニングするテーブルにインデックスを張ると、各パーティションごとにインデックスが作られることになります。

パーティショニングタイプ

で、どういう基準でテーブルをパーティションに分割するのかというと、これにはいろいろな方法があります。一番有名というか、例でよく使われるのは次のような範囲による分割です。このような分割基準はパーティショニングタイプと呼ばれます。

f:id:norikone:20181006194951p:plain:w470

ここでは、created_at などの日付を表現するカラムを基にパーティショニングをしているとします。このように、基本的なパーティショニングでは、特定のカラムをキーとして各レコードの配置先が決まります。パーティショニングに使うこのキーはパーティションキーと呼ばれます。

パーティションキーを基に、どこのパーティションにレコードを配置するかを決定するのがパーティション関数です。例えば、範囲に基づくパーティショニング環境下で新たに行を挿入する場合、以下のようにパーティション関数が振り分け先を判断します。

f:id:norikone:20181012003704p:plain

図では created_at が1月のレコードを挿入しようとしているので、パーティション関数によってそれが評価されて、1月用のパーティションへの挿入が決定されます。逆に言えば、このパーティション関数を変えればパーティション分割の基準を変えられるということです。ここでは範囲条件による分割を紹介しましたが、他にはハッシュを使ったものなどがあります。

パーティショニングのメリットやデメリット

パーティショニングの利点には幾らかありますが、ここではクエリ処理の高速化(レスポンスの向上)につながるものについて書いていきます。

パーティショニングでは前述のように、テーブルをパーティションという単位に分割します。ここで、分割されたテーブルに問い合わせが発生したことを考えます。クエリの実行時になんの工夫もしないとすると、以下のようにすべてのパーティションを検索して特定のレコードを探し出すことになるでしょう。

f:id:norikone:20181011152306p:plain:w480

しかし、上のように検索条件が特定のパーティション内に閉じているのであれば、次のように、当該パーティションだけを検索するだけで済むはずです。

f:id:norikone:20181011152925p:plain:w485

検索する必要のないパーティションは検索しないに越したことありません。スキャンする行数が減れば、無駄な検索処理が減り、パフォーマンスが上がります。このような仕組みはすでに実装されていて、パーティションプルーニングと呼ばれています。という感じで、パーティショニングによってスキャンの範囲を局所化できるケースでは、パフォーマンスを高められるのです。ちなみに、検索からどのパーティションを除外するのか、といった判断はオプティマイザが担当します。

インデックスを効果的に使えるクエリであれば十分高速に検索できるので、あえてこの仕組みを使った高速化を図るまでもないかもしれません。また、サマリテーブルで対応できる場合なども、あえてパーティショニングを使う必要もないということで、レスポンス高速化面でのパーティショニングの使い所は限られてくるかもしれません。

しかし、インデックスのカーディナリティが低いケースなど、テーブルスキャンが発生する場合には、検索範囲を局所化できるためパーティションプルーニングの効果は大きくなります。なので、大規模なテーブル且つカラムのカーディナリティが低いといったインデックスを貼るのが現実的でないようなケースであれば特に、パーティショニングが簡易インデックスのような形で威力を発揮します。

現在のパーティションの最大数は 8192 で、パーティションが増えれば増えるほどパーティションあたりのレコード数を減らせるので、プルーニングによる効果は大きくなります。が、プルーニングが発動しないケースではパーティション数が増えるほどオーバヘッドが大きくなります。例えばキーの範囲によってパーティショニングしている場合の行の挿入時には、オプティマイザがどのパーティションに行を挿入するかを考えることになりますが、パーティションが多いとこの作業が大変になります。ということで、パーティションは増え過ぎもよろしくないので、適度な数に抑えたほうが良さげです。CPU のコアが沢山あれば並列処理である程度はカバーできるかもしれませんが。

で、パーティションプルーニングは、WHERE 句でパーティションキーが指定されないと発動しません。このため、パーティショニングされたテーブルにクエリする際には、パーティションプルーニングを発動するために一見無駄だと思われる WHERE 句を意図的に追加したり、パーティショニングが上手く効くように条件を書き換えたりしたほうがいいことがあります。それから、結合時のWHERE条件にパーティションキーが指定された時にもプルーニングは発動します。なので、結合時にも、上手く条件を指定してあげれば結合対象のテーブルが小さくなり、NLJ のコストを抑えられます。プルーニングが効かなければ前述のようなマイナス面を喰らうだけなので、こんな感じでクエリを最適化した方がよさそうです。

また、パーティショニングは 5.6 5.1 (コメントでご指摘を頂きました)から追加された機能ですが、5.7まではパーティショニングされたテーブルに対しては ICP(詳しくはこちらの記事で紹介しています)が効かず、プルーニングに失敗した場合のダメージが大きくなりそうなので、よく注意したほうがいいかもしれません(大量の行フェッチが発生する可能性があります)。5.7 以降はパーティショニングされたテーブルに対しても ICP が効きます。

おわり

パーティショニングは他の技術と同様に、とりあえずやっておけば高速化できるというような銀の弾丸ではありません。なので、その仕組みをある程度理解しておいて、適切な状況で活用することが大事ですね。よく言われているのは、パーティショニングが威力を発揮できる状況は主に超大規模なテーブルを扱う場合、ということです。

また、結局のところ、パーティショニングによる高速化が成功するかどうかはテーブルの構造やレコード数といった要因だけでなく、クエリにも大きく依存します。なので、パーティショニングを検討する場合にはクエリについてもよく分析すべきということですね。おわり。

MySQLのIndex Condition Pushdown とはなんぞやという話

MySQL のバージョン5.6から追加された機能に、Index Condition Pushdown(ICP) というものがあります。ICP は「マルチカラムインデックスの順番を意識しなくてもよくなる仕組み」的な説明がされることがあり、それはそれで間違いではないのかもしれません。が、それだと「ストレージエンジン側に条件式をプッシュダウンする」という動作が伝わりにくい気がしますし、ICP がマルチカラムインデックスの順序による制限を解消するものだと勘違いしてしまう可能性があります。なので、この記事では行フェッチ時の動作を見ながら ICP の動作イメージや利点を考えてみようと思います。ちなみに InnoDB を前提に書いていこうと思います。

先に簡単に書いておくと、

  • ICPとは
    • ストレージエンジンがセカンダリインデックスを使って行をフェッチしようとする際に、MySQLサーバからストレージエンジンに条件式を渡してストレージエンジン側でWHEREの条件判定を行う仕組みのこと
  • なんのために
    • 行のフェッチ時の IO を減らすため

という感じでしょうか。

MySQLの基本的な構造

ICP の動作を考える上で必要な基本的な MySQL の構造を書いておきます。図にすると次のような感じになります。 f:id:norikone:20181001212436p:plain

ここでまず言いたいのは、MySQL サーバとストレージエンジンは構造的に分離されているということです(少々語弊があるかもしれませんが)。サーバ側ではクエリのパースやオプティマイズが行われ、取得したい行が決まったらストレージエンジンの API を叩いてレコードを取ってきてもらいます。この際、ストレージエンジンから取得するデータ量(レコード数)は少ない方がパフォーマンスがいいです。通信が少ない方がオーバヘッドを抑えられますし、MySQL サーバのバッファプールの消費量も抑えられます。

また、インデックスについて言えることは、セカンダリインデックスからクラスタインデックスへのアクセスは少ない方がいいということです。InnoDB ではカバリングインデックスでない限り、完全な行を取得するためにクラスタインデックスを辿る処理が必要です。で、この処理とそれに伴うディスクIOが大きなオーバヘッドになります。ちらみに最近の MySQL には MRR(Multi Range Read) という機能が実装されていて、このディスクIO時のランダムアクセスをシーケンシャル化してオーバヘッドを抑えられるみたいです。

ということで、MySQL サーバ↔ストレージエンジン間の通信」と「セカンダリインデックス使用時のクラスタインデックスへのアクセス」はできる限り抑えたいという前提があります。

ICP使用時およびICP非使用時の動作

先に書いたように、MySQL サーバ自体は実際の行の取得に深く関与しません。ですが、クエリによって最終的に返される行は、MySQL サーバとストレージエンジンの2者の連携によって選択されます。

具体例として、以下のようなクエリ(公式docに記載のクエリをちょっと変えたもの)を考えます。また、マルチカラムインデックスとして (zipcode, lastname, firstname) が設定されているとします(以後、このインデックスを「midx」と呼びます)。

SELECT * FROM people
  WHERE zipcode='95054'
  AND lastname LIKE '%uzu%'
  AND address LIKE '%Main Street%';

ここで、zipcode によって絞り込めるため、セカンダリインデックスである midx が使われることになるとします。lastname の条件判定は条件が完全一致であれば midx を活用できますが、ここでは部分一致なので、インデックスの篩にかけられるのは zipcode 条件までです。

ICP非使用時の行フェッチ動作

図にすると以下のような感じになります。

f:id:norikone:20181002210127p:plain

ストレージエンジンはクエリに答えるために midx を走査して、zipcode が 95054 のレコードを探します。図では、該当するレコードが4件存在します。見つけたら、クラスタインデックスを辿ってそれぞれの完全なレコードを取得し、MySQL サーバに返します。この時点では、MySQL サーバが受け取ったレコード群はまだ lastname や address の条件の評価をされていません。なので、MySQL サーバ側で where 条件を評価して、条件を満たさないレコードを捨てます。図では最終的に1レコードが残ったとして、それがクライアントに返されています。

さて、上の「MySQLの基本的な構造」の説明では、ストレージエンジンから MySQL サーバへの転送レコードとセカンダリインデックスからクラスタインデックスへのアクセス数は少ないほうがいいと書きました。しかし、ここでは1レコードをクライアントに返せばいいだけなのに、実際には4レコードもフェッチしています。つまり、それぞれのタイミングで3レコード分も無駄が発生しているのです。これをどうにかしたいよね、というのが Index Condition Pushdown です。

ICP使用時の動作

ICP を使うと先ほどの図は次のようになります。

f:id:norikone:20181002211609p:plain

ICP を使わない場合との大きな違いは、セカンダリインデックスを走査している時にクエリの条件式を評価している点です。ICP では、MySQL サーバ側からストレージエンジンにクエリの条件式を渡す(Pushdownする)ことで、走査のタイミングでWHERE条件の篩にかけることができます。これにより、lastname 条件式を満たさないレコードについては、フェッチせずに済むようになります。このため、ICP を使う場合の図では、先ほどと比べて矢印の数が減っています。つまり、無駄な行フェッチを減らしてパフォーマンスを上げようよということですね。

それと、このケースでは ICP を使ったとしても、address の条件式はインデックス走査中に評価できません。これは、midx に address カラムの値が含まれていないためです。なので、いずれにせよクラスタインデックスへのアクセスは必要になります(減らすことはできますが)。例えば midx が (zipcode, lastname, firstname, address) になっていれば、セカンダリインデックスを走査するタイミングで address 式の評価もできるはずです。

ちなみに、図では便宜上ストレージエンジンから MySQL サーバに一方的にレコードを送っているように描いていますが、実際には MySQL サーバ側からストレージエンジンに向けてリクエストが沢山飛んでいて、それに対するレスポンスが返っているとみるのが正確だと思います。で、ICP によってそれが抑えられているということでもあります。

おわり

ということで ICP を使えば、ストレージエンジンの完全な行へのアクセスと、MySQLサーバ↔ストレージエンジン間のアクセスを抑えることができます。それに伴い、MySQL サーバのバッファプールの消費を抑えられます。逆に ICP を使わずに大量に無駄レコードをフェッチするケースでは、ディスクIOで読んだレコード群を少しづつバッファプールにロードしたものの、ほとんどは条件判定で捨てられるという悲しいことが起きてしまいます。ICP 強い。おわり。

MySQL(InnoDB)のネクストキーロックの仕組みと範囲を図解する

MySQL(InnoDB) のロックにはレコードロックとかギャップロックとかネクスキーロックとかありますが、結構ややこしくて、クエリで条件文が与えられた時にそれがどのようなロックになるのかをイメージし辛い問題が自分の中でありました。ので、実験してみた(MySQL8.0.12、REPEATABLE READ)結果を図で書き残します。なお、結果は SELECT FOR UPDATE を使って排他ロックをとる方法で試したものですが、ロックの範囲を知る上では、排他ロックか共有ロックかとかは関係ないかと思います。

前提として、以下のような id カラムのみを持つインデックスレコードへのロックを考えます。レコードには10 ~ 40 までの 5 飛びの値が存在します。それぞれのインデックスレコードの間にはギャップ(gap)が存在します。また、最初のレコードの前と最後のレコードの後には、論理的な最小値への gap と論理的なの最大値への gap が存在します(どう表現すればいいかよくわからない)。

f:id:norikone:20180911122218p:plain

で、ここに記載のように、

  • レコードロック
    • インデックスレコード単体のロック
  • ギャップロック
    • gap のロック
  • ネクスキーロック
    • レコードロック及びその直前の gap のロック

とします。

MySQL(InnoDB)のロックの範囲

存在する一意なid

f:id:norikone:20180911122229p:plain

条件文で、存在する一意な id が指定された場合には、その id を持つインデックスレコードに対してレコードロックが発生します。なお、黒の矢印は条件文が指定するポイントを示しています。

存在しない一意なid

f:id:norikone:20180911122246p:plain

単一の id 指定時にそのレコードが存在しない場合には、その id が入るべき gap へのギャップロックが発生します。

存在しない範囲境界

f:id:norikone:20180911122256p:plain

このケースでは、指定された値が入るべき gap へのギャップロックと、その次のインデックスレコードへのレコードロックが発生します。インデックスレコードへのレコードロックと、その直前の gap へのギャップロックという表現もできます。これがネクスキーロックです。上のケースでは、25 をロックしなくても本来は OK なのですが、InnoDB ではそこまでロックしてしまいます。ネクスキーロックでは、レコードとその前の gap へのロックをセットでかけるのです。しかし、「存在しない一意なid」のケースを見てみると、gap ロックを単体でかけることが可能だということも示しています。なので、一度ロックを開始してしまうと、次のキーを辿って(条件判定してロックして)からではないとその直前にある gap にロックが掛けられなくなっているのかもしれません。

また、ここからはイメージ的にわかりやすいよう、ロック時のインデックスの走査を図示しています(なお、厳密性は考慮していません。例えば、gap は実際にはレコードのように実体として表現されていないはずなので、走査が gap から開始されるというのは少し変です)。InnoDB のロックではそのようにインデックスを辿りながら、出会ったレコードをロックしていきます。それから、図では走査の矢印を最後だけ逆向きにしてネクスキーロックを表現していますが、多分すべての走査地点で、次のキーに進むたびに手前の gap をロック(ネクスキーロック)しているのだと思います。ですから、図では便宜上赤い部分をネクスキーロックとしていますが、実際にはすべてのロック点でこの動作が発生していそうということです(その場合すべて赤色のロックになる)。つまり、ネクスキーロックをしながら1つずつキーをスキャンしていくということです。

でもこれ、次のように逆向きに走査すれば 25 はロックされないと思うのです。

f:id:norikone:20180911122310p:plain

ですから、ロック時の走査は逆向きには辿れないのかもしれません。

存在しない範囲境界(不等号逆版)

f:id:norikone:20180911122320p:plain

一つ前ののケースの不等号逆版です。このケースでは、20 が余分にロックされていないため、必要最小限のロックだと言えそうです(21 や 22 が余分にロックされてしまうと言えますが、MySQL では gap を分断するロックは難しい)。

ここまでのケースの動きを考えると、このケースでインデックスの走査を逆向きに辿ると 20 までロックされることになるはずです。先程の例とこの動作を考えると、やはりロック時のインデックスの走査は左から右に辿るようになっている、と推測できます。つまり何が言いたいかというと、不等号の向きによって(一つ前のケースでの 25 のような)余分なロックが発生したりしなかったりするのは、恐らくインデックスの走査の向きによるものだということです。

存在する範囲境界

f:id:norikone:20180911122330p:plain

これが割と厄介だと思っています。id <= 25 という条件を普通に考えれば、25 までロックされて、その後の gap や 30 はロックされないだろうと思いがちだからです。この条件なら、本来 26 への挿入や 30 の更新は許可されるべきです。このケースの動作はかなり謎めいていますが、結果から察するに、インデックス走査の終了条件が「条件文を満たさない場合」になっているのかもしれません。そうであれば、25 まで辿ってもまだ id <= 25 という条件は満たすので、次の 30 に進み、ここで条件が満たされなくなり、ネクスキーロックによってその前の gap もロックされる、と説明できるからです。

ちなみにここでも、もし逆走できれば 30 やその前の gap はロックされないはずです。なので個人的には、ロック時のインデックスの走査は「左から右に、条件を満たさないインデックスレコードまで走る」という風に理解しておけばいいかなあと思ったりしています。

存在する範囲境界(逆版)

f:id:norikone:20180911122340p:plain

このケースでは、当該値からロックを開始します。「存在しない範囲境界(不等号逆版)」と異なり、このケースでは手前の gap をロックしません。なんでもかんでもネクスキーロックするわけではないということですね。レコードロックでスタートして、それ以降はネクスキーロックという感じでしょうか。

存在しない2点の範囲境界

f:id:norikone:20180911122354p:plain

これまでに書いたケースで説明できる動作になっています。

存在する2点の範囲境界

f:id:norikone:20180911122404p:plain

こちらもこれまでに書いたケースの組み合わせです。

範囲が分割されている場合

f:id:norikone:20180911122413p:plain

範囲が分割されている場合でも同様に動作します。

おわり

一通り書いてみましたが、もしかしたら間違いがあるかもしれませんのでご注意。ドキュメントやネット上の情報、手元での実験だけでは不明確な部分もあり、ソース嫁という話ではあるのですが、若干のめんどくさみがあるのでもし気が向くことがあれば見てみようと思います。おわり。

MySQL8で削除されたシステム変数たち

MySQL8 にしてから、分離レベルを確認するために以下の文を実行したらエラーが発生するようになりました。

SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
ERROR 1193 (HY000): Unknown system variable 'tx_isolation'

どうやら原因は 8.0.3 から tx_isolation が廃止されたことみたいで、transaction_isolation を使えとのことです。 そんな感じで、8 以降で削除されているシステム変数が以下。

削除された変数 削除されたバージョン 削除後の対応
log_syslog 8.0.13 log_sink_syseventlogを使ってエラーログを出力する
metadata_locks_cache_size 8.0.13
metadata_locks_hash_instances 8.0.13
old_passwords 8.0.11
log_warnings 8.0.3 log_error_verbosityに変更
multi_range_count 8.0.3
query_cache_limit 8.0.3
query_cache_min_res_unit 8.0.3
query_cache_size 8.0.3
query_cache_type 8.0.3
query_cache_wlock_invalidate 8.0.3
secure_auth 8.0.3
tx_isolation 8.0.3 transaction_isolationに変更
tx_read_only 8.0.3 transaction_read_onlyに変更
show_compatibility_56 8.0.1

Sequel ProはそもそもMySQL8に対応していなかった件

昨日以下の記事を書きました。

norikone.hatenablog.com

MySQL8 からデフォルトの認証方式が変わったせいで Sequel Pro からログインできないという話で、認証方式を変えればログインできるようになります。が、ログインできてもその後のテーブル操作等が一切できないことが分かりました(DB 選択すると落ちる)。そのエラー内容がこちら。

NSInvalidArgumentException

-[_NSInlineData isEqualToString:]: unrecognized selector sent to instance 0x60802d051c10

(
    0   CoreFoundation                      0x00007fff921d0e7b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x00007fffa6dbbcad objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff92252cb4 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    3   CoreFoundation                      0x00007fff92142fb5 ___forwarding___ + 1061
    4   CoreFoundation                      0x00007fff92142b08 _CF_forwarding_prep_0 + 120
    5   Sequel Pro                          0x000000010002fce3 -[SPDatabaseDocument setDatabases:] + 783
    6   Sequel Pro                          0x000000010002f2a9 -[SPDatabaseDocument setConnection:] + 610
    7   Foundation                          0x00007fff93baa88a __NSThreadPerformPerform + 326
    8   CoreFoundation                      0x00007fff92166981 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    9   CoreFoundation                      0x00007fff92147a7d __CFRunLoopDoSources0 + 557
    10  CoreFoundation                      0x00007fff92146f76 __CFRunLoopRun + 934
    11  CoreFoundation                      0x00007fff92146974 CFRunLoopRunSpecific + 420
    12  HIToolbox                           0x00007fff916d2acc RunCurrentEventLoopInMode + 240
    13  HIToolbox                           0x00007fff916d2901 ReceiveNextEventCommon + 432
    14  HIToolbox                           0x00007fff916d2736 _BlockUntilNextEventMatchingListInModeWithFilter + 71
    15  AppKit                              0x00007fff8fc78ae4 _DPSNextEvent + 1120
    16  AppKit                              0x00007fff903f321f -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 2789
    17  AppKit                              0x00007fff8fc6d465 -[NSApplication run] + 926
    18  AppKit                              0x00007fff8fc37d80 NSApplicationMain + 1237
    19  Sequel Pro                          0x0000000100002454 start + 52
    20  ???                                 0x0000000000000001 0x0 + 1
)

これだけではよく原因が推測できませんが、結構前に以下で報告されている問題でした。

Need MySQL 8.0 support · Issue #2699 · sequelpro/sequelpro · GitHub

issue で Sequel Pro は UI が素晴らしいから今後も使っていきたい的な意見がちらほらありますが、全く同感です。未だに解決できていないので直ぐの対応はあまり期待できないかもしれませんが、是非修正をお願いしたいところです...(他力本願)。

Sequel Pro を使いたい場合は MySQL のバージョンを 5.7 に落とすしかなさそうです。もしくは MySQL8 がサポートされている TablePlus を使うなど。この辺の布教記事を読んでみて割と良さそうだったので、今後は TablePlus を使ってみることにします。おわり。

MySQL8から認証方式が変わったせいでSequelProからログインできなかった件

追記 どうやら Sequel Pro は MySQL8 にまだ対応しておらず、ログインできないだけではなくて DB を選択するとクラッシュします。詳しくは以下に書きました。

norikone.hatenablog.com


MySQL を 8 にアップグレードしたところ、Sequel Pro からログインできなくなってしまいました。この記事ではその原因と対処法を書きます。以下、そのエラーメッセージ。

Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(/usr/local/lib/plugin/caching_sha2_password.so, 2): image not found

公式によると、同じ原因で以下のようなエラーメッセージも発生するかもよとのことです。

Authentication plugin 'caching_sha2_password' is not supported
Warning: mysqli_connect(): The server requested authentication
method unknown to the client [caching_sha2_password]

問題の原因

どうやら MySQL 8 (正確には8.0.4) からデフォルトの認証方式が変わったみたいで、それが原因でこのエラーが出ているみたいです。変更についてのざっくりとした話は公式docのここに書いてあります。この変更の主な利点として、安全性や高速性があります。

MySQL の認証って所謂チャレンジレスポンス方式で、クライアントとサーバの2箇所で認証用のアルゴリズムがそれぞれ独立して動いていて、その結果を照合する感じになっています。なので、クライアントとサーバ間で認証アルゴリズムを揃えていないと上手く認証ができないわけです。

つまり、今回みたいなバージョンアップによってサーバ側の認証方式が変わった場合、クライアントもそれに合わせて認証方式を変更しなければいけません。これができていなかったというのが上のエラーの原因です(これは Sequel Pro に限らない話ですが)。

Sequel Pro はどうやら未だに 5.5 の libmysqlclient (MySQLサーバに接続するためのクライアントライブラリ)を使っていて、さらには互換性チェックのために 5.5 → 5.6 → 5.7 → 8.0 という形でステップを踏んでアップデートしなきゃいけないみたいです(ソース)。ということで、Sequel Pro 側がなんとかしてくれるまで割と時間がかかりそうです。

ちなみに新しい認証方式の動作シーケンスなどの具体的な話は、開発チームのブログに書いてありました。要は、ユーザのパスワードハッシュをキャッシュすることで初回以降の認証では暗号通信によるコストが発生しないようにしようということだと思いますが、まだ詳しくは理解していません。

暫定的な対処

2つの対処方法があります。 1つは、MySQL の設定を書き換えてデフォルトの認証方式を元に戻す方法です。 my.cnf に以下を書いてあげればOKです。

[mysqld]
default_authentication_plugin=mysql_native_password

もう1つの方法は、ユーザ単位で認証方式を変更するものです(認証方式はユーザ単位で設定されています)。 これは以下のように設定できます。

ALTER USER ユーザ名 IDENTIFIED WITH mysql_native_password BY 'パスワード';

ただ、公式にも書いてありますがこれらはあくまでも暫定的な対処なので、できれば安全でセキュアな新しい認証方式に移行したほうが良さそうですね。

おわり

ということで MySQL の認証方式について書きました。せっかくアップグレードしたのでこれから少しづつ遊んでみようと思います。新しい認証方式ではユーザのパスワードハッシュをサーバ側でキャッシュするということなので、それが攻撃点になって pass the hash 的に食らうようになりそうな気がしなくもないですが、おそらくその辺は大丈夫なようになっているのでしょう(方式の詳細を全く理解していないのでよくわかりません)。おわり。

Clean Architecture を読んだ感想

Clean Architecture を読んだ感想というかまとめというか、備忘も兼ねて書きたいことをテキトーにメモします。

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Clean Architecture 達人に学ぶソフトウェアの構造と設計

全体的に、Clean Architecture という設計手法の具体的な How to が書かれているという感じではなく、ソフトウェアを設計する際に大事にするべき考え方の部分を軸に書かれているなあという印象でした。ソフトウェア設計の原則や考え方がざーっと序盤に書かれていて、それらを大事にするとこういうアーキテクチャがいいよね、という流れで Clean Architecture という設計が登場する流れです。ソフトウェア開発って、手法の優劣というか、主張の妥当性を示す決定的な根拠を出すのは難しいと思うのですが、その中でも、アーキテクチャが大事なんだという筆者の主張はそれなりに納得感のある形で伝わってきた気がします。

そんな感じで、優れた設計方法を知るというよりは、優れた設計の背景にある考え方を知るという感覚で読めました。あまりプログラミングや設計に慣れていない自分のような人間にもわかりやすく書かれていて、汎用的な知識として身についたものが結構あったと思います。オブジェクト指向や設計に深い理解がある人にとっては情報量が少ないかもしれないなーという気がしなくもないですが。

設計を題材にした本なので、優れた設計とはこういうものだという話を色々しているのですが、まとめると「変更に強い設計」を優れた設計と考えていいのではないかと思いました。逆に変更に対しての弱さはどこから生まれてくるのかと考えると、おそらく多くの場合で不適切な依存関係からではないかと思います。すなわち、システムやプログラムが疎結合な状態や依存関係がキレイな状態は、変更に強い状態と言えるのではないでしょうか。

で、プログラムをそのような状態にするために、オブジェクト指向プログラミングではポリモーフィズムが活躍するという話が沢山書いてあります。ポリモーフィズムを活用することで詳細な実装を隠し、アダプティブというかプラガブルな設計を作り出すことができるからです。依存関係逆転の原則によれば、ポリモーフィズムを使えば依存関係を自由自在にコントロールできるとも言えます。筆者はこれをオブジェクト指向の最たる強みであるとしています。変更への弱さが不適切な依存関係から生まれるのだとすれば、依存関係をコントロールできれば変更への強さを意図的に作れるからです。

オブジェクト指向についてまだまだ理解が浅い自分にとっては、その辺の考え方も整理された感があります。有名なソフトウェア設計の原則に、この依存関係逆転の原則や、オープンクローズドの原則といったものがありますが、改めてこれらにオブジェクト指向のエッセンスが垣間見えた気がします。「オブジェクト指向プログラミングとは何か」と聞かれたら「抽象でプログラミングする手法」とテキトーに答えていましたが、その点では筆者と考え方自体は近い気がしたので少し安心した感もありました。ちなみにオブジェクト指向以前のパラダイムの言語でも、一応ポリモーフィズム的なことは実現できたらしいのですが、関数ポインタを使うため危険性が高く、実用的ではなかったとのことです。

ただ、抽象化にはコストが伴うということは多くの人が経験的に知っていると思います。過激に抽象化して変更に強い状態を作るのは手間のかかる作業だし、逆に構造を把握しづらくなったりもします(YAGNI なんて言葉が生まれるくらいですから)。設計なんて考えずに、動けばいいやで書いたほうがサクッと作れたりするものです。しかしこの方法では機能変更、追加時に高く付きます。すなわち、将来のために投資するか否かという選択がここにあるわけです。投資すべきかどうかを絶対的に評価/判断する方法があるわけでもなさそうなので、この辺りはアーキテクトの腕の見せどころといったところなのでしょう。適切な意思決定には経験値はもちろん、業務知識なんかも必要になりそうです。とまあそんな感じで、YAGNI や DRY なんかと相反するように見える「優れた設計」についても筆者の意見が書かれていて、なるほどなーという感じでした。

あと個人的に面白いなと思ったのは、デキるアーキテクトは詳細(使うDBとかの具体的な話)の決定をできるだけ遅らせるようにデザインする的な話で、あまりできていなかったことだなーと。決定を遅らせるのが大事というよりは、決定を遅らせても大丈夫なような状態にしておくのが大事ということでしょう。Web サービスを作るときなんかは、開発初期段階でサーバや DB のことをかなり意識してしまっていたので、詳細よりもサービスの本当の価値となるビジネスロジック部分の方にもっとフォーカスしていくべきだと思いました。中核にあるビジネスロジックを捉えることは DDD に限らず大事な気がしますし。Web サービスを作る際に大前提として考えてしまいがちな 「Web で配信する」ということすら意識する必要がないという主張にはハッとした感がありました。

Clean Architecture を導入したプロジェクトはどのようなディレクトリ構成をとるべきなのかとか、ユースケースが実際にはどのように表現されるのかとか、Clean Architecture の具体的な部分はまだ理解していないことだらけなので、テスト的に実践してみようかなーと思います。ただ、こういうシッカリしたアーキテクチャってそれなりの規模のプロジェクトじゃないと有り難さがわからなそうな感はありますが...。

総括として、個人的な本書の位置づけは、ある程度オブジェクト指向を理解できてきたら次のステップとして読みたい本、という感じでした。同列のステップとしてデザインパターンの本などもありだと思いますが、あっちはなんというか具体例の裏にあるエッセンスを自分で掴んでいかないと、汎用的な知識としては得られるものが少ない気がしたので。本書は設計の「考え方」の部分が大きなウェイトを占めていたので、Clean Architecture というアーキテクチャ自体に興味がなくてもなにかしら気づきがありそうな印象を受けました。おわり。