Symfony2でのXATransaction

この記事は、PHP Advent Calendar 2014 - Qiitaの記事です。

1年に1度くらい、「あー二相コミットしてーなー」って時、PHPエンジニアだとあるかもしれません。正直僕にはありません。
とはいえ、時々「二相コミット」って言葉聞きますが、どういうものかイマイチ分からないので、非常に上辺だけですが、実装をしてみました。

そもそも二相コミットとは?

  • Two Phase commitでぐぐるといろいろ出てきます
  • すごく単純にいうと複数にデータベースを分割している場合に通常のトランザクションでは保証できない部分を保証できる仕組みです。

通常の二相コミットを使わず複数にデータベースを分けている場合のトランザクション
以下の様な感じになると思います。

$first = $doctrine->getConnection('first');
$second = $doctrine->getConnection('second');
$first->beginTransaction();
$second->beginTransaction();
try {

    // なんらかのクエリ

    $first->commit(); // (1)
    $second->commit(); //(2)
} catch (\Exception $e) {
    $first->rollback();
    $second->rollback();
}

この場合、(2)の時点でなんらかの例外が発生した場合には(1)のcommitが有効になるため、'first'のロールバックは有効になりません。

二相コミットの場合は、(1)と(2)のコミット前にが「ちゃんとコミットできるよね?」と確認してから、実際のコミットを行います。

この二相コミットはMySQLでは、XA Transactionという名前で実装されています。
http://dev.mysql.com/doc/refman/5.6/en/xa.html

XAトランザクションのクエリの流れをざっくりと書きますと、以下のようになります。(例は1つのDBでのトランザクションです)

XA START xid

//ここでなんらかのSQLを発行

XA END xid
XA PREPARE xid -- ここで COMMITできるかの確認
XA COMMIT xid -- 実際のCOMMIT

実装

namespaceは適当なんですがこういう感じの実装をしてみました。

TwoPhaseConnection.php

https://gist.github.com/d82b933f88c17aed28d8

DoctrineのConnectionクラスをXA対応にしたものです。
Connetionクラス自体は結構拡張ポイントがあって
``Doctrine\DBAL\Connections\MasterSlaveConnection``あたりの実装を見ると結構参考になります。
app/config/config.ymlのDBの項目に以下の様な指定をすることにより利用可能になります。

    wrapper_class: XaTestBundle\Doctrine\Connections\TwoPhaseConnection
TransactionManager.php

https://gist.github.com/45e336db353cd0b77fe8
複数のトランザクションをまとめて管理するためのクラスです。
こっちは、複数のトランザクションをまとめて処理するためのクラスです。

実際の利用イメージは以下の様になります。

    $first = $doctrine->getConnection('first');
    $second = $doctrine->getConnection('second');
    $transactionSuccess = new TransactionManager([
        $first,
        $second,
    ]);

    $transactionSuccess->beginTransaction();
    try {

        // なんらかのクエリ

        $transactionSuccess->commit();
    } catch (\Exception $e) {
        $transactionSuccess->rollback();
    }

$transactionSuccess->commit();の中の実装をもう少し見て行きたいと思います。

public function commit()
{
    $successConnections = [];
    try {
        foreach ($this->connections as $connection) {
            $connection->prepareTwopahce();
            $successConnections[] = $connection;
        }
    } catch (Exception $e) {
        $this->rollback();
    }

    foreach ($this->connections as $connection) {
        $connection->commit();
    }
}

$connection->prepareTwopahce();をすることで、すべてのconnectionがcommitできることを確認していることがわかります。

テスト用に簡単な、コマンドを実装してみました。
https://gist.github.com/ede09c3ddb612daa0c38

実行してみるとSQLのログは、以下のようになります。

doctrine.DEBUG: "START TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA START '433713564548829db945bd'  
doctrine.DEBUG: "START TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA START '810041109548829db9461a'  
doctrine.DEBUG: INSERT INTO transaction1 (data) VALUES (?)
["433713564548829db945bd"] 
doctrine.DEBUG: INSERT INTO transaction2 (data) VALUES (?)
["810041109548829db9461a"] 
doctrine.DEBUG: "END TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA END '433713564548829db945bd'  
doctrine.DEBUG: "PREPARE TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA PREPARE '433713564548829db945bd'  
doctrine.DEBUG: "END TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA END '810041109548829db9461a'  
doctrine.DEBUG: "PREPARE TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA PREPARE '810041109548829db9461a'  
doctrine.DEBUG: "COMMIT TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA COMMIT '433713564548829db945bd'  
doctrine.DEBUG: "COMMIT TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA COMMIT '810041109548829db9461a'  

doctrine.DEBUG: "START TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA START '1804794638548829db9a041'  
doctrine.DEBUG: "START TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA START '1858734663548829db9a0a2'  
doctrine.DEBUG: INSERT INTO transaction1 (data) VALUES (?)
["1804794638548829db9a041"] 
doctrine.DEBUG: INSERT INTO transaction2 (data) VALUES (?)
["1858734663548829db9a0a2"] 
doctrine.DEBUG: "END TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA END '1804794638548829db9a041'  
doctrine.DEBUG: XA ROLLBACK '1804794638548829db9a041'  
doctrine.DEBUG: "END TWO PHACE TRANSACTION"  
doctrine.DEBUG: XA END '1858734663548829db9a0a2'  
doctrine.DEBUG: XA ROLLBACK '1858734663548829db9a0a2'  

それぞれのDBに2回ずつ系4回INSERT文を発行していますが、2回目のクエリはROLLBACKしているため、DBには1件ずつのデータしか入ってないことが確認できます。

mysql> select * from xatest_first.transaction1;
+------------------------+
| data                   |
+------------------------+
| 433713564548829db945bd |
+------------------------+
1 row in set (0.00 sec)

select * from xatest_second.transaction2;
+------------------------+
| data                   |
+------------------------+
| 810041109548829db9461a |
+------------------------+
1 row in set (0.00 sec)

なんとなく動いてますね!!

最後に言い訳

だいたい実装概要は「あ、これで動きそう」というところまでは来たのですが、
正直まだまだ、課題があります。

  • 実案件に使う余地がないので、テストしてない
  • ライブラリに切り出すべき
  • トランザクションリカバー的なことをしていない see: http://www.slideshare.net/takezoe/ss-35337478
  • MySQLべったりなコード
  • 二相コミットといえどPREPAREのあとのCOMMITで1つ目をコミットして2つめのトランザクションをコミットしようとする間にサーバ障害などが発生すると、結局トランザクション的不整合を残すことになる
  • パフォーマンス的に良くないという記述を見かける
  • 実は↑考えると無理に二相コミットするメリットってあんまりないんじゃないかと思えてくる

これだけマイナスポイント言っておけば、使おうという人はいないと思いますが・・・。
もし、「あー将来的に使うかも」という人がいたら、お声がけいただければ、ゆるゆる実装を進めたいと思います。

とはいえ、基本概念などは他のORMでも使えるものだと思いますので、PHPで二相コミットをやってみる一助になれば幸いでございます。

最後に、ブログの更新がおくれて申し訳ありませんでした。

IISとnginxステータスコードの変更

昔の携帯のオープンソーシャルとかで、プラットフォームのプロクシが間に入る場合などに、アプリケーションサーバが出力した400番台とか、500番台のステータスコードとかをエラー画面とともに200のステータスコード返したいというニーズがあったりする。

で、そういう場合どうするかというとnginxとかでは。設定ファイルに

 error_page  404  =200 /200.html;


IISとかではweb.configとかに、こんな感じに書くらしい

<httpErrors>
    <remove statusCode="404" subStatusCode="-1" />
    <error statusCode="404" prefixLanguageFilePath="" path="200.html" responseMode="File" />
</httpErrors>

なんでそんなことしたかっていうとチューニングイベントで404のアクセスがたくさんきてうざかったので。200返したのでした。(今は外してます)。


参考:
http://wiki.nginx.org/HttpCoreModule#error_page
http://serverfault.com/questions/401415/iis-7-returns-http-200-on-custom-404-error-page

Amazon Linux で s3cmd 使う時の注意点

そういうこともあるんだなーってメモ。

現象

s3cmdでcronに定期的にバックアップを取っていたが、ある日突然バックアップできなくなった

原因

バックアップを対象のファイルが1ファイルで5Gを超えていたため。

s3は5G以上のファイルは、Multipart Uploadでアップロードする必要がある

対応

s3cmdに、--multipart-chunk-size-mbオプションをつけて実行することによって解消


・・・はしなかった。

僕がいじってるAmazon Linux AMI 2013.03のs3cmdのRPMはs3cmd-1.0.1だったりする。
s3cmdのページを参考にすると、--multipart-chunk-size-mbが利用できるようになるのは、1.1.0-beta2からぽい。


というわけで・・・ソースから入れる・・・・なんてことはせずに、RPMを作る


元になるファイルは、
Fedora 20のs3cmd-1.5.0-0.alpha3のソースパッケージから1.5.0のパッケージをもってきてRPMをつくる。
ちなみにRPMの作り方は


http://openlab.dino.co.jp/2007/11/16/13162254.html
http://wiki.poyo.jp/read/Writing/misc/linux/rpm_de_php/06.Make%20Simple%20PHP#content_1_0


ここらへんが参考になる。

で、パッケージつくてインストールしたら、無事s3cmdに、--multipart-chunk-size-mbつけて完了ー。

PHPでEnum型

AWSとかAmazonとかSOAPとかRESTなサービスのコードを書いているとPHPEnum型とかをClassで表現したくなるわけです。
たとえばこんなイメージ

<?php
class Hoge extends Enum
{
    const MOTE = 'motemote';
}

Hoge::keys(); //array('MOTE');

で実装まよってたんですが、AWS SDKの実装が参考になったのでメモ

https://github.com/aws/aws-sdk-php/blob/master/src/Aws/Common/Enum.php

<?php
/**
 * Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 * http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

namespace Aws\Common;

/**
 * Represents an enumerable set of values
 */
abstract class Enum
{
    /**
     * @var array A cache of all enum values to increase performance
     */
    protected static $cache = array();

    /**
     * Returns the names (or keys) of all of constants in the enum
     *
     * @return array
     */
    public static function keys()
    {
        return array_keys(static::values());
    }

    /**
     * Return the names and values of all the constants in the enum
     *
     * @return array
     */
    public static function values()
    {
        $class = get_called_class();

        if (!isset(self::$cache[$class])) {
            $reflected = new \ReflectionClass($class);
            self::$cache[$class] = $reflected->getConstants();
        }

        return self::$cache[$class];
    }
}

なるほどReflectionかー。
そんだけです!

Macでphp.5.5.0beta2を気軽に試す

MacOSX で php5.5.0beta2 をいれるには、現時点で phpbrew が一番楽だと思うのでやり方をめもっておく

とりあえず Homebrew 環境で気軽にいれるため Formula ファイルを書いてたのでそれをインストール

~# curl https://gist.github.com/cocoiti/5161690/raw/ca184634e22087c392601ff9cd76980cd8d502ce/phpbrew.rb > /usr/local/Library/Formula/phpbrew.rb
~# brew install phpbrew

環境を設定。
ちゃんと使うときは、~/.zshrcあたりに追加。

~# source $HOME/.phpbrew/bashrc

対応しているバージョンを確認

~# phpbrew known --dsp
(中略)
	php-5.3.1
	php-5.3.0
Available versions from PHP Release Manager: David Soria Parra
	php-5.5.0alpha1
	php-5.5.0alpha2
	php-5.5.0alpha3
	php-5.5.0alpha4
	php-5.5.0alpha5
	php-5.5.0alpha6
	php-5.5.0beta1
	php-5.5.0beta2

インストールの実行

~# phpbrew install php-5.5.0beta2 +default

ちなみに複数種類のPHPを同時実行するのに楽な php-fpm を使いたいときはこう。

~# phpbrew install php-5.5.0beta2 +default +fpm

コンパイルがおわったら設定を切り替え

~# phpbrew use php-5.5.0beta2
~# php -v
PHP 5.5.0beta2 (cli) (built: Apr  5 2013 01:51:41) 
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.5.0-dev, Copyright (c) 1998-2013 Zend Technologies

詳しくは開発者サイトに書いてある https://github.com/c9s/phpbrew

Enjoy!

ウノウの昔話

この記事は、Unoh Advent Calendarの参加記事です。
夫(@cocoitiban)のブログを拝借して、エントリーします @no_ugat です。


今からかれこれ10年前、有限会社ウノウ高田馬場にありました。
私はその頃既にウノウにお世話になっておりましたので、
その頃の話〜(中略)〜Zynga Japanへの移行期の体験について書きます。
記憶が曖昧になってきているので、事実と異なる部分があったりしても雰囲気が伝われば。


当時ゆめみさんのオフィスに間借りしており、
社内はほとんど見渡す限り男性で(それはウノウ全時代ほとんど同じかもしれませんが)、
女性は2、3名というITベンチャーらしい雰囲気で、
寝袋完備のため、朝行くと床に転がっている人がいたりいなかったり。


当時はウノウメンバーとしての人数がほとんど居ない状態で、
デザイナーとして入った人がPHPを書かされるという職場でした。
(しかもコーダー寄りというより紙とかグラフィック・エディトリアルデザインが得意な方)


そんなわけで、当時はエンジニアの会社・技術者が集まる会社といった雰囲気はなかったように思います。
他にも間借りしている人達がいたので、エンジニアさんは何人かおりましたが。
(だから男臭が、、)


また、一度社長にMTGに連れて行かれると「次回から対応お願いね(メイン担当者として)」
と急に仕事を全面的に振られたりしていました。
昔から思い立ったら即、というスピード感は戸惑う程にあったかもしれません。


その後ゆめみさんオフィス移転とともにウノウも馬場から引っ越すのですが、
私はそのデザイナーさん(夫の前のkeita@unoh)とその案件をそのまま引き取って独立してしまったので、
ここで一旦ウノウとお別れとなりました。


2010年1月に再び戻ってくることになり、再び同じメールアドレスを使う日が来ようとは夢にも思わず。
(だってちょっと副社長と会ってみてって言われたらそれが面接で、30分後には入社日が設定されちゃったんだもん…)


それから私は総務・人事・採用・経理等事務職として再び入社し、
Zynga Japan になるための移行業務が大変でしたが、
(大量の英語書類の次はオフィスレイアウト設計・移転!そして広報)
現在は写真共有サービス「フォト蔵」とともにデジタルガレージに移り、制作やってます。


PCサイト、リニューアルしたよ!写真おっきくなったよ!
iOSアプリもつくったよ!


ということで、容量枚数無制限、無料なのでフォト蔵をぜひ使っていただけると嬉しいです♪
http://photozou.jp/photo/show/2/17002431


最後に、ウノウ〜Zynga Japanでご一緒させていただいた皆様には大変お世話になりました。
特に社長であり17年来の友人である進太郎さんにはいろいろな経験を積ませていただき感謝しています。ありがとね!進ちゃん

入社と退社後の話

この記事は、Unoh Advent Calendarの参加記事です。

ウノウという会社についてみんなでなんか書いてみようという企画。
ウノウ歴は長いので、エピソードは一晩くらいは話続けられるくらいは持っています。

そんなものは酒飲みながら語ればいい話なのかもしれないけど、まぁ何かをきっかけにみんなでわーってやるのって、ウノウっぽいよねと思ったので参加することにしました。

入社の時

僕が入社したとき、しばらくメールが届かないことがありました。
僕の本名は新井啓太なんですが、原因は、keitaってメールアドレスで使用していたアカウント名はすでにほかの人が使用していたため。
もう使われないメールアドレスだったので、僕に変更してもらいましたが、

「技術に強い会社に入ったのに」

と一瞬不安になったのを覚えてます。

退職後

退職後しばらくして、僕は結婚することになりました。
その結婚式には、もう一人啓太さん、という方がいました。

その啓太さんこそ、僕がしばらくメールが届かなくなった原因の一代目、啓太さんでした。
なんで結婚式に参加いただいていたかというと、妻の元同僚だからです。

僕の妻は、ウノウ買収前に入社したウノウ社員で僕の同僚だった人です、さらにその前、ウノウの立ち上げ時にもスタッフとして参加していた僕の先輩でもありました。(時期はかぶっていませんので、先輩として接したことはありませんけど)

というわけで、僕がウノウで得たものは本当に数えきれないほどありますが、嫁を得たのが一番大きかったなって自慢話でした。

技術的なこと一切なかった!!が、まぁいいや!!
この記事が、ご参考になれば幸いです。