Skip to content

Testing a UDA

Introduction

User-Defined Analytics (UDAs) are custom functions in kdb+ designed for complex data aggregation across multiple sources. Thorough testing and debugging of UDAs is essential to ensure they function correctly. This page provides a step-by-step approach to testing and debugging query and aggregation functions within a UDA, helping you identify and resolve errors to ensure accurate and efficient data processing.

This page covers the following sections:

  • Testing query functions

  • Testing aggregation functions

Testing query functions

When creating a query, you may encounter bugs that require investigation and debugging. The easiest way to debug an issue is to attach to a DAP and run the code directly, with error trapping enabled. For more information on error trapping, refer to the error trap clients section.

To test query functions you can also use the Q query option in the Query Window. For more information on using the Query Window, refer to the Using a Q based query section.

Step 1 - Attach to the DAP process

  1. Attach to a DAP to run the code directly. This approach enables real-time debugging with error trapping.

  2. Enable error trapping by running the following commands:

   q).kxi.pe.disable[]  / Disable protected evaluation
   q)\e 1               / Enable kdb+ error trapping

Step 2 - Run the query and analyze the logs

  1. If a query runs through the framework and encounters an error, but the framework catches and moves past it, review the DAP logs.

  2. For example, consider the following query function, which generates a type error:

.custom.countBy:{[table;startTS;endTS;byCols]
    1+`
    }
3. After calling this API, the DAP logs provide the following output:

2024-08-12 15:45:30.146 [hdb] DEBUG KXCTX {23329698-9642-4654-b65e-81bfefe16b05} Starting context, corr=23329698-9642-4654-b65e-81bfefe16b05
2024-08-12 15:45:30.146 [hdb] DEBUG KXCTX {23329698-9642-4654-b65e-81bfefe16b05} Setting context to no-save, reason='requests not saved in DAP'
2024-08-12 15:45:30.146 [hdb] DEBUG DA {23329698-9642-4654-b65e-81bfefe16b05} Executing .custom.countBy
2024-08-12 15:45:30.146 [hdb] ERROR SAPI {23329698-9642-4654-b65e-81bfefe16b05} Error (type) encountered executing .custom.countBy, rc=6 ac=11 ai=Unexpected error (type) encountered executing .custom.countBy
2024-08-12 15:45:30.146 [hdb] DEBUG DA {23329698-9642-4654-b65e-81bfefe16b05} Completed .custom.countBy, rc=6 ac=11 ai=Unexpected error (type) encountered executing .custom.countBy
2024-08-12 15:45:30.146 [hdb] DEBUG DA {23329698-9642-4654-b65e-81bfefe16b05} Sending response to aggregator, agg=:10.244.0.9:5070
2024-08-12 15:45:30.147 [hdb] DEBUG KXCTX {23329698-9642-4654-b65e-81bfefe16b05} Ending context, no-save
  1. The log indicates a type error, which helps identify that the issue is related to data types.

Step 3 - Enable detailed debugging

  1. To pinpoint the exact location of the failure, re-run the query while connected to the process with error trapping enabled This approach generates more detailed debugging output.

  2. Review the detailed output:

q)2024-08-12 15:51:51.154 [hdb] DEBUG KXCTX {ba62e0b9-f290-4abd-be3d-dcc91cb6116d} Starting context, corr=ba62e0b9-f290-4abd-be3d-dcc91cb6116d
2024-08-12 15:51:51.154 [hdb] DEBUG KXCTX {ba62e0b9-f290-4abd-be3d-dcc91cb6116d} Setting context to no-save, reason='requests not saved in DAP'
2024-08-12 15:51:51.154 [hdb] DEBUG DA {ba62e0b9-f290-4abd-be3d-dcc91cb6116d} Executing .custom.countBy
type error
.custom.countBy[0]   {[table;startTS;endTS;byCols]1+`}
                                                   ^
q))table / See what current table argument is
`trade
q))startTS / See what current startTS argument is
-0Wp
  1. Based on the output you can see specifically where the function is failing. Additionally, you can inspect the current values of the arguments. For larger functions, you can also run through the code line by line to identify the underlying issue.

Testing aggregation functions

When creating a UDA, it is essential to develop an aggregation function that consolidates partial results from multiple DAPs. If a bug exists in the aggregation logic, identifying the root cause can be challenging when an aggregation fails.

To assist in debugging, there are options available to have the aggregator return the partial results of a failed aggregation back to the caller. This allows for debugging within the same session. When the partial results are returned, the response includes an application code (ac) in the header, describing the type of aggregation failure, and an rc value of 100h (PARTIALS).

If the partialsSent field is present and set to true, the payload will consist solely of the partial results from each DAP involved in the request. By default, partial results are not sent when an aggregation fails. However, you can configure the aggregator to send back partial results using two methods:

  • Set the sendPartials value in the request header

  • Configure the aggregator to always send partial results

Step 1 - Enable partial results on aggregation failure

To debug failed aggregations, configure the aggregator to return partial results:

  1. Option 1 - Set the sendPartials value in the request header

    • Include the sendPartials field in the request header sent to the gateway. This approach ensures that the aggregator returns partial results if an aggregation failure occurs.

    q)GATEWAY:10i / Handle to gateway
    q)args:enlist[`table]!enlist `trade
    q)GATEWAY (`.kxi.getData; args;`ignored;enlist[`sendPartials]!enlist 1b)
    
    2. Option 2 - Configure the aggregator to always send partial results

    • Set the environment variable KXI_SEND_PARTIALS to true on the aggregator. This configuration ensures that any failed aggregation request automatically sends partial results back in the payload.

Step 2 - Analyze the aggregator's response

If sendPartials is enabled (either through the KXI_SEND_PARTIALS environment variable or through the opts of a request) and the request errors in the aggregator, the response contains partial results. The response format depends on the specific error encountered:

  1. Return Code = PARTIALS (100)

    • The payload is a list of partial responses from each DAP that participated in the request:
    q)`rc`ac`ai#response 0
    rc| 100h
    ac| 30h
    ai| "Unexpected error (mismatch) encountered aggregating myAPI"
    q)response 1
    +(,`x)!,1 2
    +(,`y)!,3 4
    
  2. Return Code = PARTIALS_SUB (101)

    • If one or more sub-requests fail, the payload is a table that includes the response header and payload of each failed sub-request.
  3. Return Code = PARTIALS_RESUME (102)

    • The payload is a dictionary that contains partials, context, and callback information. <!-- TODO: Add an example to explain nested API calls>