2

RavenDB に、クライアント アプリケーションが比較的単純な CRUD を実行するドキュメント コレクションがあります。コレクションをクエリするための単純なマップのみのインデックスが 1 つあります。

ここで、個々のドキュメントがクエリで取得された回数、つまりアプリケーションによって「提供された」回数を表示する機能をアプリケーションに追加する必要があります。アプリケーションを介して個々のドキュメントを表示すると、そのドキュメントが提供された回数を確認できます。また、カウントをゼロに戻してカウントを増やし続けるための「リセット」ボタンも要求されます。

この要件は、アプリケーション インターフェイスの複雑さを軽減するために、意図的に粗く保たれています。日付範囲レポートのオプションは議論されており、不要であると考えられています。

いくつかのコンテキスト: 一部のドキュメントの増分配信数は、1 日あたり 10,000 を超える可能性があります。ドキュメント配信数の「リセット」は、毎週発生することが予想されます。CQRS のように、クエリ操作の外部で、データベースへのインクリメント書き込みを非同期的に実行できるはずです。

3 つのアプローチを考えることができますが、最適なオプションはどれでしょうか?

  1. ドキュメント オブジェクトに整数の Count プロパティを追加します。これは、オブジェクトをインクリメントするための呼び出しがその後行われたときに、各オブジェクトでインクリメントされます。これにより、ドキュメントのサイズが小さく保たれます。

  2. リスト プロパティを現在の日付のドキュメント オブジェクトに追加し、オプション 1 と同様に各オブジェクトに追加します。これは、後で日付ベースのレポートが要求されたが、ドキュメントが危険なほど大きくなる可能性がある場合に役立ちますか?

  3. 提供されたドキュメント ID と提供された日時で構成される「カウンター」ドキュメントの別のコレクションを追加します。これにより、マップ/リデュース インデックスのメリットが得られると思いますか?

そして、いくつかの初期のフォローアップの質問...

  • RavenDB でこれを達成するアスペクト指向の方法はありますか?
  • 挿入 (オプション 3) またはその逆よりも更新 (オプション 1 + 2) を使用することで、パフォーマンス上の利点はありますか?

答えてくれてありがとう、そしてこれをすべて読んでくれてありがとう!

4

2 に答える 2

3

RavenDB でこれを実現するアスペクト指向の方法は、読み取りトリガーを使用することです。取得回数をドキュメント メタデータに格納できます。個々の取得に関する情報は必要ないため、これが最もパフォーマンスの高いソリューションになります。

アップデート

maxbeaudoinで指摘されているように、メタデータはデータに関するデータであるため、読み取りカウンターに適した場所です。また、ドキュメントと一緒にメタデータが保存されるため、パフォーマンスも良好です。ここでは、RavenDB でメタデータを操作する方法について説明します

更新 2

カウントのみを保存する場合は、メタデータがパフォーマンスと実用性の点で最適なオプションです。各ビュー イベントを保存する必要があり、ドキュメントごとに何千もの潜在的なビュー イベントが予想される場合は、同じドキュメントまたはドキュメント メタデータではなく、別のドキュメントにビュー イベントを保存します。メタデータは単なるキーと値のペアのコレクションであり、大規模なコレクション向けではありません。同じドキュメントに保存しない理由は、ドキュメントモデルを変更してビューイベントを含める必要があるためです。これにより、モデルが汚染されます。また、指摘したように、10K ビューイベントを含むドキュメントを取得すると IO になるためです。ヒービングし、パフォーマンスの問題を引き起こします。プロジェクションを使用して特定のドキュメント フィールドのみを取得できますが、返されたドキュメントの変更は追跡されません。問題を考えると、私は'

于 2012-10-16T23:45:03.397 に答える
3

おそらく、私はここでベースから外れているかもしれませんが、実際にこのドキュメントをデータベースから 1 日に 10,000 回ロードする必要があるのはなぜですか? おそらく、出力キャッシュを使用したほうがよいでしょう。

Raven キャッシングもあなたに有利に働くでしょう。確かに、クライアントでキャッシュ ヒットが発生した場合にサーバーで読み取りトリガーが起動するかどうかはわかりません。トリガーパスを下る場合は、まずこれを確認します。

おそらく、クライアント側でカウンターを実行し続ける方がよいでしょう。キャッシュ ヒットが発生し、カラスに触れていない場合でも、カウンターをインクリメントできます。次に、定期的にカウンターをサーバーにフラッシュして、ドキュメント自体または別の統計ドキュメントのカウント プロパティを更新できます。

これは、パフォーマンスに非常に役立ちます。5 分間で 50 回の視聴があったとします。毎回 1 ずつインクリメントする理由は、5 分ごとに 50 ずつインクリメントするだけです。正確には 50 ではありませんが、その間にフロント エンドで測定した値は何でも構いません。これは複数のサーバーでもスケーリングでき、既存のカウントに新しいカウントを追加するだけであれば、raven のパッチ API を介して変更を適用できます。

アップデート

参考になりそうな例をまとめました。これには、定期的に発生するタイマーを除いて、クライアント側で行う必要があるすべてが含まれています。うまくいけば、これはあなたの賞金に値する.

public class Counter
{
    // Uses the Multithreaded Singleton pattern
    // See http://msdn.microsoft.com/en-us/library/ff650316.aspx

    private Counter() { }

    private static volatile Counter _instance;
    private static readonly object SyncRoot = new object();

    public static Counter Instance
    {
        get
        {
            if (_instance != null)
                return _instance;

            lock (SyncRoot)
            {
                if (_instance == null)
                    _instance = new Counter();
            }
            return _instance;
        }
    }

    private readonly ConcurrentDictionary<string, long> _readCounts =
                                         new ConcurrentDictionary<string, long>();

    public void Increment(string documentId)
    {
        _readCounts.AddOrUpdate(documentId, k => 1, (k, v) => v + 1);
    }

    public long ReadAndReset(string documentId)
    {
        lock (SyncRoot)
        {
            long count;
            return _readCounts.TryRemove(documentId, out count) ? count : 0;
        }
    }

    public IDictionary<string, long> ReadAndResetAll()
    {
        var docs = _readCounts.Keys.ToList();
        return docs.ToDictionary(x => x, ReadAndReset);
    }
}

public class Story
{
    public string Id { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
    public DateTime Published { get; set; }
    public long ReadCount { get; set; }
    public string Content { get; set; }
}

[TestClass]
public class Tests
{
    [TestMethod]
    public void TestCounter()
    {
        var documentStore = new DocumentStore { Url = "http://localhost:8080" };
        documentStore.Initialize();

        documentStore.DatabaseCommands.EnsureDatabaseExists("Test");

        using (var session = documentStore.OpenSession("Test"))
        {
            var story = new Story
                {
                    Id = "stories/1",
                    Title = "A long walk home",
                    Author = "Miss de Bus",
                    Published = new DateTime(2012, 1, 1),
                    Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
                };
            session.Store(story);
            session.SaveChanges();
        }

        // This simulates many clients reading the document in separate sessions simultaneously
        Parallel.For(0, 1000, i =>
            {
                using (var session = documentStore.OpenSession("Test"))
                {
                    var story = session.Load<Story>("stories/1");
                    Counter.Instance.Increment(story.Id);
                }
            });

        // This is what you will need to do periodically on a timer event
        var counts = Counter.Instance.ReadAndResetAll();
        var db = documentStore.DatabaseCommands.ForDatabase("Test");
        foreach (var count in counts)
            db.Patch(count.Key, new[]
                {
                    new PatchRequest
                        {
                            Type = PatchCommandType.Inc,
                            Name = "ReadCount",
                            Value = count.Value
                        }
                });

        using (var session = documentStore.OpenSession("Test"))
        {
            var story = session.Load<Story>("stories/1");
            Assert.AreEqual(1000, story.ReadCount);
        }
    }
}
于 2012-10-19T00:42:58.103 に答える