Parquet History, Kafka Live
One run writes ten minutes of backdated history to Parquet. It then publishes to Kafka in real time, and this second part is the live tail. Look for the history's Parquet files in data/backfill/, then for the Logical clock reached go-live line in the log, after which records reach Kafka at the pace of the wall clock. Each tab shows the example written one way. The sections after the tabs show the records, and explain why one run is better than two, how the settings fit together and what to get right.
Before You Start
The example needs Docker and the odctl profile kafka-lite, which each Quick Start below starts and stops. Services for the Examples shows how to install odctl.
Steps
Run It
This example builds the simulation of the Builder tab from a YAML blueprint, with no Python. Until go_live_at the run is unpaced and writes backdated history to Parquet. From go_live_at it is paced in real time and publishes to Kafka.
Quick Start
desflow examples get downloads the example at the release you install, into a folder of its own. Pick uv or pip:
What It Does
Ten minutes of history are written to data/backfill/ within the first second, then the run publishes to sim-events and sim-telemetry in real time for 60 seconds and ends. The history folder is created on the first write, and KafkaEgress creates the two topics when it starts.
logical_start_time: -10m and go_live_at: now are both read against one moment, the time the file is loaded, so they are exactly ten minutes apart. until: 11m then adds one minute of live tail. when: history sends the records stamped before go_live_at to Parquet, and when: live sends the rest to Kafka.
examples/builder/backfill_live_example.py backdates the clock by ten minutes, writes those ten minutes to Parquet in well under a second, then publishes to Kafka in real time for sixty seconds. HISTORY_MINUTES and LIVE_SECONDS set the length of the two halves. A day of history (HISTORY_MINUTES=1440) is about 128,000 events.
Quick Start
desflow examples get downloads the example at the release you install, into a folder of its own. Pick uv or pip:
examples/low-level/backfill_live_example.py wires the same run by hand on DynamicRealtimeEnvironment: it backdates the clock by ten minutes, writes those ten minutes to Parquet in well under a second, then publishes to Kafka in real time for sixty seconds. HISTORY_MINUTES and LIVE_SECONDS set the two halves.
Quick Start
desflow examples get downloads the example at the release you install, into a folder of its own. Pick uv or pip:
Get Two Details Right
Predicates compare times read from the timestamp. A record carries its logical time as an ISO string with its zone offset, such as +00:00. GO_LIVE_AT is a datetime, and Python cannot compare a string with a datetime, so record["timestamp"] >= GO_LIVE_AT raises a TypeError. Read the string with datetime.fromisoformat and compare the two instants. The comparison is correct whatever zone each side is written in. Cut the go-live instant to milliseconds, as timestamps are, so a record stamped in the go-live millisecond counts as live. Take the instant with datetime.now(timezone.utc): a datetime without an offset is read in the run's timezone, else in the zone of logical_start_time, else in UTC, not in the machine's local time.
| examples/yaml/backfill_live.yaml:clock | |
|---|---|
A blueprint needs no predicate. when: history and when: live compare each record's timestamp with go_live_at.
| examples/low-level/backfill_live_example.py:clock | |
|---|---|
Before go_live_at, only batch_size decides when the history is flushed. flush_interval has no effect yet. The interval flush is a simulation process. It starts only if factor is non-zero when the egress is set up. This run starts at factor=0.0, so before go-live, records leave the buffer only when it fills to batch_size. batch_size therefore sets the number of Parquet files. The interval flush starts at go-live, so the live tail also flushes every flush_interval. flush_interval therefore decides how often Kafka receives records. Give each sink its own batch_size and flush_interval to separate those two jobs:
Check the Result
The three tabs publish the same records, which a test checks by running each version unpaced. A real run writes the records stamped before go_live_at to Parquet files under data/backfill/, and the rest to sim-events and sim-telemetry. desflow run backfill_live.yaml --sample 4 --stdout prints the first four events as JSON lines instead, unpaced and with no broker:
Each timestamp is shown as ..., because it changes with every run. It is the start of the history plus sim_ts, so these records are stamped before go_live_at and belong to the history.
During the live tail the run can look as if it has stopped responding. History finishes within the first second of the run. After the Logical clock reached go-live line, the run logs at most one more line, the Kafka producer's connection line, until it shuts down. From go_live_at the run waits on the wall clock, as any live run does. The Logical clock reached go-live line confirms the switch.
Go Live explains why one run replaces two and how logical_start_time, go_live_at and when work together.
Full Files
Full files
Files live in the examples/yaml/ folder of the repository, and the label on each block below is its path there.
Scripts live in the examples/ folder of the repository, and the label on the block below is this one's path there.
| examples/builder/backfill_live_example.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
Scripts live in the examples/ folder of the repository, and the label on the block below is this one's path there.
| examples/low-level/backfill_live_example.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |