3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

S3上でSEQUENCE(PHP)

3
Last updated at Posted at 2014-01-21

2014/03/31 追記

安易に書いてましたがこの方法は実用レベルに達していません。
理由はS3では結果整合性が保証されていないためです。
この点はちょっと古い記事ですがDevelopers.IOさんの記事が詳しいです。
http://dev.classmethod.jp/cloud/amazon-s3-eventually-consistent-and-consistent-read/

つまり、シーケンス番号のソースファイルを多数のアクセスで読み込みまくると、
たまーに更新前の番号が取れてしまうケースが発生してしまうという事です。
よってこの方法はゆるーく言えばアクセス数がめちゃめちゃ少ないとか、
たまーに違っちゃうくらい他の処理で検知してリトライすんぜー
って場合のみ使ってください。
他に何か方法はないか!?って方は別記事
(DynamoDBでSEQUENCE)[http://qiita.com/supertaihei02/items/a3cf7b374ea5699f32e3]
をご覧ください。

目的

S3上のあるファイルでDBのSEQUENCE的に一意のnumberを管理したいんだ!
会員情報とかS3で管理してランニングコストを下げたいんだー!

やってみた

排他ロックかけないといけないよねーという事で、以下な感じでやってみた
1.registerStreamWrapperする
2.読み書きモード(r+)でfopenする
3.排他モード(LOCK_EX)でflockする
4.現在の値をfreadしてインクリメントしてftruncateしてfwrite
5.lock解除してfclose

じぇじぇじぇ

Warning: The Amazon S3 stream wrapper does not allow simultaneous reading and writing. >Mode not supported: r+. Use one 'r', 'w', 'a', or 'x'. in phar:///var/www/html/hihin/htdocs/flmodule/core/aws/aws.phar/Aws/S3/StreamWrapper.php on line 701

なん・・・だと・・・?
リーディングアンドライティングハキョカサレテマセンノデスカー!
マニュアル

You can upload larger files by streaming data using fopen() and a "w", "x", or "a" stream >access mode. The Amazon S3 stream wrapper does not support simultaneous read and write >streams (e.g. "r+", "w+", etc). This is because the HTTP protocol does not allow >simultaneous reading and writing.

そもそもflockできるのか試してみると

Warning: flock(): Aws\S3\StreamWrapper::stream_lock is not implemented!

Oh my god...
というわけで・・・

実装開始

その昔Perl界隈でよく使われていたrename方式というものを応用します。
手順は以下の通り
1.対象ファイルに現在時間情報をつけてリネーム
1-1.失敗した場合(ロック中)wait&timeout処理
2.読み込む
3.書き込む
4.元の名前にリネーム


require(dirname(__FILE__).'/aws.phar'); // load aws.phar
use Aws\Common\Aws;
use Aws\Common\Enum\Region;
use Aws\S3\Exception\S3Exception;

$accesskey = 'Your AWS Access Key'; // TODO
$secret = 'Your AWS Access Secret'; // TODO
$bucket = 'Your Bucket Name'; // TODO
$key = '/path/to/filedir/filename.txt'; // TODO
$unlocktime = 10; // TODO(sec)
$waittime = 1; // TODO(sec)
$retry = 5; // TODO

$sdir = 's3://'.$bucket.$dir;
$skey = $sdir.$lockfile;

$aws = AWS::factory(array(
    'key' => $accesskey,
    'secret' => $secret,
    'certificate_authority' => false,
    'region' => Region::AP_NORTHEAST_1
));
$client = $aws->get('s3');
$client->registerStreamWrapper();

$skey = 's3://'.$bucket.$key;
$sdir = dirname($skey);
$lockfile = basename($key);
$current = '';
$fail = false;
// lock
for($i=0;$i<$retry;$i++) {
    echo "lock try ".($i + 1)."<br/>";
    try {
        if (rename($skey, $current = $skey.time())) {
            echo "Get lock<br/>";
            $fail = true;
            break;
        } else {
            echo "Rename error<br/>";
        }
    } catch(S3Exception $e) {
        echo "Locked check start<br/>";
    }
    sleep($waittime);
}

if (!$fail && $handle = opendir($sdir)) {
    // ロックファイルを検索
    while (($file = readdir($handle)) !== false) {
        // ロックファイル + 時間 のファイルが存在した場合
        if (preg_match('/^'.$lockfile.'([0-9]+)$', $file)) {
            // ロックファイルの時間を取得
            $time = preg_replace('/^'.$lockfile.'/', '', $file);
            // 期限切れならロック解除
            if (time() - $time > $unlocktime) {
                echo "Destruction lock<br/>";
                rename($sdir.$file, $current = $skey.time());
            } else {
                die("Process busy<br/>");
            }
            break;
        }
    }
    closedir($handle);
}
// read.
if ($fp = fopen($current, 'r')) {
    $seq = intval(fgets($fp), 0);
    echo "Now sequence:".$seq."<br/>";
    fclose($fp);
    // write.
    if ($fp = fopen($current, 'w')) {
        $next = $seq+1;
        fputs($fp, $next);
        echo "Next sequence:".$next."<br/>";
        fclose($fp);
    }
}

// unlock
try {
    if(rename($current, $skey)) {
        echo "Unlock<br/>";
    } else {
        echo "Unlock failed<br/>";
    }
} catch(S3Exception $e) {
    echo "Unlock failed<br/>";
}

これで排他ロックできましたとさ。
時間をファイル名に付加する部分はS3のmetadataを使ってスマートにできないかと調査してみたんだけど、
ファイルをアップロードし直す必要がある(BodyをNULLにしてのputはエラー)事がわかったのでやむなくこんな感じになりました。

githubにS3用の独自ラッパーを置いてます。
aws_s3wrapper.php
これを使えば以下の様に簡単にロックが取れるようにしました。


$s3 = S3::getInstance();
$lockfile = 's3://bucketname/path/to/filedir/filename.txt';
// lock
$lockedfile = $s3->lock($lockfile);
// unlock
$s3->unlock($lockfile, $lockedfile)

以上

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?