这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

C++ UDx 的资源使用情况

通过将类实例化并创建逻辑变量,UDx 可将自己占用的内存数量减至最少。UDx 的此基本内存使用量相当小,您完全不必关心此内存使用量。

如果 UDx 需要为数据结构分配超过 1 MB 或 2 MB 内存,或者需要访问其他资源(例如文件),您必须向 Vertica 通知其资源使用情况。然后,Vertica 可以先确保 UDx 所需的资源可用,接着再运行使用该 UDx 的查询。如果许多调用该 UDx 的查询同时运行,则即使是中等内存使用量(例如,UDx 的每个调用各 10 MB)也会成为问题。

1 - 为 UDx 分配资源

为用户定义的扩展 (UDx) 分配内存和文件句柄时,您可以选择以下两种方法:

  • 使用 Vertica SDK 宏分配资源。这是最佳方法,因为此方法使用 Vertica 自有的资源管理器,并且能够保证 UDx 所使用的资源得到回收。请参阅使用 SDK 宏分配资源

  • 虽然不是推荐的选项,但您可以使用标准 C++ 方法(使用 new 将对象实例化和使用 malloc() 分配内存块等)自己在 UDx 中分配资源。您必须在 UDx 退出之前手动释放这些资源。

无论选择哪种方法,您通常都应在 UDx 类中名为 setup() 的函数中分配资源。在 UDx 函数对象已实例化之后,但在 Vertica 调用该对象以处理数据之前,将调用此函数。

如果手动在 setup() 函数中分配内存,则您必须在名为 destroy() 的相应函数中释放内存。在 UDx 已执行所有处理之后,将调用此函数。如果 UDx 返回错误,也会调用此函数(请参阅处理错误)。

以下代码片段演示了使用 setup()destroy() 函数分配和释放内存。

class MemoryAllocationExample : public ScalarFunction
{
public:
    uint64* myarray;
    // Called before running the UDF to allocate memory used throughout
    // the entire UDF processing.
    virtual void setup(ServerInterface &srvInterface, const SizedColumnTypes
                        &argTypes)
    {
        try
        {
            // Allocate an array. This memory is directly allocated, rather than
            // letting Vertica do it. Remember to properly calculate the amount
            // of memory you need based on the data type you are allocating.
            // This example divides 500MB by 8, since that's the number of
            // bytes in a 64-bit unsigned integer.
            myarray = new uint64[1024 * 1024 * 500 / 8];
        }
        catch (std::bad_alloc &ba)
        {
            // Always check for exceptions caused by failed memory
            // allocations.
            vt_report_error(1, "Couldn't allocate memory :[%s]", ba.what());
        }

    }

    // Called after the UDF has processed all of its information. Use to free
    // any allocated resources.
    virtual void destroy(ServerInterface &srvInterface, const SizedColumnTypes
                          &argTypes)
    {
        // srvInterface.log("RowNumber processed %d records", *count_ptr);
        try
        {
            // Properly dispose of the allocated memory.
            delete[] myarray;
        }
        catch (std::bad_alloc &ba)
        {
            // Always check for exceptions caused by failed memory
            // allocations.
            vt_report_error(1, "Couldn't free memory :[%s]", ba.what());
        }

    }

2 - 使用 SDK 宏分配资源

Vertica SDK 提供了三个用于分配内存的宏:

  • vt_alloc 可分配内存块以用于容纳特定数据类型(vint、struct 等)。

  • vt_allocArray 可分配内存块以用于容纳特定数据类型的数组。

  • vt_allocSize 可分配任意大小的内存块。

所有这些宏都可以从 Vertica 管理的内存池分配内存。让 Vertica 管理 UDx 的内存的主要优点是,内存会在 UDx 已完成之后自动回收。这样可确保 UDx 中不会出现内存泄漏。

由于 Vertica 会自动释放该内存,因此请勿尝试释放您通过以上任一宏分配的任何内存。尝试释放该内存将导致发生运行时错误。

3 - 向 Vertica 通知资源要求

在隔离模式下运行 UDx 时,Vertica 会监控其内存和文件句柄的使用情况。如果 UDx 使用超过数 MB 内存或使用任意文件句柄,则它应向 Vertica 说明其资源要求。获知 UDx 的资源要求后,Vertica 能够确定是可以立即运行该 UDx,还是需要将请求排入队列直至足够的资源变为可用于运行该 UDx。

在某些情况下,可能难以确定 UDx 查询所需的内存数量。例如,如果 UDx 从数据集提取唯一的数据元素,数据项的数量可能不会受到限制。在这种情况下,您可以在测试环境中运行 UDx 并在节点上监控该 UDx 在处理多个不同大小的查询时的内存使用量,然后基于该 UDx 在生产环境中可能面临的最坏情况推断其内存使用量,这种技术很有用。在所有情况下,通常最好为您向 Vertica 告知的 Udx 内存使用量添加安全余地。

UDx 可通过在其工厂类中实施 getPerInstanceResources() 函数来向 Vertica 通知其资源需求(请参阅 SDK 文档中的 Vertica::UDXFactory::getPerInstanceResources())。如果 UDx 的工厂类实施此函数,Vertica 将调用此函数以确定 UDx 所需的资源。

getPerInstanceResources() 函数将收到 Vertica::VResources 结构的一个实例。此结构包含用于设置 UDx 所需的内存数量和文件句柄数的字段。默认情况下,Vertica 服务器会为 UDx 的每个实例分配零字节内存和 100 个文件句柄。

getPerInstanceResources() 函数的实施基于 UDx 可能使用的最大资源数量为 UDx 函数的每个实例设置 VResources 结构中的字段。因此,如果 UDx 的 processBlock() 函数所创建的数据结构最多使用 100 MB 内存,则 UDx 必须将 VResources.scratchMemory 字段设置为 104857600(100 MB 等于的字节数)或以上。通过将该数字增加至 UDx 在正常情况下应使用的内存数量以上,为您自己保留一个安全边际。在此示例中,合适做法是分配 115000000 字节(刚好低于 110 MB)。

以下 ScalarFunctionFactory 类演示了调用 getPerInstanceResources() 以向 Vertica 通知为 UDx 分配资源中所示的 MemoryAllocationExample 类的内存要求。该类会向 Vertica 说明 UDSF 需要 510 MB 内存(为保持安全大小,此容量比 UDSF 实际分配的容量稍多)。

class MemoryAllocationExampleFactory : public ScalarFunctionFactory
{
    virtual Vertica::ScalarFunction *createScalarFunction(Vertica::ServerInterface
                                                            &srvInterface)
    {
        return vt_createFuncObj(srvInterface.allocator, MemoryAllocationExample);
    }
    virtual void getPrototype(Vertica::ServerInterface &srvInterface,
                              Vertica::ColumnTypes &argTypes,
                              Vertica::ColumnTypes &returnType)
    {
        argTypes.addInt();
        argTypes.addInt();
        returnType.addInt();
    }
    // Tells Vertica the amount of resources that this UDF uses.
    virtual void getPerInstanceResources(ServerInterface &srvInterface,
                                          VResources &res)
    {
        res.scratchMemory += 1024LL * 1024 * 510; // request 510MB of memory
    }
};

4 - 为隔离模式 UDx 设置内存限制

Vertica 将调用隔离模式 UDx 的 Vertica::UDXFactory::getPerInstanceResources() 实施,以确定是否有足够的资源可用于运行包含该 UDx 的查询(请参阅向 Vertica 通知资源要求)。由于这些报告并非由实际内存使用生成,因此这些报告可能不准确。Vertica 启动某个 UDx 之后,该 UDx 实际分配的内存或文件句柄可能远远多于其报告的需求量。

使用 FencedUDxMemoryLimitMB 配置参数,您可以为 UDx 创建绝对内存限制。只要 UDx 尝试分配的内存数量超过此限制,就会生成 bad_alloc 异常。有关设置 FencedUDxMemoryLimitMB 的示例,请参阅如何强制执行资源限制

5 - 如何强制执行资源限制

运行查询之前,Vertica 会确定该查询运行所需的内存量。如果查询含有在工厂类中实施 getPerInstanceResources() 函数的隔离模式 UDx,Vertica 将调用此函数以确定其所需的内存数量,并将此内存数量添加到查询所需的内存总量中。Vertica 会根据以下要求来确定如何处理查询:

  • 如果所需的内存总量(包括 UDx 报告的所需内存量)超过会话的 MEMORYCAP 或 资源池的 MAXMEMORYSIZE 设置,Vertica 将拒绝该查询。有关资源池的详细信息,请参阅资源池架构

  • 如果内存量低于会话所设置的限制和资源池限制,但目前没有足够的可用内存可用于运行查询,Vertica 会将该查询排入队列,直至足够资源变为可用为止。

  • 如果有足够的可用资源可用于运行查询,Vertica 会执行该查询。

如果执行 UDx 的进程尝试分配比 FencedUDxMemoryLimitMB 配置参数所设置的限制更多的内存,进程将收到 bad_alloc 异常。有关 FencedUDxMemoryLimitMB 的详细信息,请参阅为隔离模式 UDx 设置内存限制

下面是执行以下操作时的输出:加载使用 500 MB 内存的 UDSF,然后更改内存设置而导致发生内存不足错误。以下示例中的 MemoryAllocationExample UDSF 只是按照为 UDx 分配资源向 Vertica 通知资源要求中所示而更改的 Add2Ints UDSF 示例,用于分配 500 MB RAM。

=> CREATE LIBRARY mylib AS '/home/dbadmin/MemoryAllocationExample.so';
CREATE LIBRARY
=> CREATE FUNCTION usemem AS NAME 'MemoryAllocationExampleFactory' LIBRARY mylib
-> FENCED;
CREATE FUNCTION
=> SELECT usemem(1,2);
 usemem
--------
      3
(1 row)

以下语句演示了将会话的 MEMORYCAP 设置为比 UDSF 报告其使用的内存数量更低的值。此操作会导致 Vertica 在执行 UDSF 之前返回错误。

=> SET SESSION MEMORYCAP '100M';
SET
=> SELECT usemem(1,2);
ERROR 3596:  Insufficient resources to execute plan on pool sysquery
[Request exceeds session memory cap: 520328KB > 102400KB]
=> SET SESSION MEMORYCAP = default;
SET

如果 UDx 所需的内存量超过池中的可用容量, 资源池还会阻止该 UDx 运行。以下语句演示了所创建和使用的资源池具有太少内存而导致 UDSF 无法运行的结果。与会话的 MAXMEMORYCAP 限制相似,池的 MAXMEMORYSIZE 设置可防止 Vertica 执行包含该 UDSF 的查询。

=> CREATE RESOURCE POOL small MEMORYSIZE '100M' MAXMEMORYSIZE '100M';
CREATE RESOURCE POOL
=> SET SESSION RESOURCE POOL small;
SET
=> CREATE TABLE ExampleTable(a int, b int);
CREATE TABLE
=> INSERT /*+direct*/ INTO ExampleTable VALUES (1,2);
 OUTPUT
--------
      1
(1 row)
=> SELECT usemem(a, b) FROM ExampleTable;
ERROR 3596:  Insufficient resources to execute plan on pool small
[Request Too Large:Memory(KB) Exceeded: Requested = 523136, Free = 102400 (Limit = 102400, Used = 0)]
=> DROP RESOURCE POOL small; --Dropping the pool resets the session's pool
DROP RESOURCE POOL

最后,将 FencedUDxMemoryLimitMB 配置参数设置为比 UDx 实际分配的内存数量更低的值会导致该 UDx 抛出异常。此示例与前两个示例之一是不同情况,因为实际上会执行该查询。UDx 的代码需要捕获并处理该异常。在此示例中,该代码使用 vt_report_error 宏将错误报告回 Vertica 并退出。

=> ALTER DATABASE DEFAULT SET FencedUDxMemoryLimitMB = 300;

=> SELECT usemem(1,2);
    ERROR 3412:  Failure in UDx RPC call InvokeSetup(): Error calling setup() in
    User Defined Object [usemem] at [MemoryAllocationExample.cpp:32], error code:
     1, message: Couldn't allocate memory :[std::bad_alloc]

=> ALTER DATABASE DEFAULT SET FencedUDxMemoryLimitMB = -1;

=> SELECT usemem(1,2);
 usemem
--------
      3
(1 row)

另请参阅