这是本节的多页打印视图。
点击此处打印.
返回本页常规视图.
C++ SDK
Vertica SDK 支持在 C++ 11 中编写受保护和未受保护的 UDx。您可以下载、编译和运行示例;请参阅下载并运行 UDx 示例代码。运行示例是验证您的开发环境是否具有所有需要的库的好方法。
如果您无权访问 Vertica 测试环境,则可以在开发计算机上安装 Vertica 并运行单个节点。每次重建 UDx 库时,都需要将其重新安装到 Vertica。下图说明了典型开发周期。
此部分涵盖适用于所有 UDx 类型的 C++ 特定主题。有关适用于所有语言的信息,请参阅实参和返回值、UDx 参数、错误、警告和日志记录、处理取消请求和特定 UDx 类型的章节。如需完整的 API 文档,请参阅
C++ SDK 文档。
1 - 设置 C++ SDK
Vertica C++ 软件开发工具包 (SDK) 将作为服务器安装的一部分分发。它包含创建 UDx 库所需的源文件和头文件。有关可以编译和运行的示例,请参阅下载并运行 UDx 示例代码。
要求
至少需要在开发计算机上安装下列资源:
注意
可以使用安装在受支持 Linux 平台上的默认 g++
版本来编译 Vertica 二进制文件。
您必须使用标记 -std=c++11
进行编译。Vertica SDK 会使用 C++ 11 的功能。
以下可选软件包可以简化开发:
-
make
,或某些其他内部版本管理工具。
-
gdb
,或某些其他调试器。
-
Valgrind 或可检测内存泄漏的类似工具。
如果要使用任何第三方库(例如统计分析库),您需要在开发计算机上安装这些库。如果不是静态地将这些库链接到 UDx 库,您必须在群集中的每个节点上安装这些库。有关详细信息,请参阅编译 C++ 库。
SDK 文件
SDK 文件位于 Vertica 服务器根目录下的 sdk 子目录中(通常为
/opt/vertica/sdk
)。此目录包含子目录 include
,其中包含编译 UDx 库所需的头文件和源文件。
include 目录包含两个文件,在编译 UDx 时,需要使用这两个文件:
VerticaUDx.h
头文件(包含在 Vertica.h
文件中)中定义了许多 Vertica SDK API。如果对此感到好奇,您可以检查此文件的内容并阅读 API 文件。
开发 UDx 时,计划使用的 SDK 版本必须与其所在的数据库版本相同。要显示当前安装在系统上的 SDK 版本,请在 vsql 中运行以下命令:
=> SELECT sdk_version();
运行示例
您可以从 GitHub 存储库下载示例(请参阅下载并运行 UDx 示例代码)。编译和运行示例有助于确保正确设置开发环境。
要编译所有示例(包括 Java 示例),请在示例目录下的 Java-and-C++
目录中执行以下命令:
$ make
注意
要编译示例,必须已安装 g++ 开发环境。要在 Red Hat 系统上安装 g++ 开发环境,请运行 yum install gcc gcc-c++ make.
2 - 编译 C++ 库
仅支持使用 GNU g++ 编译器来编译 UDx 库。请始终基于 Vertica 群集上使用的相同 Linux 版本来编译 UDx 代码。
编译库时,始终必须执行下列操作:
-
使用 -std=c++11
标志进行编译。
-
将 -shared
和 -fPIC
标记传递到链接器。最简单的方法是在编译和链接库时仅将这些标记传递到 g++。
-
使用 -Wno-unused-value
标记可抑制未使用宏参数时的警告。如果不使用此标记,您可能会收到“left-hand operand of comma has no effect”(逗号的左操作数无效)警告。
-
编译 sdk/include/Vertica.cpp
并将其链接到库。此文件包含可帮助 UDx 与 Vertica 通信的支持例程。执行此操作的最简单方法是将此文件包含到用于编译库的 g++ 命令中。Vertica 以 C++ 源代码(而非库)的形式提供此文件,以避免库兼容性问题。
-
使用 g++ -I
标记将 Vertica SDK include 目录添加到 include 搜索路径中。
SDK 示例包括一个工作生成文件。请参阅下载并运行 UDx 示例代码。
编译 UDx 的示例
以下命令可将一个 UDx(包含于名为 MyUDx.cpp
的单个源文件中)编译到名为 MyUDx.so
的共享库中:
g++ -I /opt/vertica/sdk/include -Wall -shared -Wno-unused-value \
-fPIC -o MyUDx.so MyUDx.cpp /opt/vertica/sdk/include/Vertica.cpp
重要
Vertica 仅支持在 64 位架构上开发 UDx。
调试 UDx 后,便已准备好部署它。使用 -O3
标记重新编译 UDx,以启用编译器优化。
可以将其他源文件添加到库中,方法是将这些源文件添加到命令行中。还可以分别编译这些源文件,然后将它们链接到一起。
提示
Vertica SDK 目录中的 examples 子目录包含一个生成文件,此文件可以用作您自己的 UDx 项目的起点。
处理外部库
必须将 UDx 库链接到 UDx 代码所依赖的任何支持库。这些库必须是您开发的库或由第三方提供的其他库。可以使用以下两个选项进行链接:
3 - 将元数据添加到 C++ 库
您可以将诸如作者姓名、库版本以及库描述之类的元数据添加到您的库。此元数据可以让您跟踪在 Vertica 分析数据库群集上部署的函数的版本并让您函数的第三方用户了解该函数的创建者。库加载到 Vertica 分析数据库编录之后,库的元数据会出现在 USER_LIBRARIES 系统表中。
可通过在 UDx 的一个源文件中调用 RegisterLibrary()
函数为库声明元数据。如果 UDx 的源文件中存在多个函数调用,则使用在 Vertica 分析数据库加载库时最后解释的函数调用来确定库的元数据。
RegisterLibrary()
函数采用八个字符串参数:
RegisterLibrary(author,
library_build_tag,
library_version,
library_sdk_version,
source_url,
description,
licenses_required,
signature);
-
author
包含要与库创建相关联的名称(例如自己的名称或公司名称)。
-
library_build_tag
为用于代表库的特定版本的字符串(例如 SVN 修订号或编译库的时间戳)。开发库实例时,跟踪这些库实例很有用。
-
library_version
为库的版本。您可以使用想使用的任何编号或命名方案。
-
library_sdk_version
是已为其编译了库的 Vertica 分析数据库 SDK 库的版本。
注意
此字段没有用来确定库是否与 Vertica 分析数据库服务器版本兼容。编译库时,您用来编译库的 Vertica 分析数据库 SDK 版本会嵌入在库中。Vertica 分析数据库服务器会使用此信息来确定您的库是否与其兼容。
-
source_url
为函数用户可从中查找函数详细信息的 URL。这可以是您公司的网站、托管库源代码的 GitHub 页面或您喜欢的任何站点。
-
description
为库的简要描述。
-
licenses_required
为许可信息占位符。您必须为此值传递一个空字符串。
-
signature
为对库进行身份验证的签名的占位符。您必须为此值传递一个空字符串。
例如,以下代码演示了向 Add2Ints 示例添加元数据(请参阅 C++ 示例:Add2Ints)。
// Register the factory with Vertica
RegisterFactory(Add2IntsFactory);
// Register the library's metadata.
RegisterLibrary("Whizzo Analytics Ltd.",
"1234",
"2.0",
"7.0.0",
"http://www.example.com/add2ints",
"Add 2 Integer Library",
"",
"");
加载库并查询 USER_LIBRARIES 系统表将显示调用 RegisterLibrary()
提供的元数据:
=> CREATE LIBRARY add2intslib AS '/home/dbadmin/add2ints.so';
CREATE LIBRARY
=> \x
Expanded display is on.
=> SELECT * FROM USER_LIBRARIES WHERE lib_name = 'add2intslib';
-[ RECORD 1 ]-----+----------------------------------------
schema_name | public
lib_name | add2intslib
lib_oid | 45035996273869808
author | Whizzo Analytics Ltd.
owner_id | 45035996273704962
lib_file_name | public_add2intslib_45035996273869808.so
md5_sum | 732c9e145d447c8ac6e7304313d3b8a0
sdk_version | v7.0.0-20131105
revision | 125200
lib_build_tag | 1234
lib_version | 2.0
lib_sdk_version | 7.0.0
source_url | http://www.example.com/add2ints
description | Add 2 Integer Library
licenses_required |
signature |
4 - C++ SDK 数据类型
Vertica SDK 提供了用于在 UDx 代码中表示 Vertica 数据类型的 typedef 和类。使用这些 typedef,可确保 UDx 所处理和生成的数据与 Vertica 数据库之间的数据类型兼容性。下表介绍了部分可用的 typedef。有关完整列表以及用于转换和处理这些数据类型的 helper 函数的列表,请参阅
C++ SDK 文档。
有关 SDK 支持的复杂数据类型的信息,请参阅作为实参的复杂类型和返回值。
注意
-
在对象上发出某些 Vertica SDK API 调用(例如 VerticaType::getNumericLength()
)时,请确保这些对象具有正确的数据类型。为了最大程度减少开销并提高性能,大部分 API 不会检查在其上面发出调用的对象的数据类型。对不正确的数据类型调用函数会导致发生错误。
-
不能独自创建 VString 或 VNumeric 的实例。您可以处理这些类(由 Vertica 传递到 UDx)的现有对象的值,并从这些类提取值。但是,只有 Vertica 可以实例化这些类。
5 - C++ UDx 的资源使用情况
通过将类实例化并创建逻辑变量,UDx 可将自己占用的内存数量减至最少。UDx 的此基本内存使用量相当小,您完全不必关心此内存使用量。
如果 UDx 需要为数据结构分配超过 1 MB 或 2 MB 内存,或者需要访问其他资源(例如文件),您必须向 Vertica 通知其资源使用情况。然后,Vertica 可以先确保 UDx 所需的资源可用,接着再运行使用该 UDx 的查询。如果许多调用该 UDx 的查询同时运行,则即使是中等内存使用量(例如,UDx 的每个调用各 10 MB)也会成为问题。
注意
如果 Udx 分配自己的内存,您
一定要确保 它能正确释放内存。即使未能释放已分配内存的一个字节,也可能产生严重的后果。您不应让代码分配自己的内存,而是应使用 C++
vt_alloc
宏,该宏会利用 Vertica 自有的内存管理器来分配和跟踪内存。UDx 完成执行后,系统可保证正确释放该内存。有关详细信息,请参阅
为 UDx 分配资源。
5.1 - 为 UDx 分配资源
为用户定义的扩展 (UDx) 分配内存和文件句柄时,您可以选择以下两种方法:
注意
如果选择手动在 UDx 分配资源,您必须十分小心。未能正确释放资源将造成严重的负面影响,尤其是当 UDx 在非隔离模式下运行时。
无论选择哪种方法,您通常都应在 UDx 类中名为 setup()
的函数中分配资源。在 UDx 函数对象已实例化之后,但在 Vertica 调用该对象以处理数据之前,将调用此函数。
如果手动在 setup()
函数中分配内存,则您必须在名为 destroy()
的相应函数中释放内存。在 UDx 已执行所有处理之后,将调用此函数。如果 UDx 返回错误,也会调用此函数(请参阅处理错误)。
注意
始终应使用 setup()
和 destroy()
函数来分配和释放资源,而不应使用自己的构造函数和析构函数。Vertica 从自有的内存池之一为 UDx 对象分配内存。Vertica 总是在释放对象的内存之前调用 UDx 的 destroy()
函数。无法保证会在取消分配对象之前调用 UDx 的析构函数。使用 destroy()
函数可确保 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());
}
}
5.2 - 使用 SDK 宏分配资源
Vertica SDK 提供了三个用于分配内存的宏:
-
vt_alloc
可分配内存块以用于容纳特定数据类型(vint、struct 等)。
-
vt_allocArray
可分配内存块以用于容纳特定数据类型的数组。
-
vt_allocSize
可分配任意大小的内存块。
所有这些宏都可以从 Vertica 管理的内存池分配内存。让 Vertica 管理 UDx 的内存的主要优点是,内存会在 UDx 已完成之后自动回收。这样可确保 UDx 中不会出现内存泄漏。
由于 Vertica 会自动释放该内存,因此请勿尝试释放您通过以上任一宏分配的任何内存。尝试释放该内存将导致发生运行时错误。
5.3 - 向 Vertica 通知资源要求
在隔离模式下运行 UDx 时,Vertica 会监控其内存和文件句柄的使用情况。如果 UDx 使用超过数 MB 内存或使用任意文件句柄,则它应向 Vertica 说明其资源要求。获知 UDx 的资源要求后,Vertica 能够确定是可以立即运行该 UDx,还是需要将请求排入队列直至足够的资源变为可用于运行该 UDx。
在某些情况下,可能难以确定 UDx 查询所需的内存数量。例如,如果 UDx 从数据集提取唯一的数据元素,数据项的数量可能不会受到限制。在这种情况下,您可以在测试环境中运行 UDx 并在节点上监控该 UDx 在处理多个不同大小的查询时的内存使用量,然后基于该 UDx 在生产环境中可能面临的最坏情况推断其内存使用量,这种技术很有用。在所有情况下,通常最好为您向 Vertica 告知的 Udx 内存使用量添加安全余地。
注意
规划查询执行时,将使用您传递给 Vertica 的 UDx 资源需求相关信息。没有任何方法可用于在 UDx 实际处于运行状态时更改 UDx 从 Vertica 请求的资源数量。
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
}
};
5.4 - 为隔离模式 UDx 设置内存限制
Vertica 将调用隔离模式 UDx 的 Vertica::UDXFactory::getPerInstanceResources()
实施,以确定是否有足够的资源可用于运行包含该 UDx 的查询(请参阅向 Vertica 通知资源要求)。由于这些报告并非由实际内存使用生成,因此这些报告可能不准确。Vertica 启动某个 UDx 之后,该 UDx 实际分配的内存或文件句柄可能远远多于其报告的需求量。
使用 FencedUDxMemoryLimitMB 配置参数,您可以为 UDx 创建绝对内存限制。只要 UDx 尝试分配的内存数量超过此限制,就会生成 bad_alloc
异常。有关设置 FencedUDxMemoryLimitMB 的示例,请参阅如何强制执行资源限制。
5.5 - 如何强制执行资源限制
运行查询之前,Vertica 会确定该查询运行所需的内存量。如果查询含有在工厂类中实施 getPerInstanceResources()
函数的隔离模式 UDx,Vertica 将调用此函数以确定其所需的内存数量,并将此内存数量添加到查询所需的内存总量中。Vertica 会根据以下要求来确定如何处理查询:
-
如果所需的内存总量(包括 UDx 报告的所需内存量)超过会话的 MEMORYCAP 或
资源池的 MAXMEMORYSIZE 设置,Vertica 将拒绝该查询。有关资源池的详细信息,请参阅资源池架构。
-
如果内存量低于会话所设置的限制和资源池限制,但目前没有足够的可用内存可用于运行查询,Vertica 会将该查询排入队列,直至足够资源变为可用为止。
-
如果有足够的可用资源可用于运行查询,Vertica 会执行该查询。
注意
除了使用
getPerInstanceResources()
函数报告的值以外,Vertica 没有任何其他方法可以确定 UDx 所需的资源数量。UDx 使用的资源数量可能会超过其声明的数量,从而导致被拒绝授予资源的其他查询出现性能问题。可以对 UDx 可分配的内存数量设置绝对限制。有关详细信息,请参阅
为隔离模式 UDx 设置内存限制。
如果执行 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)
另请参阅