Kyrt Blog http://kyrt.in/ もろもろの備忘録、C#とAzureなど en-us Fri, 19 May 2017 00:00:00 +0900 http://kyrt.in/2017/05/19/install_azure_powershell.html http://kyrt.in/2017/05/19/install_azure_powershell.html <![CDATA[Azure PowerShellのインストール事情]]> Azure PowerShellのインストール事情

最近のAzure PowerShellのインストール事情が少々メンドクサイことになっているのでメモ代わりに書き留める。

基本は、Azure テクニカル サポート チームがAzure PowerShell 最新版のインストール手順 (v3.8.0 現在)に書いている状況だが、いろいろあるので書いた。

2017/5/19、Azure PowerShell 4.0.1 時点の情報です

sky

推薦されるインストール方法

以前は、WebPI経由でAzure PowerShellをインストールしていたが、現在では PowerShell Gallery 経由のインストールが推薦だ。公式ドキュメント、Install and configure Azure PowerShellにも、下記のように書かれている。(ちょっと翻訳が追いついていないようで、サイトは英語のまま)

Installing Azure PowerShell from the PowerShell Gallery is the preferred method of installation.

Windows 10、Windows Server 2016より古いOSではデフォルトでPowerShellGetが使えないので、Windows Management Framework (WMF) 5.0 を入れる必要がある[1]

PowerShellGetでPowerShell Galleryから方法は簡単だ。(以下、Install-Moduleで入れると記載)

$ Install-Module AzureRm

MSIからのインストール

Install-Moduleで入れる方法の他に。Web Platform Installer (WebPI) やVisual Studio 2017のVisual Studio Installer での、Azure「Azure development」の選択、GitHubからMSIファイルをダウンロード[2]などの方法で、MSIファイルからインストールすることができる。MSIファイルからインストールは推薦されていないが、VS経由の時にように選択の余地なくMSI経路でインストールされてしまうこともある。

また、ググって出てくる日本語の公式ページがその他のインストール方法で、その他の方法としてMSIでのインストール方法を紹介している。ここには推薦は、Install-Moduleを使うことだと書いてあるが、よく読まないで書いてある通りに操作するとMSIで入れてしまうという落とし穴もある。今のところVisual Studio 2017 で入るのは 2.1.0と大分古いバージョンだというのがいろいろな問題を面倒にしている。

問題点:MSI -> Install-Module でエラー

PowerShell Gallery と、MSI 経由のインストールには互換性が無く、MSI経由でAzure PowerShellが入った環境に PowerShell Gallery で新しいものを入れたりInstall-Module AzureRM、アップデートUpdate-Module AzureRmすることはできない。

例えば、Visual Studio 2017のインストールで、Azure PowerShellが入っている状態で、Install-Module AzureRMを やると下記のようなエラーになる。

$ install-module AzureRm

PackageManagement\Install-Package : The following commands are already available on this system:'Get-AzureStorageContainerAcl,Start-CopyAzureStorageBlob,Stop-CopyAzureStorageBlob'. This module 'Azure.Storage' may override the existing commands. If you still want to install this module 'Azure.Storage', use -AllowClobber parameter.
At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:1809 char:21
+ ...          $null = PackageManagement\Install-Package @PSBoundParameters
+                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Microsoft.Power....InstallPackage:InstallPackage) [Install-Package], Exception
    + FullyQualifiedErrorId : CommandAlreadyAvailable,Validate-ModuleCommandAlreadyAvailable,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage

つまり、MSIインストールとInstall-Moduleは互換性が無く、「混ぜるな危険」という状況だ。

解決策

もし混ざってしまったからどうするか。

MSIで入れたものを、設定画面の、「アプリと機能」、あるいはコントロールパネルの「プログラムのアンインストールまたは変更」からアンイストール後、再度 Install-Moduleで PowerShell Gallery からインストールする。

下記は、PowerShellからアンインストールする方法。実行には、Admin権限が必要。

$ $app = Get-WmiObject -Class Win32_Product -Filter "Name like '%Azure PowerShell%'"
$ $app

IdentifyingNumber : {CB3F8A12-1570-4964-8206-17274AB9EF4D}
Name              : Microsoft Azure PowerShell - September 2016
Vendor            : Microsoft Corporation
Version           : 2.1.0
Caption           : Microsoft Azure PowerShell - September 2016

$ $app.Uninstall()

これで下記のように入れ直すと入る。

$ Install-Module AzureRM

原因

MSIで入れた場合とInstall-Moduleで入れた場合、モジュールは別の場所に入る。

MSIの場合は、下記のPSModulePathに入り

C:Program Files (x86)Microsoft SDKsAzurePowerShellResourceManagerAzureResourceManager
C:Program Files (x86)Microsoft SDKsAzurePowerShellServiceManagement
C:Program Files (x86)Microsoft SDKsAzurePowerShellStorage

Install-Moduleの場合は下記に入る

C:Program FilesWindowsPowerShellModules

MSIで入れた後にInstall-Moduleで入れた場合、MSIで入れたものを認識せずにインストールが進み、途中で別のバージョンのモジュールをロードして前記のエラーになる。Install-Moduleで入れた後に、MSIで入れると次のような問題が起きる。

MSIインストールの問題点

現在のMSIインストールで把握している問題点は3点ある(増えるかも)、そのうち深刻なのは1つ目の問題だ。

  1. Install-Moduleのモジュールを無条件上書きする
  2. Import-Module AzureRMでエラー
  3. WebPIでのリリースが遅い

1. Install-Moduleのモジュールを無条件上書

現在のMSIインストールは、Install-Moduleで入れたModuleがあれば削除してから、モジュールのインストールを行う。そのとき既存のモジュールのバージョンは見ていないので、Install-Moduleで4.0.1などのあたらしいバージョンを入れていても、VS 2017 を入れると、2.1.0になってしまうなどの現象が起きる。削除処理は、setup/Setup/RemoveGalleryModules.ps1のあたり。 Scope CurrentUserで入れた場合は考慮されておらず、なかなか残念なコードだ。

MSIだけで、バージョンアップを重ねている場合は、Windowsのインストールの仕組みでバージョンチャックされて古いバージョンは入らないので問題がないが、Install-Module(psget)で入れたものとの共存は考えられておらず、いきなり古いバージョンで上書きされるというわけだ。[3]

これは非常にメンドクサイ

2. Import-Module AzureRMでエラー

MSIで入れると、Import-Module AzureRmがエラーになる。これは、AzureRMというマニフェストモジュールが無いのが原因。Azure Poshの説明のあちこちで、Import-Module AzureRMが出て来るが、実はあまり意味がなく、モジュールはインストール時にModule Path配下に配置され、自動的にロードされるので、マニフェストモジュールが無くても動作上の不都合は無い。ただ、ドキュメントに書いてあるものがエラーになるのはちょと混乱する。ちなみに、Install-Module経由で入れてた場合は、AzureRM マニフェストモジュールが入り、Import-Module AzureRMも成功する。

$ import-module azurerm
import-module : The specified module 'azurerm' was not loaded because no valid module file was found in any module directory.
At line:1 char:1
+ import-module azurerm
+ ~~~~~~~~~~~~~~~~~~~~~
   + CategoryInfo          : ResourceUnavailable: (azurerm:String) [Import-Module], FileNotFoundException
   + FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand

3. WebPIでのリリースが遅い

なかなか、WebPIにリリースされない。今回だと、Managed Diskの機能がサポートされたバージョンはしばらく放置され、5/10にやっとリリースされた。最新版を使いたい場合は、Install-Module経由で入れるしかない。WebPIに頼らずに、GitHubからMSIをダウンロードして入れるという方法もある。[2]

まとめ

Azure PowerShell を使う場合、Install-Module AzureRMで入れる、VS 2017や以前WebPIで入れている場合は、一度コンパネからアンインストールしてから入れる。Windows 10より古い環境では、WMF 5.0を入れよう。

今のVS 2017は、リリースのたびにAzure PowerShellの古いバージョンを入れてくるが、挫けずにアンインストールしてから入れ直す。

現状だとGitHubからダウンロードして、MSIインストールで運用という手もあるが、推薦インストール方法がInstall-Module経由なので、そちらを取った方が無難だろう。VS 2017が原因で面倒なことになっているのは残念だ。

参考

[1]How to get PowerShellGetWMF 5.0 を入れようという話が書いてある
[2](1, 2) azure-powershell.4.0.1.msiリリースのとこに、msiへのリンクがある。
[3]VS Installer overwrite with old version Azure PowerShell.すぐTriaged になったけど、その後の動きがない。
]]>
Fri, 19 May 2017 00:00:00 +0900
http://kyrt.in/2017/05/14/azure_cosmos_intro.html http://kyrt.in/2017/05/14/azure_cosmos_intro.html <![CDATA[Azure Cosmos DB がやってきた]]>

Azure Cosmos DB がやってきた

DocumentDB の記事を書いていたら[15]、//Build 2017で、Azure Cosmos DB が発表[1]された。 Cosmos DBは、Multi-model database and multi-APIのGlobally Distributed データベース・サービスだ。

Azure Cosmos DB in KeyNote

//Build 2017 Keynote より [1]

「DocumentDB何処行った、名前が変わったの?」と思ったら、そんなに簡単な話では無かった、Azure Cosmos DBは、DocumentDB、MongoDB, Azure Table,Gremlin などの複数のAPIをサポートしたマルチモデルの分散データベース[2]で Document DBはそのAPIの一つという位置づけになった。従来の Document DBで使ってた分散データベース基盤の部分は、ある程度汎用的に出来ていてJSON以外のデータモデルも扱えるので、今回はKey-Value と GraphのデータモデルにAPIを付けてブランディングも新たにお披露目したよという話だ。素晴らしい。

ざっくりまとめると、下図のような感じになってる。Cosmos 分散データベース基盤に、データモデルが乗っていて、それぞれAPIを提供するといった感じだ。AzureTable(Key-Value), Gremlin(Graph) は、Preview(既に試すことができる)で、Splark は、アナウンスだけ、DocumentDBとMongoDB APIはGAのままだ。この2つがGAのままなのは、Cosmos DBになっても基本的な実装は同じだからだろう。

Azure Cosmos DB in KeyNote

Azure Cosmos DB スタック

Breakout Session

//Build 2017のCosmos DBの2つのBreakout Session、B8047[3]と、T6058[4]では、もう少し詳細に触れられている。

Table API in Azure Cosmos DB

//Build 2017 B8047 より [3]

その中でも興味深いのは、B8047 のTable APIの下りだ(動画だと35:50あたり[5])。超訳すると。

Cosmos DB に、既存のTable Storage(以下Standard Storage)互換のTable API(以下Premium Table)を実装した。もう、Premium Table SDK[6]をダウンロードして試せる。アプリケーションの変更は必要ない(SDKでを切り替えるだけ)で、ローレイテンシー、5つの一貫性、グローバルディストリビューション、セカンダリーインデックス等、数々のCosmos DBの特性が得られる。

特に重要なのは、これに続く部分だ。

現在「storage optimized offer」に向けて作業中、現在の Cosmos DB は、スループットの最適化、ローレイテンシーなどのpremium experience にフォーカスしているが、hot work load 以外の場合でも選択肢にできるように取り組んでいる。

現在のCosmos DB(旧DocumentDB)の料金体系は、基本hot work load向けだ。アクセス頻度が低いデータが多くなってくるとAzure Storageとのコスト差がどんどん大きくなっていく。データ全体の中で一部だけのアクセス頻度が高く、殆どのデータはアクセス頻度が低い場合は、Cosmos DBだけでなく、なんからの外部データストアとの連繫を検討する必要がある。これは特に、Standard Table(これからは、Azure Table StorageのことはStandard Tableと呼ぼう)からの移行を考えた時に重要な検討課題だろう。それがうまく解決できるなら、かなり有り難い。

同時に公開されたBlogから

Cosmos DBも発表共に公開されたBlogもなかなか面白い。Azure Cosmos DB: The industry’s first globally-distributed, multi-model database service[7]の A Brief History of Cosmosには簡単なCosmos DBの歴史が書いてあった。それによると、

Azure Cosmos DBは、2010年に「Project Florence」で開始、グローバルディストリビューションの課題を解決するためにMSの社内で使っていたが、他の皆にも役に立つと思い、2015年にAzure DocumentDBの形でこのテクノロジの第1世代を利用できるようにした。

云々だそうだ。世界中で展開するサービスの下りをみて連想したのは、AzurePortal。「最近ポータルが速くなってきてるのはエンドポイントを日本に持ってきたからかな、でもUSのリソースも普通に見れて操作できるしどうなっているのだろう」と思っていたが。Cosmos DBのようなグローバルディストリビューションの仕組みがあれば実装イメージが湧いてくる。実際のところはどうなっているかはわからないが。なかなか興味深い。

あとA technical overview of Azure Cosmos DB[8]に、Leslie Lamport 博士が出てきて、Cosmos DBの一貫性の設計、実装にTLA+[9]のCosmosでの活用の話をしていたのが興味深かった。

]]> Tue, 04 Oct 2016 00:00:00 +0900 http://kyrt.in/2016/07/01/2016_mvp.html http://kyrt.in/2016/07/01/2016_mvp.html <![CDATA[2016 Microsoft MVP Award を受賞しました]]> 2016 Microsoft MVP Award を受賞しました

2016年7月期、Microsoft Azure のカテゴリで、Microsoft MVP Award を受賞しました。

今後も、最近 Service Fabric に偏り気味ですが、Cloud Scale なサービス構築をテーマに情報発信をして行きたいと思います。

これからも、よろしくお願いいたします。

]]>
Fri, 01 Jul 2016 00:00:00 +0900
http://kyrt.in/2016/05/20/reliable_collection_lock.html http://kyrt.in/2016/05/20/reliable_collection_lock.html <![CDATA[Reliable Collection の Lock の挙動]]> Reliable Collection の Lock の挙動

先日、5/18 に、第1回Jazug Tokyo Nightで、Azure Fabric Service の Reliable Collection 話をしました。Global Azure Boot Camp 2016 in Japanでも話をしたのですが、1時間ぐらいだと概要も話し切れない感じでなかなか厳しい。4回ぐらいに分けても少し深いところ(そんなにDeepじゃないですが)をしたいなぁと思っていたところ、ちょうど良い機会が出来たので、早速時間を貰うことにしました。

今回は、その時の補足です。まずは、その時の資料です。Lock compatibility matrixの部分を更新しました

Azure Fabric Service Reliable Collection

勉強会の時に、下記の Lock compatibility matrix の赤枠の部分だけ、SQL Serverと違うという話をしました。「もしかしたら、ドキュメントが間違っているのかも」というようなことをその場では言いましたが、どきょメントが間違っているわけではなく、そこはSQL Serverと動きが違うとのことでした。そのあたりの話を説明します。(公式ドキュメントでは無いので、参考程度で)

Lock compatibility matrix

Lock compatibility matrix

Reliable Collectionは、書込に最適化されていおり、matxi の赤枠の部分が、G(U)/R(S)時の動きが SQL Server とは異なっています。また、Reliable Collection では、update lockの期間は短くほとんどの場合、exclusive に昇格して終わることを前提としています。設計思想が違うという話のようです。

下図のような場合で動作を説明します。

tnx and lock
  • 上が、Reliable Collection,下がSQL Server
  • 横軸が時間で、それぞれ、2つのtransactionがある
  • 黄色線が、tnxid 2 が、shard lock を要求したタイミング

TxnId1が、update lockを掛けている時、TxnId2がShard Lockを要求した場合(垂直黄線)、Reliable CollectionではUpdate Lock が、Exclusive Lock になって更新が終わりlockが開放されるまでshard lockは待機します。それに対して、SQL Serverでは、即時にshard lockは成功します。全てのshard lockが開放されてから、exclusive lock ->更新という処理に流れになります。この違いはなかなか面白いですね。

最後に

Reliable Collection は結構手堅くしっかり作っている感じを受けます。この手のものがあると、Data Localityを健全に保てるのでレイテンシー上は非常に有利になります、いいですね。

「上記2つに更に .NET の ReaderWriterLockSlim と、Jeffrey Richter の OneManyLock を入れて、永続化の有無がどう影響するのか考察したら面白いのかな?」と一瞬思ったのですが、今回はこの辺で。勉強会の前に明確にしておけば良かったのですが、ちょっと見逃してました。でも、SQL Serverと違って、lock やwait の状態が見えないのがちょっと不便ですね。これだと、ラッシュテストしないとデッドロックとかわからないです。

]]>
Fri, 20 May 2016 00:00:00 +0900
http://kyrt.in/2016/04/04/build_2016_azure_cool_storage.html http://kyrt.in/2016/04/04/build_2016_azure_cool_storage.html <![CDATA[BUILD/2016 Azure Cool Storage]]> BUILD/2016 Azure Cool Storage

BUILD 2016B816 Learn How to Store and Serve PBs of Object Data with Azure Block BlobsCool Storageという、とても興味深い新機能が発表されていました。Build 2016: Azure Storage announcementsに入ってなくて、見落としそうになります…

Cool Storage

Cool Storageは、アクセス頻度の低いデータを安価に保存するための新機能で、Block Blob でサポートされます。

これで、Block Blob は、2の tier に別れます。

  1. Hot - 通常利用のデータ向け (従来のBlob)
  2. Cool - あまりアクセスしないデータ向け (あたらしいやつ)

概要

  • APIは、100% 互換で、同様のスループットとレイテンシーを実現(Coolでも性能が同じなのは設計しやすくなるので有り難いことです)
  • LRS, GRS, RA-GRSの信頼性オプションを提供( 同じですね)
  • 可用性は、Coolが 99% で、Hot が 99.9%(ちょっと、Coolが低くなります)

お値段関係

Hotは、頻繁に使った場合に費用が抑えられ、Coolは、容量が大きい場合に安くなるという価格体系で構成されるようです。話の中ではちょっとはっきりしませんでしたが、トランザクションコストやデータ転送コストで差を付けるという感じなのかもしれません。

HotからCoolへの切替もできるようなので、履歴系のデータを保存するにはとても便利な気がします。next few weeks で、public preview に入るそうです。楽しみですね。

この話は、動画では、33分、パワポの資料では、30p 当たりで話が出来てますので、興味のある方はぜひ、chanel 9 をご覧ください。[1]

最後に

どうも、AWS の S3 Standard-IA とか、Google Nearline あたりと同じような領域をカバーするサービスのようです。S3 Standard-IA は、Google Nearline とは違って、レスポンスタイムが通常のBlob並ってところは良い感じです。価格の詳しい条件がわからないので、現時点でS3との比較は難しいですが、Cool Storageは、Hot/Coolの切替がアカウント単離っぽいので、実データのコピーは必要無さそうで、そのあたりで差がでてくるのでは無いかと言う気がします。

今回の、BUILDでは、 Server Siide Encription[2]も発表されて、Storage Team に活気を感じました。これからが、楽しみです。

]]>
Mon, 04 Apr 2016 00:00:00 +0900
http://kyrt.in/2016/02/05/how_to_get_current_running_guest_os_version.html http://kyrt.in/2016/02/05/how_to_get_current_running_guest_os_version.html <![CDATA[現在 のGuest OS Version の確認方法]]> 現在 のGuest OS Version の確認方法

.NET Framework 4.5, 4.5.1 のサポート終了に少し遅れましたが、Azure の Cloud Service でも、Gest OS 4.28 から .NET 4.5.2 がサポートされるようになりました。Azure Guest OS releases and SDK compatibility matrix

現在絶賛展開中ですが、手元のCloud Serviceには、すでに来ていました。

Visual Studio での確認

現在の Guest OSのバージョンは、下記のように、Visual Studio で、Roleのプロパティを開くと確認することができます。

vsimage

Azure PowerShell での確認

Azure PowerShell は、1.1.0 では確認できなかったのですが、今回のように、GUEST OSのバージョンが重要な場合に確認出来ないのは不便なので、OSVersionが帰ってくるようなパッチを作って PR を出してみたら、さくっと翌日にはマージされ次のリリースで無事に現在のバージョンがわかるようになりました。(もともと、REST APIには存在するし簡単な修正だったので、なにかの拍子に漏れてしまっただけだと思います)

Fix to missing OS Version in Get-AzureRole #1638

../../../_images/git001.png

Azure PowerShell Team オープンですね!素晴らしい

こんな感じで確認することができます。

$ Get-AzureRole -ServiceName kinmugics10 -Slot Staging

RoleName             : WorkerRole1
InstanceCount        : 1
DeploymentID         : c49d5437f1a84345b9739a79ff333056
OSVersion            : WA-GUEST-OS-4.28_201601-01
ServiceName          : kinmugics10
OperationDescription : Get-AzureRole
OperationId          : 0ffbd548-3b37-6c15-afa4-e091056e0849
OperationStatus      : Succeeded

現状だと、「新ポータル、旧ポータル」では、適応されているcscfgの内容だけで、自動更新に設定されている場合にどのGuest OS が使われているかは確認できません。

おまけ、Get-AzureDeploymant

似たような情報を返してくるコマンドに、Get-AzureDeployment というのがあり、OSVersionが帰ってきます。「*」 が帰ってきて、すぐに実行中のGest OSがかえってくるわけではないことがわかりますが、ソースを確認してみます。コード的には、このあたりでOSVersion のプロパティを設定しています。つまり、csdefで設定されているosVersionが入り、自動更新を設定した場合、「*」となって現在のOS バージョンは分かりません。

最初これで行けるかと思ったんですがね。

]]>
Fri, 05 Feb 2016 00:00:00 +0900
http://kyrt.in/2015/12/02/using_dnx_was.html http://kyrt.in/2015/12/02/using_dnx_was.html <![CDATA[Azure Storage Client Library をDNXで使ってみた]]> Azure Storage Client Library をDNXで使ってみた

Microsoft Azure Advent Calendar 2015 2日目

.NET 用のAzure Storage Client Libraryが、 6.1.1-preview (2015/10/30リリース) から DNXCore 5.0 にも対応しているので、どれぐらい動くのか簡単なコンソールアプリケーションを作ってみた。 ソースgithub.com/takekazuomi/scldnx-sample

butterfly

環境

コードは、 Visual Studio 2015 Update 1 で書いて、dnx 関連のコマンドでコンパイル実行という流れ。最近なんやら、dotnetというコマンドも有るらしいが今回は使っていない。新規プロジェクトで、C# -> Web の下の、コンソールアプリケーション(パッケージ)というのを選択。(どうしてWebの下なのかさっぱり分からない)

../../../_images/scl001.png

これで、見慣れぬ構造でプロジェクトが出来る。ソリューションのフォルダーには、sln のファイルと一緒に global.json が出来、プロジェクト本体は、src/[プロジェクト名] の下に作成される。プロジェクトファイルは、project.json とJSONのファイルになっている。(コメント書けないのでJSONは止めて欲しい)

dnxは、最新の実行環境を使うことにした。今のところ、stable とか release に拘ってもあまり意味ないほどころころとよく変わっている。dnvm コマンドのインストール方法はプラットフォーム毎に違うが、その後はかなり同じような感じで使える。

$ dnvm upgrade -a x86 -r coreclr -u

dnvm upgrade で入れると、alias default が定義され PATHも切られる。dnvm installと、dnvm useを使って方が良いかもしれない。入っているかを、dnvm list で確認する。最後の行が active になっている。

$ dnvm list

Active Version           Runtime Architecture OperatingSystem Alias
------ -------           ------- ------------ --------------- -----
       1.0.0-beta5       clr     x64          win
       1.0.0-beta5       clr     x86          win
       1.0.0-beta5       coreclr x64          win
       1.0.0-beta5       coreclr x86          win
       1.0.0-beta7       clr     x86          win
       1.0.0-beta7       coreclr x86          win
       1.0.0-beta8       clr     x64          win             clr
       1.0.0-beta8       clr     x86          win
       1.0.0-beta8       coreclr x64          win             coreclr
       1.0.0-beta8       coreclr x86          win
       1.0.0-rc1-15838   clr     x86          win
       1.0.0-rc1-15838   coreclr x86          win
       1.0.0-rc1-15904   clr     x86          win
       1.0.0-rc1-15904   coreclr x86          win
       1.0.0-rc1-update1 clr     x64          win
       1.0.0-rc1-update1 clr     x86          win
       1.0.0-rc1-update1 coreclr x64          win
       1.0.0-rc1-update1 coreclr x86          win
       1.0.0-rc2-16249   clr     x86          win
  *    1.0.0-rc2-16249   coreclr x86          win             default

このあたりは、ASP.NET 5 の アドベントカレンダー のネタなので軽く飛ばします。[1]

コードの概要

基本的に、Azure Storage Client コード部分はほとんど従来の物と変わらない。

テーブルのリストを取る部分は、こんな感じ。

TableContinuationToken token = null;
do
{
    var result = await _tableClient.ListTablesSegmentedAsync(null, null, token, null, null, cancellationToken);

    result.Results.ToList().ForEach(table => Console.WriteLine($"{table.Name}"));

    token = result.ContinuationToken;
} while (token != null);

ちょっと困ったのは、Table のQueryが Fluent Mode しかサポートしていなくて IQueryable Mode が使えないこと。[2]試しに、WADのPerformanceカウンターを保存しているテーブルを操作するコードを書たら下記のようになった。フィルターの部分が少々煩雑だ。

public async Task RunAsync(CancellationToken cancellationToken)
{
         var sw = Stopwatch.StartNew();
         var min = _fromDate.ToUniversalTime().Ticks;
         var max = _toDate.ToUniversalTime().Ticks;

         var table = _tableClient.GetTableReference("WADPerformanceCountersTable");

         var query = new TableQuery<WADPerformanceCountersTable>()
             .Where(TableQuery.CombineFilters(
                 TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.GreaterThanOrEqual, min.ToString("d19")),
                 TableOperators.And,
                 TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.LessThan, max.ToString("d19"))));

         var result = new List<WADPerformanceCountersTable>();
         TableContinuationToken token = null;
         do
         {
             var segment = await table.ExecuteQuerySegmentedAsync(query, token, null, null, cancellationToken);
             token = segment.ContinuationToken;
             result.AddRange(segment.Results);
             // Console.Write($"{segment.Results.Count}.");

         } while (token != null);
         Console.WriteLine();

         sw.Stop();
         Console.WriteLine("Count:{0}, Min: {1}, Max: {2}, Elapsed: {3:F2} sec",
             result.Count, result.Min(e => e.PartitionKey), result.Max(e => e.PartitionKey), (sw.ElapsedMilliseconds / 1000.0));
}

余談だが、WAD(Windows Azure Diagonestics )のテーブルは、tickを”d19”で書式化したもので、パーテーションを指定すると時系列で絞り込むことができる。この方法は、パーテーションのレンジクリーになるので、時間的なパフォーマンスがあまり良くないが、パッチなどで使うならば結果のデータ量を絞り込めるという利点がある。時間的なコストが重要なシナリオでは、並列化を検討すると良い。

動かしてみる

Storageの接続文字列は、環境変数 AZURE_STORAGE_CONNECTION_STRING から拾うようにした。VSから実行するときは、プロジェクトのプロパティでデバックを選択し環境変数を設定する。デバック時に環境変数を設定出来るのは非常に便利、Visual Studio では今ままで無かったのが不思議なぐらい。ランタイムも選択できるが、dnvm install などで予め入れておく必要がある。

../../../_images/vsscreen05.png

コマンドラインの場合は、下記のようにビルドして実行する。シェルの違いなどで若干違うが、ほとんど同じような感じで実行できる。便利である。

  • Windows 10 TH2
$ $env:AZURE_STORAGE_CONNECTION_STRING="Azure Storage Key"
$ dnu restore
$ dnu build
$ cd bin\output\approot
$ .\sclxplatclr.cmd ListTable
  • Mac OSX 10.11.1
$ export AZURE_STORAGE_CONNECTION_STRING="Azure Storage Key"
$ dnu restore
$ dnu build
$ cd bin/output/approot
$ ./sclxplatclr.cmd ListTable

実行しているのはテーブルのリストを取ってるだけの簡単なコードなので、結果は省略

最後に

結局、ubuntu 15.04 でやったのは下記のようになって動かなかった。[3]

$ dnu restore
failed to locate libcoreclr with error libunwind-x86_64.so.8: cannot open shared object file: No such file or directory

言語は同じC#でも、ライブラリ回りで細かい違いがあってなかなかコーディングの手間がかかる。今回だと、MEFを使おうかと思ったら dnxcore50 に対応してなくて、Microsoft.Framework.DependencyInjectionを使ってみたり、Environment.CurrentDirectory()が、dnxcore50 に無かったり、カレントのAppDomain取ろうとしたら出来なかったり[4]などなど。

でもまあ、単独のバイナリに変換されるようになると、クロスプラットフォームなコンソールツールを作成する手段として重宝するんじゃないかなとは思った。

[1]DNX環境のセットアップ、Installing ASP.NET 5 On Mac OS XInstalling ASP.NET 5 On Linux
[2]Fluent Mode と IQueryable Mode については、Announcing Storage Client Library 2.1 RTM & CTP for Windows Phoneの Conceptual model の部分が詳しい。
[3]Installing .NET Core on Linuxでは、Ubuntu 14.04 TLS を使っている。RTMではなるべく14.04以降など広いバージョンのUbuntuをサポートして欲しいところ。
[4]Assembly.GetExecutingAssembly, AppDomain.CurrentDomain and similar
]]>
Wed, 02 Dec 2015 00:00:00 +0900
http://kyrt.in/2015/10/16/niigata_net_2015_10_dml.html http://kyrt.in/2015/10/16/niigata_net_2015_10_dml.html <![CDATA[Niigata.NET Page Blob Download 最適化]]> Niigata.NET Page Blob Download 最適化

10/10(土) に、Niigata.NET 2015-10に参加してきました。日本酒とへぎ蕎麦が美味しかったのでまた行きたいところ。LTで話した(かった)ことをここにまとめておきます。

soba

最初に

Azure Storage 上でデータの移動が必要な場合、現時点でもっともお勧めなのは、AzCopy[1]です、多めのデータをアップロード、ダウンロードしたい場合には AzCopyを試してください。[2]ほかのツールを使うよりリソースをうまく使って効率的にデータを転送してくれます。転送に必要な時間は、ほぼネットワークインターの速度に依存します。(諸条件に依存しますが、ざっくり言うと)

Azure Storage Data Movement Library (Preview)

AzCopyのライブラリー版 が、Azure Storage Data Movement Library (Preview)です。(以下 DML)[3]ライブラリなので、スクリプト+AzCopyではちょっと難しいような細かい制御しながらデータの転送をすることが出来ます。DMLの実装では API の呼び出し方が Azure Storage に合わせてチューニングされていて、パフォーマンスが出るというのが大きな特徴です。DMLについては、Intro Azure Storage Data Movement Libraryも参考にしてください。

チューニングの基本戦略は2つです。これは、Azure Storage では共通の戦略となります。

  1. 並列化[5]
  2. 転送量の最小化

今回は、Page Blob での転送量の最小化の話をします。(DMLでは、その他にもGCとかLOH周りの工夫も使われています)

Page Blob Download 最適化

昨今の近代的なファイシステムでは、sparse file という仕組みを持っています。[6]大きなファイルであっても、zero の部分はDISK上にアロケートされずにデータが書きこまれた部分(緑の部分)だけが確保されます。

../../../_images/sparse.png

Azure では、仮想マシンのDisk は VHD ファイルを Azure Storage Page Blob に配置されており、そのPage Blobは sparse data の保存のための仕組みをがあります。仕組は結構簡単で、Page Blob では、データが書き込まれた部分をページ単位(512B)でレンジ管理しており、実際にBlobにデータが保存されるのはこの領域だけです。この領域の外を読むと0 fill のデータが帰ります。(これが、俗に言われるPage Blogが、Disk アクセスに最適化されているという機能の一つです)REST APIの GetPageRanges[7]では、有効なデータを含むすべての連続したページ範囲のリストを返します。[8]

つまり、GetPageRanges APIを使うと、Page Blob から実際にデータが入っている部分だけを取得することができます。例えば、100GBの仮想DISKであっても転送するのは実際使っている10GBだけにすることが可能になります。

DMLでの実装

DMでは、PageBlobReader.DoGetRangesAsync() でGetPageRangesAsyncを実行してRangeを取得[9]しています。

protected override async Task<List<Range>> DoGetRangesAsync(RangesSpan rangesSpan)
{
    AccessCondition accessCondition = Utils.GenerateIfMatchConditionWithCustomerCondition(
        this.Location.Blob.Properties.ETag,
        this.Location.AccessCondition);

    List<Range> rangeList = new List<Range>();

    foreach (var pageRange in await this.pageBlob.GetPageRangesAsync(
            rangesSpan.StartOffset,
            rangesSpan.EndOffset - rangesSpan.StartOffset + 1,
            accessCondition,
            Utils.GenerateBlobRequestOptions(this.Location.BlobRequestOptions),
            Utils.GenerateOperationContext(this.Controller.TransferContext),
            this.CancellationToken))
    {
        rangeList.Add(new Range()
        {
            StartOffset = pageRange.StartOffset,
            EndOffset = pageRange.EndOffset,
            HasData = true
        });
    }

    return rangeList;
}

ここはわかり易いですね。range list は、RangeBasedReader.DoGetRangesAsync() でダウンロードする範囲を決めるのに使われます。

ちょっと脱線しますが、Win32 APIでファイルシステムを操作する場合には、DeviceIoControl で FSCTL_QUERY_ALLOCATED_RANGES[10]を使うと実際にデータが保持されている range を取得することができます。

資料

LTで用意した資料です。

Azure Storage Data Movement Library Page Blob Optimize

最後に

最後に、「Niigata.NET 2015-10」を聞きながら、思ったことを少し書きます。

プロジェクトで、最初にビルドとかnuget周りを揃えて置くのはとても良いと思います。msbuild 、nuget の闇に飲み込まれない範囲で華麗に捌いてくれる人が1人入ればイイデスネ。

make -> ant -> nant -> msbuild -> maven ... と使ってきたけど、小技を使い過ぎると分からなくなるのが問題で。ビルドファイルを、いろいろ弄っているうちに秘伝のタレ化して触れなくなりがちです。

package manager は、dnxで改善されるはずなので、それに期待したいところ。install.ps1 とか必要になるとソウルジェムが濁ります。

「MEF の、Core CLR 対応ってどうなっているの?」という話が出たので、帰ってきてちょっと見たら、ASP.NET 5 と Entity Framework 7 では、Microsoft.Framework.DependencyInjectionってやつが使われていました。聞いたことがあったような気もするけど、使ったこと無かったので少し試してみたら、どうも、container builder、setter injection などが無いようで、えらくシンプルな実装。でもまあ、これはこれで良いのかなという感じでした。

「コードが”短く”書かれている」のが良いって話は、総論としては文句無いですが、その台詞を聞いた瞬間「linq がすべてを台無しにしてしまったなぁ」と思いました。基本的に短い方が何をしたいのかが理解し易いけど、それがメモリとCPUにどういう影響を与えるかは別問題で、アルゴリズム、遅延評価、linq 関数の実装を理解しないとコード見ただけでは短い方が良いかどうか分からないのが現状でしょう。結局いろいろ考えても分からないので、データを用意して流してプロファイラに掛けるしか無いという現実があります。

これは Linq だけの罪かと言うと、そういうわけでは無く、「生産性の高いライブラリを使う場合に起こる一般的な課題」です。抽象化度の高いものは人間に理解し易く生産性の向上に貢献しますが、実際のノイマン型コンピューターの仕組みと乖離が大きくなり、実行ステップとコードの乖離も大きくなります。簡単に言うと、「こう書いたからこう動く」という世界から離れていくことになる。 じゃあ、乖離してはダメなのかという、そうでもない。全てはバランスによって決まる。ズルズルと、長くなったので、これぐらいで。

プログラムを読むことに関しては、このOSS全盛時代に読むコードに困ることは無いと思うので、ぜひガンガン読んで欲しい。

などなど、いろいろ思うことがあって面白かった。

]]>
Fri, 16 Oct 2015 00:00:00 +0900
http://kyrt.in/2015/09/25/intro_azure_storage_data_movement_library.html http://kyrt.in/2015/09/25/intro_azure_storage_data_movement_library.html <![CDATA[Intro Azure Storage Data Movement Library]]> Intro Azure Storage Data Movement Library

先日(2015/9/23)、Microsoft Azure Storage Data Movement Library (Preview 以下 DML) というAzure Storage用のファイル転送ライブラリが公開されました。[1]

DML は、従来 Storage Client Library だけでは難しかったような、高速なファイル転送を簡単にアプリで実装できるようなライブライです。(ベースは AzCopyと同じらしい) Azure File、Blob、Local の 3つの間のデータ移動ができます。

brick

下記のような、関数が揃っていて、sourceとdestをセットして呼ぶとその間でデータが転送されるって感じです。

public static Task CopyAsync(
     Uri sourceUri,
     CloudBlob destBlob,
     bool isServiceCopy,
     CopyOptions options,
     TransferContext context,
     CancellationToken cancellationToken
)

どんな組み合わせがサポートされているかとを表にまとめるとこんな感じ。このうちUrl->Blob, Url->FileのCopyだけ isServerCopy (サーバー側で実行される非同期コピー) のオプションがあります。Local to Local はサポートされていません。現在のバージョンでは、すべての処理はファイル(オブジェクト)1つの単位です。例えば、ディレクトリを指定して、その下をまとめてUploadなどのようなことはできません。表は横が転送元で縦が転送先です。

DML Matrix
元/先 Local Blob File Url
Local なし DownloadAsync DownloadAsync なし
Blob UploadAsync CopyAsync CopyAsync CopyAsync※
File UploadAsync CopyAsync CopyAsync CopyAsync※

isServerCopy(※付きのやつ) は、URLとして指定できるようなものならコピー元(ソース)にすることができます。public に公開されているURLなら、そのまま。private のものでも、S3のように、pre-signed URLが使えるならば、そのURLからAzure Storageにコピーすることが可能です。TransferManager.CopyAsync Method (Uri, CloudBlob, Boolean, CopyOptions, TransferContext, CancellationToken)

これは、DMLの機能というよりAzure Storage Blobの機能で、2012/6 に追加されたAsynchronous Cross-Account Copy Blobの副産物(?)です。[2](気が付いて無かったですが orz)

Introducing Asynchronous Cross-Account Copy Blob から引用
Note: The source blob could even be a blob outside of Windows Azure, as long as it is publicly accessible or accessible via some form of a Signed URL. For source blobs outside of Windows Azure, they will be copied to block blobs.

「Note: 公開された url あるいは、なんらかのSigned URLのようなものなら Azure の外側でもかまいません。それは、block blob として copy されます」という感じでしょうか。DMLのAPIを見ると、Azure Fileにもコピーできますね、これは、2015/8/3 に公開された新機能です、Azure Files Preview Updateversion 2015-02-21 から利用できます。[7]

DML のGitHub レポジトリを見ると、S3から Azure Blobにコピーするサンプルコードが公開されています。S3ToAzureSample

余談ですが、このサンプルを読んでいて気になったことがあったので PR を出してみたらさくっとマージされました。[3](軽微な修正ですが)

WAD Custom Log Downloader

とりあえず、なにか作ってみようと思い Azure Diagonestics の Custom Log をローカルにDownloadするアプリWADCustomLogDownloaderを書きました。AzCopyだけだと、ダウンロード対象のファイルのリストがどこか外部にあって参照しながらダウンロードするというのは効率悪くWADのCustom Logなどは不得意パターンでした。それをライブラリになったので、ちょっとやってみようという感じです。

環境変数AZURE_STORAGE_CONNECTION_STRINGに、StorageのConnection文字列を入れて、下記のように実行します。-f から、-t の間のFileTimeのログを、WADDirectoriesTable から探し対応するファイルをblobから、-d で指定したローカルディレクトリにダウンロードします。ちょっと動かしてみた感じだとAzCopyと同じような速度を期待できそうです。(ちゃんと計っていないのですが)

$ $env:AZURE_STORAGE_CONNECTION_STRING="YOUR_STORAGE_KEY"
$ ./WADCustomLogDownloader -container diagnostics-custom-logs -d c:\temp\logs -f 2015-09-10 -t 2015-09-23 -v

ここから、コードの解説をしながらコーディングしていて気が付いた点をいくつか並べていきます。

基本的な作り

ダウンロード対象のBlobのリストを、Azure Table の WADDirectoriesTable から読んで、BlockingCollectionに突っ込み、別TaskでBlockingCollection を読んで対象のBlobをダウンロードするという流れになっています。Producer が、ListWadCustomLogs()で、Consumer が、DownloadFromBlob()、その間を繋ぐ queue が、BlockingCollection _jobQueueです。

今回は、Producer が軽いので割と序盤で全部 queue に入ってしまって、順次 Consumer が処理するということになります。大量に対象のファイルがある場合、BlockingCollection の本領発揮なのでしょうが、今回は Producer–consumer problem[4]には引っかからない気がするので枠組みだけ使ってさくっと進みます。この基本構造は、DMLのS3のサンプルからいただきました、王道ですね。

Best Practice

パフォーマンスを出すには、DefaultConnectionLimit を増やすのと、 100-continue をOffにする必要があります。Azure Storage を使っている人にはお馴染みかと思いますが。

ServicePointManager.DefaultConnectionLimit = Environment.ProcessorCount * 8;
ServicePointManager.Expect100Continue = false;

ちなみに、ASP.NET環境下では、HttpRuntimeの初期化が走ると、HttpRuntime.InitializeHostingFeatures() から、いろいろ呼ばれて、HttpRuntime.SetAutoConfigLimits()で、下記のコードが実行されるので、DefaultConnectionLimit の変更は不要です。Consoleアプリや、WorkerRoleでは必須なので要注意です。

System.Net.ServicePointManager.DefaultConnectionLimit = Int32.MaxValue;

Producer ListWadCustomLogs

WADDirectoriesTable から、該当日付近のデータを取り出します。WAD(Windows Azure Diagonestics)では、PartitionKey にTickを使っているので日付をUTCにしてTickを19桁の文字列に展開します。ここは、Partition Queryなので、速度が必要なら分割してパラレルで実行した方が良いですが、実測で DC内で 100 ms/1000件 程度、西から東を見た場合でも、140ms/1000件、の時間でした。そのためあまり気にせず単純なクエリーで書きます。件数が多くなるのが想定できる場合は、適時分割した方が良いと思います。

ソース:WADDirectoriesTable から該当期間のログのエンティティを取得

そのあと、ExecuteSegmentedAsync() を使って、1000件毎にエンティティを取得し、jobQueueに入れます。全部入れ終わたら、_jobQueue.CompleteAdding()します。Consumer 側では、_jobQueue.IsCompletedがtrueなら処理終了と判断します。true になるのは、complete とマークされてて queue が空の時です。[5]これだけでもBlockingCollectionちょっと便利ですね。

今回は、BlockingCollectionの maximum capacity を設定していないので_jobQueue.Add(result.Results)はブロックしません。Producer と Consumer のバランスをとる必要がある場合は適時設定するといいでしょう。

Consumer DownloadFromBlob

_jobQueue に乗っている WADDirectoriesTable をTransferManager.DownloadAsyncに渡してダウンロードするタスクを作成します。ここは、Task の作成しすぎとかは、気にせずにグルグル回してしまって良く、実際のTaskのスケジューリングは適時 DML内でやってくれます。基本的には、4MB chunk で、DownloadRangeToStreamAsync を使って分割ダウンロード、chunk は、buffer pool を利用、core 数*8 を並列実行という感じです。

DMLソース:BlokBlob ダウンロードの実装部分

並列度は、TransferConfigurationsで設定できます。デフォルトは、core数*8、その下の行を見ると、chunk size は、4MB(固定?)です。使うメモリーは、Native Method の GlobalMemoryStatusEx()を呼んで物理メモリーサイズを拾っています。(なんと!)

そして、DMLの一番の肝は、 buffer pool と並列実行度をリンクさせたスケジューラーTransferSchedulerTransferSchedulerですね。Blobでパフォーマンスを上げるなら並列度を上げるのが重要で、大きなファイルを扱うためにはGCを避けるのが王道なので、こんな感じになるのかという気がします。

DMLの中に脱線していました、まだあまり読めてないのですが、いろいろ参考になるコードだと思います。

Consumerの細かいところを説明すると、大体4点

  1. ダウンロード済みのファイルがあればSkipする:TransferContext.OverwriteCallbackで、ダウンロード先にファイルがあるかどうかを見ています。WADDirectoriesTable の中のFileTime、FileSize と比較して同じだったらスキップとしています。WADDirectoriesTable のデータを参照するために、_sourceFileDictionary を参照しているのがイマイチな気がします。OverwriteCallback に、オプションのobject とか渡せれば解決なのですが…
  2. MD5 Validation をOffにする:DownloadOptions.DisableContentMD5ValidationWADのCustom log 転送を使って転送したBlobにはMD5Sumが付いていないので仕方ない。ここはOffです。WADDirectoriesTable の FileSize と同じかどうかを確認できるので大丈夫かな?と思いますが、選択できても良い気がします。MD5Sumの計算はコストが高いのでなるべく避けたい気持ちもわかりますが。
  3. TransferException をハンドルする:TransferExceptionここでは、TransferErrorCode の TransferAlreadyExists、NotOverwriteExistingDestination を特別扱いしています。 TransferContext.OverwriteCallback で、false を返すと、NotOverwriteExistingDestination が帰ります。今回はすでにダウンロード済みの場合は、スキップしているのでエラーではありません。 TransferManager.DownloadAsync に、同じ source, dest のセットが追加されると TransferAlreadyExists が発生します。今回は、WADDirectoriesTable に残っている転送履歴からBlobを探しているので、複数回転送されると TransferAlreadyExists になります。これは、WAD の Custom Log転送をどう使うのかという話にも絡みますが、今回はスキップで扱います。[6]
  4. ダウンロード後のファイルのタイムスタンプを合わせる:File.SetLastWriteTimeUtcここは、見て通りです。よく見ると、余計なcaptureがあるし、cancel とかの場合の分岐が出来てませんね orz ....

あとは、 CountdownEvent を使って、全部のTaskの終了を待ちます。ProgressHandler をフックすると、転送量とか転送ファイル数のステータスをもらえるのは便利です。

まとめ

ちょっと長いコードになりましたが、割と簡単に効率的な転送プログラムが実装できました。DMLの内部構造を見てわかる通り、CPUとメモリーがある程度あるマシンでないと本来の性能が発揮できない傾向があります。合計1.6GB の50本ほどのlog を D1とD3の両方でダウンロードしてみたところ、D1で 12 Mbps ぐらい、D3では 800 Mbps ぐらいのスループットになりました。(速度を見るにはもう少し本格的にやってみないといけないので参考程度ですが)

exe 単体で動作するように、 Release Build では、ILMergeで、wadcldl.exe を作っています。

環境変数 AZURE_STORAGE_CONNECTION_STRING にStorage Account Keyをセットするスクリプトsetenv.ps1を入れてあるので参考にしてください。実行前に、Add-AzureAccount をします。

[1]2015/9/23 Introducing Azure Storage Data Movement Library Preview
[2]Introducing Asynchronous Cross-Account Copy Blob
[3]Dispose GetObjectResponse object. Here are many run.
[4]Producer–consumer problem
[5]Reference Source BlockingCollection.IsCompleted
[6]TransferErrorCode Enumeration
[7]MSDN Copy File
]]>
Fri, 25 Sep 2015 00:00:00 +0900
http://kyrt.in/2015/09/01/go_5_jazug_service_fabric_session.html http://kyrt.in/2015/09/01/go_5_jazug_service_fabric_session.html <![CDATA[Go (5) JAZUG で Service Fabric の話をします]]> Go (5) JAZUG で Service Fabric の話をします

今週末、9/5のJAZUG5周年記念イベント「ごーごー じゃずゆーじー」 Go(5) JAZUGで、Azure Service Fabric[1]をネタに酒井さんと話をします。宣伝とメモを兼ねてちょっと予告編です。興味を持たれた方は是非ご来場下さい ↓click↓

gogo jazug

Azure Service Fabric とは

Azure Service Fabric は、BUILD 2015 に先駆けて発表された、「スケーラブルで信頼性が高く管理しやすいクラウド向けアプリケーションの構築向けの分散型システム プラットフォーム」[2]です。

2015/4/20 に、Mark Russinovich が書いたBlog[3]、翻訳[4]から引用します。

  • Service Fabric は、高度な制御プラットフォーム
  • 開発者や ISV は、拡張性とカスタマイズ性の高いクラウド サービスを構築可
  • ミッションクリティカルなクラウド サービスを提供してきたマイクロソフトの経験を基礎として開発
  • 5 年以上にわたって実際に運用
  • 基盤となるテクノロジは、Azure のコア インフラストラクチャに使用(実証済)
  • Skype for Business、Intune、Event Hubs、DocumentDB、Azure SQL Database 、Bing Cortana などの基盤で利用
  • 毎秒 5 億回を超える評価に対応可能な拡張性を持つ

ざっくりまとめると、「マイクロソフトでAzureのインフラとして5年以上使っている技術がベースにとなっていて実績もいろいろあるよ」ってことのようです。これまでのAzureだとCloud Native アプリケーションの Computing は、Cloud ServiceあるいはApp Serviceでということでしたが、新しい選択肢としてService Fabricというのが出てきたってことです。(まだ、preview でローカルの開発環境でしか動きませんが)

※ここでは、便宜上WebRole/WorkerRole を使うCloud ServicesPaaSv1と呼び、それに対して、Service Fabric をSForPaaSv2と呼びます。

BUILD 2015のセッション[5],[6]や、ドキュメントを見ると機能が結構盛りだくさんなのですが、「SFが取り組んで問題は何なのか、何故必要なのか?」というのが分かりずらい、面白そうだけど、ヘ(゚д゚)ノ ナニコレ?って感じもします。

9/5 のセッションでは、Service Fabricが出てから既に、4カ月以上経っているし、「Azure Service Fabric とは」的な内容はさくっと流して、私が感じていたSFに対する疑問に焦点を当てて話をしてみようと思います。

Service Fabricが取り組んでいる課題

課題ですね、英語だとChallengeです。無理筋なのをやるとダメなやつですが、ここでは関係ありません。クラウド時代に突入してモダンなサービスは可用性とスケーラビリティを求められるようになりました。[7]

その結果、Cluster アプリケーションでは複数のマシンに配置されることになります(つまり分散、分割)。複数台で構成されているので障害時に全停止せず、台数を増やせば処理能力が増えてスケールすると言うのが基本的なストーリーです。

この考えに沿ったアーキテクチャの典型が、PaaSv1です。WebRole/WorkerRole を複数の障害ドメインのマシンに配置することで耐障害性を担保し、アプリケーションをステートレスにすることで、台数増加を性能向上に直結させ (Immutable Infrastructureってやつです) 、高可用でスケーラブルなサービスを構築するというのが物語の粗筋です。

これはこれで良いのですが、PaaSv1では、全てのRoleは仮想マシンを専有するような配置を行います。そのため、OSの設定やランタイムのインストールなど細かいことを自由にできるという大きなメリットがありますが「問題もあるよね」というのが今回の話しです。SFが、取り組んでいる課題はいろいろあるのですが、今回はこの配置に関する課題の話をします。

Static Partitioning 問題

PaaSv1では、Role毎に台数を増減できますが、複数のWebRoleを同一の仮想マシンで動かすことはできません。また、WebRoleとWorkerRoleを同一仮想マシンでホストすることもできません。[8]このようにRoleの分割が静的に行われているのを、Static Partitioning (静的分割?)と言います。Static Partitioning は、Role を増やせば、増すほど扱いづらくなりますし、ワークロードによっては事前に予測することが難しい場合もあり、リソースの利用効率という観点からはムダが生じやすい仕組みで、これは大きな課題ですね。

../../../_images/pat01.png

Static Partitioning はシンプルなせいか、Cluster アプリケーションで広く使われてきました。最初のHadoop とか、昔の twitter とかですね。その結果あちこちから問題が聞こえてくるようになりました。その課題の解法の1つとして注目されているのが、Apache Mesos などで導入された手法です。 Mesosだと、このドキュメントが面白いですBuilding and Deploying Application to Apache Mesos、ちょっと引用します。

Mesos 以前は、Static Partition(静的分割)だった。Static Partition(静的分割)IS BAD、とても扱いづらい。

  • マシンの効率的な利用ができない
  • 効果的なスケールが出来ない
  • 障害の取り扱いが難しく、ダウンタイムの発生を招く

リンク先には、「動的リソース割り当てが必要だ。 Operating System === Datacenter で、Mesos => data center のkernelだ」というようなことが書いてあります。Hadoopも、台数増えてくると十分Nodeが働かないという問題がありました。[9]

確かにサービスを構成するアプリケーションを複数のコンポーネントに分けた場合、(例えばWeb Frontと、管理サイト、日時バッチ、月次バッチとか)。それぞれ、ワークロードも違えば、開発サイクルも違う。これを、クラスターに最適配置しようとするといろいろメンドクサイPaaSv1で、現実的な解はRoleを分けることですが、月次バッチはWebFrontのオフピークの夜間にして効率的な運用をなどを考えると面倒なことになってくる・・・

Dynamic Partitioning

Mesosから離れて、Service Fabric でどうしているのかを見てみます。 Service Fabric では、複数のNodeでクラスターを構成して、アプリケーションは同一Node内に複数配置することができます。どのように配置するかは、アプリケーションが要求を出して、配置条件にあたところにService Fabric が配置するという流れです。配置条件は、サービス マニフェストまたはアプリケーション マニフェストを使用するか、コード内で直接定義します。[10]

../../../_images/pat04.png

Node内に複数のアプリケーションを配置することができ、リソースの有効利用を実現します。YARNとかMesosとかも同じようなことができます。このあたりの考え方は最近のトレンドのようです。

Cluster Manager [11]

このように、Datacenetrから仮想マシンをまとめて借りだして Cluserとしてプールし、アプリケーションの要求に応じてリソースを割り当てるのコンポーネントを、Cluster Managerと呼びます。(最近のクラウド界隈は、混沌としているので名称はちょっと曖昧さがあるけど)代表的なCluster Managerを、Service Fabric も含め上げておきます。Hadoop MRv1 では JobTrancker がボトルネックとなって働かない Node 問題が発生しており、YARNが対策として作成されました。いずれの、Cluster Manager (YARN では、Resource Managerと呼ぶ)も、万単位のサーバーを扱うの前提で作成されているので完全に1つのリソースプールに全部入れてしまって、そこからリースするような使い方でも行けそうです。

最後に

こんな感じで、Service Fabricがアプリケーションをクラスターに配置してくれるので、リソースとアプリケーションの分割の関係を柔軟に構成することができます。ワークロードと配置の関係をコード外でコントロールできるのは、なかなか嬉しい感じです。今回は時間の都合上入れられないと思いますが、開発支援、運用支援の機能もService Fabricには盛り込まれていて、かなり拾い範囲をサポートした分散型システム プラットフォームと言えそうです。

ソフトウェアの基本は、「分割して統治せよ」だと思いますが、残念ながら分割にはもろもろのメリットとデメリットがあり、過剰な分割はデスマーチの元という面もありました。Service Fabricは、今風に多くの問題を解決し次世代クラウドアプリケーションの作成に役立ってくれそうで

※Spark は、複数のCluster Managerに対応していて、これはこれで面白いと思います。

[1]Service Fabric の概要
[2]Service Fabric ドキュメント
[3]Announcing Azure Service Fabric: Reducing Complexity in a Hyper-scale World
[4]翻訳:Azure Service Fabric を発表: 高度なスケーラビリティをより簡単に実現
[5]BUILD 2015, Mark Russinovich, The Next Generation of Azure Compute Platform with Mark Russinovich
[6]BUILD 2015, Gopal Kakivaya, Microsoft Azure Service Fabric Architecture
[7]クラウドだから可用性とスケーラビリティを求められるのか、両者が求めらるからクラウドなのかは ニワトリと卵の関係のような気もします。クラウドによって、いくつかの面でコストが抑えられるようになったので、現実的なビジネスでの適応局面が増えたという言い方もできるます。
[8]WebRole で複数Siteを定義すれば複数Endpoint Hostできます、また、WebRoleのRoleEntryPointのRunをoverride するとWorkerRole的なことが出来たりします。これらの方法の課題はアプリケーション間が無用に密接にくっついてしまっていることです。できるけど、イマイチやなという感じです。
[9]YARN の紹介
[10]サービス記述の概要
[11]Cluster manager
[12]クラスターの説明
]]>
Tue, 01 Sep 2015 00:00:00 +0900
http://kyrt.in/2015/08/23/how_to_use_nlog_in_cloud_service.html http://kyrt.in/2015/08/23/how_to_use_nlog_in_cloud_service.html <![CDATA[Cloud Service で NLog を使う]]> Cloud Service で NLog を使う

先日故合ってNLogのコードを見てたら、Cloud Service のカスタムログ転送で面倒なところが簡単に改善できそうだったので、ローカルストレージの定義を、NLogの設定ファイルに書けるようにした layout renderer ${azure-local-resource}NLog.Azureを作ってみました。

face

Azure Diagnostics 1.3 PaaS BUGS」は、無事 1.4で改修されて再び使えるようになりました。これで、普通に使えるようになったのですが、相変わらず面倒がことが2つ。

  1. ローカルストレージからのカスタムログ転送はVSからの設定ができない
  2. ローカルストレージのパスはデプロイ毎に変わる

今回の趣旨は、2のデプロイ毎に変わるパスをNLogの設定ファイルに書くにはどうするかという話です。

ローカル ストレージ リソース については、MSDNの 「ローカル ストレージ リソースを構成する」 を見てください。

1. ローカルストレージからのカスタムログ転送設定

ここは前段です。残念ながら現在のVisual Studioでは、GUIでローカルストレージからのカスタムログ転送設定ができません。ここはdiagnostics.wadcfgxに手で書きます。たいして面倒でもなくDataSources のタグの中が今回追加した記述です。これだけで、ローカルストレージ LogStorage からBlobコンテナdiagnostics-custom-logs への転送を定義になります。

 <?xml version="1.0" encoding="utf-8"?>
 <DiagnosticsConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
   <PublicConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
     <WadCfg>
       <DiagnosticMonitorConfiguration overallQuotaInMB="4096">
         <DiagnosticInfrastructureLogs scheduledTransferLogLevelFilter="Error" />
         <Directories scheduledTransferPeriod="PT1M">
           <IISLogs containerName="wad-iis-logfiles" />
           <FailedRequestLogs containerName="wad-failedrequestlogs" />
           <DataSources>
             <DirectoryConfiguration containerName="diagnostics-custom-logs">
               <LocalResource relativePath="." name="LogStorage" />
             </DirectoryConfiguration>
           </DataSources>
         </Directories>

     ... 省略 ...

 </DiagnosticsConfiguration>

2. ローカルストレージのパスはデプロイ毎に変わる

これで、ローカルストレージ LogStorage へログを書けばカスタムログ転送でBlobへ送られます。しかし、Cloud Service だと起動するまでローカルストレージがわかりません。また、デプロイ毎に異なったディレクトリを割り当てられます。WorkerRoleでは、csdef でローカルストレージのパスを環境変数に入れておいて、NLog.config の中で、Environment layout rendererを使ってパスを展開するという技も使えます。しかしWebRoleがFull IISになって以来、csdefが解釈実行されるプロセスとアプリケーションが動くプロセスが別になったので、ローカルストレージのパスを環境変数に入れて、configに渡すという技が使えなくなりました。(WebRoleがFull IIS になったのは、はるか昔のAzure SDK 1.3のことですが)基本的には、Microsoft.WindowsAzure.ServiceRuntime の RoleEnviromentからローカルストレージのパスを取得するのですが、そのためにはNLogの設定をコードでコニョゴニョしないといけません。そこで、NLog の設定ファイルにローカルストレージのパスを展開できるような、layout renderer “${azure-local-resource}NLog.Azureを書きました。(名前がおおざっぱ過ぎたかもしれません)

バイナリがNuGetに上げてあるので、インストールはここからします。微妙な気もするので、まだpreviewにしてあります。

$ Install-Package NLog.Azure -Pre

これでインストールしたら、NLog.configに下記のように書くと実行時にローカルストレージのパスに展開されます。

<variable name="logDirectory" value="${azure-local-resource:name=LogStorage:default=c\:/Logs/}"/>

NLog.configのサンプル

name という引数に ローカルストレージ名を書きます。default は、ServiceRuntime が存在しないとき、あるいはローカルストレージの定義が無い場合に使われるバスを書きます。Azureの本番環境あるいはエミュレータ環境では、ローカルストレージにログを書き、そうでない場合はdefaultで指定された場所にログを書くという風に使うことを想定しています。NLogは結構素直な出来で、ここまではすっきり、さくっとできました。log4net よりいい感じですね。

参考までに、簡単なWebRoleのプロジェクトをGitHubに上げてあります。NLog.Azure.Example

おまけ

実は、Microsoft.WindowsAzure.ServiceRuntime.dll には、再配布可能では無いという大きな問題があります。これはどういうことかというと、プロジェクトでServiceRuntime.dllを参照している場合、必ず Azure SDK を入れないと開発/実行できないということです。Storageなどのライブラリは、Azure SDK無しでもNuGet から落としてきたのだけで動くのに、ASP.NETの普通のプロジェクトとして作っていてもServiceRuntime に依存した瞬間にAzure SDKが必要です。NuGetからも入らないのでなかなかメンドクサイことになります。今回の ${azure-local-resource} layout renderer も、ServiceRuntimeを参照してしまうと、Azure SDK の入ってない環境ではアセンブリのロードでエラーになってしまいます。せっかく、defaultパラメータとか作ったのに意味が無い、単純なASP.NETのアプリはそのままでに動くようにしておきたいのに。

こんなとき、.NET では、アセンブリを直接参照しないで自前でロードしてリフレクションで関数を呼び出すという方法があります。今回はServiceRuntimeのアセンブリはGACに登録されているけど物理パスがわからない!、さて困った。そんなときは Fusion API でGACから探し出すのが上等手段です。[1]常套手段と言っても普通に暮らしてるとあまり出番ないですが。Fusion (Unmanaged API Reference)[2]は、GACに登録されているアセンブリを操作する Unmanaged API です、昔はドキュメント無かった気がしますが今は普通に書いてありますね。今回は、GACからアセンブリを探してパスを所得するのだけに使います。CreateAssemblyCache() を呼んで AssemblyInfo を取得し、AssemblyInfo を読むだけです簡単ですね。 コード的には、GetAssemblyPath()のあたり。あとは、リフレクションを駆使して、RoleEnvironment.IsAvailable とか、LocalResource.RootPath を呼んでいますが、ここはコード生成するべきかもしれません。 やっていることは簡単なのにメンドクサイ

最後に

ずいぶん間が空いてしまいましたが、2015/5/12 に、株式会社 kyrt にしました、先月には、2015年7月期、Microsoft Azure のカテゴリで、Microsoft MVP Award を受賞をいただきました。皆さまのおかげです、これらかもよろしくお願いいたします。

File System をバッファーとして使うロギングシステムではパフォーマンスに注意した方が良さそうです。Azure Diagonesticsを使うと、out-process の log collector でよしなにBlobへの転送をしてくれるので、直にBlobやTableに書くより良いとは思いますが、アプリとout-process の間のつなぎがFile System なのは信頼性は良いけどパフォーマンスが疑問です。そんなにログ書くのかという話はあると思いますが。そう考えるとアプリとlog collectorの間はETWが良いような気もします。ついでに、log collector でアーカイブだけじゃなくてリアルタイム系のフィルタリングとかやってくれると便利なんだけど。SLABに期待ですね。

[1]How to programmatically determine if .NET assembly is installed in GAC?
[2]Fusion (Unmanaged API Reference)
]]>
Sun, 23 Aug 2015 00:00:00 +0900
http://kyrt.in/2015/05/05/update_tinker_1_5_sphinx_1_3.html http://kyrt.in/2015/05/05/update_tinker_1_5_sphinx_1_3.html <![CDATA[tinker 1.5 + sphinx 1.3.1 に updateしました]]> tinker 1.5 + sphinx 1.3.1 に updateしました

ゴールデンウェークでちょっと時間が出来たのでblobのbackendを更新しました

bubble by Takekazu Omi

テンプレートを変更してないので、見た目は変わりませんね。

]]>
Tue, 05 May 2015 00:00:00 +0900
http://kyrt.in/2015/04/08/azure_diagonestics_1_3_bugs.html http://kyrt.in/2015/04/08/azure_diagonestics_1_3_bugs.html <![CDATA[Azure Diagnostics 1.3 PaaS BUGS]]> Azure Diagnostics 1.3 PaaS BUGS

更新: 2015/4/17 にリリースされた。Diagnostics Extention 1.4 で下記の問題は修正されました

Azure SDK 2.5 から、Windows Azure Diagnostics (WAD) がVM Extentionになりました。それに伴って、Cloud Service (PaaS)でIISのログ転送と、ローカルストレージのカスタムログ転送が行われないという2つの問題があります。(IaaSでは問題はありません)

Tohaku 2015

※この情報は、2015/4/8 Azure SDK 2.5.1, VM Extention 1.3.1.6 を元にして書いています。

IISのログ転送

デプロイ直後はIISのログ転送が行われず、再起動後に転送が開始されるという問題があります。

原因

WADは起動したときにIIS Management Service経由でIIISログの場所探します。デフォルトでは、この場所は、%SystemDrive%inetpublogsLogFiles に設定されています。PaaSでは WebRoleのIISConfigurator がサービス定義に基いてIISの設定を C:Resourcesdirectory{deploymentid.rolename}.DiagnosticStoreLogFilesWeb へ変更します。その結果、WADの構成が、IISConfigurator の前に実行されると、WADは間違ったディレクトリを監視することになってしまいます。

対策

WADの構成がIISConfiguratorの実行の後に起こればいいので、一度Roleを再起動します。それには、Startup Taskあるいは、Role の OnStartで下記のように処理します。最初の起動の時に、IISConfigurator が行った変更に基いて、2回めの起動でWADが構成を設定します。

{
    // Write a RebootFlag.txt file to the %RoleRoot%\Approot\bin folder to track if this VM has rebooted to fix WAD 1.3 IIS logs issue
    string path = "RebootFlag.txt";
    // If RebootFlag.txt already exists then skip rebooting the VM
    if (!System.IO.File.Exists(path))
    {
        System.IO.File.WriteAllText(path, "Writing RebootFlag at " + DateTime.Now.ToString("O"));
        System.Diagnostics.Trace.WriteLine("Rebooting");
        System.Diagnostics.Process.Start("shutdown", "/r /t 0");
    }

    return base.OnStart();
}

この方法だと、ファイルをアプリケーションのディレクトリに作成しているので、インプレースアップグレードの度に再起動がかかります。そのかわり、アクセス権の問題が無いため、elevated する必要がありません。システムドライブや、Cドライブにファイルを作成する、あるいはレジストリにフラグを作成する方法を使うと、再起動は最小限に抑えられますが、.csdef に <Runtime executionContext=”elevated” /> の設定が必要です。

カスタムログ転送

現在のバージョンでは、従来行っていたローカルストレージからのカスタムログ転送は動作しませんので変わりに絶対ディレクトリからの転送を使います。(ちなみにローカルストレージからのカスタムログ転送はVSからの設定も出来ません)

注意点は2点、

  1. ローカルストレージと違ってquotaが利きません。書き出す量を間違えると、ディスクが溢れて他の動作に影響する可能性があります。
  2. 絶対ディレクトリにはCドライブを指定しますが、指定ディレクトリには転送したいファイルだけが存在するようにします。
  3. 絶対ディレクトリを指定した場合、デプロイID毎に別のディレクトリにローカルストレージの領域が取られますが、絶対パスでは同じディレクトリが使われます。
  4. アクセス権を設定する必要があります。

対策

VSで診断構成のアプリケーションログ転送を開きます。

application log settings

この設定をしたら、IISのログ転送対策と同じように、RoleのOnStartで、下記の処理を入れます。ここでは、ディレクトリの有無を確認していますが、WADが先に起動した場合は、ディレクトリは作成されています。アクセス権が足りないので追加します。必要なIISのアクセス権については、しばやん雑記:IIS 7 以降でのアプリケーションプールと権限について調べたを参考にしています。

if (Directory.Exists(@"c:\AppLogs"))
    Directory.CreateDirectory(@"c:\AppLogs");
System.Diagnostics.Process.Start("icacls", "c:\\AppLogs /grant \"BUILTIN\\IIS_IUSRS:\":(OI)(CI)W");

最後に

最後に上記の問題を合わせたコード例を上げておきます。

var path = @"c:\AppLogs\RebootFlag.txt";
if (!System.IO.File.Exists(path))
{
    if (Directory.Exists(@"c:\AppLogs"))
        Directory.CreateDirectory(@"c:\AppLogs");

    System.Diagnostics.Process.Start("icacls", "c:\\AppLogs /grant BUILTIN\\IIS_IUSRS:(OI)(CI)W");

    System.IO.File.WriteAllText(path, "Writing RebootFlag at " + DateTime.Now.ToString("O"));
    System.Diagnostics.Trace.WriteLine("Rebooting");
    System.Diagnostics.Process.Start("shutdown", "/r /t 0");
}

csdefに、下記のように、 <Runtime executionContext=”elevated” /> を追加します。

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="AzureCloudService1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2014-06.2.4">
  <WebRole name="WebRole1" vmsize="Small">
    <Runtime executionContext="elevated" />
    <Sites>
      <Site name="Web">
        <Bindings>
          <Binding name="Endpoint1" endpointName="Endpoint1" />
        </Bindings>
      </Site>
    </Sites>
    <Endpoints>
      <InputEndpoint name="Endpoint1" protocol="http" port="80" />
    </Endpoints>
    <Imports>
      <Import moduleName="RemoteAccess" />
      <Import moduleName="RemoteForwarder" />
    </Imports>
  </WebRole>
</ServiceDefinition>
]]>
Wed, 08 Apr 2015 00:00:00 +0900
http://kyrt.in/2015/01/18/goazure_2015.html http://kyrt.in/2015/01/18/goazure_2015.html <![CDATA[GoAzure 2015 でAzureの永続化について話をしてきました]]> GoAzure 2015 でAzureの永続化について話をしてきました

GoAzure 2015の2つのセッションで喋ってきました。ひとつでは、Azure Storageの概要からIaaSのDiskについてまでを「Persistence on Azure - Microsoft Azure の永続化」でみっちり53分ほど、もうひとつは、「しばやん」のセッション(GoAzure 2015 でクラウドデザインパターンと Azure Websites について話してきました) にちょっとお邪魔。

しばやんのセッションは、しばやんが気持ちよく喋ってるので間に割り込むのが難しく、しゃべりの修行の必要性を感じましたね。今後の課題です。

Persistence on Azure - Microsoft Azure の永続化

スライドは、SlideShareに公開しています。

Persistence on Azure - Microsoft Azure の永続化

セッション、「Persistence on Azure - Microsoft Azure の永続化」は、資料を作り始めたら80ページ、90ページと順当に増えていって、到底50分に収まりそうもなく非常に難儀し、最終的にIaaSのDisk(Block Device)を落とし所にしてStorageの概要を話す形にしました。Azure Storageの特徴は、データセンターの構成や内部構造が絡んで、冗長構成とパフォーマンス特性に現れてくるので、どうしてそうなっているのかあたりを含めつつ短時間で話をするのはなかなか難しいです。

もう少し、Tableも交えて話すと、Azure Storageという共通の仕組みの上に、NoSQLとBlock Device(VM Disk)が乗ってることの素晴らしさが伝わったんじゃないかと思うと少々残念です。クラウドベンダーとしてのMicrosoftという観点から見ると、Storage共通基盤は投資効果が高く、他のベンダーとの差別化ができるポイントではないかと思うので、さらなる今後の発展に期待しています。

榎本さんのセッション 「GoAzure2015 - オープンソース.NETと Mono / Xamarin の今後について」が同じ時間の開催になってしまって見れなかったのが残念でした。自分で聞きたかったから、「台湾でばかり喋ってないで日本でも話してくれないか」とセッションに誘ったのに・・・ orz

後になって、クラウドデザインパターン色を入れても良かったのかなと思いつつも、イベントを楽しめました。聞きたいセッションが被り過ぎてたのが残念と言えば残念ですが、非常に充実したイベントでした。

謝辞

榎本さん、酒井さん、森島さん、しばやん、セッションお疲れ様でした。

JAZUGの皆さん、Microsoftの皆さん、スポンサーの皆さん、関係者の皆さん本当にありがとうございました。いつも裏方から表方までありがとうございます。

参加者の皆様、ありがとうございました。

]]>
Sun, 18 Jan 2015 00:00:00 +0900
http://kyrt.in/2014/12/02/introduction_to_azure_batch.html http://kyrt.in/2014/12/02/introduction_to_azure_batch.html <![CDATA[Introduction to Azure Batch]]> Introduction to Azure Batch

※本記事は、Azure Advent Calendar 2014への寄稿記事です。

最近あまりに Azure の新機能が続々と出てくるので、なかなかフォローが難しですが、そんな時は、No.1のBlogと、 twitterで#azurejpを見ておけば良いですね。

そんな数ある今年のAzure新機能の中で、DocumentDBと、Batchの2つがお気に入りです。DocumentDBについては、過日仙台で話をしたので、その時の資料を観てもらうことににして、今回はAzure Batchを紹介します。 公式名称は、Batchということなのですが、一般名称を製品名に使うのは紛らわしいので止めて欲しいですが、ここでは、それを曲げて、Batchと書きます。

Batchは、2014/10/28 に行われた TechEd Europe の Keynote でお披露目されました。その時の動画が公開されています。 開始から 1:11 ぐらいからBatchの紹介が始まって、1:15 から Mark Russinovich がblenderを使った3Dレンダリングを見せてくれます。そこだけなら、そんなに長くないしお勧めです。

TechEd Europe 2014 Keynote

.

Batch とは

簡単に言うと、バッチ処理向けのPaaSです。WorkerRoleを使ってバッチ処理を組むのに比べると、ジョブ·キュー、スケジューラ、VMのプロビジョニングなどをアプリケーション側で用意する必要がありません。また既存のexeファイルをクラウド上で大量に実行することなどが容易に出来るようになっています。Microsoft内部では、Media Serviceが動画のエンコードの処理の部分で使っているそうです。Batchのコンセプトと用語について説明します。

Azure Batch Account

Batchサービスを使うには、Bach アカウントが必要で、そのアカウントはポータルで作成します。アカウントを作成すると、サブスクリプション、リージョン、アカウント名とキーが確定します。アカウント名とキーはコードで利用します。

Azure Batch Account

SDK

Bachでは、低レベルのHTTP REST APIと、高レベルの Batch Apps を用意しています。現在のところ、 .NET 向けBatch Apps Cloud SDKBatch Apps Client SDKと、python 向け SDKが用意されていて、Visual Studio 向けの extensionもあります。

Azure Batch REST API

低レベルのHTTP REST API では、VM pool の管理、work item の実行を柔軟にコントロールすることができます。この方法では、あなたのリソースを完全に制御できますが、タスク実行パイプラインを管理するためにクライアントが必要です。

Azure Batch Apps

もう一つの高レベルの仕組みとして、Batch Appsと呼ばれる機能が用意されています。この仕組みでは、バッチワークロードを登録し、クライアントあるいは、Batch Apps portal から実行することが出来るなどよりユーザーよりの管理機能を用意しています。

Azure Batch Apps portal

Pools

バッチを実行する、ノードのグループです。Poolは、複数のVMで構成され、VMは、auto scaling rules で増減します。PoolへのVMの追加が必要な場合は、自動的に新規ノードが作成・追加されます。

Task Virtual Machines (TVMs)

TVMs は、Poolを構成する、compute nodeの1つです。Batch用に構成された Web/Worker Role と思ってください。scalable で stateless な仮想マシンとして利用できます。Poolに TVMs が追加される場合は、新規にクリーンな仮想マシンが用意されます。そのため、Poolの作成には少々時間がかかります。初期ノード数を指定して試してみたところ、1ノードで8分、100ノードぐらいの時間がかかりました。(Japan West、A1)

Pool and TVMs

Work Items

Work Itemsは、アプリケーションがプールでTVMs上で実行される方法を指定するテンプレートです。スケジューリングの設定(execute once とか 実行時間制限など)や、どの Pool でjobを実行するかなどを指定します。

Jobs

jobは、work item のスケジュールされたインスタンスで、一度だけ実行されたり、何度か実行されたりします。複数の task の集まりです。個々の task は、pool のどこかのTVM で実行されます。

Tasks

task は、job の中の一つの実行ステップです。 work item, job, task の関係は、下記の図のようになります。

work item, job, task

Batch APIのサンプルコード

現時点では、Batchのサンプルコードが6個コードレシピに上がっています。そのうち、Azure Batch Sample - Hello World <Batch AppsがBatch APIのサンプルで、 `Microsoft Azure Batch Apps SamplesがBatch Apps のサンプルでした。

Batch APIのサンプルのHello Worldの方をざっと眺めます。

packages.config

まず、packages.config <https://code.msdn.microsoft.com/Azure-Batch-Sample-Hello-6573967c/sourcecode?fileId=127847&pathId=1932330368>を眺めます。最近、サンプルコードや、他人のコードを見る時は、最初にpackages.configを見るようになりました。どんなライブラリを使ってるかを把握しておくと理解が速くなる気がしています。

Azure.Batchを使っています。APIリファレンスは、Batch APIにあります。

設定

試しに動かして見ようとするならば、 BatchとStorageのアカウントが必要です。 これらを作成したら、Program.csに書き込みます。変更するのは、赤線部分、blobのendpoint、batchのアカウント、キー、ストレージのアカウント、キーの5箇所です。

hello world setting

実行

VSでビルドして実行すると下記のような感じで動きます。このサンプルを動かすだけならば、VS extension は必要ありません。

hello world exec

1. BatchClient

Batch アカウント、キーから、BatchCredentialsを作成して、BatchClient.Connect で BatchClientを取得します。

// Get an instance of the BatchClient for a given Azure Batch account.
BatchCredentials cred = new BatchCredentials(Account, Key);
using (IBatchClient client = BatchClient.Connect(Url, cred))
{
...

2. Pool

Poolを用意しますが、もしあれば使います。Poolは、PoolManager経由で操作します。Pool 名はBatch アカウント内でユニークな名前で無ければいけません。Pool作成時に、VMのサイズやOSを指定します。

private static void CreatePoolIfNotExist(IBatchClient client, string poolName)
{
    // All Pool and VM operation starts from PoolManager
    using (IPoolManager pm = client.OpenPoolManager())
    {
        // go through all the pools and see if it already exists
        bool found = false;
        foreach (ICloudPool p in pm.ListPools())
        {
            // pools are uniquely identified by their name
            if (string.Equals(p.Name, poolName))
                {
                    Console.WriteLine("Found pool {0} already exists", poolName);
                    found = true;
                    break;
                }
            }

            if (!found)
            {
                Console.WriteLine("Creating pool: {0}", poolName);
                // if pool not found, call CreatePool
                //You can learn more about os families and versions at:
                //http://msdn.microsoft.com/en-us/library/azure/ee924680.aspx
                ICloudPool pool = pm.CreatePool(poolName, targetDedicated: 3, vmSize: "small", osFamily: "3");
                pool.Commit();
            }
        }
    }
}

3. work item

ここはちょっと分かり辛い。CloudTask がTaskで、次に、Work Itemを定義します。 client.OpenWorkItemManager で、WorkItemManager を取得して。TaskSubmissionHelper を使って、複数のタスクを追加していきます。最後に、 TaskSubmissionHelper.Commitで、Work Itemが発行されJobとしてインスタンス化されます。WorkItemManager.GetJobでJobは取得できます。Jobの実行状況は、TaskStateMonitor で確認することができます。このコードでは、TaskStateMonitor.WaitAll() で全てのjobの完了を待っています。

private static void AddWork(IBatchClient client)
{
    using (IWorkItemManager wm = client.OpenWorkItemManager())
    {
        //The toolbox contains some helper mechanisms to ease submission and monitoring of tasks.
        IToolbox toolbox = client.OpenToolbox();

        // to submit a batch of tasks, the TaskSubmissionHelper is useful.
        ITaskSubmissionHelper taskSubmissionHelper = toolbox.CreateTaskSubmissionHelper(wm, Program.PoolName);

        // workitem is uniquely identified by its name so we will use a timestamp as suffix
        taskSubmissionHelper.WorkItemName = Environment.GetEnvironmentVariable("USERNAME") + DateTime.Now.ToString("yyyyMMdd-HHmmss");

        Console.WriteLine("Creating work item: {0}", taskSubmissionHelper.WorkItemName);

        // add 2 quick tasks. Tasks within a job must have unique names
        taskSubmissionHelper.AddTask(new CloudTask("task1", "hostname"));
        taskSubmissionHelper.AddTask(new CloudTask("task2", "cmd /c dir /s"));

        //Commit the tasks to the Batch Service
        IJobCommitUnboundArtifacts artifacts = taskSubmissionHelper.Commit() as IJobCommitUnboundArtifacts;

        // TaskSubmissionHelper commit artifacts returns the workitem and job name
        ICloudJob job = wm.GetJob(artifacts.WorkItemName, artifacts.JobName);

        Console.WriteLine("Waiting for all tasks to complete on work item: {0}, Job: {1} ...", artifacts.WorkItemName, artifacts.JobName);

        //We use the task state monitor to monitor the state of our tasks -- in this case we will wait for them all to complete.
        ITaskStateMonitor taskStateMonitor = toolbox.CreateTaskStateMonitor();

        // blocking wait on the list of tasks until all tasks reach completed state
        bool timedOut = taskStateMonitor.WaitAll(job.ListTasks(), TaskState.Completed, new TimeSpan(0, 20, 0));

        if (timedOut)
        {
            throw new TimeoutException("Timed out waiting for tasks");
        }

        // dump task output
        foreach (var t in job.ListTasks())
        {
            Console.WriteLine("Task " + t.Name + " says:\n" + t.GetTaskFile(Constants.StandardOutFileName).ReadAsString());
        }

        // remember to delete the workitem before exiting
        Console.WriteLine("Deleting work item: {0}", artifacts.WorkItemName);
        wm.DeleteWorkItem(artifacts.WorkItemName);
    }
}

4. 結果の取得

完了後のJobを、CloudJob.ListTasks()して、個々のTaskの実行結果を拾うことができます。このコードでは、 GetTaskFile(Constants.StandardOutFileName) して標準出力を拾っています。

終わり

最後に、work item を消したり、BatchClientをdisposeしたりなどの後処理をします。基本的な流れは割りとシンプルだと思います。Batch Appsを使うと、予めクラウド側にアプリを登録して置いて、ポータルから実行したり、モニターもポータル経由で出来たりなど諸々便利機能が使えます。その分ちょっと面倒になる部分もありますが、VS extension がその辺りのデバッグなどを支援してくれるようです。

最後に

Batchは、非常に定型的なコードで、ラージスケールなバッチ処理を実行できる面白い仕組みになっています。Azureの諸々の仕組みと上手く組み合わって、簡単なことならHadoop よりシンプルに同じようなことが実現できると言えそうです。ただ、ちょっとWork Item周りの処理が分かり辛く残念です。わりと簡単に使えるし、日本のデータセンターにも来ているので、興味があれば使ってみれば、良いのではないでしょうか。

この前Blogを書いてからだいぶ時間が空いてしました。今年はJAZUG でやっているCDP勉強会が次回第五回で延べ400人ほどの参加になりそうな勢い、slideshareの資料は累積で7万PVを超える勢いで、Microsoft Azure の熱を肌に感じる1年でした。(slideshare のPVは2014/1/1 時点で5千ほどだったので急激に増えました)

GoAzure 2015

GoAzure

あの Scott Hanselman が来る! ~ GoAzure が 1/16 に開催~ のGoAzureで、Storage周りの話しをします(予定)Persistence on Azure - Microsoft Azure における永続化お待ちしてます。

]]>
Tue, 02 Dec 2014 00:00:00 +0900
http://kyrt.in/2014/08/01/cdp_azure.html http://kyrt.in/2014/08/01/cdp_azure.html <![CDATA[クラウドデザインパターン の監訳に参加しました]]> クラウドデザインパターン の監訳に参加しました

「クラウドデザインパターン Azureを例としたクラウドアプリケーション設計の手引き」という本が 日経BP から出ました。

Microsoft patterns & practices チームの人達が書いた、「Cloud Design Patterns: Prescriptive Architecture Guidance for Cloud Applications」http://msdn.microsoft.com/en-us/library/dn568099.aspxの翻訳本です。JAZUG繋がりで話があって、監訳に参加させていただきました。

ここのところ、日本語のデザインパターン本出ていませんでしたが、重要な課題が拾われていてお勧めな内容です。

とても気に入ったので機会を設けて、この本をネタにして7月に2回ほど話をさせてもらいました。最初は、ネタを被らせるていけば、大丈夫かな?と思っていたのですが、書いてみたらかぶりが殆ど無くさらに26,30日の日程にしたので、ちょっと辛かったです。

クラウドデザインパターン Azureを例としたクラウドアプリケーション設計の手引き

http://ec.nikkeibp.co.jp/item/books/P98330.html

クラウド温泉4.0@小樽 - The Return of F# 7/26-27

内容は全く知らず、なんとなくクラウドって付いているから良いかなと思って行ってみたら、ケンブリッジと名古屋から怖い人達が集まるガチなヤツでした。非常に濃いF#成分のなか、現時点でPublic Cloud で、Data Management をどう扱うのか、制約と設計について話をしました。話切れないところもあったので、どこかででまた話したいと思っています。この集まりは、とても楽しくて中途半端だった、 functional programing の理解に風穴を開けてくれました。

その時のセッション資料です

クラウドデザイン パターンに見る クラウドファーストな アプリケーション設計 Data Management編

クラウド温泉4.0@小樽 - The Return of F#

JAZUG クラウドデザインパターン勉強会 7/30

この本は、コードフラグメントではなくて10個の完全に動作するサンプルコードが付属しています。このセッションでは、サンプルコードを読んで興味を引いたところを紹介するという流れにしました。JAZUGで話をするときはいつも、「他ではあまり聞けないような内容で、楽しんでいただこう」と思って題材を選ぶんのですが、あまり「わかりやすかった、良かった」という話を聞くことがありません。もしかすると、題材の選択に問題があるのかもしれないと思い。サンプルコード内の、「Exceptionのハンドリングどうする?」とか、「そのプロパティを設定するとどうなるの?」とかを流しながらパターンを見ていくというストーリにしました。これなら、いつもコーディングしている人なら、あるあるネタになって楽しめるんじゃないかといのが狙いです。これなら万人受けするはずと思ったのですが、セッション終わったら「濃かったですね」と言われ、残念ながら目的達成は道半ばだったようです。orz

その時のセッション資料です

JAZUG クラウドデザインパターンのコードを覗く

最後に

本の中で問題提起されている事柄をどのように扱うのか、それぞれの課題は古くからあるものですが、今回再度クローズアップされているのは何故なのかあたりも面白いと思います。

]]>
Fri, 01 Aug 2014 00:00:00 +0900
http://kyrt.in/2014/07/02/mvp_2014_7.html http://kyrt.in/2014/07/02/mvp_2014_7.html <![CDATA[2014 Microsoft MVP Award を受賞しました]]> 2014 Microsoft MVP Award を受賞しました

2014年7月期、Microsoft Azure のカテゴリで、Microsoft MVP Award を受賞しました。

これも一重に皆様から多くの御支援をいただいた結果です。

今後も、Azure Storage を中心に、Cloud Scale なサービス構築をテーマに情報発信をして行きたいと思います。

これからも、よろしくお願いいたします。

]]>
Wed, 02 Jul 2014 00:00:00 +0900
http://kyrt.in/2014/06/09/websites_gradle_git.html http://kyrt.in/2014/06/09/websites_gradle_git.html <![CDATA[WebSites で、Java + gradle + git を使う]]> WebSites で、Java + gradle + git を使う

5/12 - 15 に行われた、TechEd North America 2014 で、待望のWebSitesのJavaサポートが発表されました。TechEdでは、AzureのJavaサポートとして、仮想マシン、クラウドサービス、WebSites の話がされていました。Azureの各種SDKはJavaは.NETと並ぶぐらい順調に進んでいるようなので今後も注目です。

その中でも、WebSitesのJavaサポートが面白かったので紹介します。

TechEdのセッションの内容は、Java on Microsoft Azureを観てもらうことにして、一般的な開発で行われるプロセスを Java on WebSites でやってみます。

道具立て

ソースコード管理には、git を使い。ビルドには昨今流行りの gradle 、 手元で動作を確認後、git push すると、kudu の deploy hook でbuildされて Web Sites に deploy というシナリオです。deploy の最終段には、tomcat の autoDeploy を使います。

unagi by Takekazu Omi, on Flickr

Web Sitesを作成

まず、azure cli を使うので、インストールしてください。

git repository 付きでWebSitesを作成します。(以下kinmugi006でやっていますが、同じ名前では作れないので適当に変更してください)

$ mkdir kinmugi006
$ cd kinmugi006
$ azure site create --location "Japan West" --git --gitusername kinmugi-king  kinmugi006
info:    Executing command site create
+ Getting sites
+ Getting locations
info:    Creating a new web site at kinmugi006.azurewebsites.net
-info:    Created website at kinmugi006.azurewebsites.net
+
info:    Executing `git init`
info:    Initializing remote Azure repository
+ Updating site information
info:    Remote azure repository initialized
+ Getting site information
info:    Executing `git remote add azure https://kinmugi.king@kinmugi006.scm.azurewebsites.net/kinmugi006.git`
info:    A new remote, 'azure', has been added to your local git repository
info:    Use git locally to make changes to your site, commit, and then use 'git push azure master' to deploy to Azure
info:    site create command OK

これで、Web Sitesが作成されて、Web Sites内に git repository が用意されます。この状態では、ローカルレポジトリは下記のような設定になっています。

$ git remote -v
azure   https://kinmugi-king@kinmugi006.scm.azurewebsites.net/kinmugi006.git (fetch)
azure   https://kinmugi-king@kinmugi006.scm.azurewebsites.net/kinmugi006.git (push)
origin

この状態で、作成したサイトを見るには、下記のコマンドを実行します。

$ azure site browse kinmugi006

もし、Site kinmugi006 does not exist or has no hostnameのようなエラーになったら、まだ作成が完了していないと可能性があるので、少しまって再度実行してください。ほとんどの場合は、数分掛からずに作成は完了します。

このような画面が表示されるはずです。

Web Site の初期画面

試しに、なにか push してみましょう

まず、ポータルサイトから git のパスワードを設定します。DASHBOARD->Reset your deployment credentialsで設定できます。

local git のパスワード設定

index.html という名前で、Hello Java が書かれたHTMLファイルを作成します。git add、commitして、pushします。その時に、パスワードを聞かれるので、先ほどの設定したものを入れて下さい。

$ git push azure master
Password for 'https://kinmugi-king@kinmugi006.scm.azurewebsites.net':
Counting objects: 3, done.
Writing objects: 100% (3/3), 230 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: Updating branch 'master'.
remote: Updating submodules.
remote: Preparing deployment for commit id 'f522084597'.
remote: Generating deployment script.
remote: Generating deployment script for Web Site
remote: Generated deployment script files
remote: Running deployment command...
remote: Handling Basic Web Site deployment.
remote: KuduSync.NET from: 'D:\home\site\repository' to: 'D:\home\site\wwwroot'
remote: Deleting file: 'hostingstart.html'
remote: Copying file: 'index.html'
remote: Finished successfully.
remote: Deployment successful.
To https://kinmugi-king@kinmugi006.scm.azurewebsites.net/kinmugi006.git
 * [new branch]      master -> master

これで、サイトを確認すると、こんな感じで見えます。 ちなみに、azure site browse kinmugi006でブラウザが開きます。

Hello Java

Java ランタイムの設定

これで、git の設定ができたので、次のJava Runtimeの設定をします。ここでもgitのパスワード設定と同じようにポータルを使います。Web Sitesの CONFIGURE のメニューから、JAVA VERSION をOFFから、1.7.0_51(バージョン番号は随時変わります)にして、WEB CONTAINER をTOMCAT 7.0.50 にして、SAVEを押します。

Select Java runtime

ここで、http://kinmugi006.azurewebsites.net/を見ると、さきほどのHello Javaから下記のような画面になります。

Hello Java

さっき上げた、index.html では無くて、Web Sitesがデフォルトで用意しているJava Web Application が表示されています。なにが起きているのかを簡単に Kudu の画面から確認してみましょう。

https://kinmugi006.scm.azurewebsites.net/にアクセスします。ポータルとシングルサインオンになっているので、既にログインしている場合は、そのままアクセスすることができます。

Debug Console を選んで、d:/home/site/wwwroot に行きます。すると、index.htmlと、webapps が見えます。先ほどpushしたファイルが、index.htmlで、java runtime を有効にしたときに作成されたのが、webappsです。Web Sitesで動いている tomcat のserver.xmlでは下記のように定義されています。appBase が、ここに指定されているのが確認できます。autoDeploy=”true” になっているので、appBaseにwarファイルを置くと自動展開されます。Java Runtimeを有効にする前は、IISがwwwrootをdocumet rootにして動いおりindex.htmlが見えていましたが、tomcat がhttp requestをハンドリングするようになったので、webapps/ROOTが見えるようになりました。

<Host name="localhost"  appBase="d:\home\site\wwwroot\webapps" xmlBase="d:\home\site\wwwroot\"
      unpackWARs="true" autoDeploy="true" workDir="${site.tempdir}">

  <Valve className="org.apache.catalina.valves.AccessLogValve" directory="${site.logdir}"
         prefix="site_access_log." suffix=".txt"
         pattern="%h %l %u %t &quot;%r&quot; %s %b" />

</Host>

今回下記の設定をしてください。何故か、user.home が定義されていないので d:home にします。JDK-4787931 : System property “user.home” does not correspond to “USERPROFILE” (win)Azureでは、IPV6をサポートしていない(今のところ)ので、IPv4 Stackのみにします。

$ azure site  appsetting add JAVA_OPTS="-Duser.home=d:\home -Djava.net.preferIPv4Stack=true" kinmugi006

※想定外に話が長くなってきたので、飛ばして行きます。

git push後の自動ビルド

標準の構成では、Web Sitesの git deploy を使うと、git の sever side hook のpost-receive で、kudu.exeを起動します。kudu.exeは、repository をupdateし、規定のdeploy scriptを呼び出します。ASP.NETのようなコンパイルが必要なアプリケーションは良しなにやり、phpなどのように、そのまま展開すれば良いものはkudusyncで同期させます。今のところ、java用のビルド、展開スクリプトは用意されていないので、自前で作成します。

まずは、Javaのアプリの準備をします。

ビルドにgradleを使うので、gradle公式から、gradle-1.12-bin.zipをダウンロードして展開します。バージョン番号が入ると面倒なので、ディレクトリ名は変更してます。srcの下のアプリケーションは、gradle-1.12-(src|all).zipに入っている、webApplication/quickstartです。

ディレクトリ構成

├─gradle
│  ├─bin
│  ├─init.d
│  ├─lib
│  │  └─plugins
│  └─media
└─src
    └─main
        ├─java
        │  └─org
        │      └─gradle
        │          └─sample
        ├─resources
        └─webapp

.deployemntという下記のような内容のファイルを作成します。このファイルがあると、kudu.exe は、git update の後に、規定のスクリプトを実行せずに、.deployemntファイルの設定に従います。今回は、作成したバッチを実行するだけです。

[config]
command = deploy.cmd

deploy.cmdでは、gradleを実行して、出来上がったwarファルをappBaseにコピーします。

@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off

SET GRADLE_HOME=%~dp0%\gradle
SET GRADLE_HOME_CMD=%GRADLE_HOME%\bin\gradle.bat

:: Setup
:: -----

setlocal enabledelayedexpansion

IF NOT DEFINED DEPLOYMENT_SOURCE (
  SET DEPLOYMENT_SOURCE=%~dp0%.
)

IF NOT DEFINED DEPLOYMENT_TARGET (
  SET DEPLOYMENT_TARGET=%~dp0%\..\wwwroot
)

IF EXIST "%GRADLE_HOME_CMD%" (
  call :ExecuteCmd "%GRADLE_HOME_CMD%" clean war
  IF !ERRORLEVEL! NEQ 0 goto error
)


:: copy war file to wwwroot

IF NOT EXIST "%DEPLOYMENT_TARGET%"\webapps (
   mkdir "%DEPLOYMENT_TARGET%"\webapps > NUL 2>&1
)

COPY /Y "%DEPLOYMENT_SOURCE%"\build\libs\kinmugi.war "%DEPLOYMENT_TARGET%"\webapps\ROOT.war
IF !ERRORLEVEL! NEQ 0 goto error

goto end

:: Execute command routine that will echo out when error
:ExecuteCmd
setlocal
set _CMD_=%*
call %_CMD_%
if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%
exit /b %ERRORLEVEL%

:error
endlocal
echo An error has occurred during web site deployment.
call :exitSetErrorLevel
call :exitFromFunction 2>nul

:exitSetErrorLevel
exit /b 1

:exitFromFunction
()

:end
endlocal
echo Finished successfully.

ここまでできたら、git add, commit して、pushしてください。

$ git push azure master
Counting objects: 189, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (178/178), done.
Writing objects: 100% (188/188), 40.11 MiB | 973.00 KiB/s, done.
Total 188 (delta 3), reused 0 (delta 0)
remote: Updating branch 'master'.
remote: Updating submodules.
remote: Preparing deployment for commit id 'c190ab30ef'.
remote: Running custom deployment command...
remote: Running deployment command...
remote: ..................
remote:         1 file(s) copied.
remote: Finished successfully.
remote: Deployment successful.
To https://kinmugi-king@kinmugi006.scm.azurewebsites.net/kinmugi006.git
   f522084..c190ab3  master -> master

ただ一つだけ問題があります。最初に、WebSitesの作成したwebappsには、ROOTのディレクトリがあるのにROOT.warが無いので autoDeploy が上手く動作しません。どこかを修正してpushし直すか、ポータルから redeploy してください。問題があるのは初回だけで2回めからは前回upしたROOT.warがあるので無事自動展開されます。

最後に

kuduの仕組みが良く出来ていて特別なjava対応などが無くても、今回 WebSitesに入った httpPlatformHandler だけで、git pushしたらbuildしてdeployということが簡単に実現できました。ただ、kudu projectにある、kuduのソースはちょっと流し読みをして理解するには量が多く、動かしなら少しづつ確認するという形じゃないとなかなか動きを掴むのは難しところがあります。

下記のサイトを参考にしました

[1]Git のカスタマイズ - Git フック
[2]kudu.exeは、Kudu.Console
[3]SCM_TRACE_LEVEL = 4 にするとdeploy scriptのtraceが出る
[4]Well Known Environment Variables
[5]KuduSync のオプション
[6]Deployment Logic Flow
[7]Deployment hooks
[8]Custom Deployment Scripts For Windows Azure Website Using Git Deployment
]]>
Mon, 09 Jun 2014 00:00:00 +0900
http://kyrt.in/2014/06/08/snowflake_c.html http://kyrt.in/2014/06/08/snowflake_c.html <![CDATA[スケーラブルな採番とsnowflake]]> スケーラブルな採番とsnowflake

snowflake は、Twitter 社が作成した、ユニークなID生成のネットワークサービスです。いくつかの簡単な保証で高いスケーラビリティを実現しています。Twitter 社が、MySQLから Cassandra に移行するにあたって、Cassandra にシーケンシャルな id 生成の仕組みが無かったことから作成したそうです。 snowflake についてはTwitter IDs, JSON and Snowflakeに書いてあります。

snowflake のコードは、Apache License, Version 2.0 でSnowflakeに公開されています。

スケーラブルな採番、背景的な話

Cloudでスケーラビリティのあるサービスを見据えてコードを書いていると採番に関する問題が必ず出てきます。従来、RDBの自動採番などに頼っていたのがコスト、スケーラビリティ、耐障害性の観点から問題となり、別の方法を考える必要が出てくるというのが一般的です。

「ID=連番」という話なら始まると話が混乱するので、基本に返って採番の要件を整理するところから初めましょう。ざっと考えたところ4つほどの条件が出てきたので、この線で話を進めます。

  1. ユニークである:重複が無い。これはIDをキーにしたいので当然ですね
  2. 時系列で増えていく(または減っていく):IDが生成される度に増加していくのは連番での重要な性質の一つです
  3. 秒間の発行数:採番を一箇所で行わずに分散してスケールするのがスケールする
  4. 連続性(連続した欠落の無い番号の生成)

最後の連続性に関しては、スケーラビリティとパフォーマンスを両立させての実現は難しいでしょう。スケールと対象外性を考えて分散環境でID生成の度にどうしてもNode間でメッセージのやりとりをせざるを得ず、パフォーマンスを犠牲にせざる得ないので、今回は外して考えます。オンプレでも限定された条件下でのみ可能な採番ですね。

これとは別に何処で採番するかという観点もあります。これは2つのパターンがあります。

  1. Cluster Service で採番
  2. クライアントで採番

Cluster Service で採番するパターンとしては、memcached/Redis などの カウンター (incr command) を使ったり、storage を使った分散counterを使ったパターンが知られています。Snowfrakeもこの1種です。クライアントでの採番は、通信を伴わないので一番速い(コストが小さい)ですが制約もあります。

採番のユニーク性

採番と考えると最低限の要求は 「一意であること UNIQUENESS」でしょう。これには、発番した時にユニーク性を担保するか、永続化したときにユニーク性を担保するかの2つの選択肢があります。採番した時に一意性を担保するパターンとしては、GUIDを使う方法よく知られています。ここでは、Guidを使う方法については深くふれません、GUIDs are globally unique, but substrings of GUIDs aren’tで面白い話が書いてあるんのでぜひ読んでみてください。(中には Snowflakeへのヒントになることも記述されています)

GUIDは非常に手軽で便利ですが、128bit(longlong)でかなり長い。時系列で並ばないなどの問題があります。

永続化レイヤーでの一意性の担保

一方永続化したときにユニーク性を担保するという考えもあります。一意なIDを振出すのはコストが高いので、擬似的な(一意性を十分期待できる)IDを振り出して永続化して可否を判断して、ダメならもう一度やってみるというのが「永続化レイヤーでの一意性の担保」の基本的な考え方です。

これは、永続化レイヤーが用意した限定された条件の下でユニーク制約を実装を利用することでユニーク性を担保するというアーキテクチャです。この場合は、「永続化レイヤーの制約」、「重複時の再試行」の2点を考慮しなければいけません。

例えば、Azure Tableでは、同一パーテーション内のRow Keyは一意であることが保証されています。他のパーテーションでは同一のRowkeyを持つことができますが、同一パーテーション内同じRawkeyは Insert できません。

この場合、Partition Key + Row Key でユニークになるので、ランダムなキーを生成して、TableにInsertできればそのパーテーション内ではユニークだと保証されるわけです。RDBのUniq制約でも同様です。Shareding されたデータベースでは、Inserできれば Shard内でユニークだという事になります。いずれも、Partition 内、 Shard 内という条件が付いていることに気を付けてください。永続化レイヤーによって異なった制約となりますが、分散システムではなんからの制約を伴うことが一般的です。

重複時の再試行

永続化レイヤーでUNIQ性を保証をする場合、「重複時の再試行」を前提としてください。クライアントがデータを永続化するまで、IDが有効かどうかわからないので、永続化した時に重複だった場合は再度IDを生成し直して処理を再試行する必要があります。

時系列で増減

IDがランダムではなく、時間と共に増加あるいは減少して欲しいことがあります。このようにIDが並んでいると、永続化レイヤーがレンジクエリーをサポートしていれば、前方一致で効率的に目的のデータを取得することができます。しかし時系列で増加するIDを発番しようとしてNodeのクロックを使うと信頼性が問題になります。

通常、複数Nodeで発番した場合は同一時刻で複数のコードが動くのでIDが重複します。それ以外にもNode内でも問題があります。Nodeのクロックは、外部のtime service に同期させているので、ローカルの時刻は随時進んだり戻ったりします。そのため、時刻だけを元に単純に採番するとIDが重複します。これらの時刻の不安定性に起因する問題を Clock Skew (クロックスキュー)問題と言います。

分散システムの問題の多くは、時刻の同期問題でもあり根が深いので、ここはスルーで先に行きます。

これらの問題を回避するために、IDの一部にランダムな要素を付与する方法が一般的に使われています。

例えば、下記のような感じ

private static void Hoge()
{
  var r = new Random();
  for (var i = 0; i < 100; i++)
  {
    var id = string.Format("{0:D19}_{1:D4}", DateTime.Now.Ticks, r.Next(10000));
    Console.WriteLine(id);
  }
}

ここで発番しているのは擬似的なUNIQ性を持ったIDでです。最終的なUNIQ性の保証は別のレイヤーに預ける前提です。ランダム桁数は、衝突の頻度と衝突時の再試行のコストを鑑みて決定します。この例だと、4桁取っていますが、昨今のCPUだとシングルCoreで同一Tick内で2-3程度は発番できるようなので、100coreぐらいあれば、2*100/10000 = 1/50 となって、結構な頻度で衝突します。(この数字は結構いい加減なので参考程度で)

順番を逆にしたい場合は、下記のようにします。

private static void Hoge()
{
  var r = new Random();
  for (var i = 0; i < 100; i++)
  {
    var id = string.Format("{0:D19}_{1:D3}", long.MaxValue - DateTime.Now.Ticks, r.Next(1000));
    Console.WriteLine(id);
  }
}

こうすると、最新のものが最初にきます。先頭から指定行を取得するようなクエリーをサポートしている永続化レイヤーで最新の情報を取得する場合にはこの並びが便利に使えます。

秒間の発行数

秒間どれぐらいの数のIDを生成可能なのかも重要なファクターです。一般的にオンメモリで計算のみで発番するものが一番速く、永続化付きKVS、RDBMSの順の速度になります。

ID生成はクラスターで行って、分散することでスケールするというのは基本的な考え方で、その場合ID生成クラスターのNodeコストが問題となります。一般的にメモリーで計算のみで発番するものはIDのユニーク性と耐障害性の両立が難しく、なんからの永続化をすることで問題を回避するという方法が取られる場合が多いようです。RDBはID生成を目的としてクラスター化するにはコスト高いのが問題となると思います。

クライアントで擬似的なUNIQ IDを生成して、永続化時に担保させるという方法はリーズナブルにスケールする方法だと言えます。

Snowflake

Clock Skew に上手く対応することで、オンメモリの計算と最低限のノード間通信でUNIQなIDを生成する仕組みです。永続化レイヤーの支援無しにUNIQなIDが生成できる、オンメモリなので速い(10k ids per second per processSnowflake)というのが特徴です。

ある意味GUIDと似ているのですが、64bit(long) で実現しているところ、おおよそ時系列で増えていく「(Roughly) Time Ordered」なことが異なっています。

データ構造

snowfake では、64bitを下記のように分割して使います。timeが時間由来の数字でミリ秒単位です。machine id は、datacenter id と、worker idで構成されていて、Cluster内のNode固有の値になります。sequence number は、同一時間(timeが同じ)間にカウントアップされていく連番です。

64 bits
0
time
41 bit
machine id 10bit
sequence number
12bit
datacenter id 5bit worker id 5bit

前記の時間ベースのID生成と比較すると、IDにNode固有の番号(machine id)が含まれている、同一時間内の採番では乱数ではなくカウンターを使って重複を避けていることが異なっているのがわかります。

実は、この構成は GUID V1と似ています。GUID V1 は、 timestamp 60 bits、computer identifier (MAC Address) 48 bits、uniquifier (乱数+シーケンス)14 bits、fixed ()バージョン等) 6 bits から構成されています。全体的にGUIDの方がBitの使い方がリッチで余裕がある構成です。データは、DWORD-WORD-WORD-WORD-BYTE[8] として扱われます。Endian の問題もあってtimestamp由来なのに時系列で並びません。timestampは、100 ns 単位です。

snowflake は、GUIDを64 bits (正確には最上位が0固定のなので63 bits)に押し込んでUNIQ性を確保しています。

ではどうやって64bitに押し込んでいるのでしょう。

  1. 範囲の制限: 簡単に言うと、GUIDは グローバルにUNIQだけど、snowflakeは発番体系内でUNIQです。別のサービスで使っている snowflake clusterと比較するとUNIQ性は保証されません。ここは大きな違いですが、別サービスとIDが被るのは問題無いので、実用上は制限にはなりません。これで、MAC Address 48 bits の所を10 bits に押し込めています。
  2. timestampの精度と範囲: GUIDは、100 ns 、snowflakeは、1 ms です。timestamp の精度を下げ、開始時間(0の時の日時、何時からの時間なのか)をサービス開始時などに合わせることで、timerのラップアラウンドの問題を軽減しています。(2^41)*(10^-3)/60/60/24/365 で約69年持ちます。ちなみにGUIDは、(2^60)*(10^-7)/60/60/24/365 で3655年以上持ちます。開始は、1582/10/15 0:00 です(3千年以上もつの開始はあまり重要じゃないですね)69年保てば問題無い気がしますね。これで、41 bitsにしています

ソースコード上での工夫

なかなか興味深かったので、IdWorker.scalaをC#で写経してみました。(scala が非常に分かりやすくて感動しました)

基本的な流れは、非常にシンブルです。

NextId()をざっと見てみましょう。

  1. 現在のtimestamp を取得L60
  2. 最後に発番してからtimestampが戻っていないか確認L63
  3. 巻き戻っていればエラーで帰る(呼び出し側が再試行する)L66
  4. 同一timestampなら、_sequence をインクリメントして採番L72
  5. timestamp が進んでいれば、_sequence = 0で採番L82

この他は、下記のコードがポイントです。

timestampの開始時間を、採番開始時間にオフセットさせるL87

同一 timestamp 内で、_sequence がオーバーフローした場合は、timestampが変わるまで待つL75

この他に、Worker Idの管理には、zookeeper を使っているなど興味深いところはありますが、今回はこの辺でまとめます。

最後に

実は、この記事を書いている途中に、Global Microsoft Azure Bootcamp 2014があり、このネタで発表してしまったので長らくお蔵入りしていました。読み直してみたら、セッションでは時間の都合で話をしなかった内容もあり、それなりに役に立つかなと思ったので公開します。肝心のsnowflake の採番が速い理由が書いてありませんが、採番node間の通信が最低限で(生きているかどうか)、重複採番の防止にしか使われていないのが、一番効いている気がします。

合わせて、セッション資料も御覧ください。

Generating unique id numbers in Azure

]]>
Sun, 08 Jun 2014 00:00:00 +0900
http://kyrt.in/2014/03/02/read_access_geo_redundant_storage.html http://kyrt.in/2014/03/02/read_access_geo_redundant_storage.html <![CDATA[FiddlerCoreでRead-Access Geo Redundant Storage を検証する]]> FiddlerCoreでRead-Access Geo Redundant Storage を検証する

Windows Azure – 技術者でつなぐ日めくりカレンダーの 3/2 の記事です。先日、Windows Azure 4周年記念 & Japan DCオープンマジデシタ!JAZUG大会のLTで、Azure Storageには3つの可用性設定があるという話をしました。例によって、時間内には収まらず。「続きはWebで・・・」ということで続きです orz

まずはLTの内容の復習から、簡単にまとめると下記のような内容です。

振り返り

Azure Storageの可用性設定(冗長構成)には3種類ある。
  1. ローカル冗長ストレージ (LRS=Locally Redundant Storage)データセンター内3箇所、同期
  2. 地理冗長ストレージ (GRS=Geo Redundant Storage)地理的に離れた場所への複製(Local 3箇所+リモート3箇所)、リモートは非同期、フェイルオーバー
  3. 読み取りアクセス地理冗長ストレージ (RA-GRS=Read Access - Geo Redundant Storage) PREVIEW地理的に離れた場所にあるデータを読む機能

読み取りアクセス地理冗長ストレージ (以下、RA-GRS)は、現状Previewなので、まずはWindows Azure Previewのページで申し込みをする必要がある。

SDKの対応状況

RA-GRSの新機能を使うには REST version 2013-08-15 を使うStorage SDKが必要。ただし、RESTでEndpointが違うだけだから自前でもアクセスするだけならそんなに難しくない。

  1. .NETだと、Storage Client Library 3.0以降
  2. Javaだと、Windows Azure Storage SDK for Java 0.5.0以降
  3. その他、各自対応

上記2つのSDKには、Blobs, Tables and Queues の最終同期時刻(Last Sync Time)をクエリーする機能と primary にアクセしできなかった時に secondary に自動的に retry する機能、並びにLocationMode で参照先の設定がる。リトライは、IRetryPolicyを拡張した IExtendedRetryPolicy が追加されている。これではリトライ時に参照先を切り替えするようになっている。

LTの資料

RA-GRS Windows Azure Storage LT

ここからが本題です。

確認する

RA-GRSで障害が起きた時のIExtendedRetryPolicyの動作確認をしたいのですが、そうは都合良くデータセンターの障害が起きるわけはありません。デバッガーでBreakしてネットワークを切断するとか、変数を書き換えるとかの方法もありますが、なんども繰り返すのは手間が掛かるしパターン増やすと面倒です。そんな時に、丁度いいタイミングでCLR/H in Tokyo 第1回で過酷な争奪戦に勝利し「実践Fiddler」をゲットしました。

実践 Fiddler Eric Lawrence 著、日本マイクロソフト株式会社 エバンジェリスト 物江 修 監訳、長尾 高弘 訳

FidderCoreを使うとHTTP通信の途中に割り込んで内容を変更することが簡単に出来ます。フルスクラッチでProxy書いて「ゴニョゴニョ」すると結構手間がかかるので、今回は、FeddlerCoreを使って サクッとLocationMode.PrimaryThenSecondary を指定した時のリトライ動作を確認します。本にはいろいろ詳しく書いてあるので、興味が出た方は是非!

準備

下記の場所から、インストーラーを落としてきて入れます。exeが落ちてくるのでインストールすると、FiddlerCoreAPIというディレクトリーに入ります。

http://www.telerik.com/fiddler/fiddlercore

../../../_images/2014-03-02-ragrs001.png

インストール先のFiddlerCoreAPI\DotNet4\FiddlerCore4.dllを参照に追加します。

../../../_images/2014-03-02-ragrs002.png

今回読み込みアクセスを試したいので、予めBlob にファイルを上げて起きます。

Blobからのダウンロードのコードは下記のような感じです。CloudBlobClient の LocationModeを設定しています。Client全体の設定はここで書き、ここのリクエストでは、BlobRequestOptionsで設定します。それ以外は普通のBlobからDownloadするコードですね。try の次の行ので読んでいる、FiddlerCoreSetup()でFiddlerCoreの設定を行っています。

static void Main(string[] args)
{
  var connectionString = CloudConfigurationManager.GetSetting("ConnectionString") ?? "UseDevelopmentStorage=true";
  var account = CloudStorageAccount.Parse(connectionString);
  var client = account.CreateCloudBlobClient();

  try
  {
    FiddlerCoreSetup(client.StorageUri.PrimaryUri.Host);

    // プライマリ→セカンダリで切り替え
    client.LocationMode = LocationMode.PrimaryThenSecondary;

    var container = client.GetContainerReference("jejeje");
    var blob = container.GetBlockBlobReference("cray.png");
    blob.DownloadToFile("cray.png", FileMode.OpenOrCreate);
    
  }
  finally
  {
    FiddlerCoreShutdown();
  }
}

FiddlerCoreSetup() の中を見てみましょう。基本的には必要な処理をFiddlerのイベントハンドラに追加して、FidderCore プロキシを起動するだけです。

private static void FiddlerCoreSetup(string blockedHostName)
{
  FiddlerApplication.AfterSessionComplete += os =>
      Console.WriteLine("AfterSessionComplete Session {0}({4}):{1} {2} {3}",
      os.id, os.responseCode, os.RequestMethod, os.fullUrl, os.oResponse.MIMEType);

  FiddlerApplication.BeforeRequest += os =>
  {
    if (os.HostnameIs(blockedHostName))
    {
      Console.WriteLine("BeforeRequest Session {0} {1} {2}", os.id, os.hostname, os.host);
      os.host = blockedHostName + ".jejeje";
    }
  };

  FiddlerApplication.Startup(0, FiddlerCoreStartupFlags.ChainToUpstreamGateway);

  var endpoint = string.Format("127.0.0.1:{0}", FiddlerApplication.oProxy.ListenPort);

  var feddlerProxy = new WebProxy(endpoint);
  WebRequest.DefaultWebProxy = feddlerProxy;
  Console.WriteLine(endpoint);

}

private static void FiddlerCoreShutdown()
{
  FiddlerApplication.Shutdown();
}

ちょっと細かく追いかけてみます。

AfterSessionCompleteはHTTP セッションが完了した後に生成されるイベントです。 このイベントハンドラにロギング代わりにコンソールへの書き出しを追加します。。今回は、ここのログを見て何処にアクセスに行っているのかを確認します。

BeforeRequestはHttp リクエストのデータが揃いサーバーに接続する前に生成されるイベントです。このイベントハンドラに、Primary のホストが接続先だったときに、存在しないhostに繋ぎに行くようなハンドラを追加します。

FiddlerApplication.Startup()を呼び出して、FeddlerCoreプロキシインスタンスを起動します。ポートに0を指定すると、FeddlerCore が自動的に空いているポートを探してリスニングに入ってくれます。ポートが使用中の問題が発生しないので便利なので、ポート番号を固定したいとき以外は0で良いと思います。自動で割り当てられたポート番号は FiddlerApplication.oProxy.ListenPort で確認できます。

今回は、このアプリケーションから出るリクエストだけ操作できればいいので、FiddlerCoreStartupFlagsChainToUpstreamGatewayを指定します。その他に、システム全体のProxyに入りたい場合は、RegisterAsSystemProxyを使います。

そして、WebRequestDefaultWebProxyを、FeddlerCoreプロキシ で書き換えます。これで、Azure Storage Client のリクエストが、Primary に向いているときは失敗するようになりました。

これで、実行すると下記のようになります。

127.0.0.1:19282
BeforeRequest Session 1 ragrsomi001jp.blob.core.windows.net ragrsomi001jp.blob.core.windows.net
AfterSessionComplete Session 1(text/html):502 GET http://ragrsomi001jp.blob.core.windows.net.jejeje/jejeje/cray.png?timeout=90
AfterSessionComplete Session 2(image/png):200 GET http://ragrsomi001jp-secondary.blob.core.windows.net/jejeje/cray.png?timeout=90

最初に、Primary にアクセスし、その後 Secondary にアクセスし直しているのがわかります。

Primary へのアクセスが502 Bad Gatewayなのはちょっとイマイチですが、FeddlerCoreがProxyで入るので仕方がないのかもしれません。

まとめ

実は、FeddlerCoreは、Azure Storage ClientのUnit Testでも使われています。GitHub azure-storage-net HttpMangler.cs今回は非常に簡単な例を紹介しましたが、FeddlerCoreを使うとレスポンスを偽装したり変更したりなど柔軟にできて、通常エラーにならないような場合のテストケースを作ることができます。手軽に使えるのでカジュアルに使っても便利ですし、がっちりUnitTestを作るときにも役に立ちます。

CLR/H in Tokyo 第1回はいろいろ知らないことを聴けたので面白かったですね。特に、技術者のブランディング戦略の重要性と独自の進化を遂げたプロビジョニング、構成管理ツールの話が秀逸だった。

最後に1つだけ、FiddlerCore をnugetで配布してくれると嬉しいなぁ・・・

]]>
Sun, 02 Mar 2014 00:00:00 +0900
http://kyrt.in/2014/01/29/windows_azure_storage_emulator_2_2_1_preview_release.html http://kyrt.in/2014/01/29/windows_azure_storage_emulator_2_2_1_preview_release.html <![CDATA[Windows Azure Storage Emulator 2.2.1 Preview]]> Windows Azure Storage Emulator 2.2.1 Preview

Azure Storage 愛好者待望の、Azure Storage Emulator の 2013-08-15 version サポートがリリースされました。(まだ、preview ですが)

Windows Azure Storage Emulator 2.2.1 Preview Release with support for “2013-08-15” version

Windows Azure Storage Emulator 2.2.1 Preview Release の MSI package は、ここからダンロードできます。

insect by Takekazu Omi, on Flickr

インストールの手順

この release では、Windows Azure SDK 2.2 が予めインストールされている必要があります。これは preview release で、インストーラーは、Windows Azure Storage Emulator 2.2 のバイナリーを自動では入れ替えません。代わりに、32bit OSでは、"%ProgramFiles%\Windows Azure Storage Emulator 2.2.1\devstore"に、64 bit OSでは"%ProgramFiles(x86)%\Windows Azure Storage Emulator 2.2.1\devstore"にバイナリーを展開します。

更新されたエミュレータ(2.2.1 preview)を利用する場合は下記のステップを踏んでください。

  1. Storage Emuratorの停止
  2. "%ProgramFiles%\Microsoft SDKs\Windows Azure\Emulator\devstore"にある既存のファイル(2.2)を他のディレクトリに保存
  3. 新たにインストールされた2.2.1 preview のファイルを"%ProgramFiles%\Microsoft SDKs\Windows Azure\Emulator\devstore"へ上書き

以前のエミュレータ(2.2)に戻す場合は、Storage Emuratorを停止して保存したファイルを戻します。この方法で、簡単に2.2のStorage Emuratorに戻すことができ、SDK 2.2の再インストールなどは必要ありません。

これはちょっと手間ですが、Windows Azure Storage Emulator 2.2.1 Preview Release with support for “2013-08-15” versionによると、preview のための暫定的な処理のようです。

Do It

やってみます。まず、最初にAzure Storage Emulatorが動いていたら止めます。

../../../_images/2014-01-29-WASE221-005.png

Windows Azure Storage Emulator 2.2.1 Preview Release の MSI package を、ここからダンロードしてきてインストールします。

../../../_images/2014-01-29-WASE221-001.png

インストールが終わると、README.txt が表示されます。上に書いたようなことが書いてあります。

../../../_images/2014-01-29-WASE221-002.png

手元はx64環境なので、"%ProgramFiles(x86)%\Windows Azure Storage Emulator 2.2.1\devstore"にファイルが有るかを確認します。インストーラーは、ここにファイルを展開しますが、ここだと実行されません。これから規定の場所にコピーします。

../../../_images/2014-01-29-WASE221-003.png

問題無さそうなので、"%ProgramFiles%\Microsoft SDKs\Windows Azure\Emulator\devstore"のファイルをZIPで固めて避難してから、2.2.1のファイルをコピー上書きします。

確認

まずはStorage Emulatorを実行して起動を確認します

普通に Windows Azure Storage Emulator - v2.2 のショートカットをクリックする方法以外に、下記のようにコマンドラインから起動をすることも出来ます。

$ .\csrun.exe /devstore
Windows(R) Azure(TM) Desktop Execution Tool version 2.2.0.0
for Microsoft(R) .NET Framework 4.0
Copyright c Microsoft Corporation. All rights reserved.

Starting the storage emulator...
$

起動してきたので、念のためバージョン番号を確認します。残念ながら変わっていません…

../../../_images/2014-01-29-WASE221-004.png

次にJSONでアクセスしてみます。Install-Package WindowsAzure.Storage後、下記のようなコードを書いて実行します。

using System;
using System.Linq;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;

namespace WASE221pre
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var acct = CloudStorageAccount.DevelopmentStorageAccount;
            var tableClient = acct.CreateCloudTableClient();
            var table = tableClient.GetTableReference("TestTable");
            table.CreateIfNotExists();

            var entity = new TableEntity
            {
                PartitionKey = "pk",
                RowKey = string.Format("rk{0}", DateTime.UtcNow.Ticks)
            };

            table.Execute(TableOperation.Insert(entity));

            var rows = table.ExecuteQuery(new TableQuery<TableEntity>()).ToArray();
            foreach (var testEntity in rows)
                Console.WriteLine("{0}, {1}", testEntity.PartitionKey, testEntity.RowKey);
        }
    }
}

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.Data.Edm" version="5.6.0" targetFramework="net451" />
  <package id="Microsoft.Data.OData" version="5.6.0" targetFramework="net451" />
  <package id="Microsoft.Data.Services.Client" version="5.6.0" targetFramework="net451" />
  <package id="Microsoft.WindowsAzure.ConfigurationManager" version="1.8.0.0" targetFramework="net451" />
  <package id="Newtonsoft.Json" version="5.0.8" targetFramework="net451" />
  <package id="System.Spatial" version="5.6.0" targetFramework="net451" />
  <package id="WindowsAzure.Storage" version="3.0.2.0" targetFramework="net451" />
</packages>

実行結果

$ WASE221pre.exe
pk, rk635265773262439816
pk, rk635265773303542415
pk, rk635265773965590960
pk, rk635265774455960355
pk, rk635265824937388844

上手く行きました。(何度か実行したので複数のエンティティがあります)

本当にJSONで流れているのかどうか念のために確認します。

接続文字列を、"UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://ipv4.fiddler"に変更します。

// var acct = CloudStorageAccount.DevelopmentStorageAccount;
var acct = CloudStorageAccount.Parse("UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://ipv4.fiddler");

実行してCaptureを確認します。

リクエスト

GET http://127.0.0.1:10002/devstoreaccount1/TestTable?timeout=90 HTTP/1.1
User-Agent: WA-Storage/3.0.2 (.NET CLR 4.0.30319.34003; Win32NT 6.3.9600.0)
x-ms-version: 2013-08-15
Accept-Charset: UTF-8
MaxDataServiceVersion: 3.0;NetFx
Accept: application/json;odata=minimalmetadata
x-ms-client-request-id: a4ed326b-e3a6-43f9-bb6e-8fb295014473
x-ms-date: Wed, 29 Jan 2014 09:04:42 GMT
Authorization: SharedKey devstoreaccount1:7Wt86EphqhTZTwLxkMrAkPGNV9WDSKa0df5Feaw8zkU=
Host: 127.0.0.1:10002


レスポンス

HTTP/1.1 200 OK
Cache-Control: no-cache
Transfer-Encoding: chunked
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: fdd3f33e-e2d7-401e-a4c6-7654bd7ae584
x-ms-version: 2013-08-15
X-Content-Type-Options: nosniff
Date: Wed, 29 Jan 2014 09:04:42 GMT

285
{"odata.metadata":"http://127.0.0.1:10002/devstoreaccount1/$metadata#TestTable","value":[{"PartitionKey":"pk","RowKey":"rk635265773262439816","Timestamp":"2014-01-29T07:28:46.35Z"},{"PartitionKey":"pk","RowKey":"rk635265773303542415","Timestamp":"2014-01-29T07:28:50.513Z"},{"PartitionKey":"pk","RowKey":"rk635265773965590960","Timestamp":"2014-01-29T07:29:56.707Z"},{"PartitionKey":"pk","RowKey":"rk635265774455960355","Timestamp":"2014-01-29T07:30:45.74Z"},{"PartitionKey":"pk","RowKey":"rk635265824937388844","Timestamp":"2014-01-29T08:54:54.147Z"},{"PartitionKey":"pk","RowKey":"rk635265830822644693","Timestamp":"2014-01-29T09:04:42.46Z"}]}
0

無事に動いていることを確認できました。

最後に

preview ですが、Storage Emulatorが 2013-08-15 versionをサポートし、軽くですがJSONでのアクセスが動くことを確認できました。12月のWindows Azure Storage Client Library for C++ Previewでは、2013-08-15 version 対応のStorage Emulator が、来月には出せそうということだったので、ほぼ予定通りですね。どうしても手元で確認したい場合は、Storage Emulator 2.2.1 previewを使って、そうでない場合は本番を利用して開発ということになりそうです。

]]>
Wed, 29 Jan 2014 00:00:00 +0900
http://kyrt.in/2014/01/06/windows_azure_storage_3_0_2_hotfix.html http://kyrt.in/2014/01/06/windows_azure_storage_3_0_2_hotfix.html <![CDATA[Windows Azure Storage 3.0.2 Hotfix]]> Windows Azure Storage 3.0.2 Hotfix

2014/1/4Windows Azure Storage 3.0.2がリリースされました。2013/11/27 3.0のリリース 「Windows Azure Storage Release - CORS、JSON、Minute Metrics の紹介」、2013/12/11 の 3.0.1 hotfix 「Windows Azure Storage Client 3.0.1」 に続く3回めのリリースです。

zenko-ji by Takekazu Omi, on Flickr

修正点

Issues fixed in 3.0.2.0 ::

  1. All (WP): 多くのAPIで ArgumentOutOfRangeException になる問題の修正
  2. Queues: 存在するqueueの再度作成で、NullReferenceException になる問題を修正
  3. Tables: レスポンスがparseできなかったときに、TableServiceContext が NullReferenceException になる問題を修正
  4. Tables (RT): JSON形式がまだRT library でサポートされていないため、ユーザーは、RTで RequestOptions に JsonFullMetadata formart を設定することはできません

コードを確認したところ、最初のやつが興味深いものでした。

1. All (WP): 多くのAPIで ArgumentOutOfRangeException になる問題の修正

対象は、Windows Phone プラットフォームだけで特定のAPIでなく全般的に発生します。関連する Issue として 、System.ArgumentOutOfRangeException in Microsoft.WindowsAzure.Storage.Table.CloudTable.EndExecuteが出ています。

共通で使っている、API内のパラメータのバウンダリー検査関数(AssertInBounds)が動いていなくてあちこちで問題が発生するということになっていたようです。(そうは言っても、テストはしているので、そうそう起きるわけでは無いとは思います)

Storage Client Library 3.0.2 のPRでdiffを見みると、WINDOWS_PHONEだけで下記のように修正されていました。

../../../_images/2014-01-06-github001.png

https://github.com/WindowsAzure/azure-storage-net/pull/17/files#diff-546e2cf76676e7cafc795f6d4d105fefR160

#if WINDOWS_PHONE で AssertInBounds に、[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)]を指定しています。この指定は、JITやNGENでのコード最適化を抑制するもので、[1]Windows Phone の場合だけ native code 生成に問題があったので抑制するオプションを付けたということです。

Windows Phone というだけで、どのような場合に発生する問題かなどわかりませんが、WP アプリのコードが挙動不審の場合は試してみると良いかもしれません。Storage Clinet Library内では、この方法で修正されているようなので問題は無いのですが、普通に書いたコードでも起きるとすると、ちょっと困りますね。もう少し詳しい情報が欲しいところです。[2]

これ以外は、あまり気になる点はありませんでした。互換性の問題も無さそうなので、3系のStorage Clinetは 3.0.2 を使うのがお勧めです。

2014/1/11 追記 Windows Phoneの問題

PR の質問に返事を貰いました。

この問題は、Windows Phone でだけ起き、assemblyに、AssertInBoundsとAssertInBounds 両方が存在する場合に、最適化で実行時にAssertInBoundsの間違ったインスタンス化を選択することによって引き起こされる

internal static void AssertInBounds<T>(string paramName, T val, T min, T max) where T : IComparableと、internal static void AssertInBounds<T>(string paramName, T val, T min) where T : IComparableの両方が同一 assembly にあるのが問題のようです。 必ず起きるというわけでも無さそうなので、Windows Phone で嵌ったら思い出してみるという程度で良さそうです。

[1]About System.Runtime.CompilerServices.MethodImplOptions
[2]We escalated this internally ...
]]>
Mon, 06 Jan 2014 00:00:00 +0900
http://kyrt.in/2013/12/27/azure_storage_known_issues_november_2013_resolved.html http://kyrt.in/2013/12/27/azure_storage_known_issues_november_2013_resolved.html <![CDATA[[Resolved] Windows Azure Storage Known Issues 2013/11]]> [Resolved] Windows Azure Storage Known Issues 2013/11

Windows Azure Storage Known Issues 2013/11の既知の問題が解決されたとアナウンスがありました。Windows Azure Storage Known Issues (November 2013) [Resolved]どんな感じになったのかを確認しました。

引用

更新 [2013/12/10] : このBlog に記述されている問題 [1] は、service と client library の更新で解決されました。issue 2 の client table の件は、 2.1.0.4 [2]3.x [3] の client library の releases で解決されています。

matuyama by Takekazu Omi, on Flickr

問題点の確認

問題点は下記の3つが有りました。「3」は、既に、Storage Client 2.1.0.4 以降での Cast問題の修正で、Client Library 2.1.0.4、3.xで修正確認ができているので、ここでは1と2を確認します。

  1. SASとコンテナの前の// 問題
  2. TableのDataServiceContext.ResolveName 指定 問題
  3. Table Query での Cast 問題

SASとコンテナの前の “//” 問題

下記のようなプログラムで確認しました。Signitureの生成結果を確認しやすいように、2013/12/01から一年有効なREADのSASにしています。Storage Client 2.1.0.4 では、2012-02-12 versionが使われて、コンテナの前が"//"になっていても動きましたが、3.0.1だと、2013-08-15 versionが使われ、400 のエラーで弾かれるという結果になりました。もともと、"//"と書いたら"/"と解釈されるというのはあまりイケてない動きなので、古いバージョンのみ互換性を持つように変更して新しいものでは動作変更ということにしたのは妥当な落とし所かと思います。Storage Versionによって動作が違うようです。

private static string GetSASUrl(CloudStorageAccount storageAccount, string containerName, string blobName)
{
    var blobClient = storageAccount.CreateCloudBlobClient();

    var container = blobClient.GetContainerReference(containerName);

    var blockBlob = container.GetBlockBlobReference(blobName);

    var startDate = DateTime.Parse("2013/12/01");
    var sas = blockBlob.GetSharedAccessSignature(new SharedAccessBlobPolicy()
    {
        Permissions = SharedAccessBlobPermissions.Read,
        SharedAccessStartTime = startDate,
        SharedAccessExpiryTime = startDate.AddYears(1)
    });
    
    return blockBlob.Uri.ToString() + sas;
}

2.1.0.4での確認結果

Install-Package WindowsAzure.Storage -Version 2.1.0.4で2.1.0.4を入れます。何度も、違うバージョンのライブラリを入れ替えて使っていて、混乱したので念のためpackage.configの内容を併記します。

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.Data.Edm" version="5.2.0" targetFramework="net451" />
  <package id="Microsoft.Data.OData" version="5.2.0" targetFramework="net451" />
  <package id="Microsoft.WindowsAzure.ConfigurationManager" version="1.8.0.0" targetFramework="net451" />
  <package id="System.Spatial" version="5.2.0" targetFramework="net451" />
  <package id="WindowsAzure.Storage" version="2.1.0.4" targetFramework="net451" />
</packages>

生成されたURLで成功を確認

コンテナの前に、"/"を追加して確認、成功!

sv=2012-02-12になっていて、versionがわかります。

3.0.1での確認結果

Install-Package WindowsAzure.Storageで最新版、3.0.1で確認

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Microsoft.Data.Edm" version="5.6.0" targetFramework="net451" />
  <package id="Microsoft.Data.OData" version="5.6.0" targetFramework="net451" />
  <package id="Microsoft.Data.Services.Client" version="5.6.0" targetFramework="net451" />
  <package id="Microsoft.WindowsAzure.ConfigurationManager" version="1.8.0.0" targetFramework="net451" />
  <package id="Newtonsoft.Json" version="5.0.8" targetFramework="net451" />
  <package id="System.Spatial" version="5.6.0" targetFramework="net451" />
  <package id="WindowsAzure.Storage" version="3.0.1.0" targetFramework="net451" />
</packages>

生成されたURLで成功を確認

コンテナの前に、"/"を追加。これは失敗します。

レスポンスを見ると下記のようになっていました。(XMLは整形してあります)

HTTP/1.1 400 The requested URI does not represent any resource on the server.
Content-Length: 434
Content-Type: application/xml
Server: Microsoft-HTTPAPI/2.0
x-ms-request-id: a9275897-8810-48b2-bcd5-d45af85f6f14
Date: Fri, 27 Dec 2013 12:34:46 GMT

<?xml version="1.0" encoding="utf-8"?>
<Error>
  <Code>InvalidUri</Code>
  <Message>
    The requested URI does not represent any resource on the server.
    RequestId:a9275897-8810-48b2-bcd5-d45af85f6f14
    Time:2013-12-27T12:34:46.6851586Z
  </Message>
  <UriPath>
  http://test.blob.core.windows.net//images/photo.jpg?sv=2013-08-15&amp;sr=b&amp;sig=[signiture]&amp;se=2014-11-30T15:00:00Z&amp;sp=r
  </UriPath>
</Error>

TableのDataServiceContext.ResolveName 指定 問題

同様に、下記のようなコードを使って、2.1.0.4と、3.0.1で確認しました。 2.1.0.4 では、動作しましたが 3.0.1 では動きませんでした。結果はSASと似ているのですが、service側では処理が成功しているのでちょっと違った感じを受けます。

private static void ResolveName(CloudStorageAccount storageAccount)
{
    var cloudTableClient = storageAccount.CreateCloudTableClient();

    var table = cloudTableClient.GetTableReference("sometable");
    table.CreateIfNotExists();

    var tableServiceContext = cloudTableClient.GetTableServiceContext();
    tableServiceContext.ResolveName = entityType => entityType.FullName;

    var entity = new SimpleEntity("somePK", "someRK2");
    tableServiceContext.AddObject("sometable", entity);
    tableServiceContext.SaveChanges();
}

2.1.0.4での確認結果

普通に動いて成功しました。リクエストを見ると、categoryの属性に、term="StorageIssue201311.SimpleEntity"と有りますが、正常に処理されてレスポンスが帰ってきているのがわかります。

参考までに、リクエストとレスポンスを貼っておきます

リクエスト

POST http://test.table.core.windows.net/sometable HTTP/1.1
User-Agent: Microsoft ADO.NET Data Services
DataServiceVersion: 1.0;NetFx
MaxDataServiceVersion: 2.0;NetFx
x-ms-date: Fri, 27 Dec 2013 13:30:17 GMT
Authorization: SharedKeyLite [signiture]
x-ms-version: 2012-02-12
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
Content-Type: application/atom+xml
Host: test.table.core.windows.net
Content-Length: 735

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
  <category scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" term="StorageIssue201311.SimpleEntity" />
  <title />
  <author>
    <name />
  </author>
  <updated>2013-12-27T13:30:17.8428287Z</updated>
  <id />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey>somePK</d:PartitionKey>
      <d:RowKey>someRK7</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime">0001-01-01T00:00:00</d:Timestamp>
    </m:properties>
  </content>
</entry>

レスポンス

HTTP/1.1 201 Created
Cache-Control: no-cache
Transfer-Encoding: chunked
Content-Type: application/atom+xml;type=entry;charset=utf-8
ETag: W/"datetime'2013-12-27T13%3A30%3A08.6638521Z'"
Location: http://test.table.core.windows.net/sometable(PartitionKey='somePK',RowKey='someRK7')
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 765ae397-974c-4f3c-9d3a-d4e99bdcf9f5
x-ms-version: 2012-02-12
X-Content-Type-Options: nosniff
Date: Fri, 27 Dec 2013 13:30:08 GMT

3AE
<?xml version="1.0" encoding="utf-8"?><entry xml:base="http://test.table.core.windows.net/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:etag="W/&quot;datetime'2013-12-27T13%3A30%3A08.6638521Z'&quot;"><id>http://test.table.core.windows.net/sometable(PartitionKey='somePK',RowKey='someRK7')</id><category term="test.sometable" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><link rel="edit" title="sometable" href="sometable(PartitionKey='somePK',RowKey='someRK7')" /><title /><updated>2013-12-27T13:30:08Z</updated><author><name /></author><content type="application/xml"><m:properties><d:PartitionKey>somePK</d:PartitionKey><d:RowKey>someRK7</d:RowKey><d:Timestamp m:type="Edm.DateTime">2013-12-27T13:30:08.6638521Z</d:Timestamp></m:properties></content></entry>
0



3.0.1での確認結果

Install-Package WindowsAzure.Storageとして最新版、3.0.1で確認したところ、下記のようにエラーになりました。データ自体は、Tableに入っており正常終了していますが、レスポンスを読み込んでエンティティを更新するのに失敗しているようです。

ハンドルされていない例外: System.Data.Services.Client.DataServiceRequestException: この要求の処理中にエラーが発生しました。 ---> System.InvalidOperationException: メタデータ URI 'http://fooomiimg001.table.core.windows.net/$metadata#sometable/@Element' は 'fooomiimg001.sometable' という名前のエンティティ型を参照していますが、予期されたエンティティ型の名前は 'StorageIssue201311.SimpleEntity' で、'fooomiimg001.sometable' という名前のエンティティ型と互換性がありません。 ---> Microsoft.Data.OData.ODataException: メタデータ URI 'http://fooomiimg001.table.core.windows.net/$metadata#sometable/@Element' は 'fooomiimg001.sometable' という名前のエンティティ型を参照していますが、予期されたエンティティ型の名前は 'StorageIssue201311.SimpleEntity' で、'fooomiimg001.sometable' という名前のエンティティ型と互換性がありません。
   場所 Microsoft.Data.OData.ReaderValidationUtils.ValidateFeedOrEntryMetadataUri(ODataJsonLightMetadataUriParseResult metadataUriParseResult, Scope scope)
   場所 Microsoft.Data.OData.JsonLight.ODataJsonLightReader.ReadAtStartImplementationSynchronously(DuplicatePropertyNamesChecker duplicatePropertyNamesChecker)
   場所 Microsoft.Data.OData.JsonLight.ODataJsonLightReader.ReadAtStartImplementation()
   場所 Microsoft.Data.OData.ODataReaderCore.ReadImplementation()
   場所 Microsoft.Data.OData.ODataReaderCore.ReadSynchronously()
   場所 Microsoft.Data.OData.ODataReaderCore.InterceptException[T](Func`1 action)
   場所 Microsoft.Data.OData.ODataReaderCore.Read()
   場所 System.Data.Services.Client.Materialization.ODataReaderWrapper.Read()
   場所 System.Data.Services.Client.Materialization.FeedAndEntryMaterializerAdapter.TryRead()
   --- 内部例外スタック トレースの終わり ---
   場所 System.Data.Services.Client.Materialization.FeedAndEntryMaterializerAdapter.TryRead()
   場所 System.Data.Services.Client.Materialization.FeedAndEntryMaterializerAdapter.TryStartReadFeedOrEntry()
   場所 System.Data.Services.Client.Materialization.FeedAndEntryMaterializerAdapter.TryReadFeedOrEntry(Boolean lazy, ODataFeed& feed, MaterializerEntry& entry)
   場所 System.Data.Services.Client.Materialization.FeedAndEntryMaterializerAdapter.Read()
   場所 System.Data.Services.Client.Materialization.ODataReaderEntityMaterializer.ParseSingleEntityPayload(IODataResponseMessage message, ResponseInfo responseInfo, Type expectedType)
   場所 System.Data.Services.Client.SaveResult.HandleOperationResponseData(IODataResponseMessage responseMsg, Stream responseStream)
   --- 内部例外スタック トレースの終わり ---
   場所 System.Data.Services.Client.SaveResult.HandleResponse()
   場所 System.Data.Services.Client.BaseSaveResult.EndRequest()
   場所 System.Data.Services.Client.DataServiceContext.SaveChanges(SaveChangesOptions options)
   場所 System.Data.Services.Client.DataServiceContext.SaveChanges()
   場所 StorageIssue201311.Program.ResolveName(CloudStorageAccount storageAccount) 場所 c:\Users\Takekazu\Documents\GitHub\sandbox\csharp\StorageIssue201311\StorageIssue201311\Program.cs:行 52
   場所 StorageIssue201311.Program.Main(String[] args) 場所 c:\Users\Takekazu\Documents\GitHub\sandbox\csharp\StorageIssue201311\StorageIssue201311\Program.cs:行 59







参考までに、リクエストとレスポンスを貼っておきます。これを見ると、x-ms-version: 2013-08-15 で、payloadは、Content-Type: application/json;odata=minimalmetadataになっていますが、Prefer: return-no-contentが指定されておらず、レスポンスのBodyにechoが帰ってきているのがわかります。

リクエスト

POST http://test.table.core.windows.net/sometable HTTP/1.1
DataServiceVersion: 3.0;NetFx
MaxDataServiceVersion: 3.0;NetFx
Accept: application/json;odata=minimalmetadata
Accept-Charset: UTF-8
User-Agent: Microsoft ADO.NET Data Services
x-ms-date: Fri, 27 Dec 2013 13:07:55 GMT
Authorization: SharedKeyLite [signiture]
x-ms-version: 2013-08-15
Content-Type: application/json;odata=minimalmetadata
Host: test.table.core.windows.net
Content-Length: 125

{"odata.type":"StorageIssue201311.SimpleEntity","PartitionKey":"somePK","RowKey":"someRK6","Timestamp":"0001-01-01T00:00:00"}

レスポンス

HTTP/1.1 201 Created
Cache-Control: no-cache
Transfer-Encoding: chunked
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
ETag: W/"datetime'2013-12-27T13%3A07%3A45.3753816Z'"
Location: http://test.table.core.windows.net/sometable(PartitionKey='somePK',RowKey='someRK6')
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: b1797f33-1888-487a-a0cb-0a454fca1356
x-ms-version: 2013-08-15
X-Content-Type-Options: nosniff
Date: Fri, 27 Dec 2013 13:07:45 GMT

B2
{"odata.metadata":"http://test.table.core.windows.net/$metadata#sometable/@Element","PartitionKey":"somePK","RowKey":"someRK6","Timestamp":"2013-12-27T13:07:45.3753816Z"}
0


まとめ

Windows Azure Storage Known Issues 2013/11で報告されている既知の Braking Change は、2.1.0.4 の修正と、サーバー側(service)の修正でFIXされました。ただし、SASとコンテナの前の “//” 問題は、2013-08-15 version では仕様となり、TableのDataServiceContext.ResolveName 指定 問題も、Storage Client 3.0.1 では、ResolveNameの指定をすると動作しません。 最新のライブラリを使う場合はコードを直して欲しいということだと思います。

2.1系のライブラリを使う場合は上記3点の問題がFIXされた 2.1.0.4 がお勧めです。3.x系は、幾つかのBraking Changeが含まれるので既存のコードは移行が必要ですが、2013-08-15 versionのパフォーマンス向上策がちゃんと活用できるのが大きな利点だと言えます。



[1]日本語訳Windows Azure ストレージの既知の問題
[2]gitbugのWindowsAzure/azure-sdk-for-netレポジトリのmasterには、Storage Clientのコードは既にありません。2.1.0.4を確認するには、tag:v2.1.0.4 Storageを見て下さい。
[3]3.x github
]]>
Fri, 27 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/22/azure_posh_2012_12_19_version_0_7_2_1.html http://kyrt.in/2013/12/22/azure_posh_2012_12_19_version_0_7_2_1.html <![CDATA[Windows Azure Powershell 0.7.2.1 リリース]]> Windows Azure Powershell 0.7.2.1 リリース

Windows Azure Powershello 0.7.2.1 Hotfix がリリースされました。Windows Azure PowerShell 0.7.2 リリースのHotfix です。

修正: Hive query “%” が含まれた場合のエンコーディングの問題をFIX ChangeLog.txt

beans persimmon by Takekazu Omi, on Flickr

インストール

Web Platform Installer を使うと最新版が入ります。12/18 リリースのWindows Azure Poershell を選択してください。

../../../_images/2013_12_22_webpi001.png

インストールしたら念のためバージョンを確認します。Azureの所が、0.7.2 のママです orz....

$ Get-Module | ft name,version

Name                                                        Version
----                                                        -------
Autoload                                                    0.0
Azure                                                       0.7.2
Microsoft.PowerShell.Management                             3.1.0.0
Microsoft.PowerShell.Utility                                3.1.0.0
posh-git                                                    0.0
PsEnv                                                       0.0
PSReadline                                                  1.0.0.1

今回のリリースでは、ModuleVersion が変更されていません。Azure.psd1#L15要注意です。

確認のためインストール先のアセンブリを見ます。

手元の環境では、C:Program Files (x86)Microsoft SDKsWindows AzurePowerShellAzureにインストールされています。 Explorer で、Microsoft.WindowsAzure.Commands.dllのプロパティを確認したら下記のようになっていました。0.7.2.1になっているようです。

../../../_images/2013_12_22_asm001.png

最後に

更新だけする場合は、Web Platform Installer を起動して新しいものが出ているかどうかを確認するのが一番簡単です。

この方法だと、現在入っているバージョンは分からないので、現在入っているバージョンを知りたければ、Get-Module | ft name,versionして、0.7.2 だったら、アセンプリのバージョンを見て0.7.2.1かどうかの確認をするということになります。

ちょっと面倒ですね。

2013/12/23 追記

ModuleVersionが更新されていない件について、GitHubにIssueを上げました。

2014/1/8 追記

Issueに「0.7.3 で直すよ」と返事をもらいましたThis will be fixed in 0.7.3まだ、0.x ですし、バージョン番号を変更するためにリリースするのもどうかと思うので妥当な判断だと思います。

ちなみに、バージョン番号は、Semantic Versioningに沿って付けられているそうです。nugetでは、Versioningは、semverなので揃える方向性なんでしょうか、開発者的には分かりやすくなって嬉しいですね。

]]>
Sun, 22 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/21/windows_azure_storage_client_library_for_c_preview.html http://kyrt.in/2013/12/21/windows_azure_storage_client_library_for_c_preview.html <![CDATA[Windows Azure Storage Client Library for C++ Preview]]> Windows Azure Storage Client Library for C++ Preview

Windows Azure Storage Client Library for C++ Preview がリリースされました

待望のC++用のライブラリです。

以下にざっくりと訳します。

抄訳 Windows Azure Storage Client Library for C++ Preview

これは、Preview release です、まだ puroduction codeでは使用しないでください(should not be used in your production code)。 その代わり、ざっと目を通して試してみて、あなたがGAに必要だと考える拡張・変更のフィードバックを下さい。

Windows Azure Storage に付いての詳しい情報は、SOSP PaperWindows Azure Storage: A Highly Available Cloud Storage Service with Strong Consistency[1]を参照してください。

Emulator Guidance

このライブラリが使う2013-08-15 RESTは、現在のAzure SDK(2.2)のStorage Emulatorでサポートされていません。これらの全ての機能をサポートしたAzure Storage Emulatorのアップデートを来月には出せる見込みです。(An updated Windows Azure Storage Emulator is expected to ship with full support of these new features in the next month)Storage Emulatorの現在のバージョンを使って開発しようとすると不正な要求エラーを受け取ることになります。アップデートが出るまでは、新機能を使用したいユーザーは、Windows Azure Storage Account (本番環境)を使ってテストをして下さい。[2]

サポートされるプラットフォーム

今回のリリースでは、Visual Studio 2012 (v110) と Visual Studio 2013 (v120) platform toolsets 用ライブラリの x64およびx86バージョンを提供します。パッケージには、8 build flavors が含まれます。

  1. Release, x64, v120
  2. Debug, x64, v120
  3. Release, Win32, v120
  4. Debug, Win32, v120
  5. Release, x64, v110
  6. Debug, x64, v110
  7. Release, Win32, v110
  8. Debug, Win32, v110

入手方法

ライブラリは、NuGetから、完全なソースコードはGitHubからダウンロードできます。NuGet packageは、CoApp toolsを使って作成され、3つのpackage として構成されています。

  • wastorage.0.2.0-preview.nupkg:このパッケージには、アプリケーションの開発に必要なヘッダーとLIBファイルが含まれています。これは、再配布(redist) package に依存します。NuGet は、自動的にredist packageのインストールを行います。
  • wastorage.redist.0.2.0-preview.nupkg:このパッケージを実行し、アプリケーションを再配布するために必要なDLLファイルが含まれています。
  • wastorage.symbols.0.2.0-preview.nupkg:このパッケージには、それぞれのDLLファイルのシンボルが含まれています。オプションのパッケージです。

パッケージは、C++ REST SDKに依存しており、それもNuGetで自動的にインストールされます。C++ REST SDK (codename “Casablanca”)は、native code の cloud-based client-server communication のための Microsoft のプロジェクトです。これは、native code の REST services アクセスを、multiple platforms で非同期な HTTP, JSON, URIs の C++ bindings として提供します。Windows Azure Storage Client Library では、このライブラリを Windows Azure Storage Blob, Queue, Table services との通信で利用しています。

できること

ここでは、REST API に付いて話をする代わりに、Windows Azure Storage Client Library の概要を説明します。

  • Windows Azure Storage REST API version2013-08-15全体を簡単に使えるようにする実装
  • Retry policies: リクエストが失敗した場合の再試行ロジックとして、exponential や linear back off algorithm を実装
  • Streamlined authentication model: 共有鍵と共有認証署名の両方をサポート
  • 操作コンテキストとETWのログを使用して要求の詳細と結果に潜り込む能力
  • サイズや、Blob typeに関係の無い、ユーザー設定による、parallel block/page アップロード
  • 特定のupload/download APIに対応しなくても、読み取りまたはBLOBへの書き込みを許可するBLOB Stream
  • すべてのBlob uploadおよびdownload での完全なMD5のサポート
  • Table layerでは、Azure Storage Table の新しいJSON サポートを利用
  • Table service の、Entity Group Transaction サポート。(Entity Group Transactionでは、単一トランザクションで複数操作が可能)

Read Access Geo Redundant Storage (RA-GRS) サポート

このリリースでは、storage account のsecondary region データへの読込アクセスをフルサポートしています。 この機能は、Portalで有効にしないと利用できません、詳しくは、RA-GRSを参照[3]して下さい。

How to use it?

NuGet package を入れると、was(Windows Azure Storageの略) のフォルダーに全てのヘッダー が入ります。 このディレクトリでは下記のheader ファイルが重要です。

  • blob.h: Blob service 関連の全ての宣言
  • queue.h: Queue service 関連の全ての宣言
  • table.h: Table service 関連の全ての宣言
  • storage_account.h: account の name/key 、あるいは connection string から、service client のオブジェクトを簡単に作成するための cloud_storage_account type の宣言
  • retry_policies.h: 全ての操作で使われる、幾つかの retry policies の宣言

下記のように使います

#include "was/storage_account.h"
#include "was/queue.h"
#include "was/table.h"
#include "was/blob.h"

その後、我々は作成されますcloud_storage_accountコードの後半でサービス·クライアント·オブジェクトを作成するために私達を可能にするオブジェクトを、。私たちは安全な接続のために以下のHTTPSを使用しているが、アプリケーションをデバッグするときに、HTTPは非常に便利であることに注意してください。

その後、下記のコードでcloud_storage_account オブジェクトを作成します。 ここでは、セキュアな接続のため https を使っていますが、デバックする場合は http が非常に便利です。

wa::storage::cloud_storage_account storage_account = wa::storage::cloud_storage_account::parse(U("AccountName=<account_name>;AccountKey=<account_key>;DefaultEndpointsProtocol=https"));

Blobs

ここでは、blob container を作成し、“some text” と入ったblobを作って、それを download 、そして container 内のblobをリストします。

// Create a blob container
wa::storage::cloud_blob_client blob_client = storage_account.create_cloud_blob_client();
wa::storage::cloud_blob_container container = blob_client.get_container_reference(U("mycontainer"));
container.create_if_not_exists();

// Upload a blob
wa::storage::cloud_block_blob blob1 = container.get_block_blob_reference(U("myblob"));
blob1.upload_text(U("some text"));

// Download a blob
wa::storage::cloud_block_blob blob2 = container.get_block_blob_reference(U("myblob"));
utility::string_t text = blob2.download_text();

// List blobs
wa::storage::blob_result_segment blobs = container.list_blobs_segmented(wa::storage::blob_continuation_token());

Tables

以下のサンプルでは、 Tableを作成し、様々な型のプロパティの組をエンティティに挿入し、最終的にはその指定したエンティティを取得します。最初の検索操作では、ポイント·クエリを実行し、特定のエンティティを取得します。一方クエリ操作では、PartitionKeyが“partition”に等しく、かつ、RowKey が、“m”より大きいすべてのエンティティを照会します。これは、結果的にinsertした全てのエンティティになります。

Windows Azure Tables に関する詳しい情報は、Understanding the Table Service Data Modelの記事と、How to get most out of Windows Azure Tablesの blog post を見て下さい。

// Create a table
wa::storage::cloud_table_client table_client = storage_account.create_cloud_table_client();
wa::storage::cloud_table table = table_client.get_table_reference(U("mytable"));
table.create_if_not_exists();

// Insert a table entity
wa::storage::table_entity entity(U("partition"), U("row"));
entity.properties().insert(wa::storage::table_entity::property_type(U("PropertyA"), wa::storage::table_entity_property(U("some string"))));
entity.properties().insert(wa::storage::table_entity::property_type(U("PropertyB"), wa::storage::table_entity_property(utility::datetime::utc_now())));
entity.properties().insert(wa::storage::table_entity::property_type(U("PropertyC"), wa::storage::table_entity_property(utility::new_uuid())));
wa::storage::table_operation operation1 = wa::storage::table_operation::insert_or_replace_entity(entity);
wa::storage::table_result table_result = table.execute(operation1);

// Retrieve a table entity
wa::storage::table_operation operation2 = wa::storage::table_operation::retrieve_entity(U("partition"), U("row"));
wa::storage::table_result result = table.execute(operation2);

// Query table entities
wa::storage::table_query query;
query.set_filter_string(wa::storage::table_query::combine_filter_conditions(
    wa::storage::table_query::generate_filter_condition(U("PartitionKey"), wa::storage::query_comparison_operator::equal, U("partition")), 
    wa::storage::query_logical_operator::and, 
    wa::storage::table_query::generate_filter_condition(U("RowKey"), wa::storage::query_comparison_operator::greater_than_or_equal, U("m"))));
std::vector<wa::storage::table_entity> results = table.execute_query(query);

Queues

最後の例では、Queueを作成します、それにmessageを追加、同じmessageを取得し、最終的にそれを更新します。

// Create a queue
wa::storage::cloud_queue_client queue_client = storage_account.create_cloud_queue_client();
wa::storage::cloud_queue queue = queue_client.get_queue_reference(U("myqueue"));
queue.create_if_not_exists();

// Add a queue message
wa::storage::cloud_queue_message message1(U("mymessage"));
queue.add_message(message1);

// Get a queue message
wa::storage::cloud_queue_message message2 = queue.get_message();

// Update a queue message
message2.set_content(U("changedmessage"));
queue.update_message(message2, std::chrono::seconds(30), true);

デバック方法

なにか上手く行っていない場合、exception で帰ってきます。この exception は、wa::storage::storage_exception の型で、なにか問題なのかの詳細な情報を含んでいます。次のコードを見て下さい。

try
{
    blob1.download_attributes();
}
catch (const wa::storage::storage_exception& e)
{
    std::cout << "Exception: " << e.what() << std::endl;
    ucout << U("The request that started at ") << e.result().start_time().to_string() << U(" and ended at ") << e.result().end_time().to_string() << U(" resulted in HTTP status code ") << e.result().http_status_code() << U(" and the request ID reported by the server was ") << e.result().service_request_id() << std::endl;
}

blobが存在しない場合, このコードは下記のような結果になります。

Exception: The specified blob does not exist.

The request that started at Fri, 13 Dec 2013 18:31:11 GMT and ended at Fri, 13 Dec 2013 18:31:11 GMT resulted in HTTP status code 404 and the request ID reported by the server was 5de65ae4-9a71-4b1d-9c99-cc4225e714c6

ライブラリでは、type wa::storage::operation_context を提供しています。これは、全てのAPIでサポートされ、操作中に行われていることについてより多くの情報を取得するために使います。次のコードを見て下さい。

wa::storage::operation_context context; 

context.set_sending_request([] (web::http::http_request& request, wa::storage::operation_context) 
{ 
  ucout << U("The request is being sent to ") << request.request_uri().to_string() << std::endl; 
});

context.set_response_received([] (web::http::http_request&, const web::http::http_response& response, wa::storage::operation_context) 
{ 
  ucout << U("The reason phrase is ") << response.reason_phrase() << std::endl; 
});

try 
{ 
  blob1.download_attributes(wa::storage::access_condition(), wa::storage::blob_request_options(), context); 
} 
catch (const wa::storage::storage_exception& e) 
{ 
  std::cout << "Exception: " << e.what() << std::endl; 
} 

ucout << U("Executed ") << context.request_results().size() << U(" request(s) to perform this operation and the last request's status code was ") << context.request_results().back().http_status_code() << std::endl;

もう一度、blobが存在しない状況で動かします。すると下記のような結果になります。

wa::storage::operation_context context; 

context.set_sending_request([] (web::http::http_request& request, wa::storage::operation_context) 
{ 
  ucout << U("The request is being sent to ") << request.request_uri().to_string() << std::endl; 
});

context.set_response_received([] (web::http::http_request&, const web::http::http_response& response, wa::storage::operation_context) 
{ 
  ucout << U("The reason phrase is ") << response.reason_phrase() << std::endl; 
});

try 
{ 
  blob1.download_attributes(wa::storage::access_condition(), wa::storage::blob_request_options(), context); 
} 
catch (const wa::storage::storage_exception& e) 
{ 
  std::cout << "Exception: " << e.what() << std::endl; 
} 

ucout << U("Executed ") << context.request_results().size() << U(" request(s) to perform this operation and the last request's status code was ") << context.request_results().back().http_status_code() << std::endl;

Samples

github 上のプロジェクトのサンプルフォルダの中に、幾つかの重要なシナリを説明する コードがあります。

Visual Studioで「Microsoft.WindowsAzure.Storage.Samples.sln」という名前のサンプルのソリューションファイルを開きます。Microsoft.WindowsAzure.Storage.SamplesCommonプロジェクトの下samples_common.hファイルの、ストレージのアカウント情報を更新します。Solution Explorer のウィンドウに移動し、(例えば、Microsoft.WindowsAzure.Storage.BlobsGettingStarted)実行したいサンプルプロジェクトを選択してStartUp Projectにするか、コンテキストメニューから実行します。

Summary

コメント欄 (原文)、フォーラム、あるいはGitHubでのフィードバックをお待ちしています。BUGに当たった場合、GitHubに上げてもらえると、その後の解決策などをトラックすることができるようになります。

Serdar Ozler, Mike Fisher, and Joe Giardino

まとめ

C++用のクライアントは良いですね、やっぱり欲しい。

その他に、今回CoApp toolsが気になりました。native code をnugetでpackege化して配布するプロジェクトです。 Windows にapt-getみたいな位置づけに機能を盛り込むのを狙っているようで、期待できます。[4]これで、python、ruby、node.js で Windows パッケージのバイナリコードのビルドに引っかかる問題も、このレポジトリを参照することで解決できると良いなあと思いますが、今のところプロジェクトのターゲットに入っていないようで、そこはちょっと残念です。

もう一つ、http://casablanca.codeplex.com/は、MSDN C++ REST SDK (Codename “Casablanca”)に割りとしっかりしたドキュメントが上がっていて、nugetでは、production 扱いなので、使っても良さそうな感じです。C++ REST SDK 1.4.0今年の4月からパッケージとしては上がっていたようですが、気が付きませんでした。

nugetを見てみたところ、C++ REST SDK も、SDK、 Redist、Symbols の3本立てになってました。でも、SDKが1.4.0なのに他が1.3.1のままですね。

パッケージマネージャー関連は、nuget を中心に、整備が進んでいるようで、いろいろ期待できますね。



[1]日本語訳SOSP 論文 Windows Azure ストレージ: 高可用性と強い一貫を両立する クラウド ストレージ サービス
[2]Windows Azure Storage Client Library for Java v. 0.5.0では、An updated Windows Azure Storage Emulator is expected to ship with full support of these new features in the next couple of months.になっていました。数ヶ月先って話だったので、来月だとすると良い知らせですね。
[3]日本語訳Windows Azure ストレージの冗長オプションと読み取りアクセス地理冗長ストレージ
[4]The State of CoApp
]]>
Sat, 21 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/20/windows_azure_storage_client_library_for_java_v_0_5_0.html http://kyrt.in/2013/12/20/windows_azure_storage_client_library_for_java_v_0_5_0.html <![CDATA[Windows Azure Storage Client Library for Java v. 0.5.0]]> Windows Azure Storage Client Library for Java v. 0.5.0

Windows Azure Storage Client Library for Java v. 0.5.0 がリリースされました

2013/12/27 Windows Azure Japan Team Blog 公式 谷訳出ました。 Java v. 0.5.0 用 Windows Azure ストレージ クライアント ライブラリ

以下にざっくりと訳します。

抄訳 Windows Azure Storage Team Blog - Windows Azure Storage Client Library for Java v. 0.5.0

このリリースでは、logging support, 新しい API overloads、そして 2013-08-15 REST[1]が完全にサポートされています。今までのようにソースコードは、github Windows Azure Storage libraries for Javaで公開されています。[2]従来のようにmaven のレポジトリに登録されています。

<dependency>  
     <groupId>com.microsoft.windowsazure.storage</groupId>
     <artifactId>microsoft-windowsazure-storage-sdk</artifactId>
     <version>0.5.0</version>
</dependency>

エミュレータ ガイダンス

現在のAzure SDK(2.2)のエミュレータは、2013-08-15 RESTをサポートしていません。今後数ヶ月以内にサポートしたものを出す予定です。

Samples

githubのソースのなかに、幾つかの重要なシナリを説明するサンプルがあります。指定されたサンプルを実行するには、サンプルプロジェクトをロードし、ストレージの資格情報を提供するために、Utility.javaの次の行を更新します。

public static final String storageConnectionString = "DefaultEndpointsProtocol=http;AccountName=[ACCOUNT_NAME];AccountKey=[ACCOUNT_KEY]";

fiddler を使ってサンプル実行中の traffic を見たい場合は、Utility.java の下記の2行をコメントアウトを外してください。

// System.setProperty("http.proxyHost", "localhost");
// System.setProperty("http.proxyPort", "8888");

Note about Packaging and Versioning

このリリースで、大きな Windows Azure SDK for Java から、ストレージパッケージを独立させました。現在、既存のSDKを活用している開発者は、それに応じて依存関係を更新する必要があります。さらに、パッケージ名は、この新しい構造を反映するように変更されました:

  • com.microsoft.windowsazure.storage - RetryPolicies、LocationMode、StorageException、StorageAccountなどサービス間で共通しているすべてのpublicクラス
  • com.microsoft.windowsazure.storage.blob - Blob convenience implementation、Windows Azure Blobを利用しているアプリケーションは、importしてください。
  • com.microsoft.windowsazure.storage.queue - Queue convenience implementation、Windows Azure Queueを利用するアプリケーションは、importしてください。
  • com.microsoft.windowsazure.storage.table - Table convenience implementation、Windows Azure Tableを利用しているアプリケーションは、importしてください。

さらに詳しい情報は、以下の Change Log & Breaking Changes のセクションを見て下さい。

また、Storage Client SDK componentでは、Semantic Versioningを採用します。これは、SDKを活用する開発者に一貫した予測可能なバージョン管理指針を提供するのに役立ちます。

Whats New

Java client library の 0.5.0 では、2013-08-15 REST service version の全ての機能[1]をサポートします。

Support for Read Access Geo Redundant Storage

このリリースでは、secondary region の storage account data の読み取りアクセス (RA-GRS)[3]を完全にサポートしています。クライアントオブジェクト上の位置モードを設定し、getServiceStatsを起動すると、以下の例に示されている。LocationModeもRequestOptionsオブジェクト上に設定することで、リクエストごとに設定することができます。

下記の例では、client objectの location mode を設定して getServiceStats を呼び出すことで、 replication の状態を取得しています。

CloudStorageAccount httpAcc = CloudStorageAccount.parse(connectionString);
CloudTableClient tClient = httpAcc.createCloudTableClient();

// Set the LocationMode to SECONDARY_ONLY since getServiceStats is supported only on the secondary endpoints.
tClient.setLocationMode(LocationMode.SECONDARY_ONLY);
ServiceStats stats = tClient.getServiceStats();
Date lastSyncTime = stats.getGeoReplication().getLastSyncTime();

System.out.println(String.format("Replication status = %s and LastSyncTime = %s",stats.getGeoReplication().getStatus().toString(), lastSyncTime != null ? lastSyncTime.toString(): "empty"));

Expanded Table Protocol Support (JSON)

table traffic で、JSONがサポートされました。TableClient の setTablePayloadFormat() で、TablePayloadFormatを指定することでJSONformatを使うことができます。
CloudStorageAccount httpAcc = CloudStorageAccount.parse(connectionString);
CloudTableClient tableClient = httpAcc.createCloudTableClient();

// Set the payload format to JsonNoMetadata.
tableClient.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata);

JsonNoMetadataを指定した場合メタデータがpayloadに有りません、その場合プロパティの型はpropertyResolver を用意して下記のようにコードで解釈します。

public static class Class1 extends TableServiceEntity implements PropertyResolver {    
    private String A;
    private byte[] B;

    public String getA() {
	return this.A;
    }
    
    public byte[] getB() {
	return this.B;
    }
    
    public void setA(final String a) {
	this.A = a;
    }
    
    public void setB(final byte[] b) {
	this.B = b;
    }
    
    @Override
    public EdmType propertyResolver(String pk, String rk, String key, String value) {
	if (key.equals("A")) {
	    return EdmType.STRING;
	}
	else if (key.equals("B")) {
	    return EdmType.BINARY;
	}
	return null;
    }
    
}

この、propertyResolver は、TableRequestOptions にセットします。

Class1 ref = new Class1();
ref.setA("myPropVal");
ref.setB(new byte[] { 0, 1, 2 });
ref.setPartitionKey("testKey");
ref.setRowKey(UUID.randomUUID().toString());

options.setPropertyResolver(ref);

Table Insert Optimizations

以前のバージョンでは、Table Insertのレスポンスでentity全体が返されていましたが、このバージョンから inserts が成功したときは 204 (no-content) でbodyは空がかえるようになりました。従来と同じように動作させるには、insert(TableEntity, boolean) method に true を指定します。

Table Reflection Optimizations

clients が、POJOを永続化するときに、繰り返しの reflection calls を無くすために、type や property information をcacheするようにしました。この最適化の結果劇的にCPU時間が削減されます。このCache機能を無効にするには、TableServiceEntity.setReflectedEntityCacheDisabled(true) を使って下さい。

New APIs and overloads

customer feedback から下記のAPIを追加しました、

  • CloudBlob.downloadRange
  • CloudBlob.downloadToByteArray
  • CloudBlob.downloadRangeToByteArray
  • CloudBlob.uploadFromByteArray
  • CloudBlob.uploadFromByteArray
  • CloudBlob.downloadToFile
  • CloudBlob.uploadFromFile
  • CloudBlockBlob.uploadText
  • CloudBlockBlob.downloadText

Logging

0.5.0 リリースでは、SLF4Jlogging facade をサポートしています。

詳細省略

Change Log & Breaking Changes

このリリースには、 2013-08-15 REST[1]のサポート以外にも幾つかの重要な変更があります。要点を下記にまとめます。

Common

  • Package の再構築
  • RetryResultが追加機能を提供しRetryInfoに置き換えられています
  • request中に起きるevent operations (event firingを含む) は、同期では無くなりました。 (thread safety は、event listenersのCopyOnWriteArrayList で保証されます)
  • OperationContext.sendingRequest eventは、 headerを変更することができるように connection が established されるまえに fireされます

Blob

  • BlobdownloadRangeはStreamにダウンロードします。以前のdownloadRangeはdownloadRangeToByteArrayに変更されました
  • sparse page blob feature は削除されました
  • CloudBlobContainer.createIfNotExistはCloudBlobContainer.createIfNotExistに改名されました
  • CloudBlobClient.streamMinimumReadSizeInBytesは削除されました。この機能は、はCloudBlob.streamMinimumReadSizeInBytesで提供さえます(これは、Client毎のではなくBlob毎の設定です)
  • CloudBlobClient.pageBlobStreamWriteSizeInBytesとCloudBlobClient.writeBlockSizeInBytesは削除されました。この機能は、CloudBlob.streamWriteSizeInBytesで提供されます

Table

  • TableResultから id field が削除されました(getId、setIdも)
  • CloudTable.createIfNotExistはCloudTable.createIfNotExistに名前が変更されました
  • Insert 操作はcontents echoをしません。
  • デフォルトのPayload形式は、JsonMinimalMetadataになりました。CloudTableClient.setTablePayloadFormatで全てのリクエスト、あるいは個々のリクエストでTableRequestOptions.setTablePayloadFormatを使ってpayload formatを変更することができます

Queue

  • CloudQueue.createIfNotExistはCloudQueue.createIfNotExistに名前が変更されました

最後に

0.4 にあった、Azure SDK for Java 0.4.6 long値のfilter BUGは無事改修されているようです。https://github.com/WindowsAzure/azure-storage-java/commit/834daae4e23b51e16e65e5bbf13096075d1f47ca#diff-f8d410db4741b0e56a8050948b11115c

互換性の問題などから、0.4を使い続ける場合は引き続き注意が必要です。


[1](1, 2, 3) version 2013-08-15 storage英語日本語SATO NAOKI訳
[2]0.4 までは、azure-sdk-for-javaだったのですが、レポジトリが変わり。 0.5からは、azure-storage-javaになりました。.NETのStorage Clientと同じような名前変更がされたようです。
[3]Windows Azure ストレージの冗長オプションと読み取りアクセス地理冗長ストレージ
]]>
Fri, 20 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/17/owin_azure_cache_session_middleware.html http://kyrt.in/2013/12/17/owin_azure_cache_session_middleware.html <![CDATA[OWIN - Open Web Interface for .NET を使う]]> OWIN - Open Web Interface for .NET を使う

OWIN(Open Web Interface for .NET)は、.NET Framework の WebサーバとWebアプリケーション接続するためのインタフェースであり、新しい HTTP Serverのプログラミング抽象化レイヤーを定義するものだ。2010年の終わりのころにBenjamin van der Veen 氏が始め、Draft 7 12 July 2012では、Author: OWIN working group となっている。参照:http://owin.org/spec/history-1.0.html

arch by Takekazu Omi, on Flickr

.NETでは、HTTP Serverのプログラミング抽象化レイヤーは、ASP.NETの初期のことに構築されてその後ほぼ変らずに今まで来た、An Overview of Project Katana August 30, 2013 Howard Dierkingに、初めのころの話としてASP.NET設計時のターゲットの話が書いあり実に面白い。

これによると、当時(ASP.NETの初期設計時)の主なターゲットは、「Classic ASPを使っている人」と、「VB6等でWindows で業務アプリを書いている人」にWebプラットフォームプログラミングを提供することで、.NET Frameworkの一部としてリリースされるということもありあまり時間も無い中で作られたらしい。

その結果出来上がったのが、従来のVB6アプリの習慣に沿ったイベントモデルをベースにしたWeb Formsのアーキテクチャーと論理的に異なる HTTP ObjectとWeb Forms FrameworkがタイトにカップルされたSystem.Web.dll らしい。

昔を振り返ってみると、1993年の終わりころ[1]UCSA HTTPdにCGI が現れ、1996年には Windows NT 4.0 Option Pack で ASPが登場、1997年には、Java Servlet が出ている。ASP.NETのリリースは2002年なので、ASPのリリースから6年たってほぼ同じモデルを踏襲した設計になっているということになる。今は更に11年後、ASPから数えると17年経ってる、変わらないのは資産の継承という点では良い面もあるが、その間蓄積された知識が十分生かされているかどうかとかんがえるとちょっと期間が長すぎたような気もする。

その間Rubyを始めとする他のプラットフォームは新しいデザインを模索しており、OWINの発想のもとになっていると言われているRack: a Ruby Webserver Interface Feb 2007が生まれる。こちらは(Ruby)は、.NET と事情が違って、標準的なWebサーバとWebアプリケーション接続するためのインタフェースが存在しない中数多くのWeb サーバー、フレームワークが存在する問題への解法としてRackが生まれている。

.NETでは最初に標準的なWeb Server(IIS)と、Framework(System.Web.dll)ありきで始まったため混乱は無かったが自由な発展が阻害され、一方Ruby/Pythonなど標準的なものが無い中では混沌のなかから優れた標準(Rack/WSGI)が生まれたというのは実に面白い。

OWINの基本

The Open Web Interface for .NET (OWIN) の主要なデータ構造は2つしか無い。ひとつは、環境を保持する environment dictionary これに、HTTP request and response を処理するのに必要なデータは保持される。

IDictionary<string, object>

2つ目は、application delegate 全てのコンポーネントの間は下記の function signature で呼ばれる。

Func<IDictionary<string, object>, Task>;

Headers、Request Body、Response Body などの抽象度の高いオブジェクトを、この上に構築している。ちょっと中を見てみた感じでは、OWIN自体は非常にシンプルな構成[2]でコンポーネント指向も高くいい感じで使えそうだ。とりあえずなにか、OWIN Middleware を作ってみようと思ったけどネタが思い付かない。どうしようかと思っていたら、neuecc/Owin.RedisSessionなんてものを見つけ「ああOWINだとSessionすら無いのか」と気が付いて Azure Cache 版を作ってみることにした。

OWIN Middleware Azure Cache Session

そんなわけで、Azure Cache に Session を保存するOWIN Middleware を作成した。コードはGitHubにあるOWIN Azure Cache Session Middleware手探りで作った習作だが、簡単に中身を説明する。OWIN Middleware を使って下記のような構成にする。Azure CacheとSessionのMiddlewareはブラウザとアプリケーションの間にフィルタのように入る。この手のパターンは便利でWeb Application Frameworkでは随所に出てくる。今回、CacheとSessionで分ける必要があるか迷ったが、書いてみたら分けた方がシンプルになったので分けてある。

OWIN SelfHost Applicationの作成

まずは試しで、OWIN Selft Host環境を作って書いてみる。

Console Projectを作成して必要なパッケージを入れる

nugetから必要なパッケージを入れる

install-package Microsoft.Owin.Hosting
install-package Microsoft.Owin.Host.HttpListener
install-package Microsoft.Owin.Diagnostics
install-Package Owin.Extensions

Program.csのmainを下記のようにする

WebApp.Start<Startup>(url)とするとlistnerを上げて、Startup Classにリクエストを回してくれる。今回の設定だと、Microsoft.Owin.Host.HttpListener が入っているのでHttpListnerを使ったSelfHostになる。

using System;
using Microsoft.Owin.Hosting;

namespace SelfHostSample
{
    class Program
    {
        static void Main(string[] args)
        {
            string uri = args.Length == 0 ? "http://localhost:8081/" : args[0];

            using (WebApp.Start<Startup>(uri))
            {
                Console.WriteLine("Started");
                Console.ReadKey();
                Console.WriteLine("Stopping");
            }
        }
    }
}

Startup用のクラスを追加する

[assembly: OwinStartup(typeof(SelfHostSample.Startup))]でアプリケーションのクラスを登録する。登録されたクラスのConfiguration MethodがWebApp.Start()で実行される。

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(SelfHostSample.Startup))]

namespace SelfHostSample
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseWelcomePage();
        }
    }
}

これで動かすとこんな感じになる

このコードでは、app.UseWelcomePage();としているので、Welcomeページが表示される。このあたりの実装は、katanaproject.codeplex.com WelcomePageMiddleware見ると非常に参考になる。

../../../_images/2013_12_08_scrn-001.png

Azure Cache Session Middleware

ざっと手順を説明して、コード上のポイントを解説する。例外処理周りなどは検討の余地が多い。

Windows Azure Cache Client を入れる

Install-Package Microsoft.WindowsAzure.Caching

App.config内のAzure Cacheの設定をする

App.config 内の configuration/dataCacheClients/dataCacheClient/autoDiscover のidentifier属性をAzure CacheのEndpointにして、securityProperties/messageSecurityのauthorizationInfo属性にManage Access Keys を設定する。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="dataCacheClients" type="Microsoft.ApplicationServer.Caching.DataCacheClientsSection, Microsoft.ApplicationServer.Caching.Core" allowLocation="true" allowDefinition="Everywhere" />
    <section name="cacheDiagnostics" type="Microsoft.ApplicationServer.Caching.AzureCommon.DiagnosticsConfigurationSection, Microsoft.ApplicationServer.Caching.AzureCommon" allowLocation="true" allowDefinition="Everywhere" />
  </configSections>
  <startup> 
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <dataCacheClients>
    <dataCacheClient name="default">
      <!--To use the in-role flavor of Windows Azure Cache, set identifier to be the cache cluster role name -->
      <!--To use the Windows Azure Cache Service, set identifier to be the endpoint of the cache cluster -->
      <autoDiscover isEnabled="true" identifier="********.cache.windows.net" />

      <!--<localCache isEnabled="true" sync="TimeoutBased" objectCount="100000" ttlValue="300" />-->
      
      <!--Use this section to specify security settings for connecting to your cache. This section is not required if your cache is hosted on a role that is a part of your cloud service. -->
      <securityProperties mode="Message" sslEnabled="false">
        <messageSecurity authorizationInfo="*************************" />
      </securityProperties>
    </dataCacheClient>
</dataCacheClients></configuration>

OWIN Middleware用のプロジェクトを追加する

今回は、Owin.Middleware という名前で作成して、Azure CacheとSessnionのMiddlewareを作成する。Owin.Middleware project必要なパッケージを追加する。このプロジェクトではOWIN Hosting系のパッケージは入れない。

install-Package Microsoft.Owin
install-Package Microsoft.WindowsAzure.Caching
install-Package EnterpriseLibrary.TransientFaultHandling.Caching

Azrue Cache Middleware

OWIN Middleware 定番クラスを3つ追加する。Middleware が処理の本体、Optionsは、Middlewareのオプション、Extensionsは拡張メソッドが入っている。今回は、素のOwinではなく、Microsoft.Owin を使っている。Microsoft.Owin は、Microsoftが作成したOwinの薄いラッパである程度型割り当て済みのデータを渡してくれるのでコーディングが楽になる。[3]

using System;
using System.Threading.Tasks;
using Microsoft.ApplicationServer.Caching;
using Microsoft.Owin;

namespace Owin.Middleware
{
    public class AzureCacheMiddleware : OwinMiddleware
    {
        public const string CacheKeyName = "Kyrt.CacheKeyName";

        private readonly AzureCacheOptions _options;

        public AzureCacheMiddleware(OwinMiddleware next, AzureCacheOptions options) : base(next)
        {
            _options = options ?? new AzureCacheOptions();
        }

        public override Task Invoke(IOwinContext context)
        {
            try
            {
                object cache;
                if (!context.Environment.TryGetValue(CacheKeyName, out cache))
                {
                    cache = new AzureCacheClient(_options.CacheName);
                    context.Environment[CacheKeyName] = cache;
                }
            }
            catch (DataCacheException e)
            {
                context.TraceOutput.WriteLine(e);
            }
            catch (Exception ex)
            {
                context.TraceOutput.WriteLine(ex);
                throw;
            }

            return Next.Invoke(context);
        }
    }
}

Middlewareの基本的な考えは非常に簡単で Invoke の処理内で次のInvokeの前後に割り込んで処理をするだけ。AzureCacheMiddlewareでは、AzureCacheClient(中身はDataCache)を作成してEnvironmentに追加している。このケースでは、Invokeの前に処理を入れただけで、Invoke後は何もしていない。

オプションや拡張メソッドのクラスはおまけのようになもので大したことはしていない。

namespace Owin.Middleware
{
    public class AzureCacheOptions
    {
        public AzureCacheOptions()
        {
            CacheName = null;
        }
        public string CacheName { get; set; }
    }
}

using System;
using Microsoft.Owin;
using Owin;

namespace Owin.Middleware
{
    public static class AzureCacheExtensions
    {
        public static IAppBuilder UseAzureCache(this IAppBuilder builder, AzureCacheOptions options = null)
        {
            if (builder == null)
            {
                throw new ArgumentNullException("builder");
            }

            return builder.Use(typeof (AzureCacheMiddleware), options);
        }

        public static AzureCacheClient Cache(this IOwinContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            return context.Environment[AzureCacheMiddleware.CacheKeyName] as AzureCacheClient;
        }

    }

}

Session Middlewareは、Invoke前処理でCacheからのSessionの復元とResponseのCookieへのセッションIDの設定、後処理でSessionの保存を行っている。

using System;
using System.Threading.Tasks;
using Microsoft.Owin;

namespace Owin.Middleware
{
    public class SessionMiddleware : OwinMiddleware
    {
        public const string SessionKeyName = "Kyrt.Session";

        public SessionMiddleware(OwinMiddleware next) : base(next)
        {
        }

        public override Task Invoke(IOwinContext context)
        {
            string sessionId = null;
            try
            {
                sessionId = AzureCacheSessionProvidor.PreInvoke(context, SessionKeyName);
            }
            catch (Exception e)
            {
                context.TraceOutput.WriteLine(e);
            }

            return Next.Invoke(context).ContinueWith((task, state) =>
            {
                try
                {
                    var p = state as Tuple<IOwinContext, string>; 
                    if (p!=null && p.Item2 != null)
                        AzureCacheSessionProvidor.PostInvoke( p.Item1, SessionKeyName, p.Item2);
                }
                catch (Exception e)
                {
                    context.TraceOutput.WriteLine(e);
                }
                return task;
            }, Tuple.Create(context, sessionId));
        }
    }
}

アプリケーションの変更

最後に、アプリケーションをCacheとSessionを使うように変更する。

using System;
using System.Collections.Generic;
using Microsoft.Owin;
using Owin;
using Owin.Middleware;

[assembly: OwinStartup(typeof(SelfHostSample.Startup))]

namespace SelfHostSample
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseAzureCache();
            app.UseSession();

            app.Run(async context =>
            {
                context.TraceOutput.WriteLine("start app.Run {0}", context.Request.Path);

                context.Response.ContentType = "text/html";
                try
                {
                    var time = context.Cache().GetOrAdd("first time", s => DateTimeOffset.Now);
                    var count = context.Cache().Increment("counter", 1, 0);
                    int sessionCount = context.Session().Get("sessionCount", -1);
                    sessionCount++;
                    context.Session()["sessionCount"] = sessionCount;

                    var msg = string.Format("Hello, World! {0} {1}/{2} {3}<br>", time.ToString(), sessionCount, count, context.Request.Path);
                    await context.Response.WriteAsync(msg);


                }
                catch (Exception e)
                {
                    context.TraceOutput.WriteLine(e);
                }
            });
        }
    }
}

まとめ

OWINは、シンプルで柔軟なHTTP 抽象化レイヤーを提供してくれる。Middlewareのinvoke chain の仕組みと拡張可能なEnvironmetはシンプルだた強力だ。katanaproject.codeplex.comを見ると認証系、View Engine、Compressionなどのmiddlewareが散見され、それぞれのコードは興味深い。ただ、現時点ではアプリケーションの構築プラットフォームとして使うには道具立てが足りないようだ。でも今回のようにAzure Cache Session Provider などを書いてみると、パフォーマンス的な問題や実装上の課題などが見えてきてなかなか勉強になるし、ブレイクスルーできるような点も見えてくる。少々フロンティア的な色が強いが挑戦する価値のある分野だと思う。


[1]Server Scripts, by Rob McCool, www-talk mailing list, Sun, 14 Nov 1993
[2]Katana Project のコードを見ると重量級で途方にくれる。System.Web との相互運用性をもたせようとして難しいことになっているらしい。
[3]生 Owin だと型の情報がほどんど無い(IDictionary<string, object>なので)ので日和ってしまった。
]]>
Tue, 17 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/11/windows_azure_storage_client_3_0_1.html http://kyrt.in/2013/12/11/windows_azure_storage_client_3_0_1.html <![CDATA[Windows Azure Storage Client 3.0.1]]> Windows Azure Storage Client 3.0.1

2013/12/11 Windows Azure Storage Client 3.0.1 がリリースされました。変更内容はBUG FIXのみです。nuget:Windows Azure Storage 3.0.1

修正点 3.0.1.0:

  • All (WP): Get/SetACL で明示的に Accept type application/xml を設定
  • Blobs: Lease operations の後で、LastModified と ETag プロパティを設定
  • Tables: Nuget package に Microsoft.Data.Services.Client への明示的な参照を追加
  • Tables: Json .NET の bug に起因したtable query responseのパース時の問題を修正。Json .NET bugの詳細:http://james.newtonking.com/archive/2013/11/29/fixing-jarray-getenumerator-method-not-found-bug
  • Tables (RT): クエリーと列挙操作の継続トークンに関する問題を修正

メモ

Json .NET の bug から発生している問題は、Json .NETの5.0.5 で JArray.GetEnumerator をpublicにした結果、IEnumerableを取った時に、今までIEnumerable<JToken>)が返ってたのがJArray.GetEnumerator になってしまったことに起因するようです。5系の中で非互換な修正をしてしまったので、storageが使うJson .NETと他の部分で使うのが混在したときに両立出来ずにお手上げになってしまう可能性があったそうですが、このバージョンで解決されます。

]]>
Wed, 11 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/11/windows_azure_powershel_0_7_2_release.html http://kyrt.in/2013/12/11/windows_azure_powershel_0_7_2_release.html <![CDATA[Windows Azure PowerShell 0.7.2 リリース]]> Windows Azure PowerShell 0.7.2 リリース

Windows Azure PowerShell 0.7.2 がリリースされました。 10月の 0.7.0 、11月の 0.7.1 続く12月リリースです。最近毎月リリースされています。Azureに新機能が出ると追っかけでcmdletが追加されます。| 主な変更は、HDInsight cmdletsの追加、Web Site、VM cmdletの改善、Virtual IP reservation、Cloud Service cmdletの Visual Studio 互換です。

IMG_0911 by Takekazu Omi, on Flickr

インストール

最新版はWeb Platform Installer 経由で入れられます。12/10 リリースのWindows Azure Poershell を選択してください。

../../../_images/2013_12_11_webpi001.png

インストールしたら念のためバージョンを確認します。Azureの所が、0.7.2ですね。

$ Get-Module | ft name,version

Name                                                        Version
----                                                        -------
Autoload                                                    0.0
Azure                                                       0.7.2
Microsoft.PowerShell.Management                             3.1.0.0
Microsoft.PowerShell.Utility                                3.1.0.0
posh-git                                                    0.0
PsEnv                                                       0.0
PSReadline                                                  1.0.0.1

変更点

2013.12.10 Version 0.7.2

  • HDInsight cmdlets
    • Add-AzureHDInsightConfigValues
    • Add-AzureHDInsightMetastore
    • Add-AzureHDInsightStorage
    • Get-AzureHDInsightCluster
    • Get-AzureHDInsightJob
    • Get-AzureHDInsightJobOutput
    • Get-AzureHDInsightProperties
    • New-AzureHDInsightCluster
    • New-AzureHDInsightClusterConfig
    • New-AzureHDInsightHiveJobDefinition
    • New-AzureHDInsightMapReduceJobDefinition
    • New-AzureHDInsightPigJobDefinition
    • New-AzureHDInsightSqoopJobDefinition
    • New-AzureHDInsightStreamingMapReduceJobDefinition
    • Remove-AzureHDInsightCluster
    • Revoke-AzureHDInsightHttpServicesAccess
    • Set-AzureHDInsightDefaultStorage
    • Start-AzureHDInsightJob
    • Stop-AzureHDInsightJob
    • Use-AzureHDInsightCluster
    • Wait-AzureHDInsightJob
    • Grant-AzureHDInsightHttpServicesAccess
    • Invoke-AzureHDInsightHiveJob
  • Web Site の WebSocket と managed pipe mode の設定
    • Set-AzureWebsite -WebSocketEnabled -ManagedPipelineMode
  • Web Site の remote debugging 設定
    • Enable-AzureWebsiteDebug -Version
    • Disable-AzureWebsiteDebug
  • VM を削除した時の VHD cleaning up オプション
    • Remove-AzureVM -DeleteVHD
    • Remove-AzureService -DeleteAll
    • Remove-AzureDeployment -DeleteVHD
  • 仮想 IP 予約 (Virtual IP reservation) preview feature (in AzurePreview module)
    • Get-AzureDeployment
    • Get-AzureReservedIP
    • New-AzureReservedIP
    • New-AzureVM
    • Remove-AzureReservedIP
  • 下記の cmdletsでの Visual Studio Cloud Service プロジェクトのサポート
    • Start-AzureEmulator
    • Publish-AzureServiceProject
    • Save-AzureServiceProjectPackage

最後に

今回の目玉は、HDInsight cmdletsかなと思いますが、個人的にはVisual Studioで作ったプロジェクトが使えるようになったのが一番嬉しいですね。

]]>
Wed, 11 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/06/azure_table_json_payload.html http://kyrt.in/2013/12/06/azure_table_json_payload.html <![CDATA[Windows Azure Table の JSON payload]]> Windows Azure Table の JSON payload

2013/11/27 に公開された最新のWindows Azure Storageでは新しくJSON Payload Format が導入されました。変更点の詳細が Windows Azure Storage Team Blog のWindows Azure Tables: Introducing JSONに出ています、興味深い内容です。

mojya mojya by Takekazu Omi, on Flickr

翻訳は追々出ると思うので、拾い読みしながら気になったことを書きます。

  • [引用]JSON Payloadは、version “2013-08-15”の一部してリリースされました。JSONは従来の AtomPub[1]フォーマットの OData payload format に比べて著しくサイズが小さくなり latency が低くなっています。また、payload size を削減するため、 insert の payload echo を Off にする方法を提供します。これらの新機能は、新しいWindows Azure Storage Client 3.0ではデフォルトの機能として働きます。

従来の HTTP request/response を見ていると無駄が目立ったので、それが削減されるのは大歓迎です。ここには、version “2013-08-15”でのplayloadの削減が書いてありますが、2013年11月(先月)ぐらいから、version “2012-02-12” の AtomPub でも余計な改行や空白を削減するなどの変更が行われています。このあたりの変更については、Windows Azure Tables の Breaking Changes 2013/11を見て下さい。

Insert の payload echo を Off にする話が出ていますが、version “2011-08-18” でサポートされたInsert Or Merge Entity (REST API)ではレスポンスのBODYが空でステータスコードは204 (No Content) を返すようになっていました、これを insert でも同じように動作するモードを付けたということのようです。Insertの時にサーバー側で付与される情報はETagとTimestampだけで、それ以外は送信した内容と同じです、レスポンスヘッダーにETagは返ってくるので、エンティティ全体が帰ってこなくても困ることはほとんど無いと思います。場合によっては、Timestamp を使う場合があるかもしれませんが、その場合は設計を見直すか、エンティティ全体を返すモードで使うかになります。

What is JSON

  • [引用]JSON(JavaScript Object Notation) は、構造化データをシリアライズするための lightweight text format です。AtomPubと同じように、 OData extends JSON formatでは、エンティティとプロパティの一般的な規則を定義します。AtomPubは異なり、OData JSON では response payload が一部がペイロードサイズを低減するために省略されます。そのため、受信側でlink, type と control dataなどのデータを再構成ための表現力が不足しています。OData には下記のような複数の JSON フォーマットがあります:

ちょっと分かりづらいです(訳が悪いのでしょうか)、この辺りの考えは、OData JSON Format Version 4.0 2 Candidate OASIS Standard 01 の 2.JSON Format Designがわかりやすく考え方を説明してくれています。それによると、実際のpayloadからwire formatの予測可能な部分を取り除いて送信できるようにするという考えでデザインされているそうです。クライアントがデータに関するメタデータを持っているというシナリオでは毎回のpayloadに全メタ情報を載せて送る(以前のAtomPubのように)のは無駄なので省略できるようにしますという話です。どこまでメタデータを持たせるかは nometadata、minimalmetadata、fullmetadata の3段階用意しています。あとそれに伴って、name/valueのペアに順序の制約を付けています。順序の話は、Hashにそのまま読み込めなくなるので少々面倒な制約です。IDLやXML Schema のようなメタ情報を定義する仕組みを持ち込まずに、受信側のクライアントのコードで実装もしくは、JSON内にメタ情報埋め込みという話にしたのは、JSONの手軽さを損なわないという点で評価できます。

nometadata、minimalmetadata、fullmetadata や、JSON payload の詳細についてはPayload Format for Table Service Operationsを見て下さい。

AtomPub vs JSON format

具体的に、AtomPubとJSONを比較してみます。JSONにすると、基本的に閉じタグが無くなるので、それだけでもかなりの削減になるのはすぐわかります。AtomPub [引用]と、JSON nometadata [引用]を見ると一目瞭然です。AtomPub [引用]の方は、元々、Atom Publishing Protocolもので、インターネット上でのコンテンツの交換を目的として設計されたものです。Table Storageでは使われない、冗長なタグが散見されます。title、id、link、author あたりとか、namespace は不要なので、それも外してしまうと結構スッキリします。試しに、XMLで余計なタグを削ったXML nometadata (参考までに作ってみました)というのを作ってみました。閉じタグだけはなんとも成らないですが、結構シンプルになります。これを見ると、XMLを選択した功罪というより AtomPub を選択した問題の方が大きかったことがわかります。OData/WCFの流れに拘りすぎてちょっと遠回りしてしまったようです。

Payloadの違いによる比較のテーブルが面白いので引用します

To compare JSON and AtomPub
Format Request Header Size Request Body Size Response Header Size Response Body Size % Savings in HTTP Body Size only vs. AtomPub % Savings in total HTTP transfer vs. AtomPub
AtomPub 3,611 2,861 3,211 8,535 N/A N/A
JSON MinimalMetadata 3,462 771 3,360 2,529 71% 44%
JSON NoMetadata 3,432 771 3,330 1,805 77% 49%

AtomPub [引用]

<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="http://someaccount.table.core.windows.net/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
  <id>http://someaccount.table.core.windows.net/Customers</id>
  <title type="text">Customers</title>
  <updated>2013-12-03T06:37:21Z</updated>
  <link rel="self" title="Customers" href="Customers" />
  <entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A20.9709094Z'&quot;">
    <id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Jonathan',RowKey='Foster')</id>
    <category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="Customers" href="Customers(PartitionKey='Jonathan',RowKey='Foster')" />
    <title />
    <updated>2013-12-03T06:37:21Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>Jonathan</d:PartitionKey>
        <d:RowKey>Foster</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:20.9709094Z</d:Timestamp>
        <d:Address>1234 SomeStreet St, Bellevue, WA 75001</d:Address>
        <d:Email>Jonathan@fourthcoffee.com</d:Email>
        <d:PhoneNumber>425-555-0101</d:PhoneNumber>
        <d:CustomerSince m:type="Edm.DateTime">2005-01-05T00:00:00Z</d:CustomerSince>
        <d:Rating m:type="Edm.Int32">3</d:Rating>
      </m:properties>
    </content>
  </entry>
  <entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A21.1259249Z'&quot;">
    <id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Lisa',RowKey='Miller')</id>
    <category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="Customers" href="Customers(PartitionKey='Lisa',RowKey='Miller')" />
    <title />
    <updated>2013-12-03T06:37:21Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>Lisa</d:PartitionKey>
        <d:RowKey>Miller</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:21.1259249Z</d:Timestamp>
        <d:Address>4567 NiceStreet St, Seattle, WA 54332</d:Address>
        <d:Email>Lisa@northwindtraders.com</d:Email>
        <d:PhoneNumber>425-555-0101</d:PhoneNumber>
        <d:CustomerSince m:type="Edm.DateTime">2003-01-05T00:00:00Z</d:CustomerSince>
        <d:Rating m:type="Edm.Int32">2</d:Rating>
      </m:properties>
    </content>
  </entry>
  <entry m:etag="W/&quot;datetime'2013-12-03T06%3A37%3A20.7628886Z'&quot;">
    <id>http://someaccount.table.core.windows.net/Customers(PartitionKey='Walter',RowKey='Harp')</id>
    <category term="someaccount.Customers" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="Customers" href="Customers(PartitionKey='Walter',RowKey='Harp')" />
    <title />
    <updated>2013-12-03T06:37:21Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:PartitionKey>Walter</d:PartitionKey>
        <d:RowKey>Harp</d:RowKey>
        <d:Timestamp m:type="Edm.DateTime">2013-12-03T06:37:20.7628886Z</d:Timestamp>
        <d:Address>1345 Fictitious St, St Buffalo, NY 98052</d:Address>
        <d:Email>Walter@contoso.com</d:Email>
        <d:PhoneNumber>425-555-0101</d:PhoneNumber>
        <d:CustomerSince m:type="Edm.DateTime">2010-01-05T00:00:00Z</d:CustomerSince>
        <d:Rating m:type="Edm.Int32">4</d:Rating>
      </m:properties>
    </content>
  </entry>
</feed>

JSON minimalmetadata [引用]

{
    "odata.metadata":"http://someaccount.table.core.windows.net/$metadata#Customers",
    "value":[
        {
            "PartitionKey":"Jonathan",
            "RowKey":"Foster",
            "Timestamp":"2013-12-03T06:39:56.6443475Z",
            "Address":"1234 SomeStreet St, Bellevue, WA 75001",
            "Email":"Jonathan@fourthcoffee.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince@odata.type":"Edm.DateTime",
            "CustomerSince":"2005-01-05T00:00:00Z",
            "Rating":3
        },
        {
            "PartitionKey":"Lisa",
            "RowKey":"Miller",
            "Timestamp":"2013-12-03T06:39:56.7943625Z",
            "Address":"4567 NiceStreet St, Seattle, WA 54332",
            "Email":"Lisa@northwindtraders.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince@odata.type":"Edm.DateTime",
            "CustomerSince":"2003-01-05T00:00:00Z",
            "Rating":2
        },
        {
            "PartitionKey":"Walter",
            "RowKey":"Harp",
            "Timestamp":"2013-12-03T06:39:56.4743305Z",
            "Address":"1345 Fictitious St, St Buffalo, NY 98052",
            "Email":"Walter@contoso.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince@odata.type":"Edm.DateTime",
            "CustomerSince":"2010-01-05T00:00:00Z",
            "Rating":4
        }
    ]
}

JSON nometadata [引用]

{
    "value":[
	{
            "PartitionKey":"Jonathan",
            "RowKey":"Foster",
            "Timestamp":"2013-12-03T06:45:00.7254269Z",
            "Address":"1234 SomeStreet St, Bellevue, WA 75001",
            "Email":"Jonathan@fourthcoffee.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince":"2005-01-05T00:00:00Z",
            "Rating":3
	},
	{
            "PartitionKey":"Lisa",
            "RowKey":"Miller",
            "Timestamp":"2013-12-03T06:45:00.8834427Z",
            "Address":"4567 NiceStreet St, Seattle, WA 54332",
            "Email":"Lisa@northwindtraders.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince":"2003-01-05T00:00:00Z",
            "Rating":2
	},
	{
            "PartitionKey":"Walter",
            "RowKey":"Harp",
            "Timestamp":"2013-12-03T06:45:00.5384082Z",
            "Address":"1345 Fictitious St, St Buffalo, NY 98052",
            "Email":"Walter@contoso.com",
            "PhoneNumber":"425-555-0101",
            "CustomerSince":"2010-01-05T00:00:00Z",
            "Rating":4
	}
    ]
}

XML nometadata (参考までに作ってみました)

<?xml version="1.0" ?>
<Feed>
  <Properties>
    <PartitionKey>Jonathan</PartitionKey>
    <RowKey>Foster</RowKey>
    <Timestamp>2013-12-03T06:39:56.6443475Z</Timestamp>
    <Address>1234 SomeStreet St, Bellevue, WA 75001</Address>
    <Email>Jonathan@fourthcoffee.com</Email>
    <PhoneNumber>425-555-0101</PhoneNumber>
    <CustomerSince>2005-01-05T00:00:00Z</CustomerSince>
    <Rating>3</Rating>
  </Properties>
  <Properties>
    <PartitionKey>Lisa</PartitionKey>
    <RowKey>Miller</RowKey>
    <Timestamp>2013-12-03T06:39:56.7943625Z</Timestamp>
    <Address>4567 NiceStreet St, Seattle, WA 54332</Address>
    <Email>Lisa@northwindtraders.com</Email>
    <PhoneNumber>425-555-0101</PhoneNumber>
    <CustomerSince>2003-01-05T00:00:00Z</CustomerSince>
    <Rating>2</Rating>
  </Properties>
  <Properties>
    <PartitionKey>Walter</PartitionKey>
    <RowKey>Harp</RowKey>
    <Timestamp>2013-12-03T06:39:56.4743305Z</Timestamp>
    <Address>1345 Fictitious St, St Buffalo, NY 98052</Address>
    <Email>Walter@contoso.com</Email>
    <PhoneNumber>425-555-0101</PhoneNumber>
    <CustomerSince>2010-01-05T00:00:00Z</CustomerSince>
    <Rating>4</Rating>
  </Properties>
</Feed>

最後に

書き始めたら案外知らないことが多く。てっきり、OData V3 の JSON[2]Content-Type: application/json;odata=verbosejson light (content-type : application/json; odata=light)が使われているのかと思ったら、いつの間にかOData Version 4.0 JSON[3]なんてものがあって、そっちが使われていたことに気が付いたり。

Windows Azure Storage Client 3.0で使っている、ODataLib 5.6.0から OData V4をサポートしているらしい[4]ということが分かったりということで手間取りました。

じゃあ、ODataLib 5.6.0 のコードちょっと見ておくかと思ったら、odata.codeplex.com の ODataLib は2011年から放置状態になっていて(これは知ってました)、http://www.symbolsource.org/Public/Metadata/NuGet/Project/Microsoft.Data.Services/5.6.0からコードが確認できるってことに気が付いたりで手間取りました。

今回は前半しか紹介できていないので、そのうち続きを書きたいと思います。

Resources

[1]AtomPub
[2]OData V3 JSON Verbose Format
[3]OData JSON Format Version 4.0 Candidate OASIS Standard 01 19 November 2013
[4]OData V4 のstackが、ODataLib として提供されることがコメントに書いてあるWCF Data Services 5.6.0 Release
]]>
Fri, 06 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/06/github_windows_azure_storage_libraries_for_net_3_0.html http://kyrt.in/2013/12/06/github_windows_azure_storage_libraries_for_net_3_0.html <![CDATA[GitHub/Windows Azure Storage Libraries for .NET 3.0.0]]> GitHub/Windows Azure Storage Libraries for .NET 3.0.0

3.0.0では、GitHub上の Windows Azure Storage の .NET Client ライブラリの場所が変わりました。

leaf by Takekazu Omi, on Flickr

Storage の .NET Client ライブラリは、2.1までは、Windows Azure SDK for .NET github:azure-sdk-for-netにありましたが、3.0からは、Storageは独立してWindows Azure Storage Libraries for .NET github:azure-storage-netになりました。2.1系は継続して、Windows Azure SDK for .NET github:azure-sdk-for-netでメンテされるようです。現在2.1系の最新は、2.1.0.4です。

今後Windows Azure SDK for .NETは、Windows Azure Configuration ManagerWindows Azure Management Librariesの場所ということになりそうです。

最初は、Windows Azure SDK for .NET という名前で、中身がStorageのライブラリで始まって、そのうち Configuration Manager が追加、Management Libraries が追加され、Media関連はディレクトリはあるけど別レポジトリ、とだんだん膨れてわかり辛くなってきたところだったので整理するにはいいタイミングだった気がします。

Breaking Changes

Windows Azure Storage 2013-08-15 の Minute Metricsで、3.0の変更点が分からないと書きましたが、レポジトリのBreakingChanges.txtに記載がありました。

3つあります。簡単にまとめます。

  1. テーブルの操作にDataServiceContextを使った場合、OperationContextのresponse received eventは no longer fired(もはや起こらない)
  2. ContinuationToken の WriteXml()/ReadXml()が変更されました。詳細は原文を参照してください。
  3. ServiceProperties では、uploadされたものだけが変更されます。例えば、CORSの設定だけをすると、Logging と Metering 関連の設定は変更されません。

ServicePropertiesのプロパティが変更された件などは、上記 Breaking Changes に書いてありませんので互換性に関しては注意が必要だと思います。

まとめ

このレポジトリの移動に関してWindows Azure Storage Release - CORS、JSON、Minute Metrics の紹介に下記のような記述があります。

これらの機能に対応した、Windows Azure Storage Client Library を github:azure-storage-net にリリースします。

今までのレポジトリを見てソースが上がってこないので、3.0のソースは公開されないのかと思って困惑していましたが、無事ソースも確認できて安心しました。

]]>
Fri, 06 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/05/minute_metrics_storage_version_2013_08_15.html http://kyrt.in/2013/12/05/minute_metrics_storage_version_2013_08_15.html <![CDATA[Windows Azure Storage 2013-08-15 の Minute Metrics]]> Windows Azure Storage 2013-08-15 の Minute Metrics

Windows Azure Advent Calendar 2013 - Qiita [キータ]の 5 日目の記事です。最初Javaネタを書こうと思ってたのですが、11/27にWindows Azure Storageの新しいバージョン(x-ms-version:2013-08-15)が公開され盛り上がっているのでストレージネタに切り替えました。

新バージョンに付いては以前に書いているので、それを見て下さい。

Windows Azure Storage Team Blog で、新しいWindows Azure Storageのリリースが紹介されています。Windows Azure Storage Release - Introducing CORS, JSON, Minute Metrics, and More

立派な訳が出てるので、こちらも合わせてどうぞ。

久しぶりのストレージ のリリースで機能盛り沢山なのですが[1]、その中でもパフォーマンスジャンキー注目の Minute Metrics (分単位メトリックス)を紹介します。機能紹介だけだと面白くないので脱線しながら書きますのでお楽しみ下さい。

DEAD END by takekazu, on Flickr

Minute Metrics (分単位メトリックス)とは

今まで、Windows Azure StorageのMetricsは時間集計でした。Storage Analytics Metrics の詳細新しい 2013-08-15 version のMinute Metrics では、5分以内に 分単位の集計(Minute Metrics) が参照できるようになりました。それに伴って下記のテーブルが追加されています。

  • $MetricsHourPrimaryTransactionsBlob
  • $MetricsHourPrimaryTransactionsTable
  • $MetricsHourPrimaryTransactionsQueue
  • $MetricsMinutePrimaryTransactionsBlob
  • $MetricsMinutePrimaryTransactionsTable
  • $MetricsMinutePrimaryTransactionsQueue

Hourと付いているのは、以前の $MetricsTransactionsBlob、$MetricsTransactionsTable、$MetricsTransactionsQueue の名前が変わったものです。古い名前のものもありますが、Minute Metrics の導入に伴って命名規則が整理されたようです。今のところ Windows Azure Portal では分単位の設定はできませんが、将来サポートされるそうです。

名前を見ていて面白いのは、Primary という文字列が入っていることです。もしかしたら、GEO replication 先のメトリックスも見れるようになるんじゃないかと思って、「$MetricsMinuteSecondaryTransactionsTable」って名前でテーブルを参照してみました。

エラーには成らなかったのですが、下記のようなレスポンスでTableは空のようです。[2]

HTTP/1.1 200 OK
Cache-Control: no-cache
Transfer-Encoding: chunked
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 9e39e71e-70ea-41b3-be3e-b7a6231315b0
x-ms-version: 2013-08-15
X-Content-Type-Options: nosniff
Date: Thu, 05 Dec 2013 12:00:39 GMT

83
{"odata.metadata":"http://waac2013omi001diag.table.core.windows.net/$metadata#$MetricsMinuteSecondaryTransactionsTable","value":[]}
0

ちなみに、$で始まる適当な名前でリクエストを流すと400 Bad Requestで、リソースに不正な文字が含まれているというエラーになります。

HTTP/1.1 400 Bad Request
Transfer-Encoding: chunked
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 272dce2f-ac46-46b7-b59d-b0338cefa78b
x-ms-version: 2013-08-15
X-Content-Type-Options: nosniff
Date: Thu, 05 Dec 2013 11:57:59 GMT

DE
{"odata.error":{"code":"InvalidResourceName","message":{"lang":"en-US","value":"The specifed resource name contains invalid characters.\nRequestId:272dce2f-ac46-46b7-b59d-b0338cefa78b\nTime:2013-12-05T11:58:00.5348125Z"}}}
0

メトリックス系のテーブルは下記のようにListTableでも帰ってこないので存在するかどうかは難しいところですが、エラーにならないところを見ると特別扱いされているようで期待してしまいます。

var tables = tableClient.ListTables("$Metrics");

Minute Metrics (分単位メトリックス)を有効にする

Minute Metricsを有効にするには、xms-version:2013-08-15 を指定して、Set Service Properties REST APIを使います。今回は、Widnows Azure Storage Client 3.0.0を使います。今のところWidnows Azure Storage Client 3.0.0のドキュメントがほぼ存在しないのですが、nugetで入れるとデフォルトでは入ります。設定は、ServiceProperties に、Version、MetricsLevel、RetentionDays(保存期間)を入れて、CreateCloud(Blob|Table|Queue)ClientのSetServicePropertiesを呼ぶだけです、簡単ですね。下記のコードだと、Loggingと時間集計メトリックスの設定も同時にやっています。Minute MetricsのRetentionの期間は従来と同じで日付なのでデータ量には要注意です。従来の時間単位集計に比べて60倍のデータが蓄積されます。[3]

private static void SetStorageAnalytics(CloudStorageAccount account)
{
    var serviceProperty = new ServiceProperties
    {
        Logging = new LoggingProperties
        {
            Version = "1.0",
            LoggingOperations = LoggingOperations.All,
            RetentionDays = 7,

        },
        HourMetrics = new MetricsProperties
        {
            Version = "1.0",
            MetricsLevel = MetricsLevel.ServiceAndApi,
            RetentionDays = 7
        },
        MinuteMetrics = new MetricsProperties
        {
            Version = "1.0",
            MetricsLevel = MetricsLevel.ServiceAndApi,
            RetentionDays = 1
        }
    };
    account.CreateCloudBlobClient().SetServiceProperties(serviceProperty);
    account.CreateCloudTableClient().SetServiceProperties(serviceProperty);
    account.CreateCloudQueueClient().SetServiceProperties(serviceProperty);

}

このコード書いていてServicePropertiesのプロパティで「あれ?」と思いました。以前は、ServicePropertiesは、LoggingProperties と MetricsProperties の2つのプロパティを持っていたのですが、3.0では MetricsProperties が無くなって HourMetrics になってしまっているようです、Table側は古い名前も残してあるのに、ライブラリは Braking Changesにしてしまったようですね。このあたりの判断は難しいところなのでしょうが、古い名前もあっても良い気がします。

2.1.0.4 ServiceProperties.cs

nugetから無条件で最新版として入るのに前のバージョンと互換性が無いっていうのは大胆な気がしますが、なんらかの事情でちょっとドキュメントが遅れているだけなのでしょう。

実行してリクエスト、レスポンスを覗いてみます。

http request

PUT http://waac2013omi001diag.blob.core.windows.net/?comp=properties&restype=service&timeout=90 HTTP/1.1
User-Agent: WA-Storage/3.0.0 (.NET CLR 4.0.30319.34003; Win32NT 6.3.9600.0)
x-ms-version: 2013-08-15
x-ms-client-request-id: 56bffee5-4626-4835-b254-af38a1520242
x-ms-date: Thu, 05 Dec 2013 12:39:54 GMT
Authorization: SharedKey ********************************************************
Host: waac2013omi001diag.blob.core.windows.net
Content-Length: 626
Connection: Keep-Alive

<?xml version="1.0" encoding="utf-8"?><StorageServiceProperties><Logging><Version>1.0</Version><Delete>true</Delete><Read>true</Read><Write>true</Write><RetentionPolicy><Enabled>true</Enabled><Days>7</Days></RetentionPolicy></Logging><HourMetrics><Version>1.0</Version><Enabled>true</Enabled><RetentionPolicy><Enabled>true</Enabled><Days>7</Days></RetentionPolicy><IncludeAPIs>true</IncludeAPIs></HourMetrics><MinuteMetrics><Version>1.0</Version><Enabled>true</Enabled><RetentionPolicy><Enabled>true</Enabled><Days>1</Days></RetentionPolicy><IncludeAPIs>true</IncludeAPIs></MinuteMetrics><Cors /></StorageServiceProperties>

http response

HTTP/1.1 202 Accepted
Transfer-Encoding: chunked
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 650e2253-9e51-43e5-90bd-6077ef43ef31
x-ms-version: 2013-08-15
Date: Thu, 05 Dec 2013 12:39:47 GMT

0

リクエストで、ServicePropertiesに設定した内容を投げていますがXMLなんですね。ここはJSONじゃないのかと思ったけど、そんなにトラフィックが多いところじゃないので問題ないですね。シンプルでいい感じです。

Minute Metrics (分単位メトリックス)の結果

細かい確認ができていないのですが、結果自体はテーブル名が違うだけで従来の時間集計と同じものが出てるようです。パーテションキーが単位で採番されています。

Minute Metricsの例
PartitionKey RowKey TotalIngress TotalEgress AverageE2ELatency AverageServerLatency
20131205T0303 user;All 393 59079 352 53
20131205T0303 user;QueryEntities 393 59079 352 53
20131205T0304 system;All 8057 9452 20 19
20131205T0304 user;All 2188 15747 46.833333 5.166667
20131205T0304 user;QueryEntities 1478 14291 68.5 6.25
20131205T0304 user;QueryTables 710 1456 3.5 3
20131205T0305 system;All 24161 28398 14.333333 13.333333
20131205T0305 user;All 2041 13787 268.5 5.75
20131205T0305 user;GetTableServiceProperties 338 584 2 2
20131205T0305 user;QueryEntities 1703 13203 357.333333 7
20131205T0306 system;All 32239 37972 17.25 16
20131205T0307 system;All 23475 27904 15 14.5

表だと厳しいのでcsvをどうぞ (zip)

まとめ

Minute Metrics は出てくるまで少々遅延(5分ぐらい?)があるようですが、分単位のメトリックスが見れるのは助かります。簡単な負荷テストの結果を確認するのにも使えます。今回はStorage Clinet 3.0を使いましたが、現時点ではGitHubにコードが出ていない、変更点のドキュメントが無いなど少々使いづらいところがあります。もし新機能にこだわりが無いなら、あの嫌らしいCastのBUGも治ってますし2.1.0.4 を使うのが良いと思います。今回のように、Minute Metricsの設定がしたいというなら、Storage Clinet 3.0 が必要です。あと、ServicePropertiesに、CorsPropertiesが追加されているので、CROS が使いたい場合も3.0が便利だと思います。

訂正 2013/12/06

訂正です。Storage Clinet 3.0は、現時点ではGitHubにコードが出ていないと書きましたが、正式名称 Windows Azure Storage Libraries for .NET のソースはgithub:azure-storage-netで公開されています。



[1]日本リージョン、IaaSのGAなど大きな話題が続いている Azure 全体に比べると地味ですね。
[2]レスポンスのbodyがJSONですね、このバージョンからJSONがサポートされています。
[3]当たり前ですね
]]>
Thu, 05 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/12/03/windows_azure_sdk_for_ruby_release_0_6_0.html http://kyrt.in/2013/12/03/windows_azure_sdk_for_ruby_release_0_6_0.html <![CDATA[Windows Azure SDK for Ruby Release 0.6.0]]> Windows Azure SDK for Ruby Release 0.6.0

Windows Azure SDK for Ruby 0.6.0 がリリースされました。2013/04/25に 0.5.0 が出て以来7ヶ月ぶりのリリースです。RubyGemsから入ります

INISTALL > gem install azure

新機能

0.6.0では、主にmanagement APIの追加とBUG修正が行われたようです。

  • service management API のライブラリとして下記のものが追加されています。
    • Virtual Machine
    • Virtual Machine Image
    • Virtual Network
    • Cloud Service
    • Storage
    • Sql Database
    • Location and Affinity Group

get_blob_properties のBUG修正

5月にPRを出した修正も取り込まれてリリースされました。これで、Blobのプロパティを取ったらBodyが付いてくるという大きな問題は解決されたことになります。

これで出すに出せなかったやつが出せるようになります。

]]>
Tue, 03 Dec 2013 00:00:00 +0900
http://kyrt.in/2013/11/30/fixed_storage_client_cast_problem_in_2013_08_15_version.html http://kyrt.in/2013/11/30/fixed_storage_client_cast_problem_in_2013_08_15_version.html <![CDATA[Storage Client 2.1.0.4 以降での Cast問題の修正]]> Storage Client 2.1.0.4 以降での Cast問題の修正

Storage Client 2.0.3以前と、最新のWindows Azure Storage の組み合わせで発生していたTable Query での Cast 問題が最新(2.1.0.4, 3.0.0 2013/11/29 現在)では解決されているようです。正式なアナウンスはまだ有りませんが、必要な方は以下の検証結果を参考にしてください。

red takekazu, on Flickr

issueの再現

まずは、問題が報告されているStorage Clinet 2.1.0.3で再現することを確認します。再現コードはTable Query での Cast 問題で提示されているものを使います。

static IEnumerable<T> GetEntities<T>(CloudTable table)  where T : ITableEntity, new()
{
    IQueryable<T> query = table.CreateQuery<T>().Where(x => x.PartitionKey == "1");
    return query.ToList();
}

nugetで、問題のあるバージョンのライブラリをインストールします:

Install-Package WindowsAzure.Storage -Version 2.1.0.3

実行結果

Table Query での Cast 問題で報告されている通り、http requestのtraceのGET URLに$filter=castの文字列が入ってしまっており、http responseのtraceでは、400 Bad Requestになっているのが確認できました。x-ms-versionは、 2012-02-12 です。

http requestのtrace:

GET http://asdpojasldfj001.table.core.windows.net/people?$filter=cast%28%27%27%29%2FPartitionKey%20eq%20%271%27&timeout=90 HTTP/1.1
User-Agent: WA-Storage/2.1.0.3 (.NET CLR 4.0.30319.34003; Win32NT 6.3.9600.0)
x-ms-version: 2012-02-12
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
MaxDataServiceVersion: 2.0;NetFx
x-ms-client-request-id: af7cf5c3-c287-4c1f-a9fe-ee0b6c374317
x-ms-date: Fri, 29 Nov 2013 23:25:01 GMT
Authorization: SharedKey asdpojasldfj001:*********************************************
Host: asdpojasldfj001.table.core.windows.net

http responseのtrace:

HTTP/1.1 400 Bad Request
Transfer-Encoding: chunked
Content-Type: application/xml;charset=utf-8
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: abf14b12-7a80-4b75-a652-604c56bdb817
x-ms-version: 2012-02-12
X-Content-Type-Options: nosniff
Date: Fri, 29 Nov 2013 23:25:01 GMT

131
<?xml version="1.0" encoding="utf-8"?><error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><code>InvalidInput</code><message xml:lang="en-US">One of the request inputs is not valid.
RequestId:abf14b12-7a80-4b75-a652-604c56bdb817
Time:2013-11-29T23:25:02.0389849Z</message></error>
0

2.1.0.4 での修正確認

nugetの履歴によるとnuget.org: Windows Azure Storage2013/11/27 に、2.1.0.4と、3.0.0が同時にリリースされています。同じコードをライブラリのバージョンだけ変更して実行します:

Install-Package WindowsAzure.Storage -Version 2.1.0.4

実行結果

GET URLがスッキリして、結果は 200 OK が帰ってきています。FIXされているようです。

http requestのtrace:

GET http://asdpojasldfj001.table.core.windows.net/people?$filter=PartitionKey%20eq%20%271%27&timeout=90 HTTP/1.1
User-Agent: WA-Storage/2.1.0.4 (.NET CLR 4.0.30319.34003; Win32NT 6.3.9600.0)
x-ms-version: 2012-02-12
Accept: application/atom+xml,application/xml
Accept-Charset: UTF-8
MaxDataServiceVersion: 2.0;NetFx
x-ms-client-request-id: e9319d53-b9eb-41c6-9a93-63566842984e
x-ms-date: Fri, 29 Nov 2013 23:33:17 GMT
Authorization: SharedKey asdpojasldfj001:*********************************************
Host: asdpojasldfj001.table.core.windows.net

http responseのtrace:

HTTP/1.1 200 OK
Cache-Control: no-cache
Transfer-Encoding: chunked
Content-Type: application/atom+xml;type=feed;charset=utf-8
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: d1056e7a-f0ff-4ab4-b5a4-4b546eb48515
x-ms-version: 2012-02-12
X-Content-Type-Options: nosniff
Date: Fri, 29 Nov 2013 23:33:17 GMT

96A
<?xml version="1.0" encoding="utf-8"?><feed xml:base="http://asdpojasldfj001.table.core.windows.net/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><id>http://asdpojasldfj001.table.core.windows.net/people</id><title type="text">people</title><updated>2013-11-29T23:33:18Z</updated><link rel="self" title="people" href="people" /><entry m:etag="W/&quot;datetime'2013-11-28T10%3A24%3A23.5715047Z'&quot;"><id>http://asdpojasldfj001.table.core.windows.net/people(PartitionKey='1',RowKey='1')</id><category term="asdpojasldfj001.people" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><link rel="edit" title="people" href="people(PartitionKey='1',RowKey='1')" /><title /><updated>2013-11-29T23:33:18Z</updated><author><name /></author><content type="application/xml"><m:properties><d:PartitionKey>1</d:PartitionKey><d:RowKey>1</d:RowKey><d:Timestamp m:type="Edm.DateTime">2013-11-28T10:24:23.5715047Z</d:Timestamp><d:Data>foo</d:Data></m:properties></content></entry><entry m:etag="W/&quot;datetime'2013-11-28T10%3A21%3A02.0298712Z'&quot;"><id>http://asdpojasldfj001.table.core.windows.net/people(PartitionKey='1',RowKey='2')</id><category term="asdpojasldfj001.people" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><link rel="edit" title="people" href="people(PartitionKey='1',RowKey='2')" /><title /><updated>2013-11-29T23:33:18Z</updated><author><name /></author><content type="application/xml"><m:properties><d:PartitionKey>1</d:PartitionKey><d:RowKey>2</d:RowKey><d:Timestamp m:type="Edm.DateTime">2013-11-28T10:21:02.0298712Z</d:Timestamp><d:Data>foo</d:Data></m:properties></content></entry><entry m:etag="W/&quot;datetime'2013-11-29T23%3A16%3A04.1332012Z'&quot;"><id>http://asdpojasldfj001.table.core.windows.net/people(PartitionKey='1',RowKey='3')</id><category term="asdpojasldfj001.people" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /><link rel="edit" title="people" href="people(PartitionKey='1',RowKey='3')" /><title /><updated>2013-11-29T23:33:18Z</updated><author><name /></author><content type="application/xml"><m:properties><d:PartitionKey>1</d:PartitionKey><d:RowKey>3</d:RowKey><d:Timestamp m:type="Edm.DateTime">2013-11-29T23:16:04.1332012Z</d:Timestamp><d:Data>foo</d:Data></m:properties></content></entry></feed>
0

おまけの 3.0.0 での修正確認

同じことを最新の 3.0.0 でやりました。バッチリ動きます。x-ms-versionが 2013-08-15になって結果がjsonになっています:

Install-Package WindowsAzure.Storage -Version 2.1.0.4

http requestのtrace:

GET http://asdpojasldfj001.table.core.windows.net/people?$filter=PartitionKey%20eq%20%271%27&timeout=90 HTTP/1.1
User-Agent: WA-Storage/3.0.0 (.NET CLR 4.0.30319.34003; Win32NT 6.3.9600.0)
x-ms-version: 2013-08-15
Accept-Charset: UTF-8
MaxDataServiceVersion: 3.0;NetFx
Accept: application/json;odata=minimalmetadata
x-ms-client-request-id: 5b6e366f-dde8-4ec0-b433-10fac62c117d
x-ms-date: Fri, 29 Nov 2013 23:17:23 GMT
Authorization: SharedKey asdpojasldfj001:*********************************************
Host: asdpojasldfj001.table.core.windows.net

http responseのtrace:

HTTP/1.1 200 OK
Cache-Control: no-cache
Transfer-Encoding: chunked
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: fa6db02e-b080-4bc4-8761-1016920fb1b0
x-ms-version: 2013-08-15
X-Content-Type-Options: nosniff
Date: Fri, 29 Nov 2013 23:17:22 GMT

168
{"odata.metadata":"http://asdpojasldfj001.table.core.windows.net/$metadata#people","value":[{"PartitionKey":"1","RowKey":"1","Timestamp":"2013-11-28T10:24:23.5715047Z","Data":"foo"},{"PartitionKey":"1","RowKey":"2","Timestamp":"2013-11-28T10:21:02.0298712Z","Data":"foo"},{"PartitionKey":"1","RowKey":"3","Timestamp":"2013-11-29T23:16:04.1332012Z","Data":"foo"}]}
0

ソースコード上の修正点

2.1.0.4はgithub上でコードが公開されているので、レポジトリ上の修正点を確認しましょう。

ChangeLogでは:

Issues fixed in 2.1.0.4 :
  - Tables: Do not send the cast operator in the table query filter string.

microsoft-azure-api/Services/Storage/Lib/Common/Table/Queryable/ExpressionWriter.cs で、2.1でExpressionをURLに展開するときのUnaryの処理にcastを使ってしまっていたのを削除したようです。

修正のCommitは、XSCL 2.1.0.4 - Hotfixで、ソースは、ExpressionWriter.csです。

残念ながら、3.0.0は、まだコードが公開されていないので確認することができません。

まとめ

2.1.0.4/3.0.0では、Table Query での Cast 問題の問題はFIXされています。そのうちアナウンスは出ると思いますが、必要な場合は試してみてください。Windows Azure Storage Known Issues 2013/11で報告されていた幾つかの問題の中で、Cast問題はスマートに回避するのが難しいものだったので、これで解決されて助かります。これ以外のものはどちらということ利用者側のコードに問題がある場合に起きる問題な気がします。

本題とは外れますが、今回の場合では、363 byte(json) と、2,436 byte (xml)とhttp responseのbodyのサイズが大きく違い期待以上に良好な結果になっています。今回はデータが非常に小さいのでmeta 情報部分が冗長なxmlのオーバーヘッドが目立つ結果になっているのでしょうが、それにしても大きな違いです。

]]>
Sat, 30 Nov 2013 00:00:00 +0900
http://kyrt.in/2013/11/28/cors_json_minute_metrics_and_more.html http://kyrt.in/2013/11/28/cors_json_minute_metrics_and_more.html <![CDATA[Windows Azure Storage Release - CORS、JSON、Minute Metrics の紹介]]> Windows Azure Storage Release - CORS、JSON、Minute Metrics の紹介

Windows Azure Storage Team Blogで、新しいWindows Azure Storageのリリースが紹介されています。Windows Azure Storage Release - Introducing CORS, JSON, Minute Metrics, and More

以下に抜粋で内容を紹介します。

3つの主要な機能

  1. CORS (Cross Origin Resource Sharing):Windows Azure Blobs, Tables, Queues でCORS が有効できるようにになった。これによって、browser から異なったドメインのリソースへのアクセス・操作ができる。CORS は、Service Properties の 設定で有効化。詳しくはhttp://msdn.microsoft.com/en-us/library/windowsazure/dn535601.aspxを参照してください。

  2. JSON (JavaScript Object Notation): 現在、Windows Azure Table は、OData 3.0JSON formatをサポートしている。JSON format では、AtomPub XML payloadの冗長な部分が削減されより効率的な転送になる。

    JSONは下記3つの形式でサポート

    • No Metadata - これは最も効率的なformatで、クライアントがカスタムプロパティのデータ型を知っている場合に便利です
    • Minimal Metadata - この形式は、暗黙的に解釈できない特定の種類のカスタムプロパティのデータ型情報が含まれている。例えば、Azure Table Browserのような、一般的なツールのように保存されているEntityのデータ型を知らないで読まなければいけない場合に便利です
    • Full Metadata - このフォーマットは、 generic OData readers で読む場合に便利です

    Windows Azure TableのJSONについての詳細情報はhttp://msdn.microsoft.com/en-us/library/windowsazure/dn535600.aspxを参照してださい。

  3. Windows Azure Storage Analytics の Minute Metrics: 今まで、Windows Azure StorageのMetricsは時間集計でした。(Storage Analytics Metrics の詳細) 新しい 2013-08-15 version では、 いくつかの主要な値に付いて 5分以内に 分集計(Minute Metrics) が取得できるようになります。それに伴って下記のテーブルを追加します。

    • $MetricsHourPrimaryTransactionsBlob
    • $MetricsHourPrimaryTransactionsTable
    • $MetricsHourPrimaryTransactionsQueue
    • $MetricsMinutePrimaryTransactionsBlob
    • $MetricsMinutePrimaryTransactionsTable
    • $MetricsMinutePrimaryTransactionsQueue

    時間集計のテーブル名が変わっているので注意してください。古いテーブルも継続して存在します。

    分集計の設定は、2013-08-15 version を設定して、Set Service Properties REST APIを使います。現在 Windows Azure Portal では設定ができませんが、将来サポートされる予定です。

    詳細情報は、About Storage Analytics Metricsを見てくだい。

その他の追加機能

これらの他の2013-8-15 versionでは、以下の機能を実装しています。2013-8-15 version の変更詳細のリストは:http://msdn.microsoft.com/en-us/library/windowsazure/dd894041.aspxにあります。

  • Copy blob で、Shared Access Signature (SAS) を、コピー先にも適応します。(同じstorage accountの場合)
  • Windows Azure Blob service は、Content-Disposition と response headers の cache-control などの ability control (via. SAS)をサポートします。Content-Disposition は、Set Blob Properties で設定します。
  • Windows Azure Blob service は、Get Blob と Get Blob Propertiesで、複数の HTTP conditional header をサポートします。この機能は、web-browsers which から CDN servers 経由でアクセスする場合に有用です。
  • Windows Azure Blob Service は、uncommitted blobはある状態での、Delete Blob operation をサポートします。以前は、事前にcommitしないとdelete Blob出来ませんでした。
  • List Containers, List Blobs と List Queues は、2013-08-15 version から、resourceに、URL address field を含みません。 これは、clientで再構築できる fields を削減したためです。
  • Lease Blob と Lease Container は、2013-08-15 version から、ETag と Last Modified Time を response headers で返します。これによって、lease holder は最後に見た時から、リソースが変更されたかどうかを簡単に確認することができます。(つまり、blob や その metadata が更新されたか)。以前と同じくblob の lease operations では、ETagは変更されません。

これらの機能に対応した、Windows Azure Storage Client Library をgithub:azure-storage-netにリリースします。数ヶ月で、Windows AzureのSDK 2.2、Windows Azure Storage Emulatorのアップデートをリリースする予定です。この更新は、2013-08-15 version の新機能をサポートします。

既知の問題が幾つかあります。下記の記事を参照してください。

http://blogs.msdn.com/b/windowsazurestorage/archive/2013/11/23/windows-azure-storage-known-issues-november-2013.aspxhttp://blogs.msdn.com/b/windowsazurestorage/archive/2013/11/23/windows-azure-storage-breaking-changes-for-windows-azure-tables-november-2013.aspx

以上

最後に

Azure Storage Client (Windows Azure Storage)は、3.0.0がリリースされています。nugetはnuget:WindowsAzure.StorageDependenciesを見ると、Microsoft.Data.OData 5.6以上になっているので、既存のコードとコンフリクトするかもしれません、要注意です。BUILD 2013で話が出てきたStorageの新機能の一部がまだ出てきていないようなので、年末に向けてさらに期待しています。負荷試験している時とかはMinute Metricsは便利ですね、嬉しいです。

]]>
Thu, 28 Nov 2013 00:00:00 +0900
http://kyrt.in/2013/11/28/using_helios_on_azure_cloud_service.html http://kyrt.in/2013/11/28/using_helios_on_azure_cloud_service.html <![CDATA[Helios を Azure Cloud Service で使う]]> Helios を Azure Cloud Service で使う
2013/12/13 追記 Microsoft.Owin.Host.IIS 0.1.1-pre では、WebRoleのStartup taskの作成 の問題は解決されました。

巷で話題のHeliosをCloud Serviceで使おうとしたらちょっとハマりました。基本的には、Checking out the Helios IIS Owin Web Server Hostと同じですが、Cloud Service、WebRoleの組み合わせでDeployしたら下記のようなエラーになります。

../../../_images/2013_11_helios009.png

最初、なにかアセンブリが足りないのかと思って、Fusion Logを調べたりしていたのですが、結局Helios内で使っているnavite code dllが依存しているVC12のランタイムが無かったという話でした。startup taskを用意してVC12のランタイムを入れてやると上手く動くようになります。

ここでは、Cloud Serviceの作成から、Heliosの組み込み、startup taskの作成まで一通り説明します。

手順の確認


WebRoleの作成からHeliosのインストールまで

  1. Cloud Service を作ってWebRoleを追加します普通にCloudServiceを作成し、WebRoleを追加します。テンプレートはEmptyにします
../../../_images/2013_11_helios003.png
  1. projectを、.NET 4.5.1 を使うようにします
../../../_images/2013_11_helios004.png
  1. コンパイルして問題無いのを確認します。

  2. System.Webの参照を全て削除します

  3. nugetを使って、Microsoft.Owin.Host.IISをインストールします:

    Install-Package Microsoft.Owin.Host.IIS -Pre
    

下記のような参照になります

../../../_images/2013_11_helios006.png

Startup Classの設定

下記のようなStartup classのコードを追加します:

using System;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(WebRole1.Startup))]

namespace WebRole1
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {

            app.Run(async context =>  // IOWinContext
            {
                context.Response.StatusCode = 200;
                context.Response.ContentType = "text/html";

                await context.Response.WriteAsync("Hello Herios. Time is: " + DateTime.Now.ToString());
            });
        }
    }
}

これで、WebRoleを動かしてみて、動くことを確認します。

WebRoleのStartup taskの作成

Helios 0.1.0の中で使われている、unmanaged codeがmsvcr120.dllに依存しているので、動作環境ではVC12 のランタイムが必要です。ここでは、WebRoleのstartup taskでVC12のランタイムをインストールする方法を説明します。

../../../_images/2013_11_helios007.png

ServiceDefinition.csdefに下記の定義を追加します:

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="HelloHelios" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2013-10.2.2">
  <WebRole name="WebRole1" vmsize="Small">
    <Startup>
      <Task commandLine="startup.cmd" executionContext="elevated" taskType="simple" />
    </Startup>

    <Sites>
    以下省略・・・・

WebRoleのプロジェクトに、startup.cmdというbatchファイルとvcredist_x64.exeを追加して、プロパティで出力ディレクトリにコピーするように設定します。

startup.cmd::
vcredist_x64.exe /install /quiet

vcredist_x64.exe は、Visual Studio 2013 の Visual C++ 再頒布可能パッケージからダウンロードできます。VS 2013をインストールしている場合は、C:Program Files (x86)Microsoft Visual Studio 12.0VCredist等のディレクトリにファイルがあります。

startup taskについて:Windows Azure でスタートアップ タスクを実行する

osFamilyの変更

.NET Framework 4.5.1は、Windows Server 2012R2では最初から入っています。簡単なので、osFamilyを4にして.NET Framework 4.5.1を使います。

ServiceConfiguration.(Local|Cloud).cscfgのosFamilyを3から4に変更します:

<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="HelloHelios" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="4" osVersion="*" schemaVersion="2013-10.2.2">
  <Role name="WebRole1">

まとめ

Helios 0.1.0runtimeには、native codeのDLLが含まれている。このDLLは、VC12(VS2013)のランタイム、msvcr120.dllに依存している。msvcr120.dllは、Cloud Service のWindows 2013R2 サーバーに存在しない。vcredist_x64.exeを使うとmsvcr120.dllがインストールされて問題が解決する。この問題は、厳密に言うとMicrosoft.Owin.Host.IIS 0.1.0-preが使っている、Microsoft.AspNet.Loader.IIS 0.1.0-preに起因する。このままだとちょっと使いづらいですね。

2013/12/13 追記

2013/12/02Microsoft.Owin.Host.IIS 0.1.1-preでは、Microsoft.AspNet.Loader.IIS 0.1.1-preに含まれる。Microsoft.AspNet.Loader.IIS.Interop.dll がMSVCR120.DLLに依存しなくなりました。そのため、WebRoleのStartup taskの作成のようなことをしないでも動作します。これで普通に使えるようになりますね。

../../../_images/2013_11_helios011.png
]]>
Thu, 28 Nov 2013 00:00:00 +0900
http://kyrt.in/2013/11/24/windows_azure_storage_known_issues_2013_11.html http://kyrt.in/2013/11/24/windows_azure_storage_known_issues_2013_11.html <![CDATA[Windows Azure Storage Known Issues 2013/11]]> Windows Azure Storage Known Issues 2013/11
2013/11/29 以下の内容は正式な日本語訳が出ています Windows Azure ストレージの既知の問題
2013/11/30 Table Query での Cast 問題 が解決されたStorage Client Libraryがリリースされています。検証記事 Storage Client 2.1.0.4 以降での Cast問題の修正

cros, json 対応などのmajor releaseの準備に伴って実装が変更されているようです。それが原因でいくつかの意図しない問題が発生していることが報告されています。以下は Windows Azure Storage の BlogWindows Azure Storage Known Issues (November 2013)からの抜粋です。これらの問題が修正されプロダクションに公開され次第 Blog の記事は更新されるということです。

Windows Azure Blobs, Tables and Queue Shared Access Signature (SAS)のIssue

  • 下記のような、2012-02-12 バージョンのSASが、 HTTP Status Code 400 (Bad Request)になります。従来の実装だと、コンテナの前の“//”は、“/”に折りたたまれて処理されていましたが、現時点ではコンテナが無効である(null)として解釈されてしまいます。これは、修正される予定ですが、当面は“//”を送らないようにして下さい

http://myaccount.blob.core.windows.net//container/blob?sv=2012-02-12&si=sasid&sx=xxxx

Windows Azure TablesのIssue

下記の2つの既知のissueがあります。サービス側または当社のクライアント·ライブラリの一部としてhotfixを出す予定です。

  1. clients で、DataServiceContext.ResolveNameを定義し、<Account Name>.<Table Name>以外の型の名前を指定すると、CUD operation が 400 (Bad Request) を返します。これは、新しい実装では、ATOM の “Category” element の term 属性が、<Account Name>.<Table Name> と同じで無ければいけないのが原因です。以前のバージョン(実装)では、送信された型の名前は無視していました。これは再び無視するように修正される予定ですが、それまでの間は次の回避策を検討してください。ResolveName の設定は、Azure Tables では必要無いのでclient application のから外してください。そうすると OData の “category” element は送信されません。

下記は問題が発生するコードの例です。これを実行するとサーバー側で失敗します。

CloudTableClient cloudTableClient = storageAccount.CreateCloudTableClient();
TableServiceContext tableServiceContext = cloudTableClient.GetDataServiceContext();
tableServiceContext.ResolveName = delegate(Type entityType)
{
// This would cause class name to be sent as the value for term in the category element and service would return Bad Request.
return entityType.FullName;
};

SimpleEntity entity = new SimpleEntity("somePK", "someRK");
tableServiceContext.AddObject("sometable", entity);
tableServiceContext.SaveChanges();

この Issue の解決のためには client 側でtableServiceContext.ResolveNamedelegate の設定を外してください。

  1. service updateの一環として、サーバー側で使っている新しい .NET WCF Data Services library は、$filter query の 一部に empty “cast” があると 400 (Bad Request) で拒否します。古い .NET framework libraryではそうではありませんでした。これによって、Windows Azure Storage Client Library 2.1のIQueryable implementationに影響が出ます。.NET の DataServiceContext の挙動を、cast を送信しないようにクライアントライブラリを修正中です。これは、数週間以内に利用できるようになります( this should be available in the next couple of weeks )それまでの間次の回避策を検討してください。このクライアントライブラリの問題では、IEnumerable<T> で ITableEntityインターフェイスに制約するのでは無く、インスタンス化される型を明示的に使うことで回避できます。

下記は問題があるコードです

static IEnumerable<T> GetEntities<T>(CloudTable table)  where T : ITableEntity, new()
{
    IQueryable<T> query = table.CreateQuery<T>().Where(x => x.PartitionKey == "mypk");
    return query.ToList();
}

このように書くと 2.1 storage client library の IQueryable interface は、下記のUriに展開されて新しい service updateでは、400 (Bad Request) で拒否されます

http://myaccount.table.core.windows.net/invalidfiltertable?$filter=cast%28%27%27%29%2FPartitionKey%20eq%20%27mypk%27&timeout=90

コードを下記のように変更してquery の castを取り除いてください。そうすれば、cast operator は送信されません

IQueryable<SimpleEntity> query = table.CreateQuery<SimpleEntity>().Where(x => x.PartitionKey == "mypk");
return query.ToList();

Uri request は下記のようになり、service に受け付けられます

http://myaccount.table.core.windows.net/validfiltertable?$filter=PartitionKey%20eq%20%27mypk%27&timeout=90

We apologize for these issues and we are working on a hotfix to address them. (我々はこれらの問題について謝罪し、我々はそれらに対処するための修正プログラムに取り組んでいます)

感想、コメント等

  • 最後の Uriに cast operatorが出てしまって、それがあるとサーバーではねられて 400 (Bad Request) というのは嵌りそうです。回避策もなかなか厳しい気がします。
  • コンテナの前が//になってるとSASが効かないっていうのは、自前でUriを作成していると起きそうな気がします。要注意ですね。
  • ResolveName の件は、DataServiceContextにあって、TableServiceContextでは動作に関係しないものという混乱の原因になりがちなやつです。元々意味無かったはずなので、外せば良いと思います。
]]>
Sun, 24 Nov 2013 00:00:00 +0900
http://kyrt.in/2013/11/24/windows_azure_tables_breaking_changes_2013_11.html http://kyrt.in/2013/11/24/windows_azure_tables_breaking_changes_2013_11.html <![CDATA[Windows Azure Tables の Breaking Changes 2013/11]]> Windows Azure Tables の Breaking Changes 2013/11
2013/11/29 以下の内容は正式な日本語訳が出ています Windows Azure テーブルにおける重大な(互換性のない)変更

Azure Storage Team より、Windows Azure Table のJSONサポート準備のため Table の response が一部変更されている旨アナウンスされました。基本的に、HTTP、AtomPubの規格内の変更で互換性を保てるように最大限の努力をしているということですが、自前のカスタムパーサーを書いている場合などは問題になるかもしれません。

Windows Azure Tables Breaking Changes (November 2013)から変更点を紹介します。

変更点

  1. 新しいリリースでは、AtomPub response は、XML要素の間に改行、空白がありません。
  2. AtomPub XML Responce内のXML element(title、idなど)は、順番が変更される場合があります。
  3. HTTP HeaderのContent-Typeに”type” placeholder が追加されました。例えば, query の response (point query以外) は、content type に charset と application/atom+xmlに、type=feedが追加されます変更前: Content-Type: application/atom+xml;charset=utf-8変更後: Content-Type: application/atom+xml;type=feed;charset=utf-8
  4. MIME type のセキュリティ リスクの削減のための新しいresponse headerX-Content-Type-Options: nosniffが返されます。参照:http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx

感想等

  • 手元のアカウントで確認してみたら既に上記の通りに変更されていました。事前に予告が欲しいです。
  • 改行、空白が無くなった件は、今までのは、「XMLが element毎に改行されインデントされてるようなフォーマットで人が読むわけではないのに転送データ量が増えてMOTTAINAI」と思ってたので妥当な変更な気がします。これは、普通のXML parsersを通していれば問題になることは無さそうですし。
  • XML element の順番の件は引っかかるとちょっと面倒ですが、元々AtomPubの仕様に沿ったものなので無茶な話ではないと思います。
  • AtomPub内のXML elementの順番に関しては、RFC 4287 The Atom Syndication Format 日本語訳が参考になります。feedの中のelementはどんな順番で出てきても良いことになっていますね。
  • HTTP ヘッダーの変更は、ここまでパースしていることがあまり無いような気がするので、「影響はあまり無いのかな」という気がします。

json対応に向けて着々と進んでいるのは、とても嬉しいです。

]]>
Sun, 24 Nov 2013 00:00:00 +0900
http://kyrt.in/2013/10/17/azure_java_sdk_long_value_filtering_bug.html http://kyrt.in/2013/10/17/azure_java_sdk_long_value_filtering_bug.html <![CDATA[Azure SDK for Java 0.4.6 long値のfilter BUG]]> Azure SDK for Java 0.4.6 long値のfilter BUG

Azure SDK for Java 0.4.6 では、Azure TableのプロパティをLong値で$filterした場合に、URL展開で数字の末尾の’L’が付かないという不備があります。その結果、MAX_INTより大きな値を条件にするとサーバーの処理がエラーになってしまいます。

この問題に気が付いたのは、0.4.4で、0.4.6でもまだ修正されていません。

修正して、Pull Requestを投げています。(2013/10/13)

#413 Long value filtering has error when value more than MAX_INT

Amago by takekazu, on Flickr

修正内容

edmType が、EdmType.INT64の場合に、値のpostfixに’L’を付けるように変更しました。

元の仕様

どこからこの’L’が出てきたかという話をチョットします。Azure Table REST APIは、OData の仕様に準拠しているのでリテラルの書式などはそれを見ると書いてあるはずです。ODataのPrimitive Data Typesでは、64bit整数は下記のように定義されていました。’L’ですね。

OData Primitive Data Types
Primitive Types Literal Form Example      
Edm.Int64 Represents a signed 64-bit integer value [-] [0-9]+L Example 1: 64L Example 2: -64L

odata.org 6. Primitive Data Typesより

念のため他のデータ型の実装も確認すると、Azure Tableでサポートされているデータ型でリテラル表記に癖があるEdm.Guid, Edm.DateTimeのあたりですは問題なさそうです。

]]>
Thu, 17 Oct 2013 00:00:00 +0900
http://kyrt.in/2013/10/15/windows_azure_plugin_for_eclipse_with_java.html http://kyrt.in/2013/10/15/windows_azure_plugin_for_eclipse_with_java.html <![CDATA[Windows Azure Plugin for Eclipse with JavaとPlay Framework 2.1]]> Windows Azure Plugin for Eclipse with JavaとPlay Framework 2.1

Pyay Framework 2.1のアプリを作ってWindows AzureにDeployするまでを簡単に流します。Java, Play Frameworkに付いてある程度知識があって、Windows Azureを使ってみようという人を前提としています。

必要環境

確認は下記の環境で行いました。

  1. Windows 8
  2. Java Developer Kit (JDK), v1.7
  3. Eclipse IDE for Java EE Developers Kepler
  4. Windows Azure SDK 2.1
  5. Play Framework 2.1

Windows Azure Plugin for Eclipse with JavaとPlay Framework 2.1の開発環境としては、JDKは1.6以降、Eclipseは、 Indigo 以降がサポートされています。Windows Azure SDKは最新(2.1)が必要です。Play Frameworkに関しては2.1.5で試しましたが、他のバージョンとの互換性は確認していません。

Windows Azure SDK 2.1のインストール

Windows Azure SDK 2.1は、Web Platform Installer 4.6経由で入れるのがお勧めです。少し慣れないと分かりづらいので説明します。

Web Platform Installerを起動して、右上の検索ボックス①にazure sdk 2.1と入力して改行すると検索結果が表示されます。その中のWindows Azure SDK 2.1② を「インストールする」して下さい。(画面はインストール後になってしまっているのですが、右端のインストールボタンを押すとインストール候補として選択されます)Visual Studio用のツールなど複数表示されますが、今回必要なのは、Windows Azure SDK 2.1だけです。

画面下の「インストール」ボタン③を押すと処理が始まります、この時に必要な依存関係も同時にインストールされます。

../../../_images/2013_10_webpi002.png

もっと簡単な方法

This plugin requires Windows Azure SDK 2.1. This can be downloaded using theWeb Platform Installer (WebPI) 経由でWindows Azure SDK 2.1をインストールするplugin(exe)も配布されています。http://go.microsoft.com/fwlink/?LinkID=252838このリンク先さからダウンロードされるEXEを起動するとSDKのインストールが自動的に始まります。どちらの方法でインストールしても同じものが入ります。

Windows Azure Plugin for Eclipse with Java (by Microsoft Open Technologies)

次に、Microsoft Open Technologiesが作っている Windows Azure Plugin for Eclipse with Java を入れます。現在(2013/10/15)の最新版は、2.1.1です。プラグインのインストールは、通常のものと同じに、Help メニューのInstall New Softwareから行います。

レポジトリとして、http://dl.msopentech.com/eclipseを追加すると、Windows Azure Toolkit for Javaが表示されます。

../../../_images/2013_10_javaplugininstall002.png

必要に応じてライブラリを選択してください。今回はとりあえず、全部選択します。

サポートされているライブラリの種類

Windows Azure Plugin for Eclipse with Javaが、EclipseのUIとツールを提供するもので、その他のものはAzureのAPIをラップしたクラスライブラリです。

  • Microsoft JDBC Driver 4.0 for SQL Server: SQL Database 用のコンポーネント
  • Package for Apache Qpid Client Libraries for JMS (by MS Open Tech): Azureのメッセージングサービス向けのJMS client library (Apache Qpid project が元になっています)
  • Package for Windows Azure Libraries for Java (by MS Open Tech): このコンポーネントは、Windows Azure でスケーラブルなクラウドコンピューティングを実現するためのライブラリを提供
  • Windows Azure Access Control Services Filter (by MS Open Tech): このコンポーネントはWindows Azure ACS を使った認証アプリケーション向け
  • Windows Azure Common Plugin (by MS Open Tech): 他のこのコンポーネントとの共通コンポーネント
  • Windows Azure Plugin for Eclipse with Java (by MS Open Tech): このコンポーネントは、project configuration logic、the publish-to-cloud wizard、と user interfaceを含む

インストールに異常に時間がかかる場合は、Contact all update sites during install to find required softwareのチェックを外してみてください。

ここまでの内容は、Installing the Windows Azure Plugin for Eclipse with Java (by Microsoft Open Technologies)に詳しく書いてありますので、そちらも参照してください。

Play Frameworkのプロジェクト作成からEclipseへの取り込へ

動作確認のためにPlay Frameworkのプロジェクトを作成して、Eclipseへ取り込みます。

MyFirstAppという名前で、play frameworkのアプリを作ります。今回は全くコードは書かないので関係ありませんが言語はJavaを選択します:

$ play new MyFirstApp
       _            _
 _ __ | | __ _ _  _| |
| '_ \| |/ _' | || |_|
|  __/|_|\____|\__ (_)
|_|            |__/

play! 2.1.5 (using Java 1.7.0_25 and Scala 2.10.0), http://www.playframework.org

The new application will be created in C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp

What is the application name? [MyFirstApp]
>

Which template do you want to use for this new application?

  1             - Create a simple Scala application
  2             - Create a simple Java application

> 2
OK, application MyFirstApp is created.

Have fun!

eclipseのプロジェクトを作ります。先ほど作成したアプリのディレクトリに移動してeclipseのプロジェクトを作成します。 普通の開発ならば、eclipse with-source=trueの方が良いかもしれませんが、今回はダウンロード時間の節約でソースは持ってきません:

$ cd .\MyFirstApp
$ play
[info] Loading project definition from C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\project
[info] Set current project to MyFirstApp (in build file:/C:/Users/Takekazu/Documents/GitHub/sandbox/java/play002/MyFirstApp/)
       _            _
 _ __ | | __ _ _  _| |
| '_ \| |/ _' | || |_|
|  __/|_|\____|\__ (_)
|_|            |__/

play! 2.1.5 (using Java 1.7.0_25 and Scala 2.10.0), http://www.playframework.org

> Type "help play" or "license" for more information.
> Type "exit" or use Ctrl+D to leave this console.

[MyFirstApp] $ eclipse
[info] About to create Eclipse project files for your project(s).
[info] Updating {file:/C:/Users/Takekazu/Documents/GitHub/sandbox/java/play002/MyFirstApp/}MyFirstApp...
[info] Done updating.
[info] Compiling 4 Scala sources and 2 Java sources to C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\target\scala-2.10\classes...
[info] Successfully created Eclipse project files for project(s):
[info] MyFirstApp
[MyFirstApp] $ exit

AzureのDeploy用のパッケージ(cspkg)に入れるためアプリの配布用zipを作成します:

$ play dist
[info] Loading project definition from C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\project
[info] Set current project to MyFirstApp (in build file:/C:/Users/Takekazu/Documents/GitHub/sandbox/java/play002/MyFirstApp/)
[info] Packaging C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\target\scala-2.10\myfirstapp_2.10-1.0-SNAPSHOT-sources.jar ...
[info] Done packaging.
[info] Generating Scala API documentation for main sources to C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\target\scala-2.10\api...
[info] Wrote C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\target\scala-2.10\myfirstapp_2.10-1.0-SNAPSHOT.pom
[info] Packaging C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\target\scala-2.10\myfirstapp_2.10-1.0-SNAPSHOT.jar ...
[info] Done packaging.
model contains 17 documentable templates
[info] Scala API documentation generation successful.
[info] Packaging C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\target\scala-2.10\myfirstapp_2.10-1.0-SNAPSHOT-javadoc.jar ...
[info] Done packaging.

Your application is ready in C:\Users\Takekazu\Documents\GitHub\sandbox\java\play002\MyFirstApp\dist\myfirstapp-1.0-SNAPSHOT.zip

[success] Total time: 8 s, completed 2013/10/15 14:57:13
$

この時に、Your application is ready inの行に表示される zip ファイル名(以下 dist zip名)をメモして置いて下さい、この前で使います。

eclipseを起動して、プロジェクトをimportします。

../../../_images/2013_10_eclipse002.png

これで、サンプルのplay frameworkのプロジェクトの作成とビルドが終わりました。この先は、Azure 用のプロジェクトを作成に入ります。

参考:Setting up your preferred IDE

Azure 用のProjectの作成

ツールバーのNew Windows Azure Deployment Projectを押します。

../../../_images/2013_10_eclipse003.png

New Windows Azure Deployment Projectの設定POPUPが開きます。Project Nameを入れます。今回は、MyAzureProjectにしました。例では、default locationを変更してplay frameworkのプロジェクトの横のディレクトリに持ってきていますが、プロジェクトの場所はどこでも構いません。

../../../_images/2013_10_eclipse004.png

Nextを押すと、JDKの設定に移ります。Emulator deployment と書いてある部分が、Emulatorを使った場合に利用されるJDKの設定で、Cloud deployment の部分がクラウド上(Azure環境)で使われるJDKの設定です。Deploy my local JDKを選択すると、Emulatorで使うように設定したものを自動的にCloudにアップロードしてクラウド上でも同じものを使うようになります。今回は、ローカルのJDK 1.7を両方で使うように設定しています。この画面ではJDKの設定しかしません。SeverとApplicationは何も触らずにFinish のボタンを押します。

../../../_images/2013_10_eclipse005.png

下記のような内容のプロジェクトが作成されます。

../../../_images/2013_10_eclipse006.png

プロジェクトのWorkerRole1へMyFirstAppのdist zipを追加する

WorkerRole1を選択してプロパティを開き、Windows Azure RoleのTreeを開いてComponentsを選びます。コンポーネントリストにHelloWorld.warがありますが、不要なのでremoveします。その後Addを押してMyFirstAppのdist zipを追加します。

../../../_images/2013_10_eclipse007.png

「Windows Azure Role Component」のpopupをでは、Import into packageのFrom Pathの部分に、dist zip のフルパス名を入れます。Methodは、copyを選択、As Nameは、dist zipのファイル名入れます(ここは、From Pathのファイル名部分がデフォルトで入力されるはずです)その下の、Deploy from packageの設定は、Methodをunzip、To directoryを.にしてください。今回

../../../_images/2013_10_eclipse008.png

環境変数の追加

dist zip名をRoleの実行タスクに渡す良い方法が無かったので、環境変数を使います。環境変数名ZIP_NAMEにdist zipのbase名(今回は、myfirstapp-1.0-SNAPSHOT)を定義します。

../../../_images/2013_10_eclipse009.png

EndPointを変更

play frameworkアプリのデフォルトの待ち受けポートが9000なので、EndPointを9000に変更します。publicで定義されているのがAzure のload brancer がインターネット上で公開しているポート番号で、privateがAzure インスタンスでアプリが待ち受けているポート番号です。play frameworkアプリのデフォルトの待ち受けポートが9000なので、EndPointを9000に変更します。Azure Load brancerがこの定義に基いてポート変換を実行します。

../../../_images/2013_10_eclipse010.png

scriptの変更

MyAzureProject/WorkerRole1/approotにあるstartup.cmdとrun.cmdを下記のように変更します。

startup.cmd:

del /q run_body.cmd
powershell -ExecutionPolicy RemoteSigned -f replace.ps1 run_body.cmd.template > run_body.cmd

run.cmd:

rem @ECHO OFF

set _SLEEPLENGTH=15000
set _FILENAME=run_body.cmd

@REM Create a temporary sleep script in VBScript
echo WScript.sleep(%_SLEEPLENGTH%) > %Temp%\_mysleep.vbs

:Loop
if exist %_FILENAME% (goto:StartToRun)

cscript /Nologo %Temp%\_mysleep.vbs
goto:Loop

del %Temp%\_mysleep.vbs

:StartToRun
call %_FILENAME%

replace.ps1と、run_body.cmd.templateの2つファイルを追加します。

replace.ps1:

cat $args[0] | % {$l = $_ -creplace '__JAVA_HOME__',"$Env:JAVA_HOME"; "$l" } | % {$l = $_ -creplace '__ZIP_NAME__',"$Env:ZIP_NAME"; "$l" }

run_body.cmd.template:

set JAVA_HOME=__JAVA_HOME__
set ZIP_NAME=__ZIP_NAME__

set PATH=%JAVA_HOME%\bin;%PATH%

setlocal
set d=%~dp0
set d=%d:\=/%
java %* -cp "%d%/%ZIP_NAME%/lib/*;" play.core.server.NettyServer %d%

Emulatorでの実行

これで準備ができました。Run In Windows Azure Emurator`を押してEmulatorでの実行します。成功すると、80と9000のポートで結果を見ることができます。80はAzure SDKに付属のCompute Emulator経由で、9000はPlay Frameworkの待受を見ていることになります。また、EndPointの設定で80にしていますが、Compute Emulatorが起動時に、既に80が使われていた場合は順次ポート番号をインクリメントしていき空いているポートを利用します。

../../../_images/2013_10_eclipse011.png

Emulatorの管理画面が同時に起動します。Windows Azure Compute Emurator のウインドウを開いてWorkerRole1の0を選択すると、コンソール画面が表示されます。

../../../_images/2013_10_eclipse012.png

Azure環境へのDeploy

Publish to Windows Azure Cloudを押してAzure環境にDeploy します。必要に応じて、StorageとCloud Serviceを作成してください。

../../../_images/2013_10_eclipse013.png

この設定だと、Azure環境ではAzure LoadBarancerが介在して外部から port 80で見えます。

]]>
Tue, 15 Oct 2013 00:00:00 +0900
http://kyrt.in/2012/12/26/asc2_dot_0asyncbug.html http://kyrt.in/2012/12/26/asc2_dot_0asyncbug.html <![CDATA[Azure Storage Client 2.0 CompletedSynchronously FIX]]> Azure Storage Client 2.0 CompletedSynchronously FIX

以前の記事Azure Storage Gen 2は速かったの補足です。その中の非同期で同時接続数が上がらない?で、

このコードを動かしてみたら、「単一スレッド+非同期の組み合わせだと、おおよそ2から3程度のコネクションしか作成されない」ことに気が付きました。場合によっては、5ぐらいまで上がることもあるようですが、どうしてこうなるのか不思議です。これは、Azure Storage Client 2.0のBUGだったようです。2.0.2で修正されています。

と書きました、結局執筆時点でのAzure Storage Client 2.0.1にはBUGがあり、後日2.0.2で修正されたことが分かりました。少々混乱したのでここに顛末をまとめます。

candle by takekazu, on Flickr

BUGの内容

BUGの内容としては、非同期メソッドが返すIAsyncResultオブジェクトのCompletedSynchronouslyプロパティが一貫性の無い値になっていて、その結果、TaskFactory.FromAsyncが正しく動作しないというものでした。


再現試験

まずは、2.0.1での問題の再現性の確認し、2.0.3で解決されているのかを検証します。コードは[前の記事] (Azure Storage Gen 2は速かった) とほとんど同じですが、なるべく簡略化したものにしています。

まずは、APM (Asynchronous Programming Model)パターンの非同期メソッドをTask.FromAsync()でラップしてExecuteAsyncメソッドを作ります。今回問題となっているのは、CloudTable.BeginExecute から、AsyncCallback を呼び出すときに渡すIAsyncResultオブジェクトのCompletedSynchronouslyプロパティです。ちょと問題があるような気がしますが、今回はこれで行きます。

このExecuteAsyncを使って指定回ループしてテーブルにエンティティをInsertします。

このコードは、Insertの数だけ、Taskが生成されて全部まとめてWaitしています。これを、.NET 4.0でやるとTask毎にWait Handleを確保するので非常に効率が悪いですが、.NET 4.5では、Waitの数しかリソースを使わないので、そんなに悪くありません。それでも件数に応じて使用メモリーが増えるので本番で使うのはあまりお勧めできないコーディングパターンです。

.NET 4.5のTask回りの変更については、このBlogの記事「C#たんっ! 新機能が入るまで」から読み始めるのがお勧めです、必要な部分へのリンクが張られています。

2.0.1 で動かす

このコードを、Azure Storage Client 2.0.1 で動かしてみます。ライブラリのバージョンを指定するには、nugetを使うと便利です。もし、すでにAzure Storage Client が入っていたら下記のように削除してからバージョンを指定して入れ直します。

> Uninstall-Package WindowsAzure.Storage –RemoveDependencies
> Install-Package  WindowsAzure.Storage -Version 2.0.1

これで動かします。非同期メソッドが本当に非同期で動いているかどうかの確認はUIならUI Threadがブロックされていているかどうかなどで分かり易いのですが、サーバーサイドのプログラム(今回コンソールですが)ではちょっと見には分かりません。このコードはAzure Storageとの間でSocketを張っているのでTCP/IP接続の数を見ることで並列度が分かります。また、ネットワーク転送速度(Send)も参考になります。

Azure Storage Client 2.0.1 時のResource Moniter画面

2.0.1時のResource Moniter画面

見事に接続数が伸びません。

2.0.3では?

これを、2.0.3 でビルドし直します。2012/12/24現在の最新が2.0.3でバージョン指定しないと最新版が落ちてきます。

> Uninstall-Package WindowsAzure.Storage –RemoveDependencies
> Install-Package  WindowsAzure.Storage

Azure Storage Client 2.0.3 時のResource Moniter画面

2.0.3時のResource Moniter画面

結論

劇的にコネクション数が変わります。画面だとコネクションの数ははっきりとわかりませんが、 2.0.1 の時の画面と全く違っているのがわかると思います。数を数えると開始直後に1000接続以上が作成されます。これで、2.0.1の実装には問題があり、非同期メソッドを使ってもほとんど非同期に実行されてなかったこと、それが、2.0.3では修正されていることが確認できました。

ちなみに、今回確認はしていませんが、以前に1.4のAzure Storage Clientを試した時には非同期メソッドで同時接続数が少なくて困るという問題は無ありませんでした、2.0で発生したBUGで2.0.2でFIXということのようです。


次の問題

万事解決、良かった良かったと言いたいところですが別の問題が起きます。並列度があがったのは良いのですが、コネクションを張りすぎてExceptionが大量に発生します。

Azure Storage Client 2.0.3 時でのException

Azure Storage Client 2.0.3 時でのException

何らかの方法で、並列度を制限しないと実用的ではありません。特にバッチの中で非同期呼び出しを使う場合などはこれは致命的です。

ここでは、Blob でのUpload処理が参考になります。Windows Azure Storage 2.0 の Blob Uploadで参照している処理を見ると、Semaphoreを使って非同期処理には入れる数を制御していますので、これを参考にします。

Semaphoreを使う

上記の処理方法に習って、Semaphoreを使って同時実行数を制御します。SemaphoreSlim という便利がものがあるのでそれを使います。こうすることで、同時実行数を制御することがでます。とりあえず100で制限します。これで普通に動きます。


まとめ

  1. Azure Storage Client 2.0 は、2.0.2で非同期周りのBUGが直っている。
  2. 非同期呼び出しをループ内で使うと過剰にリソースを消費することがある。
  3. 同時実行数を制御するにはSemaphoreを使うと制限できる。
]]>
Wed, 26 Dec 2012 00:00:00 +0900
http://kyrt.in/2012/12/22/azurevmfix1221.html http://kyrt.in/2012/12/22/azurevmfix1221.html <![CDATA[Azure Virtual MachineのDISK性能]]> Azure Virtual MachineのDISK性能

twitterで、「Azure VMのLinuxを21日以降作るか、更新手順を実施するとパフォーマンスが改善されるらしー」というのを読んで、以前DISK性能を調べ始めてそのまま放置していたのを思い出した。Azure Ubuntu 12.04 iozone 速報 2012/7/4

Azure Storageの非同期と同期の比較をしようと始めたのだけど、なかなか手間取って進まない。ちょっと寄り道して速くなったというAzure VMを試してみることにした。

前のVMは消してしまったので、新たにインストールし直すところから始める。AzureのポータルからUbuntuをインストールして、DataDiskを接続するあたりまでは他に任せてubuntuが起動した後から書いていきます。

Triangle by takekazu, on Flickr

Ubuntu 環境の準備

基本的には、前回と同じになるようにします。だたUbuntuを12.10にして、data diskのホストキャッシュの設定を変えて3つのDISKを接続して測定しました。以前の測定:Azure Ubuntu 12.04 iozone 速報 2012/7/4ホストキャッシュの設定はポータルからはできずに、デフォルトでした。その時(2012/7/4)は、ホストキャッシュ無しがデフォルトだったと思うのですが、ちょっとドキュメントが見つからないので前の結果は参考程度にしてください。

Azure iDC は、West USで、2 coreのインスタンス(M)を使いました。Sにするか少し考えたのですが、クラウドサービスについては、I/O パフォーマンスがXS、Sでは制限されているのでMを使うことにしました。参考:Windows Azure の料金と、請求の計測単位の詳細

正確には、今回試そうとしているVirtual Machine はまだ Previewで Cloud Serviceと同じような制限になるかは情報が公開されていない(私は知らないだけかもしれませんが)のですが、同じになってそうな気がしたのでMにしました。

ちょっと古いものでは、仮想マシンのサイズの構成方法という情報もあります。

インスタンスの選択で考慮する必要があると思われるのは、「Data Diskはネットワーク経由で接続されるく、ソフトウェアで処理する部分が多い=CPUを使う」ということです。従ってネットワーク帯域制限やCore数の影響を無視できないはずです。XSやSのインスタンスだと何を測定しているのか不安になる気がしたのでMを選択しました。実際どのインスタンスサイズの程度影響があるのかは興味ありますが未測定です。

ざっと流すと、以下のような手順踏んで用意をします。

  1. Ubuntu 12.10 を、azure portalから、virtual machineイメージをインストール
  2. data disk を、256Gで3つ作成、/dev/sdc, sdd, sdeを確認、キャッシュをそれぞれ「なし、読み取り専用、読み取り/書き込み」と指定
  3. fdiskして、/dev/sd[cde]1にext4でfilesystemを作成し/mnt/data, /mnt/data1, /mnt/data2へmount
  4. apt-get update, upgrade して最新に更新
  5. /etc/apt/sources.list で、multiverse を追加(コメントを外しただけ)
  6. apt-get install iozone3 でインストール

ディスク構成

表1 ディスク構成
ディスク 種類 ホスト キャッシュ サイズ 備考
/dev/sda OS ディスク 読み取り/書き込み 30GB  
/dev/sdc データ ディスク なし 256GB  
/dev/sdd データ ディスク 読み取り専用 256GB  
/dev/sde データ ディスク 読み取り/書き込み 256GB  

手順

今後の再テストのためのメモも兼ねて、コマンドをラインに流したもの抜粋を挙げておきます。(以下sudo省略)

  1. ポータルで256Gでdata diskを作成して接続を確認
$ dmesg | grep -e "\[sd[a-z]\]"
sd 2:0:0:0: [sda] 62914560 512-byte logical blocks: (32.2 GB/30.0 GiB)
sd 2:0:0:0: [sda] Write Protect is off
sd 2:0:0:0: [sda] Mode Sense: 0f 00 10 00
sd 2:0:0:0: [sda] Write cache: enabled, read cache: enabled, supports DPO and FUA
sd 2:0:0:0: [sda] Attached SCSI disk
sd 3:0:1:0: [sdb] 283115520 512-byte logical blocks: (144 GB/135 GiB)
sd 3:0:1:0: [sdb] Write Protect is off
sd 3:0:1:0: [sdb] Mode Sense: 0f 00 10 00
sd 3:0:1:0: [sdb] Write cache: enabled, read cache: enabled, supports DPO and FUA
sd 3:0:1:0: [sdb] Attached SCSI disk
sd 6:0:0:0: [sdc] 536870912 512-byte logical blocks: (274 GB/256 GiB)
sd 6:0:0:0: [sdc] Write Protect is off
sd 6:0:0:0: [sdc] Mode Sense: 0f 00 10 00
sd 6:0:0:0: [sdc] Write cache: enabled, read cache: enabled, supports DPO and FUA
sd 6:0:0:0: [sdc] Attached SCSI disk
sd 6:0:0:1: [sdd] 536870912 512-byte logical blocks: (274 GB/256 GiB)
sd 6:0:0:1: [sdd] Write Protect is off
sd 6:0:0:1: [sdd] Mode Sense: 0f 00 10 00
sd 6:0:0:1: [sdd] Write cache: enabled, read cache: enabled, supports DPO and FUA
sd 6:0:0:1: [sdd] Attached SCSI disk
sd 6:0:0:2: [sde] 536870912 512-byte logical blocks: (274 GB/256 GiB)
sd 6:0:0:2: [sde] Write Protect is off
sd 6:0:0:2: [sde] Mode Sense: 0f 00 10 00
sd 6:0:0:2: [sde] Write cache: enabled, read cache: enabled, supports DPO and FUA
sd 6:0:0:2: [sde] Attached SCSI disk
  1. parted で全セクタを使ってパーテーションを作成
$ sudo parted /dev/sdc --script mklabel gpt
$ sudo parted /dev/sdc --script 'mkpart disk1 ext4 1M -1'
$ sudo parted /dev/sdc --script 'print'

Model: Msft Virtual Disk (scsi)
Disk /dev/sdc: 275GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End    Size   File system  Name   Flags
1      1049kB  275GB  275GB  ext4         disk1

$ sudo parted /dev/sdd --script mklabel gpt
$ sudo parted /dev/sdd --script 'mkpart disk2 ext4 1M -1'
$ sudo parted /dev/sdd --script 'print'

___ snip ___

$ sudo parted /dev/sde --script mklabel gpt
$ sudo parted /dev/sde --script 'mkpart disk3 ext4 1M -1'
$ sudo parted /dev/sde --script 'print'

___ snip ___

$ sudo mkfs.ext4 /dev/sdc1
___ snip ___
$ sudo mkfs.ext4 /dev/sdd1
___ snip ___
$ sudo mkfs.ext4 /dev/sde1
___ snip ___
  1. mount point 作って、/mnt/resouceとともにパーミッションを変更
$ mkdir /mnt/data1 /mnt/data2 /mnt/data3
$ chmod a+wrx /mnt/data*
$ chmod a+wrx /mnt/resource
$ ls -l /mnt/
total 20
drwx------ 3 root root 4096 Dec 21 15:56 cdrom
drwxrwxrwx 2 root root 4096 Dec 22 21:04 data1
drwxrwxrwx 2 root root 4096 Dec 22 21:04 data2
drwxrwxrwx 2 root root 4096 Dec 22 22:47 data3
drwxrwxrwx 4 root root 4096 Dec 21 22:14 resource
  1. とりあえず、マウントして確認
$ sudo mount -t ext4 /dev/sdc1 /mnt/data1
$ sudo mount -t ext4 /dev/sdd1 /mnt/data2
$ sudo mount -t ext4 /dev/sde1 /mnt/data3

$ df -T
Filesystem     Type     1K-blocks    Used Available Use% Mounted on
/dev/sda1      ext4      30953664 1142056  28539332   4% /
udev           devtmpfs   1751196      12   1751184   1% /dev
tmpfs          tmpfs       704872     280    704592   1% /run
none           tmpfs         5120       0      5120   0% /run/lock
none           tmpfs      1762172       0   1762172   0% /run/shm
none           tmpfs       102400       0    102400   0% /run/user
/dev/sdb1      ext4     139334632  192000 132064848   1% /mnt/resource
/dev/sdc1      ext4     264221700  191576 250608456   1% /mnt/data1
/dev/sdd1      ext4     264221700  191576 250608456   1% /mnt/data2
/dev/sde1      ext4     264221700  191576 250608456   1% /mnt/data3
  1. 再起動してもマウントされるように、UUIDを確認して /etc/fstab に追加。
$ blkid
/dev/sda1: LABEL="cloudimg-rootfs" UUID="56d8a977-c1fe-461e-a328-b19fc47c743f" TYPE="ext4"
/dev/sdb1: UUID="d063d8a2-32fc-486c-a9b4-e6bcf7e5deae" TYPE="ext4"
/dev/sdd1: UUID="88f28b19-fdc6-46dc-a2d7-2daa1754754f" TYPE="ext4"
/dev/sdc1: UUID="a1cb5045-178a-476e-9821-084f8f6d92a6" TYPE="ext4"
/dev/sde1: UUID="15b8b45e-fbd0-4efc-9534-5e38b1877828" TYPE="ext4"

$ vi /etc/fstab

___ snip ___

$ cat /etc/fstab
UUID=56d8a977-c1fe-461e-a328-b19fc47c743f       /        ext4   defaults        0 0
UUID=a1cb5045-178a-476e-9821-084f8f6d92a6       /mnt/data1        ext4   defaults        0 0
UUID=88f28b19-fdc6-46dc-a2d7-2daa1754754f       /mnt/data2        ext4   defaults        0 0
UUID=15b8b45e-fbd0-4efc-9534-5e38b1877828       /mnt/data3        ext4   defaults        0 0
  1. 最新にして再起動する
$ apt-get update
___ snip ___
$ apt-get upgrade
___ snip ___

$ shutdown -r now
  1. iozone3 を入れる

/etc/apt/sources.list を変更して、multiverse を追加(コメントを外しただけ)

$ vi /etc/apt/sources.list

___ snip ___

$ apt-get update
$ apt-get install iozone3

測定

これで環境が出来たので測定します。基本的には、iozone 一発で細かいオプションの指定はしません。なんとなく、Excelファイルにしたのですが、面倒になるだけであまりメリットは無かったかもしれません。

$ iozone -Ra -f /mnt/resource/tmp/test -b sdb2-001.xls -s 1g
$ iozone -Ra -f /mnt/data1/tmp/test -b sdc1-001hcnone.xls -s 1g
$ iozone -Ra -f /mnt/data2/tmp/test -b sdd1-001hcro.xls -s 1g
$ iozone -Ra -f /mnt/data3/tmp/test -b sde1-001hcrw.xls -s 1g

結果のファイル(zip)

iozone の実行結果

iozoneの測定結果をローカルドライブ、Data Diskの順で見ていく。それぞれの結果を図にした。

ローカルディスクの性能

まずは、ローカルドライブの実行結果から見る。読み込みはレコードサイズが8Kあたりから256KBまでは、2,500,000 KB/sec - 3,000,000 KB/sec で、レコードサイズが増えていくとだんだん遅くなっていく。書き込み側は同じ軸ではとスケールが違い過ぎてよくわからない。

図1 /dev/sdb2 ローカルドライブ 2012/12/22 測定

図1 /dev/sdb2 ローカルドライブ 2012/12/22 測定

そこで、書き込みの系統だけを表示させた。Record Rewriteの結果が桁外れに速い。これは「Iozone Filesystem Benchmark Download Documentation」 によると、同じ内容を繰り返し書き込むテストということなのでキャッシュの効果だろうと思われる。

図1-1 /dev/sdb2 ローカルドライブ 書き込みのみ表示(1) 2012/12/22 測定

図1-1 /dev/sdb2 ローカルドライブ 書き込みのみ表示(1) 2012/12/22 測定

さらによく見ると、同じ再書き込みでも、Rewrite、Recoed Rewrite、Refwriteの違いがなかなか興味深い。Recoed Rewriteだけがリード並に桁外れに速い。Rewrite、Refwriteはファイル単位の再書き込みで、Recoed Rewriteは特定レコードの再書き込み(Iozone Filesystem Benchmark Download Documentationから)ということなので、キャッシュが利く場合は限定されてるらしいことがわかる。同じものを繰り返し書き込むというのは、現実にはあまり無いことなので、Rewriteをグラフから外して、書き込みのパフォーマンスを見やすくてみる。

図2 /dev/sdb2 ローカルドライブ  書き込みのみ表示(2) 2012/12/22 測定

図2 /dev/sdb2 ローカルドライブ 書き込みのみ表示(2) 2012/12/22 測定

小さいブロックのランダム書き込みが苦手だということがわかる。これはHDDの一般的な傾向で納得できる。以降では書き込みの図は図2と同じデータ項目を表示する。

Data Diskの性能

話題のData Diskの性能に入る。ローカルドライ比較で、読み込みはほぼ同等な性能だったが、書き込みは半分程度の性能しか出ていない。ホストキャッシュの設定で大きな違いが出ることを期待したが図を見る限りでは顕著な違いというほどの差異は認められなかった。

図3 /dev/sdc1 ホストキャッシュなし 2012/12/22 測定

図3 /dev/sdc1 ホストキャッシュなし 2012/12/22 測定

図4 /dev/sdc1 ホストキャッシュなし 2012/12/22 書き込みのみ表示  測定

図4 /dev/sdc1 ホストキャッシュなし 2012/12/22 書き込みのみ表示 測定

図5 /dev/sdd1 ホストキャッシュ 読み取り専用 2012/12/22 測定

図5 /dev/sdd1 ホストキャッシュ 読み取り専用 2012/12/22 測定

図6 /dev/sdd1 ホストキャッシュ 読み取り専用 書き込みのみ表示 2012/12/22 測定

図6 /dev/sdd1 ホストキャッシュ 読み取り専用 書き込みのみ表示 2012/12/22 測定

図7 /dev/sde1 ホストキャッシュ 読み取り/書き込み 2012/12/22 測定

図7 /dev/sde1 ホストキャッシュ 読み取り/書き込み 2012/12/22 測定

図8 /dev/sde1 ホストキャッシュ 読み取り/書き込み 書き込みのみ表示 2012/12/22 測定

図8 /dev/sde1 ホストキャッシュ 読み取り/書き込み 書き込みのみ表示 2012/12/22 測定


結論

iozoneという選択肢がどうだったのかという気もするが、なかなか調子が良い。最初にパフォーマンス向上的な話ではなじめたが、7/4の結果に比べて劇的に変わっているという気はしない。もう少しデータを精査する必要を感じるが、思ったより長くなりすぎたので、また別の方法を絡めて再考してみようと思う。

今回、テスト自体は一回しか走らせていないので再試験して結果を公開してもらえる嬉しい。今まで、Azure Tableの性能評価をした時も、何度か走らせると結果が違ったり、いつの間にかパフォーマンスが改善されたりなどすることがあるので、明確な数字を出すことは難しい。しかし、いろいろなパターンの性能情報があると設計時の精度もあがるので、この手の情報は重要とは思う。

Bookmarks

  1. Iozone Filesystem Benchmark
  2. Iozone Filesystem Benchmark Download Documentation
  3. IOzoneによるファイルシステムのパフォーマンス測定
  4. Azure Ubuntu 12.04 iozone 速報 2012/7/4
]]>
Sat, 22 Dec 2012 00:00:00 +0900
http://kyrt.in/2012/12/08/blobasyncinside.html http://kyrt.in/2012/12/08/blobasyncinside.html <![CDATA[Windows Azure Storage 2.0 の Blob Upload]]> Windows Azure Storage 2.0 の Blob Upload

前の記事Azure Storage Gen 2は速かったでは非同期呼び出しを使っていますが、これには理由があります。以前(2010年ぐらい)、Windows Azureを使い始めたころにSorage Client 1.xと、.NET Framework 4.0の組み合わせでいろいろ試した時には、スレッドを上げてやったのと非同期にしてやったので比べた時には有意な違いは出ませんでした。非同期でコードを書くと面倒になることも多かったので、「手間の割にはあまりメリットは無いなあ」というのが当時の結論だったのです。

ところが、2012年10月の末にAzure Storage Client 2.0が出てAPIや実装が大幅に変わったので変更点を眺めていたら面白いことに気が付きました。2.0ではBlobの書き込みは、Stream.WriteToSync()でやっていて、そのWriteToSyncの中が非同期呼び出しで実装されているとか、非同期呼び出し数をセマフォを使って制限しているところなどなかなか良さげな実装になっています。

ある日、AzureLargeFileUploaderというのがGitHubに上がっているのに気が付いて中を見てみたら、前に読んだSDKの実装に比べても、そんなに優れているようには見えません。「あのコードより2.0の実装の方が大きなファイルでも効率的にUploadできるはず、もしかしたら2.0のコードは壊れているのからこんなことしてるのかな?」と思い2.0のコードを動かして実際に試して見ました。やってみたらなかなか調子が良く2.0の実装では十分な速度でBlobにアップロードされます。

C# 5.0で await/asyc もサポートされ .NET 4.5になってTask周りも改善されて非同期を使うには良い環境が揃ってきていると感じました。それで改めて非同期呼び出しを使ってみることにしました。

shibuya by takekazu, on Flickr

試行(やってみた)

Azure Datacenter内にLargeのインスタンスを用意して適当なファイルを元にして8GBのファイルを用意しました。そのファイルを同一のBlobに4回アップロードして平均の速度を測定します。結果は、 ** 平均473Mbps ** でした。これは、ほぼインスタンスのネットワーク帯域制限値と同じです。なかなか良い結果と言えます。

確認に使ったコード、(メッセージがドイツ語になっているのは、AzureLargeFileUploader の名残です)

このコードのポイントは下記の3点です。

  1. 18行目ので接続数の制限を1024に設定していること
  2. 59行目で並列度の設定をコア数の12倍にしていること
  3. 55,56行目ではPage/Block Blobのどちらを使うかを切り替えていること

接続が作れないと並列度が上がらないのでDefaultConnectionLimitを増やし、Storage Client 2.0ではParallelOperationThreadCount のデフォルトが1になっているのでコア数の12倍に設定します。Storage Client 2.0では、55, 56行目のように切り替えるだけで、どちらでも並列アップロードができるようになっています。1.xのときは、UploadFromStreamを使った時にBlock Blobでしか並列アップロードがサポートされてなかったことに比べて改善されています。

アップロード中をリソースマネージャーで観察するとコネクションが数多く作成されているのが確認できます。右側のNetworkトラフィックのグラフが波打っているのが興味深いところです。ピーク時に600-700Mbps程度行くこともありますが平均すると470 Mbpsという結果でした。CPUは5-10%程度しか使われていませんし、メモリーも開始から終了までほぼ一定です。なかなか優秀です。

../../../_images/2012-08-screen01.png

![Resource Monitor](/images/2012-08-screen01.png)


** ここからは、ソースを見ながら確認していった過程のメモです。リンクばかりで分かり辛いかもしれませんが参考までに。興味深いのは非同期と同期の処理の境界と並列度の制限をしている部分です。 **


どうしてこんなところが変わったの? ParallelOperationThreadCount のデフォルト値

1.x では

CloudBlobClientに、ParallelOperationThreadCount というのがあります。1系では、下記のように定義されていました。

StorageClient/CloudBlobClient.cs#L261

CloudBlobClient.cs#L52

試しに、下記のようなコードでioThreadsを確認したところデスクトップPCでは2,Azure上のLargeのインスタンスでは4でした。どちらの環境でもデフォルトでParallelOperationThreadCountが2以上になり並列で動作します。

2.0 では

それに対し、2系では下記のように定義されています。parallelismFactorは、47行目付近で1で初期化されておりデフォルトは1となります。

CloudBlobClientBase.cs#L232

CloudBlobClientBase.cs#L47

これからParallelOperationThreadCount のデフォルトが1に変わったことがわかります。これは、Windows Azure Storage Client Library 2.0 Breaking Changes & Migration Guideにも書いてあるBreaking Changesです。

2.0に移行した後、Block Blobのアップロードが遅くなった場合はParallelOperationThreadCountを確認するといいかもしれません。


ParallelOperationThreadCountの使われ方

1.xでは、ParallelOperationThreadCount は、ParallelUpload で並列度の定義になっています。このクラスは、Streamをblock blobにUploadするもので、BlobClient.UploadFromStreamを、Block blobで使った時しか使われません。** Page Blobでは並列アップロードは実装されていません。 ** おそらく、SDK 1xではPage Blogのパラレルアップロードをサポートしていないので、AzureLargeFileUploaderを用意したのだと思います ** あのソースだけだと分からないですが

ParallelUpload.cs

ParallelExecute あたりの処理をみると、Block毎にTaskを上げているらしいことがわかります。ParallelUpload.cs#L148

2.0.1では、CloudBlockBlob のUploadFromStreamは、並列処理をするときにはStreamの拡張メソッドのWriteToSyncを呼んでいます。CloudBlockBlob.cs#L116


同期と非同期の境界

WriteToSyncの実装は下記のようになっています。StreamExtensions.cs#L64

ちょっと見ると、WriteToSyncは、読み込み側のStreamを非同期で読み出すためのフラグをもっているだけで書き込みは同期していて、並列動作しないような感じです。これだと、ParallelOperationThreadCountに2以上をセットしてもパラレルアップロードは行われないのかな?と思いますが、その先のtoStream.Write の実装を見ると内部が非同期に処理されています。

toStreamの実態は、BlobをStreamとして扱うBlobWriteStreamのインスタンスで、これは内部的に非同期で書き込みを行います。

BlobWriteStream.cs

呼び出し側を見ると同期処理のように見えるが、BlobWriteStreamBase で、AsyncSemaphore parallelOperationSemaphoerをParallelOperationThreadCountの数で初期化しており、ストーリーム内のブロック書き込みは非同期に行われています。

BlobWriteStream.cs#L286

この設計はなかなかイイ。


非同期実行数の制限

ここで、AsyncSemaphoreは、既定の数以上に処理が実行されないように非同期実行数を制御している役割を果たしている。

AsyncSemaphore.cs

BlobWriteStreamでは、書き込みが全部終わると、最後に PutBlockList して終了する。同様な処理がPage Blobにも用意されていて並列アップロードされるような実装になっている。

このあたりは、What’s New in Storage Client Library for .NET (version 2.0)に書いてある説明通りの実装になってるようだ。


結論

Blobのアップロードのような I/O がボトルネックとなるような処理ではI/O の非同期を使うことでCPU、メモリの負荷を最低限にして効率的に処理をすることができる。このコードでは、Stream 書き込みの内部処理を非同期化することで全体のパフォーマンスを向上しプログラミングモデルへの影響は最低限にしている。サーバーサイドのプログラミングではこのような、同期、非同期の境界を発見して設計することが重要だと言える。非同期実行数の制限もなかなか興味深い。


おまけ

LargeのRoleからStorageにUploadしたら450Mbps程度の速度が出た。ローカルからも、20Mbps程度だったので結構速い。転送中を見ていると、しばらくは複数のコネクションを使ってデータ転送していて最後にコネクションが一本になって終わる。、

PutBlobを非同期でやって最後にPutBlobListで終了となってるようだ。PutBlobの処理中はCPUはほとんど使われずに、ネットワーク帯域がボトルネックになっるぐらいには効率がいい。最後のPutBlobListの間はStorage側の待ちになってしまう。

これを考えると、複数のファイルをUploadする場合は、スレッドを分けて個々に処理した方が短時間で終わるのではないかと考えられる。ただ、あまり多くのスレッドを起動するメリットは無さそうだ。

今回は、UploadFromStreamを使ったが下記の説明にはOpenWriteを使うとStreamのように処理できると書いてある。やってみたら同じように動いた。つまりBlobをStreamとして使えるってことだ素晴らしい。

CloudBlockBlob.OpenWrite Method

]]>
Sat, 08 Dec 2012 00:00:00 +0900
http://kyrt.in/2012/12/08/waac2012day2.html http://kyrt.in/2012/12/08/waac2012day2.html <![CDATA[Azure Storage Gen 2は速かった]]> Azure Storage Gen 2は速かった

今年も早いもので、あっという間に12月になりました。個人的なAzure今年の目玉は、Azure Storageのパフォーマンスの向上(Gen2)と新しくなったWindows Azure Storage 2.0です。

IaaS、Web Site、Mobile Service、Media Serviceなど新機能満載なAzureですが、目立たないところで地味にストレージ関連は改善されています。ストレージはクラウドの足回りなので重要です。

omikuji by takekazu, on Flickr

Azure Storageのパフォーマンスの向上

2012/6/7 以降に作成されたストレージアカウントで、下記のようにパフォーマンスターゲットが引き上げられました。Gen 2と呼ばれているようです。以前のもの(Gen1)に比べ秒間のトランザクションベースだと4倍程度になっています(Azure Table 1Kエンティティの場合)

詳しくはリンク先を見てもらうとして下記の4点が注目です。

  1. ストレージ ノード 間のネットワーク速度が1Gbpsから10Gbpsに向上
  2. ジャーナリングに使われるストレージデバイスがHDDからSSDに改善
  3. 単一パーテーション 500 エンティティ/秒 -> 2,000 エンティティ/秒 (15Mbps)
  4. 複数パーテーション 5,000 エンティティ/秒 -> 20,000 エンティティ/秒 (156Mbps)

参照:Windows Azureのフラット ネットワーク ストレージと2012年版スケーラビリティ ターゲット


確認しよう

ではどれだけ速くなったのか確認しましょう。なるべく実利用環境に近いようにということでC#を使います。ライブライは、最近出たばかりですが、Azure Storage Client 2.0を使います。このライブラリのコードをざっと見た感じだと、従来のコードに比べてシンプルになって読みやすく速度も期待できそうです。

比較的限界が低い単一パーテーションで確認します。前記のGen2の記事には、エンティティが1KByteで、単一パーテーションの場合、2,000 エンティティ/秒というパフォーマンスターゲットが記述されています。これを確認しようとするとAzure外部からのネットワークアクセスだと厳しいのでWorkerRoleを立てて、リモートデスクトップでログインしてプログラムを実行します。プログラムは秒間2000オブジェクトを計測時間の間は作りづけないといけないのでCPUやGCがボトルネックになるかもしれません、今回はLargeのインスタンスを使うことにしました。

Largeだとメモリ7GByte、coreが8つ、ネットワーク400Mbpsというスペックなので気にしなくても良いかと思ったのですが、GCをなるべく減らすためにエンティティのデータ部分をCache(共有)します。1KByteぐらいだとあまり効果が無いかもしれませんが。

さらに、Threadを上げる数を減らして並列性を上げるために非同期呼び出しを使います。.NET 4.5 から await/async が使えるので割合簡単に非同期コードが記述できるのですが、少し手間がかかりました。

なんと残念ながら、Windows Azure Storage 2.0になっても APM (Asynchronous Programming Model) のメソッドしか用意されておらず、 await で使えるTaskAsyncの形式がサポートされていません。仕方がないので、自分で拡張メソッドを書きますが、引数が多くて intellisense があっても混乱します。泣く泣く、コンパイルエラーで期待されているシグニチャーをみながら書きました。コードとしてはこんな感じで簡単です。

この辺りは、下記のサイトが詳しくお勧めです。

参照:++C++; // 未確認飛行C 非同期処理

非同期で同時接続数が上がらない?

このコードを動かしてみたら、「単一スレッド+非同期の組み合わせだと、おおよそ2から3程度のコネクションしか作成されない」ことに気が付きました。場合によっては、5ぐらいまで上がることもあるようですが、どうしてこうなるのか不思議です。

#### ** これは、Azure Storage Client 2.0のBUG ** だったようです。2.0.2で修正されています。WindowsAzure/azure-sdk-for-net Issue #141

** [2012/12/26 このFIXに関するまとめを書きました](Azure Storage Client 2.0 CompletedSynchronously FIX) **

非同期でガンガンリクエストが飛ぶのかと思ったのですが、それほどでもなかったので、今回のコードは複数スレッド(Task)をあげて、それぞれのスレッド内で非同期呼び出しを使って処理を行うようになっています。Taskの起動には、Parallel.ForEach を使っています。

さらに、上限に挑戦するためにEntity Group Transactionを使います。TableBatchOperation のインスタンスを作って操作を追加していってCloudTableのExecuteBatchAsync()で実行します。この辺りは以前の使い方とだいぶ違っています。今回は時間を測っているだけですが、resultにはEntityのリストが帰ってきて、それぞれにtimestampとetagがセットされています。

結果

いくつかパラメータを調整して実行し、スロットリングが起きる前後を探して4回測定しました。ピークe/sは、もっとも時間当たりのエンティティの挿入数が大きかった時の数字で秒間のエンティティ挿入数を表しています。単一プロセスでスレッドを増やしていく方法では頭打ちになってしまうので、複数のプロセスを起動して測定ています。(このあたりも少しオカシイです)下記の表の最初のカラムは起動するプロセス数です。

失敗が無かったケースで6,684、 6,932 エンティティ/秒で処理できており、Gen2で挙げられているパフォーマンスターゲットは十分達成できているようです。

測定時間の、Table Metricsを見るとThrottlingErrorと同時に、ClientTimeoutErrorも出ているのでプロセスを3つ上げているケースではクライアント側でサーバからの戻りが受けきれずにエラーになっている場合も含まれているようです。

表1 条件:エンティティサイズ 1KByte、単一パーテーション、スレッド数12、バッチサイズ100
プロセス数 最少 中央値 平均 最大 90%点 95%点 99%点 ピークe/s 成功数 失敗数
2 97.27 166.6 258 14,800 359.578 472.373 1,106.28 6,684 40,000 0
2 94.17 260.5 333.7 5,320 564.774 723.272 1,339.03 6,932 40,000 0
3 90.13 174.8 734.1 21,270 1,621.49 1,845.90 3,434.26 7,218 59,377 623
3 90.35 341.6 610.1 27,490 1,064.59 1,380.42 4,431.79 8,005 59,740 260

最後に

今回、第一世代(Gen 1)の単一パーテーションで500 エンティティ/秒というパフォーマンスターゲットに比べ10倍近いパフォーマンスを出しているのが測定できました。測定時間が短かったので、継続してこのパフォーマンスがでるのかどうかなど検証の余地はありますが、劇的に向上していると言えます。takekazuomi/WAAC201202のレポジトリに計測に使ったコードをいれてあります。

12/2の担当でしたが、JSTでは日付も変わってだいぶ遅くなってしました。データの解析に最近お気に入りの(慣れない)「R」を使ったのですが、いろいろ手間取ってしまいました。最初はRで出した図なども入れたいと思ったのですが、軸や凡例の設定がうまくできずに時間切れで断念です。

レポジトリには、なんかずいぶん古い履歴まで上がってしましたが、手元のコードを使いまわしたら出てしまいました。スルーでお願いします。


おまけ

数時間振り回してみると、エンティティ/秒の中央値は2000から3000エンティティ/秒程度になりそうです。負荷がかかり始めると、Gen 1ではスロットリングをかけてエラーにしてしまうという動きでしたが、Gen 2 ではスロットリングを随時掛けつつ2000から3000エンティティ/秒程度に絞っていくという動きになったようです。`

]]>
Sat, 08 Dec 2012 00:00:00 +0900