C++ 查询不断增长的数据集

C++ 查询不断增长的数据集,c++,C++,我们有一个在应用程序处理数据集时不断增长的数据集。经过长时间的讨论,我们决定现在不需要阻塞或异步API,我们将定期查询数据存储 我们考虑了两个选项来设计用于查询存储的API: 查询方法返回数据的快照和一个标志,指示我们是否可能有更多数据。当我们完成对最后一个返回的快照的迭代后,我们将再次查询以获取其余数据的另一个快照 查询方法在数据上返回一个“活动”迭代器,当该迭代器前进时,它返回以下选项之一:数据可用,没有更多数据,可能有更多数据 我们使用C++,我们借用了.NET样式枚举器API,因为这个问

我们有一个在应用程序处理数据集时不断增长的数据集。经过长时间的讨论,我们决定现在不需要阻塞或异步API,我们将定期查询数据存储

我们考虑了两个选项来设计用于查询存储的API:

  • 查询方法返回数据的快照和一个标志,指示我们是否可能有更多数据。当我们完成对最后一个返回的快照的迭代后,我们将再次查询以获取其余数据的另一个快照
  • 查询方法在数据上返回一个“活动”迭代器,当该迭代器前进时,它返回以下选项之一:数据可用,没有更多数据,可能有更多数据
  • 我们使用C++,我们借用了.NET样式枚举器API,因为这个问题超出了范围。下面是一些代码来演示这两个选项。你喜欢哪种选择

    /* ======== FIRST OPTION ============== */
    
    // similar to the familier .NET enumerator.
    class IFooEnumerator
    {
        // true --> A data element may be accessed using the Current() method
        // false --> End of sequence. Calling Current() is an invalid operation.
        virtual bool MoveNext() = 0;
        virtual Foo Current() const = 0;
        virtual ~IFooEnumerator() {}
    };
    
    enum class Availability
    {
        EndOfData,
        MightHaveMoreData,
    };
    
    class IDataProvider
    {
        // Query params allow specifying the ID of the starting element. Here is the intended usage pattern:
        //  1. Call GetFoo() without specifying a starting point. 
        //  2. Process all elements returned by IFooEnumerator until it ends.
        //  3. Check the availability. 
        //     3.1 MightHaveMoreDataLater --> Invoke GetFoo() again after some time by specifying the last processed element as the starting point
        //                                    and repeat steps (2) and (3)   
        //     3.2 EndOfData --> The data set will not grow any more and we know that we have finished processing.
        virtual std::tuple<std::unique_ptr<IFooEnumerator>, Availability> GetFoo(query-params) = 0;
    };
    
    
    /* ====== SECOND OPTION ====== */
    
    enum class Availability
    {
        HasData,
        MightHaveMoreData,
        EndOfData,
    };
    
    
    class IGrowingFooEnumerator
    {
        // HasData:
        //      We might access the current data element by invoking Current()
        // EndOfData:
        //      The data set has finished growing and no more data elements will arrive later
        // MightHaveMoreData:
        //      The data set will grow and we need to continue calling MoveNext() periodically (preferably after a short delay)
        //      until we get a "HasData" or "EndOfData" result.
        virtual Availability MoveNext() = 0;
        virtual Foo Current() const = 0;
        virtual ~IFooEnumerator() {}
    };
    
    class IDataProvider
    {
        std::unique_ptr<IGrowingFooEnumerator> GetFoo(query-params) = 0;
    };
    
    /*==================================================================*/
    //类似于familier.NET枚举器。
    类IFooEnumerator
    {
    //true-->可以使用Current()方法访问数据元素
    //false-->序列结束。调用Current()是无效的操作。
    虚拟布尔移动下一步()=0;
    虚拟Foo Current()常量=0;
    虚拟~IFooEnumerator(){}
    };
    枚举类可用性
    {
    EndOfData,
    可能有更多的数据,
    };
    类IDataProvider
    {
    //查询参数允许指定起始元素的ID。以下是预期的使用模式:
    //1.调用GetFoo()而不指定起点。
    //2.处理IFooEnumerator返回的所有元素,直到结束。
    //3.检查可用性。
    //3.1可能有MoreDataLater-->在一段时间后通过指定最后处理的元素作为起点再次调用GetFoo()
    //并重复步骤(2)和(3)
    //3.2 EndOfData-->数据集将不再增长,我们知道已完成处理。
    虚拟std::tuple GetFoo(查询参数)=0;
    };
    /*=======第二个选项========*/
    枚举类可用性
    {
    HasData,
    可能有更多的数据,
    EndOfData,
    };
    类IGrowingFooEnumerator
    {
    //HasData:
    //我们可以通过调用current()来访问当前数据元素
    //EndOfData:
    //数据集已完成增长,以后不会有更多的数据元素到达
    //可能有更多数据:
    //数据集将增长,我们需要继续定期调用MoveNext()(最好在短时间延迟后)
    //直到我们得到“HasData”或“EndOfData”结果。
    虚拟可用性MoveNext()=0;
    虚拟Foo Current()常量=0;
    虚拟~IFooEnumerator(){}
    };
    类IDataProvider
    {
    std::unique_ptr GetFoo(查询参数)=0;
    };
    

    更新

    鉴于目前的答案,我有一些澄清。争论主要集中在界面上——它在表示不断增长的数据集的查询时的表现力和直观性,这些数据集在某个时间点将停止增长。由于以下特性,在没有竞争条件的情况下(至少我们认为是这样),两个接口的实现都是可能的:

  • 如果迭代器+标志对表示查询时系统的快照,则可以正确实现第一个选项。获取快照语义不是问题,因为我们使用数据库事务
  • 如果正确实施第一个选项,则可以实施第二个选项。第二个选项的“MoveNext()”将在内部使用类似于第一个选项的内容,并在需要时重新发出查询
  • 数据集可以从“可能有更多数据”更改为“数据结束”,但反之亦然。因此,如果我们因为竞争条件错误地返回“可能有更多数据”,我们只会得到一小部分性能开销,因为我们需要再次查询,下一次我们将收到“数据结束”
  • “一段时间后,通过指定最后处理的元素作为起点,再次调用GetFoo()”


    你打算怎么做?如果它使用的是先前返回的
    IFooEnumerator
    ,那么这两个选项在功能上是等效的。否则,让调用者销毁“枚举器”,然后调用
    GetFoo()
    以继续迭代,这意味着您将失去监视客户端对查询结果的持续兴趣的能力。可能现在您不需要这样做,但我认为在整个结果处理过程中排除跟踪状态的能力是一种糟糕的设计。

    这实际上取决于许多事情,整个系统是否能够正常工作(不涉及实际实现的细节):

  • 无论您如何扭曲它,在检查“是否有更多数据”和向系统添加更多数据之间都会存在竞争条件。这意味着试图捕获最后几个数据项可能毫无意义
  • 您可能需要限制“是否有更多数据”的重复运行次数,否则您可能会陷入“处理最后一批数据时出现新数据”的无休止循环
  • 了解数据是否已更新是多么容易-如果所有更新都是“新项目”,且新ID依次较高,则您可以简单地查询“X上方是否有数据”,其中X是您的最后一个ID。但是,如果您正在计算数据中有多少项的属性Y设置为值A,数据可能会在当时更新数据库中的任何位置(例如,出租车目前所在位置的数据库,每几秒钟通过GPS更新一次,并且拥有数千辆汽车,可能很难确定自上次读取数据库以来哪些汽车有过更新) 至于您的实现,在选项2中,我不确定您所说的
    可能有更多数据
    状态是什么意思-要么有,要么没有,对吗?重复轮询更多数据是一个坏主意