Configure GCP Vertex AI Feature Online Store Feature Views

The gcp:vertex/aiFeatureOnlineStoreFeatureview:AiFeatureOnlineStoreFeatureview resource, part of the Pulumi GCP provider, defines how features are synced from BigQuery or Feature Registry into a FeatureOnlineStore for low-latency serving. This guide focuses on three capabilities: BigQuery source configuration, Feature Registry source configuration, and vector search indexing for embeddings.

FeatureViews belong to a FeatureOnlineStore and reference BigQuery tables or Feature Registry resources that must exist separately. The examples are intentionally small. Combine them with your own FeatureOnlineStore, BigQuery tables, and Feature Groups.

Sync features from BigQuery on a schedule

ML pipelines often store feature data in BigQuery tables and need to serve those features with low latency for online prediction. FeatureViews sync data from BigQuery into a FeatureOnlineStore on a schedule.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const featureonlinestore = new gcp.vertex.AiFeatureOnlineStore("featureonlinestore", {
    name: "example_feature_view",
    labels: {
        foo: "bar",
    },
    region: "us-central1",
    bigtable: {
        autoScaling: {
            minNodeCount: 1,
            maxNodeCount: 2,
            cpuUtilizationTarget: 80,
        },
    },
});
const tf_test_dataset = new gcp.bigquery.Dataset("tf-test-dataset", {
    datasetId: "example_feature_view",
    friendlyName: "test",
    description: "This is a test description",
    location: "US",
});
const tf_test_table = new gcp.bigquery.Table("tf-test-table", {
    deletionProtection: false,
    datasetId: tf_test_dataset.datasetId,
    tableId: "example_feature_view",
    schema: `  [
  {
    \\"name\\": \\"entity_id\\",
    \\"mode\\": \\"NULLABLE\\",
    \\"type\\": \\"STRING\\",
    \\"description\\": \\"Test default entity_id\\"
  },
    {
    \\"name\\": \\"test_entity_column\\",
    \\"mode\\": \\"NULLABLE\\",
    \\"type\\": \\"STRING\\",
    \\"description\\": \\"test secondary entity column\\"
  },
  {
    \\"name\\": \\"feature_timestamp\\",
    \\"mode\\": \\"NULLABLE\\",
    \\"type\\": \\"TIMESTAMP\\",
    \\"description\\": \\"Default timestamp value\\"
  }
]
`,
});
const featureview = new gcp.vertex.AiFeatureOnlineStoreFeatureview("featureview", {
    name: "example_feature_view",
    region: "us-central1",
    featureOnlineStore: featureonlinestore.name,
    syncConfig: {
        cron: "0 0 * * *",
    },
    bigQuerySource: {
        uri: pulumi.interpolate`bq://${tf_test_table.project}.${tf_test_table.datasetId}.${tf_test_table.tableId}`,
        entityIdColumns: ["test_entity_column"],
    },
});
const project = gcp.organizations.getProject({});
import pulumi
import pulumi_gcp as gcp

featureonlinestore = gcp.vertex.AiFeatureOnlineStore("featureonlinestore",
    name="example_feature_view",
    labels={
        "foo": "bar",
    },
    region="us-central1",
    bigtable={
        "auto_scaling": {
            "min_node_count": 1,
            "max_node_count": 2,
            "cpu_utilization_target": 80,
        },
    })
tf_test_dataset = gcp.bigquery.Dataset("tf-test-dataset",
    dataset_id="example_feature_view",
    friendly_name="test",
    description="This is a test description",
    location="US")
tf_test_table = gcp.bigquery.Table("tf-test-table",
    deletion_protection=False,
    dataset_id=tf_test_dataset.dataset_id,
    table_id="example_feature_view",
    schema="""  [
  {
    \"name\": \"entity_id\",
    \"mode\": \"NULLABLE\",
    \"type\": \"STRING\",
    \"description\": \"Test default entity_id\"
  },
    {
    \"name\": \"test_entity_column\",
    \"mode\": \"NULLABLE\",
    \"type\": \"STRING\",
    \"description\": \"test secondary entity column\"
  },
  {
    \"name\": \"feature_timestamp\",
    \"mode\": \"NULLABLE\",
    \"type\": \"TIMESTAMP\",
    \"description\": \"Default timestamp value\"
  }
]
""")
featureview = gcp.vertex.AiFeatureOnlineStoreFeatureview("featureview",
    name="example_feature_view",
    region="us-central1",
    feature_online_store=featureonlinestore.name,
    sync_config={
        "cron": "0 0 * * *",
    },
    big_query_source={
        "uri": pulumi.Output.all(
            project=tf_test_table.project,
            dataset_id=tf_test_table.dataset_id,
            table_id=tf_test_table.table_id
).apply(lambda resolved_outputs: f"bq://{resolved_outputs['project']}.{resolved_outputs['dataset_id']}.{resolved_outputs['table_id']}")
,
        "entity_id_columns": ["test_entity_column"],
    })
project = gcp.organizations.get_project()
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/bigquery"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/vertex"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		featureonlinestore, err := vertex.NewAiFeatureOnlineStore(ctx, "featureonlinestore", &vertex.AiFeatureOnlineStoreArgs{
			Name: pulumi.String("example_feature_view"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
			Region: pulumi.String("us-central1"),
			Bigtable: &vertex.AiFeatureOnlineStoreBigtableArgs{
				AutoScaling: &vertex.AiFeatureOnlineStoreBigtableAutoScalingArgs{
					MinNodeCount:         pulumi.Int(1),
					MaxNodeCount:         pulumi.Int(2),
					CpuUtilizationTarget: pulumi.Int(80),
				},
			},
		})
		if err != nil {
			return err
		}
		tf_test_dataset, err := bigquery.NewDataset(ctx, "tf-test-dataset", &bigquery.DatasetArgs{
			DatasetId:    pulumi.String("example_feature_view"),
			FriendlyName: pulumi.String("test"),
			Description:  pulumi.String("This is a test description"),
			Location:     pulumi.String("US"),
		})
		if err != nil {
			return err
		}
		tf_test_table, err := bigquery.NewTable(ctx, "tf-test-table", &bigquery.TableArgs{
			DeletionProtection: pulumi.Bool(false),
			DatasetId:          tf_test_dataset.DatasetId,
			TableId:            pulumi.String("example_feature_view"),
			Schema: pulumi.String(`  [
  {
    \"name\": \"entity_id\",
    \"mode\": \"NULLABLE\",
    \"type\": \"STRING\",
    \"description\": \"Test default entity_id\"
  },
    {
    \"name\": \"test_entity_column\",
    \"mode\": \"NULLABLE\",
    \"type\": \"STRING\",
    \"description\": \"test secondary entity column\"
  },
  {
    \"name\": \"feature_timestamp\",
    \"mode\": \"NULLABLE\",
    \"type\": \"TIMESTAMP\",
    \"description\": \"Default timestamp value\"
  }
]
`),
		})
		if err != nil {
			return err
		}
		_, err = vertex.NewAiFeatureOnlineStoreFeatureview(ctx, "featureview", &vertex.AiFeatureOnlineStoreFeatureviewArgs{
			Name:               pulumi.String("example_feature_view"),
			Region:             pulumi.String("us-central1"),
			FeatureOnlineStore: featureonlinestore.Name,
			SyncConfig: &vertex.AiFeatureOnlineStoreFeatureviewSyncConfigArgs{
				Cron: pulumi.String("0 0 * * *"),
			},
			BigQuerySource: &vertex.AiFeatureOnlineStoreFeatureviewBigQuerySourceArgs{
				Uri: pulumi.All(tf_test_table.Project, tf_test_table.DatasetId, tf_test_table.TableId).ApplyT(func(_args []interface{}) (string, error) {
					project := _args[0].(string)
					datasetId := _args[1].(string)
					tableId := _args[2].(string)
					return fmt.Sprintf("bq://%v.%v.%v", project, datasetId, tableId), nil
				}).(pulumi.StringOutput),
				EntityIdColumns: pulumi.StringArray{
					pulumi.String("test_entity_column"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var featureonlinestore = new Gcp.Vertex.AiFeatureOnlineStore("featureonlinestore", new()
    {
        Name = "example_feature_view",
        Labels = 
        {
            { "foo", "bar" },
        },
        Region = "us-central1",
        Bigtable = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreBigtableArgs
        {
            AutoScaling = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreBigtableAutoScalingArgs
            {
                MinNodeCount = 1,
                MaxNodeCount = 2,
                CpuUtilizationTarget = 80,
            },
        },
    });

    var tf_test_dataset = new Gcp.BigQuery.Dataset("tf-test-dataset", new()
    {
        DatasetId = "example_feature_view",
        FriendlyName = "test",
        Description = "This is a test description",
        Location = "US",
    });

    var tf_test_table = new Gcp.BigQuery.Table("tf-test-table", new()
    {
        DeletionProtection = false,
        DatasetId = tf_test_dataset.DatasetId,
        TableId = "example_feature_view",
        Schema = @"  [
  {
    \""name\"": \""entity_id\"",
    \""mode\"": \""NULLABLE\"",
    \""type\"": \""STRING\"",
    \""description\"": \""Test default entity_id\""
  },
    {
    \""name\"": \""test_entity_column\"",
    \""mode\"": \""NULLABLE\"",
    \""type\"": \""STRING\"",
    \""description\"": \""test secondary entity column\""
  },
  {
    \""name\"": \""feature_timestamp\"",
    \""mode\"": \""NULLABLE\"",
    \""type\"": \""TIMESTAMP\"",
    \""description\"": \""Default timestamp value\""
  }
]
",
    });

    var featureview = new Gcp.Vertex.AiFeatureOnlineStoreFeatureview("featureview", new()
    {
        Name = "example_feature_view",
        Region = "us-central1",
        FeatureOnlineStore = featureonlinestore.Name,
        SyncConfig = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewSyncConfigArgs
        {
            Cron = "0 0 * * *",
        },
        BigQuerySource = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewBigQuerySourceArgs
        {
            Uri = Output.Tuple(tf_test_table.Project, tf_test_table.DatasetId, tf_test_table.TableId).Apply(values =>
            {
                var project = values.Item1;
                var datasetId = values.Item2;
                var tableId = values.Item3;
                return $"bq://{project}.{datasetId}.{tableId}";
            }),
            EntityIdColumns = new[]
            {
                "test_entity_column",
            },
        },
    });

    var project = Gcp.Organizations.GetProject.Invoke();

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.vertex.AiFeatureOnlineStore;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreBigtableArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreBigtableAutoScalingArgs;
import com.pulumi.gcp.bigquery.Dataset;
import com.pulumi.gcp.bigquery.DatasetArgs;
import com.pulumi.gcp.bigquery.Table;
import com.pulumi.gcp.bigquery.TableArgs;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreFeatureview;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreFeatureviewArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreFeatureviewSyncConfigArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreFeatureviewBigQuerySourceArgs;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var featureonlinestore = new AiFeatureOnlineStore("featureonlinestore", AiFeatureOnlineStoreArgs.builder()
            .name("example_feature_view")
            .labels(Map.of("foo", "bar"))
            .region("us-central1")
            .bigtable(AiFeatureOnlineStoreBigtableArgs.builder()
                .autoScaling(AiFeatureOnlineStoreBigtableAutoScalingArgs.builder()
                    .minNodeCount(1)
                    .maxNodeCount(2)
                    .cpuUtilizationTarget(80)
                    .build())
                .build())
            .build());

        var tf_test_dataset = new Dataset("tf-test-dataset", DatasetArgs.builder()
            .datasetId("example_feature_view")
            .friendlyName("test")
            .description("This is a test description")
            .location("US")
            .build());

        var tf_test_table = new Table("tf-test-table", TableArgs.builder()
            .deletionProtection(false)
            .datasetId(tf_test_dataset.datasetId())
            .tableId("example_feature_view")
            .schema("""
  [
  {
    \"name\": \"entity_id\",
    \"mode\": \"NULLABLE\",
    \"type\": \"STRING\",
    \"description\": \"Test default entity_id\"
  },
    {
    \"name\": \"test_entity_column\",
    \"mode\": \"NULLABLE\",
    \"type\": \"STRING\",
    \"description\": \"test secondary entity column\"
  },
  {
    \"name\": \"feature_timestamp\",
    \"mode\": \"NULLABLE\",
    \"type\": \"TIMESTAMP\",
    \"description\": \"Default timestamp value\"
  }
]
            """)
            .build());

        var featureview = new AiFeatureOnlineStoreFeatureview("featureview", AiFeatureOnlineStoreFeatureviewArgs.builder()
            .name("example_feature_view")
            .region("us-central1")
            .featureOnlineStore(featureonlinestore.name())
            .syncConfig(AiFeatureOnlineStoreFeatureviewSyncConfigArgs.builder()
                .cron("0 0 * * *")
                .build())
            .bigQuerySource(AiFeatureOnlineStoreFeatureviewBigQuerySourceArgs.builder()
                .uri(Output.tuple(tf_test_table.project(), tf_test_table.datasetId(), tf_test_table.tableId()).applyValue(values -> {
                    var project = values.t1;
                    var datasetId = values.t2;
                    var tableId = values.t3;
                    return String.format("bq://%s.%s.%s", project,datasetId,tableId);
                }))
                .entityIdColumns("test_entity_column")
                .build())
            .build());

        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

    }
}
resources:
  featureonlinestore:
    type: gcp:vertex:AiFeatureOnlineStore
    properties:
      name: example_feature_view
      labels:
        foo: bar
      region: us-central1
      bigtable:
        autoScaling:
          minNodeCount: 1
          maxNodeCount: 2
          cpuUtilizationTarget: 80
  tf-test-dataset:
    type: gcp:bigquery:Dataset
    properties:
      datasetId: example_feature_view
      friendlyName: test
      description: This is a test description
      location: US
  tf-test-table:
    type: gcp:bigquery:Table
    properties:
      deletionProtection: false
      datasetId: ${["tf-test-dataset"].datasetId}
      tableId: example_feature_view
      schema: |2
          [
          {
            \"name\": \"entity_id\",
            \"mode\": \"NULLABLE\",
            \"type\": \"STRING\",
            \"description\": \"Test default entity_id\"
          },
            {
            \"name\": \"test_entity_column\",
            \"mode\": \"NULLABLE\",
            \"type\": \"STRING\",
            \"description\": \"test secondary entity column\"
          },
          {
            \"name\": \"feature_timestamp\",
            \"mode\": \"NULLABLE\",
            \"type\": \"TIMESTAMP\",
            \"description\": \"Default timestamp value\"
          }
        ]
  featureview:
    type: gcp:vertex:AiFeatureOnlineStoreFeatureview
    properties:
      name: example_feature_view
      region: us-central1
      featureOnlineStore: ${featureonlinestore.name}
      syncConfig:
        cron: 0 0 * * *
      bigQuerySource:
        uri: bq://${["tf-test-table"].project}.${["tf-test-table"].datasetId}.${["tf-test-table"].tableId}
        entityIdColumns:
          - test_entity_column
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The bigQuerySource property points to your BigQuery table using a bq:// URI format. The entityIdColumns property specifies which columns identify unique entities for feature lookup. The syncConfig property controls when data is refreshed; here, the cron expression 0 0 * * * syncs daily at midnight UTC. At each sync, Vertex AI reads the latest feature values from BigQuery and updates the FeatureOnlineStore for online serving.

Sync features from Feature Registry

Teams using Vertex AI Feature Store organize features into Feature Groups for reuse across models. FeatureViews can sync specific features from the Feature Registry rather than directly from BigQuery.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const featureonlinestore = new gcp.vertex.AiFeatureOnlineStore("featureonlinestore", {
    name: "example_feature_view_feature_registry",
    labels: {
        foo: "bar",
    },
    region: "us-central1",
    bigtable: {
        autoScaling: {
            minNodeCount: 1,
            maxNodeCount: 2,
            cpuUtilizationTarget: 80,
        },
    },
});
const sampleDataset = new gcp.bigquery.Dataset("sample_dataset", {
    datasetId: "example_feature_view_feature_registry",
    friendlyName: "test",
    description: "This is a test description",
    location: "US",
});
const sampleTable = new gcp.bigquery.Table("sample_table", {
    deletionProtection: false,
    datasetId: sampleDataset.datasetId,
    tableId: "example_feature_view_feature_registry",
    schema: `[
    {
        \\"name\\": \\"feature_id\\",
        \\"type\\": \\"STRING\\",
        \\"mode\\": \\"NULLABLE\\"
    },
    {
        \\"name\\": \\"example_feature_view_feature_registry\\",
        \\"type\\": \\"STRING\\",
        \\"mode\\": \\"NULLABLE\\"
    },
    {
        \\"name\\": \\"feature_timestamp\\",
        \\"type\\": \\"TIMESTAMP\\",
        \\"mode\\": \\"NULLABLE\\"
    }
]
`,
});
const sampleFeatureGroup = new gcp.vertex.AiFeatureGroup("sample_feature_group", {
    name: "example_feature_view_feature_registry",
    description: "A sample feature group",
    region: "us-central1",
    labels: {
        "label-one": "value-one",
    },
    bigQuery: {
        bigQuerySource: {
            inputUri: pulumi.interpolate`bq://${sampleTable.project}.${sampleTable.datasetId}.${sampleTable.tableId}`,
        },
        entityIdColumns: ["feature_id"],
    },
});
const sampleFeature = new gcp.vertex.AiFeatureGroupFeature("sample_feature", {
    name: "example_feature_view_feature_registry",
    region: "us-central1",
    featureGroup: sampleFeatureGroup.name,
    description: "A sample feature",
    labels: {
        "label-one": "value-one",
    },
});
const featureviewFeatureregistry = new gcp.vertex.AiFeatureOnlineStoreFeatureview("featureview_featureregistry", {
    name: "example_feature_view_feature_registry",
    region: "us-central1",
    featureOnlineStore: featureonlinestore.name,
    syncConfig: {
        cron: "0 0 * * *",
    },
    featureRegistrySource: {
        featureGroups: [{
            featureGroupId: sampleFeatureGroup.name,
            featureIds: [sampleFeature.name],
        }],
    },
});
import pulumi
import pulumi_gcp as gcp

featureonlinestore = gcp.vertex.AiFeatureOnlineStore("featureonlinestore",
    name="example_feature_view_feature_registry",
    labels={
        "foo": "bar",
    },
    region="us-central1",
    bigtable={
        "auto_scaling": {
            "min_node_count": 1,
            "max_node_count": 2,
            "cpu_utilization_target": 80,
        },
    })
sample_dataset = gcp.bigquery.Dataset("sample_dataset",
    dataset_id="example_feature_view_feature_registry",
    friendly_name="test",
    description="This is a test description",
    location="US")
sample_table = gcp.bigquery.Table("sample_table",
    deletion_protection=False,
    dataset_id=sample_dataset.dataset_id,
    table_id="example_feature_view_feature_registry",
    schema="""[
    {
        \"name\": \"feature_id\",
        \"type\": \"STRING\",
        \"mode\": \"NULLABLE\"
    },
    {
        \"name\": \"example_feature_view_feature_registry\",
        \"type\": \"STRING\",
        \"mode\": \"NULLABLE\"
    },
    {
        \"name\": \"feature_timestamp\",
        \"type\": \"TIMESTAMP\",
        \"mode\": \"NULLABLE\"
    }
]
""")
sample_feature_group = gcp.vertex.AiFeatureGroup("sample_feature_group",
    name="example_feature_view_feature_registry",
    description="A sample feature group",
    region="us-central1",
    labels={
        "label-one": "value-one",
    },
    big_query={
        "big_query_source": {
            "input_uri": pulumi.Output.all(
                project=sample_table.project,
                dataset_id=sample_table.dataset_id,
                table_id=sample_table.table_id
).apply(lambda resolved_outputs: f"bq://{resolved_outputs['project']}.{resolved_outputs['dataset_id']}.{resolved_outputs['table_id']}")
,
        },
        "entity_id_columns": ["feature_id"],
    })
sample_feature = gcp.vertex.AiFeatureGroupFeature("sample_feature",
    name="example_feature_view_feature_registry",
    region="us-central1",
    feature_group=sample_feature_group.name,
    description="A sample feature",
    labels={
        "label-one": "value-one",
    })
featureview_featureregistry = gcp.vertex.AiFeatureOnlineStoreFeatureview("featureview_featureregistry",
    name="example_feature_view_feature_registry",
    region="us-central1",
    feature_online_store=featureonlinestore.name,
    sync_config={
        "cron": "0 0 * * *",
    },
    feature_registry_source={
        "feature_groups": [{
            "feature_group_id": sample_feature_group.name,
            "feature_ids": [sample_feature.name],
        }],
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/bigquery"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/vertex"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		featureonlinestore, err := vertex.NewAiFeatureOnlineStore(ctx, "featureonlinestore", &vertex.AiFeatureOnlineStoreArgs{
			Name: pulumi.String("example_feature_view_feature_registry"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
			Region: pulumi.String("us-central1"),
			Bigtable: &vertex.AiFeatureOnlineStoreBigtableArgs{
				AutoScaling: &vertex.AiFeatureOnlineStoreBigtableAutoScalingArgs{
					MinNodeCount:         pulumi.Int(1),
					MaxNodeCount:         pulumi.Int(2),
					CpuUtilizationTarget: pulumi.Int(80),
				},
			},
		})
		if err != nil {
			return err
		}
		sampleDataset, err := bigquery.NewDataset(ctx, "sample_dataset", &bigquery.DatasetArgs{
			DatasetId:    pulumi.String("example_feature_view_feature_registry"),
			FriendlyName: pulumi.String("test"),
			Description:  pulumi.String("This is a test description"),
			Location:     pulumi.String("US"),
		})
		if err != nil {
			return err
		}
		sampleTable, err := bigquery.NewTable(ctx, "sample_table", &bigquery.TableArgs{
			DeletionProtection: pulumi.Bool(false),
			DatasetId:          sampleDataset.DatasetId,
			TableId:            pulumi.String("example_feature_view_feature_registry"),
			Schema: pulumi.String(`[
    {
        \"name\": \"feature_id\",
        \"type\": \"STRING\",
        \"mode\": \"NULLABLE\"
    },
    {
        \"name\": \"example_feature_view_feature_registry\",
        \"type\": \"STRING\",
        \"mode\": \"NULLABLE\"
    },
    {
        \"name\": \"feature_timestamp\",
        \"type\": \"TIMESTAMP\",
        \"mode\": \"NULLABLE\"
    }
]
`),
		})
		if err != nil {
			return err
		}
		sampleFeatureGroup, err := vertex.NewAiFeatureGroup(ctx, "sample_feature_group", &vertex.AiFeatureGroupArgs{
			Name:        pulumi.String("example_feature_view_feature_registry"),
			Description: pulumi.String("A sample feature group"),
			Region:      pulumi.String("us-central1"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			BigQuery: &vertex.AiFeatureGroupBigQueryArgs{
				BigQuerySource: &vertex.AiFeatureGroupBigQueryBigQuerySourceArgs{
					InputUri: pulumi.All(sampleTable.Project, sampleTable.DatasetId, sampleTable.TableId).ApplyT(func(_args []interface{}) (string, error) {
						project := _args[0].(string)
						datasetId := _args[1].(string)
						tableId := _args[2].(string)
						return fmt.Sprintf("bq://%v.%v.%v", project, datasetId, tableId), nil
					}).(pulumi.StringOutput),
				},
				EntityIdColumns: pulumi.StringArray{
					pulumi.String("feature_id"),
				},
			},
		})
		if err != nil {
			return err
		}
		sampleFeature, err := vertex.NewAiFeatureGroupFeature(ctx, "sample_feature", &vertex.AiFeatureGroupFeatureArgs{
			Name:         pulumi.String("example_feature_view_feature_registry"),
			Region:       pulumi.String("us-central1"),
			FeatureGroup: sampleFeatureGroup.Name,
			Description:  pulumi.String("A sample feature"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
		})
		if err != nil {
			return err
		}
		_, err = vertex.NewAiFeatureOnlineStoreFeatureview(ctx, "featureview_featureregistry", &vertex.AiFeatureOnlineStoreFeatureviewArgs{
			Name:               pulumi.String("example_feature_view_feature_registry"),
			Region:             pulumi.String("us-central1"),
			FeatureOnlineStore: featureonlinestore.Name,
			SyncConfig: &vertex.AiFeatureOnlineStoreFeatureviewSyncConfigArgs{
				Cron: pulumi.String("0 0 * * *"),
			},
			FeatureRegistrySource: &vertex.AiFeatureOnlineStoreFeatureviewFeatureRegistrySourceArgs{
				FeatureGroups: vertex.AiFeatureOnlineStoreFeatureviewFeatureRegistrySourceFeatureGroupArray{
					&vertex.AiFeatureOnlineStoreFeatureviewFeatureRegistrySourceFeatureGroupArgs{
						FeatureGroupId: sampleFeatureGroup.Name,
						FeatureIds: pulumi.StringArray{
							sampleFeature.Name,
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var featureonlinestore = new Gcp.Vertex.AiFeatureOnlineStore("featureonlinestore", new()
    {
        Name = "example_feature_view_feature_registry",
        Labels = 
        {
            { "foo", "bar" },
        },
        Region = "us-central1",
        Bigtable = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreBigtableArgs
        {
            AutoScaling = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreBigtableAutoScalingArgs
            {
                MinNodeCount = 1,
                MaxNodeCount = 2,
                CpuUtilizationTarget = 80,
            },
        },
    });

    var sampleDataset = new Gcp.BigQuery.Dataset("sample_dataset", new()
    {
        DatasetId = "example_feature_view_feature_registry",
        FriendlyName = "test",
        Description = "This is a test description",
        Location = "US",
    });

    var sampleTable = new Gcp.BigQuery.Table("sample_table", new()
    {
        DeletionProtection = false,
        DatasetId = sampleDataset.DatasetId,
        TableId = "example_feature_view_feature_registry",
        Schema = @"[
    {
        \""name\"": \""feature_id\"",
        \""type\"": \""STRING\"",
        \""mode\"": \""NULLABLE\""
    },
    {
        \""name\"": \""example_feature_view_feature_registry\"",
        \""type\"": \""STRING\"",
        \""mode\"": \""NULLABLE\""
    },
    {
        \""name\"": \""feature_timestamp\"",
        \""type\"": \""TIMESTAMP\"",
        \""mode\"": \""NULLABLE\""
    }
]
",
    });

    var sampleFeatureGroup = new Gcp.Vertex.AiFeatureGroup("sample_feature_group", new()
    {
        Name = "example_feature_view_feature_registry",
        Description = "A sample feature group",
        Region = "us-central1",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        BigQuery = new Gcp.Vertex.Inputs.AiFeatureGroupBigQueryArgs
        {
            BigQuerySource = new Gcp.Vertex.Inputs.AiFeatureGroupBigQueryBigQuerySourceArgs
            {
                InputUri = Output.Tuple(sampleTable.Project, sampleTable.DatasetId, sampleTable.TableId).Apply(values =>
                {
                    var project = values.Item1;
                    var datasetId = values.Item2;
                    var tableId = values.Item3;
                    return $"bq://{project}.{datasetId}.{tableId}";
                }),
            },
            EntityIdColumns = new[]
            {
                "feature_id",
            },
        },
    });

    var sampleFeature = new Gcp.Vertex.AiFeatureGroupFeature("sample_feature", new()
    {
        Name = "example_feature_view_feature_registry",
        Region = "us-central1",
        FeatureGroup = sampleFeatureGroup.Name,
        Description = "A sample feature",
        Labels = 
        {
            { "label-one", "value-one" },
        },
    });

    var featureviewFeatureregistry = new Gcp.Vertex.AiFeatureOnlineStoreFeatureview("featureview_featureregistry", new()
    {
        Name = "example_feature_view_feature_registry",
        Region = "us-central1",
        FeatureOnlineStore = featureonlinestore.Name,
        SyncConfig = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewSyncConfigArgs
        {
            Cron = "0 0 * * *",
        },
        FeatureRegistrySource = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewFeatureRegistrySourceArgs
        {
            FeatureGroups = new[]
            {
                new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewFeatureRegistrySourceFeatureGroupArgs
                {
                    FeatureGroupId = sampleFeatureGroup.Name,
                    FeatureIds = new[]
                    {
                        sampleFeature.Name,
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.vertex.AiFeatureOnlineStore;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreBigtableArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreBigtableAutoScalingArgs;
import com.pulumi.gcp.bigquery.Dataset;
import com.pulumi.gcp.bigquery.DatasetArgs;
import com.pulumi.gcp.bigquery.Table;
import com.pulumi.gcp.bigquery.TableArgs;
import com.pulumi.gcp.vertex.AiFeatureGroup;
import com.pulumi.gcp.vertex.AiFeatureGroupArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureGroupBigQueryArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureGroupBigQueryBigQuerySourceArgs;
import com.pulumi.gcp.vertex.AiFeatureGroupFeature;
import com.pulumi.gcp.vertex.AiFeatureGroupFeatureArgs;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreFeatureview;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreFeatureviewArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreFeatureviewSyncConfigArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreFeatureviewFeatureRegistrySourceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var featureonlinestore = new AiFeatureOnlineStore("featureonlinestore", AiFeatureOnlineStoreArgs.builder()
            .name("example_feature_view_feature_registry")
            .labels(Map.of("foo", "bar"))
            .region("us-central1")
            .bigtable(AiFeatureOnlineStoreBigtableArgs.builder()
                .autoScaling(AiFeatureOnlineStoreBigtableAutoScalingArgs.builder()
                    .minNodeCount(1)
                    .maxNodeCount(2)
                    .cpuUtilizationTarget(80)
                    .build())
                .build())
            .build());

        var sampleDataset = new Dataset("sampleDataset", DatasetArgs.builder()
            .datasetId("example_feature_view_feature_registry")
            .friendlyName("test")
            .description("This is a test description")
            .location("US")
            .build());

        var sampleTable = new Table("sampleTable", TableArgs.builder()
            .deletionProtection(false)
            .datasetId(sampleDataset.datasetId())
            .tableId("example_feature_view_feature_registry")
            .schema("""
[
    {
        \"name\": \"feature_id\",
        \"type\": \"STRING\",
        \"mode\": \"NULLABLE\"
    },
    {
        \"name\": \"example_feature_view_feature_registry\",
        \"type\": \"STRING\",
        \"mode\": \"NULLABLE\"
    },
    {
        \"name\": \"feature_timestamp\",
        \"type\": \"TIMESTAMP\",
        \"mode\": \"NULLABLE\"
    }
]
            """)
            .build());

        var sampleFeatureGroup = new AiFeatureGroup("sampleFeatureGroup", AiFeatureGroupArgs.builder()
            .name("example_feature_view_feature_registry")
            .description("A sample feature group")
            .region("us-central1")
            .labels(Map.of("label-one", "value-one"))
            .bigQuery(AiFeatureGroupBigQueryArgs.builder()
                .bigQuerySource(AiFeatureGroupBigQueryBigQuerySourceArgs.builder()
                    .inputUri(Output.tuple(sampleTable.project(), sampleTable.datasetId(), sampleTable.tableId()).applyValue(values -> {
                        var project = values.t1;
                        var datasetId = values.t2;
                        var tableId = values.t3;
                        return String.format("bq://%s.%s.%s", project,datasetId,tableId);
                    }))
                    .build())
                .entityIdColumns("feature_id")
                .build())
            .build());

        var sampleFeature = new AiFeatureGroupFeature("sampleFeature", AiFeatureGroupFeatureArgs.builder()
            .name("example_feature_view_feature_registry")
            .region("us-central1")
            .featureGroup(sampleFeatureGroup.name())
            .description("A sample feature")
            .labels(Map.of("label-one", "value-one"))
            .build());

        var featureviewFeatureregistry = new AiFeatureOnlineStoreFeatureview("featureviewFeatureregistry", AiFeatureOnlineStoreFeatureviewArgs.builder()
            .name("example_feature_view_feature_registry")
            .region("us-central1")
            .featureOnlineStore(featureonlinestore.name())
            .syncConfig(AiFeatureOnlineStoreFeatureviewSyncConfigArgs.builder()
                .cron("0 0 * * *")
                .build())
            .featureRegistrySource(AiFeatureOnlineStoreFeatureviewFeatureRegistrySourceArgs.builder()
                .featureGroups(AiFeatureOnlineStoreFeatureviewFeatureRegistrySourceFeatureGroupArgs.builder()
                    .featureGroupId(sampleFeatureGroup.name())
                    .featureIds(sampleFeature.name())
                    .build())
                .build())
            .build());

    }
}
resources:
  featureonlinestore:
    type: gcp:vertex:AiFeatureOnlineStore
    properties:
      name: example_feature_view_feature_registry
      labels:
        foo: bar
      region: us-central1
      bigtable:
        autoScaling:
          minNodeCount: 1
          maxNodeCount: 2
          cpuUtilizationTarget: 80
  sampleDataset:
    type: gcp:bigquery:Dataset
    name: sample_dataset
    properties:
      datasetId: example_feature_view_feature_registry
      friendlyName: test
      description: This is a test description
      location: US
  sampleTable:
    type: gcp:bigquery:Table
    name: sample_table
    properties:
      deletionProtection: false
      datasetId: ${sampleDataset.datasetId}
      tableId: example_feature_view_feature_registry
      schema: |
        [
            {
                \"name\": \"feature_id\",
                \"type\": \"STRING\",
                \"mode\": \"NULLABLE\"
            },
            {
                \"name\": \"example_feature_view_feature_registry\",
                \"type\": \"STRING\",
                \"mode\": \"NULLABLE\"
            },
            {
                \"name\": \"feature_timestamp\",
                \"type\": \"TIMESTAMP\",
                \"mode\": \"NULLABLE\"
            }
        ]        
  sampleFeatureGroup:
    type: gcp:vertex:AiFeatureGroup
    name: sample_feature_group
    properties:
      name: example_feature_view_feature_registry
      description: A sample feature group
      region: us-central1
      labels:
        label-one: value-one
      bigQuery:
        bigQuerySource:
          inputUri: bq://${sampleTable.project}.${sampleTable.datasetId}.${sampleTable.tableId}
        entityIdColumns:
          - feature_id
  sampleFeature:
    type: gcp:vertex:AiFeatureGroupFeature
    name: sample_feature
    properties:
      name: example_feature_view_feature_registry
      region: us-central1
      featureGroup: ${sampleFeatureGroup.name}
      description: A sample feature
      labels:
        label-one: value-one
  featureviewFeatureregistry:
    type: gcp:vertex:AiFeatureOnlineStoreFeatureview
    name: featureview_featureregistry
    properties:
      name: example_feature_view_feature_registry
      region: us-central1
      featureOnlineStore: ${featureonlinestore.name}
      syncConfig:
        cron: 0 0 * * *
      featureRegistrySource:
        featureGroups:
          - featureGroupId: ${sampleFeatureGroup.name}
            featureIds:
              - ${sampleFeature.name}

The featureRegistrySource property replaces bigQuerySource when pulling from Feature Registry. The featureGroups array specifies which Feature Groups and Features to sync. This approach lets you curate features once in the registry and reference them across multiple FeatureViews, rather than duplicating BigQuery source configuration.

Enable vector similarity search for embeddings

Recommendation and search systems store embeddings in BigQuery and need to perform approximate nearest neighbor lookups at serving time. FeatureViews can index embeddings for vector search.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const featureonlinestore = new gcp.vertex.AiFeatureOnlineStore("featureonlinestore", {
    name: "example_feature_view_vector_search",
    labels: {
        foo: "bar",
    },
    region: "us-central1",
    optimized: {},
    embeddingManagement: {
        enabled: true,
    },
});
const tf_test_dataset = new gcp.bigquery.Dataset("tf-test-dataset", {
    datasetId: "example_feature_view_vector_search",
    friendlyName: "test",
    description: "This is a test description",
    location: "US",
});
const tf_test_table = new gcp.bigquery.Table("tf-test-table", {
    deletionProtection: false,
    datasetId: tf_test_dataset.datasetId,
    tableId: "example_feature_view_vector_search",
    schema: `[
{
  \\"name\\": \\"test_primary_id\\",
  \\"mode\\": \\"NULLABLE\\",
  \\"type\\": \\"STRING\\",
  \\"description\\": \\"primary test id\\"
},
{
  \\"name\\": \\"embedding\\",
  \\"mode\\": \\"REPEATED\\",
  \\"type\\": \\"FLOAT\\",
  \\"description\\": \\"embedding column for primary_id column\\"
},
{
  \\"name\\": \\"country\\",
  \\"mode\\": \\"NULLABLE\\",
  \\"type\\": \\"STRING\\",
  \\"description\\": \\"country\\"
},
{
  \\"name\\": \\"test_crowding_column\\",
  \\"mode\\": \\"NULLABLE\\",
  \\"type\\": \\"INTEGER\\",
  \\"description\\": \\"test crowding column\\"
},
{
  \\"name\\": \\"entity_id\\",
  \\"mode\\": \\"NULLABLE\\",
  \\"type\\": \\"STRING\\",
  \\"description\\": \\"Test default entity_id\\"
},
{
  \\"name\\": \\"test_entity_column\\",
  \\"mode\\": \\"NULLABLE\\",
  \\"type\\": \\"STRING\\",
  \\"description\\": \\"test secondary entity column\\"
},
{
  \\"name\\": \\"feature_timestamp\\",
  \\"mode\\": \\"NULLABLE\\",
  \\"type\\": \\"TIMESTAMP\\",
  \\"description\\": \\"Default timestamp value\\"
}
]
`,
});
const featureviewVectorSearch = new gcp.vertex.AiFeatureOnlineStoreFeatureview("featureview_vector_search", {
    name: "example_feature_view_vector_search",
    region: "us-central1",
    featureOnlineStore: featureonlinestore.name,
    syncConfig: {
        cron: "0 0 * * *",
    },
    bigQuerySource: {
        uri: pulumi.interpolate`bq://${tf_test_table.project}.${tf_test_table.datasetId}.${tf_test_table.tableId}`,
        entityIdColumns: ["test_entity_column"],
    },
    vectorSearchConfig: {
        embeddingColumn: "embedding",
        filterColumns: ["country"],
        crowdingColumn: "test_crowding_column",
        distanceMeasureType: "DOT_PRODUCT_DISTANCE",
        treeAhConfig: {
            leafNodeEmbeddingCount: "1000",
        },
        embeddingDimension: 2,
    },
});
const project = gcp.organizations.getProject({});
import pulumi
import pulumi_gcp as gcp

featureonlinestore = gcp.vertex.AiFeatureOnlineStore("featureonlinestore",
    name="example_feature_view_vector_search",
    labels={
        "foo": "bar",
    },
    region="us-central1",
    optimized={},
    embedding_management={
        "enabled": True,
    })
tf_test_dataset = gcp.bigquery.Dataset("tf-test-dataset",
    dataset_id="example_feature_view_vector_search",
    friendly_name="test",
    description="This is a test description",
    location="US")
tf_test_table = gcp.bigquery.Table("tf-test-table",
    deletion_protection=False,
    dataset_id=tf_test_dataset.dataset_id,
    table_id="example_feature_view_vector_search",
    schema="""[
{
  \"name\": \"test_primary_id\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"primary test id\"
},
{
  \"name\": \"embedding\",
  \"mode\": \"REPEATED\",
  \"type\": \"FLOAT\",
  \"description\": \"embedding column for primary_id column\"
},
{
  \"name\": \"country\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"country\"
},
{
  \"name\": \"test_crowding_column\",
  \"mode\": \"NULLABLE\",
  \"type\": \"INTEGER\",
  \"description\": \"test crowding column\"
},
{
  \"name\": \"entity_id\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"Test default entity_id\"
},
{
  \"name\": \"test_entity_column\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"test secondary entity column\"
},
{
  \"name\": \"feature_timestamp\",
  \"mode\": \"NULLABLE\",
  \"type\": \"TIMESTAMP\",
  \"description\": \"Default timestamp value\"
}
]
""")
featureview_vector_search = gcp.vertex.AiFeatureOnlineStoreFeatureview("featureview_vector_search",
    name="example_feature_view_vector_search",
    region="us-central1",
    feature_online_store=featureonlinestore.name,
    sync_config={
        "cron": "0 0 * * *",
    },
    big_query_source={
        "uri": pulumi.Output.all(
            project=tf_test_table.project,
            dataset_id=tf_test_table.dataset_id,
            table_id=tf_test_table.table_id
).apply(lambda resolved_outputs: f"bq://{resolved_outputs['project']}.{resolved_outputs['dataset_id']}.{resolved_outputs['table_id']}")
,
        "entity_id_columns": ["test_entity_column"],
    },
    vector_search_config={
        "embedding_column": "embedding",
        "filter_columns": ["country"],
        "crowding_column": "test_crowding_column",
        "distance_measure_type": "DOT_PRODUCT_DISTANCE",
        "tree_ah_config": {
            "leaf_node_embedding_count": "1000",
        },
        "embedding_dimension": 2,
    })
project = gcp.organizations.get_project()
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/bigquery"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/vertex"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		featureonlinestore, err := vertex.NewAiFeatureOnlineStore(ctx, "featureonlinestore", &vertex.AiFeatureOnlineStoreArgs{
			Name: pulumi.String("example_feature_view_vector_search"),
			Labels: pulumi.StringMap{
				"foo": pulumi.String("bar"),
			},
			Region:    pulumi.String("us-central1"),
			Optimized: &vertex.AiFeatureOnlineStoreOptimizedArgs{},
			EmbeddingManagement: &vertex.AiFeatureOnlineStoreEmbeddingManagementArgs{
				Enabled: pulumi.Bool(true),
			},
		})
		if err != nil {
			return err
		}
		tf_test_dataset, err := bigquery.NewDataset(ctx, "tf-test-dataset", &bigquery.DatasetArgs{
			DatasetId:    pulumi.String("example_feature_view_vector_search"),
			FriendlyName: pulumi.String("test"),
			Description:  pulumi.String("This is a test description"),
			Location:     pulumi.String("US"),
		})
		if err != nil {
			return err
		}
		tf_test_table, err := bigquery.NewTable(ctx, "tf-test-table", &bigquery.TableArgs{
			DeletionProtection: pulumi.Bool(false),
			DatasetId:          tf_test_dataset.DatasetId,
			TableId:            pulumi.String("example_feature_view_vector_search"),
			Schema: pulumi.String(`[
{
  \"name\": \"test_primary_id\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"primary test id\"
},
{
  \"name\": \"embedding\",
  \"mode\": \"REPEATED\",
  \"type\": \"FLOAT\",
  \"description\": \"embedding column for primary_id column\"
},
{
  \"name\": \"country\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"country\"
},
{
  \"name\": \"test_crowding_column\",
  \"mode\": \"NULLABLE\",
  \"type\": \"INTEGER\",
  \"description\": \"test crowding column\"
},
{
  \"name\": \"entity_id\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"Test default entity_id\"
},
{
  \"name\": \"test_entity_column\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"test secondary entity column\"
},
{
  \"name\": \"feature_timestamp\",
  \"mode\": \"NULLABLE\",
  \"type\": \"TIMESTAMP\",
  \"description\": \"Default timestamp value\"
}
]
`),
		})
		if err != nil {
			return err
		}
		_, err = vertex.NewAiFeatureOnlineStoreFeatureview(ctx, "featureview_vector_search", &vertex.AiFeatureOnlineStoreFeatureviewArgs{
			Name:               pulumi.String("example_feature_view_vector_search"),
			Region:             pulumi.String("us-central1"),
			FeatureOnlineStore: featureonlinestore.Name,
			SyncConfig: &vertex.AiFeatureOnlineStoreFeatureviewSyncConfigArgs{
				Cron: pulumi.String("0 0 * * *"),
			},
			BigQuerySource: &vertex.AiFeatureOnlineStoreFeatureviewBigQuerySourceArgs{
				Uri: pulumi.All(tf_test_table.Project, tf_test_table.DatasetId, tf_test_table.TableId).ApplyT(func(_args []interface{}) (string, error) {
					project := _args[0].(string)
					datasetId := _args[1].(string)
					tableId := _args[2].(string)
					return fmt.Sprintf("bq://%v.%v.%v", project, datasetId, tableId), nil
				}).(pulumi.StringOutput),
				EntityIdColumns: pulumi.StringArray{
					pulumi.String("test_entity_column"),
				},
			},
			VectorSearchConfig: &vertex.AiFeatureOnlineStoreFeatureviewVectorSearchConfigArgs{
				EmbeddingColumn: pulumi.String("embedding"),
				FilterColumns: pulumi.StringArray{
					pulumi.String("country"),
				},
				CrowdingColumn:      pulumi.String("test_crowding_column"),
				DistanceMeasureType: pulumi.String("DOT_PRODUCT_DISTANCE"),
				TreeAhConfig: &vertex.AiFeatureOnlineStoreFeatureviewVectorSearchConfigTreeAhConfigArgs{
					LeafNodeEmbeddingCount: pulumi.String("1000"),
				},
				EmbeddingDimension: pulumi.Int(2),
			},
		})
		if err != nil {
			return err
		}
		_, err = organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var featureonlinestore = new Gcp.Vertex.AiFeatureOnlineStore("featureonlinestore", new()
    {
        Name = "example_feature_view_vector_search",
        Labels = 
        {
            { "foo", "bar" },
        },
        Region = "us-central1",
        Optimized = null,
        EmbeddingManagement = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreEmbeddingManagementArgs
        {
            Enabled = true,
        },
    });

    var tf_test_dataset = new Gcp.BigQuery.Dataset("tf-test-dataset", new()
    {
        DatasetId = "example_feature_view_vector_search",
        FriendlyName = "test",
        Description = "This is a test description",
        Location = "US",
    });

    var tf_test_table = new Gcp.BigQuery.Table("tf-test-table", new()
    {
        DeletionProtection = false,
        DatasetId = tf_test_dataset.DatasetId,
        TableId = "example_feature_view_vector_search",
        Schema = @"[
{
  \""name\"": \""test_primary_id\"",
  \""mode\"": \""NULLABLE\"",
  \""type\"": \""STRING\"",
  \""description\"": \""primary test id\""
},
{
  \""name\"": \""embedding\"",
  \""mode\"": \""REPEATED\"",
  \""type\"": \""FLOAT\"",
  \""description\"": \""embedding column for primary_id column\""
},
{
  \""name\"": \""country\"",
  \""mode\"": \""NULLABLE\"",
  \""type\"": \""STRING\"",
  \""description\"": \""country\""
},
{
  \""name\"": \""test_crowding_column\"",
  \""mode\"": \""NULLABLE\"",
  \""type\"": \""INTEGER\"",
  \""description\"": \""test crowding column\""
},
{
  \""name\"": \""entity_id\"",
  \""mode\"": \""NULLABLE\"",
  \""type\"": \""STRING\"",
  \""description\"": \""Test default entity_id\""
},
{
  \""name\"": \""test_entity_column\"",
  \""mode\"": \""NULLABLE\"",
  \""type\"": \""STRING\"",
  \""description\"": \""test secondary entity column\""
},
{
  \""name\"": \""feature_timestamp\"",
  \""mode\"": \""NULLABLE\"",
  \""type\"": \""TIMESTAMP\"",
  \""description\"": \""Default timestamp value\""
}
]
",
    });

    var featureviewVectorSearch = new Gcp.Vertex.AiFeatureOnlineStoreFeatureview("featureview_vector_search", new()
    {
        Name = "example_feature_view_vector_search",
        Region = "us-central1",
        FeatureOnlineStore = featureonlinestore.Name,
        SyncConfig = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewSyncConfigArgs
        {
            Cron = "0 0 * * *",
        },
        BigQuerySource = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewBigQuerySourceArgs
        {
            Uri = Output.Tuple(tf_test_table.Project, tf_test_table.DatasetId, tf_test_table.TableId).Apply(values =>
            {
                var project = values.Item1;
                var datasetId = values.Item2;
                var tableId = values.Item3;
                return $"bq://{project}.{datasetId}.{tableId}";
            }),
            EntityIdColumns = new[]
            {
                "test_entity_column",
            },
        },
        VectorSearchConfig = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewVectorSearchConfigArgs
        {
            EmbeddingColumn = "embedding",
            FilterColumns = new[]
            {
                "country",
            },
            CrowdingColumn = "test_crowding_column",
            DistanceMeasureType = "DOT_PRODUCT_DISTANCE",
            TreeAhConfig = new Gcp.Vertex.Inputs.AiFeatureOnlineStoreFeatureviewVectorSearchConfigTreeAhConfigArgs
            {
                LeafNodeEmbeddingCount = "1000",
            },
            EmbeddingDimension = 2,
        },
    });

    var project = Gcp.Organizations.GetProject.Invoke();

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.vertex.AiFeatureOnlineStore;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreOptimizedArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreEmbeddingManagementArgs;
import com.pulumi.gcp.bigquery.Dataset;
import com.pulumi.gcp.bigquery.DatasetArgs;
import com.pulumi.gcp.bigquery.Table;
import com.pulumi.gcp.bigquery.TableArgs;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreFeatureview;
import com.pulumi.gcp.vertex.AiFeatureOnlineStoreFeatureviewArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreFeatureviewSyncConfigArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreFeatureviewBigQuerySourceArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreFeatureviewVectorSearchConfigArgs;
import com.pulumi.gcp.vertex.inputs.AiFeatureOnlineStoreFeatureviewVectorSearchConfigTreeAhConfigArgs;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var featureonlinestore = new AiFeatureOnlineStore("featureonlinestore", AiFeatureOnlineStoreArgs.builder()
            .name("example_feature_view_vector_search")
            .labels(Map.of("foo", "bar"))
            .region("us-central1")
            .optimized(AiFeatureOnlineStoreOptimizedArgs.builder()
                .build())
            .embeddingManagement(AiFeatureOnlineStoreEmbeddingManagementArgs.builder()
                .enabled(true)
                .build())
            .build());

        var tf_test_dataset = new Dataset("tf-test-dataset", DatasetArgs.builder()
            .datasetId("example_feature_view_vector_search")
            .friendlyName("test")
            .description("This is a test description")
            .location("US")
            .build());

        var tf_test_table = new Table("tf-test-table", TableArgs.builder()
            .deletionProtection(false)
            .datasetId(tf_test_dataset.datasetId())
            .tableId("example_feature_view_vector_search")
            .schema("""
[
{
  \"name\": \"test_primary_id\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"primary test id\"
},
{
  \"name\": \"embedding\",
  \"mode\": \"REPEATED\",
  \"type\": \"FLOAT\",
  \"description\": \"embedding column for primary_id column\"
},
{
  \"name\": \"country\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"country\"
},
{
  \"name\": \"test_crowding_column\",
  \"mode\": \"NULLABLE\",
  \"type\": \"INTEGER\",
  \"description\": \"test crowding column\"
},
{
  \"name\": \"entity_id\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"Test default entity_id\"
},
{
  \"name\": \"test_entity_column\",
  \"mode\": \"NULLABLE\",
  \"type\": \"STRING\",
  \"description\": \"test secondary entity column\"
},
{
  \"name\": \"feature_timestamp\",
  \"mode\": \"NULLABLE\",
  \"type\": \"TIMESTAMP\",
  \"description\": \"Default timestamp value\"
}
]
            """)
            .build());

        var featureviewVectorSearch = new AiFeatureOnlineStoreFeatureview("featureviewVectorSearch", AiFeatureOnlineStoreFeatureviewArgs.builder()
            .name("example_feature_view_vector_search")
            .region("us-central1")
            .featureOnlineStore(featureonlinestore.name())
            .syncConfig(AiFeatureOnlineStoreFeatureviewSyncConfigArgs.builder()
                .cron("0 0 * * *")
                .build())
            .bigQuerySource(AiFeatureOnlineStoreFeatureviewBigQuerySourceArgs.builder()
                .uri(Output.tuple(tf_test_table.project(), tf_test_table.datasetId(), tf_test_table.tableId()).applyValue(values -> {
                    var project = values.t1;
                    var datasetId = values.t2;
                    var tableId = values.t3;
                    return String.format("bq://%s.%s.%s", project,datasetId,tableId);
                }))
                .entityIdColumns("test_entity_column")
                .build())
            .vectorSearchConfig(AiFeatureOnlineStoreFeatureviewVectorSearchConfigArgs.builder()
                .embeddingColumn("embedding")
                .filterColumns("country")
                .crowdingColumn("test_crowding_column")
                .distanceMeasureType("DOT_PRODUCT_DISTANCE")
                .treeAhConfig(AiFeatureOnlineStoreFeatureviewVectorSearchConfigTreeAhConfigArgs.builder()
                    .leafNodeEmbeddingCount("1000")
                    .build())
                .embeddingDimension(2)
                .build())
            .build());

        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

    }
}
resources:
  featureonlinestore:
    type: gcp:vertex:AiFeatureOnlineStore
    properties:
      name: example_feature_view_vector_search
      labels:
        foo: bar
      region: us-central1
      optimized: {}
      embeddingManagement:
        enabled: true
  tf-test-dataset:
    type: gcp:bigquery:Dataset
    properties:
      datasetId: example_feature_view_vector_search
      friendlyName: test
      description: This is a test description
      location: US
  tf-test-table:
    type: gcp:bigquery:Table
    properties:
      deletionProtection: false
      datasetId: ${["tf-test-dataset"].datasetId}
      tableId: example_feature_view_vector_search
      schema: |
        [
        {
          \"name\": \"test_primary_id\",
          \"mode\": \"NULLABLE\",
          \"type\": \"STRING\",
          \"description\": \"primary test id\"
        },
        {
          \"name\": \"embedding\",
          \"mode\": \"REPEATED\",
          \"type\": \"FLOAT\",
          \"description\": \"embedding column for primary_id column\"
        },
        {
          \"name\": \"country\",
          \"mode\": \"NULLABLE\",
          \"type\": \"STRING\",
          \"description\": \"country\"
        },
        {
          \"name\": \"test_crowding_column\",
          \"mode\": \"NULLABLE\",
          \"type\": \"INTEGER\",
          \"description\": \"test crowding column\"
        },
        {
          \"name\": \"entity_id\",
          \"mode\": \"NULLABLE\",
          \"type\": \"STRING\",
          \"description\": \"Test default entity_id\"
        },
        {
          \"name\": \"test_entity_column\",
          \"mode\": \"NULLABLE\",
          \"type\": \"STRING\",
          \"description\": \"test secondary entity column\"
        },
        {
          \"name\": \"feature_timestamp\",
          \"mode\": \"NULLABLE\",
          \"type\": \"TIMESTAMP\",
          \"description\": \"Default timestamp value\"
        }
        ]        
  featureviewVectorSearch:
    type: gcp:vertex:AiFeatureOnlineStoreFeatureview
    name: featureview_vector_search
    properties:
      name: example_feature_view_vector_search
      region: us-central1
      featureOnlineStore: ${featureonlinestore.name}
      syncConfig:
        cron: 0 0 * * *
      bigQuerySource:
        uri: bq://${["tf-test-table"].project}.${["tf-test-table"].datasetId}.${["tf-test-table"].tableId}
        entityIdColumns:
          - test_entity_column
      vectorSearchConfig:
        embeddingColumn: embedding
        filterColumns:
          - country
        crowdingColumn: test_crowding_column
        distanceMeasureType: DOT_PRODUCT_DISTANCE
        treeAhConfig:
          leafNodeEmbeddingCount: '1000'
        embeddingDimension: '2'
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The vectorSearchConfig property enables ANN search on your embeddings. The embeddingColumn property specifies which BigQuery column contains the embedding vectors (must be REPEATED FLOAT type). The distanceMeasureType property controls similarity calculation (DOT_PRODUCT_DISTANCE, COSINE_DISTANCE, or EUCLIDEAN_DISTANCE). The treeAhConfig property tunes the index structure; leafNodeEmbeddingCount controls how many embeddings are stored per leaf node, affecting search speed and accuracy. Optional filterColumns and crowdingColumn properties enable filtered search and diversity controls.

Beyond these examples

These snippets focus on specific FeatureView-level features: BigQuery and Feature Registry sync sources, scheduled and continuous sync modes, and vector search indexing for embeddings. They’re intentionally minimal rather than full ML serving pipelines.

The examples reference pre-existing infrastructure such as FeatureOnlineStore instances (Bigtable or Optimized), BigQuery datasets and tables with feature data, and Feature Groups and Features (for Feature Registry source). They focus on configuring the FeatureView rather than provisioning the underlying storage or feature definitions.

To keep things focused, common FeatureView patterns are omitted, including:

  • Cross-project feature access and IAM configuration
  • Continuous sync mode (syncConfig.continuous)
  • Vector search tuning (embedding dimension, leaf node count)
  • Filter and crowding column configuration for search

These omissions are intentional: the goal is to illustrate how each FeatureView feature is wired, not provide drop-in ML serving modules. See the FeatureOnlineStoreFeatureview resource reference for all available configuration options.

Let's configure GCP Vertex AI Feature Online Store Feature Views

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration Basics
What properties can't I change after creating a feature view?
The featureOnlineStore, name, region, project, and vectorSearchConfig properties are immutable and require resource recreation if changed.
Does my feature view region need to match the feature store?
Yes, the region property should be the same as the parent featureonlinestore region.
What are the naming constraints for feature views?
The name can be up to 60 characters using [a-z0-9_], and the first character cannot be a number.
Data Sources
Should I use BigQuery or Feature Registry as my data source?
Use bigQuerySource to extract data directly from BigQuery tables, or featureRegistrySource to load features from Vertex AI Feature Registry. These options are mutually exclusive.
How do I configure a BigQuery source?
Set bigQuerySource with uri in bq://project.dataset.table format and specify entityIdColumns as an array of column names.
How do I use Feature Registry as a source?
Configure featureRegistrySource with a featureGroups array containing featureGroupId and featureIds for each feature group.
Sync Configuration
What's the difference between cron and continuous sync modes?
Use syncConfig.cron with a cron expression (e.g., 0 0 * * * for daily) for scheduled syncs, or set syncConfig.continuous to true for continuous data synchronization.
Advanced Features
How do I enable vector search for my feature view?
Configure vectorSearchConfig with embeddingColumn, embeddingDimension, and distanceMeasureType. The parent feature store must have embeddingManagement.enabled set to true.
How do I set up a cross-project feature view?
Grant roles/bigquery.dataViewer to the Vertex AI service account (service-PROJECT_NUMBER@gcp-sa-aiplatform.iam.gserviceaccount.com), set projectNumber in featureRegistrySource, and add wait times (30-60 seconds) for IAM propagation using dependsOn.
Troubleshooting
Why aren't all my labels showing up in the labels field?
The labels field is non-authoritative and only manages labels in your configuration. Use the effectiveLabels output to see all labels present on the resource.

Using a different cloud?

Explore analytics guides for other cloud providers: