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

返回本页常规视图.

使用 Vertica SDK 进行开发

在编写用户定义的扩展之前,您必须设置一个开发环境。完成此操作后,最好通过下载、构建和运行已发布的示例来进行测试。

除了介绍如何设置您的环境之外,本节还描述了有关使用 Vertica SDK 的一般信息,包括特定于语言的注意事项。

1 - 设置开发环境

在开始开发 UDx 之前,您需要配置您的开发环境和测试环境。开发测试和测试环境必须使用与生产环境相同的操作系统和 Vertica 版本。

有关其他特定于语言的要求,请参阅以下主题:

开发环境选项

您用于开发 UDx 的语言决定了开发环境的设置选项和要求。C++ 开发人员可以使用 C++ UDx 容器,而所有开发人员都可以使用非生产 Vertica 环境

C++ UDx 容器

C++ 开发人员可以使用 C++ UDx 容器进行开发。UDx 容器的 GitHub 存储库提供了用于构建容器的工具,该容器可对开发 C++ Vertica 扩展所需的二进制文件、库和编译器进行打包。C++ UDx 容器具有以下构建选项:

  • CentOS 或 Ubuntu 基础映像

  • Vertica 10.x 和 11.x 版本

有关要求、构建和测试的详细信息,请参阅存储库自述文件

非生产 Vertica 环境

您可以使用非生产 Vertica 数据库中的节点,也可以使用另一台运行与生产环境相同的操作系统和 Vertica 版本的计算机。有关具体要求和依赖项,请参考操作系统要求语言要求

测试环境选项

要测试 UDx,需要访问非生产 Vertica 数据库。您具有以下选择:

  • 在开发计算机上安装单节点 Vertica 数据库。
  • 下载并构建容器化测试环境。

容器化测试环境

Vertica 提供以下容器化选项来简化您的测试环境设置:

操作系统要求

在用于生产 Vertica 数据库群集的同一 Linux 平台上开发 UDx 代码。基于 Centos 和 Debian 的操作系统都要求您下载其他包。

基于 CentOS 的操作系统

在以下基于 CentOS 的操作系统上安装时需要 devtoolset-7 包

  • CentOS

  • Red Hat Enterprise Linux

  • Oracle Enterprise Linux

有关具体的安装命令,请参阅适用于您所用操作系统的文档。

基于 Debian 的操作系统

在以下基于 Debian 的操作系统上安装时需要 GCC-7 包

  • Debian

  • Ubuntu

  • SUSE

  • OpenSUSE

  • Amazon Linux(GCC 包已预装在 Amazon Linux 上)

有关具体的安装命令,请参阅适用于您所用操作系统的文档。

2 - 下载并运行 UDx 示例代码

您可以从 Vertica GitHub 存储库下载本文档中显示的所有示例以及更多示例。此存储库包含所有类型的 UDx 的示例。

您可以通过以下两种方式之一下载示例:

  • 下载 ZIP 文件。将文件内容解压缩到目录中。

  • 克隆存储库。使用终端窗口,运行以下命令:

    $ git clone https://github.com/vertica/UDx-Examples.git
    

该存储库包含一个可用于编译 C++ 和 Java 示例的生成文件。它还包含加载和使用示例的 .sql 文件。有关编译和运行示例的说明,请参阅 README 文件。要编译示例,您将需要 g++ 或 JDK 和 make。有关相关信息,请参阅设置开发环境

运行示例不仅有助于了解 UDx 的工作原理,而且有助于确保正确设置开发环境以编译 UDx 库。

另请参阅

3 - C++ SDK

Vertica SDK 支持在 C++ 11 中编写受保护和未受保护的 UDx。您可以下载、编译和运行示例;请参阅下载并运行 UDx 示例代码。运行示例是验证您的开发环境是否具有所有需要的库的好方法。

如果您无权访问 Vertica 测试环境,则可以在开发计算机上安装 Vertica 并运行单个节点。每次重建 UDx 库时,都需要将其重新安装到 Vertica。下图说明了典型开发周期。

此部分涵盖适用于所有 UDx 类型的 C++ 特定主题。有关适用于所有语言的信息,请参阅实参和返回值UDx 参数错误、警告和日志记录处理取消请求和特定 UDx 类型的章节。如需完整的 API 文档,请参阅 C++ SDK 文档。

3.1 - 设置 C++ SDK

Vertica C++ 软件开发工具包 (SDK) 将作为服务器安装的一部分分发。它包含创建 UDx 库所需的源文件和头文件。有关可以编译和运行的示例,请参阅下载并运行 UDx 示例代码

要求

至少需要在开发计算机上安装下列资源:

  • devtoolset-7 包 (CentOS) 或 GCC-7 包 (Debian),包括 gcc 版本 7 和最新的 libstdc++ 包。

  • g++ 及其关联的工具链,例如 ld。有些 Linux 分发将 g++gcc 分开打包。

  • Vertica SDK 的副本。

您必须使用标记 -std=c++11 进行编译。Vertica SDK 会使用 C++ 11 的功能。

以下可选软件包可以简化开发:

  • make,或某些其他内部版本管理工具。

  • gdb,或某些其他调试器。

  • Valgrind 或可检测内存泄漏的类似工具。

如果要使用任何第三方库(例如统计分析库),您需要在开发计算机上安装这些库。如果不是静态地将这些库链接到 UDx 库,您必须在群集中的每个节点上安装这些库。有关详细信息,请参阅编译 C++ 库

SDK 文件

SDK 文件位于 Vertica 服务器根目录下的 sdk 子目录中(通常为 /opt/vertica/sdk)。此目录包含子目录 include,其中包含编译 UDx 库所需的头文件和源文件。

include 目录包含两个文件,在编译 UDx 时,需要使用这两个文件:

  • Vertica.h 是 SDK 的主要头文件。UDx 代码需要包含此文件才能查找 SDK 的定义。

  • Vertica.cpp 包含需要编译到 UDx 库中的支持代码。

VerticaUDx.h 头文件(包含在 Vertica.h 文件中)中定义了许多 Vertica SDK API。如果对此感到好奇,您可以检查此文件的内容并阅读 API 文件。

开发 UDx 时,计划使用的 SDK 版本必须与其所在的数据库版本相同。要显示当前安装在系统上的 SDK 版本,请在 vsql 中运行以下命令:

=> SELECT sdk_version();

运行示例

您可以从 GitHub 存储库下载示例(请参阅下载并运行 UDx 示例代码)。编译和运行示例有助于确保正确设置开发环境。

要编译所有示例(包括 Java 示例),请在示例目录下的 Java-and-C++ 目录中执行以下命令:

$ make

3.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

调试 UDx 后,便已准备好部署它。使用 -O3 标记重新编译 UDx,以启用编译器优化。

可以将其他源文件添加到库中,方法是将这些源文件添加到命令行中。还可以分别编译这些源文件,然后将它们链接到一起。

处理外部库

必须将 UDx 库链接到 UDx 代码所依赖的任何支持库。这些库必须是您开发的库或由第三方提供的其他库。可以使用以下两个选项进行链接:

  • 静态地将支持库链接到 UDx。此方法的优点是 UDx 库不依赖外部文件。拥有一个 UDx 库文件可以简化部署,因为您只需将一个文件传输到 Vertica 群集。此方法的主要缺点是增加 UDx 库文件的大小。

  • 动态地将库链接到 UDx。如果第三方库不允许静态链接,则有时必须使用动态链接。在这种情况下,必须将库和 UDx 库文件复制到 Vertica 群集。

3.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 库的版本。

  • 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         |

3.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 可以实例化这些类。

3.5 - C++ UDx 的资源使用情况

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

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

3.5.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());
        }

    }

3.5.2 - 使用 SDK 宏分配资源

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

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

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

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

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

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

3.5.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
    }
};

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

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

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

3.5.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)

另请参阅

4 - Java SDK

Vertica SDK 支持编写除聚合函数之外的所有类型的 Java UDx。所有 Java UDx 均被隔离。

您可以下载、编译和运行示例;请参阅下载并运行 UDx 示例代码。运行示例是验证您的开发环境是否具有所有需要的库的好方法。

如果您无权访问 Vertica 测试环境,则可以在开发计算机上安装 Vertica 并运行单个节点。每次重建 UDx 库时,都需要将其重新安装到 Vertica。下图说明了典型开发周期。

此部分涵盖适用于所有 UDx 类型的特定于 Java 的主题。有关适用于所有语言的信息,请参阅实参和返回值UDx 参数错误、警告和日志记录处理取消请求和特定 UDx 类型的章节。如需完整的 API 文档,请参阅 Java SDK 文档。

4.1 - 设置 Java SDK

Vertica Java 软件开发工具包 (SDK) 将作为服务器安装的一部分分发。它包含创建 UDx 库所需的源文件和 JAR 文件。有关可以编译和运行的示例,请参阅下载并运行 UDx 示例代码

要求

至少需要在开发计算机上安装下列资源:

  • 与您已在数据库主机上安装的 Java 版本相匹配的 Java 开发工具包 (JDK) 版本(请参阅在 Vertica 主机上安装 Java)。

  • Vertica SDK 的副本。

或者,您可以使用内部版本管理工具(例如 make)来简化开发。

SDK 文件

要使用 SDK,您需要 Java 支持包中的两个文件:

  • /opt/vertica/bin/VerticaSDK.jar 包含 Vertica Java SDK 和其他支持文件。

  • /opt/vertica/sdk/BuildInfo.java 包含有关 SDK 的版本信息。您必须编译此文件并将其包含到 Java UDx JAR 文件中。

如果您不在数据库节点上进行开发,则可以从数据库节点之一将这两个文件复制到开发系统。

用来编译 UDx 的 BuildInfo.javaVerticaSDK.jar 文件必须来自同一个 SDK 版本。这两个文件还必须与 Vertica 主机上的 SDK 文件的版本匹配。只有不在 Vertica 主机上编译 UDx 时,版本控制才会出现问题。如果要在单独的开发系统上进行编译,请始终刷新这两个文件的副本,并在部署之前重新编译 UDx。

开发 UDx 时,计划使用的 SDK 版本必须与其所在的数据库版本相同。要显示当前安装在系统上的 SDK 版本,请在 vsql 中运行以下命令:

=> SELECT sdk_version();

编译 BuildInfo.java

您需要将 BuildInfo.java 文件编译到类文件中,以便可以将该文件包含到 Java UDx JAR 库中。如果要使用 Vertica 节点作为开发系统,您可以执行以下任一操作:

  • BuildInfo.java 文件复制到主机上的其他位置。

  • 如果您没有 root 权限,请在原位置编译 BuildInfo.java 文件。(只有 root 用户拥有将文件写入到 /opt/vertica/sdk 目录的权限。)

使用以下命令编译文件。将 path 替换为文件的路径,并将 output-directory 替换为将在其中编译 UDx 的目录。

$ javac -classpath /opt/vertica/bin/VerticaSDK.jar \
      /path/BuildInfo.java -d output-directory

如果使用诸如 Eclipse 等 IDE,则您可以将 BuildInfo.java 文件包含到项目中,而不必单独编译此文件。您还必须将 VerticaSDK.jar 文件添加到项目的构建路径中。有关如何将文件和库包含到项目中的详细信息,请参阅 IDE 的文档。

运行示例

您可以从 GitHub 存储库下载示例(请参阅下载并运行 UDx 示例代码)。编译和运行示例有助于确保正确设置开发环境。

如果您尚未这样做,请将 JAVA_HOME 环境变量设置为您的 JDK(而非 JRE)目录。

要编译所有示例(包括 Java 示例),请在示例目录下的 Java-and-C++ 目录中执行以下命令:

$ make

要仅编译 Java 示例,请在示例目录下的 Java-and-C++ 目录中执行以下命令:

$ make JavaFunctions

4.2 - 编译并打包 Java 库

您需要先编译 Java UDx 并将其打包到 JAR 文件中,然后才能开始使用。

SDK 示例包括一个工作生成文件。请参阅下载并运行 UDx 示例代码

编译 Java UDx

编译 Java UDx 源文件时,您需要将 SDK JAR 文件包含到 CLASSPATH 中,以便 Java 编译器可以解析 Vertica API 调用。如果要使用位于数据库群集中的主机上的命令行 Java 编译器,请输入以下命令:

$ javac -classpath /opt/vertica/bin/VerticaSDK.jar factorySource.java \
      [functionSource.java...] -d output-directory

如果所有源文件均位于同一个目录中,则您可以在命令行中使用 *.java,而不必逐个列出文件。

如果要使用 IDE,请验证 VerticaSDK.jar 文件的副本是否位于构建路径中。

UDx 类文件组织

编译 UDx 之后,您必须将其类文件和 BuildInfo.class 文件打包到 JAR 文件中。

要使用打包在 JDK 中的 jar 命令,您必须将 UDx 类文件组织成与类包结构匹配的目录结构。例如,假设 UDx 的工厂类的完全限定名称为 com.mycompany.udfs.Add2ints。在这种情况下,类文件必须位于目录层次结构 com/mycompany/udfs(相对于项目的基本目录)中。此外,您还必须将 BuildInfo.class 文件的副本放到路径 com/vertica/sdk 中,以便可以将该副本包含到 JAR 文件中。此类必须存在于 JAR 文件中,以指示用于编译 Java UDx 的 SDK 版本。

Add2ints UDSF 示例的 JAR 文件在编译后具有以下目录结构:

com/vertica/sdk/BuildInfo.class
com/mycompany/example/Add2intsFactory.class
com/mycompany/example/Add2intsFactory$Add2ints.class

将 UDx 打包到 JAR 文件中

要从命令行创建 JAR 文件,请执行下列操作:

  1. 更改为项目的根目录。

  2. 使用 jar 命令打包 BuildInfo.class 文件和 UDx 中的所有类:

    # jar -cvf libname.jar com/vertica/sdk/BuildInfo.class \
           packagePath/*.class
    

    键入此命令时,libname 表示为 JAR 文件选择的文件名(您可以随意选择任何名称),packagePath 表示包含 UDx 的类文件的目录的路径。

    • 例如,要打包 Add2ints 示例中的文件,请使用以下命令:

      # jar -cvf Add2intsLib.jar com/vertica/sdk/BuildInfo.class \
      com/mycompany/example/*.class
      
    • 更简单来说,如果已将 BuildInfo.class 和类文件打包到同一个根目录中,则您可以使用以下命令:

      # jar -cvf Add2intsLib.jar .
      

    您必须将构成 UDx 的所有类文件包含到 JAR 文件中。UDx 始终包含至少两个类(工厂类和函数类)。即使您已将函数类定义为工厂类的内部类,Java 也会为内部类生成单独的类文件。

将 UDx 打包到 JAR 文件之后,您便已准备好将其部署到 Vertica 数据库。

4.3 - 处理 Java UDx 依赖项

如果 Java UDx 依赖一个或多个外部库,您可以通过以下三种方法之一处理依赖项:

  • 使用工具(例如,One-JAR 或 Eclipse Runnable JAR Export Wizard)将 JAR 文件捆绑到 UDx JAR 文件中。

  • 将 JAR 文件解包,然后将其内容重新打包到 UDx 的 JAR 文件中。

  • 将库复制到 Vertica 群集和 UDx 库。然后使用 CREATE LIBRARY 语句的 DEPENDS 关键字向 Vertica 说明 UDx 库依赖外部库。此关键字用作特定于库的 CLASSPATH 设置。Vertica 会将支持库分发给群集中的所有节点,并设置 UDx 的类路径以便能够找到这些支持库。

    如果 UDx 依赖本地库(SO 文件),请使用 DEPENDS 关键字指定其路径。调用 UDx 中的 System.loadLibrary(使用本地库之前必须执行此操作)时,此函数使用 DEPENDS 路径来查找这些库。您不需要另外设置 LD_LIBRARY_PATH 环境变量。

外部库示例

以下示例演示了将外部库与 Java UDx 结合使用。

以下示例代码定义了一个名为 VowelRemover 的简单类。此类包含名为 removevowels 的单个方法,该方法将从字符串中移除所有元音字母(字母 aeiouy)。

package com.mycompany.libs;

public class VowelRemover {
    public String removevowels(String input) {
        return input.replaceAll("(?i)[aeiouy]", "");
    }
};

可以使用以下命令编译此类并将其打包到 JAR 文件中:

$ javac -g com/mycompany/libs/VowelRemover.java
$ jar cf mycompanylibs.jar com/mycompany/libs/VowelRemover.class

以下代码定义了一个名为 DeleteVowels Java UDSF,它使用在以上示例代码中定义的库。 DeleteVowels 接受单个 VARCHAR 作为输入,并返回 VARCHAR。

package com.mycompany.udx;
// Import the support class created earlier
import com.mycompany.libs.VowelRemover;
// Import the Vertica SDK
import com.vertica.sdk.*;

public class DeleteVowelsFactory extends ScalarFunctionFactory {

    @Override
    public ScalarFunction createScalarFunction(ServerInterface arg0) {
        return new DeleteVowels();
    }

    @Override
    public void getPrototype(ServerInterface arg0, ColumnTypes argTypes,
            ColumnTypes returnTypes) {
        // Accept a single string and return a single string.
        argTypes.addVarchar();
        returnTypes.addVarchar();
    }

    @Override
    public void getReturnType(ServerInterface srvInterface,
            SizedColumnTypes argTypes,
            SizedColumnTypes returnType){
        returnType.addVarchar(
        // Output will be no larger than the input.
        argTypes.getColumnType(0).getStringLength(), "RemovedVowels");
    }

    public class DeleteVowels extends ScalarFunction
    {
        @Override
        public void processBlock(ServerInterface arg0, BlockReader argReader,
                BlockWriter resWriter) throws UdfException, DestroyInvocation {

            // Create an instance of the  VowelRemover object defined in
            // the library.
            VowelRemover remover = new VowelRemover();

            do {
                String instr = argReader.getString(0);
                // Call the removevowels method defined in the library.
                resWriter.setString(remover.removevowels(instr));
                resWriter.next();
            } while (argReader.next());
        }
    }

}

可以使用以下命令构建示例 UDSF 并将其打包到 JAR 中:

  • 第一个 javac 命令可编译 SDK 的 BuildInfo 类。Vertica 要求所有 UDx 库包含此类。此 javac 命令的 -d 选项可在 UDSF 源的目录结构中输出类文件。

  • 第二个 javac 命令可编译 UDSF 类。此命令可将先前创建的 mycompanylibs.jar 文件添加到类路径,以便编译器能够找到 VowelRemover 类。

  • jar 命令可将 BuildInfo 和 UDx 库的类打包到一起。

$ javac -g -cp /opt/vertica/bin/VerticaSDK.jar\
   /opt/vertica/sdk/com/vertica/sdk/BuildInfo.java -d .
$ javac -g -cp mycompanylibs.jar:/opt/vertica/bin/VerticaSDK.jar\
  com/mycompany/udx/DeleteVowelsFactory.java
$ jar cf DeleteVowelsLib.jar com/mycompany/udx/*.class \
   com/vertica/sdk/*.class

要安装 UDx 库,您必须将两个 JAR 文件同时复制到 Vertica 群集中的节点。然后连接到该节点以执行 CREATE LIBRARY 语句。

以下示例演示了如何在复制 JAR 文件之后将 UDx 库加载到 dbadmin 用户的主目录。DEPENDS 关键字可向 Vertica 说明 UDx 库依赖 mycompanylibs.jar 文件。

=> CREATE LIBRARY DeleteVowelsLib AS
   '/home/dbadmin/DeleteVowelsLib.jar' DEPENDS '/home/dbadmin/mycompanylibs.jar'
   LANGUAGE 'JAVA';
CREATE LIBRARY
=> CREATE FUNCTION deleteVowels AS language 'java' NAME
  'com.mycompany.udx.DeleteVowelsFactory' LIBRARY DeleteVowelsLib;
CREATE FUNCTION
=> SELECT deleteVowels('I hate vowels!');
 deleteVowels
--------------
  ht vwls!
(1 row)

4.4 - Java 和 Vertica 数据类型

Vertica Java SDK 将Vertica 的原生数据类型转换为相应的 Java 数据类型。下表列出了 Vertica 数据类型及其相应的 Java 数据类型。

设置 BINARY、VARBINARY 和 LONG VARBINARY 值

Vertica BINARY、VARBINARY 和 LONG VARBINARY 数据类型将转换为 Java UDx SDK 的 VString 类。您还可以通过 PartitionWriter.setStringBytes() 方法使用 ByteBuffer 对象(或包装在 ByteBuffer 中的字节数组)来设置具有这些数据类型的列的值。有关详细信息,请参阅 PartitionWriter.setStringBytes() 的 Java API UDx 条目。

时间戳和时区

当 SDK 将 Vertica 时间戳转换为 Java 时间戳时,它使用 JVM 的时区。如果 JVM 运行的时区与 Vertica 使用的时区不同,则结果可能会令人困惑。

Vertica 以 UTC 格式将时间戳存储在数据库中。(如果设置了数据库时区,则在查询时完成转换。)为了防止来自 JVM 时区的错误,请将以下代码添加到 UDx 的处理方法中:

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

字符串

Java SDK 包含一个名为 StringUtils 的类,此类可帮助处理字符串数据。getStringBytes() 方法是此类的较有用的功能之一。此方法可以从 String 提取字节并防止创建无效字符串。如果尝试提取会将多字节 UTF-8 字符拆分为多个部分的子字符串,getStringBytes() 会将该子字符串截断为最近似的完整字符。

4.5 - 处理 NULL 值

UDx 必须准备好处理 NULL 值。这些值通常必须与正则值分开进行处理。

读取 NULL 值

UDx 将从 BlockReader 类或 PartitionReader 类的实例读取数据。如果列的值为 NULL,则用来获取数据的方法(例如 getLong)将返回 Java null 引用。如果您在未检查 NULL 的情况下尝试使用该值,Java 运行时将抛出空指针异常。

您可以在读取列之前使用特定于数据类型的方法(例如,isLongNullisDoubleNullisBooleanNull)测试 null 值。例如,要测试 UDx 输入的第一列的 INTEGER 数据类型是否为 NULL,请使用以下语句:

// See if the Long value in column 0 is a NULL
if (inputReader.isLongNull(0)) {
    // value is null
    . . .

写入 NULL 值

可以使用特定于类型的方法(例如 setLongNullsetStringNull)在 BlockWriter 类和 PartitionWriter 类上输出 NULL 值。这些方法使用列号来接收 NULL 值。此外,PartitionWriter 类具有特定于数据类型的设置值方法(例如 setLongValuesetStringValue)。如果向这些方法传递某个值,这些方法会将输出列设置为该值。如果向这些方法传递 Java null 引用,这些方法会将输出列设置为 NULL。

4.6 - 将元数据添加到 Java UDx 库

您可以将诸如作者姓名、库版本以及库描述之类的元数据添加到您的库。此元数据可以让您跟踪在 Vertica 分析数据库群集上部署的函数的版本并让您函数的第三方用户了解该函数的创建者。库加载到 Vertica 分析数据库编录之后,库的元数据会出现在 USER_LIBRARIES 系统表中。

要向 Java UDx 库添加元数据,请创建 UDXLibrary 类(包括库的元数据)的子类。然后,您可以将该类包含到 JAR 文件中。使用 CREATE LIBRARY 语句将类加载到 Vertica 分析数据库编录时,请查找 UDXLibrary 的子类以获取库的元数据。

UDXLibrary 的子类中,您需要实施八个 getter,它们将返回包含库的元数据的字符串值。此类中的 getter 如下:

  • getAuthor() 将返回您要用来与创建的库关联的名称(例如,您自己的名字或您的公司的名称)。

  • getLibraryBuildTag() 将返回您要用来表示库的特定内部版本的任何字符串(例如,SVN 修订号或编译该库时的时间戳)。开发库实例时,跟踪这些库实例很有用。

  • getLibraryVersion() 将返回库的版本。您可以使用想使用的任何编号或命名方案。

  • getLibrarySDKVersion() 将返回您为其编译了库的 Vertica 分析数据库 SDK 库的版本。

  • getSourceUrl() 将返回一个 URL,函数用户可以通过此 URL 找到有关该函数的详细信息。这可以是您公司的网站、托管库源代码的 GitHub 页面或您喜欢的任何站点。

  • getDescription() 将返回库的简要描述。

  • getLicensesRequired() 将返回用于许可信息的占位符。您必须为此值传递一个空字符串。

  • getSignature() 将返回用于签名(此签名将对库进行身份验证)的占位符。您必须为此值传递一个空字符串。

例如,以下代码演示了创建 UDXLibrary 子类并将其包含到 Add2Ints UDSF 示例 JAR 文件中(请参阅任何 Vertica 代码上的 /opt/vertica/sdk/examples/JavaUDx/ScalarFunctions)。

// Import the UDXLibrary class to hold the metadata
import com.vertica.sdk.UDXLibrary;

public class Add2IntsLibrary extends UDXLibrary
{
    // Return values for the metadata about this library.

    @Override public String getAuthor() {return "Whizzo Analytics Ltd.";}
    @Override public String getLibraryBuildTag() {return "1234";}
    @Override public String getLibraryVersion() {return "1.0";}
    @Override public String getLibrarySDKVersion() {return "7.0.0";}
    @Override public String getSourceUrl() {
        return "http://example.com/add2ints";
    }
    @Override public String getDescription() {
        return "My Awesome Add 2 Ints Library";
    }
    @Override public String getLicensesRequired() {return "";}
    @Override public String getSignature() {return "";}
}

当加载包含 Add2IntsLibrary 类的库时,元数据将显示在 USER_LIBRARIES 表中:

=> CREATE LIBRARY JavaAdd2IntsLib AS :libfile LANGUAGE 'JAVA';
CREATE LIBRARY
=> CREATE FUNCTION JavaAdd2Ints as LANGUAGE 'JAVA'  name 'com.mycompany.example.Add2IntsFactory' library JavaAdd2IntsLib;
CREATE FUNCTION
=> \x
Expanded display is on.
=> SELECT * FROM USER_LIBRARIES WHERE lib_name = 'JavaAdd2IntsLib';
-[ RECORD 1 ]-----+---------------------------------------------
schema_name       | public
lib_name          | JavaAdd2IntsLib
lib_oid           | 45035996273869844
author            | Whizzo Analytics Ltd.
owner_id          | 45035996273704962
lib_file_name     | public_JavaAdd2IntsLib_45035996273869844.jar
md5_sum           | f3bfc76791daee95e4e2c0f8a8d2737f
sdk_version       | v7.0.0-20131105
revision          | 125200
lib_build_tag     | 1234
lib_version       | 1.0
lib_sdk_version   | 7.0.0
source_url        | http://example.com/add2ints
description       | My Awesome Add 2 Ints Library
licenses_required |
signature         |

4.7 - Java UDx 资源管理

当启动时,Java 虚拟机 (JVM) 会分配固定数量的内存。此固定内存分配机制会使 Java UDx 的内存管理复杂化,因为 UDx 在处理数据时无法动态分配和释放内存。与此不同的是,C++ UDx 可以动态分配资源。

为了控制 Java UDx 所占用的内存量,Vertica 包含一个名为 jvm 的内存池,它使用该池为 JVM 分配内存。如果该内存池已用完,则在该池中有足够内存可用于启动新的 JVM 之前,调用 Java UDx 的查询会阻塞。

默认情况下,jvm 池存在以下情况:

  • 由于没有分配给自己的内存,它会从 GENERAL 池借用内存。

  • 其 MAXMEMORYSIZE 设置为系统内存的 10% 或 2 GB(以较小者为准)。

  • 其 PLANNEDCONCURRENCY 设置为 AUTO,因此它会继承 GENERAL 池的 PLANNEDCONCURRENCY 设置。

您可以通过查询 RESOURCE_POOLS 表来查看 jvm 池的当前设置:

=> SELECT MAXMEMORYSIZE,PLANNEDCONCURRENCY FROM V_CATALOG.RESOURCE_POOLS WHERE NAME = 'jvm';
 MAXMEMORYSIZE | PLANNEDCONCURRENCY
---------------+--------------------
 10%           | AUTO

当 SQL 语句调用 Java UDx 时,Vertica 会检查 jvm 内存池是否具有足够内存可用于启动新的 JVM 实例以执行函数调用。在启动每个新的 JVM 时,Vertica 会将其堆内存大小设置为大约 jvm 池的 MAXMEMORYSIZE 参数除以其 PLANNEDCONCURRENCY 参数。如果内存池不包含足够内存,则在另一个 JVM 退出并将其内存返回到池中之前,查询会阻塞。

如果 Java UDx 尝试使用的内存多于已分配给 JVM 堆大小的内存,它会退出并显示错误。您可以尝试通过以下方法解决此问题:

  • 增加 jvm 池的 MAXMEMORYSIZE 参数。

  • 减少 jvm 池的 PLANNEDCONCURRENCY 参数。

  • 更改 Java UDx 的代码以使用更少内存。

调整 jvm 池

根据需求调整 jvm 池,您必须考虑以下两个因素:

  • Java UDx 运行所需的 RAM 容量

  • 您希望数据库运行多少个并发 Java UDx 函数

您可以使用几种方法来了解 Java UDx 所需的内存数量。例如,您的代码可以使用 Java 的 Runtime 类来获取已分配的总内存的估算值并使用 ServerInterface.log() 记录该值。(此类的一个实例将传递给您的 UDx。)如果数据库中有多个 Java UDx,请基于使用最多内存的 UDx 设置 jvm 池的内存大小。

需要运行 Java UDx 的并发会话数不能与全局 PLANNEDCONCURRENCY 设置相同。例如,您可能只有一个用户运行 Java UDx,这意味着您可以将 jvm 池的 PLANNEDCONCURRENCY 设置减少至 1。

在获取 RAM 数量的估算值和需要运行 Java UDX 的并发用户会话数之后,您可以将 jvm 池调整为适当大小。将该池的 MAXMEMORYSIZE 设置为需要最多资源的 Java UDx 所需的最大 RAM 容量乘以运行 Java UDx 所需的并发用户会话数。将该池的 PLANNEDCONCURENCY 设置为运行 Java Udx 所需的并发用户会话数。

例如,假设您的 Java UDx 最多需要 4 GB 内存才能运行,并且您希望最多有两个用户会话使用 Java UDx。您应使用以下命令调整 jvm 池:

=> ALTER RESOURCE POOL jvm MAXMEMORYSIZE '8G' PLANNEDCONCURRENCY 2;

MEMORYSIZE 已设置为 8 GB,即 Java UDx 使用的最大内存 (4 GB) 乘以并发用户会话数(2 个)。

有关对 jvm 和其他资源池进行优化的详细信息,请参阅管理工作负载

释放 JVM 内存

当用户在其会话期间第一次调用 Java UDx 时,Vertica 会分配 jvm 池中的内存并启动新的 JVM。只要用户会话处于打开状态,此 JVM 就会保持运行,以便可以处理其他 Java UDx 调用。让 JVM 保持运行可以减少由同一个会话执行多个 Java Udx 所产生的开销。如果 JVM 不保持处于打开状态,则对 Java UDx 的每次调用都会导致 Vertica 需要耗费更多时间来分配资源和启动新的 JVM。但是,让 JVM 保持处于打开状态意味着无论是否会再次使用 JVM 的内存,该内存在会话的有效期内都会保持处于已分配状态。

如果减小 jvm 内存池,则包含 Java UDx 的查询会在内存变为可用之前阻塞,或者最终会由于缺少资源而失败。如果发现查询由于此原因而阻塞或失败,您可以向 jvm 池分配更多内存并增加其 PLANNEDCONCURRENCY。另一个选项是要求用户在他们不再需要运行 Java UDx 时调用 RELEASE_JVM_MEMORY 函数。此函数可关闭属于该用户会话的任何 JVM 并将其已分配的内存恢复到 jvm 内存池中。

以下示例演示了查询 V_MONITOR.SESSIONS 以确定所有会话已分配给 JVM 内存。此示例还演示了如何通过调用 Java UDx 来分配内存以及如何通过调用 RELEASE_JVM_MEMORY 来释放内存。

=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
 user_name | external_memory_kb
-----------+---------------
 dbadmin   |             0
(1 row)

=> -- Call a Java UDx
=> SELECT add2ints(123,456);
 add2ints
----------
      579
(1 row)
=> -- JVM is now running and memory is allocated to it.
=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
 USER_NAME | EXTERNAL_MEMORY_KB
-----------+---------------
 dbadmin   |         79705
(1 row)

=> -- Shut down the JVM and deallocate memory
=> SELECT RELEASE_JVM_MEMORY();
           RELEASE_JVM_MEMORY
-----------------------------------------
 Java process killed and memory released
(1 row)

=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
 USER_NAME | EXTERNAL_MEMORY_KB
-----------+---------------
 dbadmin   |             0
(1 row)

在极少数情况下,您可能需要关闭所有 JVM。例如,您可能需要为重要查询释放内存,或者 Java UDx 的多个实例可能需要太长时间才能完成。您可以使用 RELEASE_ALL_JVM_MEMORY 关闭所有用户会话中的所有 JVM:

=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
  USER_NAME  | EXTERNAL_MEMORY_KB
-------------+---------------
 ExampleUser |         79705
 dbadmin     |         79705
(2 rows)

=> SELECT RELEASE_ALL_JVM_MEMORY();
                           RELEASE_ALL_JVM_MEMORY
-----------------------------------------------------------------------------
 Close all JVM sessions command sent. Check v_monitor.sessions for progress.
(1 row)

=> SELECT USER_NAME,EXTERNAL_MEMORY_KB FROM V_MONITOR.SESSIONS;
 USER_NAME | EXTERNAL_MEMORY_KB
-----------+---------------
 dbadmin   |             0
(1 row)

注意

  • jvm 资源池仅用于为语句中的 Java UDx 函数分配内存。SQL 语句所需的其余资源来自其他内存池。

  • 第一次调用 Java UDx 时,Vertica 会启动 JVM 以执行某些 Java 方法,从而在查询规划阶段获取有关 UDx 的元数据。此 JVM 的内存也是从 jvm 内存池获取的。

5 - Python SDK

Vertica SDK 支持在 Python 3 中编写某些类型的 UDx。

Python SDK 不需要任何额外的系统配置或头文件。由于开销较低,您可以在短时间内开发新功能并将其部署到您的 Vertica 群集。

以下工作流是 Python SDK 的典型工作流:

由于 Python 含有解释器,因此您不必在 Vertica 中加载 UDx 之前编译您的程序。但是,您应该在创建函数并开始在 Vertica 中测试函数后对代码进行一些调试。

当 Vertica 调用您的 UDx 时,它会启动一个对服务器和 Python 解释器之间的交互进行管理的从属进程。

此部分涵盖适用于所有 UDx 类型且特定于 Python 的主题。有关适用于所有语言的信息,请参阅实参和返回值UDx 参数错误、警告和日志记录处理取消请求和特定 UDx 类型的章节。如需完整的 API 文档,请参阅 Python SDK

5.1 - Python 库

在可以使用 Python UDx 之前,您需要验证它是否满足以下库要求:

  • 您的 UDx 必须在代码中导入“vertica_sdk”包。您无需下载此包。它是 Vertica 服务器的一部分。
    python import vertica_sdk
  • Vertica Python SDK 包含 Python 标准库。如果您的 UDx 依赖于其他库,则必须使用 CREATE LIBRARY 将它们添加为依赖项。您不能只是简单地导入它们。

5.2 - Python 和 Vertica 数据类型

Vertica Python SDK 会将原生 Vertica 数据类型转换为相应的 Python 数据类型。下表描述了一些数据类型转换。有关完整列表以及用于转换和处理这些数据类型的 helper 函数的列表,请参阅 Python SDK

有关 SDK 支持的复杂数据类型的信息,请参阅作为实参的复杂类型和返回值

6 - R SDK

Vertica R SDK 扩展了 Vertica 分析数据库的功能,因此您可以利用其他 R 库。开始在 R 中开发用户定义的扩展 (UDx) 之前,您必须在群集中的每个节点上安装适用于 Vertica 的 R 语言包。R SDK 在隔离模式下支持标量和转换函数。其他 UDx 类型不受支持。

以下工作流是 R SDK 的典型工作流:

您可以在 Vertica R SDK 中找到所有类的详细文档。

6.1 - 安装/升级 Vertica 的 R 语言包

要在 Vertica 中创建 R UDx,请安装与您的服务器版本匹配的 R 语言包。R 语言包含有用来与 Vertica 交互的 R 运行时和关联库。您必须使用此版本的 R 运行时;不能升级它。

您必须在群集中的每个节点上安装 R 语言包。Vertica R 语言包必须是节点上安装的唯一 R 语言包。

Vertica R 语言包先决条件

R 语言包需要许多包才能进行安装和执行。这些依赖项的名称因 Linux 发行版而异。对于支持 Vertica 的 Linux 平台,这些包为:

  • RHEL/CentOS:libfortranxz-libslibgomp

  • SUSE Linux Enterprise Server:libfortran3liblzma5libgomp1

  • Debian/Ubuntu:libfortran3liblzma5libgomp1

  • Amazon Linux 2.0:compat-gcc-48-libgfortranxz-libslibgomp

Vertica 需要高于 7.1 的 libgfortran4 库版本才能创建 R 扩展。libgfortran 库默认包含在 devtoolgcc 包中。

安装 Vertica R 语言包

如果您使用操作系统包管理器而不是 rpm 或 dpkg 命令进行安装,则无需手动安装 R 语言包。适用于每个受支持的 Linux 版本的本机包管理器为:

  • RHEL/CentOS:yum

  • SUSE Linux Enterprise Server:zypper

  • Debian/Ubuntu:apt-get

  • Amazon Linux 2.0:yum

  1. 通过浏览 Vertica 网站,下载 R 语言包。

  2. 支持 (Support) 选项卡上,选择客户下载 (Customer Downloads)

  3. 在系统出现提示时,使用您的 Micro Focus 凭据登录。

  4. 找到并选择适用于您所用的服务器版本的 vertica-R-lang_version.rpmvertica-R-lang_version.deb 文件。R 语言包版本必须与服务器版本在三个小数点上匹配。

  5. 以 root 身份或使用 sudo 安装包:

    • RHEL/CentOS

      $ yum install vertica-R-lang-<version>.rpm
      
    • SUSE Linux Enterprise Server

      $ zypper install vertica-R-lang-<version>.rpm
      
    • Debian

      $ apt-get install ./vertica-R-lang_<version>.deb
      
    • Amazon Linux 2.0

       $ yum install vertica-R-lang-<version>.AMZN.rpm
      

安装程序会将 R 二进制文件放入 /opt/vertica/R

升级 Vertica R 语言包

升级时,您已手动安装的某些 R 包可能无法正常工作且可能需要重新安装。如果不更新包,R 将在包无法使用时返回错误。升级这些包的说明如下所述。

  1. 在升级 Vertica 之前,您必须卸载 R 语言包。卸载语言包时,已手动安装的所有其他 R 包会一直留在 /opt/vertica/R 中,而不会被移除。

  2. 按照将 Vertica 升级到新版本中的详细说明升级您的服务器包。

  3. 更新服务器包后,在每个主机上安装新的 R 语言包。

如果已在每个节点上安装了其他 R 包:

  1. 以 root 身份运行 /opt/vertica/R/bin/R 并执行以下命令:

    > update.packages(checkBuilt=TRUE)
    
  2. 从显示的列表中选择 CRAN 镜像。

  3. 系统会提示您更新具有可用更新的每个包。您必须更新已手动安装且与 R 语言包中的当前 R 版本不兼容的所有包。
    更新:

    • Rcpp

    • Rinside

    此时即会安装选择进行更新的包。使用以下命令退出 R:

    > quit()
    

使用 R 语言编写的 Vertica UDx 函数无需编译,而且升级后无需重新加载 Vertica-R 库和函数。

6.2 - R 包

除了与 R 捆绑在一起的默认包之外,Vertica R 语言包还包含以下 R 包:

  • Rcpp

  • RInside

  • IpSolve

  • lpSolveAPI

您可以使用以下两种方法之一来安装不包含在 Vertica R 语言包中的其他 R 包。您必须在所有节点上安装相同的包。

安装 R 包

您可以使用以下两种方法之一安装其他 R 包。

使用 install.packages() R 命令:

$ sudo /opt/vertica/R/bin/R
> install.packages("Zelig");

使用 CMD INSTALL:

/opt/vertica/R/bin/R CMD INSTALL <path-to-package-tgz>

安装的包位于:/opt/vertica/R/library

6.3 - R 和 Vertica 数据类型

将数据传递到 R UDx 或从中传递数据时,支持以下数据类型:

发送到 R 函数时,Vertica 中的 NULL 值会转换为 R NA 值。当从 R 函数返回到 Vertica 时,R NA 值会转换为 Vertica null 值。

6.4 - 将元数据添加到 R 库

您可以将诸如作者姓名、库版本以及库描述之类的元数据添加到您的库。此元数据可以让您跟踪在 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 库的版本。

  • source_url 为函数用户可从中查找函数详细信息的 URL。这可以是您公司的网站、托管库源代码的 GitHub 页面或您喜欢的任何站点。

  • description 为库的简要描述。

  • licenses_required 为许可信息占位符。您必须为此值传递一个空字符串。

  • signature 为对库进行身份验证的签名的占位符。您必须为此值传递一个空字符串。

以下示例显示如何将元数据添加到 R UDx。


RegisterLibrary("Speedy Analytics Ltd.",
                "1234",
                "1.0",
                "8.1.0",
                "http://www.example.com/sales_tax_calculator.R",
                "Sales Tax R Library",
                "",
                "")

加载库并查询 USER_LIBRARIES 系统表将显示调用 RegisterLibrary 时提供的元数据:

=> CREATE LIBRARY rLib AS '/home/dbadmin/sales_tax_calculator.R' LANGUAGE 'R';
CREATE LIBRARY
=> SELECT * FROM USER_LIBRARIES WHERE lib_name = 'rLib';
-[ RECORD 1 ]-----+---------------------------------------------------------
schema_name       | public
lib_name          | rLib
lib_oid           | 45035996273708350
author            | Speedy Analytics Ltd.
owner_id          | 45035996273704962
lib_file_name     | rLib_02552872a35d9352b4907d3fcd03cf9700a0000000000d3e.R
md5_sum           | 30da555537c4d93c352775e4f31332d2
sdk_version       |
revision          |
lib_build_tag     | 1234
lib_version       | 1.0
lib_sdk_version   | 8.1.0
source_url        | http://www.example.com/sales_tax_calculator.R
description       | Sales Tax R Library
licenses_required |
signature         |
dependencies      |
is_valid          | t
sal_storage_id    | 02552872a35d9352b4907d3fcd03cf9700a0000000000d3e

6.5 - 为 R 函数设置 null 输入和可变性行为

Vertica 支持为采用 R 编写的 UDx 定义可变性设置和 null 输入设置。这两项设置都有助于提高 R 函数的性能。

可变性设置

可变性设置向 Vertica 优化器描述了该函数的行为。例如,如果输入数据有相同的行,并且您知道 UDx 是不可变的,则可以将 UDx 定义为 IMMUTABLE。这样即可告知 Vertica 优化器,它可以返回对其调用该函数的后续相同行的缓存值,而不是对每个相同的行都运行该函数。

要指示 UDx 的可变性,请将 R 工厂函数的 volatility 参数设置为以下值之一:

如果未定义可变性,则函数会被视为 VOLATILE。

以下示例会在 multiplyTwoIntsFactory 函数中将可变性设置为 STABLE:

multiplyTwoIntsFactory <- function() {
  list(name                  = multiplyTwoInts,
       udxtype               = c("scalar"),
       intype                = c("float","float"),
       outtype               = c("float"),
       volatility            = c("stable"),
       parametertypecallback = multiplyTwoIntsParameters)
}

Null 输入行为

Null 输入设置决定如何响应包含 null 输入的行。例如,您可以选择在任何输入为 null 时返回 null,而不调用函数并让函数处理 NULL 输入。

要指示 UDx 如何响应 NULL 输入,请将 R 工厂函数的 strictness 参数设置为以下值之一:

如果未定义 null 输入行为,则无论是否存在 NULL 值,系统都会对每行数据调用该函数。

以下示例会在 multiplyTwoIntsFactory 函数中将 NULL 输入行为设置为 STRICT:

multiplyTwoIntsFactory <- function() {
  list(name                  = multiplyTwoInts,
       udxtype               = c("scalar"),
       intype                = c("float","float"),
       outtype               = c("float"),
       strictness            = c("strict"),
       parametertypecallback = multiplyTwoIntsParameters)
}

7 - 调试提示

以下提示可帮助您在将 UDx 部署到生产环境之前对其进行调试。

使用单个节点进行初始调试

可以使用诸如 gdb 等调试器连接到 Vertica 进程以调试 UDx 代码。但是,在多节点环境中难以执行此调试。因此,请考虑设置单节点 Vertica 测试环境以对 UDx 进行初始调试。

使用日志记录

每个 UDx 都有一个关联的 ServerInterface 实例。ServerInterface 提供了要写入 Vertica 日志和写入系统表(仅在 C++ API 中)的函数。有关详细信息,请参阅日志