2012-08-07 15 views
14

私は、紺碧のテーブルストレージにページビューカウンタを実装しようとしています。たとえば、2人のユーザーが同時にページにアクセスし、現在のPageViews = 100の値が更新操作後にPageViews = 102であることが保証されているとしますか?紺碧テーブルの記憶域での原子操作

+1

私は質問はしていませんが、なぜ100MbのSQLデータベースを月5ドルで追加するだけではないのですか? SQLはロックを扱う。 – Paparazzi

答えて

23

答えは、カウンタの実装方法によって異なります。 :-)

テーブルストレージには「インクリメント」演算子がないため、現在の値(100)を読み込み、新しい値(101)に更新する必要があります。表ストレージはオプティミスティック並行性を採用しているため、.NETストレージ・クライアント・ライブラリーを使用するときに自然に行われる処理を行うと、2つのプロセスが同時にこの処理を試みたときに例外が発生する可能性があります。

  1. プロセスAは、ページビューの値を読み取り、100
  2. プロセスBは、ページビューの値を読み取り、100
  3. プロセスAは、ページビュー手段に条件付きの更新を行う受信する受信このフローであろう"現在100である限り、PageViewsを101に設定します。"これは成功します。
  4. 前提条件(PageViews == 100)がfalseであるため、プロセスBは同じ操作を実行して失敗します。

エラーを受け取ったときに明らかになるのは、プロセスを繰り返すことです。 (現在の値は101になり、102に更新されます。)これにより、常にカウンタが正しい値になります。

その他の可能性があります。真にスケーラブルなカウンタを実装する方法については、Cloud Coverの全エピソードを行いました:http://channel9.msdn.com/Shows/Cloud+Cover/Cloud-Cover-Episode-43-Scalable-Counters-with-Windows-Azure

コリジョンが起こりそうにない場合、そのビデオで説明されていることはおそらく過剰です。つまり、ヒット率が1秒あたり1回の場合、通常の「読み込み、インクリメント、書き込み」パターンは安全で効率的です。一方、毎秒1000回のヒットを受け取った場合、よりスマートなことをしたいと思うでしょう。

EDIT

ちょうど楽観的並行性を理解するためにこれを読む人のために明確にしたかった...条件付き動作「があれば、現在100だと101にページビューを設定し、」本当にではありません前回見たときから変更されていない限り、「PageViewsを101に設定する」のようなものです。 (これは、HTTPリクエストで返されたETagを使用することで達成されます)。

+0

私はAutoRenewLease.DoOnce(私たちの良い友達smarx、http://blog.smarx.com/posts/managing-concurrency-in-windows-azure-with-leases)を使ってお勧めします:) –

+0

私は人が好きです私のコードを使用して:-)、私はそれがここでどのように役立つだろうと私は考えていない? – smarx

+0

おっと、DoOnceは間違っていましたが、名前= PageView.PartitionKey + "_" + PageView.RowKeyを持つBLOBにAutoRenewLeaseを使用します。ブロブをロックすると、そのレコードが取得され、カウントが増加します。こうすることで、各ページビューが占有されていることを確認できます。そして、これらのすべてがTransient Fault Handlingを使用して、問題が発生した場合に、コードがページビューをログに記録できるようになるまで再試行します。 –

8

また、「カウント」部分を再考することもできます。これを2ステップのプロセスに変えてみませんか?

ステップ1 - 録音ページビュー

誰かがページがテーブルにレコードを追加表示するたびに(のページビューを呼びましょう)。

:あなたはこのような何かを持っているでしょう、いくつかのビュー後

  • のPartitionKey =ページ名
  • のrowKey =ランダムなGUID

:あなたはこれらの店舗の一つに追加する情報は、次のようになります

  • マイページaspxの - someGuid
  • MyPage.aspx - someGuid
  • SomePage.aspxは - someGuid
  • MyPage.aspx - someGuid

ステップ2 - 私たちは何をしたいページビューの数え

これらのレコードをすべて取得し、カウントし、どこかのカウンタを増やして、すべてのレコードを削除します。複数の作業者が稼働しているとします。どちらの作業者も、1〜10分の間にランダムにループします。作業者の時間が経過するたびに、リースがまだ行われていない場合は、blobでリースを行います(これは常に同じブロブでなければならず、AutoRenewLeaseを使用できます)。

ロックを取得して最初のワーカーは、先に行くと、カウントを行うことができます。

  1. PageViewRecordingsテーブルからまたはキャッシュ
  2. からのすべてのレコードは、ページ
  3. あたりのすべてのページビューをカウント取得更新がどこか
  4. を数えます
  5. カウント時に考慮されたレコードを削除する

問題はここですこれを冪等のプロセスに変えることは非常に難しいです。カウントと削除の間にインスタンスがクラッシュするとどうなりますか?ページ数が増えますが、アイテムは削除されていないので、次回にアイテムを処理したときに合計数に追加されます。

これは私が以下を提案する理由です。 同じテーブル(PageViews)では、その同じパーティションに総ページビューも記録します。しかし、データは、(これは、総カウント数を保持し、そのパーティション内の単一のレコードになります)少し違うようになります。

  • のPartitionKey =ページ名
  • のrowKey = Guid.Empty(単にランダムなGUIDを使用していませんこのようにして、記録されたページビューと合計カウントを保持するレコードとの違いを知ることができます)。
  • カウント=現在のページビュー数

表ストレージが少ないスキーマであるので、これは完全に可能です。なぜ我々はこれをやっているのですか? 100個のエンティティのmaxmiumを持つ同じテーブル+パーティションに自分自身を限定すると、トランザクションが発生するためです。これで何ができますか?

  1. Takeを使用すると、そのテーブル+パーティションから100レコードが取得されます。
  2. 最初に取得するレコードは「カウンタ」レコードです。どうして?そののrowKeyはGuid.Emptyで、ソートはこれらのレコードをカウント(-1最初のレコードはページビューではありませんので、それだけで私たちのカウンターのプレースホルダです)
  3. カウンタレコード
  4. のCountプロパティを更新し
  5. 辞書式であるため、
  6. 99(またはそれ以下の)他のレコードを削除する
  7. バッチを使用してSaveChangesを削除する。
  8. レコードが1つだけ残るまで(カウンタレコード)繰り返します。

各ブロブにリースがない場合は、作業者が確認するX分ごとに、リースを受けてプロセスを再開します。

この回答は十分に明確ですか、コードを追加する必要はありますか?

+0

今、私はあなたのBLOBリースについて何を言っているのか理解しています。これは合理的ですが、私はまだ(クラウドカバーのエピソードのように)シャーディングのようなものを好みます。 – smarx

+0

私はこの考えが好きです。コードは必要ありませんが、私はそれをはっきりと理解しています。しかし、あなたが意味することは、「**リースしていない場合**は、労働者はリースを取ってプロセスを再開する」ということですか? – States

+0

はい、回答を更新しました。 –

1

私も同じ質問になりました。 AzureのPythonライブラリでは、ロックの代わりにeTagIf-Matchを使って単純なカウンタのインクリメントを開発しています。基本的な考え方は、更新プログラムが特定の条件で正常に実行されるまでカウンターを増やすことを再試行することです。更新の要求が重い場合、シャーディングが呼び出されます。

https://github.com/flyakite/simple-scalable-datastore/blob/master/datastore/azuretable.py

1

AzureのWebサイトを使用している場合は、AzureのキューとWebJobsは別のオプションです。 私の1つのシナリオでは、実際にシャーディング手法を採用し、WebJobsに集約を定期的に更新させる予定です。 PartitionKey = UserおよびRowKey = Pageを持つUserPageViewsのAzureテーブルストレージテーブル。同じユーザーIDを持つ2人の同時ユーザーは許可されません。