「Azure Virtual MachineでVisual Studio & JenkinsなCI環境を構築する(1) ~ インストール編」でAzure Virtual Machineを使ったJenkinsサーバの構築を行いました。今回はそれに続いてJenkinsからgithubのプライベートリポジトリからソースコードを取得してビルドする方法を紹介します。
2013年2月20日
2013年2月19日
Azure Virtual MachineでVisual Studio & JenkinsなCI環境を構築する(1) ~ Jenkinsのインストール
Azure Virtual Machine上にVisual StudioとJenkinsサーバを使ったCI環境を構築してみました。今回はそのVS&JenkinsなCI環境の構築手順を紹介します。
2013年2月18日
Windows Azure Tableの検索パフォーマンス
大分昔(2011年の年末あたり)にAzure Tableへのクエリパターン別の検索パフォーマンスを測って記事を書いていたのですが、公開してませんでした(忘れてた。。。)。
その後いわゆるGen 2のストレージが現れたりしているので、絶対値的には参考にならないと思います。ただ、クエリエンジンがより最適化されるようになったとかっていう話は聞かないので、各クエリ別の相対的な評価としては通用するかと思います。
このまま公開しないのももったいないので、以下最初に書いた時のままですが公開したいと思います。
Windows Azure Tableを使うにあたって、どんなクエリだとどの程度のパフォーマンスが出るのかは気になるところだと思います。昨年末あたりにパフォーマンス計測だけして放っていたので、そろそろ書きます。
ここでは単一パーティションでのクエリ性能を調べるため、単一パーティション内における「RowKeyによる検索とプロパティによる検索」と、「レンジ検索とOR検索」でそれぞれパフォーマンス差がどの程度あるかという点をベンチマークします。そこでベンチマーク対象のクエリは以下の6種類です。
- RowKeyの完全一致
- RowKeyのレンジ検索
- RowKeyのOR検索
- プロパティの完全一致
- プロパティのレンジ検索
- プロパティのOR検索
2011年11月28日
Azure Tableによるデータ検索 - エンティティの結合(Join)
Windows Azure Tableに対するLINQクエリおよびREST APIでは、Joinによるエンティティの結合をサポートしていません。もちろんAzure Tableを使う時には、関連するエンティティのプロパティをコピーさせるなどしてJoinする必要がないようにデータモデルの設計を行います。
ただそれでも、仕様変更や機能追加によって当初想定していなかったデータの結合が必要になる場合もあります。そして、どうしてもデータモデルを変更したくないような場合には、Join処理が必要になってくると思います。
今までシステムを実装してきた中で行ったJoinの実装パターンを紹介します。
Azure Tableによるデータ検索処理
Windows AzureのTableストレージにおけるデータ検索では、基本的にはStorage Client LibraryのLinq to Azure Tableを使って開発するものの、AzureのLinqでは一部のLinq式しかサポートしておらず大部分のLinq式がサポート外になっています。http://msdn.microsoft.com/en-us/library/windowsazure/dd135725.aspx
そのため、Azure Tableにおいてデータ検索を行う際は、Linq to Objectを並行して利用する必要が出てきます。この使い分けについての基本的な考え方は表示対象エンティティをLinq to Azure Tableで取得し、その後のデータ加工処理をLinq to Objectで行うといった雰囲気です。ただしこの2つのLinqの使い分けでは、Linq to Azure Tableにおける取得可能なエンティティ数に制限がある(1000件未満、クエリ実行時間5秒以下 参考:http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx)ので、この制限にかからないよう十分にLinq to Azure Tableで絞込みを行う必要です。
2011年6月17日
Windows AzureでLog4Net RollingFileAppenderを使う
Windows Azure上で、Log4NetのRollingFileAppenderによりBlobストレージ上にログファイルを書きだすようにしてみたので、その方法についてまとめです。
やり方自体は単純で、RollingFileAppenderのログファイルの出力先を Windows Azureの Local Storageに設定し(これが意外と面倒)、出力先のLocal Storageの内容をDiagnostics Monitorで転送させてるだけです。またlog4netの設定ファイルlog4net.configはBlobストレージ上に置かれたものを使うようにしました。
2011年5月16日
Azure Table Storageに非サポート型のプロパティを保存する
標準のAzure Table Storageにおいては以下の型のみが保存可能な型としてサポートされており、これ以外のプロパティとして持つエンティティを保存しようとするとエラーになってしまいます。
| EDM型 | CLR型 | 詳細 |
| Edm.Binary | byte[] | バイトの配列 (サイズは最大 64 KB) |
| Edm.Boolean | bool | ブール値 |
| Edm.DateTime | DateTime | UTC 時刻として表現された 64 ビット値 (サポートされている値の範囲は 1/1/1601 ~ 12/31/9999) |
| Edm.Double | double | 64 ビットの浮動小数点値 |
| Edm.Guid | Guid | 128 ビットのグローバル一意識別子 |
| Edm.Int32 | Int32 or int | 32 ビットの整数 |
| Edm.Int64 | Int64 or long | 64 ビットの整数 |
| Edm.String | String | UTF-16 でエンコードされた値 (サイズは最大 64 KB) |
ただ、この制限に従って実際にシステムを作ろうとすると、ビジネスロジック層がドメインモデルとして設計されている場合に、ドメインオブジェクト群をストレージへ保存する際、そのオブジェクト構造をフラットに展開してからデータを保存しなければなりません。また、そのデータを読み込む時もフラットな構造から元のドメインオブジェクトに戻す処理が必要です。
データを読み書きするためにいちいちこんなことはやってられないので、今回はStorage Client APIをカスタマイズして、オブジェクト構造をそのままTable Storageへ保存できるようにします。
2011年4月6日
2011年4月4日
TableストレージにおけるExpect100ContinueとUseNagleAlgorithmの効果(1)~1件のエンティティ追加の場合
Windows AzureのTableストレージに関するホワイトペーパー「Windows Azure Table – テーブルストレージのプログラミング」(http://www.microsoft.com/japan/windowsazure/whitepapers/)では、ストレージのパフォーマンス向上のために、Expect100ContinueおよびUseNagleAlgorithmをfalseにすることが推奨されている(p67-68あたり)。
ただ実際のところ、この設定がどの程度パフォーマンスに影響を及ぼすか不明だったため、試してみた。
Windows AzureでZeroMQを使ってみた
Windows Azureのインスタンス間通信にzeromq(http://www.zeromq.org/)をWindows Azure上で使う機会があったので、使うまでの手順とか注意点を記す。またWindows Azureプロジェクトからzeromqを利用するために、clrzmq2(http://www.zeromq.org/bindings:clr)というzeromqのC#バインディングを用いる。
2011年3月10日
Azure SDK 1.4がリリースされました
今回のアップデート内容は以下。(訳が間違っていたら教えてください、修正します)
- Windows Azure Connect:
- 管理UIでの複数管理者サポート
- クライアントUIの更新(ステータス通知やDiagnostic周りの機能向上)
- 非英語版WindowsでConnect Clientがインストールできるようになった
- Windows Azure CDN:
- Windows Azure CDN for Hosted Services: WebRoleやVMRoleのコンテンツをWindows Azure CDN経由で配信出来るようになった。静的なコンテンツは自動的にエッジサーバ(United States, Europe, Asia, Australia, South America)でキャッシュされ最大の帯域幅と低レイテンシでユーザにコンテンツが届けられる。
- Serve secure content from the Windows Azure CDN: Azure管理ポータル上のオプション設定を有効にすると、https経由でAzure CDN上のコンテンツをセキュアに配信できる
2011年3月3日
ODataObjCを使ったiOSアプリを作ってみた(1)
ODataObjCを使ってみる練習としてiOSアプリを作ってみました。
書籍管理っぽいことを行うようなアプリで、本の登録/検索/更新/削除が行えます。ついでにAzure Blob上に本の表紙画像をアップロードする機能をつけてみました。(iOSアプリをちゃんと作るのはこれが初めてだったので、ODataObjCの使い方よりもObjective-Cに悩まされましたが。。。)
まずは簡単にアプリ紹介です。
ちなみにアプリ名は、Azure上に本棚を用意するってことでCloud BookShelfと付けてみました(安直だなー。。。)。
まず起動直後の画面ですが、こんな感じで登録された本の一覧が表示されます。また、本にはそれぞれタイトル、著者、レートといった情報(これはAzure Table上に保存)と表紙画像一枚(こっちはAzure Blob上に保存)が付いてます。
本をタップすると、本の情報を編集する画面になります。
この画面で好きにタイトルとか著者、レートを書き換え、右上の保存ボタンをタップすると更新された内容でAzure Table上にデータを保存します。
一番下の[表紙を見る/変更する]をタップすると
表紙として登録された画像が表示されます。で、右上の表紙を変更をタップするとiPhoneのカメラが起動し、撮影した画像で表紙が置き換えらるって感じです。
簡単なアプリケーションですが、iOSプログラミングの癖とかODataObjCの使い方、Azure BlobのREST APIとかいろんなことがわかりました。これを機会にiOSアプリを作ってみようかな~と。
ODataObjCで日本語パラメータを使う方法
OData Client for Objective-C(http://odataobjc.codeplex.com/)を触っていて気づいたことが、Azure Tableストレージを対象に日本語をパラメータとして検索するフィルタクエリを実行すると、Cocoaのライブラリ側で不正なURLという例外が発生してしまう。
なぜそんなことになっているかソースコードを見ていたら、ライブラリ内部ではフィルタクエリのURLエンコードを行っていないことがわかった。また、そのソースの近くにURLエンコードを試みたらしくコメントアウトされたコードがあるものの、それを外しても動かない。
原因をよくよく考えてみると、Azure Tableストレージに対するエンティティのクエリ構文では、全体をURLエンコードするのではなく個々のフィルタパラメータをURLエンコードしなくてはならないことがわかった。
例えば、
$filter=(PartitionKey eq 'あいうえお')
こんなクエリを投げたいときは
$filter=(PartitionKey%20eq%20%27%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A%27)
ではなく、
$filter=(PartitionKey%20eq%20'%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A')
のようにしなければならない(%20は+でもOK)。
しかし、このODataObjCではこの$filter=の後の文字列を直接メソッドのパラメータとして受け取るように作られているため、ライブラリ内部で対応するのは難しい感じです。(だから諦めたのかな。。。)
しかしこのままじゃ使えないため、$filter=の後の文字列を生成する簡単なクエリビルダを作成し、その中でURLエンコードするようにして対処してみました。というわけで作成したクエリビルダのソースを置いておきます。(URLエンコードの処理はODataObjCライブラリのUtilityクラスを利用してます)
Queries.h
#import <Foundation/Foundation.h>
#import "Utility.h"
@interface Queries : NSObject {
@private
NSString *filterString;
}
@property (nonatomic, retain) NSString *filterString;
- (id) initWithString:(NSString *)aFilterString;
+ (Queries *) withEmpty;
+ (Queries *) with:(NSString *)name eq:(NSString *)value;
+ (Queries *) with:(NSString *)name gt:(NSString *)value;
+ (Queries *) with:(NSString *)name ge:(NSString *)value;
+ (Queries *) with:(NSString *)name lt:(NSString *)value;
+ (Queries *) with:(NSString *)name le:(NSString *)value;
+ (Queries *) with:(NSString *)name ne:(NSString *)value;
- (Queries *) and:(Queries *)aQueries;
- (Queries *) or:(Queries *)aQueries;
- (Queries *) not;
@end
Queries.m
#import "Queries.h"
@implementation Queries
@synthesize filterString;
- (id) initWithString:(NSString *)aFilterString {
self.filterString = aFilterString;
return self;
}
+ (Queries *) withEmpty {
return [[[Queries alloc] initWithString:@""] autorelease];
}
+ (Queries *) with:(NSString *)name eq:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ eq '%@')", name, [Utility URLEncode:value]]] autorelease];
}
+ (Queries *) with:(NSString *)name gt:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ gt '%@')", name, [Utility URLEncode:value]]] autorelease];
}
+ (Queries *) with:(NSString *)name ge:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ ge '%@')", name, [Utility URLEncode:value]]] autorelease];
}
+ (Queries *) with:(NSString *)name lt:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ lt '%@')", name, [Utility URLEncode:value]]] autorelease];
}
+ (Queries *) with:(NSString *)name le:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ le '%@')", name, [Utility URLEncode:value]]] autorelease];
}
+ (Queries *) with:(NSString *)name ne:(NSString *)value {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ ne '%@')", name, [Utility URLEncode:value]]] autorelease];
}
- (Queries *) and:(Queries *)aQueries {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ and %@)", self.filterString, aQueries.filterString]] autorelease];
}
- (Queries *) or:(Queries *)aQueries {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(%@ or %@)", self.filterString, aQueries.filterString]] autorelease];
}
- (Queries *) not {
return [[[Queries alloc] initWithString:[NSString stringWithFormat:@"(not %@)", self.filterString]] autorelease];
}
- (void) dealloc {
[self.filterString release];
[super dealloc];
}
@end
このクエリビルダを使うと上記のクエリは、
[Queries with:@"PartitionKey" eq:@"あいうえお"].filterString;
と書ける。