冪等性(べきとうせい、idempotency, idempotence)とは、__同じ操作を何度繰り返しても、同じ結果が得られる__という性質です。冪等性がある操作(idempotent operation)は、1回操作した場合の結果と、2回以上操作した場合の結果は同じになります。
関数における冪等性
f
という関数に引数x
を与えた場合、次の式が成り立つ性質。(もちろんf
は副作用がない関数であることが前提です)
f(x) = f(f(x))
冪等性がある関数の例
絶対値を求める関数は冪等性があります。abs(-100)
の結果は100
ですが、それを何度abs()
に渡しても、最初に得られた結果である100
と同じ結果が必ず得られます。
abs(-100) = abs(abs(-100))
冪等性がない関数の例
冪等性でない関数には、平方根を求める関数などがあります。sqrt(16)
の結果は4
です。4
を更にsqrt()
に渡すと、結果が2
になります。一度目の結果と二度目の結果が異なるため、冪等性が無いわけです。
sqrt(16) ≠ sqrt(sqrt(16))
RESTfulにおける冪等性
冪等性はRESTfulの文脈でもよく話題になります。RESTfulでも冪等性の概念は同じで「同じ操作を何度繰り返しても、同じ結果が得られる」という意味です。もう少しRESTfulっぽく寄せた説明をするなら、「同じリクエストを何度繰り返しても、同じリソース状態になること」という感じになると思います。
得られる結果というのは__リソース状態であって、レスポンスではない__ことに注意してください。DELETE
メソッドには冪等性がありますが、1度目のリクエストでは200レスポンスを、2度目以降のリクエストは404レスポンスを返すことが普通です1。このように冪等性があるAPIでも回数に応じてレスポンスが変化することがありますが、リソースが削除されたという結果(リソース状態)が2度目のリクエストで覆ることはありません。
冪等性があるRESTful APIの例
RESTfulではGET, PUT, PATCH, DELETEは冪等性があるメソッドとされています。
GitHubのAPIにはリポジトリにスターをつけるAPIが生えていますが、これは冪等性があるAPIです。
PUT /user/starred/:owner/:repo
何度上のPUTメソッドを叩いても、結果はスターがついた状態になります。スターが外れることはありません。
冪等性がないRESTful APIの例
一方、POSTは冪等性がないメソッドとされています。例えば、GitHubのAPIにはissueを立てるAPIがありますが、これは冪等性がないAPIです。
POST /repos/:owner/:repo/issues
同じリクエストを二度送ると、2個issueができてしまいます。
RESTfulの冪等性に関連して、PUT か POST か PATCH か? - Qiitaもご覧ください。
自動化における冪等性
ChefやAnsibleなどのようなインフラ構築をプログラムで自動化する界隈でも冪等性という言葉がよく出てきます。自動化における冪等性の考え方も、他の分野の考え方と同様に「同じ操作を何度繰り返しても、同じ結果が得られる」というものです。
冪等性がある操作の例
ファイルをコピーする操作は冪等性があります。$ cp file_a file_b
のような操作は、file_b
がなければ作られ、すでに有ったとしても内容が変わったりすることはありません。これは何度やってももたらされる結果が同じであるため冪等性があると言えるわけです。
冪等性がない操作の例
逆に、echo "some_config" >> /etc/some.conf
のような、ファイル末尾に行を継ぎ足す操作には冪等性がありません。実行するたびに行数が増えていきます。つまり、操作の回数に応じてもたらされる結果が異なるわけです。こうした操作は、すでにファイル末尾に設定が書き込まれているかをチェックしてあげる必要があります。
冪等性のメリット
冪等性のメリットのひとつは、関数やAPI、コマンドを実行する側が、__前提となる状態を気にしなくてよくなる__という点です。RESTful APIを例に考えてみましょう。
この投稿にいいね!をつけるAPIを想像してみてください。
POST /items/:item_id/like
このAPIは、この投稿にあなたがまだ「いいね」がついてなければ「いいね」をつけてくれます。しかし、もしも「いいね」済みだったら、その「いいね」は取り消される仕様になっています。これは冪等性がないAPIですね。
このAPIを使ってプログラムを書く側は、「投稿にいいねがついているか」を気にしなければなりません。プログラムとしては「いいね」を付けたいだけで、投稿がいいね済みかは気にしたくありませんが、コードはきっとこうしたif分岐が必要になることでしょう。
if (post.liked) {
// すでにいいねしている場合は、いいねしない
} else {
apiClient.likePost(post.id);
}
もしも、このAPIが冪等なものだったら、クライアントコードはいいね済みかどうかを気にせずAPIを呼び出せるようになります。結果的に、条件分岐が不要になり、コードもシンプルになります。
apiClient.likePost(post.id);
冪等性の他のメリットとしては、リトライのしやすさ、重複の排除、並行性を気にしなくていい点などもありますが、そこらへんの説明は追々していけたらと思います。
冪等性は外からみた性質
ここまで冪等性について読むと、「例えばメールアドレスを更新する操作があるとして、これを冪等なものにするなら、メールアドレスを__更新する__SQLを__毎回__発行するような実装にするんでしょ」と思うかもしれません。これは正しくもあり間違いでもあります。
冪等性は「外から見た」性質です。外からみて、つまり、APIを使う側から見て「冪等」かどうかという基準だけがあります。つまり、内部実装は、毎回更新SQLを発行するようになっていてもいいですし、更新済みなら更新しないような実装になっていてもいいわけです。
どっちも冪等性があるAPI:
function updateEmail(user, newEmail) {
sql.exec("UPDATE users SET email = {newEmail} WHERE id = {user.id}")
}
function updateEmail(user, newEmail) {
if (user.email == newEmail) {
// 更新はしない
} else {
sql.exec("UPDATE users SET email = {newEmail} WHERE id = {user.id}")
}
}
おわり
この投稿は何度読み返しても同じ説明になるので、この投稿には冪等性があります。2
最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします→Twitter@suin
-
DELETEは冪等性がありますが、1回目の削除でリソースが無くなるので、2回目の操作からは404が返ることが普通です。これについては本当にDELETEって冪等なの?というスタック・オーバーフローの質疑応答が参考になります。 ↩
-
もしもこの投稿を読む度に発見があり、知識が増したり、成長するようであれば、読み手には冪等性が無いことになるわけですが、それはさておき、この投稿を何度も読んでくださったことは大変嬉しく思います。 ↩