{"allowedRenditionsWidth":["320","480","640","768","960","1200","1440","1920"],"templateName":"quickstart-page-template","cssClassNames":"page basicpage summit-page","canonicalLink":"https://www.snowflake.com/en/developers/guides/build-real-time-ml-inference-with-feature-store/","robotsTags":[],"language":"en","title":"Build Real-Time ML Inference with Feature Store","analyticsPageType":"quickstart-page-template","analyticsCategory":"general","analyticsSubCategory":"","excludeFromAnalytics":false,":hierarchyType":"page",":path":"/content/snowflake-site/global/en/developers/guides/build-real-time-ml-inference-with-feature-store","isPasswordProtected":false,"analyticsContentTags":["snowflake-site:taxonomy/solution-center/certification/quickstart"],"analyticsEnabled":true,"coveoConfig":{"pipeline":"snowflake.com","searchHub":"snowflake.com","organizationId":"snowflakecomputingproduction8neljofn","apiKey":"xx335921a6-2a0a-40f2-a167-e390b4766c3d"},"analyticsDebugMode":false,"analyticsData":{"excludeFromAnalytics":false,"subCategory":"","pageType":"quickstart-page-template","templateName":"quickstart-page-template","siteName":"snowflake","pageUrl":"/content/snowflake-site/global/en/developers/guides/build-real-time-ml-inference-with-feature-store","language":"en","category":"general","pageName":"Build Real-Time ML Inference with Feature Store","contentTags":["snowflake-site:taxonomy/solution-center/certification/quickstart"]},":mappedPath":"/en/developers/guides/build-real-time-ml-inference-with-feature-store/",":type":"snowflake-site/components/structure/page",":items":{"root":{"columnClassNames":{"markup_editor_1950346551":"aem-GridColumn aem-GridColumn--default--12","experiencefragment-banner":"aem-GridColumn aem-GridColumn--default--12","experiencefragment-header":"aem-GridColumn aem-GridColumn--default--12","responsivegrid":"aem-GridColumn aem-GridColumn--default--12","experiencefragment-footer":"aem-GridColumn aem-GridColumn--default--12","modal_container":"aem-GridColumn aem-GridColumn--default--12","markup_editor":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnCount":12,":items":{"experiencefragment-banner":{"id":"experiencefragment-175a1250b6","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/pushdown-banner/master/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment","xfModelPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/pushdown-banner/master.xfmodel.json"},"experiencefragment-header":{"id":"experiencefragment-0b3eb760d8","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment","xfModelPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master.xfmodel.json","languageNavPath":"/content/snowflake-site/global/en/developers/guides/build-real-time-ml-inference-with-feature-store.languagenav.json"},"markup_editor_1950346551":{"id":"markup-editor-06f6520069","title":" ","cssContent":".snowflake-markdown-table code[class*=language-],.snowflake-markdown-table code[class*=language-],.snowflake-markdown .snowflake-text code[class*=language-],.snowflake-markdown .snowflake-text pre[class*=language-]{background-color:rgba(var(--ui-12-rgb),.5);color:var(--text-01);text-shadow:none;padding:var(--spacing-00);border-radius:var(--spacing-00);font-size:smaller}","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"},"responsivegrid":{"columnClassNames":{"quickstart_hero":"aem-GridColumn aem-GridColumn--default--12","flexible_column_cont":"aem-GridColumn aem-GridColumn--default--12","markup_editor":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnCount":12,":items":{"quickstart_hero":{"id":"quickstart-hero-34e1408d90","quickstartHeroFirstCertifiedTag":{"tagText":"Quickstart","tagColor":"#29B5E8","tagPath":"/content/cq:tags/snowflake-site/taxonomy/solution-center/certification/quickstart","tagIcon":""},"quickstartHeroTitle":{"lines":["Build Real-Time ML Inference with Feature Store"],"type":"heading2",":type":"snowflake-site/components/title-v2"},"quickstartHeroAuthor":"Trace Smith, Ghazaleh Kazeminejad","quickstartHeroForkRepoLink":{"id":"button-591f6de491","showOutboundIcon":false,"buttonLink":{"valid":true,"attributes":{"target":"_blank"},"url":"https://github.com/Snowflake-Labs/sfquickstarts/tree/master/site/sfguides/src/build-real-time-ml-inference-with-feature-store"},"linkTargetContentType":"GENERIC","linkType":"SNOWFLAKE_EXTERNAL",":type":"snowflake-site/components/button","text":"Fork Repo"},"quickstartHeroBreadcrumbs":[{"title":"Build Real-Time ML Inference with Feature Store","url":"https://www.snowflake.com/content/snowflake-site/global/en/developers/guides/build-real-time-ml-inference-with-feature-store","currentPage":true},{"title":"Guides","url":"https://www.snowflake.com/content/snowflake-site/global/en/developers/guides","currentPage":false},{"title":"Snowflake for Developers","url":"https://www.snowflake.com/content/snowflake-site/global/en/developers","currentPage":false}],":type":"snowflake-site/components/quickstart/quickstart-hero","fragmentPath":"/content/dam/snowflake-site/en/content-fragments/quickstarts/build-real-time-ml-inference-with-feature-store","isDeveloperGuidesPage":false},"flexible_column_cont":{"id":"flexible-column-container-1457a78673","propertiesId":"quickstart-template-main-flexible-container","type":"2-column-75-25","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"none","bottomPadding":"none","spaceBetween":"small","reverseOnMobile":false,"carouselOnMobile":false,"backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-664d5fd538",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"contentfragment":{"id":"contentfragment-17a744f9ab","description":"","paragraphs":["&lt;!-- ------------------------ --&gt;\n","\u003Ch2\u003EOverview\u003C/h2\u003E\n","\u003Cp\u003EIn production ML systems, one of the hardest problems is \u003Cstrong\u003Etraining-serving skew\u003C/strong\u003E &mdash; when features used at inference time differ from those used during training due to separate code paths and data sources. Snowflake's \u003Cstrong\u003EFeature Store + ML Inference Service\u003C/strong\u003E integration solves this by providing a single source of truth for features across training and serving.\u003C/p\u003E\n","\u003Cp\u003EIn this quickstart, you will build an end-to-end real-time fraud detection system that uses Snowflake's Postgres-backed Online Feature Store integrated with an ML Inference Service deployed on Snowpark Container Services (SPCS).\u003C/p\u003E\n","\u003Ch3\u003EArchitecture\u003C/h3\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/build-real-time-ml-inference-with-feature-store/architecture.png\" alt=\"Architecture\"\u003E\u003C/p\u003E\n","\u003Cp\u003EThe diagram above illustrates the end-to-end inference flow. Source data in the \u003Ccode\u003ETRANSACTION_FEATURES\u003C/code\u003E table is automatically synced &mdash; via a configurable refresh frequency and target lag &mdash; into the Postgres-backed Online Feature Store. When an external client sends a \u003Ccode\u003EPOST /predict\u003C/code\u003E request containing only a \u003Ccode\u003ETRANSACTION_ID\u003C/code\u003E, the SPCS Ingress endpoint routes the request to the Online Model Service, which fetches the corresponding features from the Online Feature Store, runs the XGBoost fraud classifier, and returns the prediction back through the ingress to the caller - all in one step.\u003C/p\u003E\n","\u003Ch3\u003EPrerequisites\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EA Snowflake account with \u003Cstrong\u003EACCOUNTADMIN\u003C/strong\u003E access\u003C/li\u003E\u003Cli\u003EFamiliarity with Python and basic ML concepts\u003C/li\u003E\u003Cli\u003EUnderstanding of Snowflake notebooks\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EWhat You'll Learn\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EHow to set up a Postgres-backed Online Feature Store in Snowflake\u003C/li\u003E\u003Cli\u003EHow to register entities and feature views with online serving enabled\u003C/li\u003E\u003Cli\u003EHow to train and register an ML model in the Snowflake Model Registry\u003C/li\u003E\u003Cli\u003EHow to deploy a real-time inference service on SPCS with automatic feature lookup\u003C/li\u003E\u003Cli\u003EHow to invoke the service from external clients using a REST API\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EWhat You'll Need\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EA \u003Ca href=\"https://signup.snowflake.com/\"\u003ESnowflake Account\u003C/a\u003E (Enterprise edition or higher recommended)\u003C/li\u003E\u003Cli\u003EA Snowflake Notebook with \u003Cstrong\u003EContainer Runtime\u003C/strong\u003E enabled (CPU)\u003C/li\u003E\u003Cli\u003E\u003Ccode\u003Esnowflake-ml-python &gt;= 1.44.0\u003C/code\u003E\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EWhat You'll Build\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EA \u003Cstrong\u003EPostgres-backed Feature Store\u003C/strong\u003E with online serving enabled (10-second target lag)\u003C/li\u003E\u003Cli\u003EAn \u003Cstrong\u003EXGBoost fraud detection model\u003C/strong\u003E trained on synthetic transaction data and registered in the Model Registry\u003C/li\u003E\u003Cli\u003EA \u003Cstrong\u003Ereal-time inference service\u003C/strong\u003E (SPCS) that automatically fetches features from the online store when callers send only a \u003Ccode\u003ETRANSACTION_ID\u003C/code\u003E\u003C/li\u003E\u003C/ul\u003E\n&lt;!-- ------------------------ --&gt;\n","\u003Ch2\u003EPart I: Environment Setup\u003C/h2\u003E\n","\u003Cp\u003EThis section prepares your notebook, installs dependencies, and loads sample data into Snowflake.\u003C/p\u003E\n","\u003Ch3\u003EOpen a Snowflake Notebook\u003C/h3\u003E\n","\u003Cp\u003ENavigate to \u003Cstrong\u003ESnowsight &gt; Notebooks\u003C/strong\u003E and create a new notebook with the following settings:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003E\u003Cstrong\u003ERuntime\u003C/strong\u003E: Container Runtime (CPU)\u003C/li\u003E\u003Cli\u003E\u003Cstrong\u003EWarehouse\u003C/strong\u003E: Any X-Small or larger warehouse\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EInstall Dependencies\u003C/h3\u003E\n","\u003Cp\u003EIn the first cell of your notebook, install the required package:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Epip install snowflake-ml-python -U\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003ESet Configuration\u003C/h3\u003E\n","\u003Cp\u003ECreate a configuration cell with centralized version and naming config. Update these values if you need to re-run the notebook with different versions:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003E# === VERSION CONFIG ===\nFEATURE_VIEW_VERSION = &quot;V2&quot;\nMODEL_NAME = &quot;FRAUD_XGBOOST&quot;\nMODEL_VERSION = &quot;V1&quot;\nSERVICE_NAME = &quot;FRAUD_DETECTION_SVC&quot;\nCOMPUTE_POOL_NAME = &quot;ML_ONLINE_CPU_POOL&quot;\n\n# === Roles ===\nPRODUCER_ROLE = &quot;ACCOUNTADMIN&quot;\nCONSUMER_ROLE = &quot;ACCOUNTADMIN&quot;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003ECreate Database and Schema\u003C/h3\u003E\n","\u003Cp\u003ECreate the database and schema that will house the feature store, model, and inference service:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-sql\"\u003EUSE ROLE ACCOUNTADMIN;\nCREATE DATABASE IF NOT EXISTS ML_DEMO_INF_FS_LOOKUP;\nCREATE SCHEMA IF NOT EXISTS ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS;\nUSE DATABASE ML_DEMO_INF_FS_LOOKUP;\nUSE SCHEMA FRAUD_ML_SERVICE_FS;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EGenerate Synthetic Data\u003C/h3\u003E\n","\u003Cp\u003ECreate 200 synthetic transaction records with features for fraud detection. In production, this would be your actual transaction data pipeline:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Eimport pandas as pd\nimport numpy as np\n\nnp.random.seed(42)\nn_transactions = 200\n\ndata = pd.DataFrame({\n    &quot;TRANSACTION_ID&quot;: [f&quot;TXN_{i:04d}&quot; for i in range(n_transactions)],\n    &quot;TRANSACTION_AMOUNT&quot;: np.round(np.random.uniform(5, 5000, n_transactions), 2),\n    &quot;MERCHANT_CATEGORY&quot;: np.random.choice([0, 1, 2, 3, 4], n_transactions),\n    &quot;DISTANCE_FROM_HOME&quot;: np.round(np.random.uniform(0, 500, n_transactions), 2),\n    &quot;TIME_SINCE_LAST_TXN&quot;: np.round(np.random.uniform(0.1, 72, n_transactions), 2),\n    &quot;DAILY_TXN_COUNT&quot;: np.random.randint(1, 20, n_transactions),\n    &quot;IS_FRAUD&quot;: np.random.choice([0, 1], n_transactions, p=[0.92, 0.08]),\n})\n\nprint(f&quot;Dataset shape: {data.shape}&quot;)\ndata.head()\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThe features include:\n| Feature | Description |\n|---------|-------------|\n| \u003Ccode\u003ETRANSACTION_AMOUNT\u003C/code\u003E | Dollar amount of the transaction |\n| \u003Ccode\u003EMERCHANT_CATEGORY\u003C/code\u003E | Category code (0=retail, 1=online, 2=travel, 3=grocery, 4=entertainment) |\n| \u003Ccode\u003EDISTANCE_FROM_HOME\u003C/code\u003E | Miles from cardholder's home address |\n| \u003Ccode\u003ETIME_SINCE_LAST_TXN\u003C/code\u003E | Hours since the previous transaction |\n| \u003Ccode\u003EDAILY_TXN_COUNT\u003C/code\u003E | Number of transactions today |\n| \u003Ccode\u003EIS_FRAUD\u003C/code\u003E | Binary target (0=legitimate, 1=fraudulent) |\u003C/p\u003E\n","\u003Ch3\u003ELoad Data into Snowflake\u003C/h3\u003E\n","\u003Cp\u003EWrite the synthetic DataFrame to a Snowflake table that will serve as the source for the Feature View:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Efrom snowflake.snowpark.context import get_active_session\nfrom snowflake.ml.feature_store import FeatureStore, FeatureView, Entity\n\nsession = get_active_session()\nsession.use_database(&quot;ML_DEMO_INF_FS_LOOKUP&quot;)\nsession.use_schema(&quot;FRAUD_ML_SERVICE_FS&quot;)\n\nsession.create_dataframe(data).write.save_as_table(&quot;TRANSACTION_FEATURES&quot;, mode=&quot;overwrite&quot;)\nprint(&quot;Table TRANSACTION_FEATURES created with&quot;, session.table(&quot;TRANSACTION_FEATURES&quot;).count(), &quot;rows&quot;)\n\u003C/code\u003E\u003C/pre\u003E\n&lt;!-- ------------------------ --&gt;\n","\u003Ch2\u003EPart II: Feature Store with Online Serving\u003C/h2\u003E\n","\u003Cp\u003EThis section creates the Postgres-backed online Feature Store, registers entities and feature views, and verifies low-latency feature retrieval.\u003C/p\u003E\n","\u003Ch3\u003EInitialize Feature Store and Register Entity\u003C/h3\u003E\n","\u003Cp\u003EInitialize the Feature Store connection and register the \u003Ccode\u003ETRANSACTION\u003C/code\u003E entity with \u003Ccode\u003ETRANSACTION_ID\u003C/code\u003E as the join key. The entity defines the primary key that links feature rows to inference requests:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Efrom snowflake.ml.feature_store import FeatureStore, FeatureView, Entity, CreationMode\nfrom snowflake.ml.feature_store import OnlineConfig, OnlineStoreType\n\nfs = FeatureStore(\n    session=session,\n    database=&quot;ML_DEMO_INF_FS_LOOKUP&quot;,\n    name=&quot;FRAUD_ML_SERVICE_FS&quot;,\n    default_warehouse=&quot;COMPUTE_WH&quot;,\n    creation_mode=CreationMode.CREATE_IF_NOT_EXIST\n)\n\ntransaction_entity = Entity(name=&quot;TRANSACTION&quot;, join_keys=[&quot;TRANSACTION_ID&quot;])\nfs.register_entity(transaction_entity)\nprint(&quot;Entity registered:&quot;, transaction_entity.name)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003ECreate the Online Service\u003C/h3\u003E\n","\u003Cp\u003EProvision the Postgres-backed online serving infrastructure. This spins up a managed Postgres instance that will serve features at low latency for real-time inference:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Eimport time\n\ntry:\n    result = fs.create_online_service(\n        producer_role=PRODUCER_ROLE,\n        consumer_role=CONSUMER_ROLE\n    )\n    print(f&quot;Create online service: {result}&quot;)\nexcept Exception as e:\n    print(f&quot;Online service already exists or error: {e}&quot;)\n\n# Poll until RUNNING\nfor i in range(60):\n    status = fs.get_online_service_status()\n    current_status = status.status if hasattr(status, 'status') else str(status)\n    print(f&quot;[{i+1}] Online service status: {current_status}&quot;)\n    if current_status == &quot;RUNNING&quot;:\n        print(&quot;Online service is ready!&quot;)\n        break\n    time.sleep(10)\nelse:\n    raise TimeoutError(&quot;Service did not reach RUNNING within 10 min.&quot;)\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003Easide positive\nThe online service may take a few minutes to reach \u003Ccode\u003ERUNNING\u003C/code\u003E state on first creation. Subsequent runs will be faster.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch3\u003ERegister Feature View with Postgres Online Store\u003C/h3\u003E\n","\u003Cp\u003EDefine and register the \u003Ccode\u003ETRANSACTION_FRAUD_FEATURES\u003C/code\u003E Feature View. The \u003Ccode\u003Eonline_config\u003C/code\u003E enables Postgres-backed serving with a \u003Cstrong\u003E10-second target lag\u003C/strong\u003E &mdash; features sync from the source table to the online store within 10 seconds of any update:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Esource_df = session.table(&quot;ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS.TRANSACTION_FEATURES&quot;).select(\n    &quot;TRANSACTION_ID&quot;, &quot;TRANSACTION_AMOUNT&quot;, &quot;MERCHANT_CATEGORY&quot;,\n    &quot;DISTANCE_FROM_HOME&quot;, &quot;TIME_SINCE_LAST_TXN&quot;, &quot;DAILY_TXN_COUNT&quot;\n)\n\ntxn_fv = FeatureView(\n    name=&quot;TRANSACTION_FRAUD_FEATURES&quot;,\n    entities=[transaction_entity],\n    feature_df=source_df,\n    desc=&quot;Transaction fraud detection features&quot;,\n    refresh_freq=&quot;1 minute&quot;,\n    online_config=OnlineConfig(\n        enable=True,\n        target_lag=&quot;10s&quot;,\n        store_type=OnlineStoreType.POSTGRES,\n    ),\n)\n\ntxn_fv = fs.register_feature_view(feature_view=txn_fv, version=FEATURE_VIEW_VERSION, overwrite=True)\nprint(f&quot;Feature View registered: {txn_fv.name} version {txn_fv.version}&quot;)\nprint(f&quot;Online store type: POSTGRES&quot;)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EAuthenticate to the Online Feature Store\u003C/h3\u003E\n","\u003Cp\u003EThe Online Feature Store client authenticates via a \u003Cstrong\u003EProgrammatic Access Token (PAT)\u003C/strong\u003E. Without a valid token set in the environment, calls to \u003Ccode\u003Efs.read_feature_view()\u003C/code\u003E with \u003Ccode\u003EStoreType.ONLINE\u003C/code\u003E will fail with a connection or auth error.\u003C/p\u003E\n","\u003Cp\u003EYou can extract the current session token directly:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Eimport os\n\nos.environ['SNOWFLAKE_PAT'] = session.connection.rest.token\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003Easide negative\nThe session token above is short-lived and suitable for development/testing. For production workloads, generate a dedicated PAT via \u003Cstrong\u003ESnowsight &rarr; User Menu &rarr; My Profile &rarr; Authentication &rarr; Personal Access Tokens &rarr; + Generate\u003C/strong\u003E and store it securely (e.g., as a Snowflake secret).\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch3\u003EVerify Online Feature Retrieval\u003C/h3\u003E\n","\u003Cp\u003EWith authentication configured, confirm the online store is synced and serving features at low latency:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Efrom snowflake.ml.feature_store.feature_view import StoreType\n\nos.environ['SNOWFLAKE_PAT'] = session.connection.rest.token\n\ntxn_fv = fs.get_feature_view(&quot;TRANSACTION_FRAUD_FEATURES&quot;, FEATURE_VIEW_VERSION)\nprint(f&quot;Store type: {txn_fv.online_config.store_type}&quot;)\nprint(f&quot;Target lag: {txn_fv.online_config.target_lag}&quot;)\n\ntest_keys = [[&quot;TXN_0001&quot;], [&quot;TXN_0010&quot;], [&quot;TXN_0050&quot;]]\n\nonline_result = fs.read_feature_view(\n    txn_fv,\n    keys=test_keys,\n    store_type=StoreType.ONLINE\n)\nprint(&quot;\\nOnline (Postgres) feature retrieval successful!&quot;)\nonline_result\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EYou should see the feature values returned from the Postgres online store for each transaction ID. If you receive a connection or authentication error, verify that \u003Ccode\u003ESNOWFLAKE_PAT\u003C/code\u003E is set correctly in your environment.\u003C/p\u003E\n&lt;!-- ------------------------ --&gt;\n","\u003Ch2\u003EPart III: Train, Register, and Deploy Model\u003C/h2\u003E\n","\u003Cp\u003EThis section trains an XGBoost fraud detection model, registers it in the Snowflake Model Registry, and deploys it as a real-time inference service on SPCS with automatic feature lookup from the online store.\u003C/p\u003E\n","\u003Ch3\u003ETrain XGBoost Model\u003C/h3\u003E\n","\u003Cp\u003ETrain an XGBoost binary classifier on the synthetic fraud data:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Eimport xgboost as xgb\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, roc_auc_score\n\nFEATURES = [&quot;TRANSACTION_AMOUNT&quot;, &quot;MERCHANT_CATEGORY&quot;, &quot;DISTANCE_FROM_HOME&quot;, \n    &quot;TIME_SINCE_LAST_TXN&quot;, &quot;DAILY_TXN_COUNT&quot;]\nTARGET = &quot;IS_FRAUD&quot;\n\nX = data[FEATURES]\ny = data[TARGET]\n\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n\nmodel = xgb.XGBClassifier(\n    n_estimators=50,\n    max_depth=4,\n    learning_rate=0.1,\n    use_label_encoder=False,\n    eval_metric=&quot;logloss&quot;,\n    random_state=42\n)\nmodel.fit(X_train, y_train)\n\ny_pred = model.predict(X_test)\ny_proba = model.predict_proba(X_test)[:, 1]\nprint(f&quot;Accuracy: {accuracy_score(y_test, y_pred):.3f}&quot;)\nprint(f&quot;ROC AUC:  {roc_auc_score(y_test, y_proba):.3f}&quot;)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003ERegister Model in Snowflake Model Registry\u003C/h3\u003E\n","\u003Cp\u003ELog the trained model to the Snowflake Model Registry with its input signature. This lets the inference service know which features the model expects:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Efrom snowflake.ml.registry import Registry\n\nreg = Registry(session=session, database_name=&quot;ML_DEMO_INF_FS_LOOKUP&quot;, schema_name=&quot;FRAUD_ML_SERVICE_FS&quot;)\n\ntry:\n    mv = reg.get_model(MODEL_NAME).version(MODEL_VERSION)\n    print(f&quot;Using existing model: {MODEL_NAME}/{MODEL_VERSION}&quot;)\nexcept Exception:\n    sample_input = X_train.head(5)\n    mv = reg.log_model(\n        model_name=MODEL_NAME,\n        version_name=MODEL_VERSION,\n        model=model,\n        sample_input_data=sample_input,\n        conda_dependencies=[&quot;xgboost&quot;],\n    )\n    print(f&quot;Model registered: {MODEL_NAME} version {MODEL_VERSION}&quot;)\n\nfuncs = mv.show_functions()\nprint(f&quot;Functions: {[f['name'] if isinstance(f, dict) else str(f) for f in funcs]}&quot;)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003ECreate Compute Pool\u003C/h3\u003E\n","\u003Cp\u003ECreate a compute pool for online inference. The pool provisions nodes that will host the containerized model service:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-sql\"\u003ECREATE COMPUTE POOL IF NOT EXISTS ML_ONLINE_CPU_POOL\n  MIN_NODES = 1\n  MAX_NODES = 3\n  INSTANCE_FAMILY = CPU_X64_S\n  AUTO_RESUME = TRUE\n  AUTO_SUSPEND_SECS = 300\n  COMMENT = 'Compute pool for fraud detection inference service';\n\nDESCRIBE COMPUTE POOL ML_ONLINE_CPU_POOL;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EDeploy Inference Service with Feature Store Lookup\u003C/h3\u003E\n","\u003Cp\u003EDeploy the model as a real-time HTTP service on SPCS. The key parameter is \u003Ccode\u003Efeature_sources_per_function\u003C/code\u003E &mdash; it maps the \u003Ccode\u003Epredict\u003C/code\u003E method to the Postgres-backed Feature View so the service \u003Cstrong\u003Eautomatically fetches features\u003C/strong\u003E when callers send only \u003Ccode\u003ETRANSACTION_ID\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Etxn_fv = fs.get_feature_view(&quot;TRANSACTION_FRAUD_FEATURES&quot;, FEATURE_VIEW_VERSION)\n\nmv.create_service(\n    service_name=SERVICE_NAME,\n    service_compute_pool=COMPUTE_POOL_NAME,\n    ingress_enabled=True,\n    feature_sources_per_function={&quot;predict&quot;: [txn_fv]},\n)\nprint(f&quot;Service deployment initiated: {SERVICE_NAME}&quot;)\nprint(f&quot;The service will auto-lookup features from TRANSACTION_FRAUD_FEATURES/{FEATURE_VIEW_VERSION} (Postgres online store)&quot;)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EVerify the service is running:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-python\"\u003Eservices_df = mv.list_services()\nprint(&quot;Active services:&quot;)\nprint(services_df.to_string())\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003Easide negative\nThe service may take a few minutes to start. If it shows as \u003Ccode\u003EPENDING\u003C/code\u003E, wait and re-run the verification cell.\u003C/p\u003E\n\u003C/blockquote\u003E\n&lt;!-- ------------------------ --&gt;\n","\u003Ch2\u003EPart IV: Test the Inference Endpoint\u003C/h2\u003E\n","\u003Cp\u003EIn this section, you'll test the inference service by calling its \u003Cstrong\u003Epublic REST endpoint from outside Snowflake\u003C/strong\u003E &mdash; simulating how a production client (web app, microservice, Postman, or curl) would invoke it. This is not done from within the Snowsight UI; instead, you'll authenticate with a Programmatic Access Token (PAT) and make HTTP requests directly to the SPCS ingress endpoint.\u003C/p\u003E\n","\u003Ch3\u003EHow It Works\u003C/h3\u003E\n","\u003Cp\u003EThe \u003Ccode\u003Efeature_sources_per_function\u003C/code\u003E parameter enables the inference service to \u003Cstrong\u003Eautomatically look up features\u003C/strong\u003E from the online Postgres Feature Store. Here's what happens under the hood:\u003C/p\u003E\n\u003Col\u003E\u003Cli\u003EYou send only \u003Ccode\u003ETRANSACTION_ID\u003C/code\u003E values to the REST endpoint\u003C/li\u003E\u003Cli\u003EThe Snowflake ingress gateway intercepts the request\u003C/li\u003E\u003Cli\u003EIt fetches \u003Ccode\u003ETRANSACTION_AMOUNT\u003C/code\u003E, \u003Ccode\u003EMERCHANT_CATEGORY\u003C/code\u003E, \u003Ccode\u003EDISTANCE_FROM_HOME\u003C/code\u003E, \u003Ccode\u003ETIME_SINCE_LAST_TXN\u003C/code\u003E, \u003Ccode\u003EDAILY_TXN_COUNT\u003C/code\u003E from the Postgres online store\u003C/li\u003E\u003Cli\u003EThe enriched payload is forwarded to the XGBoost model container\u003C/li\u003E\u003Cli\u003EFraud predictions are returned\u003C/li\u003E\u003C/ol\u003E\n","\u003Ch3\u003ESet Up Authentication\u003C/h3\u003E\n","\u003Cp\u003ETo invoke the service from outside Snowflake, you need a PAT:\u003C/p\u003E\n\u003Col\u003E\u003Cli\u003EIn Snowsight, click your username (top-left) &gt; \u003Cstrong\u003EMy Profile\u003C/strong\u003E &gt; \u003Cstrong\u003EAuthentication\u003C/strong\u003E &gt; \u003Cstrong\u003EPersonal Access Tokens\u003C/strong\u003E &gt; \u003Cstrong\u003E+ Generate\u003C/strong\u003E\u003C/li\u003E\u003Cli\u003EGrant the delegated authorization:\u003C/li\u003E\u003C/ol\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-sql\"\u003EALTER USER &lt;your_user&gt; ADD DELEGATED AUTHORIZATION\n  OF ROLE ACCOUNTADMIN TO SECURITY INTEGRATION SNOWSERVICES_INGRESS_OAUTH;\n\u003C/code\u003E\u003C/pre\u003E\n\u003Col start=\"3\"\u003E\u003Cli\u003E\u003Cstrong\u003E(If applicable) Whitelist your external IP in the account's network policy.\u003C/strong\u003E If your Snowflake account has an active \u003Ca href=\"https://docs.snowflake.com/en/user-guide/network-policies\"\u003Enetwork policy\u003C/a\u003E, external requests to the SPCS ingress endpoint will be blocked unless the caller's IP is in the allowed list. Create a network rule and add it to your policy:\u003C/li\u003E\u003C/ol\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-sql\"\u003ECREATE NETWORK RULE IF NOT EXISTS allow_external_inference\n  TYPE = IPV4\n  MODE = INGRESS\n  VALUE_LIST = ('&lt;YOUR_PUBLIC_IP&gt;/32');\n\nALTER NETWORK POLICY &lt;your_policy_name&gt;\n  ADD ALLOWED_NETWORK_RULE_LIST = ('allow_external_inference');\n\u003C/code\u003E\u003C/pre\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003Easide positive\nYou can find your public IP by running \u003Ccode\u003Ecurl ifconfig.me\u003C/code\u003E from your terminal. If no network policy is active on the account, this step can be skipped &mdash; Snowflake allows access from all IPs by default.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch3\u003ECall the Service\u003C/h3\u003E\n","\u003Cp\u003EFirst, retrieve the ingress endpoint URL for your service:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-sql\"\u003ESHOW ENDPOINTS IN SERVICE ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS.FRAUD_DETECTION_SVC;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ECopy the \u003Ccode\u003Eingress_url\u003C/code\u003E value from the result &mdash; this is your \u003Ccode\u003E&lt;ENDPOINT_URL&gt;\u003C/code\u003E.\u003C/p\u003E\n","\u003Cp\u003EFrom any external client (curl, Python requests, web app), invoke the service by sending only entity keys:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-bash\"\u003Ecurl -X POST &quot;https://&lt;ENDPOINT_URL&gt;/predict&quot; \\\n  -H 'Authorization: Snowflake Token=&quot;&lt;PAT_TOKEN&gt;&quot;' \\\n  -H 'Content-Type: application/json' \\\n  -d '{&quot;dataframe_split&quot;: {&quot;index&quot;: [0, 1, 2], &quot;columns&quot;: [&quot;TRANSACTION_ID&quot;], &quot;data&quot;: [[&quot;TXN_0001&quot;], [&quot;TXN_0010&quot;], [&quot;TXN_0050&quot;]]}}'\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EReplace \u003Ccode\u003E&lt;ENDPOINT_URL&gt;\u003C/code\u003E with the \u003Ccode\u003Eingress_url\u003C/code\u003E from the query above and \u003Ccode\u003E&lt;PAT_TOKEN&gt;\u003C/code\u003E with the PAT you generated.\u003C/p\u003E\n","\u003Cp\u003EThe response will contain fraud predictions for each transaction &mdash; all without the caller needing to know which features exist or how they're computed.\u003C/p\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003Easide positive\n\u003Cstrong\u003EImportant:\u003C/strong\u003E The Python SDK (\u003Ccode\u003Emv.run()\u003C/code\u003E) and SQL service functions do not trigger this feature enrichment &mdash; they bypass the gateway. This feature is designed for external real-time inference clients (web apps, microservices, mobile backends) that call the REST endpoint.\u003C/p\u003E\n\u003C/blockquote\u003E\n&lt;!-- ------------------------ --&gt;\n","\u003Ch2\u003ECleanup\u003C/h2\u003E\n","\u003Cp\u003EWhen you're done experimenting, clean up the resources:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode class=\"language-sql\"\u003EDROP SERVICE IF EXISTS ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS.FRAUD_DETECTION_SVC;\nDROP MODEL IF EXISTS ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS.FRAUD_XGBOOST;\nDROP COMPUTE POOL IF EXISTS ML_ONLINE_CPU_POOL;\nDROP DATABASE IF EXISTS ML_DEMO_INF_FS_LOOKUP;\n\u003C/code\u003E\u003C/pre\u003E\n&lt;!-- ------------------------ --&gt;\n","\u003Ch2\u003EConclusion and Resources\u003C/h2\u003E\n","\u003Cp\u003ECongratulations! You've built a complete real-time ML inference pipeline that eliminates training-serving skew by using Snowflake's Feature Store as a single source of truth.\u003C/p\u003E\n","\u003Ch3\u003EWhat You Learned\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EHow Snowflake's Postgres-backed Online Feature Store provides millisecond-level feature retrieval\u003C/li\u003E\u003Cli\u003EHow to register entities and feature views with configurable online serving lag\u003C/li\u003E\u003Cli\u003EHow to train and register models in the Snowflake Model Registry\u003C/li\u003E\u003Cli\u003EHow to deploy inference services with \u003Ccode\u003Efeature_sources_per_function\u003C/code\u003E for automatic feature enrichment\u003C/li\u003E\u003Cli\u003EHow external clients can get predictions by sending only entity keys\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EKey Takeaways\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003E\u003Cstrong\u003ENo training-serving skew\u003C/strong\u003E: The same Feature View serves both training and inference\u003C/li\u003E\u003Cli\u003E\u003Cstrong\u003EDecoupled clients\u003C/strong\u003E: API consumers send only an entity key &mdash; no need to know which features exist or how they're computed\u003C/li\u003E\u003Cli\u003E\u003Cstrong\u003ELow-latency\u003C/strong\u003E: Postgres online store provides millisecond-level feature retrieval\u003C/li\u003E\u003Cli\u003E\u003Cstrong\u003EAuto-fresh\u003C/strong\u003E: Features sync with configurable target lag (10s in this demo)\u003C/li\u003E\u003Cli\u003E\u003Cstrong\u003ESimplified contracts\u003C/strong\u003E: Adding or modifying features doesn't require API changes\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003ERelated Resources\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003E\u003Ca href=\"https://docs.snowflake.com/en/developer-guide/snowflake-ml/inference/real-time-inference-rest-api\"\u003ESnowflake ML Inference Service Documentation\u003C/a\u003E\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"https://docs.snowflake.com/en/developer-guide/snowflake-ml/feature-store/overview\"\u003ESnowflake Feature Store Documentation\u003C/a\u003E\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"https://docs.snowflake.com/en/developer-guide/snowflake-ml/model-registry/overview\"\u003ESnowflake Model Registry Documentation\u003C/a\u003E\u003C/li\u003E\u003C/ul\u003E"],"title":"Base Quickstart CF",":type":"snowflake-site/components/contentfragment","isDeveloperGuidesPage":false,":items":{},":itemsOrder":[],"elements":{"quickstartArticleBody":{"dataType":"string","title":"Quickstart Article Body","value":"\u003C!-- ------------------------ --\u003E\n## Overview\n\nIn production ML systems, one of the hardest problems is **training-serving skew** — when features used at inference time differ from those used during training due to separate code paths and data sources. Snowflake's **Feature Store + ML Inference Service** integration solves this by providing a single source of truth for features across training and serving.\n\nIn this quickstart, you will build an end-to-end real-time fraud detection system that uses Snowflake's Postgres-backed Online Feature Store integrated with an ML Inference Service deployed on Snowpark Container Services (SPCS).\n\n### Architecture\n\n![Architecture](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/build-real-time-ml-inference-with-feature-store/architecture.png)\n\nThe diagram above illustrates the end-to-end inference flow. Source data in the `TRANSACTION_FEATURES` table is automatically synced — via a configurable refresh frequency and target lag — into the Postgres-backed Online Feature Store. When an external client sends a `POST /predict` request containing only a `TRANSACTION_ID`, the SPCS Ingress endpoint routes the request to the Online Model Service, which fetches the corresponding features from the Online Feature Store, runs the XGBoost fraud classifier, and returns the prediction back through the ingress to the caller - all in one step.\n\n### Prerequisites\n- A Snowflake account with **ACCOUNTADMIN** access\n- Familiarity with Python and basic ML concepts\n- Understanding of Snowflake notebooks\n\n### What You'll Learn\n- How to set up a Postgres-backed Online Feature Store in Snowflake\n- How to register entities and feature views with online serving enabled\n- How to train and register an ML model in the Snowflake Model Registry\n- How to deploy a real-time inference service on SPCS with automatic feature lookup\n- How to invoke the service from external clients using a REST API\n\n### What You'll Need\n- A [Snowflake Account](https://signup.snowflake.com/) (Enterprise edition or higher recommended)\n- A Snowflake Notebook with **Container Runtime** enabled (CPU)\n- `snowflake-ml-python \u003E= 1.44.0`\n\n### What You'll Build\n- A **Postgres-backed Feature Store** with online serving enabled (10-second target lag)\n- An **XGBoost fraud detection model** trained on synthetic transaction data and registered in the Model Registry\n- A **real-time inference service** (SPCS) that automatically fetches features from the online store when callers send only a `TRANSACTION_ID`\n\n\u003C!-- ------------------------ --\u003E\n## Part I: Environment Setup\n\nThis section prepares your notebook, installs dependencies, and loads sample data into Snowflake.\n\n### Open a Snowflake Notebook\n\nNavigate to **Snowsight \u003E Notebooks** and create a new notebook with the following settings:\n\n- **Runtime**: Container Runtime (CPU)\n- **Warehouse**: Any X-Small or larger warehouse\n\n### Install Dependencies\n\nIn the first cell of your notebook, install the required package:\n\n```python\npip install snowflake-ml-python -U\n```\n\n### Set Configuration\n\nCreate a configuration cell with centralized version and naming config. Update these values if you need to re-run the notebook with different versions:\n\n```python\n# === VERSION CONFIG ===\nFEATURE_VIEW_VERSION = \"V2\"\nMODEL_NAME = \"FRAUD_XGBOOST\"\nMODEL_VERSION = \"V1\"\nSERVICE_NAME = \"FRAUD_DETECTION_SVC\"\nCOMPUTE_POOL_NAME = \"ML_ONLINE_CPU_POOL\"\n\n# === Roles ===\nPRODUCER_ROLE = \"ACCOUNTADMIN\"\nCONSUMER_ROLE = \"ACCOUNTADMIN\"\n```\n\n### Create Database and Schema\n\nCreate the database and schema that will house the feature store, model, and inference service:\n\n```sql\nUSE ROLE ACCOUNTADMIN;\nCREATE DATABASE IF NOT EXISTS ML_DEMO_INF_FS_LOOKUP;\nCREATE SCHEMA IF NOT EXISTS ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS;\nUSE DATABASE ML_DEMO_INF_FS_LOOKUP;\nUSE SCHEMA FRAUD_ML_SERVICE_FS;\n```\n\n### Generate Synthetic Data\n\nCreate 200 synthetic transaction records with features for fraud detection. In production, this would be your actual transaction data pipeline:\n\n```python\nimport pandas as pd\nimport numpy as np\n\nnp.random.seed(42)\nn_transactions = 200\n\ndata = pd.DataFrame({\n    \"TRANSACTION_ID\": [f\"TXN_{i:04d}\" for i in range(n_transactions)],\n    \"TRANSACTION_AMOUNT\": np.round(np.random.uniform(5, 5000, n_transactions), 2),\n    \"MERCHANT_CATEGORY\": np.random.choice([0, 1, 2, 3, 4], n_transactions),\n    \"DISTANCE_FROM_HOME\": np.round(np.random.uniform(0, 500, n_transactions), 2),\n    \"TIME_SINCE_LAST_TXN\": np.round(np.random.uniform(0.1, 72, n_transactions), 2),\n    \"DAILY_TXN_COUNT\": np.random.randint(1, 20, n_transactions),\n    \"IS_FRAUD\": np.random.choice([0, 1], n_transactions, p=[0.92, 0.08]),\n})\n\nprint(f\"Dataset shape: {data.shape}\")\ndata.head()\n```\n\nThe features include:\n| Feature | Description |\n|---------|-------------|\n| `TRANSACTION_AMOUNT` | Dollar amount of the transaction |\n| `MERCHANT_CATEGORY` | Category code (0=retail, 1=online, 2=travel, 3=grocery, 4=entertainment) |\n| `DISTANCE_FROM_HOME` | Miles from cardholder's home address |\n| `TIME_SINCE_LAST_TXN` | Hours since the previous transaction |\n| `DAILY_TXN_COUNT` | Number of transactions today |\n| `IS_FRAUD` | Binary target (0=legitimate, 1=fraudulent) |\n\n### Load Data into Snowflake\n\nWrite the synthetic DataFrame to a Snowflake table that will serve as the source for the Feature View:\n\n```python\nfrom snowflake.snowpark.context import get_active_session\nfrom snowflake.ml.feature_store import FeatureStore, FeatureView, Entity\n\nsession = get_active_session()\nsession.use_database(\"ML_DEMO_INF_FS_LOOKUP\")\nsession.use_schema(\"FRAUD_ML_SERVICE_FS\")\n\nsession.create_dataframe(data).write.save_as_table(\"TRANSACTION_FEATURES\", mode=\"overwrite\")\nprint(\"Table TRANSACTION_FEATURES created with\", session.table(\"TRANSACTION_FEATURES\").count(), \"rows\")\n```\n\n\u003C!-- ------------------------ --\u003E\n## Part II: Feature Store with Online Serving\n\nThis section creates the Postgres-backed online Feature Store, registers entities and feature views, and verifies low-latency feature retrieval.\n\n### Initialize Feature Store and Register Entity\n\nInitialize the Feature Store connection and register the `TRANSACTION` entity with `TRANSACTION_ID` as the join key. The entity defines the primary key that links feature rows to inference requests:\n\n```python\nfrom snowflake.ml.feature_store import FeatureStore, FeatureView, Entity, CreationMode\nfrom snowflake.ml.feature_store import OnlineConfig, OnlineStoreType\n\nfs = FeatureStore(\n    session=session,\n    database=\"ML_DEMO_INF_FS_LOOKUP\",\n    name=\"FRAUD_ML_SERVICE_FS\",\n    default_warehouse=\"COMPUTE_WH\",\n    creation_mode=CreationMode.CREATE_IF_NOT_EXIST\n)\n\ntransaction_entity = Entity(name=\"TRANSACTION\", join_keys=[\"TRANSACTION_ID\"])\nfs.register_entity(transaction_entity)\nprint(\"Entity registered:\", transaction_entity.name)\n```\n\n### Create the Online Service\n\nProvision the Postgres-backed online serving infrastructure. This spins up a managed Postgres instance that will serve features at low latency for real-time inference:\n\n```python\nimport time\n\ntry:\n    result = fs.create_online_service(\n        producer_role=PRODUCER_ROLE,\n        consumer_role=CONSUMER_ROLE\n    )\n    print(f\"Create online service: {result}\")\nexcept Exception as e:\n    print(f\"Online service already exists or error: {e}\")\n\n# Poll until RUNNING\nfor i in range(60):\n    status = fs.get_online_service_status()\n    current_status = status.status if hasattr(status, 'status') else str(status)\n    print(f\"[{i+1}] Online service status: {current_status}\")\n    if current_status == \"RUNNING\":\n        print(\"Online service is ready!\")\n        break\n    time.sleep(10)\nelse:\n    raise TimeoutError(\"Service did not reach RUNNING within 10 min.\")\n```\n\n\u003E aside positive\n\u003E The online service may take a few minutes to reach `RUNNING` state on first creation. Subsequent runs will be faster.\n\n### Register Feature View with Postgres Online Store\n\nDefine and register the `TRANSACTION_FRAUD_FEATURES` Feature View. The `online_config` enables Postgres-backed serving with a **10-second target lag** — features sync from the source table to the online store within 10 seconds of any update:\n\n```python\nsource_df = session.table(\"ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS.TRANSACTION_FEATURES\").select(\n    \"TRANSACTION_ID\", \"TRANSACTION_AMOUNT\", \"MERCHANT_CATEGORY\",\n    \"DISTANCE_FROM_HOME\", \"TIME_SINCE_LAST_TXN\", \"DAILY_TXN_COUNT\"\n)\n\ntxn_fv = FeatureView(\n    name=\"TRANSACTION_FRAUD_FEATURES\",\n    entities=[transaction_entity],\n    feature_df=source_df,\n    desc=\"Transaction fraud detection features\",\n    refresh_freq=\"1 minute\",\n    online_config=OnlineConfig(\n        enable=True,\n        target_lag=\"10s\",\n        store_type=OnlineStoreType.POSTGRES,\n    ),\n)\n\ntxn_fv = fs.register_feature_view(feature_view=txn_fv, version=FEATURE_VIEW_VERSION, overwrite=True)\nprint(f\"Feature View registered: {txn_fv.name} version {txn_fv.version}\")\nprint(f\"Online store type: POSTGRES\")\n```\n\n### Authenticate to the Online Feature Store\n\nThe Online Feature Store client authenticates via a **Programmatic Access Token (PAT)**. Without a valid token set in the environment, calls to `fs.read_feature_view()` with `StoreType.ONLINE` will fail with a connection or auth error.\n\nYou can extract the current session token directly:\n\n```python\nimport os\n\nos.environ['SNOWFLAKE_PAT'] = session.connection.rest.token\n```\n\n\u003E aside negative\n\u003E The session token above is short-lived and suitable for development/testing. For production workloads, generate a dedicated PAT via **Snowsight → User Menu → My Profile → Authentication → Personal Access Tokens → + Generate** and store it securely (e.g., as a Snowflake secret).\n\n### Verify Online Feature Retrieval\n\nWith authentication configured, confirm the online store is synced and serving features at low latency:\n\n```python\nfrom snowflake.ml.feature_store.feature_view import StoreType\n\nos.environ['SNOWFLAKE_PAT'] = session.connection.rest.token\n\ntxn_fv = fs.get_feature_view(\"TRANSACTION_FRAUD_FEATURES\", FEATURE_VIEW_VERSION)\nprint(f\"Store type: {txn_fv.online_config.store_type}\")\nprint(f\"Target lag: {txn_fv.online_config.target_lag}\")\n\ntest_keys = [[\"TXN_0001\"], [\"TXN_0010\"], [\"TXN_0050\"]]\n\nonline_result = fs.read_feature_view(\n    txn_fv,\n    keys=test_keys,\n    store_type=StoreType.ONLINE\n)\nprint(\"\\nOnline (Postgres) feature retrieval successful!\")\nonline_result\n```\n\nYou should see the feature values returned from the Postgres online store for each transaction ID. If you receive a connection or authentication error, verify that `SNOWFLAKE_PAT` is set correctly in your environment.\n\n\u003C!-- ------------------------ --\u003E\n## Part III: Train, Register, and Deploy Model\n\nThis section trains an XGBoost fraud detection model, registers it in the Snowflake Model Registry, and deploys it as a real-time inference service on SPCS with automatic feature lookup from the online store.\n\n### Train XGBoost Model\n\nTrain an XGBoost binary classifier on the synthetic fraud data:\n\n```python\nimport xgboost as xgb\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, roc_auc_score\n\nFEATURES = [\"TRANSACTION_AMOUNT\", \"MERCHANT_CATEGORY\", \"DISTANCE_FROM_HOME\", \n    \"TIME_SINCE_LAST_TXN\", \"DAILY_TXN_COUNT\"]\nTARGET = \"IS_FRAUD\"\n\nX = data[FEATURES]\ny = data[TARGET]\n\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n\nmodel = xgb.XGBClassifier(\n    n_estimators=50,\n    max_depth=4,\n    learning_rate=0.1,\n    use_label_encoder=False,\n    eval_metric=\"logloss\",\n    random_state=42\n)\nmodel.fit(X_train, y_train)\n\ny_pred = model.predict(X_test)\ny_proba = model.predict_proba(X_test)[:, 1]\nprint(f\"Accuracy: {accuracy_score(y_test, y_pred):.3f}\")\nprint(f\"ROC AUC:  {roc_auc_score(y_test, y_proba):.3f}\")\n```\n\n### Register Model in Snowflake Model Registry\n\nLog the trained model to the Snowflake Model Registry with its input signature. This lets the inference service know which features the model expects:\n\n```python\nfrom snowflake.ml.registry import Registry\n\nreg = Registry(session=session, database_name=\"ML_DEMO_INF_FS_LOOKUP\", schema_name=\"FRAUD_ML_SERVICE_FS\")\n\ntry:\n    mv = reg.get_model(MODEL_NAME).version(MODEL_VERSION)\n    print(f\"Using existing model: {MODEL_NAME}/{MODEL_VERSION}\")\nexcept Exception:\n    sample_input = X_train.head(5)\n    mv = reg.log_model(\n        model_name=MODEL_NAME,\n        version_name=MODEL_VERSION,\n        model=model,\n        sample_input_data=sample_input,\n        conda_dependencies=[\"xgboost\"],\n    )\n    print(f\"Model registered: {MODEL_NAME} version {MODEL_VERSION}\")\n\nfuncs = mv.show_functions()\nprint(f\"Functions: {[f['name'] if isinstance(f, dict) else str(f) for f in funcs]}\")\n```\n\n### Create Compute Pool\n\nCreate a compute pool for online inference. The pool provisions nodes that will host the containerized model service:\n\n```sql\nCREATE COMPUTE POOL IF NOT EXISTS ML_ONLINE_CPU_POOL\n  MIN_NODES = 1\n  MAX_NODES = 3\n  INSTANCE_FAMILY = CPU_X64_S\n  AUTO_RESUME = TRUE\n  AUTO_SUSPEND_SECS = 300\n  COMMENT = 'Compute pool for fraud detection inference service';\n\nDESCRIBE COMPUTE POOL ML_ONLINE_CPU_POOL;\n```\n\n### Deploy Inference Service with Feature Store Lookup\n\nDeploy the model as a real-time HTTP service on SPCS. The key parameter is `feature_sources_per_function` — it maps the `predict` method to the Postgres-backed Feature View so the service **automatically fetches features** when callers send only `TRANSACTION_ID`:\n\n```python\ntxn_fv = fs.get_feature_view(\"TRANSACTION_FRAUD_FEATURES\", FEATURE_VIEW_VERSION)\n\nmv.create_service(\n    service_name=SERVICE_NAME,\n    service_compute_pool=COMPUTE_POOL_NAME,\n    ingress_enabled=True,\n    feature_sources_per_function={\"predict\": [txn_fv]},\n)\nprint(f\"Service deployment initiated: {SERVICE_NAME}\")\nprint(f\"The service will auto-lookup features from TRANSACTION_FRAUD_FEATURES/{FEATURE_VIEW_VERSION} (Postgres online store)\")\n```\n\nVerify the service is running:\n\n```python\nservices_df = mv.list_services()\nprint(\"Active services:\")\nprint(services_df.to_string())\n```\n\n\u003E aside negative\n\u003E The service may take a few minutes to start. If it shows as `PENDING`, wait and re-run the verification cell.\n\n\u003C!-- ------------------------ --\u003E\n## Part IV: Test the Inference Endpoint\n\nIn this section, you'll test the inference service by calling its **public REST endpoint from outside Snowflake** — simulating how a production client (web app, microservice, Postman, or curl) would invoke it. This is not done from within the Snowsight UI; instead, you'll authenticate with a Programmatic Access Token (PAT) and make HTTP requests directly to the SPCS ingress endpoint.\n\n### How It Works\n\nThe `feature_sources_per_function` parameter enables the inference service to **automatically look up features** from the online Postgres Feature Store. Here's what happens under the hood:\n\n1. You send only `TRANSACTION_ID` values to the REST endpoint\n2. The Snowflake ingress gateway intercepts the request\n3. It fetches `TRANSACTION_AMOUNT`, `MERCHANT_CATEGORY`, `DISTANCE_FROM_HOME`, `TIME_SINCE_LAST_TXN`, `DAILY_TXN_COUNT` from the Postgres online store\n4. The enriched payload is forwarded to the XGBoost model container\n5. Fraud predictions are returned\n\n### Set Up Authentication\n\nTo invoke the service from outside Snowflake, you need a PAT:\n\n1. In Snowsight, click your username (top-left) \u003E **My Profile** \u003E **Authentication** \u003E **Personal Access Tokens** \u003E **+ Generate**\n2. Grant the delegated authorization:\n\n```sql\nALTER USER \u003Cyour_user\u003E ADD DELEGATED AUTHORIZATION\n  OF ROLE ACCOUNTADMIN TO SECURITY INTEGRATION SNOWSERVICES_INGRESS_OAUTH;\n```\n\n3. **(If applicable) Whitelist your external IP in the account's network policy.** If your Snowflake account has an active [network policy](https://docs.snowflake.com/en/user-guide/network-policies), external requests to the SPCS ingress endpoint will be blocked unless the caller's IP is in the allowed list. Create a network rule and add it to your policy:\n\n```sql\nCREATE NETWORK RULE IF NOT EXISTS allow_external_inference\n  TYPE = IPV4\n  MODE = INGRESS\n  VALUE_LIST = ('\u003CYOUR_PUBLIC_IP\u003E/32');\n\nALTER NETWORK POLICY \u003Cyour_policy_name\u003E\n  ADD ALLOWED_NETWORK_RULE_LIST = ('allow_external_inference');\n```\n\n\u003E aside positive\n\u003E You can find your public IP by running `curl ifconfig.me` from your terminal. If no network policy is active on the account, this step can be skipped — Snowflake allows access from all IPs by default.\n\n### Call the Service\n\nFirst, retrieve the ingress endpoint URL for your service:\n\n```sql\nSHOW ENDPOINTS IN SERVICE ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS.FRAUD_DETECTION_SVC;\n```\n\nCopy the `ingress_url` value from the result — this is your `\u003CENDPOINT_URL\u003E`.\n\nFrom any external client (curl, Python requests, web app), invoke the service by sending only entity keys:\n\n```bash\ncurl -X POST \"https://\u003CENDPOINT_URL\u003E/predict\" \\\n  -H 'Authorization: Snowflake Token=\"\u003CPAT_TOKEN\u003E\"' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"dataframe_split\": {\"index\": [0, 1, 2], \"columns\": [\"TRANSACTION_ID\"], \"data\": [[\"TXN_0001\"], [\"TXN_0010\"], [\"TXN_0050\"]]}}'\n```\n\nReplace `\u003CENDPOINT_URL\u003E` with the `ingress_url` from the query above and `\u003CPAT_TOKEN\u003E` with the PAT you generated.\n\nThe response will contain fraud predictions for each transaction — all without the caller needing to know which features exist or how they're computed.\n\n\u003E aside positive\n\u003E **Important:** The Python SDK (`mv.run()`) and SQL service functions do not trigger this feature enrichment — they bypass the gateway. This feature is designed for external real-time inference clients (web apps, microservices, mobile backends) that call the REST endpoint.\n\n\u003C!-- ------------------------ --\u003E\n## Cleanup\n\nWhen you're done experimenting, clean up the resources:\n\n```sql\nDROP SERVICE IF EXISTS ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS.FRAUD_DETECTION_SVC;\nDROP MODEL IF EXISTS ML_DEMO_INF_FS_LOOKUP.FRAUD_ML_SERVICE_FS.FRAUD_XGBOOST;\nDROP COMPUTE POOL IF EXISTS ML_ONLINE_CPU_POOL;\nDROP DATABASE IF EXISTS ML_DEMO_INF_FS_LOOKUP;\n```\n\n\u003C!-- ------------------------ --\u003E\n## Conclusion and Resources\n\nCongratulations! You've built a complete real-time ML inference pipeline that eliminates training-serving skew by using Snowflake's Feature Store as a single source of truth.\n\n### What You Learned\n- How Snowflake's Postgres-backed Online Feature Store provides millisecond-level feature retrieval\n- How to register entities and feature views with configurable online serving lag\n- How to train and register models in the Snowflake Model Registry\n- How to deploy inference services with `feature_sources_per_function` for automatic feature enrichment\n- How external clients can get predictions by sending only entity keys\n\n### Key Takeaways\n- **No training-serving skew**: The same Feature View serves both training and inference\n- **Decoupled clients**: API consumers send only an entity key — no need to know which features exist or how they're computed\n- **Low-latency**: Postgres online store provides millisecond-level feature retrieval\n- **Auto-fresh**: Features sync with configurable target lag (10s in this demo)\n- **Simplified contracts**: Adding or modifying features doesn't require API changes\n\n### Related Resources\n- [Snowflake ML Inference Service Documentation](https://docs.snowflake.com/en/developer-guide/snowflake-ml/inference/real-time-inference-rest-api)\n- [Snowflake Feature Store Documentation](https://docs.snowflake.com/en/developer-guide/snowflake-ml/feature-store/overview)\n- [Snowflake Model Registry Documentation](https://docs.snowflake.com/en/developer-guide/snowflake-ml/model-registry/overview)",":type":"text/x-markdown","multiValue":false},"quickstartArticleLogoImage":{"dataType":"string","title":"Quickstart Article Logo Image",":type":"text/plain","multiValue":false}},"elementsOrder":["quickstartArticleBody","quickstartArticleLogoImage"],"model":"snowflake-site/models/quickstart-article"},"flexible_column_cont":{"id":"flexible-column-container-d6f382d14e","type":"2-column-75-25","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"none","bottomPadding":"none","spaceBetween":"none","reverseOnMobile":false,"carouselOnMobile":false,"backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-8df1564974",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"quickstart_last_modi":{"id":"quickstart-last-modified-3d7081af9e","icon":{"id":"icon","icon":"calendar",":type":"snowflake-site/components/icon","appliedCssClassNames":"snowflake-icon-blue"},"lastModifiedDatePrefix":"Updated","lastModifiedDate":"2026-08-05",":type":"snowflake-site/components/quickstart/quickstart-last-modified","appliedCssClassNames":"snowflake-responsive-component-top-padding-small"},"text":{"id":"text-ad9fe58190","additionalClasses":"qs-disclaimer-text","text":"\u003Cp\u003E\u003Cspan style=\"color: #666;\"\u003EThis content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances\u003C/span\u003E\u003C/p\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"snowflake-responsive-component-top-padding-small"}},":itemsOrder":["quickstart_last_modi","text"]},"flexible_column_content_container_2":{"layout":"SIMPLE","id":"container-be86432cf1",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{},":itemsOrder":[]},"isBlogPage":false,"isActiveTOC":false,":type":"snowflake-site/components/flexible-column-container"}},":itemsOrder":["contentfragment","flexible_column_cont"]},"flexible_column_content_container_2":{"layout":"SIMPLE","id":"container-39c691f614",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"quickstart_table_of_":{"layout":"SIMPLE","id":"container-24f7fa3acd","isDeveloperGuidesPage":false,":type":"snowflake-site/components/quickstart/quickstart-table-of-content/quickstart-table-of-content-container",":items":{"quickstart_table_of_":{"id":"quickstart-table-of-content-b7ada65a90",":type":"snowflake-site/components/quickstart/quickstart-table-of-content","fragmentPath":"/content/dam/snowflake-site/en/content-fragments/quickstarts/build-real-time-ml-inference-with-feature-store","headings":["\u003Ch2\u003EOverview\u003C/h2\u003E","\u003Ch2\u003EPart I: Environment Setup\u003C/h2\u003E","\u003Ch2\u003EPart II: Feature Store with Online Serving\u003C/h2\u003E","\u003Ch2\u003EPart III: Train, Register, and Deploy Model\u003C/h2\u003E","\u003Ch2\u003EPart IV: Test the Inference Endpoint\u003C/h2\u003E","\u003Ch2\u003ECleanup\u003C/h2\u003E","\u003Ch2\u003EConclusion and Resources\u003C/h2\u003E"]},"quickstart_button":{"id":"quickstart-button-8c354d197e",":type":"snowflake-site/components/quickstart/quickstart-button","fragmentPath":"/content/dam/snowflake-site/en/content-fragments/quickstarts/build-real-time-ml-inference-with-feature-store","appliedCssClassNames":"snowflake-responsive-component-top-padding-none"}},":itemsOrder":["quickstart_table_of_","quickstart_button"]}},":itemsOrder":["quickstart_table_of_"]},"isBlogPage":false,"isActiveTOC":false,":type":"snowflake-site/components/flexible-column-container"},"markup_editor":{"id":"markup-editor-fa81893b88","title":"Page CSS","cssContent":"#quickstart-template-main-flexible-container{padding:24px}#quickstart-template-main-flexible-container \u003E .snowflake-flexible-column-container-items{grid-template-columns:1fr 0}.qs-disclaimer-text p \u003E span{font-size:15px !important}@media (min-width:768px){#quickstart-template-main-flexible-container{padding:24px 32px}#quickstart-template-main-flexible-container \u003E .snowflake-flexible-column-container-items{grid-template-columns:7fr 3fr;gap:48px}}@media (max-width:767px){#quickstart-template-main-flexible-container \u003E .snowflake-flexible-column-container-items{gap:0}}@media (min-width:1024px){#quickstart-template-main-flexible-container{padding:0 92px 48px 92px}#quickstart-template-main-flexible-container \u003E .snowflake-flexible-column-container-items{gap:117px}}","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"}},":itemsOrder":["quickstart_hero","flexible_column_cont","markup_editor"],":type":"wcm/foundation/components/responsivegrid"},"modal_container":{"layout":"SIMPLE","id":"container-faa87d8324",":type":"snowflake-site/components/modal/modal-container",":items":{},":itemsOrder":[]},"experiencefragment-footer":{"id":"experiencefragment-2c5e5c3e3d","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/footer/master/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment","xfModelPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/footer/master.xfmodel.json"},"markup_editor":{"id":"markup-editor-a04657e4f0","title":"Quickstarts Overrides","cssContent":".snowflake-markdown blockquote{padding:24px 32px;background:#f6f9fa;border:1px solid #29b5e8;border-radius:16px}.snowflake-markdown .snowflake-image-container img{width:auto !important;max-width:100%}.snowflake-markdown .snowflake-text ol{padding-left:20px !important}.snowflake-markdown .snowflake-text li{margin:0 0 12px 0 !important}.snowflake-markdown h3.snowflake-markdown-h3{font-size:20px !important;font-family:Texta,sans-serif !important}@media (min-width:768px){.snowflake-markdown h3.snowflake-markdown-h3{font-size:28px !important}}","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"}},":itemsOrder":["experiencefragment-banner","experiencefragment-header","markup_editor_1950346551","responsivegrid","modal_container","experiencefragment-footer","markup_editor"],":type":"wcm/foundation/components/responsivegrid"}},":itemsOrder":["root"],"locale":"en"}
  