2012-03-01 7 views
0

私はアプリを開発中で、ページの見方を追跡する必要があります。ほとんどの人がそれをやっているのと似ています。これは、特定のページの人気度を判断するために使用される値です。SQLサーバーへの書き込みを遅らせる

私はDBに新しいビューを記録する必要があるたびに書くことは、パフォーマンスに影響を与えることを懸念しています。私はこの境界線の事前最適化を知っていますが、私は前に問題を経験しています。とにかく、その値はリアルタイムである必要はありません。それが10分ほど遅れると問題ありません。私はデータをキャッシュすることを考えていましたが、X分ごとに大きな書き込みをするのは助けになるはずです。

私はWindows Azure上で実行しているので、Appfabricキャッシュを利用できます。私の元の計画は、何らかの複合キー(PostID:UserID)を作成し、そのキーに「ページビュー」をタグ付けすることでした。 Appfabricでは、すべてのキーをタグで取得できます。したがって、私はそれらをビルドアップさせることができ、多くの小さな書き込みの代わりにテーブルに1つの一括挿入を行います。テーブルはこのように見えますが、変更が可能です。

int PageID | guid userID | DateTime ViewTimeStamp 

ウェブサイトは依然としてデータベースから価値を得ることができますが、書き込みは遅れるだけでしょうか?

私は、Windows AzureのAppFabricのキャッシュは、タグベースの検索をサポートしていないことjust read、それはかなり私の考えを否定します。

私の質問は、これをどのように達成するかです。私はAzureを初めて使うので、私の選択肢が何であるか分かりません。タグベースの検索なしでキャッシュを使用する方法はありますか?これらのSQLへの書き込みを遅らせる方法に関するアドバイスを探しています。

+0

私はページヒットを追跡しようとしていません。これはログに記録されません。ビューを定義するビジネスルールがあります。ユーザーがページにいなければならない時間、同じユーザーが最後に訪問してからの経過時間など... – Joe

答えて

2
あなたは http://www.apathybutton.comを見て(と、それはにリンク雲量エピソード)物事をカウントする、高度にスケーラブルな方法について語って、撮りたいかもしれません

。 (それはあなたのニーズに行き過ぎかもしれませんが、うまくいけば、それはあなたにいくつかのオプションを提供します。)

+0

リンクが死んでいる... –

0

あなたはアズールで「診断」機能がどのように機能するかを見てしたい場合があります。あなたが何をやっているのかを診断するのではなく、それが同じような問題に取り組んでいて、インスピレーションを与えるかもしれないからです。私はちょうどデータ監査機能を実装しようとしています。テーブルストレージにログを記録したいので、アップデートを一緒にやりとりしたいので、診断から多くのインスピレーションを得ました。

さて、アズールでの診断が機能する方法は、それぞれの役割が少し背景「転送」のスレッドを開始することです。したがって、トレースを書くたびにローカルメモリのリストに格納され、バックグラウンドスレッドは(デフォルトで)すべての要求を束ね、毎分テーブルストレージに転送します。あなたのシナリオでは

、私は、各ロールのインスタンスは、データベースごとに分かそこらを更新するために、バックグラウンドスレッドを使用し、その後、ヒットの数を追跡してみましょうでしょう。 私はたぶん、静的なConcurrentDictionary(またはシングルトンにぶら下がっているもの)のようなものを、各Webロールで使用して、それぞれのヒットでページ識別子のカウンタを増やします。複数の要求がリスト内の同じカウンタを更新できるようにするには、スレッド処理コードが必要です。あるいは、各「ヒット」が共有スレッドセーフリストに新しいレコードを追加できるようにするだけです。
その後、バックグラウンドスレッドを1回実行すると、最後の時間からページあたりのヒット数でデータベースをインクリメントし、ローカルカウンタを0にリセットするか、その方法を使用する場合は共有リストを空にします(再度、マルチスレッドとロック)。

重要なことは、データベースの更新がアトミックであることを確認することです。データベースから読み取り電流カウントを行い、増分してから書き込むと、同時に2つの異なるWebロールインスタンスが存在し、1つの更新が失われる可能性があります。

編集: ここでは、どのようにさんがになるかの簡単なサンプルです。

using System.Collections.Concurrent; 
using System.Data.SqlClient; 
using System.Threading; 
using System; 
using System.Collections.Generic; 
using System.Linq; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     // You would put this in your Application_start for the web role 
     Thread hitTransfer = new Thread(() => HitCounter.Run(new TimeSpan(0, 0, 1))); // You'd probably want the transfer to happen once a minute rather than once a second 
     hitTransfer.Start(); 


     //Testing code - this just simulates various web threads being hit and adding hits to the counter 
     RunTestWorkerThreads(5); 
     Thread.Sleep(5000); 

     // You would put the following line in your Application shutdown 
     HitCounter.StopRunning(); // You could do some cleverer stuff with aborting threads, joining the thread etc but you probably won't need to 
     Console.WriteLine("Finished..."); 
     Console.ReadKey(); 

    } 

    private static void RunTestWorkerThreads(int workerCount) 
    { 
     Thread[] workerThreads = new Thread[workerCount]; 
     for (int i = 0; i < workerCount; i++) 
     { 
      workerThreads[i] = new Thread(
       (tagname) => 
        { 
         Random rnd = new Random(); 
         for (int j = 0; j < 300; j++) 
         { 
          HitCounter.LogHit(tagname.ToString()); 
          Thread.Sleep(rnd.Next(0, 5)); 
         } 
        }); 
      workerThreads[i].Start("TAG" + i); 
     } 

     foreach (var t in workerThreads) 
     { 
      t.Join(); 
     } 
     Console.WriteLine("All threads finished..."); 
    } 
} 

public static class HitCounter 
{ 
    private static System.Collections.Concurrent.ConcurrentQueue<string> hits; 
    private static object transferlock = new object(); 
    private static volatile bool stopRunning = false; 

    static HitCounter() 
    { 
     hits = new ConcurrentQueue<string>(); 
    } 

    public static void LogHit(string tag) 
    { 
     hits.Enqueue(tag); 
    } 

    public static void Run(TimeSpan transferInterval) 
    { 
     while (!stopRunning) 
     { 
      Transfer(); 
      Thread.Sleep(transferInterval); 
     } 
    } 

    public static void StopRunning() 
    { 
     stopRunning = true; 
     Transfer(); 
    } 

    private static void Transfer() 
    { 
     lock(transferlock) 
     { 
      var tags = GetPendingTags(); 
      var hitCounts = from tag in tags 
          group tag by tag 
          into g 
          select new KeyValuePair<string, int>(g.Key, g.Count()); 
      WriteHits(hitCounts); 
     } 
    } 

    private static void WriteHits(IEnumerable<KeyValuePair<string, int>> hitCounts) 
    { 
     // NOTE: I don't usually use sql commands directly and have not tested the below 
     // The idea is that the update should be atomic so even though you have multiple 
     // web servers all issuing similar update commands, potentially at the same time, 
     // they should all commit. I do urge you to test this part as I cannot promise this code 
     // will work as-is 
     //using (SqlConnection con = new SqlConnection("xyz")) 
     //{ 
     // foreach (var hitCount in hitCounts.OrderBy(h => h.Key)) 
     // { 
     //  var cmd = con.CreateCommand(); 
     //  cmd.CommandText = "update hits set count = count + @count where tag = @tag"; 
     //  cmd.Parameters.AddWithValue("@count", hitCount.Value); 
     //  cmd.Parameters.AddWithValue("@tag", hitCount.Key); 
     //  cmd.ExecuteNonQuery(); 
     // } 
     //} 

     Console.WriteLine("Writing...."); 
     foreach (var hitCount in hitCounts.OrderBy(h => h.Key)) 
     { 

      Console.WriteLine(String.Format("{0}\t{1}", hitCount.Key, hitCount.Value)); 
     } 
    } 

    private static IEnumerable<string> GetPendingTags() 
    { 
     List<string> hitlist = new List<string>(); 
     var currentCount = hits.Count(); 
     for (int i = 0; i < currentCount; i++) 
     { 
      string tag = null; 
      if (hits.TryDequeue(out tag)) 
      { 
       hitlist.Add(tag); 
      } 
     } 
     return hitlist; 
    } 
}  
+0

ちなみに、コードサンプルが「あばらめ」でない理由を知っている人はいますか?何が間違っているのですか? – Frans

1

あなたはページ単位でカウントを合計することによってキューに入れられたアイテムを折りたたむと、1つのSQLバッチ/往復で書く、キューメモリに、タイマードレインにキューを保つことができます。たとえば、TVPを使用すると、1つのsproc呼び出しでキューに入れられた合計を書き込むことができます。

もちろん、ビューカウントはメモリ内に書き込まれているとは限らず、潜在的に書き込まれたページ数はクリティカルなデータであってはならず、クラッシュはまれであるべきです。

関連する問題