2013年5月26日日曜日

32.Google BigQueryでAnalyticsとAdSenseのデータを分析する(Google I/O 2013)

このエントリーをはてなブックマークに追加




Ryo Yamasaki(@vierjp)です。

「BigQueryでGoogle AnalyticsとGoogle AdSenseのデータを分析する」というセッションについてのメモです。

I/Oではこの時間帯に別のセッションを聞いていたのでこの件について全く認識しておらず、
Google I/O報告会で初めて知ったので早速動画をチェックしてみました。

Google Analytics and AdSense Data Analysis in BigQuery (セッションページ)
動画


◯ BigQueryでAdsenseのデータを利用する

そのままではデータセット一覧にAdsenseのデータは表示されていませんが、
現時点で既に利用可能になっています。

・使えるテーブル
 DailyReport
 DailyAdUnitReport
 DailyCustomChannelReport
 DailyUrlChannelReport
 DailyDomainReport


・AdsenseのUIと比べて
 ・よりフレキシブルに好きな組み合わせや角度、基準で分析できる
 ・大量データに対して分析できる

・さらに
 ・一日中更新される
 ・Adsenseのデータに対するBigQueryからのQuery実行は無料

* 無料ってのはいいですね!


・クエリの例
 以下は売上の多いカスタムチャネルの一覧とその売上を取得するクエリです。

select custom_channel_id,
custom_channel_name,
sum(earnings) AS earnings
from
[google.com:adsense-reports:Reports.DailyCustomChannelReport]
GROUP BY
 custom_channel_id,
 custom_channel_name
ORDER BY
 earnings DESC
 LIMIT 5
結果がわかりづらかったのでセッション中でのクエリ例に「custom_channel_name」を追加しています。
 Adsenseを利用していてカスタムチャネルを設定しているならこのクエリで自分のAdsenseアカウントの結果を見ることができます。

* カスタムチャネルの命名が変かもしれませんがスルーしてくださいw


・テーブル定義の参照方法

AdSense BigQuery Integration Guide - AdSense — Google Developers」から
各テーブル名のリンクをクリックしてBigQueryのWebインタフェースに遷移するとデータセットにAdsenseのテーブル定義の一覧が追加されます。
(このURLからアクセスした場合のみ表示されるらしく、再度普通に開いても表示されない)

 BigQueryを操作しているのと同じGoogleアカウントに紐付くAdsenseのデータを参照するようです。


◯BigQueryでAnalyticsのデータを利用する


こちらはまだ一般公開されておらず、2013年9月のリリースを目指しているそうです。

・「Google Analytics プレミアム」のユーザーがAnalyticsのデータを「インポート」できるようになる。
 こちらはAnsenseのようにリアルタイムではなくデイリーでデータが更新されるそうです。
  (そもそもAnalytics自体の更新もリアルタイムではありませんが)
  Analyticsのプレミアムアカウントの存在を初めて知ったので調べてみたところ「想定価格 100万円/月」と、
  なかなかお高い。。

・利用できるデータはFirstPartyのデータだけで、AdwordsやGeographyと行ったデータは利用できない。
 (少なくとも2013年9月時点では)

・申請すれば開発者は事前に使えるかもしれないとのことです。
 Google Analytics Premium & BigQuery 申し込みページ


◯セッションで紹介されていたツール Tableau


Tableauというサードパーティ製のWindows用のGUIツールについて紹介していました。
複数のデータを同じ期間(条件)で並べてグラフ化して比較したり関連を見たりするデモでした。


◯まとめ

AdsenseにしてもAnalyticsにしても、
BigQueryを使って解析することで自社で持っている別の情報とJoinして分析できる事が魅力的かと思います。

例えばニュースサイトなら
「その日に公開した記事のカテゴリ」と「Adsenseの売上」「Analyticsのアクセス情報」
を結びつけて分析するような事もできるでしょう。

以前に書いた「23.BigQueryの新機能 (2013/03/15)」のBig JOINもこういった分析を手助けしてくれます。


Adsenseのデータはタダで分析できるのが良いですね。
分析した上での広告の最適化はGoogleにとってもメリットがあるからでしょうか。

Analyticsはお高く感じますが、BigDataの重要性が言われている昨今
AnalyticsのデータをBigQueryで自由に集計できる事には
マーケティング目的の分析において十分に見合う価値があるのかもしれません。




このエントリーをはてなブックマークに追加

2013年5月24日金曜日

31.Google Cloud Storageに大量データをアップロードする際のテクニック(Google I/O 2013)

このエントリーをはてなブックマークに追加



Ryo Yamasaki(@vierjp)です。

Google I/Oで聞いた「Importing Large Data Sets into Google Cloud Storage」のセッションについて、
動画を見て復習したのでメモを公開。
(動画も上記URLにあります)


◯Small to medium imports

gsutilを使った小〜中規模のアップロード方法
*「gsutil」はGoogle Cloud Storageを扱うためのCUIのツールです。

・ディレクトリ構造
[Directory]
├data
|├data1.csv
|├data2.csv
|├data3.csv
|└data4.csv
└data.csv

・カレントディレクトリのファイルをUploadする
gsutil cp data* gs://iodemo

・サブディレクトリ内のファイルも再帰的にUploadする
gsutil cp -R data* gs://iodemo

・bucket内のファイルを参照する
gsutil ls gs://iodemo
(Google Cloud Consoleからも同様に確認できます)

・ローカルからCloud StorageにUploadする
gsutil cp *.txt gs://my_bucket

・Cloud StorageからローカルにDownloadする
gsutil cp gs://my_bucket/*.txt .

・S3からローカルにDownloadする
gsutil cp s3://my_bucket/*.txt .

・S3からCloud Storageにコピー
gsutil cp s3://my_bucket/*.txt gs://my_bucket

・Cloud StorageからS3にコピー
gsutil cp gs://my_bucket/*.txt s3://my_bucket

*S3との転送にも対応しているのですね



◯マルチスレッドで高速にUploadする方法

Composite Objects and Parallel Uploads - Google Cloud Storage — Google Developers

通常CPコマンドはファイルを1つずつUploadするのでファイル数が多いと時間がかかります。
そこで「-m」オプションを指定するとマルチスレッドで複数ファイルを並列にUploadできます。
(比較的最近追加された機能なので古いgsutilでは使えません。最新版を使いましょう)

gsutil -m cp 〜〜

複数ファイルを並列に同時アップロードするので帯域とDisk I/Oをフルに使ってアップロードが可能になります。



◯Use object composition to improve throughput
サイズの大きなファイルを高速にUploadする方法

1.ファイル「big-file」を「big-file-part-*」に分割
split -n 10 big-file big-file-part-
2.マルチスレッドでUpload
gsutil -m cp big-file-part-* gs://bucket/dir/
3.ローカルの分割ファイルを削除する
rm big-file-part-*
4.Cloud Storage上で分割ファイルを結合する
gsutil compose gs://bucket/dir/big-file-part-* gs://bucket/dir/big-file
5.Cloud Storage上の分割ファイルを削除する
gsutil -m rm gs://bucket/dir/big-file-part-*

* 操作が増えるのでお金は若干余計にかかるそうです。



◯S3からCloud Storageへ、5ペタByteのデータをコピーした事例


・可能な限り速くコピーする
・Live Migration(ダウンタイム無しに)
という条件。

・AppEngineのDatastoreにオブジェクト(ファイル)のリストを持っているっぽい。
・オブジェクト名の一覧をAppEngine上で取得
・AppEngineからTaskQueueを使ってCompute Engine上に配置した「コピー処理」をキック
・Compute Engineはgsutilを使ってS3からGCSにコピー
 (ピーク時に160のCompute Engineのインスタンスが走っていた)
・顧客のシステムからVerify(hash値が一致する事を確認)
(たぶんこんな感じ?間違ってるかも)

平均で秒間10GB、最大で秒間20GBのコピーを行った。



・Object Change Notification (experimental)

Object Change Notification - Google Cloud Storage — Google Developers
bucketに対する変更の通知をWebアプリで受け取ることができる。



・JSON API  (experimental)

Getting Started - Google Cloud Storage — Google Developers

Cloud Storageに対して操作を行うJSONのAPI
JSON APIを使うと「batch request」をすることができる。
「gsutil -m」のように「呼び出し側からマルチスレッドで並行に複数のリクエストを投げる」のではなく、
「一度のリクエストで複数の命令を投げる」手法。

一気に大量のオブジェクトに対してアクセス権限を設定したり削除したりする場合等に有効。



◯Partition with Prefix

ファイル名の先頭文字列毎にUploadするテクニック。
Prefixを指定してコピーすることで簡単に同時実行する。

例えばファイル名が「0-9」で始まっているなら、以下のようにすることでさらに10分割して並列にUploadできます。

gsutil -m cp 0* gs://my_bucket
gsutil -m cp 1* gs://my_bucket
gsutil -m cp 2* gs://my_bucket
・・・
gsutil -m cp 8* gs://my_bucket
gsutil -m cp 9* gs://my_bucket



◯Copying a specific list of files from a text file or program output

テキストファイルやプログラムの出力結果で指定されたファイルをアップロードするためのテクニック

・files.txt
README.md
notes.txt
images/disk_import.jpg

catした結果をパイプで繋いでgsutilに渡す。
cat files.txt | gsutil -m cp -I gs://my_bucket
→リストに書いてあるファイルが全てコピーされる



◯run gsutil near to your source or target

ネットワーク的に速い環境からコピーするため、
ファイルのコピー元かファイルのコピー先に近いところで実行するべし、というお話。

Cloud StorageにUoloadするならCompute Engineからが速そう。
そして話は次の「Offline Disk Import」に繋がります。



◯Offline Disk Import (Limited Preview)

Offline Disk Import - Google Cloud Storage — Google Developers

Uploadしたいファイルをハードディスクに入れてGoogleに送ると
Google内のネットワークからUploadしてくれる

というまさに究極手段。

Google/IO 初日の5/15にサービス開始。

・手順
1.Offline Disk ImportのInterest Formから申し込み
2.SATAのハードディスクをencfs(暗号化ファイルシステム)でフォーマットする
3.データをハードディスクにコピーする
4.Googleに郵送する
5.Googleが顧客の所有する新しいbucketにimportする(Googleの高速なネットワークを使って)
6.GoogleがHDDを郵送で送り返してくれる

現時点ではアメリカ国内の顧客限定ですが、
間違いなく今後数ヶ月の間に国際的に利用できるように拡大するし、
優先順位が上がるかもしれないので海外の顧客も「Interest Form」から連絡してください、とのこと。

料金は通常のGoogle Cloud Storageの料金「リクエスト回数, 帯域使用料,ストレージ使用料」に加えて
HDD毎に「$80」加算されます。



◯Google Cloud Storageの最近のリリース一覧

Versioning
Durable Reduced Availability storage
30% price drop
Cloud Console
Composite Objects
Notifications
JSON API
Offline disk import



個人的には、あとはリクエスト回数に対する課金額
Amazon S3のリクエスト50%~60%値下げを全リージョンで開始、これまでの半額以下に
に追従してくれれば、と思います。
2013/4/3にAmazonが半額にした結果、
これだけGoogle Cloud StorageがS3の2倍のお値段になっちゃってるんですよね(´・ω・`)
他は概ねS3より若干安い料金体系なのですが。





このエントリーをはてなブックマークに追加

30.Google App Engine for PHPにおけるポータビリティを考える

このエントリーをはてなブックマークに追加


Ryo Yamasaki(@vierjp)です。

Google App Engine for PHPでオンプレミス向けのPHP既存アプリはどこまで動くか、
動かす際には何に注意すべきか、「前回行ったWordPressの動作確認」と「公式ドキュメント」を読んだまとめです。


GAE/PHPに関しては
・DatastoreではなくCloudSQLを推していること
・MemcacheのStubの関数の存在
・ドキュメントにWordPressの設置方法について書かれていること
などを見るに、
「既存アプリをそのままAppEngineで動かす」というケースをこれまでよりも強く意識しているように感じました。

これまでAppEngineはこのケースにおいて他のクラウドに遅れを取っていたように思いますが、
(PaaSではWindows Azureが早かった印象)
Cloud SQLやCloud Storage等の関連サービスの充実に伴い現実的になってきたと感じています。

その上でPHPの既存アプリをAppEngineに載せる際に問題になりそうな事を考えながらドキュメントを読んでみました。
と言ってもPHP素人なので考慮しきれていない事は多々あると思いますが。(わかりやすい予防線)



◯File入出力(Google Cloud Storage との連携)


・プログラム内で読み書きしたい場合

AppEngineはその制限上、ローカルのファイルシステムへのアクセスができません。
よって、ファイルの入出力は全てCloud Storageに対して行う必要があります。

・書き込み

$options = [ "gs" => [ "Content-Type" => "text/plain" ]];
$ctx = stream_context_create($options);
file_put_contents("gs://my_bucket/hello.txt", "Hello", 0, $ctx);

・書き込み(Stream)

$fp = fopen("gs://my_bucket/some_file.txt", "w");
fwrite($fp, "Hello");
fclose($fp);

関数的にはPHP標準のものだと思いますが、
Pathの指定方法がGoogle Cloud Storage特有なので
アプリ内でファイルの入出力をしている場合には修正が必要になるでしょう。
(デフォルトのディレクトリとして「gs://[my_bucket]」と指定できたら楽そうですが)


・ブラウザからアップロードしたファイルを保存したい場合

これは前回「29.Google App Engine for PHPでWordPressを動かしてみた」で困った内容です。
※ 2013/6/16追記 WordPressに関してはWordPress用のGoogleの公式プラグインが提供されました。
36.Google App Engine for PHP上でWordPressを運用するためのプラグインが登場


この場合はもう少し大変です。
Direct file uploads to your POST handler, without using the App Engine upload agent, are not supported and will fail.
と書いてある通り、
multipartでアップロードされたファイルの情報を「$_FILES」変数から直接参照することができません。
前回書いたようにダウンロードしてきたWordPressそのままのコードでは画像のUploadができませんでした。

この場合の対応方法は「Uploading Directly to Google Cloud Storage」に書いてあり、
以下のフローにする必要があります。

1.「createUploadUrl」関数でアップロード先のURLを生成してformのactionとして指定する。
2.「createUploadUrlで生成したURL」からPOSTされる「Handler」を作成する。
 「Handler」では一時ファイルの情報を「$_FILES」から取得することができるので、
  ここで「move_uploaded_file」関数を使ってCloud Storageに書き込む。(移動する)

アップロード時の処理のフローは以下のようになります。


元の処理内容によっては簡単に修正できるかもしれません。
CloudStorageTools::createUploadUrl('[元のアップロード先URL]', $options);
formのactionに指定するURLを上記のように生成すれば
「元のアップロード先URL(=Handler)」で一時ファイルの情報を「$_FILES」から取得でき、
その後「move_uploaded_file」で指定するPathをCloud Storageの形式にしてやれば「辻褄が合いそう」な気がします。


ただ、WordPressで修正方針を検討したところでは若干面倒そうでした。
WordPressは「元のアップロード先URL」でCookieから取得したユーザー情報に基づく権限チェック等を行なっています。
しかし、上図③の通り「uploadUrl」から「Handler」には「サーバー間でPOST」しているので、「Handler」ではブラウザが保持するCookieを取得できません。

そうであれば、ユーザー情報に基づく権限チェックは「Handlerがリクエストを受けたタイミング」ではなく
「createUploadUrl」でURLを生成するタイミングで行うことになるでしょうか。
「createUploadUrl」で生成されるURLは推測不可能な文字列を含んでいてかつ有効期間は10分間なので、
ここでチェックしていれば概ね安全そうな気がします。
(さらにHandlerにadmin権限つけて外部からアクセスできないようにできればさらに良い)

ただ、そのためには「元のアップロード先URL」で行なっているチェック処理を
ごっそり「アップロード画面表示時の処理」に移動する必要があります。

Javaの場合でも1分間(AppEngineのリクエストがタイムアウトする時間)でアップロードが終わらないような
サイズの大きいファイルをアップロードする際にはこの「createUploadUrl」を使ったアップロード方法が必要ですが、
この方法しか使えないとなると、既存アプリを修正する場合に元のアプリの作りや処理内容次第では意外と面倒な作業になるかもしれません。

処理フローのレベルで修正が必要になってしまう事を考えると、
ポータビリティの観点では1分制限があってもいいから透過的にやって欲しいなぁというのが正直な想いです。
今のままでは既存アプリのファイルアップロード機能は全て修正が必要になってしまうので。
WordPressを載せてみた感想的には「あとちょっとなのにもったいないなぁ」という気持ちです(´・ω・`)

参考に、上記の挙動を確認した際のログです。

・投稿画面を開いた際のログ(アクセス元IPアドレスが「61.201.***.***」でGET)

・uploadUrlからHandlerにPOSTした際のログ(アクセス元IPアドレスが「0.1.***.***」でPOST)

UserAgentはブラウザから送信された情報を引き継いでいるようなので
Cookieも全部送り直してくれれば辻褄が合いそうですが、それは行儀が悪いからしないのかな(´・ω・`)
それとも何かが間違っていて、もっと簡単な方法で修正できるのかな・・・?

 2013/6/14追記 WordPressに関してはこの問題を解決するプラグインが公開されました。
Blog @vierjp : 36.Google App Engine for PHP上でWordPressを運用するためのプラグインが登場


参考:
ファイル入出力 | PHP Labo
Google Cloud Storage PHP API Overview - Google App Engine — Google Developers



◯MySQLの利用 (Google Cloud SQL との連携)

Using Google Cloud SQL with App Engine PHP SDK - Google App Engine — Google Developers

・PDO
・mysql_connect
・msqli
を使用可能。

WordPressを試してみた限りではDB周りは特に問題無さそうでした。
(どちらかというとCloud SQLについてちゃんと調べた方が良いかも)


◯ログ出力

ググって見たところ、PHP標準のログ出力関数は以下の様なものがあるのでしょうか。
・error_log関数
・syslog関数

ドキュメントに書かれているのは「syslog関数」のみで
「error_log関数」については記載がありませんが、以下の挙動になります。

・log_test.php
<?php
error_log('##### 0 tset Message', 0);
error_log('##### 1 tset Message', 1);
error_log('##### 3 tset Message', 3, '/var/tmp/app.log');
//error_log('##### 3-2 tset Message', 3, 'gs://[bucket名]/app.log'); // エラー
error_log('##### 4 tset Message', 4);
echo "log_test";
?>

・出力されたログ

・ファイル名を指定しているケースはログ出力不可(代わりにWarning)
・Cloud StorageのPathを指定した場合はエラー
・それ以外はsyslogをErrorレベルで出力した場合と同様の結果
となりました。


参考:
unoh.github.com by unoh
Logs PHP API Overview - Google App Engine — Google Developers


◯Session

The PHP Runtime Environment - Google App Engine — Google Developersに書いてあるとおり、

・Sessionに保存

session_start();
$_SESSION['Foo'] = 'Bar';

・Sesionから取得

session_start();
print $_SESSION['Foo'];
という感じに普通に使えます。


しかし一点気になる記述がありました。
By default the App Engine runtime will use memcache to store session information using the MemcacheSessionHandler class.
(中略)
However data in App Engine memcache may be flushed periodically, meaning any session information will be lost.
For longer-lived sessions, it may be preferable to use an alternative storage service such as Cloud SQL.
・デフォルトではSession情報をMemcacheに保存している
・Memcacheのデータはクリアされるかもしれないのでそれによってセッション情報が失われる可能性がある。
・長い期間Sessionを維持したければ、Cloud SQL等にセッション情報を保存した方が良い。

Javaの場合はDatastoreにSessionデータが記録されていますが、
(Memcacheも併用していると聞いた事がある気もするけど、とにかく永続化されている)
PHPの場合はMemcacheにしか保存していないようです。
実際プログラム中でSessionを使ってもJavaと違ってDatastoreにSession情報を保存するためのEntityは生成されません。

大量のクライアントからのアクセスがあった場合にはSession情報が頻繁にMemcacheからクリアされてしまうかもしれません。
それが困る場合はCloud SQLを使った自前のSessionHandlerを作ってそれを使うべし、との事のようです。

ググってみたところではセッションをMYSQLに書き込む方法としては以下のサイトが見つかりました。
PHP/セッション管理 - がしまっくす
( 「MySQLを使ったセッション管理」-「session_mysql.php」の部分)

ただ、近年は自分がJavaでAppEngineのアプリを作成する場合、基本的にセッションを使わずステートレスに作っていたので、
設計次第ではセッションをがっつり使わなくてもそれほど困らないかと思います。
というか、AppEngineに限らず新規に作る場合には可能であればできるだけ使わない方が良いと思っています。

ただし、既存のシステムをAppEngine上に持ってくる場合にはこの点について留意した方が良いでしょう。


◯mbstring

※2013/6/16追記 Google App Engine 1.8.1で mbstringが追加されています。
http://phpinfo.vier-test.appspot.com/

気になるツイートを見かけたので少し試してみました。
phpinfo見たところ mbstring, Zend multibyteが off のようだけど
これらはPHPでマルチバイトを扱う際に必要なのでしょうか。
WordPressを触ったところではマルチバイト関連での問題は起きていませんが、
どういう場合に困るんでしょ。

ググって見よう見まねでphp.iniを書いたところ、
phpinfoで「zend.multibyte ON」にできました。(「Zend Multibyte Support」はdisabledのままですが)
mbstringについては今のところ有効にすることができていません。

・php.ini
;参考
;http://d.hatena.ne.jp/do_aki/20111208/1323315995
;http://wiki.ohgaki.net/index.php?PHP%2Ftips%2F%E6%97%A5%E6%9C%AC%E8%AA%9E%E7%92%B0%E5%A2%83php.ini%E8%A8%AD%E5%AE%9A
;http://www.phpbook.jp/install/phpini/index5.html

google_app_engine.enable_functions = "phpversion, phpinfo"
output_buffering = "On"

default_charset="UTF-8"
magic_quotes_gpc=off

[mbstring]
mbstring.language=Japanese
mbstring.internal_encoding=UTF-8
mbstring.http_input = pass
mbstring.http_output = pass
mbstring.encoding_translation = Off
mbstring.detect_order = UTF-8,SJIS,EUC-JP,JIS,ASCII
mbstring.substitute_charactor=none
mbstring.strict_detection = Off
mbstring.input_encoding=pass
mbstring.output_encoding=pass

zend.multibyte = On
zend.script_encoding = UTF-8

・mbstring.php
<?php
  if ( extension_loaded('mbstring') ) {
    echo "mbstring is loaded";
  }else{
    echo "mbstring is not loaded"; //←こちらが出力される
  }
?>


他に「この設定を試してみろ」等あれば是非w


◯AppEngine固有サービスの対応状況

ドキュメントに書かれているのは以下の6つです。

Logs
Mail(送信、受信、バウンス)
Memcache
  Javaでは見たことがないstubの関数(呼び出すことはできるが何もしない)がいくつも用意されています
URL Fetch
Users
Task Queues(Pushのみ、Pullは無し)


以下のサービス等はまだ使えないようです。
・Channel API
・Images API
・Prospective Search
・Full Text Search
・Sockets
(他にもPHPにはまだ無いものがあります。正確に知りたい場合はJavaやPythonと比べてみましょう)

Datastore関連の機能もドキュメントにはありません。
(別サービスの「Cloud Datastore」はJSON API経由で使えると思います)


◯まとめ

環境の管理が不要なPaaSのAppEngineはIaaSやオンプレミス環境と比べると比較的制限は多く、
また、PHPはまだLimited Previewです。

と前置きした上で、

オンプレミス環境向けに作られた既存のPHPアプリをAppEngine上で動作させる場合には、
前述のファイルアップロードやSessionのように
「ドキュメントを読んだ上で自分が載せようとしているアプリについてその制限が問題とならないかどうか」
を検証する必要があるでしょう。

ファイル入出力についてはAPI的にはローカルのファイルシステムと同様に行うことができますが、
実際にはネットワークをまたがって別サーバーに対してファイルの読み書きをしているわけですから
Webサーバーのローカルのファイルシステムに対して読み書きする場合と比べて速度は低下するでしょう。
読み書きするファイルの使い道次第ではこれがパフォーマンス的な問題に繋がる事があるかもしれません。

また、プログラムからのファイルの出力先は必ずCloud Storageなので、
「アプリケーションのディレクトリ内に*.phpファイルを生成する仕組み」や、
「*.phpファイルを含むファイルをアップロードするような仕組み」
(WordPressのテーマを管理画面からアップロードする場合なんかはそうなのかな?)
に対応するのは難しいでしょう。


機能的な制限については「The PHP Runtime Environment」から読むのが良いと思います。
その上で各機能毎のページをチェックしてみましょう。
前回のWordPressの例のようにとりあえず載せて動かしてみて、
エラーになったら調べてみるというのも手っ取り早いかもしれません。


Cloud SQLもCloud Storageも無かった頃と比べると、
オンプレミス環境向けに作られた既存アプリを動かすための環境は徐々に整ってきています。
特にPHPはまだLimited Previewなので、今後の発展に期待したいと思います。(`・ω・´)


*ご意見・ご指摘等ありましたらコメントでもGoogle+のメッセージでもメールでも、ご連絡いただけましたら幸いです。




このエントリーをはてなブックマークに追加