

Transactional consistency & advanced AI? Spanner says heck yeah!!!
This article covers Spanner’s core strengths and showcases the revolutionary impact of its built-in AI capabilities with practical example.
💡 The Use Case: Global Real-Time AI-Powered Product Catalog
Let’s build an application that is an e-commerce product catalog designed to serve a global customer base.
Why Spanner for this Use Case?
This use case perfectly justifies Spanner over a traditional relational database or a NoSQL solution because of the non-negotiable requirements for strong consistency and global scale combined:
🧠 How Spanner AI is Changing the Odds
The integration of Spanner and Vertex AI (specifically Vector Search and ML.PREDICT) revolutionizes the application’s core functionality — the search experience — turning a standard catalog into an intelligent one.
Let’s say there’s a sudden surge in demand for a particular product in a specific region. How does Spanner’s scalability and Spanner AI’s ability to quickly process new information (like updated inventory or trending reviews) ensure a consistent and positive customer experience? Consider a scenario where a new product is added to the catalog. How quickly is it reflected in semantic search results and available for AI-powered analysis of its initial reviews? Contrast this with a traditional approach where separate indexing or processing pipelines would introduce delays.
1. Semantic Search (Vector Search)
Traditional keyword search often fails if a user searches for “a comfy piece of furniture for the living room” and the product is titled “luxury velvet sectional.”. Users find products based on meaning and intent, not just keyword matches. This is a crucial feature for modern e-commerce that requires no external, eventually consistent vector database.
2. Instant intelligent analytics / analysis (ML.PREDICT)
Users get instant, real-time feedback on product data without moving data to a separate machine learning pipeline. The strong consistency ensures analytics are based on the latest, globally accurate review data.
3. Performance:
- Spanner Vertex AI integration talking to the Vertex AI service directly eliminates additional round-trips between a compute node running a Spanner’s client and the Vertex AI service.
- Spanner Vertex AI integration runs on top of Spanner’s distributed query processing infrastructure, which supports highly parallelizable query execution.
4. User experience:
Ability to use a single, simple, coherent, and familiar SQL interface to facilitate both data transformation and ML serving scenarios on Spanner level of scale lowers the ML entry barrier and allows for a much smoother user experience.
5. Costs:
Spanner Vertex AI integration uses Spanner compute capacity to merge the results of ML computations and SQL query execution, which eliminates the need to provision an additional compute (for example, in Compute Engine or Google Kubernetes Engine) for that.
Design Overview
The AI-Powered Product Catalog Architecture
Our AI-powered product catalog leverages the strengths of Google Cloud Spanner for its global scale, strong consistency, and high availability, combined with the power of Vertex AI for advanced machine learning capabilities. This integration creates a robust, real-time search experience that goes beyond traditional keyword matching. The core of our application is a Python Flask web service that orchestrates these components.
Key Architectural Components:
- Google Cloud Spanner: The Global, Consistent Data Backbone
Spanner serves as the central, globally distributed database for our product catalog. It stores all product information, including descriptions, pricing, inventory, and crucially, vector embeddings.
- Denormalization for Scale:
To emphasize Spanner’s distributed nature and the need for HA, our Products
table is denormalized. This means all relevant product details are stored in a single table, which, when combined with Spanner’s automatic sharding and multi-region replication, allows for massive scale and uptime.
Spanner’s support for ARRAY
is utilized to store the vector embeddings generated by Vertex AI.
For efficient semantic search, a VECTOR INDEX
is created on the Embeddings
column. This index allows Spanner to perform approximate nearest neighbor (ANN) searches using functions like APPROX_COSINE_DISTANCE
, returning semantically similar products in milliseconds.
2. Vertex AI: The Intelligence Engine
Vertex AI is our platform for machine learning model serving and inference. Specifically, we utilize its powerful embedding models (like textembedding-005
).
When a new product is added or its description is updated, we use Vertex AI’s embedding models to convert the natural language product descriptions into dense vector embeddings. These vectors capture the semantic meaning of the text.
While our current Python implementation generates embeddings externally and stores them in Spanner, Spanner’s ML.PREDICT
functionality (as demonstrated in our SQL) allows for the potential of invoking deployed models directly within Spanner queries. This can simplify workflows and potentially reduce latency by keeping computation closer to the data.
3. Python Flask Application: The Orchestrator
The Flask application acts as the backend for our web interface, handling user requests, interacting with Spanner and Vertex AI, and serving results. We have a few layers and modules leading up to the web application which we will see in detail in the implementation section, however here is a quick overview.
It provides RESTful API endpoints for product search (/search
) and potentially for managing product data (e.g., adding new products, updating descriptions).
- Embedding Service (
embedding_service.py
):
This module encapsulates the logic for interacting with Vertex AI to generate embeddings. It handles calling the appropriate Vertex AI models and processing their responses.
- Spanner Client Service (
spanner_client.py
):
This module manages all database interactions with Spanner. It includes methods for fetching products, performing vector searches using Spanner’s SQL capabilities, and updating product data (including embeddings).
a) New Product: When a new product is added via the Flask API, its description is immediately sent to Vertex AI for embedding generation. The product and its embedding are then saved to Spanner transactionally. Thanks to Spanner’s strong consistency, this new product becomes instantly available for semantic search.
b) Demand/Data Changes: While direct “demand surge” is complex to model in this architecture without inventory/sales data, any updates to product attributes (like descriptions) are handled in real-time. If a description is updated, its embedding is re-generated via Vertex AI and updated in Spanner, ensuring semantic search results remain relevant. This contrasts sharply with traditional systems that might rely on batch updates for search indexes, introducing significant delays. Though this feature is currently not implemented in the app, the code is set up for enhancement.
Let’s Get Building!!: Implementing the AI-Powered Product Catalog 🛠️
Now that we understand the architecture, let’s dive into the practical steps of setting up and running our AI-powered product catalog using Google Cloud Spanner, Vertex AI embeddings, and a Python Flask application.
1. Spanner Instance, Database, and Schema Creation 🗄️
Create a Spanner Instance:
- Navigate to the Google Cloud Console.
- Go to Databases > Spanner.
- Click Create Instance & select Enterprise edition.
- Choose a name for your instance (e.g.,
spanner-vertex
), select a region (e.g.,us-central1
), and choose a compute configuration (e.g.,1 node
). - Click Create.
Create a Database:
- Once the instance is created, click on its name.
- Go to the Databases tab and click Create database.
- Name your database (e.g.,
spanner-vertex-product
). - Choose your desired dialect (e.g., Google Standard SQL).
- Click Create.
Define the Schema:
- Execute the following
CREATE TABLE
DDL statement against your Spanner database. This defines the structure of our product catalog, including theEmbeddings
column to store vector data.
CREATE TABLE Products (
ProductId STRING(36) NOT NULL,
Name STRING(255) NOT NULL,
Description STRING(MAX),
Category STRING(100),
Brand STRING(100),
Price FLOAT64,
Currency STRING(3),
Availability INT64, -- Represents stock count
LastUpdated TIMESTAMP,
Region STRING(50),
Embeddings ARRAY -- Stores the vector embeddings. We won't specify vector_length here for simplicity,
-- but for production vector search, you'd configure it.
) PRIMARY KEY (ProductId);
2. Data Ingestion 📦
- Sample Data: For convenience, we’ve generated sample INSERT SQL statements. You can clone them from the repo file.
- Ingestion: Execute the generated
INSERT
statements against your Spanner database from the Spanner Studio (you can find this in the navigation menu of your database). - Alternatively, for larger datasets: You can use Google Cloud Dataflow to import data directly from external sources (like Cloud Storage, BigQuery, Pub/Sub or even files) into Spanner. You can get started here: https://console.cloud.google.com/dataflow/createjob
3. Embeddings Generation and Storage 💡
We leverage Vertex AI’s powerful embedding models (e.g., textembedding-005
) to convert textual product descriptions into numerical vector representations.
Our Python Flask application manages this process:
a. For every product description, we create its equivalent embeddings in the database.
b. Spanner Storage: The generated vector embeddings (a list[float]
) for product descriptions are stored directly in the Embeddings
column of the corresponding product’s row in Spanner using a transactional Spanner UPDATE
operation. This ensures that the embedding is kept consistent with the product data.\
For this, we’ll first create the MODEL that references the embedding model “text-embedding-005” from Vertex AI.
4. Vector Search with Spanner 🔍
To enable semantic search, we utilize Spanner’s vector similarity search capabilities. Let’s look at the flow here:
- A user enters a search query (e.g., “blue bedding”) into the Flask web application’s UI.
- The Flask app receives this query and calls the
embedding_service
to generate a vector embedding for the input text. - The user’s query text is then passed to the
spanner_client
‘ssearch_products_by_embedding
function. - The
spanner_client
executes a Spanner SQL query that utilizes aVECTOR INDEX
and a vector similarity function (likeAPPROX_COSINE_DISTANCE
orCOSINE_DISTANCE
) to find products whose stored embeddings are most similar to the query embedding. - Spanner’s distributed query engine efficiently processes this request across regions, leveraging the vector index for speed.
- The results (semantically relevant products) are returned by Spanner to the Flask application.
- The Flask app formats these results and sends them back to the user’s browser, providing a highly relevant and fast search experience.
Let’s look at the steps.
Create the model in Spanner:
CREATE MODEL EmbeddingsModel INPUT(
content STRING(MAX),
) OUTPUT(
embeddings STRUCT, values ARRAY>,
) REMOTE OPTIONS (
endpoint = '//aiplatform.googleapis.com/projects/<>/locations/us-central1/publishers/google/models/text-embedding-005'
);
Update embeddings for product description:
UPDATE products p1
SET embeddings =
(SELECT embeddings.values from ML.PREDICT(MODEL EmbeddingsModel,
(SELECT description as content FROM products p2 where p2.productid=p1.productid)))
Vector Search Query:
SELECT
name,category,
description,
COSINE_DISTANCE( embeddings,
(
SELECT
embeddings.values
FROM
ML.PREDICT( MODEL EmbeddingsModel,
(
SELECT
"books for professional developers" AS content) ) ) ) AS distance
FROM
products
ORDER BY
distance
LIMIT
5;
Vector Search Result:
Results are ordered by similarity (distance), and the top N results are returned to the user.
5. Optimizing Vector Search with a Vector Index 🚀
For efficient semantic search at scale, we need to optimize how Spanner finds similar vectors.
For that let’s alter the column and specify vector length and repopulate the embeddings field:
ALTER TABLE `products` DROP COLUMN `embeddings`;ALTER TABLE
`products` ADD COLUMN `embeddings` ARRAY(vector_length=>768);
UPDATE products p1
SET embeddings =
(SELECT embeddings.values from ML.PREDICT(MODEL EmbeddingsModel,
(SELECT description as content FROM products p2 where p2.productId=p1.productId)))
where embeddings is null;
Creating index for Vector Search:
If you want to scale the Vector Search, it is important to have indexes on the embeddings column. This index uses approximate nearest neighbor (ANN) algorithms to speed up similarity searches significantly, returning results ordered by semantic relevance much faster than a full table scan.
CREATE VECTOR INDEX ProductEmbeddingIndex
ON products(embeddings)
WHERE embeddings IS NOT NULL
OPTIONS (
distance_type = 'COSINE'
);
Here in the above DDL for index, we need to use the same distance method as the one you are going to choose in your Vector Search query.
When this index is present, Spanner can efficiently use functions like APPROX_COSINE_DISTANCE
. The FORCE_INDEX
hint in the SQL query ensures Spanner utilizes this optimized index.
Indexed Vector Search Query:
First, let’s get the Vector from the search text (we’ll typically let the application program cover this, in this case I’ve demonstrated the SQL equivalent for reference:
SELECT embeddings.values
FROM ML.PREDICT(MODEL EmbeddingsModel, (SELECT "books for professional developers" AS content));
Let’s see the output.
Now let’s use this vector in our Vector Search query.
Vector Search query:
SELECT
t.name,t.category,t.description,t.brand,t.price,t.currency,t.region,
APPROX_COSINE_DISTANCE(t.Embeddings, array[0.030367612838745117, -0.0046892608515918255, 0.012116963975131512, 0.0014116120291873813, -0.042977895587682724, 0.0743555873632431, -0.021052295342087746, -0.012601925060153008, -0.027914313599467278, -0.029203474521636963, -0.08033314347267151, -0.04240219295024872, 0.04147948697209358, 0.000897212652489543, 0.04049282148480415, 0.01587311178445816, 0.04705671966075897, -0.05771429091691971, -0.026051390916109085, 0.01478273794054985, 0.04664073884487152, -0.012783125042915344, -0.01885635033249855, 0.01513130497187376, -0.04302394017577171, -0.03260306641459465, -0.025960231199860573, 0.03447524458169937, -0.04022584110498428, 0.012506688013672829, -0.0010825557401403785, 0.010489586740732193, 0.02283313311636448, 0.08473795652389526, 0.01748279295861721, 0.03338683024048805, 0.058618370443582535, -0.0011340383207425475, 0.06484716385602951, -0.012539248913526535, -0.02489263005554676, -0.007079557050019503, -0.015263575129210949, -0.03565895929932594, 0.0730293020606041, 0.030231904238462448, -0.038080260157585144, 0.0626375749707222, -0.0026204618625342846, -0.018712075427174568, 0.04737527295947075, 0.026259128004312515, -0.001560832723043859, 0.012854858301579952, -0.0045639099553227425, 0.058945056051015854, -0.03853389993309975, -0.014263715595006943, -0.0005403775721788406, 0.08400054275989532, -0.030876394361257553, -0.013251708820462227, -0.004732347093522549, 0.0017427689163014293, -0.06276585906744003, -0.007882079109549522, 0.007295824121683836, 0.060490041971206665, 0.010592197068035603, -0.06541071832180023, 0.007740186061710119, -0.007832751609385014, 0.0068465182557702065, 0.048755548894405365, 0.02294500172138214, -0.04633032903075218, 0.02271864376962185, 0.025342650711536407, 0.022383663803339005, -0.061809394508600235, -0.10158701986074448, 0.05635252967476845, -0.01190129667520523, 0.009081440046429634, 0.02558998204767704, 0.03552040830254555, -0.004247715696692467, 0.03178687393665314, 0.008744790218770504, 0.024725059047341347, -0.041520919650793076, -0.017555776983499527, -0.048442065715789795, -0.0487903356552124, 0.023104960098862648, -0.008206568658351898, -0.010276167653501034, 0.019998770207166672, -0.015363892540335655, 0.011900585144758224, 0.008666174486279488, 0.05616646632552147, -0.018553733825683594, 0.06028709560632706, 0.027500037103891373, 0.024365393444895744, -0.015747448429465294, -0.09059122204780579, -0.025641130283474922, 0.003966866061091423, 0.029855556786060333, 0.019005579873919487, -0.023448286578059196, -0.027376016601920128, -0.02394622378051281, 0.0006442898302339017, -0.018589666113257408, 0.009960775263607502, -0.056400708854198456, -0.07328228652477264, 0.005300331395119429, -0.013666913844645023, 0.06467436999082565, 0.036488160490989685, -0.04509103670716286, -0.0014429471921175718, 0.04062977805733681, 0.0005430543678812683, 0.024341903626918793, -0.0287493746727705, -0.08089396357536316, -0.04706084355711937, -0.005170632153749466, 0.06282757967710495, 0.02365226298570633, -0.0003146969829685986, -0.007519661448895931, -0.002860382432118058, -0.012592602521181107, 0.002948981709778309, -0.07476114481687546, -0.0063386294059455395, -0.0104396753013134, 0.09494125843048096, 0.0448254831135273, -0.03501903638243675, -0.05694803223013878, -0.015080646611750126, 0.030469171702861786, -0.0035538552328944206, 0.04836287349462509, -0.018502172082662582, -0.006697770673781633, 0.08125991374254227, 0.020555691793560982, -0.02914145588874817, -0.06508182734251022, -0.041740480810403824, -0.015326270833611488, -0.015424096025526524, 0.0322408452630043, 0.03737330809235573, -0.047794703394174576, 0.05547429993748665, 0.005930887535214424, -0.015062755905091763, 0.008258466608822346, 0.01022567879408598, 0.06087072193622589, 0.007284339051693678, 0.022084591910243034, -0.01612175814807415, -0.009039044380187988, -0.06430963426828384, -0.015057660639286041, 0.008685390464961529, -0.03386161848902702, 0.018354186788201332, -0.02404746226966381, -0.012369745410978794, -0.040405452251434326, 0.05383742228150368, 0.014857535250484943, 0.033438753336668015, -0.14981618523597717, -0.022388510406017303, -0.036637477576732635, -0.020052671432495117, -0.016398068517446518, -0.0005697000888176262, -0.06902720779180527, -0.0025310541968792677, 0.05783424898982048, 0.02250239998102188, 0.014611314050853252, 0.04066735878586769, 0.00300582405179739, -0.028831738978624344, -0.009766437113285065, 0.040007613599300385, -0.058887675404548645, -0.025242367759346962, 0.10545992851257324, -0.014464003033936024, -0.019688500091433525, -0.032240238040685654, 0.007741216104477644, -0.014817788265645504, -0.005294310860335827, -0.009751190431416035, 0.09019748121500015, -0.017437521368265152, -0.06265914440155029, -0.057704221457242966, 0.00859080720692873, -0.06546543538570404, -0.004129454027861357, -0.020455116406083107, 0.05059811472892761, -0.004217900335788727, 0.03984281048178673, 0.048670269548892975, -0.005504573229700327, 0.02270047925412655, 0.029210766777396202, -0.0047812494449317455, 0.010011570528149605, 0.045249130576848984, 0.06255249679088593, -0.012775039300322533, 0.008122137747704983, -0.0018138601444661617, 0.000026998128305422142, 0.0216197706758976, 0.02600482478737831, -0.007356948684900999, 0.03266024589538574, -0.02464434877038002, -0.10276497900485992, -0.0006771708140149713, -0.0014961297856643796, -0.04106253385543823, -0.00024808719172142446, -0.0405680350959301, -0.034526094794273376, 0.020652325823903084, -0.04463504999876022, -0.005326863378286362, -0.04061209410429001, -0.038346994668245316, 0.0024482598528265953, 0.03547953441739082, -0.0050977421924471855, -0.006772624794393778, 0.06344446539878845, -0.015169334597885609, -0.018500974401831627, 0.011599072255194187, -0.030410023406147957, 0.03234513849020004, -0.022846240550279617, -0.010361971333622932, 0.02468063309788704, 0.012043573893606663, 0.007824473083019257, -0.0090591786429286, 0.0004701225261669606, 0.013981325551867485, 0.06118045002222061, 0.06522957980632782, 0.021234145388007164, 0.04057712107896805, 0.033935319632291794, -0.027113117277622223, 0.0007197982631623745, 0.018686775118112564, -0.008083969354629517, -0.08532986044883728, 0.023707836866378784, -0.04013441503047943, 0.05228119716048241, -0.018829166889190674, -0.027170049026608467, 0.09900785237550735, 0.035017821937799454, 0.004662012681365013, 0.03786669299006462, 0.04028595983982086, 0.042122818529605865, 0.014789169654250145, 0.02760012447834015, 0.0332280769944191, 0.0647226870059967, -0.06703562289476395, 0.043922122567892075, 0.02756533958017826, 0.032141733914613724, -0.016183964908123016, 0.002788290148600936, -0.0051752422004938126, 0.03762295842170715, -0.03804110735654831, -0.01763404905796051, 0.058424606919288635, 0.053213413804769516, -0.042705174535512924, 0.01598568819463253, -0.031031571328639984, 0.021110400557518005, 0.021237220615148544, 0.012559570372104645, 0.018060952425003052, 0.0037035183049738407, -0.000574476842302829, -0.07119885087013245, -0.05362232029438019, -0.0507238544523716, -0.03159349784255028, -0.042044345289468765, 0.009152011945843697, 0.017181431874632835, -0.03813008964061737, 0.02642187289893627, -0.05189143493771553, 0.03728633001446724, 0.00021379553072620183, 0.018653815612196922, -0.043896954506635666, -0.01186632364988327, -0.0077099669724702835, 0.028641970828175545, 0.043815504759550095, 0.06058646738529205, -0.0205601267516613, -0.032377224415540695, 0.029884546995162964, -0.03532936051487923, 0.03799400478601456, 0.01769944839179516, 0.05353650078177452, -0.034355662763118744, -0.005864977370947599, -0.029098544269800186, -0.06880653649568558, -0.0007811715477146208, 0.09216301143169403, -0.05010421574115753, 0.046293314546346664, -0.08083964884281158, 0.004884331952780485, 0.017001988366246223, -0.010958898812532425, 0.021374493837356567, -0.03105838969349861, 0.052778810262680054, -0.027022337540984154, -0.01499743852764368, -0.024303684011101723, -0.017944280058145523, 0.009020150639116764, -0.011248656548559666, 0.07106201350688934, 0.0019518040353432298, -0.01546433661133051, 0.0284010861068964, 0.06572370231151581, 0.012407953850924969, -0.02042495831847191, 0.02893572859466076, 0.07991530746221542, -0.011088657192885876, -0.010086245834827423, -0.02832871302962303, -0.06436880677938461, 0.0716743916273117, -0.02830176055431366, -0.06403952836990356, 0.04078401252627373, -0.013023925945162773, -0.011334636248648167, -0.002812813501805067, 0.035931188613176346, -0.0008800268988125026, 0.004505995195358992, -0.04560654237866402, 0.011774922721087933, 0.02983001619577408, 0.037259627133607864, -0.024319041520357132, 0.052378732711076736, -0.006214128341525793, 0.04401255026459694, 0.029590552672743797, 0.05256718769669533, 0.039599038660526276, -0.025123681873083115, -0.07633721828460693, 0.004619945306330919, 0.007118686567991972, 0.004713733214884996, 0.03336949273943901, 0.010503391735255718, 0.03250589594244957, -0.045513980090618134, 0.01990337297320366, -0.02898108772933483, -0.03934020176529884, 0.02584235928952694, 0.02078910544514656, -0.02548622526228428, 0.01863035187125206, 0.01474408432841301, 0.05629780888557434, 0.00614234060049057, -0.04061942547559738, 0.002953452756628394, 0.007667458150535822, -0.04035896807909012, -0.02998165600001812, 0.0069152857176959515, 0.025009743869304657, 0.049325693398714066, 0.06475874036550522, -0.02725890278816223, 0.008220001123845577, -0.01561059057712555, 0.021447477862238884, 0.02304292842745781, 0.008523463271558285, 0.02288457192480564, -0.04472577944397926, 0.040440063923597336, 0.028192568570375443, 0.0034938158933073282, 0.029225550591945648, -0.00982827041298151, 0.018547464162111282, 0.04847948998212814, 0.05704542621970177, 0.030283305794000626, -0.0023289653472602367, 0.04643411189317703, 0.015119453892111778, -0.000875831872690469, 0.005999415647238493, -0.04153071716427803, 0.04804680868983269, -0.0102646853774786, -0.002973315306007862, 0.00933853629976511, 0.018717456609010696, 0.017607450485229492, 0.012303474359214306, -0.030431672930717468, 0.008836518973112106, 0.00454137334600091, 0.010705752298235893, -0.009017417207360268, 0.08396260440349579, 0.024870581924915314, 0.0263812355697155, 0.04898839071393013, 0.0356297641992569, 0.010835886001586914, 0.019502732902765274, -0.0025671024341136217, -0.016950441524386406, -0.0036948341876268387, -0.01872052624821663, -0.00462128221988678, 0.06148068606853485, -0.03287021443247795, 0.029031259939074516, 0.040669649839401245, -0.010962841100990772, -0.014931418001651764, 0.025707582011818886, 0.04947029426693916, -0.014999176375567913, 0.010233581066131592, -0.01944884844124317, 0.02436751499772072, 0.0031660383101552725, -0.025446880608797073, -0.02872866578400135, 0.06145132705569267, -0.009470658376812935, 0.02387302555143833, 0.03423082455992699, 0.035253796726465225, -0.043715085834264755, -0.012728383764624596, -0.004232424311339855, -0.0698952004313469, 0.02869846671819687, 0.0009216339676640928, -0.0021070202346891165, 0.021354466676712036, 0.004907865542918444, 0.009232332929968834, -0.0013455828884616494, 0.03354382887482643, 0.010098019614815712, 0.028757590800523758, -0.040350209921598434, 0.011356007307767868, -0.06721767783164978, -0.007922555319964886, -0.050725024193525314, 0.02972813881933689, -0.01101289875805378, 0.004977711010724306, -0.030376672744750977, -0.049655597656965256, -0.01642792485654354, 0.07775174081325531, 0.006667519453912973, -0.05384904518723488, 0.017800547182559967, -0.01895260252058506, -0.06899365782737732, -0.007606999482959509, 0.04790603369474411, 0.0016879299655556679, -0.05182640627026558, 0.03603668510913849, 0.008734101429581642, 0.05671297386288643, 0.08781196922063828, 0.035869330167770386, 0.0006857535918243229, 0.032839786261320114, -0.03820547088980675, -0.006270923651754856, 0.014651038683950901, -0.010270555503666401, 0.023665187880396843, -0.004697843920439482, 0.02286773920059204, 0.04345107451081276, -0.007436929736286402, 0.001816157135181129, -0.0523424930870533, 0.024095764383673668, -0.02421385422348976, 0.0015471811639145017, 0.009590008296072483, -0.04575420171022415, -0.0016998393693938851, 0.06005889177322388, -0.009625738486647606, 0.041305720806121826, -0.026708262041211128, -0.029366441071033478, -0.039102908223867416, 0.012927509844303131, 0.012729019857943058, -0.028822826221585274, 0.019248278811573982, 0.00017873174510896206, 0.020163388922810555, -0.02776358649134636, 0.018732808530330658, 0.002056361874565482, 0.005391789600253105, -0.0400383323431015, 0.008351624943315983, 0.03537162020802498, -0.038873620331287384, 0.029522700235247612, 0.04028339684009552, -0.04351910576224327, -0.05483806133270264, -0.003121725982055068, 0.03488467261195183, -0.04382578283548355, -0.044196177273988724, 0.055216677486896515, 0.07073992490768433, 0.018814729526638985, -0.015153546817600727, 0.0021578643936663866, 0.009926823899149895, -0.019470391795039177, -0.023506468161940575, 0.02733442187309265, -0.02095678262412548, 0.0017292849952355027, 0.004395920783281326, -0.042281489819288254, -0.0048128883354365826, 0.04055305942893028, -0.007074260618537664, -0.031216194853186607, 0.009276025928556919, -0.011045802384614944, -0.006578000262379646, 0.01050029881298542, 0.021582428365945816, -0.017871994525194168, -0.02876921556890011, 0.025370921939611435, 0.02960824780166149, -0.0334688238799572, -0.03144979104399681, -0.04163461923599243, 0.04423363879323006, -0.024150660261511803, -0.05367643013596535, 0.0472174808382988, -0.019433394074440002, -0.00401483615860343, 0.039091069251298904, 0.06226669251918793, 0.05514254793524742, 0.0441819466650486, 0.045493002980947495, -0.07381843030452728, -0.030220333486795425, 0.024615328758955002, -0.034697018563747406, -0.03291903808712959, -0.0006323849665932357, 0.020407812669873238, 0.005047986749559641, -0.06075899302959442, 0.03637788072228432, 0.02864827774465084, -0.0464504100382328, 0.005867914762347937, -0.04045668989419937, 0.026815857738256454, -0.06424130499362946, -0.003582328325137496, -0.03476351872086525, 0.025334753096103668, 0.0413539744913578, 0.028878290206193924, -0.05053875595331192, -0.03465639799833298, -0.026503514498472214, -0.06099889054894447, 0.010738139040768147, -0.05570908635854721, 0.035070233047008514, 0.01703609526157379, -0.02560574747622013, 0.026303861290216446, 0.0577499195933342, 0.042599573731422424, -0.02473832108080387, 0.00405796617269516, -0.01611839421093464, -0.007793141528964043, -0.004124889615923166, 0.027262909337878227, 0.07014037668704987, 0.04851521924138069, -0.02023569494485855, -0.037138503044843674, 0.03939736261963844, 0.03707888349890709, -0.005486353300511837, -0.03292888402938843, 0.011789578013122082, -0.006520797032862902, -0.008761761710047722, 0.052441611886024475, -0.011219863779842854, -0.06641878187656403, -0.02961043454706669, -0.05985401198267937, 0.057945944368839264, -0.03244408592581749, -0.039271023124456406, 0.029340846464037895, 0.014698236249387264, 0.03466179966926575, 0.025122450664639473, 0.05126823112368584, 0.012010590173304081, 0.012085368856787682, 0.05185868591070175, -0.016892695799469948, 0.002303335117176175, -0.026624834164977074, 0.0208709929138422, 0.016259120777249336, -0.023342430591583252, 0.02963373064994812, 0.028120946139097214, 0.007705794647336006, -0.02066195197403431, -0.01932140626013279, -0.026153409853577614, -0.0013919948833063245, -0.020878510549664497, -0.044180482625961304, 0.04200853407382965, 0.03199620544910431, -0.013292820192873478, 0.02915188856422901, 0.033889640122652054, -0.011010543443262577, -0.001074202242307365, -0.025062233209609985, 0.019582601264119148, 0.05375922471284866, -0.0611996054649353, 0.03825601562857628, 0.01208321750164032, -0.009200607426464558, 0.03171129152178764, 0.04454581439495087, -0.006706818472594023, -0.042874161154031754, -0.0032500801607966423, 0.014646999537944794, 0.013187631964683533, -0.0424564890563488, 0.10711894929409027, -0.02074534259736538, 0.0027356725186109543, 0.01980499178171158, 0.006923704873770475, 0.006321735680103302, 0.01617269031703472, 0.045531317591667175, -0.008122546598315239, 0.05732602998614311, -0.015343885868787766, -0.04200612008571625, -0.016048872843384743, -0.054562799632549286, 0.021449202671647072, 0.026946399360895157, -0.10857202857732773, 0.010141948238015175, 0.02635359764099121, -0.03072328120470047, 0.006231748498976231, 0.008020976558327675, 0.01619039475917816, -0.005510501563549042, 0.004950778093189001, -0.03908536583185196, -0.007982117123901844, 0.05498766899108887, -0.023803627118468285, -0.021296901628375053, -0.027595585212111473, -0.02005426399409771, 0.022091364488005638, -0.05007702857255936, 0.03771459311246872, 0.11967640370130539, -0.028996823355555534, -0.015668543055653572, 0.08046252280473709, -0.01796666905283928, 0.055382512509822845, 0.06610836833715439, -0.013467290438711643, 0.05295150354504585, 0.010241592302918434, 0.017161767929792404, -0.01899106428027153, 0.021492725238204002]
, options => JSON '{"num_leaves_to_search": 10}') AS distance
FROM
Products@{force_index=ProductEmbeddingIndex} AS t
WHERE t.Embeddings IS NOT NULL
ORDER BY
distance
LIMIT 5;
And here’s the output.
6. Adding a product ➕
To maintain a comprehensive and intelligently searchable product catalog, the process of adding new products is streamlined and integrated with AI capabilities from the moment data enters the system. This ensures that new items are not only stored reliably but also become immediately discoverable through semantic search.
A new product is introduced to the system. This could be through:
- An API call to the Flask application’s
/api/products
endpoint, providing product details like name, description, category, price, etc. - An external data ingestion process (like Dataflow or a batch script) that prepares product data.
- Required Data: Crucially, the product needs at least a
Name
andDescription
for effective AI processing. Other fields likeCategory
,Brand
,Price
, andRegion
contribute to a richer catalog. - If a
Description
is provided, the Flask application’sembedding_service
takes this text. - It makes a call to the Vertex AI API, utilizing a chosen embedding model (
textembedding-005
). - Vertex AI processes the description and returns a dense vector embedding — a numerical representation of the text’s semantic meaning.
Insertion using Mutations
For transactional data ingestion programmatically, especially when adding multiple products with generated embeddings, using Spanner’s Mutations API in your Python application is highly recommended. Mutations allow you to batch multiple DML statements (like INSERTs or UPDATEs) into a single atomic transaction, which is more efficient and guarantees atomicity.
- Our Flask application’s
productService
(spanner_client.py
) receives the product data along with the generated embedding. - It constructs a
Mutation
object. Mutations are efficient for batching multiple database operations (like inserts) into a single atomic transaction. - The
Mutation.insert
method is used to prepare the data for insertion into theProducts
table. TheEmbeddings
column is populated with the vector generated in the previous step. - These mutations are then executed against the Spanner database, ensuring that the product data and its embedding are saved reliably and atomically.
The new product becomes instantly searchable via semantic queries because the embedding is stored transactionally with the product data. There’s no delay from separate indexing processes. If a user searches for something semantically related to the new product’s description, it can be surfaced in the results immediately.
Web Application using Spanner AI
Source
Clone & Deploy
Set up Spanner instance, database and schema. Ingest data and set it up for the product search. Launch Cloud Shell Terminal from your Google Cloud Project, clone the repo and deploy and try it out at your end using the steps below!!
git clone https://github.com/AbiramiSukumaran/spanner-vertex-productsgcloud run deploy --source . --set-env-vars GOOGLE_CLOUD_PROJECT=<>,SPANNER_INSTANCE_ID=<>,SPANNER_DATABASE_ID=<>,VERTEX_AI_REGION=us-central1,VERTEX_AI_EMBEDDING_MODEL=text-embedding-005
You will be prompted for the app / service name, region and authentication confirmation. Since this is a demo app, you can try unauthenticated, but for our real world scenarios best practice is to set up authentication.
Once the deployment is complete you’ll get a slightly-shiny application for testing our use case. Also note that our data is sample test data so the data name, description etc. might not match and there are duplicates as well. Your search relevance will be impacted with such test data.
Why this Architecture is a Game-Changer!
- Unmatched Relevance: Semantic search allows users to find products based on intent and meaning, not just exact keyword matches.
- Real-Time Freshness & Consistency: By integrating embedding generation and vector search within a transactional Spanner database (or closely coupled with it), we eliminate the eventual consistency issues often found with separate search indexes or vector databases. Product updates are immediately reflected in search results.
- Scalability and Availability: Spanner’s inherent global scale and 99.999% availability ensure the catalog and search functionality are always accessible and performant, regardless of user load or geographic distribution.
- Simplified ML Integration: While we used an external embedding generation in Python, Spanner’s
ML.PREDICT
capability offers a path to further simplify workflows by invoking ML models directly within SQL, potentially reducing the operational overhead of managing separate ML serving infrastructure. - Performance & Cost Efficiency: By leveraging Spanner’s distributed compute for query processing and its integrated vector search capabilities, we can achieve high performance with potentially reduced operational costs compared to managing separate, highly available search clusters and ML inference endpoints.
This design delivers a powerful, intelligent, and scalable product search experience, perfectly illustrating how Spanner AI capabilities can revolutionize e-commerce platforms. If you like to build such dta driven applications across databases, AI integration and Serverless platforms, all in instructor-led free virtual sessions, register for Code Vipassana here: https://codevipassana.dev.
Source Credit: https://medium.com/google-cloud/global-real-time-ai-powered-product-catalog-with-spanner-ai-5421c43c5e71?source=rss—-e52cf94d98af—4