PHP(Lumen)からAzure Storage Tableを使ったときのメモ2
内容
- 上記ブログでAzure Storage TableにPHPからアクセスしたときにハマったポイントをまとめたメモ
処理概要
- CSVから読み込んだデータをAzure Storage Tableに保持
- CSVの1行がAzure Storage Tableの1Entityという構造
- CSVのKey項目が存在しなければInsert(登録)、Key項目が存在すればUpdate(更新)する
- CSVのKey項目でStorageから検索して、登録または更新を判定
- Azure Storage TableのEntityのプロパティはすべて、String(文字列)の型を指定して、CSVの記載のまま登録したい
ハマったポイント1
Azure Storage Tableから取得した値が想定外!!?
- 設定したときは、String(文字列)を指定
$entity->addProperty("hoge", EdmType::STRING, csvRec[0]);
- プロパティから取得するときは、getValue関数を使用していた
$entity->getProperty("hoge")->getValue();
- 設定した値のまま取得できることを想定していた。 基本的には想定通りの動作だったが、小数点付きの数値などの場合に設定値と異なることがあった
- 設定値が "0001.0" の場合。getValueすると、 "1" で返されることを確認
- azure-storage-tableのライブラリの中を確認
- MicrosoftAzure\Storage\Table\Internal の JsonODataReaderWriter クラスでStorageから取得したEntityを変換している
- 上記クラスのparseOneEntity(private関数)でEntityを生成してプロパティを1つずつ設定している
- プロパティの型がStringの場合に、strvalした値をvalueに設定している
- strval(0001.0)の戻り値は、1となるため、今回の現象になっている
- Entityから以下のようにすると、想定通り、設定値と同様(0001.0)の値が取得できる
$entity->getProperty("hoge")->getRawValue();
ハマったポイント2
Entityを更新すると想定してプロパティの型が想定外!!?
- entityを更新するときには、Storageから取得したentityを元に一部のプロパティを変更していた
$tableClient = TableRestProxy::createTableService($connectionString); $entity = $tableClient->getEntity("mytable", "tasksSeattle", 1); $entity->setPropertyValue("hoge", "fuga"); $tableClient->updateEntity("mytable", $entity);
- 上記コード例のような場合、変更したプロパティは想定通りだったが、変更していないプロパティが元の型と別の型に自動的に変わっていることを確認
- 上記コード例でentityに「weight」というプロパティに「60.5」という値が設定されていた場合、updateEntityされると元々はString型だったものがDouble型に自動で変換されている。また日付型データも同様の現象が発生することを確認
- 取り急ぎの解決策として、Storageから取得したentityはそのまま使わないようにして、別途Entityを生成してaddPropertyすることで、すべてのプロパティをString型で保持するようにした
ハマったポイント3
getRawValueの値が想定外!!?
PHP(Lumen)からAzure Storage Tableを使ったときのメモ1
内容
- Lumenで構築しているWebアプリケーションからAzure Storage Tableを使ったときのメモ
- PHPからの接続は、MSの公開ライブラリを使用(以下の方法でライブラリ追加)
- composer require microsoft/azure-storage-table
バージョン
前準備
ストレージアカウントを作成
- 以下のリンクを参考にして、ストレージアカウントを作成し適当な名前でTableを新規作成
Azure Storage Tableを確認するためのツールをインストール
ライブラリの使用方法
- 基本的には以下のリンクの方法でTable操作とEntity操作は問題なくいける
実装ポイント
Entityの検索処理
- Key検索ではなくパーティションを条件に全件検索などするときに、1度に返される結果件数は1000件の制限がある
- 検索結果が1000件より多い場合は、検索結果のcontinuationTokenを使用して次の1000件を取得するような実装が必要になる
サンプルコード
// SampleTableからの検索結果 $entities = []; do { $result = $tableClient->queryEntities('SampleTable', $options); $entities = array_merge($entities, $result->getEntities()); $options->setContinuationToken($result->getContinuationToken()); } while($result->getContinuationToken());
複数Entity操作処理
- 複数行まとめて追加や更新などしたいときには、Batch処理としてまとめて処理することが可能
- 1Entityごとに処理するよりもBatch処理を使用する方がパフォーマンス的にも良い
- 但し、1Entityに対しての複数操作をBatchでまとめるのはやめてというのがドキュメントに記載されている
サンプルコード
$batchOperations = new BatchOperations(); foreach ($records as $rec) { $entity = new Entity(); $entity->setPartitionKey('hoge'); $entity->setRowKey('fuga'); $entity->addProperty("propKey1", 'propVal1'); $entity->addProperty("propKey2", 'propVal2'); if (count($batchOperations->getOperations()) % 100 === 0) { $tableClient->batch($batchOperations); $batchOperations = new BatchOperations(); } } if (count($batchOperations->getOperations()) % 100 !== 0) { $tableClient->batch($batchOperations); }
Board APIのデータをChart表示するElectronアプリ(vue.js)
概要
- ElectronとBoard APIを使ったサンプルアプリ作成(素振り)
- Board APIで請求データと支払データを取得してChart表示
- 基盤はelectron-vueを使用して実装
- CSSフレームワークにはbuefyを使用
- ストレージにnedbを使用
使用技術
- electorn:2.0.2
- vue:2.3.3
- vue-electron:1.0.6
- bignumber.js:7.0.1
- buefy:0.6.5
- chart.js:2.7.2
- nedb:1.8.0
今回のソース
工夫したところ
- API接続情報をストレージに保持
- APIから取得したデータをストレージに保持
- Chartを表示するときに都度、APIアクセスするのは非効率なため、nedbで保持できるように対応
- Board APIへのアクセス方法
使用方法
1. Board APIのキーとトークンの発行
- Board APIについては、以下を参照
2. 設定画面でBoard API接続情報の設定
- 1で発行した接続情報を入力して設定ボタンをクリック
- 取得ボタンは、nedbに保持しているデータを取得して表示します
3. API接続ボタンをクリックしてデータ取得
4. 請求データ画面でChart表示
- APIで取得したデータを元にChartを表示します
今後
基盤はできたので、あとは必要に応じてChartの種類を追加していきたい
Board APIの利用について
会社でWebサービスのBoardを利用していて、 API(ベータ版?)が公開されているので使ってみたメモ。
API仕様については、以下にまとまっていて KEYとトークンが発行できれば、あとは仕様に沿って リクエストするという流れ。
最初、curlコマンドで叩いていて、問題なくレスポンスが取れることが確認できた。
レスポンス結果を使って、適当にChart(jsで描画)を表示してみることを試してみようと思い ローカル環境のWebサーバに適当なhtmlとjsでページ作成し試したところ クロスドメインのエラーとなった。
Boardのサイトからチャットにて問い合わせしたところ、すぐに担当者に繋いで頂き 現時点では、セキュリティの関係上、ブラウザからの利用は想定していなく サーバからアクセスする流れとなる。 ※チャットで即座にご回答頂けたので、非常に助かりました。
とりあえず遊びで使ってみたかっただけなので今回はローカル環境に 以下のnode.jsのプロキシサーバを立てて対応して動かしました。
BoardのAPIはリクエスト数の制限が割りと厳しいので 真面目に作るなら、サーバ側でデータ保持するなど 何かしらのキャッシュ機構を作成する必要がありそうという印象。
CordovaのAndroidビルドエラーと環境構築
前提の環境
- Macを新しくして、改めて環境構築時
- Android開発用にAndroid Studioをインストール済み
- 以下2つを環境変数のPATHに追加済
- Android StudioのSDKManagerから、SDK Platformのタブを選択して適当な環境を複数インストール済
ビルドエラーについて
cordovaで開発するときに、Android用のビルドで以下のようなエラーが発生。
Starting 'cordova-with-build'... (省略) Error: Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable. 'cordova-with-build' errored after 5.55 s
これだけ見ると「ANDROID_HOME」を設定すればいいように見えるが環境変数として、「ANDROID_HOME」に「~/Library/Android/sdk」を設定しても同様のエラーとなり解決せず。
確認
cordova requirementsコマンドでみると、以下のようにAndroid targetがnot installedになっている。
本来であれば、ここはAndroid StudioのSDKManagerでインストールした環境が表示されるはず。
$ cordova requirements android Requirements check results for android: Java JDK: installed . Android SDK: installed Android target: not installed Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable. Gradle: not installed Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.
対応方法
- Android SDKの下に配置されているtoolsを差し替えました、方法は以下の通り。
- https://dl.google.com/android/repository/tools_r25.2.3-macosx.zip
上記からzipをダウンロードして解凍 - 「~/Library/Android/sdk」直下にある、toolsディレクトリを適当な名前にリネームしてBackup
- 解凍したtoolsディレクトリを、「~/Library/Android/sdk」直下に配置
- 念のため、以下のcordovaコマンドを実行
・cordova platform rm android ・cordova platform add android
上記を試してた後に、改めてcordova requirementsコマンドを実行し、targetに正しく値が表示されていることを確認してOK
$ cordova requirements android Requirements check results for android: Java JDK: installed . Android SDK: installed Android target: installed android-21,android-22,android-23,android-24,android-25,android-26,android-27 Gradle: installed
App Service(Microsoft Azure)とAzure SQL DatabaseのReconfiguration対応
概要
- Azure環境でシステム開発している中で、Reconfigurationという現象に遭遇したので、その時の対応についてまとめる
Reconfigurationについて
- Azure SQL Databaseには、サービスの仕様としてReconfigurationというものがある
- Reconfigurationが発生すると、数秒〜数十秒の間、DB接続ができなくなる
- Reconfigurationは、不定期で発生する(現状だと1ヶ月に2、3度)
- Reconfigurationそのものは、安定したDBの稼働のために必要なものらしい
- Azure SQL Databaseを使用するには、Reconfigurationが発生することを前提で開発する必要がある
[SQL Database] Reconfiguration (リコンフィグレーション) は悪ではない。 – Microsoft SQL Server Japan Support Team Blog
環境
本番環境
- Azure App Service(Windowsサーバ)
- PHP5.6
- Lumen
- Azure SQL Database(SQL Server)
開発環境
- vagrant(CentOS)
- PHP5.6
- Lumen
- Azure SQL Database(SQL Server)
Microsoftの公式ドキュメントの対処方法
- Microsoftの公式ドキュメントでは、Reconfgiuration発生を検知し、wait後にリトライを実装する流れを推奨。
- Reconfiguration発生時に使用した接続は使用できないため、改めて接続し直す必要がある。
テスト方法
- 基本的には、Reconfigurationは不定期に発生するが、意図的に発生させることも可能。
- Azure SQL Databaseのプランを変更することで、プランが切り替わる間の数秒間だけReconfigurationが発生。
- Azure Portal上からプラン変更して、SQL実行を連続して実行することでテスト可能。
ハマるポイント
Reconfigurationの判断
- 本番でDB接続エラーが発生したときに、それがReconfigurationによるものなのかは現在MSサポートに問い合わせる必要がある。 以前までは、sys_event.logに出力されていたらしいが、現在は出力されない。
- Microsoft公式ドキュメントでは、Reconfiguration発生の判断をSQLのERRCODEで制御する方法を記載しているが、コネクションプーリングが有効になっていると古いコネクションを使用することでERRCODEに値が入らないことがある、そのときには、SQLSTATEしか入っていないため、そちらで判断する。(これはPHP以外の言語では大丈夫かも。ドライバの問題な気がする。)
リトライ処理をしても同様の接続エラーが発生
- Lumenでは、リクエスト単位でPDOをキャッシュする機構がある
- Lumenの機構を改修して、Recofiguration発生時にのみ、PDOを再生成して失敗したSQL実行という流れを単純に作って対応したが、 Reconfiguration発生時と同様の接続エラーが発生
- PDOを再生成しても内部ではReconfiguration発生時のプールを使用しているため、エラーが発生する
App Serviceのコネクションプーリングの制御
- App Serviceのプラン(サーバスペック)によって、プールの最大数などが決まる仕様になっている
- PHPからプールを破棄するAPIなどは用意されていない
- PHPからアクティブなプール数を取得するAPIなどは用意されていない
- コネクションプーリングを無効化すると、その都度確実に接続しにいくため、エラーが発生し続けることはない
- 但し、遅くなる。
最終的な対応方法
- 基本方針として、コネクションプーリングを一律無効化するのは、パフォーマンスの観点からNGなため、デフォルトはプールを有効化
- 現状は対応1でも問題なく動いているため、様子見。これでも駄目な場合、別途キューのプロセス管理が必要になるが、対応2を検討
対応1
- Reconfigurationを検知したときに、wait後、コネクションプーリングを無効化したPDOを生成し失敗時のSQLを再実行
対応2
- Reconfigurationを検知したときに、システム全体でコネクションプールを無効化するように制御(PDO生成周りの処理改修)
- キューに90秒後コネクションプールを有効化する処理を追加(プールの生存期間が60秒なため、若干の余裕を持たせる)