Skip to content

Commit bb8d5bf

Browse files
author
rmoff
committed
Completed pipeline
1 parent 69e9a8a commit bb8d5bf

File tree

9 files changed

+225
-104
lines changed

9 files changed

+225
-104
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@ _Learn more [here](https://decodable.co), and [sign up for a free trial](https:/
3535

3636
### Data Pipelines
3737

38-
| Example | Description |
39-
|-----------------------------------------------------|--------------------------------------------------------|
40-
| [Opinionated Data Pipelines](opinionated-pipelines) | Building data pipelines with schema on write streams. |
41-
| [Postman](postman) | Building data pipelines with Postman. |
38+
| Example | Description |
39+
|-----------------------------------------------------|----------------------------------------------------------|
40+
| [Opinionated Data Pipelines](opinionated-pipelines) | Building data pipelines with schema on write streams. |
41+
| [Postman](postman) | Building data pipelines with Postman. |
42+
| [Postgres to Snowflake](postgres-to-snowflake-with-cdc) | Getting data from Postgres to Snowflake using Decodable. |
4243

4344
### PyFlink
4445

postgres-to-snowflake-with-cdc/README.adoc

Lines changed: 60 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
= Docker Compose for running Postgres locally, accessible from the internet using ngrok
1+
= Postgres CDC to Snowflake
2+
3+
This is supporting code for a blog about streaming data from Postgres to Snowflake using Decodable.
4+
5+
It includes a Docker Compose for running Postgres locally, accessible from the internet using ngrok
26

37
To use this you need to https://dashboard.ngrok.com/signup[create an ngrok account] and add a file called `.env` in this folder with the following entry:
48

@@ -14,14 +18,14 @@ Bring up the Postgres and ngrok stack with
1418
docker compose up
1519
----
1620

17-
Once up, find out your Postgres server host/post that is available on the internet:
21+
Once up, you can find out your Postgres server host/post that is available on the internet:
1822

1923
[source,bash]
2024
----
2125
curl -s localhost:4040/api/tunnels | jq -r '.tunnels[0].public_url' | sed 's/tcp:\/\///g'
2226
----
2327

24-
== Steps
28+
== Source connection: Postgres CDC
2529

2630
=== Configure Postgres tables for replication
2731

@@ -41,38 +45,16 @@ Check their replica status; each should show `f`:
4145

4246
[source,sql]
4347
----
44-
SELECT relreplident FROM pg_class
48+
SELECT oid::regclass, relreplident FROM pg_class
4549
WHERE oid in ( 'customers'::regclass, 'pets'::regclass, 'appointments'::regclass);
4650
----
4751

48-
=== Add PKs to the Postgres tables
49-
50-
[source,bash]
51-
----
52-
docker exec -it postgres psql -h localhost -U postgres -d postgres -f /data/add-pk.sql
53-
----
54-
55-
56-
[source,sql]
57-
----
58-
alter table customers
59-
alter column customer_id set not null;
60-
alter table customers
61-
add constraint pk_customers primary key (customer_id);
62-
63-
alter table pets
64-
alter column pet_id set not null;
65-
alter table pets
66-
add constraint pk_pets primary key (pet_id);
67-
68-
alter table appointments
69-
alter column appointment_id set not null;
70-
alter table appointments
71-
add constraint pk_appointments primary key (appointment_id );
72-
----
52+
=== Store the password
7353

54+
Ref:
7455

75-
=== Store the password
56+
* https://docs.decodable.co/declarative/apply.html
57+
* https://docs.decodable.co/administer/manage-secrets.html
7658

7759
[source,bash]
7860
----
@@ -91,6 +73,8 @@ result: updated
9173

9274
=== Generate resource definitions
9375

76+
Ref: https://docs.decodable.co/declarative/scan.html
77+
9478
[source,bash]
9579
----
9680
decodable connection scan \
@@ -107,29 +91,6 @@ decodable connection scan \
10791
> omd-pg.yaml
10892
----
10993

110-
=== Edit resource definitions
111-
112-
113-
- set to active
114-
115-
[source,yaml]
116-
----
117-
spec_version: v2
118-
spec:
119-
execution:
120-
active: true
121-
----
122-
123-
- add tags
124-
125-
[source,yaml]
126-
----
127-
metadata:
128-
tags:
129-
project: "oh-my-dawg"
130-
author: "rmoff"
131-
----
132-
13394
=== Apply resource definitions
13495

13596
[source,bash]
@@ -161,3 +122,49 @@ id: 3cc8e060
161122
result: created
162123
----
163124

125+
=== Activate the Postgres connection
126+
127+
[source,bash]
128+
----
129+
decodable query --name oh-my-dawg-pg -X activate --stabilize
130+
----
131+
132+
== Sink connection: Snowflake
133+
134+
=== Provision Snowflake resources
135+
136+
Ref:
137+
138+
* https://docs.decodable.co/connect/sink/snowflake.html
139+
* https://docs.snowflake.com/en/user-guide/key-pair-auth#verify-the-user-s-public-key-fingerprint
140+
141+
=== Generate & create the Snowflake sink
142+
143+
_You can pipe from one command to another to streamline the process._
144+
145+
[source,bash]
146+
----
147+
decodable connection scan \
148+
--name oh-my-dawg-snowflake \
149+
--connector snowflake \
150+
--type sink \
151+
--prop snowflake.database=omd \
152+
--prop snowflake.schema=omd \
153+
--prop snowflake.user=decodable \
154+
--prop snowflake.private-key=$(decodable query --name omd-snowflake --keep-ids | yq '.metadata.id') \
155+
--prop snowflake.role=load_data \
156+
--prop snowflake.account-name=<org>-<account> \
157+
--prop snowflake.warehouse=stg \
158+
--prop snowflake.merge-interval="1 minute" \
159+
--include-pattern stream-name='^omd-' \
160+
| decodable apply -
161+
----
162+
163+
NOTE: If you encounter errors then remove the pipe and `decodable apply` and inspect the YAML generated by `decodable connection scan` first.
164+
165+
=== Activate the Snowflake connection
166+
167+
[source,bash]
168+
----
169+
decodable query --name oh-my-dawg-snowflake -X activate --stabilize
170+
----

postgres-to-snowflake-with-cdc/decodable/omd-pg.yaml renamed to postgres-to-snowflake-with-cdc/decodable/sample_connection_resource_definitions/omd-pg.yaml

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@ kind: connection
33
metadata:
44
name: oh-my-dawg-pg
55
description: ""
6-
tags:
7-
project: "oh-my-dawg"
8-
author: "rmoff"
96
spec_version: v2
107
spec:
118
execution:
129
active: true
1310
connector: postgres-cdc
1411
properties:
1512
database-name: postgres
16-
hostname: 2.tcp.eu.ngrok.io
17-
password: omd-pg
18-
port: "19366"
13+
hostname: <hostname>
14+
port: <port>
1915
username: postgres
16+
password: omd-pg
2017
stream_mappings:
2118
- stream_name: omd-appointments
2219
external_resource_specifier:
@@ -38,9 +35,6 @@ spec:
3835
kind: stream
3936
metadata:
4037
name: omd-appointments
41-
tags:
42-
project: "oh-my-dawg"
43-
author: "rmoff"
4438
spec_version: v1
4539
spec:
4640
schema_v2:
@@ -79,9 +73,6 @@ spec:
7973
kind: stream
8074
metadata:
8175
name: omd-customers
82-
tags:
83-
project: "oh-my-dawg"
84-
author: "rmoff"
8576
spec_version: v1
8677
spec:
8778
schema_v2:
@@ -111,9 +102,6 @@ spec:
111102
kind: stream
112103
metadata:
113104
name: omd-pets
114-
tags:
115-
project: "oh-my-dawg"
116-
author: "rmoff"
117105
spec_version: v1
118106
spec:
119107
schema_v2:
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
kind: connection
3+
metadata:
4+
name: oh-my-dawg-snowflake
5+
description: ""
6+
spec_version: v2
7+
spec:
8+
execution:
9+
active: true
10+
connector: snowflake
11+
properties:
12+
snowflake.account-name: <org_name>-<account_name>
13+
snowflake.database: omd
14+
snowflake.private-key: omd-snowflake
15+
snowflake.role: load_data
16+
snowflake.schema: omd
17+
snowflake.user: decodable
18+
snowflake.warehouse: stg
19+
snowflake.merge-interval: 1 minute
20+
stream_mappings:
21+
- stream_name: omd-appointments
22+
external_resource_specifier:
23+
snowflake.table: omd-appointments
24+
- stream_name: omd-pets
25+
external_resource_specifier:
26+
snowflake.table: omd-pets
27+
- stream_name: omd-customers
28+
external_resource_specifier:
29+
snowflake.table: omd-customers
30+
type: sink
31+
---
32+
kind: external-resource
33+
spec_version: v1
34+
spec:
35+
connection: oh-my-dawg-snowflake
36+
external_resource_specifier:
37+
snowflake.table: omd-appointments
38+
schema:
39+
- name: appointment_id
40+
type: VARCHAR NOT NULL
41+
- name: duration_minutes
42+
type: BIGINT
43+
- name: appointment_time
44+
type: VARCHAR
45+
- name: status
46+
type: VARCHAR
47+
- name: pet_id
48+
type: VARCHAR
49+
- name: customer_id
50+
type: VARCHAR
51+
- name: service_type
52+
type: VARCHAR
53+
- name: price
54+
type: DOUBLE
55+
- name: last_updated
56+
type: VARCHAR
57+
configuration:
58+
primary_key:
59+
- appointment_id
60+
---
61+
kind: external-resource
62+
spec_version: v1
63+
spec:
64+
connection: oh-my-dawg-snowflake
65+
external_resource_specifier:
66+
snowflake.table: omd-customers
67+
schema:
68+
- name: customer_id
69+
type: VARCHAR NOT NULL
70+
- name: first_name
71+
type: VARCHAR
72+
- name: last_name
73+
type: VARCHAR
74+
- name: phone
75+
type: VARCHAR
76+
- name: email
77+
type: VARCHAR
78+
- name: registered_date
79+
type: VARCHAR
80+
configuration:
81+
primary_key:
82+
- customer_id
83+
---
84+
kind: external-resource
85+
spec_version: v1
86+
spec:
87+
connection: oh-my-dawg-snowflake
88+
external_resource_specifier:
89+
snowflake.table: omd-pets
90+
schema:
91+
- name: pet_id
92+
type: VARCHAR NOT NULL
93+
- name: customer_id
94+
type: VARCHAR
95+
- name: pet_name
96+
type: VARCHAR
97+
- name: pet_type
98+
type: VARCHAR
99+
configuration:
100+
primary_key:
101+
- pet_id
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
kind: secret
3+
metadata:
4+
name: omd-snowflake
5+
spec_version: v1
6+
spec:
7+
# Don't work, this is a throwaway private key 😅
8+
value_literal: |
9+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCRxsQUE8CdKQXu
10+
Xz0Hcwjej5JYWAvgS68WsE/P1pwRjhE8bba5w3LmD1ijMlBShh3RvcM2gT9Cj3Lx
11+
1X8D9pcdcOlwdOT3alEzXZjQlV7C4HC18xkEiEgNMjvWvP312af+Syq09u1UL4Hy
12+
zl2TSI2XBTxttRmN6lY07Uk9ZHlGDjcL7+1ALg0Cq14PKFPNMnibKJ5cOWrtwZad
13+
+HF6xTjyZRBDUpH72irHWIvwouLIM0ANhP6qTTaL5uxiSBxNyVcVKsgmaO2w1D+U
14+
njHGL9lR55HPm1OoG6HOgI4KYDZ5xnVCQ0KCtiBHrrgtFOK00Gh31tBITxvlS7qs
15+
kOGw1hz/AgMBAAECggEAAgJAr0KK0j+ZK6AK81oHXCj5uP27NpCK1AYcjqS9vgb8
16+
pgRtbHrmgu56mxxO4gw9R44hxtPNUnZilmi9DHA7kQ+X9lF0dEcpR+Sfl5EI+MHm
17+
zul2LBgd3tyupjaXGbbk0aRchswRfvb9B4kGquICXXt+sGGHl1G/qJDahDePU9x9
18+
KE2WiMvXqbsdcD3U2RgFB8rSVQwx7vxLFH8islezyM3U7Q/G8LhWXtSEDoBR7vWD
19+
uRpQ5+wqgBx8VdxJNt6dBYq5Umjag4FGnNTAEIJLqSfSHalofGMA/WjLjcb/YXth
20+
o/Eyc+9U4+9rw90diM+tcBl3rr3UAHcLzC5WNoeVtQKBgQDIysJ3tcXgCZSs/TfU
21+
aE1bgirtJxgRnvYBXKO1yQij20IsL5UM7YOCFxgzHmmjNuemKDeoCmKcU+tVFguE
22+
ga4q9hhHTX8ZyqecgXUVwdMze67Sbv9T1eoG0smnvA+AjlElP5fKVUy9pBJI5+/5
23+
+gKYilSfHxAj6N2hTnfp/OO2OwKBgQC525kcFSSNPw4Gt9VfaWlJtk0Oa6lrOn3H
24+
4q00mYAcao5uIMAny7rW2KZB6YDB6X85y41XzecjOai/mAetNjNMR4KgDDMgP7JQ
25+
yDjfHH8RxFcO86pxUriZeiU8X+dxRQQ7FXP/A5rCHtwZ+pj9YxE0/ZunDYxQmaC8
26+
sbX7dEfUDQKBgG9aiviKlT9G8O3yzBh+84+xI487pAx5pKJituOkpqcAfLU2eime
27+
OtVVa3VGA32hgFxUZ3FIuSFLJPKd9Cs7I9Ttf89jOf6atdOEs+MqB6/AgtZu+iiL
28+
NGsuUOk10T8RLg1DNDHglluBdyZ5gkuWjAP+iylnt7LCfM7tTnE0bzBrAoGBALSL
29+
zsCpCUjs6AM+sdht3gntPg20KHAx8d4rJXbjZsA0AwiYaBJAps/uxhNhceLtoNnU
30+
Ewooy1A8wuDcHxj0fgCrtwki0MeTGPXAiv6x//6SbL/plLlhUlJFhcaQo5Q1J1b+
31+
ECC6r6vDrqzN87CyfBSuCHbPgm8Jzkt/lvkejGhBAoGBAIAine7tBZ5G4bJxir3k
32+
jA3tyO0TgdXUJz0KIJLiXeNf8K7xn+8CDu6UZ/WuDAMxMkwZdgZydufSWjhBGOXc
33+
icGEqep9T0p/DPD6kz+HTMd1ad+Z+KtHA8G29NPwmn93TDAnVgQRIFh2LOHmwN74
34+
TK5RPdOw9kN1/BXQpWQOdwzn

postgres-to-snowflake-with-cdc/docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ services:
3434
volumes:
3535
- ./shadowtraffic:/data
3636
command: --config /data/config.json
37-
# --with-studio --sample 20 --watch
37+
# --with-studio --sample 20 --watch
3838
ports:
3939
# - 8080:8080
4040
- 9400:9400

postgres-to-snowflake-with-cdc/postgres/add-pk.sql

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)