最近使っているPHPライブラリ

※このエントリはOSS紹介 Advent Calendar 2017の11日目のエントリとなります。

10日目は@tsucchiさんの担当でstart-stop-daemon とちょっと変わったユースケースについてというエントリでした。

さて、今回は私が近ごろ使っているPHPのライブラリ(と言っても少ないのですが)を紹介していきます。

最終メンテ日時が結構前の物もあるのですが、おおらかな心でご笑覧いただけると幸甚です。

hashids/hashids

一意で連番ではなく短いID文字列を生成するライブラリ。bitlyやyoutubeのショートURLを想像してもらえるとわかりやすいでしょうか。

こんな感じで使うと、$hashに半角英数で表現されたハッシュ文字列が生成されます。

1
2
3
use HashidsHashids;
$hashids = new Hashids('Hello, world!');
$hash = $hashids->encode(333, 21, 101);

ちょっとメジャーすぎたかな。

uchiko/sql-maker

SQLクエリビルダですね。

詳しい使い方はドキュメントを見ていただくとして(雑)、私はこれを後述のライブラリと組み合わせて、Azure CosmosDB(DocumentDB API)への問い合わせ用クエリビルダとして利用しています。

cocteau666/AzureDocumentDB-PHP

CosmosDBのDocumentDB APIでPHPからクエリを投げ込むためのREST APIラッパーライブラリです。

前述のuchiko/sql-makerと組み合わせて、CosmosDBへPHPから自在に問い合わせを投げ込むことができるようになります。

junpei/geohex-php

GeoHex v3を取り扱うためのライブラリです。緯度経度からGeoHexコードに置き換えたり、その逆をやったり。

大まかな位置情報を扱いたいときに大変便利です。

ドキュメントがないので、だれかかいて(私が書くべきかw)

aporat/store-receipt-validator

Apple iTunesやGoogle Play、Amazon App Storeに対応したレシート検証ライブラリです。

PHPサーバサイドでモバイルアプリのレシート検証をすると言ったら、このライブラリを使えば簡単に実装できます。

Example書く元気がないので、各自ドキュメントを参照してください。(雑)

まとめ

私が使っているPHPのライブラリをいくつか紹介しました。本当に本当に雑な紹介ですけど、結構メジャーなところからマイナーなところまで取りそろえることができたと思います。

Perlコアモジュールに寄せてみる

※このエントリはPerl Advent Calendar 2017の9日目のエントリとなります。

8日目は@mackee_wさんの担当でマスタデータを宣言的かつ高速にテストするTest::MasterData::DeclareとTest2の構造についてというエントリでした。Test2を使ったモックデータを伴うテストの記述に、大変強力そうなモジュールでしたね。

さて、本エントリではPerl5におけるコアモジュールについてのさわりと、その中でも鉄板とも呼べる非常に有用なものをいくつか紹介していきます。

コアモジュールとは?

Perl5におけるコアモジュールとは、Perl5と抱き合わせでインストールされるモジュール群のことを指します。

コアモジュールとして収録されるモジュール群は、Perl5のバージョンごとに差がありますが、個人的な肌感としては、本当に鉄板とも呼べるような物は大抵5.14以降であればそろっていると考えても差し支えないと思います。

コアモジュールをちゃんと調べたい

私のようなテキトーな人間の肌感なんかは余り当てにならないでしょうから、確実性のあるソースを当たるのであればperldoc.perl.orgのコアモジュール一覧がありますので、そちらを参照するのが間違いないでしょう。

上記ページの左上には、Perlバージョンを指定するドロップダウンメニューがありますので、お使いのPerlバージョンを選ぶとよいでしょう。

もしくは、ターミナルで corelist モジュール名 と打ち込んでやると、そのモジュールがコアモジュールかどうかと、(もしコアモジュールの場合は)どのperlバージョンからコアモジュールに収録されたのかを返してくれます。

例えば私の使っているRazer Blade Stealth(Windows10 + Strawberry Perl 5.26.1)で Module::Load について corelist を使って調べてみると、以下のような結果が得られました。

1
2
3
4
C:Usersytnob>corelist Module::Load

Data for 2017-09-22
Module::Load was first released with perl v5.9.4

なお、 corelist コマンドは Module::CoreListというモジュールによって提供されておりますが、当然これもコアモジュールです。

1
2
3
4
C:Usersytnob>corelist Module::CoreList

Data for 2017-09-22
Module::CoreList was first released with perl v5.8.9

なぜコアモジュールを使うのか

Perlをはじめとしたモジュール管理のエコシステムが整ったプログラミング言語を各種開発に利用するにあたって、大抵はそのモジュラリティをフル活用した開発スタイルを取ることになると思います。つまり、インターネットなどを通じて外部からモジュールを取り入れて利用することがほとんどでしょう。

CPANなどで公開されている便利な外部モジュールを利用することで、本質的なロジックの開発に集中しようということがその狙いだったりするのですが、つぶさにコアモジュールを調べてみると、外部モジュールに頼るまでもなく、案外とコアモジュールで済ませられる事も多かったりします。

コアモジュールに寄せておくことで、以下のようなメリットに与ることができます。

  • スクリプトの利用環境制限を緩めることができる(Windowsでも動く、とか)
  • 外部モジュールのインストールの手間を省くことができる

私が選んだお気に入りコアモジュール5選

さて、ごちゃごちゃと説明してきましたが、私がよく使うコアモジュールを5つだけピックアップしてみました。本当はもっとお世話になっているコアモジュールはいっぱいあるのですが、執筆に必要な集中力の都合上、このくらいに留めさせておいてください。

HTTP::Tiny (from v5.13.9)

LWP::UserAgentFurlよりもプリミティブな感じのあるHTTPクライアントモジュール。レスポンスがオブジェクトではなくハッシュリファレンスで帰ってくる点が前述のモジュール達よりもシンプルです。

HTTP::Tinyについては、shohexさんが6年前に書いたエントリが非常にわかりやすいですので、そちらもご覧ください。

Module::Load (from v5.9.4)

モジュールの動的ローディングを実現するモジュール。状況によって読み込むモジュールを差し替える(Strategyパターン)ようなケースに使ったりします。

似たようなものにはClass::Loadとかがあるんですが、Module::Loadだとちょっと足りない、というケースで使うかもしれません。私は使わないけど。

Encode (from 5.7.3)

日本人に生まれ日本語とPerlを操っている以上、マルチバイト文字と仲良くすることが求められるのですが、Encodeはそのためのツールを提供してくれるモジュールです。もはや説明の余地もないですね。

JSON::PP (from v5.13.9)

PerlでJSONを扱うためのモジュール。これとHTTP::Tinyをつかうことで、コアモジュールだけでちょっとしたAPIクライアントを作ったりすることができます。

とにかく速度を求められるケースであればJSON::XSを使ったりするのですが、速度よりも簡便性を求める場合にはJSON::PPを使うことが多いです。Windowsでもつるしで使えますし。

List::Util (from v5.7.3)

配列操作に関する追加の関数を提供してくれるモジュール。プログラムを書く仕事をして15年以上経過していますが、配列を処理するプログラムは頻出パターンの一つで、必然的にこのモジュールに助けられる事が多いです。

君もコアモジュールを探検してみよう

時折思い出したようにコアモジュールを探検してみると、「こんな便利なのがコアにあったんだ!」という発見があったり、「このモジュールはこういう使い方もできるな」という発想ができたりと、なかなか飽きないものです。

そもそもコアモジュール自体は結構種類があるので、全部を覚えるのはまぁ無理なんじゃないかとも思われますが、普段なかなか見かけないコアモジュールの使い道を考えたりするのも面白いものですよ。

さて、明日は@tsucchiさんの番で、なにやらMinionの話かなにかをしてくれるようですよ。楽しみですね。

FunctionsのApp Serviceプランで快適な気になった話

※このエントリはMicrosoft Azure Advent Calendar 2017の3日目のエントリとなります。

個人的に運営しているサービスの運営にAzure Functions(従量課金プラン)を使っていたが・・・

@ytnobodyです。

私が個人で運営している「うまミる」(旧:南関テイオー)という競馬予想サービスでは、予想データを作成するロジックをAzure Functionsで実行しています。

そのうまミるですが、先月くらいに不可解な問題に直面しながらも、従量課金プランでだましだまし運用していたのですが、とうとう現象が毎日発生するようになってしまいました。

予想ロジックの複雑化に伴い、従量課金プランでは捌ききれなくなったのだろうと思い、思い切ってApp Serviceプラン(Basic S1)にデプロイしてたところ、予想以上に快適でした。

当たり前だけど速い

当然のことなのですが、従量課金プランと比較して段違いに速いです。料金的には、それまではだいたい月額1500円程度だったところが5400円ほどにアップしていますので、個人で支払うにはなかなかの額面になってしまったと感じています。

しかし、毎朝Functionsの状況を確認する必要性がなくなったことを考えると、コスパは予想以上に良いと感じました。

実際どのくらいのコンピューティングリソースを使っているのかを把握できる

PaaSを使うという観点では正直なところ、どれだけコンピューティングリソースを使っているのかという点について、個人的には全く気にしたくないところではあります。

しかし、安定稼働とコストの両立を考えると、どれくらいのコンピューティングリソースを使っているのかを把握しないと、ちょうどよさのあるプラン選定ができない、というのが現状です。

App Serviceプランにすると、Application Insightsを有効化するだけで簡単にリソース使用状況や関数の実行に関する統計(成功・失敗や関数の実行にかかった時間など)が可視化できます。これはちょうどよさのあるプランを選定するうえでありがたい話です。

そういえばこの原稿を書きながらApplication Insightsを見ていたら、妙に失敗率のたかい関数がなぜ失敗しているのかをようやく把握することができました(これについてはそのうち別途エントリを起こすと思います)。

まとめ

以前サポートを受けた際、「従量課金プランでは厳しいかもしれませんので、App Serviceプランもご検討ください」という助言をいただいていましたが、明らかに快適になりました。当然元々のペインポイントであった「毎日関数が起動できなくなってしまう」という問題も解決しています。

最後に、状況にもよるのでしょうが、Azure Functionsについては「従量課金プランで感覚をつかんだら、App Serviceプラン + Application Insightsも試してみてほしい。MSの本気はどうやらこっち側だ。」ということでまとめとさせていただきたいと思います。

Azure Functionsの関数実行時にエラーとなった場合のとりあえずの処置方法

今朝、南関テイオーの予想配信関数が実行失敗しており、その復旧作業を行いました。本エントリではその一部始終をまとめてみました。

原因を確認する

南関テイオーの予想配信関数は7:10(JST)に起動するようにTimer Triggerが設定されています。

Azure Functionsでは、関数ごとに「モニター」という項目があり、そこで実行ログを閲覧することができます。

まず実行ログになんらかの異常が吐き出されているだろうということで確認してみたところ、案の定、予想配信関数の実行時に、以下のようなエラーが出ていました。

1
2
3
4
5
6
7
8
9
10
11
12
Exception while executing function: Functions.getRace
Microsoft.Azure.WebJobs.Host.FunctionInvocationException : Exception while executing function: Functions.getRace ---> System.ApplicationException : 0 [unknown (0xFB4)] bash 4924 D:Program Files (x86)Gitbin..usrbinbash.exe: *** fatal error - Couldn't set directory to \?PIPE temporarily.
Cannot continue.

at async Microsoft.Azure.WebJobs.Script.Description.ScriptFunctionInvoker.ExecuteScriptAsync(String path,String arguments,Object[] invocationParameters,FunctionInvocationContext context) at C:projectsazure-webjobs-sdk-scriptsrcWebJobs.ScriptDescriptionScriptScriptFunctionInvoker.cs : 114
at async Microsoft.Azure.WebJobs.Script.Description.ScriptFunctionInvoker.InvokeCore(Object[] parameters,FunctionInvocationContext context) at C:projectsazure-webjobs-sdk-scriptsrcWebJobs.ScriptDescriptionScriptScriptFunctionInvoker.cs : 64
at async Microsoft.Azure.WebJobs.Script.Description.FunctionInvokerBase.Invoke(Object[] parameters) at C:projectsazure-webjobs-sdk-scriptsrcWebJobs.ScriptDescriptionFunctionInvokerBase.cs : 95
at async Microsoft.Azure.WebJobs.Host.Executors.VoidTaskMethodInvoker`2.InvokeAsync[TReflected,TReturnType](TReflected instance,Object[] arguments)
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionInvoker`2.InvokeAsync[TReflected,TReturnValue](Object instance,Object[] arguments)
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.InvokeAsync(IFunctionInvoker invoker,ParameterHelper parameterHelper,CancellationTokenSource timeoutTokenSource,CancellationTokenSource functionCancellationTokenSource,Boolean throwOnTimeout,TimeSpan timerInterval,IFunctionInstance instance)
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithWatchersAsync(IFunctionInstance instance,ParameterHelper parameterHelper,TraceWriter traceWriter,CancellationTokenSource functionCancellationTokenSource)
at async Microsoft.Azure…

予測

ひとまずリトライを何度か試してみたところ、全て同じ現象が発生。これは以前サポートに相談した時と同様にリソース枯渇が原因なのではないか?と予想。

復旧手順

復旧までの手順は以下の通りとなり、今回は無事に復旧することができました。

  1. 新しいFunctions Appリソースを作成する(今回はリージョンの問題ではないので同一リージョンでOKだった)
  2. アプリケーション設定(環境変数)を新しいFunctions Appリソースにコピペする
  3. 従来のFunctions Appリソースを停止する(重複起動を防ぐ)
  4. 新しいFunctions App側でデプロイオプションを設定する(ここで関数のソースコードがデプロイされる)
  5. 関数を実行する

特に4の手順が実行できるよう、bitbucketなどを利用してgitレポジトリ連携をしておくことが、手早い復旧をする上で肝要と言えます。

まとめ

Functions App(特に従量課金プラン)においては、リソース枯渇による関数実行の失敗がありえます。これはクラウドサービスを活用する以上、付いて回る類の問題と言えるのではないでしょうか。

そのような問題を乗り越えるためには、最初から「壊れてもすぐ直せる」ようにシステムを作っておくことが大事だと考えます。

AzureのSupportが大変素晴らしいという話

山の日(8/11)、私がひっそりと進めている南関競馬予想「南関テイオー」が朝の予想バッチをサボタージュしてしまいました。南関テイオーの開発秘話についてはこちらに書いてありますので、興味のある方は是非読んでみてください。なお、南関テイオーは事業化できるように鋭意調整中です。

南関テイオーの大まかな構成

南関テイオーは、Azure(従量課金プラン)上に予想エンジンを構築してあり、予想結果と当選情報をBloggerに垂れ流すようにしてあります。最近まで一部、機械学習を用いた学習モデルを適用している箇所がありましたが、試行錯誤の末、一般的な数式に置き換えました(その方が回収率が高い)。

南関テイオーの大まかなシステム構成

forkに失敗する?

山の日に起こった現象は、「forkに失敗するせいで関数が起動できない」というものでした。以下、実際にAzure Portalで確認した内容です。

Azure Portalで確認

念のためAzureSupportに聞いてみる

あまりにもよくわからない現象でしたので、@AzureSupportに質問することにしました。

その後Direct Messageで詳細を聞かれましたが、Support Requestを送るように指示されました。

We recommend filing a support case via “Resource health” from the Azure Portal. After selecting the resource you are having issues with, navigate to “Resource health” by clicking the link under the “Support + Troubleshooting” section of the left blade. Select the option for “Contact Support” and follow the “New support request” workflow to submit your case. If you do not have a Support Plan enabled, be sure to select “Resource health” as the Support Plan.

オペレーション方法も教えてくれて、めっちゃ親切です。

で、この指示に従ってSupport Requestを送りました(念のため英語で・・・本当に拙い英語で・・・)。

すると10分後・・・

電話がかかってきた!

電話の相手は日本マイクロソフトのサポート担当の女性の方でした。英語ではなく日本語で対応してくれたことをありがたく思っていたところ、サポート曰く、

「事業影響度をAに設定したのはなぜですか?」

とのこと。事業影響度というのは、Support Requestのパラメータの一つであり、A,B,Cの3段階で1つ選択して設定することができるものなのですが、それぞれ以下のような定義となっています。

A. Critical impact - Significant loss or degradation of services (重大な影響 - サービスの大幅な損失または劣化)

B. Moderate impact - Moderate loss or degradation of services (中程度の影響 - 中程度の損失またはサービスの低下)

C. Minimal impact - Minimal loss or degradation of services (最小限の影響 - サービスの最小限の損失または劣化)

南関テイオーは当時も現在もサービスとして有償提供しているものではありません。しかし冒頭にも書いた通り、事業化を視野に入れていることも事実です。そこで私は先ほどの質問に対し、

「現時点では個人で稼働させているものであり、サービスインしているわけではないのですが、将来的にサービスインさせた時に、現在のこの状況はお客さんからクレームが来ることになりますので、Aとしました。」

と答えたところ、

「わかりました、それでは事業影響度をAとして対応を進めさせていただきます。後ほどエンジニアから連絡します。」

とのお返事をいただきました。すごい。ちゃんと話すことは重要ですね。

サポート部門のエンジニアさんからの連絡

数分後、サポートエンジニアのXさん(こちらは男性)からお電話がありました(祝日にもかかわらず対応ありがとうございました)。どこかで聞いたようなお名前の方でしたが、それはさておき、まずは調査してくださるということでした。

さらに数分後、Xさんから改めてお電話があり、関連しているかもしれない異変があることを説明してくれました。

1
2
3
4
5
6
7
Xさん「当該時間帯に、デプロイ先のインスタンスでメモリリソースの枯渇があったようです。現時点では解決しているので、関数を再実行できるようでしたらお試しいただけますでしょうか?」

私「ありがとうございます。試してみます。(インスタンスのメモリリソース枯渇って、PaaS利用者としては気にしたくないことだよなぁ・・・)」

Xさん「それから、現在Consumption Planで稼働しているようですが、関数のメモリ使用量が多いようですので、App Service Planでの稼働もご検討してみてください。」

私「わかりました、ありがとうございます。」

若干の違和感を感じながらも、ひとまず関数の再実行を試みたところ、正常に稼働しました。Azure Functionsでforkできない時には、メモリ不足を疑うと良いようですね。

まとめ

どうしてもわからない障害にぶち当たった時でも、24/365で対応してくれるAzureのサポート体制に大変感動しましたし、そして本当に助かりました。

Azure Web Appsのデプロイオプションと.sshの関係性

今日色々とAzure Web Appsで設定をしていて、デプロイオプション周りでハマりました。とはいえ、わかってしまえばどうと言うことのないことなので、簡単に説明していきます。

Azure Web Appsでデプロイオプションを変更すると、~/.sshが初期化される

見た通りのことなのですが、例えばn0bisuke氏が書いたこのエントリを参考にデプロイ設定をしたとします(なお、リンク先は本当に参考になるエントリです)。

で、一通り設定が終わった後に、Azure Portalから「デプロイオプション」> 「切断」を実行すると、 ~/.ssh の中身が空っぽになります。

この挙動に気がつかないと、改めて「デプロイオプション」を再設定しても「なぜかgit cloneに失敗する」という状態に陥ることになります。

まとめ

デプロイオプションを変更したら、 ~/.ssh の中身を確認しましょう。

Azure FunctionsでqueueTriggerのスロットリングをする

Azure Functionsを使った以下のようなサイト巡回の仕組みについて、是非とも気をつけたい箇所がありましたので、共有です。

このような特定のサイトを巡回する仕組みを作るとき、巡回先のサイトに対して秒間10リクエストくらいの比較的高密度なリクエストを送ることは、普通に考えると相手先のシステムにとって迷惑であろうと想像がつくことでしょう。

このような場合、リクエスト密度を軽減するために、並列リクエスト数の制限を行ったり、リクエストとリクエストの間にクールダウンを設けたりすることがあります。

クールダウンについては適宜sleepを入れるなどの対応をすることで、一応相手先に対するリクエスト密度の軽減が見込めることでしょう。しかし、並列リクエスト数の制限を行わないことには、ひっきりなしにリクエストを投げることになりかねません。

Queue Triggerにおけるジョブの並列処理数調整

今回例に挙げた構成の場合、巡回対象URLが多くなってくるとQueue Storageに一気にジョブが登録され、それらが片っ端から一気にQueue Triggerによって処理されてしまうことが問題となります。

上記の図にもある通り、Azure FunctionsにおけるQueue Triggerの並列処理数は、デフォルトでは16となっています。

この並列処理数を下げるためには、プロジェクト直下に以下のような内容でhost.jsonを作成し、デプロイする必要があります。

1
2
3
4
5
{
"queues": {
"batchSize": 3
}
}

このqueues.batchSizeこそが、Queue Triggerの並列処理数設定となっています。

このように、相手先のシステムに過剰な負担を与えないようにすることができます。

まとめ

web巡回をAzure Functionsでシステム化しようとしている方は、是非ともQueue Triggerの並列処理数にも気をつけてください。

なお、host.jsonに関わる情報はgithubのazure-webjobs-sdk-scriptプロジェクトしか情報源を見つけることができませんでしたが、host.jsonをしっかり確認することで、Azure Functionsのチューニングが可能とも言えますので、要チェックです。

Kichijoji.pm #11 でLTしました

Kichijoji.pm #11 に参加し、「あれっもしかしてPHPっていいんじゃないの?」というタイトルでLTしてきました。

This is an embedded Microsoft Office presentation, powered by Office Online.

内容としてはYAPC::Fukuoka 2017で話した内容の超絶短縮版なのですが、若干の内容追加も行なっております。

個人的に興味あった話

Songmuさんのbusyboxを参考にしたバイナリサイズダイエットの話ってのがなかなか面白くて、go generateからのperlってのがとにかく格好良さありましたね。一つの言語に縛られずに手間を無くしていく様子はさすがというところです。

macopyさんkuiperbeltの話なんかは、だいぶ前に構想を聞いた時と比較してどんどんdocs周りが洗練されてきていると感じました。リアルタイムなやり取りが必要なケースではちょっと試してみたいかも。

sakuraさんの検索の話、独特のリズム感と検索エンジンに出てくる広告の話への転換があり、トークの間「次は何がくる?」という期待の連続で非常に楽しませていただきました。

Azure Functions CosmosDB input bindingsを使う上でのコツ - 余剰演算子編

先日あたりからAzure FunctionsのCosmosDB input bindingsを使っていて、いくつかはまりどころのような挙動に出くわしましたので、その説明と対処法を紹介していきます。

sqlQueryで剰余演算子(Modulation Symbol)がそのまま使えない

例えば以下のようなQueryをCosmosDBに投げるとします。

1
SELECT c.id FROM c WHERE c.level % 20

このクエリを直接クエリエクスプローラで実行する場合、期待する結果が得られます。

ところがこのクエリをAzure FunctionsのCosmosDB input bindingsにsqlQueryとして指定した場合、以下のようなエラーがでてしまい、期待した動作をしません。

1
2017-07-05T16:45:43.113 Exception while executing function: Functions.MyFunction. Microsoft.Azure.WebJobs.Host: The '%' at position 34 does not have a closing '%'.

理由

実はsqlQueryの仕様として、%というのはアプリケーション設定の値を参照するために使う記号でして、%myappSetting%のような感じで使うものだったんですね。

そのため、単独で%が登場すること自体があってはならない、と、そういうことが理由となっています。

対処

対処法の一つとして、アプリケーション設定値に%を設定して、それを使えばよい、というものがあります。

アプリケーション設定から値を設定する

上記画像のように、例えばmodulationsymbolというアプリケーション設定値を定義しておき、sqlQuery側では以下のように%%modulationsymbol%に置き換えることで、期待通りの結果を得ることができます。

1
SELECT c.id FROM c WHERE c.level %modulationsymbol% 20

あまりカッコよくはないですが、致し方がない感じはしますね。

元ネタ:https://stackoverflow.com/questions/44903632/sqlquery-of-documentdb-input-bindings-with-modulation-symbol-makes-functions-fa

Azure Functions CosmosDB input bindingsを使う上でのコツ - バインド変数編

さて、今度はバインド変数に関する躓きを共有します。

クエリエクスプローラとFunctions経由との間でクエリの結果が異なる???

例えば以下のようなクエリをCosmosDBに投げるとします。

1
SELECT c.id FROM c WHERE CEILING(c.age / 10) * 10 = 30

クエリエクスプローラでは以下のような結果が得られました。

1
2
3
4
5
6
7
8
9
10
11
[
{
"id": "user-123"
},
{
"id": "user-124"
},
{
"id": "user-133"
}
]

今度はクエリの30に当たる箇所を{generation}として、sqlQueryに設定します。

1
SELECT c.id FROM c WHERE CEILING(c.age / 10) * 10 = {generation}

こんな感じですね。そしてFunctions側でQueue Storageトリガーを使って以下のようなジョブを受け取ります。

1
{"generation": 30}

この時、Functionsで受け取れるCosmosDB inout bindingsの結果は以下のようなものとなります。

1
[]

すっからかんですね。。。一体どういうことでしょう。

理由

初めて知った時には非常に驚きましたが、どうやら各bindingsにわたってくる値は文字列としてキャストされてしまうっぽいということです。

本当は以下のように展開してほしいのですが。。。

1
SELECT c.id FROM c WHERE CEILING(c.age / 10) * 10 = 30

こんな感じになってしまうということです。

1
SELECT c.id FROM c WHERE CEILING(c.age / 10) * 10 = "30"

これは厄介ですね。。。。

対処

これを回避するには、クエリの方で強制的に数値に置き換えてやればよいのですが、CosmosDBのビルトイン関数には数値へのキャストをしてくれるものがありません。

そこで登場するのが**ユーザ定義関数(User Defined Function/UDF)**です。

CosmosDBでは、javascriptを使ってユーザが関数を定義することができるのです。

CosmosDBのブレードにあるスクリプトエクスプローラというメニューから、UDFの定義ができます。

UDF開発画面

上記画像のような関数を定義してやり、sqlQuery側を以下のように変更すると、無事に期待した結果が得られます。

1
SELECT c.id FROM c WHERE CEILING(c.age / 10) * 10 = udf.ConvertToNumber({generation})

このほか、時刻にまつわる関数などもCosmosDBにはビルトインでは存在しませんが、このようなUDFを作って対応することが可能です。

CosmosDBのUDFは非常に強力な機能ですので、是非とも有効に活用していきたいものですね。

元ネタ:https://stackoverflow.com/questions/44916811/difference-among-the-azure-cosmosdb-query-explorers-results-and-the-azure-funct/44918767#44918767