俺の報告

RoomClipを運営するエンジニアの日報(多分)です。

このポストは意味無いです。 - 日報 #125

この状態で花粉以外の話をしろという方が無理あるだろう。
薬の飲み過ぎで乾燥した上に、鼻をかみまくったせいで、
若干血の味の匂いがし始めた鼻腔の季節になりましたね。

嫌いじゃないですよ、鉄の匂い。

さて、本日はガチャガチャとタスクキルをしておりました。
反省点としては「とりあえず作ってみよう」と思って作った機能であっても、
ある程度ちゃんとしてないと大変なことになるということですね。
適当であっても作ってしまうと使ってしまうのが人の性で、
使ってしまうと求めてしまうのが人の欲で、
求めてしまった結果、五月雨式にやってしまうと、
地獄が待っとるわけです。
何事も計画しないと駄目ですね。

あ!
話すことない!
じゃぁね!!

MySQLでバージョン形式を認識する関数 - 日報 #124

花粉がどえらいことになっとるようです。
僕のCloudWatchはずーっとAlert出しっぱなしです。
対策は修正コードをpatchすればいいのですが、
このpatchは有効期限があって定期的な運用が必要になります。

だるいわぁ。。。

なんか大量の花粉対策薬を事前に腹にいれといて、
定期的に少量ずつ注入して欲しいです。
cronのように。

さて、本日はちょっとしたSQLのクエリチップスを。
バージョン表記ってあるじゃないですか。
例えば、ver 5.3.1 みたいな。
これをDBに突っ込んだ時、結構扱いづらくなったりします。
メジャーバージョン4以上の、、、とかそういう条件が組めなくなるんですね。
まぁ別にやり方は他にいも色々あるので、別にクエリで解決しなくても問題ないんですが、
クエリだってデキる子なはずなので、バージョンを桁ごとに抽出する関数を自作しました。
まぁほとんど使わないと思いますが、、、
MySQLだって色々とFunctionでできるんだってことが分かりました。

使い方としては、
get_version("5.3.1", 2)
のように書くと 5.3.1 の二桁目なので 3 が返る、という関数です。
SPLIT関数があればよかったのですが、そんなものないので、POSTIONを追跡する形で、
なんとかしております。
version_table テーブルに、versionカラムというのがあって、
そこに任意の桁数 5.3.1 のような文字列が入ってるとします。
その時、こんなクエリを書くと、一発で目的の桁を抽出することができます。

DROP FUNCTION IF EXISTS get_version ;

DELIMITER //
CREATE FUNCTION get_version(
  `str` TEXT,
  `section` INT
)
RETURNS VARCHAR(10) DETERMINISTIC

BEGIN
 
  DECLARE result VARCHAR(10);
  DECLARE counter INT(10);
  DECLARE start_pos INT(10);
  DECLARE end_pos INT(10);
  DECLARE tmp_pos INT(10);
 
  SET start_pos=1;
  SET end_pos=LOCATE('.', str, start_pos);
  SET counter=1;
  WHILE counter < section DO
    SET tmp_pos=end_pos+1;
    SET end_pos=LOCATE('.', str, end_pos+1);
    IF end_pos = 0 THEN
      SET end_pos=LENGTH(str)+1;
    END IF;
    SET start_pos=tmp_pos;
    SET counter=counter + 1;
  END WHILE;
 
  SET result=SUBSTRING(str, start_pos, end_pos-start_pos);

  RETURN result;

END;
//
DELIMITER ;

SELECT
  version,
  get_version(appversion, 1) AS major,
  get_version(appversion, 2) AS minor,
  get_version(appversion, 3) AS fix
FROM version_table;

-- | 5.3.1 | 5 | 3 | 1 | と出力されます。

便利、、、なこともないと思いますが、
なんかに使えればどうぞ。

PHPで手軽にページネーション サンプルコード - 日報 #123

ページネーションって面倒ですよね。
きっとよいプラクティスがあるはずですし、
色んなフレームワークに既に入っているものかと思います。

ですが、毎度毎度自分で書いている気がしますのは何故でしょうか。
なんか要求と合わない実装が多いんですよね、、、
php pagination exampleでぐぐってもなんだかなぁという感じでした。 一応今回ボクがつくった簡単なページネーションfunctionを置いておきますが、
恐らくこれも誰にも役にたたないのでしょう。

でも置くだけ置いておきます。

<?php

// こんな感じで使う
// 全体で20ページ、表示させたいページの数は5、今のページは4、最初のページは1
$page_info = display_paging(4, 5, 20, 1);
var_dump($page_info);
// array(min_page=>1, prev_page=>3, next_page=>5, max_page=>20, display_pages=>array(2,3,4,5,6))
// という配列が返ってくる

// ちなみに、左端、右端に到達している時、max_pageやprev_pageなどは消失するため、NULLが代入される

if (!function_exists('display_paging')) {
     /**
      * In paging processes, If you need to show like below,
      * 1... prev 6 7 8 9 10 next ...100
      * Use this function.
      * Return array('min_page', 'prev_page', 'display_pages'=>array(), 'next_page', 'max_page')
      *
      * The patterns are... ($page_range = 5)
      *
      * 1...6 7 8 9 10...100: [1, 7, [6,7,8,9,10], 9, 100]
      * 1 2 3 4 5 ...100: [null, 2, [1,2,3,4,5], 4, 100]
      * 1 2 3 4 5: [null, 2, [1,2,3,4,5], 4, null]
      * 1 2 : [null, null, [1,2], 2, null]
      * 1...6 7 8 9 10 : [null, 8, [6.7.8.9.10], 10, null]
      *
      * @param int $start_page     current page number
      * @param int $page_range     page range for displaying
      * @param int $max_page     max page number
      * @param int $min_page     min page number | default value is 1
      * @return array
      */
     function display_paging ($start_page, $page_range, $max_page, $min_page=1)
     {
          // args must be int
          $start_page = intval($start_page);
          $page_range = intval($page_range);
          $max_page   = intval($max_page);
          $min_page   = intval($min_page);

          // not zero
          if (($start_page * $page_range * $max_page * $min_page) === 0)
               return array();

          // $page_range must be odd number, and not 1
          if ($page_range % 2 === 0 || $page_range === 1)
               return array();
         
          $result = array();
         
          $fair_range_min = $start_page - ($page_range - 1) / 2;
          $fair_range_max = $start_page + ($page_range - 1) / 2;
         
          // Left adjust
          if ($fair_range_min <= $min_page) {
               $result['min_page'] = NULL;
               $fair_range_max = $fair_range_max + ($min_page - $fair_range_min);
               $fair_range_min = $min_page;
          } else {
               $result['min_page'] = $min_page;
          }
         
          // Right adjust
          if ($fair_range_max >= $max_page) {
               $result['max_page'] = NULL;
               $fair_range_min = $fair_range_min - ($fair_range_max - $max_page);
               $fair_range_max = $max_page;
               if ($fair_range_min < $min_page)
                    $fair_range_min = $min_page;
          } else {
               $result['max_page'] = $max_page;
          }
         
          // create array
          for ($i=$fair_range_min; $i <= $fair_range_max; $i++) {
               // prev page
               if ($i === $start_page) {
                    $result['prev_page'] = ($start_page - 1) < $min_page ? NULL : ($start_page - 1);
               }
               // next page
               if ($i === $start_page) {
                    $result['next_page'] = ($start_page + 1) > $max_page ? NULL : ($start_page + 1);
               }
               if ($i > 0 && $i >= $min_page && $i <= $max_page)
                    $result['display_pages'][] = $i;
          }
          return $result;
     }
}
?>

まぁ、、、
どなたかの参考になれば、、、
それでは。

季節とか変動要素っておしなべられないかね - 日報 #122

引き続き日々作業でございます。
特に報告することもないので、今日は形骸日報。

飼ってるインコのお千代さんが随分沢山フケを飛ばすようになりました。
季節の変わり目なんでしょうか。
僕の鼻がグチョグチョなのも季節のせいですしね。

前回も少し触れましたが季節要因というのはとても面白いです。
皆がまぁ大枠同じパターンで活動している証拠ですね。
夜更かし組なら誰しも思うことですが、
夜組・朝組に分かれて生活したほうが交通渋滞とかもなくなるし、
寝てる間の事故とかもなくなるしいいんじゃないの?理論ってのがあります。
季節でもそんな感じにならないもんですかね。
何言ってるかわかりませんが、
要は需要期とか、ちょっとグロースからすると困る要因なんですよね。
まぁ嬉しいんですが、、、施策がヒットしたのかがわからなくなるのです。

あぁ本当に中身の無い日報でしたが、
こんな日もこんな日で。

Product Advertising API の SOAPリクエスト aws:Client.RequiresSSL エラーについて - 日報 #121

さて金曜日になりました。
RoomClipは曜日変動が微妙にあるサービスなのですが、
想像しやすい事象とはいえ、中々この曜日変動というのは不思議なものだと感じます。
1週間というループが仮に10日だったら、10日なりの変動周期になっていただろうと考えると、
それと7日ループとを比べてみたいものです。
相似形になるのでしょうかね。

さて、前回に引き続いて、
突発案件について少し。

直近でいうと、Amazon Product Advertising のAPI応答エラーについて。
早い話が、Amazon商品情報APIに対して何らかクエリを投げて、
検索結果一覧を得るというシステムが、ある日突然動かなくなりました。という事件です。

AmazonECS.class.phpというここで手に入る ライブラリを使ってSOAP接続をして結果をパースしていたのですが、
ある日こんなエラーが吐出されるように。

Fatal error: Uncaught SoapFault exception: [aws:Client.RequiresSSL] SSL connection required for SOAP authentication

このエラー文で検索すればワンサカ出てきますが、
大体取りまとめるとこんな助言が得られます。

  • SOAPクライアント本当に入ってる?ちゃんとインストールしな。
  • なんかco.jpだけエラーでてるみたいだから.comとかにしてみたら?
  • Amazon側の問題みたいだから、復旧を祈るしかないべ

乱暴ですが、こんな感じです。まぁ結論「よく分からん!」という感じでした。
そもそもSOAPとはプロトコルの1つで、HTTPとかと同じようなジャンルのものです。
一般的に、ブラウザでページアクセスをする時というのは、
ブラウザソフトがサーバソフトに対して「決まった書式と段取り」に従ってリクエスト情報を投げたり、
レスポンスを受けたりするわけです。
この「決まった書式と段取り」のことをプロトコルと呼び、いまスタンダードなのはHTTPプロトコルなわけですね。
簡単に言うと。
SOAPはその「決まった書式や段取り」、プロトコルの1つです。
特にXMLでリクエスト文やレスポンスを受けるルールになっているため、
HTTPよりしっかりと構造化されているプロトコルで一時期は「凄いかも」と目されていたはずです。
今はどうか、、、知らないですけど、、、
とにかく、AmazonはそのSOAPで商品情報APIを取得することができるわけなんです。
その細かなやり取りを担ってくれるのがAmazonECS.class.phpだったはずなのですが、、、
上記のエラーなわけです。
最初からエラーだったらいいのですが、いきなりなるってことは気味が悪いですね。

で、色々なドキュメントを見ると、
驚くことにSOAPのエンドポイントがドキュメントごとで結構違います。
https://images-na.ssl-images-amazon.com/images/G/09/associates/paapi/dg/index.html?SOAPEndpoints.html
ここと、
http://docs.aws.amazon.com/ja_jp/AWSECommerceService/latest/DG/SOAPEndpoints.html
ここで、異なります。
なんだかこんがらがってきてよく分からんので、
この辺は目をつぶって「きっと何らかの仕様が変更されたんだろう」という予測のもと、
ライブラリの方をアップデートしてみました。

AmazonECS.class.phpはレガシーとなり、今はApaiというライブラリになっております。
ここgithubリポジトリです。
かなりナウい構造になっているので、面くらいますが、大人しくcomposerでインストールします。
composerでインストールしたらautoload.phpをインクルードして、さっそく使ってみます。

が、
やはりSOAPでつなぐと同じエラーが!
もーーやだ!
SOAPなんて使わないぞ!
ということで、別の方法としてRESTを使います。
コードはこんな感じ。

<?php

require_once(LIB_PATH.'/libraries/ApaiIO/vendor/autoload.php');
use ApaiIO\ApaiIO;
use ApaiIO\Configuration\GenericConfiguration;
use ApaiIO\Operations\Search;
use ApaiIO\Operations\Lookup;

$devId = "AMAZON_ACCESS_KEY"; // アマゾンキー
$secret = "AMAZON_ACCESS_SECRET_KEY"; // シークレットキー
$aflId = "AFIL_ID"; // アフィリエイトID

$conf = new GenericConfiguration();
try {
  $conf
    ->setCountry('co.jp') // 日本
    ->setAccessKey($devId)
    ->setSecretKey($secret)
    ->setAssociateTag($aflId)
    ->setResponseTransformer('\ApaiIO\ResponseTransformer\XmlToSimpleXmlObject'); // レスポンスをXmlObjectに変更するトランスフォーマークラス
    /*
      ApaiIO/vendor/exeu/apai-o/lib/ApaiIO/ResponseTransformer/ 配下にいっぱい変換クラスがありますので、
      目的のを選ぶといいです
    */
} catch (\Exception $e) {
    echo $e->getMessage();
}
$apaiIO = new ApaiIO($conf);

// Keywordで検索
$search = new Search();
$search->setCategory('All');
$search->setKeywords($keyword);
$search->setPage(1);
$search->setResponseGroup(array('ItemAttributes', 'Offers', 'Images', 'EditorialReview'));
$search_response = $apaiIO->runOperation($search);
$search_response = json_decode(json_encode($response), TRUE);

// ItemIDで検索
$lookup = new Lookup();
$lookup->setItemId($this->params['item_id']);
$lookup->setResponseGroup(array('ItemAttributes','Offers','Images','EditorialReview'));
$lookup_response = $apaiIO->runOperation($lookup);
$lookup_response = json_decode(json_encode($response), TRUE);

?>

RESTはとても順調光速で機能しました。
ありがたいことです。
ちなみに、Xmlをそのままオブジェクトにするのがだるかったので、

$lookup_response = json_decode(json_encode($response), TRUE);

このように1回jsonにしてから引き戻してArray化するという手法をとっています。
Xmlの属性値が使えなくなりますが、、まぁ一応機能は果たしましたのでよしとします。

以上でした。
でも結局原因が分からず、非常に消化不良です。
どなたか何か情報ございましたら教えて頂けると嬉しいです。
それでは!

突発案件が多いとケアレスミスが増えますよ - 日報 #120

AM 3:30くらいの静寂が永遠だったらな、 なんて思う時点で腰が引けてますよね。
ここんところ突発案件ばかりでちょいとパッツパツでした。
全項目において知見が一応ありますので、
ゆるやかーに共有していければいいと思っています。

ということで、ここ3日くらいで発生した、
俺殺しの突発案件リスト。

  1. ある時突然、Fluentdでflushされないbufferファイルが発生したよ(依然調査中、知見あり)
  2. ある時突然、WEBサーバが1台不安定になったよ(調査済み、知見あり)
  3. ある時突然、Amazon Product Advertizeing APIが突然応答しなくなったよ(調査済み、知見なし)
  4. ある時突然、Slowクエリが突如増加したよ(調査済み、知見なし)
  5. ある時突然、memcachedが保存しなくなったよ(調査済み、下らないミスあり)

という四面楚歌状態で、「突然」という言葉が完全にゲシュタルト崩壊する事態に陥りました。
しかも結構一発一発が重いので、
中々にグロッキーな状態でファイトを続けるといった手合です。

1つ1つ、同じ過ちを繰り返さないように、皆様にもゆっくり共有していきます。
ちなみに、僕という個体に対してもバグが発生していおりまして、

  1. ある時突然、鼻水と目の痒みが止まらなくなったよ(調査済み、知見なし)

ということもあったのですが、
コレに関しては「根本治療なし、頓服対応あり」という姿勢を崩さずに対処していく所存です。

なんか尻切れトンボなんで、5番の下らないミスのみちょっと共有。

PHPなどで、まれに、IF文の条件式内での変数代入というのをやることがあります。
こんな感じ。

<?php

$var = 'abc';
$_var = 'def';

if ($var = $_var)
  echo $var; // def と出力されます

?>

この出力結果は def になります。
稀によくある、とかいう頻度でみるやつですね。
代入結果が評価されて、IF判定が行われるという感じです。

この書き方はあんま良くないのかな、というのが今回の反省です。
(もしかしたら有名なアンチパターンなのかもしれませんが、調べてません。すません、、、)
というのも、複数条件のように見せようとした時、ちょいと誤解が生じるのです。

$var = 'abc';
$_var = 'def';

if ($var = $_var && $_var === 'def')
  echo $var;

これはどうなるでしょうか。
ぱっと見、
「$var = $var はTRUEだった。そして$var === 'def' もTRUEだ。&&で繋がっているのだから結果はTRUE。よって出力は $var = $_var をしたんだから、def になるだろう」
という風にも読めます。
が、結果は 1 と出力されます。
var_dumpすれば bool(true) のように見えると思います。
わかってる人からすれば当たり前の話ですが、これに30分程立ち止まった僕でございます。

$var = $_var && $_var === 'def'

この式って、単純に&&計算、AND演算なんですね。
$varの評価 ∩ $var === 'def'の評価
=> TRUE ∩ TRUE
=> TRUE
これが右辺の結果で、それが $var に代入される、
という式になっているんですね。
間抜けですね。
本当はこう書かなくてはなりません。

$var = 'abc';
$_var = 'def';

if ( ($var = $_var) && $_var === 'def' )
  echo $var;

こうすれば無事に $var は def と代入されているので、
出力も def になります。
手癖で書いていると本当にオマヌケなミスをするものですね。
皆様に於かれまして大丈夫かと思いますが、 内心ドキッとした方は、努々お気をつけ下さい。。。

そいでは、寝まする。。。

Fluentdに巨大chunkがあって困っている - 日報 #119

非常に歯がゆい状況です。
現在複数のサーバからfluentdにてアクセスログのようなものをログサーバへ送信し、
受信したログサーバはS3経由でRedshiftにデータ送信をしておるのですが、
なぜだか分からない理由でログサーバからS3への送信が滞っておりました。

もちろん優秀なFluentdのことなので、
bufferプラグインが正しく働き、各サーバからのログ情報はchunkとして残っております。
ですが、1chunkのサイズが大きかったせいで、どでかいchunkがキュー状態のまま停止してしまったのです。

送りきれないで残っているbufferファイルを特設S3にぶちあげてやろうとも思ったのですが、
さすがにバイナリエンコードされてしまっているので、なんとも出来ない状態です。

とにかく、
http://qiita.com/tatsu-yam/items/bd7006e483f3b3c64309
ここの素晴らしい説明を読んで理解して、
http://togetter.com/li/651190?page=2
ここの議論を参考にしつつ、
https://gist.github.com/sonots/c54882f73e3e747f4b20#bufferedoutput-%E3%81%AE%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E7%8A%B6%E6%85%8B
ここでしっかりとコードリーディングが必要になりそうです。

あぁ!さっさと送りきってしまいたい!!!