Hatena::ブログ(Diary)

個々一番のHTTP通信

 

2014-12-03 Symfony2でのXATransaction このエントリーを含むブックマーク

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

1年に1度くらい、「あー二相コミットしてーなー」って時、PHPエンジニアだとあるかもしれません。正直僕にはありません。

とはいえ、時々「二相コミット」って言葉聞きますが、どういうものかイマイチ分からないので、非常に上辺だけですが、実装をしてみました。

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

通常の二相コミットを使わず複数にデータベースを分けている場合のトランザクション

以下の様な感じになると思います。

$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)

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

最後に言い訳

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

正直まだまだ、課題があります。

これだけマイナスポイント言っておけば、使おうという人はいないと思いますが・・・。

もし、「あー将来的に使うかも」という人がいたら、お声がけいただければ、ゆるゆる実装を進めたいと思います。

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

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

hnwhnw 2014/12/10 23:09 有名な奴を貼っておきますね
https://code.google.com/p/gregors-ramblings-ja/wiki/18_starbucks

escape_artistescape_artist 2014/12/12 12:36 二相コミット ではないですか?
2相コミット - Wikipedia
http://ja.wikipedia.org/wiki/2%E7%9B%B8%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88

cocoiticocoiti 2014/12/12 22:34 あ、本当だ!!直した!

トラックバック - http://d.hatena.ne.jp/cocoiti/20141203

2014-05-20

IISとnginxステータスコードの変更 00:50  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

トラックバック - http://d.hatena.ne.jp/cocoiti/20140520

2013-11-15 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つけて完了ー。

トラックバック - http://d.hatena.ne.jp/cocoiti/20131115

2013-05-10 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かー。

そんだけです!

トラックバック - http://d.hatena.ne.jp/cocoiti/20130510

2013-04-05 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!

トラックバック - http://d.hatena.ne.jp/cocoiti/20130405