You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+41-24Lines changed: 41 additions & 24 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,7 @@ For scalability details, see [benchmarks](/benchmarks/README.md).
12
12
13
13
## How it works
14
14
15
-
We just need to have three tables (postgres syntax):
15
+
We just need to have a few tables (postgres syntax):
16
16
17
17
```sql
18
18
CREATETABLEtopic (
@@ -21,28 +21,51 @@ CREATE TABLE topic (
21
21
created_at TIMESTAMPNOT NULL DEFAULT NOW()
22
22
);
23
23
24
+
CREATETABLEconsumer (
25
+
topic TEXTNOT NULL,
26
+
name TEXTNOT NULL,
27
+
partition SMALLINTNOT NULL,
28
+
first_event_id BIGINT,
29
+
last_event_id BIGINT,
30
+
last_consumption_at TIMESTAMP,
31
+
consumed_events BIGINTNOT NULL,
32
+
created_at TIMESTAMPNOT NULL DEFAULT NOW(),
33
+
PRIMARY KEY (topic, name, partition)
34
+
);
35
+
24
36
CREATETABLEevent (
25
37
topic TEXTNOT NULL,
26
38
id BIGSERIALNOT NULL,
27
39
partition SMALLINTNOT NULL,
28
40
key TEXT,
29
41
value BYTEANOT NULL,
42
+
buffered_at TIMESTAMPNOT NULL,
30
43
created_at TIMESTAMPNOT NULL DEFAULT NOW(),
31
44
metadata JSON NOT NULL,
32
45
PRIMARY KEY (topic, id)
33
46
) PARTITION BY LIST (topic);
34
47
35
-
CREATETABLEconsumer (
48
+
-- Same schema as event, just not partitioned. --
49
+
-- It is used to handle eventual consistency of auto increment; --
50
+
-- there is no guarantee that record of id 2 is visible after id 1 record. --
51
+
-- Events are first inserted to the event_buffer; --
52
+
-- they are then moved to event table in bulk, by a single, serialized writer; --
53
+
-- because there is only one writer, it fixes eventual consistency issue --
54
+
CREATETABLEevent_buffer (
36
55
topic TEXTNOT NULL,
37
-
name TEXTNOT NULL,
56
+
id BIGSERIALPRIMARY KEY,
38
57
partition SMALLINTNOT NULL,
39
-
first_event_id BIGINT,
40
-
last_event_id BIGINT,
41
-
last_consumption_at TIMESTAMP,
42
-
consumed_events BIGINTNOT NULL,
58
+
key TEXT,
59
+
value BYTEANOT NULL,
43
60
created_at TIMESTAMPNOT NULL DEFAULT NOW(),
44
-
PRIMARY KEY (topic, name, partition)
61
+
metadata JSON NOT NULL
62
+
);
63
+
-- Used to lock single event_buffer to event writer; --
64
+
-- there cannot be more than one record of this table! --
65
+
CREATETABLEevent_buffer_lock (
66
+
created_at TIMESTAMPNOT NULL DEFAULT NOW()
45
67
);
68
+
INSERT INTO event_buffer_lock VALUES (DEFAULT);
46
69
```
47
70
48
71
To consume messages, we just need to periodically (every one to a few seconds) do:
@@ -56,9 +79,6 @@ FOR UPDATE SKIP LOCKED;
56
79
57
80
SELECT*FROM event
58
81
WHERE topic = :topic AND (:last_event_id IS NULLOR id > :last_event_id)
59
-
-- eventual consistency of auto increment; there is no guarantee that record of id 2 is visible after id 1 record --
60
-
-- in the implementation, we set insert statements timeout to '250 ms' so it is safe --
61
-
AND created_at < (NOW() - interval '333 ms')
62
82
ORDER BY id LIMIT :limit;
63
83
64
84
(process events)
@@ -74,7 +94,7 @@ COMMIT;
74
94
Optionally, to increase throughput & concurrency, we might have a partitioned topic and consumers (-1 partition standing
75
95
for not partitioned topic/consumer/event).
76
96
77
-
Distribution of partitioned events is the sole responsibility of the publisher.
97
+
Distribution of partitioned events is the sole responsibility of a publisher.
78
98
79
99
Consumption of such events per partition (0 in an example) might look like this:
80
100
@@ -87,9 +107,6 @@ FOR UPDATE SKIP LOCKED;
87
107
88
108
SELECT*FROM event
89
109
WHERE topic = :topic AND partition =0AND (:last_event_id IS NULLOR id > :last_event_id)
90
-
-- eventual consistency of auto increment; there is no guarantee that record of id 2 is visible after id 1 record --
91
-
-- in the implementation, we set insert statements timeout to '250 ms' so it is safe --
92
-
AND created_at < (NOW() - interval '333 ms')
93
110
ORDER BY id LIMIT :limit;
94
111
95
112
(process events)
@@ -107,20 +124,20 @@ definition has. It's a rather acceptable tradeoff and easy to enforce at the lib
107
124
108
125
## How to use it
109
126
110
-
`EventSQL` is an entrypoint to the whole library. It requires single data source properties or a list of
127
+
`EventSQL` is an entrypoint to the whole library. It requires standard Java `javax.sql.DataSource` or a list of
111
128
them:
112
129
113
130
```java
131
+
114
132
importcom.binaryigor.eventsql.EventSQL;
115
-
//EventSQL.Dialect is a dialect of your events backend - POSTGRES, MYSQL, MARIADB and so on;
133
+
// dialect of your events backend - POSTGRES, MYSQL, MARIADB and so on;
116
134
// as of now, only POSTGRES has fully tested support;
117
135
// should also work with others but some things - event table partition management for example - works only with Postgres, for others it must be managed manually
118
-
var eventSQL =EventSQL.of(newEventSQL.DataSourceProperties(EventSQL.Dialect.POSTGRES, "dbUrl", "dbUsername", "dbPassword"));
0 commit comments