冪等性

冪等性とは「同じ操作を何度繰り返しても、同じ結果が得られる性質」のこと

冪等性(べきとうせい、idempotency, idempotence)とは、同じ操作を何度繰り返しても、同じ結果が得られるという性質です。冪等性がある操作(idempotent operation)は、1回操作した場合の結果と、2回以上操作した場合の結果は同じになります。

冪等性のろうそく_key.png


関数における冪等性

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





  1. DELETEは冪等性がありますが、1回目の削除でリソースが無くなるので、2回目の操作からは404が返ることが普通です。これについては本当にDELETEって冪等なの?というスタック・オーバーフローの質疑応答が参考になります。 



  2. もしもこの投稿を読む度に発見があり、知識が増したり、成長するようであれば、読み手には冪等性が無いことになるわけですが、それはさておき、この投稿を何度も読んでくださったことは大変嬉しく思います。