tkati-node-dedup 0.4.3__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. {tkati_node_dedup-0.4.3/src/tkati_node_dedup.egg-info → tkati_node_dedup-0.4.4}/PKG-INFO +16 -6
  2. tkati_node_dedup-0.4.3/PKG-INFO → tkati_node_dedup-0.4.4/README.md +14 -16
  3. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/pyproject.toml +2 -2
  4. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup/main.py +22 -10
  5. tkati_node_dedup-0.4.3/README.md → tkati_node_dedup-0.4.4/src/tkati_node_dedup.egg-info/PKG-INFO +26 -4
  6. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup.egg-info/requires.txt +1 -1
  7. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/setup.cfg +0 -0
  8. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup/__init__.py +0 -0
  9. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup/__main__.py +0 -0
  10. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup/py.typed +0 -0
  11. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup/settings.py +0 -0
  12. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup/store.py +0 -0
  13. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup.egg-info/SOURCES.txt +0 -0
  14. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup.egg-info/dependency_links.txt +0 -0
  15. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup.egg-info/entry_points.txt +0 -0
  16. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/src/tkati_node_dedup.egg-info/top_level.txt +0 -0
  17. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/tests/test_node_dedup.py +0 -0
  18. {tkati_node_dedup-0.4.3 → tkati_node_dedup-0.4.4}/tests/test_store.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tkati-node-dedup
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: Kafka-to-Kafka streaming node that deduplicates events by a configurable field within a rolling processing-time window
5
5
  Requires-Python: >=3.13
6
6
  Description-Content-Type: text/markdown
7
- Requires-Dist: tkati-core==0.4.3
7
+ Requires-Dist: tkati-core==0.4.4
8
8
  Requires-Dist: loguru>=0.7.0
9
9
  Requires-Dist: pydantic-settings>=2.11.0
10
10
  Requires-Dist: pyarrow>=21.0.0
@@ -85,24 +85,34 @@ Every 10 seconds the node logs where its wall clock went, using
85
85
 
86
86
  ```
87
87
  dedup perf over 10s: 157000 rows in, 153880 out (3120 dropped), 157 iterations (0 input-starved)
88
- dedup perf: read=4.91s (49%) lookup=0.52s (5%) produce=3.96s (39%) write=0.21s (2%) commit=0.38s (4%)
88
+ dedup perf: poll=4.43s (44%) parse=0.48s (5%) lookup=0.52s (5%) produce=3.96s (39%) write=0.21s (2%) commit=0.38s (4%)
89
89
  ```
90
90
 
91
91
  `dropped` is the rows this node deduplicated away.
92
92
 
93
- * `read` — fetching from Kafka *and* JSON-parsing into Arrow
93
+ * `poll` — fetching message batches from the broker. Mostly broker round trips,
94
+ but it also includes librdkafka handing each message to Python, which has a
95
+ floor of roughly 0.8 us/message no matter how fast the broker is
96
+ * `parse` — JSON-decoding those payloads into an Arrow table, and casting to
97
+ the internal schema
94
98
  * `lookup` — encoding keys, resolving in-batch duplicates, querying the store
95
99
  * `produce` — serializing and producing, including the blocking `flush`
96
100
  * `write` — marking the surviving keys seen
97
101
  * `commit` — the synchronous offset commit (and bucket cleanup, which is ~0
98
102
  except once an hour when a bucket is destroyed)
99
103
 
104
+ `poll` and `parse` come from `tkati-core`'s consumer rather than from this
105
+ node, which splices them in from `CONSUMER_PHASES`. They are split apart
106
+ because their fixes are unrelated: a large `poll` points at batch sizing,
107
+ broker latency or an under-fed topic, while a large `parse` points at the JSON
108
+ decode and is what a faster wire format would address.
109
+
100
110
  Percentages are of the interval, not of each other, so they **do not sum to
101
111
  100** — the remainder is time in none of the named phases.
102
112
 
103
113
  `input-starved` counts iterations where the node drained the topic and waited
104
- out the batch timeout. Those iterations were not CPU-bound, and because `read`
105
- blocks for the whole wait, a mostly-starved interval will show `read` at close
114
+ out the batch timeout. Those iterations were not CPU-bound, and because `poll`
115
+ blocks for the whole wait, a mostly-starved interval will show `poll` at close
106
116
  to 100% and tells you nothing about whether the node can keep up.
107
117
 
108
118
  `benchmarks/bench_store.py` A/B tests the store in isolation. It populates in
@@ -1,15 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: tkati-node-dedup
3
- Version: 0.4.3
4
- Summary: Kafka-to-Kafka streaming node that deduplicates events by a configurable field within a rolling processing-time window
5
- Requires-Python: >=3.13
6
- Description-Content-Type: text/markdown
7
- Requires-Dist: tkati-core==0.4.3
8
- Requires-Dist: loguru>=0.7.0
9
- Requires-Dist: pydantic-settings>=2.11.0
10
- Requires-Dist: pyarrow>=21.0.0
11
- Requires-Dist: rocksdict>=0.3.0
12
-
13
1
  # tkati-node-dedup — streaming deduplication node
14
2
 
15
3
  Reads batches from a Kafka input topic, drops events that are duplicates of an
@@ -85,24 +73,34 @@ Every 10 seconds the node logs where its wall clock went, using
85
73
 
86
74
  ```
87
75
  dedup perf over 10s: 157000 rows in, 153880 out (3120 dropped), 157 iterations (0 input-starved)
88
- dedup perf: read=4.91s (49%) lookup=0.52s (5%) produce=3.96s (39%) write=0.21s (2%) commit=0.38s (4%)
76
+ dedup perf: poll=4.43s (44%) parse=0.48s (5%) lookup=0.52s (5%) produce=3.96s (39%) write=0.21s (2%) commit=0.38s (4%)
89
77
  ```
90
78
 
91
79
  `dropped` is the rows this node deduplicated away.
92
80
 
93
- * `read` — fetching from Kafka *and* JSON-parsing into Arrow
81
+ * `poll` — fetching message batches from the broker. Mostly broker round trips,
82
+ but it also includes librdkafka handing each message to Python, which has a
83
+ floor of roughly 0.8 us/message no matter how fast the broker is
84
+ * `parse` — JSON-decoding those payloads into an Arrow table, and casting to
85
+ the internal schema
94
86
  * `lookup` — encoding keys, resolving in-batch duplicates, querying the store
95
87
  * `produce` — serializing and producing, including the blocking `flush`
96
88
  * `write` — marking the surviving keys seen
97
89
  * `commit` — the synchronous offset commit (and bucket cleanup, which is ~0
98
90
  except once an hour when a bucket is destroyed)
99
91
 
92
+ `poll` and `parse` come from `tkati-core`'s consumer rather than from this
93
+ node, which splices them in from `CONSUMER_PHASES`. They are split apart
94
+ because their fixes are unrelated: a large `poll` points at batch sizing,
95
+ broker latency or an under-fed topic, while a large `parse` points at the JSON
96
+ decode and is what a faster wire format would address.
97
+
100
98
  Percentages are of the interval, not of each other, so they **do not sum to
101
99
  100** — the remainder is time in none of the named phases.
102
100
 
103
101
  `input-starved` counts iterations where the node drained the topic and waited
104
- out the batch timeout. Those iterations were not CPU-bound, and because `read`
105
- blocks for the whole wait, a mostly-starved interval will show `read` at close
102
+ out the batch timeout. Those iterations were not CPU-bound, and because `poll`
103
+ blocks for the whole wait, a mostly-starved interval will show `poll` at close
106
104
  to 100% and tells you nothing about whether the node can keep up.
107
105
 
108
106
  `benchmarks/bench_store.py` A/B tests the store in isolation. It populates in
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "tkati-node-dedup"
3
- version = "0.4.3"
3
+ version = "0.4.4"
4
4
  description = "Kafka-to-Kafka streaming node that deduplicates events by a configurable field within a rolling processing-time window"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  dependencies = [
8
- "tkati-core==0.4.3",
8
+ "tkati-core==0.4.4",
9
9
  "loguru>=0.7.0",
10
10
  "pydantic-settings>=2.11.0",
11
11
  "pyarrow>=21.0.0",
@@ -1,15 +1,23 @@
1
1
  import pyarrow as pa
2
2
  from loguru import logger
3
- from tkati_core import Consumer, LoopStats, Producer, build_consumer, build_producer
3
+ from tkati_core import (
4
+ CONSUMER_PHASES,
5
+ Consumer,
6
+ LoopStats,
7
+ Producer,
8
+ build_consumer,
9
+ build_producer,
10
+ )
4
11
 
5
12
  from tkati_node_dedup.settings import AppSettings
6
13
  from tkati_node_dedup.store import BucketedDedupStore
7
14
 
8
15
  # Reported in this order, not sorted by duration: a stable field order is what
9
- # makes two consecutive log lines comparable at a glance. Lives here rather
10
- # than in tkati-core because these five names are this node's pipeline —
11
- # tkati-node-el, for instance, has no lookup or write phase.
12
- _PHASES = ("read", "lookup", "produce", "write", "commit")
16
+ # makes two consecutive log lines comparable at a glance. The tail lives here
17
+ # rather than in tkati-core because these four names are this node's pipeline —
18
+ # tkati-node-el, for instance, has no lookup or write phase. The head is spliced
19
+ # in from the consumer, which owns the names it times itself against.
20
+ _PHASES = (*CONSUMER_PHASES, "lookup", "produce", "write", "commit")
13
21
 
14
22
 
15
23
  def _new_stats() -> LoopStats:
@@ -57,11 +65,15 @@ def run_one_iteration(
57
65
  except Exception:
58
66
  logger.exception("dedup store cleanup failed; will retry next iteration")
59
67
 
60
- with stats.phase("read"):
61
- batch = consumer.read_arrow(
62
- num_messages=settings.input.consumer.batch_size,
63
- timeout=settings.input.consumer.batch_timeout_sec,
64
- )
68
+ # No phase block here: the consumer splits its own time into `poll` and
69
+ # `parse`. Wrapping it in an umbrella phase as well would double-count that
70
+ # time, and the percentages are of the interval — they are meant to fall
71
+ # short of 100%, with the shortfall being genuinely unaccounted work.
72
+ batch = consumer.read_arrow(
73
+ num_messages=settings.input.consumer.batch_size,
74
+ timeout=settings.input.consumer.batch_timeout_sec,
75
+ stats=stats,
76
+ )
65
77
  stats.iterations += 1
66
78
  if batch is None:
67
79
  stats.starved_iterations += 1
@@ -1,3 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: tkati-node-dedup
3
+ Version: 0.4.4
4
+ Summary: Kafka-to-Kafka streaming node that deduplicates events by a configurable field within a rolling processing-time window
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: tkati-core==0.4.4
8
+ Requires-Dist: loguru>=0.7.0
9
+ Requires-Dist: pydantic-settings>=2.11.0
10
+ Requires-Dist: pyarrow>=21.0.0
11
+ Requires-Dist: rocksdict>=0.3.0
12
+
1
13
  # tkati-node-dedup — streaming deduplication node
2
14
 
3
15
  Reads batches from a Kafka input topic, drops events that are duplicates of an
@@ -73,24 +85,34 @@ Every 10 seconds the node logs where its wall clock went, using
73
85
 
74
86
  ```
75
87
  dedup perf over 10s: 157000 rows in, 153880 out (3120 dropped), 157 iterations (0 input-starved)
76
- dedup perf: read=4.91s (49%) lookup=0.52s (5%) produce=3.96s (39%) write=0.21s (2%) commit=0.38s (4%)
88
+ dedup perf: poll=4.43s (44%) parse=0.48s (5%) lookup=0.52s (5%) produce=3.96s (39%) write=0.21s (2%) commit=0.38s (4%)
77
89
  ```
78
90
 
79
91
  `dropped` is the rows this node deduplicated away.
80
92
 
81
- * `read` — fetching from Kafka *and* JSON-parsing into Arrow
93
+ * `poll` — fetching message batches from the broker. Mostly broker round trips,
94
+ but it also includes librdkafka handing each message to Python, which has a
95
+ floor of roughly 0.8 us/message no matter how fast the broker is
96
+ * `parse` — JSON-decoding those payloads into an Arrow table, and casting to
97
+ the internal schema
82
98
  * `lookup` — encoding keys, resolving in-batch duplicates, querying the store
83
99
  * `produce` — serializing and producing, including the blocking `flush`
84
100
  * `write` — marking the surviving keys seen
85
101
  * `commit` — the synchronous offset commit (and bucket cleanup, which is ~0
86
102
  except once an hour when a bucket is destroyed)
87
103
 
104
+ `poll` and `parse` come from `tkati-core`'s consumer rather than from this
105
+ node, which splices them in from `CONSUMER_PHASES`. They are split apart
106
+ because their fixes are unrelated: a large `poll` points at batch sizing,
107
+ broker latency or an under-fed topic, while a large `parse` points at the JSON
108
+ decode and is what a faster wire format would address.
109
+
88
110
  Percentages are of the interval, not of each other, so they **do not sum to
89
111
  100** — the remainder is time in none of the named phases.
90
112
 
91
113
  `input-starved` counts iterations where the node drained the topic and waited
92
- out the batch timeout. Those iterations were not CPU-bound, and because `read`
93
- blocks for the whole wait, a mostly-starved interval will show `read` at close
114
+ out the batch timeout. Those iterations were not CPU-bound, and because `poll`
115
+ blocks for the whole wait, a mostly-starved interval will show `poll` at close
94
116
  to 100% and tells you nothing about whether the node can keep up.
95
117
 
96
118
  `benchmarks/bench_store.py` A/B tests the store in isolation. It populates in
@@ -1,4 +1,4 @@
1
- tkati-core==0.4.3
1
+ tkati-core==0.4.4
2
2
  loguru>=0.7.0
3
3
  pydantic-settings>=2.11.0
4
4
  pyarrow>=21.0.0