Skip to content

Operate RelationalAI

The app runs as a service in your Snowflake account that you start, monitor, and stop.

When you start the app:

  • The RAI Native App service in Snowflake is turned on so users can run PyRel programs.
  • The app’s compute pools are provisioned and reasoners are made available to evaluate queries from semantic models.

Activation and the CDC service are separate:

  • On first activation, CDC stays disabled. Enable it explicitly with app.resume_cdc(). See Enable change data capture in the setup guide.
  • On reactivation of a previously deactivated app, CDC returns to its earlier state (enabled or suspended); you do not need to resume it again.

When you stop the app, it suspends the app’s CDC service, deletes all reasoners, and cancels in-progress jobs. Stopping the app does not uninstall it from your Snowflake account, so you can start it again without reinstalling. No data is lost when you stop the app.

Select the tab for your preferred language to see how to start the RAI Native App.

Use the relationalai.app.activate() procedure:

CALL relationalai.app.activate();
Output
+----------------------------------------------+
| RelationalAI service activated successfully. |
| The service may still be starting up; check. |
| that the service is running by calling the. |
| APP.SERVICE_STATUS() procedure to check for |
| the READY status. |
+----------------------------------------------+

If activation returns an error, see Fix app activation errors caused by Snowflake account settings.

Use the app’s status procedures to confirm whether the RAI Native App is ready to use, stopped, or still starting.

Use app.service_status() to check whether the RAI service containers are running and ready:

Call the app.service_status() procedure:

CALL relationalai.app.service_status();
Output
+----------------------------------------------+
| [ |
| { |
| "message": "Running", |
| "name": "main", |
| "restartCount": 0, |
| "startTime": "2024-10-27T22:10:05Z", |
| "status": "READY" |
| }, |
| { |
| "message": "Running", |
| "name": "otel-collector", |
| "restartCount": 0, |
| "startTime": "2024-10-27T22:10:07Z", |
| "status": "READY" |
| }, |
| { |
| "message": "Running", |
| "name": "registry", |
| "restartCount": 0, |
| "startTime": "2024-10-27T22:10:06Z", |
| "status": "READY" |
| } |
| ] |
+----------------------------------------------+
  • If each container’s status is READY, the service is activated and ready to use.
  • If one or more containers report a status such as SUSPENDED or SUSPENDING, the service is stopped and not ready to use.
  • If the call to app.service_status() fails, the service may not exist, which can occur after a deeper shutdown that drops the service instead of just suspending it.

You can stop the RAI Native App in order to reduce costs during periods of inactivity. When the app is stopped, the app’s SPCS service can either be suspended or fully dropped, depending on the shutdown method used and how long ago the shutdown occurred.

Follow these steps to check whether the service has been suspended or dropped:

  1. Check whether the service still exists

    Run app.get_service():

    CALL relationalai.app.get_service();

    This step tells you whether Snowflake still has a managed app service to inspect.

    • If app.get_service() returns service metadata, the service still exists.
    • If app.get_service() shows no service metadata, the service was removed during a deeper shutdown and must be created again before it can start.
  2. Check whether the existing service is stopped or running

    If the service still exists, run app.service_status():

    CALL relationalai.app.service_status();

    This step tells you whether the existing service is stopped, still starting, or ready to use.

    If the service still exists but is stopped, app.service_status() can return output like this:

    Example
    +----------------------------------------------+
    | [ |
    | { |
    | "message": "Suspended", |
    | "name": "main", |
    | "restartCount": 0, |
    | "startTime": "2024-10-27T22:10:05Z", |
    | "status": "SUSPENDED" |
    | }, |
    | { |
    | "message": "Suspended", |
    | "name": "otel-collector", |
    | "restartCount": 0, |
    | "startTime": "2024-10-27T22:10:07Z", |
    | "status": "SUSPENDED" |
    | }, |
    | { |
    | "message": "Suspended", |
    | "name": "registry", |
    | "restartCount": 0, |
    | "startTime": "2024-10-27T22:10:06Z", |
    | "status": "SUSPENDED" |
    | } |
    | ] |
    +----------------------------------------------+
    • If app.service_status() returns container entries with a status such as SUSPENDED or SUSPENDING, the service exists but is stopped and not ready to use.
    • If each container reports READY, the service is running rather than stopped.
    • If the service was dropped during a deeper shutdown, app.service_status() may fail because there is no service to inspect.

Check whether startup is still in progress

Section titled “Check whether startup is still in progress”

Use app.service_status() when the app was recently started and you want to see whether Snowflake is still bringing the service online:

Call the app.service_status() procedure:

CALL relationalai.app.service_status();
Possible
+-----------------------------------+
| [ |
| { |
| "message": null, |
| "name": null, |
| "restartCount": null, |
| "startTime": null, |
| "status": "PENDING" |
| } |
| ] |
+-----------------------------------+
  • Look for status = PENDING to confirm that startup is still in progress.
  • While the service is still PENDING, Snowflake can return NULL for name, message, restartCount, and startTime because the service containers are not ready yet.
  • Keep calling app.service_status() until the returned status changes to READY.

Monitor the app with observability metrics from Snowflake’s Event Table, and use the app’s logs when you need more detail.

Requires the observability_admin application role and SELECT access on the active Snowflake Event Table.

Enable observability by registering a secure view over Snowflake’s Event Table. Once registered, the app reads metrics through that view and makes them available in the relationalai.observability_preview schema, such as reasoner memory, CPU, and workload demand.

The Native App uses Snowflake’s Event Table to report internal metrics about app and reasoner usage, including memory, CPU, and demand. To access those metrics, you create a secure view that filters the Event Table for relevant app-shared metric rows, then register that view with the app so it can read the metrics and make them available in the relationalai.observability_preview schema. Registering this view also enables a pipeline that shares telemetry generated by deployed models with RelationalAI as the app provider.

Register a secure events view with the RAI Native App

Section titled “Register a secure events view with the RAI Native App”
  1. Find the active Event Table

    Use the account setting first so the rest of the SQL points at the right table.

    SHOW PARAMETERS LIKE 'EVENT_TABLE' IN ACCOUNT;
    SHOW TABLES LIKE 'EVENTS' IN SCHEMA SNOWFLAKE.TELEMETRY;
  2. Enable change tracking if needed

    Registration fails if change tracking is disabled on the Event Table.

    ALTER TABLE SNOWFLAKE.TELEMETRY.EVENTS
    SET CHANGE_TRACKING = TRUE;
  3. Create the filtered secure view

    Create the view in a database and schema that you own. The filter keeps the view limited to RAI database rows and rows explicitly flagged for sharing with RAI.

    CREATE DATABASE IF NOT EXISTS MONITORING;
    -- Change the schema name if needed.
    CREATE SCHEMA IF NOT EXISTS MONITORING.RAI_OBSERVABILITY;
    CREATE OR REPLACE SECURE VIEW MONITORING.RAI_OBSERVABILITY.RAI_OBS
    COMMENT = 'RelationalAI Native App observability filtered events view'
    CHANGE_TRACKING = TRUE
    AS
    SELECT
    TIMESTAMP,
    START_TIMESTAMP,
    RECORD_TYPE,
    RECORD_ATTRIBUTES,
    RECORD,
    TRACE,
    VALUE,
    RESOURCE_ATTRIBUTES['snow.executable.name'] as EXECUTABLE_NAME
    FROM SNOWFLAKE.TELEMETRY.EVENTS
    WHERE (resource_attributes['snow.database.name']::STRING = 'RELATIONALAI'
    OR record_attributes['rai_share']::BOOLEAN = TRUE);
  4. Register the view with the app

    Registration stores a persistent reference that the app uses to read the filtered telemetry.

    CALL RELATIONALAI.app.ENABLE_OBSERVABILITY(
    SYSTEM$REFERENCE(
    'view',
    'MONITORING.RAI_OBSERVABILITY.RAI_OBS',
    'PERSISTENT',
    'SELECT'
    )
    );
  5. Validate the registration

    Do not continue until the status check is healthy.

    CALL RELATIONALAI.app.CHECK_EVENTS_VIEW_STATUS();

    Events view active means the configuration is valid and events are flowing. No events view registered means setup is incomplete. ERROR means the registration exists but the app cannot use it yet.

The RAI Native App writes information to different Snowflake surfaces:

LocationWhat it is used for
Active Snowflake Event TableContinuous app telemetry and observability metrics. This is the main source for routine monitoring.
RELATIONALAI.LOGGING.LOGSInternal application log records. This table gives you direct access to recent log entries, but it is not intended for end-user consumption and should be treated as potentially sensitive operational data.
RELATIONALAI.APP_STATE.CLIENT_LOG_STAGEFiltered support-share files used when RelationalAI asks for deeper diagnostics. See Share logs with RelationalAI support for details.

You do not need to query these surfaces directly for routine monitoring or troubleshooting. Refer to the following resources for observability and monitoring guidance.

The Native App collects different kinds of operational data for different purposes: Event Table telemetry gives you high-level health and usage signals for monitoring, LOGGING.LOGS contains raw internal log messages and metadata for deeper diagnosis, and the support-share workflow produces a filtered subset of engine and optional control-plane logs for a specific incident or time window.

Use this section when the app doesn’t start or when RelationalAI support asks for deeper diagnostics.

Fix app activation errors caused by Snowflake account settings

Section titled “Fix app activation errors caused by Snowflake account settings”

If CALL relationalai.app.activate(); returns an error instead of starting the app, a Snowflake account setting may be blocking it. The most common cause is the account parameter PREVENT_UNLOAD_TO_INTERNAL_STAGES. When this parameter is enabled, activation fails with an error saying that PREVENT_UNLOAD_TO_INTERNAL_STAGES is stopping the RAI Native App from writing to its internal stage.

An internal stage is a temporary storage area inside Snowflake. When PREVENT_UNLOAD_TO_INTERNAL_STAGES is TRUE, the app can’t write to its own internal stage. This blocks the CDC service and data streams that load your data, so activation stops instead of starting the app in a broken state.

To fix the error, complete one of the options below, then run CALL relationalai.app.activate(); again.

What to doWhen to do it
Enable internal stage unloads for the RAI app onlyYou want to keep PREVENT_UNLOAD_TO_INTERNAL_STAGES enabled for the rest of your account.
Disable the setting account-wideYou can disable the parameter for the whole account.

After activation succeeds, confirm the app is ready by following Check that the RAI service is running.

Enable internal stage unloads for the RAI app only

Section titled “Enable internal stage unloads for the RAI app only”

This is Snowflake’s recommended approach. It applies only to the app’s own stages and doesn’t change PREVENT_UNLOAD_TO_INTERNAL_STAGES for the rest of your account, so your account-wide protection stays in place.

  1. Open the RelationalAI app in the Snowflake web interface.

    Sign in to the Snowflake web interface (Snowsight) and open the installed RelationalAI Native App.

  2. Approve the internal stage unload request.

    Review the app’s request to enable internal stage unloads (the ENABLE_RAI_INTERNAL_STAGE_UNLOAD specification) and approve it. For the exact steps, see Review specifications for an app in the Snowflake documentation.

Disable PREVENT_UNLOAD_TO_INTERNAL_STAGES account-wide

Section titled “Disable PREVENT_UNLOAD_TO_INTERNAL_STAGES account-wide”

Ask an ACCOUNTADMIN to disable the parameter for the whole account:

ALTER ACCOUNT SET PREVENT_UNLOAD_TO_INTERNAL_STAGES = FALSE;

After activation succeeds, confirm the app is ready by following Check that the RAI service is running.

Requires the ACCOUNTADMIN role and permission to create and run Snowflake Notebooks in Snowsight.

Use on-demand logs when RelationalAI asks for deeper diagnostics for a specific time window, engine, or incident. This workflow shares a filtered set of staged logs instead of exposing your full telemetry history.

Do the following to create the secure share and copy the requested logs:

  1. Download and import the notebook into Snowflake

    Download the on-demand logs notebook.

    In Snowsight, choose + Create Projects > Notebook > Import .ipynb File, then upload the notebook. If you want to inspect the workflow before importing it, use the embedded preview below.

    View a preview of the notebook.

    Sharing On-Demand Logs with RAI

    Overview

    This notebook creates and shares a secure-share view with RAI to provide access to on-demand logs. It’s designed to run as a Snowflake Notebook, but it can be adapted to run as a local Jupyter notebook by replacing the call to get_active_session() with code to generate a Session object locally. See Snowflake’s documentation for details.

    from datetime import datetime, timedelta, date
    def get_date_range(start_date, end_date):
    start = datetime.strptime(start_date, "%Y-%m-%d")
    end = datetime.strptime(end_date, "%Y-%m-%d")
    dates = [(start + timedelta(days=i)).strftime("%Y-%m-%d")
    for i in range((end - start).days + 1)]
    return "|".join(dates)

    Edit this cell with your specific values:

    # Override this to the date you want to get logs starting from in the format YYYY-MM-DD:
    start_date = date.today().strftime("%Y-%m-%d")
    # Override this to the date you want to get logs until in the format YYYY-MM-DD
    end_date = date.today().strftime("%Y-%m-%d")
    date_range = get_date_range(start_date, end_date)
    # Override this to True if you want to share spcs_control_plane logs
    include_erps_logs = False
    # Override this to the engine name (ex, 'testEngine') or leave it as it is if you want to get logs for all engines
    engine_name = ".*"
    warehouse = ""
    # Override this to a unique id and share it with RAI
    id = ""
    # The account you want to share the logs with -- it should be in the format org.account_name
    event_sharing_account = ""
    # Your native app name (usually relationalai)
    native_app_name = "relationalai"
    engine_file_pattern = f'{engine_name}/clientlogs-.*({date_range}).*\\.json|{engine_name}/clientlogs-engine.json'
    erp_file_pattern = f'|clientlogs-cp-({date_range}).*\\.json|.*clientlogs-cp.json'
    if include_erps_logs:
    file_pattern = f'.*({engine_file_pattern}{erp_file_pattern}).*'
    else:
    file_pattern = f'.*({engine_file_pattern}).*'
    sql_query = """
    BEGIN
    USE ROLE ACCOUNTADMIN;
    USE WAREHOUSE &warehouse; -- Update to use another warehouse if necessary.
    CREATE DATABASE IF NOT EXISTS TELEMETRY_SHARING;
    USE DATABASE TELEMETRY_SHARING;
    CREATE SCHEMA IF NOT EXISTS LOGS;
    USE SCHEMA LOGS;
    --*****--
    -- Load staged data files to temporary tables
    --*****--
    CREATE OR REPLACE TABLE TELEMETRY_SHARING.LOGS.TELEMETRY_LOAD_TABLE_&id (
    LOG_RECORD VARCHAR
    );
    CREATE OR REPLACE TABLE TELEMETRY_SHARING.LOGS.TELEMETRY_SHARE_TABLE_&id (
    TIMESTAMP TIMESTAMP,
    OBSERVED_TIMESTAMP TIMESTAMP,
    SPAN_ID VARCHAR,
    TRACE_ID VARCHAR,
    MESSAGE VARCHAR,
    LOG_RECORD VARCHAR
    );
    --*****--
    -- Create secure view from table with target log records
    --*****--
    CREATE OR REPLACE SECURE VIEW TELEMETRY_SHARING.LOGS.TELEMETRY_SHARE_VIEW_&id
    COMMENT = 'View containing telemetry records to share with the RAI provider account'
    AS
    SELECT *
    FROM TELEMETRY_SHARING.LOGS.TELEMETRY_SHARE_TABLE_&id ;
    --*****--
    -- Share secure view with the RAI provider account
    --*****--
    CREATE OR REPLACE SHARE TELEMETRY_SHARE_&id;
    CREATE DATABASE ROLE IF NOT EXISTS TELEMETRY_SHARE_ROLE;
    GRANT USAGE ON DATABASE TELEMETRY_SHARING TO SHARE TELEMETRY_SHARE_&id;
    GRANT USAGE ON SCHEMA TELEMETRY_SHARING.LOGS TO DATABASE ROLE TELEMETRY_SHARE_ROLE;
    GRANT SELECT ON VIEW TELEMETRY_SHARE_VIEW_&id TO DATABASE ROLE TELEMETRY_SHARE_ROLE;
    GRANT DATABASE ROLE TELEMETRY_SHARE_ROLE TO SHARE TELEMETRY_SHARE_&id;
    ALTER SHARE TELEMETRY_SHARE_&id ADD ACCOUNTS = &event_sharing_account;
    COPY INTO TELEMETRY_SHARING.LOGS.TELEMETRY_LOAD_TABLE_&id
    FROM (
    SELECT
    $1 AS log_record
    FROM @&native_app_name.app_state.client_log_stage
    )
    PATTERN = '&file_pattern'
    FILE_FORMAT = (TYPE = JSON)
    ON_ERROR = CONTINUE; -- This will skip any log records that are invalid JSON.
    -- The output of the query will indicate if any records were skipped due to errors.
    -- Copy from TELEMETRY_LOAD_TABLE_&id into TELEMETRY_SHARE_TABLE_&id and remove safe logs
    INSERT INTO TELEMETRY_SHARING.LOGS.TELEMETRY_SHARE_TABLE_&id
    SELECT
    to_timestamp(timeUnixNano) as timestamp,
    to_timestamp(observedTimeUnixNano) as observed_timestamp,
    spanId,
    traceId,
    a.value:value:stringValue as message,
    log_record
    FROM (SELECT
    value:timeUnixNano as timeUnixNano,
    value:observedTimeUnixNano as observedTimeUnixNano,
    value:spanId as spanId,
    value:traceId as traceId,
    value as log_record
    FROM TELEMETRY_SHARING.LOGS.TELEMETRY_LOAD_TABLE_&id, LATERAL FLATTEN( INPUT => TRY_PARSE_JSON($1):resourceLogs[0]:scopeLogs[0]:logRecords, OUTER => TRUE )),
    LATERAL FLATTEN( INPUT => log_record:body:kvlistValue:values, OUTER => TRUE) a, LATERAL FLATTEN( INPUT => log_record:attributes, OUTER => TRUE) b
    WHERE a.VALUE:key = 'message'
    and
    (
    ( -- engine unsafe logs
    b.value:key = 'log.file.name'
    and b.value:value:stringValue ='engine-unsafe.log'
    )
    or
    (
    -- erps unsafe logs
    log_record not like '%___safe_to_log%'
    and log_record not like '%engine-safe.log%'
    and log_record like '%spcs_control_plane%'
    )
    );
    END;
    """
    sql_query = (
    sql_query
    .replace('&warehouse', warehouse)
    .replace('&id', id)
    .replace('&event_sharing_account', event_sharing_account)
    .replace('&date_range', date_range)
    .replace('&native_app_name', native_app_name)
    .replace('&file_pattern', file_pattern)
    )
    # if running as a Snowflake notebook:
    session = get_active_session()
    # # if running as a local Jupyter notebook:
    # from snowflake.snowpark import Session
    #
    # connection_parameters = {
    # "account": "<your snowflake account>",
    # "user": "<your snowflake user>",
    # "password": "<your snowflake password>",
    # "role": "<your snowflake role>", # optional
    # "warehouse": "<your snowflake warehouse>", # optional
    # "database": "<your snowflake database>", # optional
    # "schema": "<your snowflake schema>", # optional
    # }
    #
    # session = Session.builder.configs(connection_parameters).create()
    session.sql(sql_query).collect()

    Run the following cell to confirm that the logs are copied to the secure share:

    session.sql("USE ROLE ACCOUNTADMIN").collect()
    session.sql(f"USE WAREHOUSE {warehouse}").collect()
    query = f"SELECT * FROM TELEMETRY_SHARING.LOGS.TELEMETRY_SHARE_TABLE_{id} LIMIT 5"
    session.sql(query).collect()
  2. Set the notebook inputs

    Before you update the configuration cell, gather the values that control which logs are copied and where the secure share is sent:

    InputWhat to decide
    start_date and end_dateThe date range to include in the log share.
    engine_nameA specific engine (i.e., reasoner) name, or .* if RelationalAI asked for logs across all engines.
    include_erps_logsWhether to include spcs_control_plane logs in addition to engine logs.
    warehouseThe Snowflake warehouse the notebook should use while building the share.
    idA unique identifier for this share so the created tables, view, and share do not collide with another run.
    event_sharing_accountThe target Snowflake account name that RelationalAI gives you for the secure share.
    native_app_nameThe installed app name, which is usually relationalai.

    Then update the configuration cell with the scope and sharing details you prepared:

    start_date = "YYYY-MM-DD"
    end_date = "YYYY-MM-DD"
    include_erps_logs = False
    engine_name = ".*"
    warehouse = "<warehouse_name>"
    id = "<unique_share_id>"
    event_sharing_account = "<org.account_name>"
    native_app_name = "relationalai"
  3. Run the notebook to create the secure share

    Run the notebook cells in order. The workflow computes the file pattern for your requested date range, creates the TELEMETRY_SHARING.LOGS objects, copies matching files from the app stage, filters the records, and grants the resulting secure share to the target account.

  4. Verify that the share contains the requested logs

    Run the verification cell at the end of the notebook, or run the equivalent query below:

    session.sql("USE ROLE ACCOUNTADMIN").collect()
    session.sql(f"USE WAREHOUSE {warehouse}").collect()
    query = f"SELECT * FROM TELEMETRY_SHARING.LOGS.TELEMETRY_SHARE_TABLE_{id} LIMIT 5"
    session.sql(query).collect()

    If the query returns rows, the filtered logs were copied into the share table and the secure share is populated.

Stop the app when you are done using it or want to reduce ongoing costs while it is idle.

There are three shutdown modes that achieve different levels of cost reduction by dropping different sets of resources. All modes suspend the app’s CDC service, delete all reasoners, and cancel in-progress jobs. The difference is whether the app service, app warehouse, and compute pools are only suspended or are dropped and recreated the next time you start the app.

Use the following table to choose a shutdown depth before you stop the app:

What to useWhen to use it
Stop and suspend managed resources
  • Shuts down the app and suspends the app service, app warehouse, and compute pools.
  • Delivers the smallest cost reduction of the three modes.
  • Provides the fastest restart of the three modes.
Stop, suspend, and remove SPCS service
  • Shuts down the app, removes the app service, and suspends the warehouse and compute pools.
  • Delivers more cost reduction than suspend-only mode, but less than removing all resources.
  • Restarts more slowly than suspend-only mode, but faster than removing all resources.
Stop and remove all managed resources
  • Shuts down the app and removes the app service, app warehouse, and compute pools.
  • Delivers the largest cost reduction of the three modes.
  • Provides the slowest restart of the three modes.

Choose this option when you want to stop the app but leave the app service, app warehouse, and compute pools in place as suspended resources. This is the lightest shutdown and the quickest option to reverse.

Call the relationalai.app.deactivate() procedure with no arguments:

CALL relationalai.app.deactivate();
Output
+------------------------------------------------+
| RelationalAI service deactivated successfully. |
+------------------------------------------------+

Choose this option when you want a deeper shutdown that removes the app service but preserves the app warehouse and compute pools as suspended resources. Use it when you want more teardown than the default mode without fully removing the app’s managed infrastructure.

Call the relationalai.app.deactivate() procedure with 'service' as the argument:

CALL relationalai.app.deactivate('service');
Output
+------------------------------------------------+
| RelationalAI service deactivated successfully. |
+------------------------------------------------+

Stop the app and drop all managed resources

Section titled “Stop the app and drop all managed resources”

Choose this option when you want the deepest shutdown and do not need to keep the app warehouse or compute pools available for the next start. Use it when cost reduction matters more than restart speed.

Call the relationalai.app.deactivate() procedure with 'all' as the argument:

CALL relationalai.app.deactivate('all');
Output
+------------------------------------------------+
| RelationalAI service deactivated successfully. |
+------------------------------------------------+