200ミリ秒の検索APIをMicrosoft Azureでデザインする

200ミリ秒の検索APIとは

ここでは、httpクライアントからリクエストを受けてから全文検索を含む何らかの検索処理を行い、httpクライアントにレスポンスを返すまでの処理を、おおむね200ミリ秒(200ms、0.2秒)前後の時間でこなすWeb APIを指します。

検索という機能を考えたとき、200msという数字はまずまず軽快な応答速度ではないかと私は考えます。

今回はAzureの各種サービスを使って検索APIを作った時のノウハウを共有します。このAPIはデータサイズによって多少のばらつきはあるものの、ほぼ200ms前後の応答速度を実現することができております。

以下は、実際の処理履歴となります。おおむね200ms前後で処理が完了し、応答していることが分かると思います。

ログ

Microsoft Azureで作る

Microsoft Azure(以下Azure)で検索APIを作るにあたり、以下のようなシステムデザインを行いました。

システムデザイン

ある程度Azureに詳しい方であれば、上記の図を見ただけで概ねの構成がご理解いただけると思います。

Azure CDN

いわゆるCDN(Contents Delivery Network)サービスです。今年から動的コンテンツの高速化にも利用できるようになりました。

今回のケースでも「動的コンテンツの高速化」がその利用目的となります。

Azure Functions

実際にhttpリクエストを受け付け、httpレスポンスを返すためのアプリケーション・ロジックをデプロイし、運用するためのFaaSとなります。最近v2がリリースされましたが、私のケースではv1を利用しました。

C#やF#、PHPなどの言語に対応していますが、今回はNode.jsをつかって作ってみました。実際の実装・はまりどころについては前回のエントリにまとめてありますので、そちらも併せてご参照ください。

Apache LuceneクエリやODataフィルタを利用可能な検索エンジンサービスです。

全文検索や緯度経度をもとにした距離検索、ファセットなどにも対応しているため、複雑な検索機能を実装するにはほぼ必須となります。

また、Azure CosmosDBやAzure Table Storageなどから定期的(最短で5分おき)にデータを取り込むIndexerという仕組みがついてきますので、検索結果にリアルタイム性を求めない限り、データのインポートをする仕組みを自分で作る必要がありません。

Azure CosmosDB (または Azure Table Storage)

どちらもスキーマレスなデータストアとして利用できるサービスです。

今回はAzure CosmosDBを利用し、Azure Search向けの一次データをストックしておくデータストアとします。

まとめ

200ms前後の応答速度の検索APIをAzureで実現するシステムデザインを紹介しました。

「えっ、実際の作り方は書かないの?」という声が聞こえてきそうですが、その辺は公式ドキュメントや有志のブログに書いてあることしかやっていません。

強いてあげるとすれば、前回のエントリで書いたようなはまりどころがあるくらいですので、そちらを見ていただきたいと思います。

参考にしたドキュメント・ブログ

[Azure + javascript]Functionsのhttp triggerな関数でSearchの検索結果を返す

暖簾に腕押し

私がAzure Functionsに提供してほしいと切に願っている機能の一つに、Search Bindings(仮称)があります。なお、私がこの機能実装のリクエストを出しました。

しかし、現時点ではこのような機能が提供される気配は全くなく、付帯するロジックを記述するしかありません。暖簾に腕押し、というやつです。

実際、このようなロジックを書くことすら億劫なのですが、嘆いていても仕方がないですので、javascriptからAzure Searchへ問い合わせを行うnpmライブラリazure-searchを利用し、レスポンスを返すところまで実装することにしました。

役割

  • クライアント : webAPIを利用し、HTTP Responseに含まれた検索結果を受け取ります。

  • Azure Functions : クライアントからHTTP Requestを受け取り、Azure Searchに問い合わせをし、結果をクライアントに返します。

  • Azure Search : クエリをFunctionsから受け取り、検索結果を返します。また、Indexerという機能を利用し、定期的にTable Storageからデータを同期します。

  • Table Storage : 検索対象となるデータがストックされています。Indexerによって、定期的にデータ参照されます。

データ構造

検索対象となるデータ構造は以下のようなものです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
{
"PartitionKey": "2018-08-29:myroom",
"RowKey": "ecd53616-e756-41fb-98d2-fe2b387e0c8a",
"id": "ecd53616-e756-41fb-98d2-fe2b387e0c8a",
"channel": "myroom",
"body": "5000兆円 欲しい!!!",
"author": "ytnobody",
"visible": true,
"timestamp": 1535508299
},
...
...
]

PartitionKeyおよびRowKeyはいずれもTable Storageで必須の項目です。(参照:Azure ストレージ テーブルの設計ガイド: スケーラブルな設計とハイパフォーマンスなテーブル

Search側のスキーマ構造

インデックスmessageには、Table Storageに格納されているデータ構造を、ほぼそのまま持ってきています。Table Storageで利用していたPartitionKeyおよびRowKeyはここでは使いません。

  • id Edm.String (key, retrievable, searchable)
  • channel Edm.String (retrievable, filterable)
  • body Edm.String (retrievable, searchable)
  • author Edm,String (retrievable, filterable)
  • visible Edm.Boolean (retrievable, filterable)
  • timestamp Edm.Int64 (retrievable, filterable, sortable)

普通に実装

最初、以下のように実装しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"disabled": false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// index.js

module.exports = function (context, req) {
// Searchクライアント初期化
const AzureSearch = require("azure-search");
const client = AzureSearch({
url: process.env.SEARCH_URL,
key: process.env.SEARCH_KEY
});

// 検索ワードをスペースで切って配列にする
const words = req.query.word ? req.query.word.split(' ') : [];

// ページング指定
const page = req.query.page ? parseInt(req.query.page) : 1;
const top = req.query.size ? parseInt(req.query.size) : 10;
const skip = top * (page - 1);

// 検索オプション
const search = words.map(w => `body:${w}`).join(' AND ');
const filter = 'visible eq 1';
const searchOptions = {
queryType: "full",
searchMode: "all",
top: top,
skip: skip,
search: search,
filter: filter
};

// 問い合わせ
client.search('message', searchOptions, (err, results) => {
context.res = err ? {
status: 500,
headers: {"Content-type": "application/json"},
body: {"message": `Internal Server Error: ${err}`}
} :
{
status: 200,
headers: {"Content-type": "application/json"},
body: results
};
context.done();
});
};

察しの良い方なら気づいたかもしれませんが、このロジックは期待通りには動かず、502エラーを返してしまいます。

何がダメなのか

期待通りに動かない原因は、client.search(...)の結果を受け取る前にmodule.exports自体が処理を終えてしまうからです。

レスポンスらしいレスポンスを設定しないまま処理が終わってしまうので、502エラーを返す、ということです。

対応方法

結論から書くと、以下の2点を直すと良いです。

  1. module.exportsを、Promisereturnするように変更する。
  2. function.jsonにて、http output bindingsのname$returnにする。(portalの場合、応答パラメータ名のところにある「関数の戻り値を使用する」をチェックする)

なおしてみる

なおした後の実装がこちら。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
],
"disabled": false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// index.js

module.exports = function (context, req) {
// Searchクライアント
const AzureSearch = require("azure-search");

// 検索ワードをスペースで切って配列にする
const words = req.query.word ? req.query.word.split(' ') : [];

// ページング指定
const page = req.query.page ? parseInt(req.query.page) : 1;
const top = req.query.size ? parseInt(req.query.size) : 10;
const skip = top * (page - 1);

// 検索オプション
const search = words.map(w => `body:${w}`).join(' AND ');
const filter = 'visible eq 1';
const searchOptions = {
queryType: "full",
searchMode: "all",
top: top,
skip: skip,
search: search,
filter: filter
};

// 問い合わせ
const promise = Promise.resolve(AzureSearch({
url: process.env.SEARCH_URL,
key: process.env.SEARCH_KEY
}))
.then(client => client.search('message', searchOptions))
.then(results => {
return {
status: 200,
headers: {"Content-type": "application/json"},
body: results
};
})
.catch(err => {
return {
status: 500,
headers: {"Content-type": "application/json"},
body: {"message": `Internal Server Error: ${err}`}
};
});

return promise;
};

function.jsonでは、http output bindingsのname$returnとなっており、functionの戻り値をレスポンスに使う設定となっています。

そしてindex.jsではPromise.resolve(...).then(result => ...).catch(err => ...)の形式でSearchに問い合わせを行った後の処理をハンドリングするよう定義し、promiseそのものをreturnするロジックへと書き換えられました。

本当はドキュメントに書いておいて欲しかった、もしくは・・・

実はPromiseをreturnすることで解決できるという事について、公式ドキュメントには書かれていないようです(ソース。openになっているし、書こうとはしてる模様)。

今回の解決法は、上記issueを辿ってようやく見つけることができたものでした。

この手の手間をなくすためにも、Search Bindingsが欲しいな、と思うのでした。

まとめ

  • Search Bindings欲しいですね。

2018-08-30 追記

node-azure-searchでPromise/thenを使った書き方では、検索結果のヒット件数を取得することができないという問題がありました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const AzureSearch = require('azure-search');
const word = '5000兆円';
AzureSearch(...)
.then(client => search({
queryType: "full",
searchMode: "all",
top: 20,
skip: 0,
search: `message:${word}`,
filter: 'visible eq 1',
orderby: 'timestamp desc',
count: true // <--- @odata.countをレスポンスに含めるための指定
}))
.then(rows => {
// rowsは検索結果(オブジェクト)が入った配列。
// ここで検索結果のヒット件数である@odata.countを利用したいができない!!
})
.catch(err => { ... });

これはnode-azure-searchのindex.jsを修正することで、取得できるようになります。(ただしインターフェイスを破壊する変更です)

1
2
3
4
5
6
7
8
9
@@ -492,7 +492,7 @@ module.exports = function (options) {
return new Promise(function (resolve, reject) {
args.push(function (err, value, data) {
if (err) reject(err)
- else resolve(value)
+ else resolve(data) // resolve(value) not contains '@odata.count'
})
fn.apply(self, args)
})

利用する側は以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const AzureSearch = require('azure-search');
const word = '5000兆円';
AzureSearch(...)
.then(client => search({
queryType: "full",
searchMode: "all",
top: 20,
skip: 0,
search: `message:${word}`,
filter: 'visible eq 1',
orderby: 'timestamp desc',
count: true // <--- @odata.countをレスポンスに含めるための指定
}))
.then(result => {
const count = result['@odata.count']; // 検索結果のヒット件数。
const rows = result['value']; // rowsは検索結果(オブジェクト)が入った配列。
...
})
.catch(err => { ... });

破壊的な変更であるため、forkして利用しています。

Hokkaido.pm #14で 予想の話 というか 予想を支えるシステム設計の話 をしてきました

予想の話予想を支えるシステム設計の話

札幌で開催されたHokkaido.pm #14というイベントに参加し、予想の話(というか予想を支えるシステム設計の話)をしてきました。

内容としては「おとなの自由研究」の取り組みの中で作った南関競馬予想システム「うまミる」のシステム構成と予想ロジック、運用などについてを話したのですが、30分は予想以上に短く、あらかじめ40分で発表枠を押さえておけばよかったというのが正直な感想です。

うまミるについてより詳しく聞きたい方は、アルコールチャンスを用意してくださればお話しますので、是非ともよろしくお願いします。

Hokkaido.pmの皆さん、本当に楽しかったです。ありがとうございました。出来たら11月くらいにもう一発くらい開催してくれたらいいのにな、って思いました!

あと当然ながら、初夏の札幌を満喫したのは言うまでもありません。

Gotanda.pm #18 六本木編 でLTしました

Gotanda.pm #18 六本木編で、Core Modulesに寄せてみたら?というタイトルでLTしてきました。

オフィスから歩いていける場所ということもあり、割と早々とLTすることを決めたんですが、勢いは大事ということでLTもどうにか勢いを維持して発表できたんではないかなと自負しております。

「システムプログラミング」というテーマだったようでして、 strace を使ったperlのopenとシステムコールの結びつきについて詳説したトーク(kazeburoさん)や、ネタが被ったと言いながらも、別の切り口によるディープなトーク(skajiさん)、はてなブログSSL化の裏側についてのトーク(papixさん)など、全体的に「掘り下げられた発表」が多かったように感じました。

Mishima.pm #3 に参加してきました

Mishima.pm #3というイベントが6/3(日)に開催されました。

主催の@dokechinさんが三島在住ということで、数年前から不定期的に開催されていまして、私は #0, #1, そして今回の #3 と、開催されるたびにできるだけ行こうと思っているpmの一つです。

今回の参加者は私を含めて4名。当日の様子は#mishimapmtogetterにもまとまっていますので、そちらも併せてご覧いただけると雰囲気が伝わりやすいのではないでしょうか。

「Why people says “Perl is guilty”?」というタイトルで発表

こちらに発表スライドのMarkdownを置いてありますので、そのままご覧いただくか、reveal.jsで任意のテーマを当ててご覧ください。

この発表で言いたかったこと

かなりポエムっぽいことなのですが・・・

様々な言葉でPerlを貶める人はいるし、私も彼らの言っていることにも一定の理解を示します。確かに一部は事実でしょう。

しかしそれらは大抵、他の言語で鳴らしてきた人が、大して知りもしないPerlについて語っているだけではありませんか?少なくとも私にはそう見えます(私がPerlのすごい人かと言われると、そんなこともないんですけど)し、悲観的すぎるだろうと思います。

そんなことを気にするより、低い理解度でも自分の思った処理を書くことができることの方が大事だと思いますし、Perlにはそのためのツールセットが揃っているんだ、と言いたい。

実は覚えることがかなり少なくて良いので(例えば変数記号については$と@と%を理解できればひとまずOK)、まず書いてみて、足りないところの知識を補いながら少しづつ上達するのに向いている言語なんです。

斜に構えて100%理解した風に振る舞うより、まず手を動かして試すところからPerlを触ってみると、意外といいものだと思えますよ。

・・・スライドからは読み取れないでしょうけど、こんな感じのことを話しました。

コンテキストの観点でPerlを理解する

私の発表の前に@karupaneruraさんの発表内容が、Perlのコンテキストに関する話題に触れていました。

例えば$age = 100というデータについて、

  • $age . "歳のおじいさん" という文字列的な扱い
  • $age + 1 という数値的な扱い

の両方を同じ変数に対して行えることは、コンテキストを意識することで、一見複雑に思える挙動を理解できるという感じの話でした。

似たようなことをやっている言語はLispやFORTHなどが挙げられます。

この考え方のメリット・デメリットについて書くと長くなるので別の機会に回しますが、Perlについての理解を深める視点として、非常に慧眼だなと感じました。

三島を満喫する

うな丼

三島で八王子

竹倉温泉の待合室

新幹線コンコースから望む富嶽

webアプリを提供する上でやめた7つのもの

よもやま話など

ytnobodyです。実に3か月ぶりの更新です。ご無沙汰しております。

最近はPerlやサーバサイド、クラウドなどについて、個人的にこれといって真新しいとかすごい、と感じたものがあまりなく、そのため更新が滞っておりました。

また、私の主業務におけるiOSアプリ開発の割合が非常に高くなっており、そちら側のキャッチアップにエネルギーを注ぎまくっている状況でして、これがまた楽しくて楽しくて。

・・・というような事情で、なかなかこちらにはアウトプットできていませんでした。

今日はそんな状況を顧みて、「じゃあ自分がいまどういう哲学に基づいて設計・開発などを行っているかをひけらかしてみたらどうだろうか?」と思い立ち、キーボードをたたいている次第です。

もはや当たり前、と思われる方もおられるかもしれません。まぁしかし、個人のブログですし、お目こぼしくださると幸甚です。

本題

さて、webアプリを公開するにあたって、いくつか「最低限これは必要だよね」というものがあると思います。私の場合、7年ほど前であれば以下のものを挙げたことでしょう。

  • ドメイン名
  • ネームサーバ(とりあえず外部のやつでもよい)
  • webサーバ(もしくはVM)
  • ネットワークおよび構成機器(ルータだとかスイッチだとか)
  • OS(UbuntuとかCentOSとか)
  • httpd(ApacheとかNginxとか)
  • プログラミング言語の動作環境(perlだとかphpだとかrubyだとか)
  • デプロイ手段(capistranoとかansibleとかftpとかscpとか)

ここでは「最低限」なので、DBは割愛しました。しかし、こうして見てみると結構多いですね。

では現在はどうかというと、以下のものを挙げます。

  • ドメイン名
  • DNS as a Service(Amazon Route53だとかGoogle Cloud DNSだとかAzure DNSだとか)
  • Platform as a Service(Amazon Elastic BeanstalkだとかGoogle Apps EngineだとかAzure Web Appsだとか)
  • git

だいぶ少なくなりました。減ったものについて着眼していきます。

ネームサーバ

DNS as a Serviceに取って代わられました。

DNS as a Serviceを利用するにも相変わらずネームサーバの知識は必要なのですが、物理的にサーバを用意したりする必要がなく、クラウドプラットフォームへのログインが可能な状況であれば、いつでもリソースレコードの修正や追加が可能となりました。

webサーバ

Platform as a Serviceに取って代わられました。

これにより、VMや物理サーバの運用をする必要がなくなりました。概念的にはサーバがなくなったことになります。

ネットワークおよび構成機器

概念的にサーバがなくなってしまったことによって、ネットワークとその構成機器もまた概念的に不要となりました。

従来これらが賄っていたことはPlatform as a Serviceの一機能として提供されているか、あるいはLoad Balancer as aa Serviceによって代替できるため、必要があればLoad Balancer as a Serviceを追加する程度です。

OS

概念的にサーバがなくなっていますので、OSも概念的に不要となりました。

これはPlatform as a ServiceによってOSが自動的に提供され、かつ更新が行われるようになったためです。

httpd

これもPlatform as a Serviceによって不要となりました。

ApacheやNginxなどの複雑なconfigを書く必要がなくなり、PaaS側に組み込まれた機能によって代替できるためです。

プログラミング言語の動作環境

Platform as a Serviceに組み込まれているもので十分なので、自分で用意することはやめました。

最近はPHP7に寄せておりますが、JavaやC#、pythonなどもよさそうだと感じます。

デプロイ手段

デプロイ手段を自前で用意するのもやめました。

Platform as a Serviceがgitに対応しており、tagを切ることでデプロイが行われるように設定しました。

やめたことで得られたもの

OSやその下位レイヤであるVM/サーバの管理から解放された

PaaSを使う主目的の一つとしてあげられるメリットですが、その大きさは想定以上でした。

思えば、私が過去にやってきた業務の半分近くが、VM/サーバの管理やOSの管理だったという時期があったくらいですから、従来の半分強のエネルギーで業務が回るようになったと言えます。

ネットワークの管理から解放された

OSやVM/サーバもそうなんですけど、一からネットワークを構築して運用することは、それだけで一つの仕事として成り立つくらいに高度かつエネルギーや時間を使う行為です。

また、ネットワークで問題が発生した時には、そのトラブルシュートに必要な知識は専門性が高く、エンジニアのジョブセキュリティが高まってしまいます。そのため、ひとたび問題が発生すると、解決までは帰宅できないという事態が発生します。

OS、VM、ネットワークの管理から解放されるということはつまり、「エンジニアでありながらも、いつも定時で帰宅しプライベートを満喫できる」という状況が、確固たるものになるということではないでしょうか。

httpdの設定から解放された

httpdの設定は一箇所間違うと、サイト全体に致命的な影響を及ぼすことがあります。ネットワークの問題にも性質が似ているのですが、基本的には「解決しないと帰れない」を生み出す可能性を内包しております。

また、OS同様にセキュリティ上の問題が発見された場合、その対応をするのはhttpdを管理している人間が担当することになります。

これらの管理コストをPaaSに代替させることで、利用料金という形であらかじめ解決しておくことができるのは、不確定要素を予定調和化する上で良い判断だったと考えます。

プログラム動作環境・デプロイ手段の整備から解放された

プログラム動作環境の構築やデプロイをAnsibleなどの構成管理ツールで対応することは、もう3年以上前には行われていたことですが、playbookの作成と管理に心理的コストがかかることをずっと気にしていました。

ところがPaaSを使うことで、プログラム動作環境は毎回お仕着せのものが降って来ますし、デプロイについてはgitレポジトリの特定ブランチにpushするか、tagを切ることで実施されるようになりました。

結果、playbookの管理にかかる心理コストがなくなり、「デプロイをするぞ」という意識から、「pushするぞ」「tag切るぞ」という意識へと移ろい、「デプロイをする」という作業自体がなくなりました。

負荷をあまり気にしなくなった

完全に気にしなくなる状況には至っていないのですが、それでもVMを自分で管理するのに比べて、オートスケール機能が設定されたPaaSの場合は負荷に対する心理的コストが圧倒的に低いと感じます。

手が空いた分iOSアプリの開発ができるようになった

もともと私はサーバサイドアプリ(要はwebアプリ)がメインで、インフラを少々嗜む程度のスキルセットでしたが、新しいスキルを習得する余力ができたのは、明らかに従来の業務遂行に必要な時間やエネルギーが半減したからだと考えます。

得られたものから考える

ここまで書いてきたことは、唯一の判断基準「手間・管理の削減」を突き詰めた結果です。

httpdの項目でも書いた「不確定要素を予定調和化する」ということは、手間・管理を削減するうえでキーとなる考え方ではないでしょうか。

歴史を見ると、「速く快適に移動する方法」として馬や馬車、人力車などが広く利用されていた時代がありました。ところが時代が進むにつれ、馬も馬車も人力車も利用シーンが激減するのですが、その一番の理由は「自動車の普及」でした。

「webアプリを公開する方法」という本質だけを見た時に、歴史や得られたものから考えると、何がより進化したものなのかを思わずにはいられないものです。そして、進化したものはいつも「斬新」で「ミニマリズム」であると、私は考えます。

レビュー RAZER Blade Stealth 2017

RAZER Blade Stalth 2017 を購入してから1か月ほどが経過しましたので、レビューしてみます。

WELCOME TO THE CULT OF RAZER

閉じた状態

閉じた状態

※もう1か月使っているので、多少の汚れはご勘弁を。サイズ感が分かるようにコミックを隣に置いてみました。

まず、Windows PCのわりにシンプルで洗練されたデザインが目を引くところでしょう。アルミ削り出しの筐体はブラック(写真)とガンメタリックの2種類から選択でき、背面にはRazerのシンボルがしっかりとあしらわれています。

真正面から

真正面から

閉じた状態で厚さは15mmないくらいとかなり薄く、そして1.25kgと、13.3インチPCのわりには軽い部類に入ります。

左側

左側

左から Thunderbolt™3(USB-typeC/電源供給兼)、USB3.0(SuperSpeed)、ヘッドフォン/マイク複合ジャック。USB-typeAの口が欲しい!というケースにもちゃんと対応してます。

右側

右側

こちらは左からUSB3.0(これもSuperSpeed)、HDMI2.0。フルサイズのHDMIポートがあるのは、セミナーなどで登壇する際に非常に重宝します。

裏側

裏側

ラバー製の足がちょっと汚れてますが、これのおかげでデスクの上で滑ることなく利用可能となっています。ゲーマーにとっては非常に重要なパーツなのでは?

そして見えているファンは吸気ファンとなっていて、排気はスピンドル側にある排気口から出ていくことになります。かなり排熱に気を使った構造です。

開けてみる

開けてみる

ほとんどUSキー配列のMacBook Proと同じようなレイアウトですね。キーボードはMacBookに慣れている方ならばあまり違和感を覚えないような打鍵感となっていますが、幾分フィードバックが強めな気がします。

Macと違うのは、Touchバーがあるはずの場所にファンクションキーが並んでいるところでしょうか。電源ボタンはキーボード上部中央にわかりやすく配置されています。

トラックパッドはほぼMacBook Proと同様の操作感となっており、3本指スワイプの挙動もWindows10からかなりMacOSに寄せてきています。

キーボード両脇についているスピーカーについては、ぜいたくを言わなければ十分納得できるクオリティですが、より本格的なオーディオを楽しみたい場合はRazer TIAMAT 7.1 v2あたりで「Razer寄せ」してみるのがよさそうです。

ディスプレイはQHD(3200x1800)のIGZOタッチディスプレイとなっており、ゲーミングPCを標榜するだけあってハイクオリティです。

七色に輝くRazer Chroma™

七色に輝くRazer Chroma™

キートップはRazer Chroma™と呼ばれるライトアップ機能によって常に七色に光り輝いており(設定変更でカスタム可能)、暗所でもキータイプに支障が出ないようになっています。これこそRazerならではの粋な仕様ですね。

背面

背面のRazerシンボルもグリーンに光ります。

スペックとバッテリー

私が利用しているモデルは256GB SSDのものとなります。CPUはIntel Core i7 7500U(第7世代/デュアルコア)、メモリは16GBを搭載。ちょうどMacBook Proの13インチ版に似た性能ですが、メモリだけ倍積んでいる感じになります。

バッテリーは9時間稼働可能ということになっていますけど、実際のところ6時間程度の稼働という感じです。利用の仕方によって多少の前後はありそうですけど、目安としてそのくらいです。

グラフィック性能

内臓しているグラフィックチップは Intel® UHD Graphics 620 となっており、正直単体ではそんなに期待できるものではなかったのですが、Minecraftをプレイするくらいであれば全然問題ありませんでした。

なお、Thunderbolt™3でRazer Core v2を接続することで、グラフィックボードを外付けできるというキワモノな仕組みが存在します。

そのRazer Core v2ですが、公式サイトによると「 NVIDIA® GeForce® GTX 10 シリーズおよび AMD XConnect™ 対応 Radeon™ RX ボードを含む最新グラフィックスチップセットに対応。」とのことで、MacBookっぽいUltraBookがゲーミングシステムに早変わりするというわけです。

総評

筐体に指紋が付きやすいのが弱点ですが、アプリケーション開発にも耐える硬派なスペック、シンプルなデザイン、美しく見やすいディスプレイと、Web系エンジニアにとって決して悪くない選択であると感じました。

UnixではないというWindowsならではの厳しさがあるかもしれませんが、Unixを手元で触らないエンジニアリングスタイルを確立してしまえば、何ら困ることはありません。Unix/Linuxはクラウドにお任せして、手元ではWindowsを使うというやり方は案外楽なものですよ。ぜひVSCodeとChocolateyをお忘れなく。

え、iOSアプリの開発?それはMacを使ってくださいw

書評 Azureテクノロジ入門2018

遅ればせながら、新年おめでとうございます。

さて、昨年末にAzureの風雲児であるところの@myfinder氏より献本いただきました。改めてお礼申し上げます。ありがとうございます。

本書について

2016年11月に出版されたAzureテクノロジ入門2016の改訂版となります。

もともとAzure関連の技術書は数少ないのですが、本書はその中でも入門者・初学者向けの位置付けとして発売されています。索引まで含めて233ページとなっており、定価は2500円と、技術書としてはかなり安価です。

著者陣はほぼ日本マイクロソフトの現任エバンジェリストやアーキテクトなどで占められており、マイクロソフトがいかにAzureというプロダクトを重要視しているかがわかります。

本書の目次

  1. Azureの基本と全体像
  2. AzureのインフラとIaaS ~ 仮想マシン、ストレージ、ネットワーク
  3. データベースと分析サービス
  4. 開発者のためのPaaS ~ Azure App ServicesとAzure Functions
  5. アイデンティティ管理と認証・認可
  6. 地上に広がるハイブリッドクラウド ~ Azure Stack

書評

表紙に「Azureを知るための最初の1冊!」「さらに進化したAzure全体像がよくわかる!」と書かれているのですが、まさにその通りの内容です。

そこそこAzureを使っている立場からすると、2章と5章は順序が逆でもいいかもしれないな、と感じました。AzureにおいてはそのくらいにID管理(Azure Active Directory 通称Azure AD, AAD)が肝となりますし、IaaSについてはAzureのPaaS/SaaSについて理解してからフォーカスするくらいでも遅くないと思います。

とはいえ、各章は初学者にもわかりやすいよう図解が多く用いられており、概念を理解する上で非常に心強い一助となることでしょう。すべて通読する必要はなく、手始めに利用する予定のあるサービスに応じて、必要な個所だけピックアップして読むのがおすすめです。ある程度ユースケースをカバーできたら、今度は通読してAzureの可能性をもっと広く学習することができます。

また、ある程度熟練した向きにとっても、次に利用する予定のAzureサービスについて手早く把握しておきたい場合に、辞書のように利用できることでしょう。というのも、本書では各章の要所要所にオンラインドキュメントのURLが記載されており、細かい部分についてはそちらで補完するような構成となっているためです。

あらゆることを正確に記憶しておける人間は稀です。本書は、Azureに関する知の高速道路を読者に提供してくれることでしょう。そして忘れかけたAzureの知見を改めて引き出す際にも十分に価値を発揮してくれると思います。

2017年のPaaS/SaaS周りを振り返って - フルマネージドDBサービス発展の年

2017年も終わりを迎えようとしておりますが、2017年はPaaS/SaaSにとって非常にインパクトの大きい一年でした。

このエントリでは、PaaS/SaaS利用者の観点から、2017年を振り返ってみようと思います。

なお、私は主にMicrosoft Azureを利用しているため、内容の濃さなどの面でどうしてもMicrosoft Azure寄りとなりますことをあらかじめご了承ください。

フルマネージドDBサービス発展の年

2017年は、フルマネージドDBサービスの利便性が大変向上した一年だったと思います。

特に「DBインスタンス」という考え方から脱却したサービスが登場し、本格的に利用された年だったと言えるのではないでしょうか。

アプリケーション開発者からすると、インスタンスのことを気にするのは本質的ではありません。そのような本質的ではないことについて一切触れる必要なく、DBをサービスとして利用することは、2017年で一気に身近になってきたことだと思います。

また、料金体系的にも「トランザクションごとに課金」というパターンが浸透してきたのも2017年の特徴ではないでしょうか。これによって、真に「利用した分だけ支払う」ことが現実のものとなったと思えます。

Azure Cosmos DB

スキーマレスなNoSQL DBとして利用でき、各リージョン間でデータレプリケーションが可能なDBaaSです。

もともとAzure Cosmos DBの源流として、Azure DocumentDBというサービスが2010年から提供されていたのですが、Build 2017にて名称をAzure Cosmos DBとし、以下のようなAPIへと対応しました。

料金体系についてはRU(Request Unit)と呼ばれる単位で計上されることになっており、インスタンスベースの課金体系ではないことが非常に重要です。

Azure Database for MySQL/PostgreSQL (プレビュー)

Azure SQL Databaseで培われてきたフルマネージドDBサービスの堅牢性とスケーラビリティが、MySQLおよびPostgreSQLのインターフェースで利用できるようになったものです。これもBuild 2017で発表されました。

従来であればMySQL/PostgreSQLともにVMを立ち上げ(あるいはオンプレミスのリソースを用意して)そこにインストールし、パフォーマンスモニタリングに気を使いながら、自力で運用するものでした。

このサービスは現時点ではまだプレビューですので、手放しで本番に投入して良いものかどうかは微妙だと思いますが、上記のような運用から解放される道筋が示されていることについて、将来十分に考慮に値することだと思います。

こちらの料金体系は、時間課金のコンピューティングユニットとデータ容量課金のストレージの合算値となり、やはりインスタンスベースの料金体系ではありません。

Amazon DynamoDB

Amazon DynamoDBは、フルマネージドDBサービスの中でも元祖と言えるでしょう。2012年にサービス提供が開始され、2013年に東京リージョンで利用可能となりました。

リージョン間でのレプリケーション(クロスリージョンレプリケーション)にも対応しており、課金体系もデータ量とスループットに基づいたものとなっています。

これらのことは、近代的なフルマネージドDBサービスの基礎となっており、Amazon DynamoDBがサービスを通して示したビジョンは大変重要なものと言えるでしょう。

フルマネージドDBサービスは開発者をより開発に集中させる

上記で紹介したサービスはいずれも、開発者からDBの管理という手間をなくそうという方向性のサービスです。自力でDBの管理を行う場合にかかるコストと、上記サービスを利用した場合のコストは、性質的に全く異なるものです。

また、自力でDBの管理を行う場合は、どうしても人間そのものがボトルネックになってしまいます(腰痛や風邪などの体調不良、冠婚葬祭に伴う休暇など)。速く移動するという本質で見た時に馬車から自動車へと変遷していった歴史があるように、DBの管理やスケーリングについてもより利便性の高い手段を検討していくことが肝要なのではないでしょうか。

そして、開発者がより開発に集中できるようにし、生産性の向上に繋げていくことが、最終的な競争力に寄与するのだろうと私は思います。

TumblrからWordPressに移行しました。

TumblrとCloudflareの組み合わせは難しい

Tumblr + Cloudflareの食い合わせがあまり良くなかったらしく、どうにもDNS設定周りでしくじるので、思い切ってCloudflare + WordPress + Azure WebAppsにしてみました。

従来のエントリについては全く同じURLでアクセスできるようにしてあります。