From d8524bd240183a23cf95492be83b6cae6671bbfe Mon Sep 17 00:00:00 2001 From: addloopy Date: Sat, 5 Jul 2025 14:19:24 +0530 Subject: [PATCH] Draft RFC for pull features from Snowflake feature store --- .gitignore | 3 +- rfc/00001/RFC-00001.drawio | 101 ++++++++++++++++++++++++++++ rfc/00001/arch.png | Bin 0 -> 44624 bytes rfc/00001/snowflake-connector.md | 110 +++++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 rfc/00001/RFC-00001.drawio create mode 100644 rfc/00001/arch.png create mode 100644 rfc/00001/snowflake-connector.md diff --git a/.gitignore b/.gitignore index 343659ac..0cc1c7b6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ trufflebox-ui/node_modules/ dist/ .env* .idea/ -workspace/ \ No newline at end of file +workspace/ +*.drawio.bkp \ No newline at end of file diff --git a/rfc/00001/RFC-00001.drawio b/rfc/00001/RFC-00001.drawio new file mode 100644 index 00000000..83fcb6af --- /dev/null +++ b/rfc/00001/RFC-00001.drawio @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rfc/00001/arch.png b/rfc/00001/arch.png new file mode 100644 index 0000000000000000000000000000000000000000..2474b338daa03351fad8328e08aac9b810909913 GIT binary patch literal 44624 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 U%%lroy;q2_frWmxF6!3505R2ti~s-t literal 0 HcmV?d00001 diff --git a/rfc/00001/snowflake-connector.md b/rfc/00001/snowflake-connector.md new file mode 100644 index 00000000..092b7f4d --- /dev/null +++ b/rfc/00001/snowflake-connector.md @@ -0,0 +1,110 @@ +# RFC00001: Snowflake Feature Pull Connector for BharatMLStack + +## Metadata +- **Author:** Adarsha Das +- **Date Created:** 2025-07-05 +- **Status:** Draft +- **Target Release:** v1.1.0 +- **Discussion Channel:** #connectors-snowflake + +--- + +## 1. Summary +This proposal introduces native support for **Snowflake** as an offline feature store **and** describes an end-to-end pipeline that ports those features into **BharatMLStack's online feature store** so they can be served for real-time inference. The connector retrieves features from Snowflake using the `snowflake.ml.feature_store` API, applies the transformation/deduplication logic in `data_helpers.py`, then relies on `spark_feature_push_client` to serialize the resulting Spark DataFrame into Protobuf and push it—via Kafka or another configured sink—into the online store. The aim is to provide a single, frictionless path from Snowflake to online serving with zero intermediate exports. + +## 2. Motivation +1. **Industry Adoption** – Snowflake is the de-facto cloud data warehouse for many ML teams. +2. **Unified Governance** – Keeping features in Snowflake allows teams to reuse established data contracts, lineage, and RBAC. +3. **Performance & Scale** – Snowflake's automatic scaling matches BharatMLStack's design philosophy of elastic cost. +4. **Developer Ergonomics** – The `snowflake.ml.feature_store` SDK abstracts low-level SQL and improves developer productivity. + +Without this connector, users have to export data to object storage before ingesting it, introducing latency and operational overhead. + +## 3. Goals +- Add **`SNOWFLAKE_TABLE`** as a new `source_type` value consumed by `read_from_source()`. +- Use the **`snowflake.ml.feature_store.FeatureStore`** abstraction for reading data, not raw JDBC. +- Support predicate-pushdown on a configurable partition column (default: `ingestion_timestamp`). +- Preserve existing semantics: the helper must return a Spark DataFrame deduplicated on entity keys. +- Provide clear configuration & credential management guidelines. +- Seamlessly push the transformed Snowflake features to BharatMLStack's online feature store through `spark_feature_push_client`, enabling immediate consumption at inference time. + +## 4. Non-Goals +- Online serving from Snowflake (future work). +- Bi-directional sync or writing features back into Snowflake. +- Support for Snowflake external tables or stages (out of scope for first version). + +## 5. Detailed Design + +![Arch](./arch.png) +### 5.1 Data Pull Flow +1. **Session Creation** – A `snowpark.Session` is established using env-vars or a passed-in config map. +2. **Feature Store Bind** – Instantiate `FeatureStore(session)`. +3. **Table Selection** – Retrieve the maximum value of the partition column via the Feature Store API. +4. **Data Retrieval** – Load the filtered rows into a Spark DataFrame through `feature_store.read().to_spark()`. +5. **Schema Alignment** – Column names are normalized and optionally renamed according to `feature_mapping`. + +### 5.2 Data Push Flow +1. **Null Handling & Default Filling** – Invoke `fill_na_features_with_default_values` to replace missing values based on feature metadata. +2. **Proto Generation** – Use `OnlineFeatureStorePyClient.generate_df_with_protobuf_messages` (from `spark_feature_push_client`) to convert rows into Protobuf messages. +3. **Batching & Kafka Write** – Call `write_protobuf_df_to_kafka`, optionally in batches, to stream messages to the online feature-store ingress topic. +4. **Audit Sink (Optional)** – If `features_write_to_cloud_storage` is `true`, persist the transformed DataFrame to the configured cloud path for replay or debugging. +5. **Online Availability** – Once consumed by BharatMLStack's ingestion service, features become queryable for real-time inference within SLA (p95 < X s). + +### 5.3 API Touch-Points +| Function | Change | Rationale | +| --- | --- | --- | +| `read_from_source()` | Add `elif source_type == "SNOWFLAKE_TABLE"` branch to perform steps in 5.1 | Aligns with existing connector pattern | +| `src_type_to_partition_col_map` | Append `"SNOWFLAKE_TABLE": "ingestion_timestamp"` | Ensures incremental loads | + +### 5.4 Configuration +| Parameter | Description | Example | +| --- | --- | --- | +| `SNOWFLAKE_ACCOUNT` | Snowflake account identifier | `xy12345.us-east-1` | +| `SNOWFLAKE_USER` | Login user | `ml_readonly` | +| `SNOWFLAKE_PASSWORD` | Password or OAuth token | `****` | +| `SNOWFLAKE_WAREHOUSE` | Virtual warehouse used for queries | `ML_WH` | +| `SNOWFLAKE_DATABASE` | Database containing feature tables | `FEATURE_DB` | +| `SNOWFLAKE_SCHEMA` | Default schema | `PUBLIC` | + +Credentials are loaded by helper utilities and injected into the Snowpark session. Users may override through function arguments for multi-account scenarios. + +### 5.5 Error Handling & Observability +- **Retry Logic:** Exponential backoff for transient Snowflake errors (`XYZ-0001`, `XYZ-0002`). +- **Metrics:** + - `snowflake.connector.latency_ms` + - `snowflake.connector.rows_read` + - `snowflake.connector.bytes_scanned` +- **Logging:** Obfuscate sensitive parameters before logging connection strings. + +## 6. Test Plan +1. **Unit Tests** – Mock `snowflake.ml.feature_store` to simulate data fetches. +2. **Integration Tests** – Use Snowflake's `TPCH_SF1` sample to validate partition filtering, column renaming, null-handling, Protobuf generation, and successful push to the online feature store. +3. **Schema Drift Tests** – Verify connector surface errors when expected columns are missing or types change. + +## 7. Rollout Strategy +| Phase | Description | +| --- | --- | +| Alpha | Flag-guarded release for internal users. Collect feedback. | +| Beta | Publicly documented. Backwards compatibility guaranteed. | +| GA | Remove feature flag. Add SLA monitoring. | + +## 8. Alternatives Considered +- **Spark JDBC Connector** – Simpler, but lacks feature-store metadata awareness and type mapping. +- **Materializing Views to Parquet** – Adds latency and duplication of storage. + +## 9. Risks & Mitigations +| Risk | Impact | Mitigation | +| --- | --- | --- | +| Snowflake API instability | Ingest failures | Pin to major version; add compatibility tests | +| Credential leakage | Security breach | Use secrets manager integration; no plaintext logs | +| Large data pulls | Cost overruns | Enforce partition filters; sample row counts during CI | + +## 10. Success Metrics +- 100% schema parity with Snowflake source tables. + +## 11. Future Work +- Automatic statistics push to BharatML observability dashboard. + +## 12. References +- Snowflake ML Feature Store Documentation (2024-04) — *"snowflake.ml.feature_store"*. +