@@ -24,6 +24,9 @@ use tokio::{
24
24
time:: interval,
25
25
} ;
26
26
use tracing:: error;
27
+ use ttl_cache:: TtlCache ;
28
+
29
+ const DEDUP_CACHE_SIZE : usize = 100_000 ;
27
30
28
31
#[ derive( Clone , Debug ) ]
29
32
pub struct LazerPublisher {
@@ -88,6 +91,7 @@ impl LazerPublisher {
88
91
pending_updates : Vec :: new ( ) ,
89
92
relayer_sender,
90
93
signing_key,
94
+ ttl_cache : TtlCache :: new ( DEDUP_CACHE_SIZE ) ,
91
95
} ;
92
96
tokio:: spawn ( async move { task. run ( ) . await } ) ;
93
97
Self {
@@ -109,6 +113,7 @@ struct LazerPublisherTask {
109
113
pending_updates : Vec < FeedUpdate > ,
110
114
relayer_sender : broadcast:: Sender < SignedLazerTransaction > ,
111
115
signing_key : SigningKey ,
116
+ ttl_cache : TtlCache < u32 , FeedUpdate > ,
112
117
}
113
118
114
119
impl LazerPublisherTask {
@@ -136,7 +141,16 @@ impl LazerPublisherTask {
136
141
let mut updates: Vec < FeedUpdate > = self . pending_updates . drain ( ..) . collect ( ) ;
137
142
updates. sort_by_key ( |u| u. source_timestamp . as_ref ( ) . map ( |t| ( t. seconds , t. nanos ) ) ) ;
138
143
if self . config . enable_update_deduplication {
139
- updates = deduplicate_feed_updates ( & updates) ?;
144
+ updates = deduplicate_feed_updates_in_tx ( & updates) ?;
145
+ deduplicate_feed_updates (
146
+ & mut updates,
147
+ & mut self . ttl_cache ,
148
+ self . config . update_deduplication_ttl ,
149
+ ) ;
150
+ }
151
+
152
+ if updates. is_empty ( ) {
153
+ return Ok ( ( ) ) ;
140
154
}
141
155
142
156
let publisher_update = PublisherUpdate {
@@ -182,7 +196,9 @@ impl LazerPublisherTask {
182
196
183
197
/// For each feed, keep the latest data. Among updates with the same data, keep the one with the earliest timestamp.
184
198
/// Assumes the input is sorted by timestamp ascending.
185
- fn deduplicate_feed_updates ( sorted_feed_updates : & Vec < FeedUpdate > ) -> Result < Vec < FeedUpdate > > {
199
+ fn deduplicate_feed_updates_in_tx (
200
+ sorted_feed_updates : & Vec < FeedUpdate > ,
201
+ ) -> Result < Vec < FeedUpdate > > {
186
202
let mut deduped_feed_updates = HashMap :: new ( ) ;
187
203
for update in sorted_feed_updates {
188
204
let entry = deduped_feed_updates. entry ( update. feed_id ) . or_insert ( update) ;
@@ -193,10 +209,35 @@ fn deduplicate_feed_updates(sorted_feed_updates: &Vec<FeedUpdate>) -> Result<Vec
193
209
Ok ( deduped_feed_updates. into_values ( ) . cloned ( ) . collect ( ) )
194
210
}
195
211
212
+ fn deduplicate_feed_updates (
213
+ sorted_feed_updates : & mut Vec < FeedUpdate > ,
214
+ ttl_cache : & mut TtlCache < u32 , FeedUpdate > ,
215
+ ttl : std:: time:: Duration ,
216
+ ) {
217
+ sorted_feed_updates. retain ( |update| {
218
+ let feed_id = match update. feed_id {
219
+ Some ( id) => id,
220
+ None => return false , // drop updates without feed_id
221
+ } ;
222
+
223
+ if let Some ( cached_feed) = ttl_cache. get ( & feed_id) {
224
+ if cached_feed. update == update. update {
225
+ // drop if the same update is already in the cache
226
+ return false ;
227
+ }
228
+ }
229
+
230
+ ttl_cache. insert ( feed_id, update. clone ( ) , ttl) ;
231
+ true
232
+ } ) ;
233
+ }
234
+
196
235
#[ cfg( test) ]
197
236
mod tests {
198
237
use crate :: config:: { CHANNEL_CAPACITY , Config } ;
199
- use crate :: lazer_publisher:: { LazerPublisherTask , deduplicate_feed_updates} ;
238
+ use crate :: lazer_publisher:: {
239
+ DEDUP_CACHE_SIZE , LazerPublisherTask , deduplicate_feed_updates_in_tx,
240
+ } ;
200
241
use ed25519_dalek:: SigningKey ;
201
242
use protobuf:: well_known_types:: timestamp:: Timestamp ;
202
243
use protobuf:: { Message , MessageField } ;
@@ -210,6 +251,7 @@ mod tests {
210
251
use tempfile:: NamedTempFile ;
211
252
use tokio:: sync:: broadcast:: error:: TryRecvError ;
212
253
use tokio:: sync:: { broadcast, mpsc} ;
254
+ use ttl_cache:: TtlCache ;
213
255
use url:: Url ;
214
256
215
257
fn get_private_key ( ) -> SigningKey {
@@ -258,6 +300,7 @@ mod tests {
258
300
publish_interval_duration : Duration :: from_millis ( 25 ) ,
259
301
history_service_url : None ,
260
302
enable_update_deduplication : false ,
303
+ update_deduplication_ttl : Default :: default ( ) ,
261
304
} ;
262
305
263
306
let ( relayer_sender, mut relayer_receiver) = broadcast:: channel ( CHANNEL_CAPACITY ) ;
@@ -268,6 +311,7 @@ mod tests {
268
311
pending_updates : Vec :: new ( ) ,
269
312
relayer_sender,
270
313
signing_key,
314
+ ttl_cache : TtlCache :: new ( DEDUP_CACHE_SIZE ) ,
271
315
} ;
272
316
tokio:: spawn ( async move { task. run ( ) . await } ) ;
273
317
@@ -337,7 +381,7 @@ mod tests {
337
381
10 ,
338
382
) ] ;
339
383
340
- let deduped_updates = deduplicate_feed_updates ( updates) . unwrap ( ) ;
384
+ let deduped_updates = deduplicate_feed_updates_in_tx ( updates) . unwrap ( ) ;
341
385
assert_eq ! ( deduped_updates, expected_updates) ;
342
386
}
343
387
@@ -357,7 +401,7 @@ mod tests {
357
401
test_feed_update( 2 , TimestampUs :: from_millis( 6 ) . unwrap( ) , 10 ) ,
358
402
] ;
359
403
360
- let mut deduped_updates = deduplicate_feed_updates ( updates) . unwrap ( ) ;
404
+ let mut deduped_updates = deduplicate_feed_updates_in_tx ( updates) . unwrap ( ) ;
361
405
deduped_updates. sort_by_key ( |u| u. feed_id ) ;
362
406
assert_eq ! ( deduped_updates, expected_updates) ;
363
407
}
@@ -384,7 +428,7 @@ mod tests {
384
428
test_feed_update( 2 , TimestampUs :: from_millis( 12 ) . unwrap( ) , 10 ) ,
385
429
] ;
386
430
387
- let mut deduped_updates = deduplicate_feed_updates ( updates) . unwrap ( ) ;
431
+ let mut deduped_updates = deduplicate_feed_updates_in_tx ( updates) . unwrap ( ) ;
388
432
deduped_updates. sort_by_key ( |u| u. feed_id ) ;
389
433
assert_eq ! ( deduped_updates, expected_updates) ;
390
434
}
0 commit comments