Skip to content

Commit 36530cf

Browse files
committed
Support nanosecond timestamp in Redis
1 parent b913a51 commit 36530cf

File tree

17 files changed

+131
-76
lines changed

17 files changed

+131
-76
lines changed

sea-streamer-file/src/file.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ impl AsyncFile {
224224
.seek(match to {
225225
SeqPos::Beginning => SeekFrom::Start(0),
226226
SeqPos::End => SeekFrom::End(0),
227-
SeqPos::At(to) => SeekFrom::Start(to),
227+
SeqPos::At(to) => SeekFrom::Start(to.try_into().expect("SeqNo out of range")),
228228
})
229229
.await
230230
.map_err(FileErr::IoError)?;

sea-streamer-file/src/format.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ use crate::{
6363
ByteSink, ByteSource, Bytes, FileErr,
6464
};
6565
use sea_streamer_types::{
66-
Buffer, Message as MessageTrait, OwnedMessage, ShardId, StreamKey, StreamKeyErr, Timestamp,
66+
Buffer, Message as MessageTrait, OwnedMessage, SeqNo, ShardId, StreamKey, StreamKeyErr,
67+
Timestamp,
6768
};
6869
#[cfg(feature = "serde")]
6970
use serde::Serialize;
@@ -353,7 +354,7 @@ impl MessageHeader {
353354
use sea_streamer_types::MessageHeader as Header;
354355
let stream_key = StreamKey::new(ShortString::read_from(file).await?.string())?;
355356
let shard_id = ShardId::new(U64::read_from(file).await?.0);
356-
let sequence = U64::read_from(file).await?.0;
357+
let sequence = U64::read_from(file).await?.0 as SeqNo;
357358
let timestamp = UnixTimestamp::read_from(file).await?.0;
358359
Ok(Self(Header::new(stream_key, shard_id, sequence, timestamp)))
359360
}
@@ -363,7 +364,8 @@ impl MessageHeader {
363364
let h = self.0;
364365
sum += ShortString::new(h.stream_key().name().to_owned())?.write_to(sink)?;
365366
sum += U64(h.shard_id().id()).write_to(sink)?;
366-
sum += U64(*h.sequence()).write_to(sink)?;
367+
sum += U64(TryInto::<u64>::try_into(*h.sequence()).expect("SeqNo out of range"))
368+
.write_to(sink)?;
367369
sum += UnixTimestamp(*h.timestamp()).write_to(sink)?;
368370
Ok(sum)
369371
}

sea-streamer-file/src/messages.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ impl MessageSource {
109109
/// Warning: This future must not be canceled.
110110
pub async fn rewind(&mut self, target: SeqPos) -> Result<u32, FileErr> {
111111
let pos = match target {
112-
SeqPos::Beginning | SeqPos::At(0) => SeqPos::At(Header::size() as u64),
112+
SeqPos::Beginning | SeqPos::At(0) => SeqPos::At(Header::size() as SeqNo),
113113
SeqPos::End => SeqPos::End,
114114
SeqPos::At(nth) => {
115-
let at = nth * self.beacon_interval();
115+
let at = nth as u64 * self.beacon_interval();
116116
if at < self.known_size() {
117-
SeqPos::At(at)
117+
SeqPos::At(at as SeqNo)
118118
} else {
119119
SeqPos::End
120120
}
@@ -130,7 +130,7 @@ impl MessageSource {
130130
SeqPos::Beginning | SeqPos::At(0) => unreachable!(),
131131
SeqPos::End => max,
132132
SeqPos::At(nth) => {
133-
let at = nth * self.beacon_interval();
133+
let at = nth as u64 * self.beacon_interval();
134134
if at < self.known_size() {
135135
at
136136
} else {
@@ -139,7 +139,7 @@ impl MessageSource {
139139
}
140140
};
141141
if self.offset != pos {
142-
self.offset = self.source.seek(SeqPos::At(pos)).await?;
142+
self.offset = self.source.seek(SeqPos::At(pos as SeqNo)).await?;
143143
}
144144
}
145145

@@ -175,7 +175,7 @@ impl MessageSource {
175175
while let Ok(message) = Message::read_from(&mut buffer).await {
176176
next += message.size() as u64;
177177
}
178-
self.offset = self.source.seek(SeqPos::At(next)).await?;
178+
self.offset = self.source.seek(SeqPos::At(next as SeqNo)).await?;
179179
}
180180

181181
Ok((self.offset / self.beacon_interval()) as u32)
@@ -226,7 +226,7 @@ impl MessageSource {
226226
}
227227
};
228228
// now we know roughly where's the message
229-
match self.rewind(SeqPos::At(pos as u64)).await {
229+
match self.rewind(SeqPos::At(pos as SeqNo)).await {
230230
Ok(_) => (),
231231
Err(e) => {
232232
break 'outer match e {
@@ -260,7 +260,7 @@ impl MessageSource {
260260
self.source = source.switch_to(source_type).await?;
261261

262262
if res.is_err() {
263-
self.source.seek(SeqPos::At(savepoint)).await?;
263+
self.source.seek(SeqPos::At(savepoint as SeqNo)).await?;
264264
self.buffer.clear();
265265
self.pending.take();
266266
}
@@ -399,7 +399,7 @@ impl BeaconReader for MessageSource {
399399
fn survey(&mut self, at: NonZeroU32) -> Self::Future<'_> {
400400
async move {
401401
let at = at.get() as u64 * self.beacon_interval();
402-
let offset = self.source.seek(SeqPos::At(at)).await?;
402+
let offset = self.source.seek(SeqPos::At(at as SeqNo)).await?;
403403
if at == offset {
404404
let beacon = Beacon::read_from(&mut self.source).await?;
405405
Ok(beacon)
@@ -462,7 +462,7 @@ impl MessageSink {
462462
if nth > 0 {
463463
// we need to rewind further backwards
464464
nth -= 1;
465-
source.rewind(SeqPos::At(nth as u64)).await?;
465+
source.rewind(SeqPos::At(nth as SeqNo)).await?;
466466
} else {
467467
// we reached the start now
468468
break;
@@ -492,7 +492,7 @@ impl MessageSink {
492492
let has_beacon = source.has_beacon(offset).is_some();
493493
if let DynFileSource::FileReader(reader) = source.source {
494494
let (mut file, _, _) = reader.end();
495-
assert_eq!(offset, file.seek(SeqPos::At(offset)).await?);
495+
assert_eq!(offset, file.seek(SeqPos::At(offset as SeqNo)).await?);
496496
let mut sink = FileSink::new(file, limit)?;
497497

498498
if has_beacon {

sea-streamer-file/src/producer/backend.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ impl Writer {
204204
n -= 1;
205205
}
206206
// 2. go forward from there and read all messages up to started_from, recording the latest messages
207-
source.rewind(SeqPos::At(n as u64)).await?;
207+
source.rewind(SeqPos::At(n as SeqNo)).await?;
208208
while source.offset() < sink.started_from() {
209209
match source.next().await {
210210
Ok(msg) => {
@@ -234,7 +234,7 @@ impl Writer {
234234
_ => panic!("Must be FileReader"),
235235
};
236236
let (mut file, _, _) = reader.end();
237-
file.seek(SeqPos::At(sink.offset())).await?; // restore offset
237+
file.seek(SeqPos::At(sink.offset() as SeqNo)).await?; // restore offset
238238
sink.use_file(FileSink::new(file, file_size_limit)?);
239239
// now we've gone through the stream, we can safely assume the stream state
240240
let entry = streams.entry(key.clone()).or_default();

sea-streamer-file/tests/consumer.rs

+19-14
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ async fn consumer() -> anyhow::Result<()> {
1515
};
1616
use sea_streamer_types::{
1717
export::futures::TryStreamExt, Buffer, Consumer, Message, MessageHeader, OwnedMessage,
18-
ShardId, SharedMessage, StreamErr, StreamKey, Streamer, Timestamp,
18+
SeqNo, ShardId, SharedMessage, StreamErr, StreamKey, Streamer, Timestamp,
1919
};
2020

2121
const TEST: &str = "consumer";
@@ -33,13 +33,14 @@ async fn consumer() -> anyhow::Result<()> {
3333
let shard = ShardId::new(1);
3434

3535
let message = |i: u64| -> OwnedMessage {
36-
let header = MessageHeader::new(stream_key.clone(), shard, i, Timestamp::now_utc());
36+
let header =
37+
MessageHeader::new(stream_key.clone(), shard, i as SeqNo, Timestamp::now_utc());
3738
OwnedMessage::new(header, format!("{}-{}", stream_key.name(), i).into_bytes())
3839
};
3940
let check = |i: u64, mess: SharedMessage| {
4041
assert_eq!(mess.header().stream_key(), &stream_key);
4142
assert_eq!(mess.header().shard_id(), &shard);
42-
assert_eq!(mess.header().sequence(), &i);
43+
assert_eq!(*mess.header().sequence() as u64, i);
4344
assert_eq!(
4445
mess.message().as_str().unwrap(),
4546
format!("{}-{}", stream_key.name(), i)
@@ -119,8 +120,8 @@ async fn demux() -> anyhow::Result<()> {
119120
DEFAULT_FILE_SIZE_LIMIT,
120121
};
121122
use sea_streamer_types::{
122-
Buffer, Consumer, Message, MessageHeader, OwnedMessage, ShardId, SharedMessage, StreamKey,
123-
Streamer, Timestamp,
123+
Buffer, Consumer, Message, MessageHeader, OwnedMessage, SeqNo, ShardId, SharedMessage,
124+
StreamKey, Streamer, Timestamp,
124125
};
125126

126127
const TEST: &str = "demux";
@@ -139,16 +140,18 @@ async fn demux() -> anyhow::Result<()> {
139140
let shard = ShardId::new(1);
140141

141142
let cat = |i: u64| -> OwnedMessage {
142-
let header = MessageHeader::new(cat_key.clone(), shard, i, Timestamp::now_utc());
143+
let header =
144+
MessageHeader::new(cat_key.clone(), shard, i as SeqNo, Timestamp::now_utc());
143145
OwnedMessage::new(header, format!("{}", i).into_bytes())
144146
};
145147
let dog = |i: u64| -> OwnedMessage {
146-
let header = MessageHeader::new(dog_key.clone(), shard, i, Timestamp::now_utc());
148+
let header =
149+
MessageHeader::new(dog_key.clone(), shard, i as SeqNo, Timestamp::now_utc());
147150
OwnedMessage::new(header, format!("{}", i).into_bytes())
148151
};
149152
let check = |i: u64, mess: &SharedMessage| {
150153
assert_eq!(mess.header().shard_id(), &shard);
151-
assert_eq!(mess.header().sequence(), &i);
154+
assert_eq!(*mess.header().sequence() as u64, i);
152155
assert_eq!(mess.message().as_str().unwrap(), format!("{}", i));
153156
};
154157
let is_cat = |i: u64, m: SharedMessage| {
@@ -217,7 +220,7 @@ async fn group() -> anyhow::Result<()> {
217220
};
218221
use sea_streamer_types::{
219222
Buffer, Consumer, ConsumerGroup, ConsumerMode, ConsumerOptions, Message, MessageHeader,
220-
OwnedMessage, ShardId, SharedMessage, StreamKey, Streamer, Timestamp,
223+
OwnedMessage, SeqNo, ShardId, SharedMessage, StreamKey, Streamer, Timestamp,
221224
};
222225

223226
const TEST: &str = "group";
@@ -236,13 +239,14 @@ async fn group() -> anyhow::Result<()> {
236239
let group = ConsumerGroup::new("friends");
237240

238241
let message = |i: u64| -> OwnedMessage {
239-
let header = MessageHeader::new(stream_key.clone(), shard, i, Timestamp::now_utc());
242+
let header =
243+
MessageHeader::new(stream_key.clone(), shard, i as SeqNo, Timestamp::now_utc());
240244
OwnedMessage::new(header, format!("{}-{}", stream_key.name(), i).into_bytes())
241245
};
242246
let check = |i: u64, mess: SharedMessage| {
243247
assert_eq!(mess.header().stream_key(), &stream_key);
244248
assert_eq!(mess.header().shard_id(), &shard);
245-
assert_eq!(mess.header().sequence(), &i);
249+
assert_eq!(*mess.header().sequence() as u64, i);
246250
assert_eq!(
247251
mess.message().as_str().unwrap(),
248252
format!("{}-{}", stream_key.name(), i)
@@ -325,7 +329,7 @@ async fn seek() -> anyhow::Result<()> {
325329
};
326330
use sea_streamer_types::{
327331
Buffer, Consumer, ConsumerGroup, ConsumerMode, ConsumerOptions, Message, MessageHeader,
328-
OwnedMessage, SeqPos, ShardId, SharedMessage, StreamKey, Streamer, Timestamp,
332+
OwnedMessage, SeqNo, SeqPos, ShardId, SharedMessage, StreamKey, Streamer, Timestamp,
329333
};
330334

331335
const TEST: &str = "seek";
@@ -344,13 +348,14 @@ async fn seek() -> anyhow::Result<()> {
344348
let group = ConsumerGroup::new("group");
345349

346350
let message = |i: u64| -> OwnedMessage {
347-
let header = MessageHeader::new(stream_key.clone(), shard, i, Timestamp::now_utc());
351+
let header =
352+
MessageHeader::new(stream_key.clone(), shard, i as SeqNo, Timestamp::now_utc());
348353
OwnedMessage::new(header, format!("{}-{}", stream_key.name(), i).into_bytes())
349354
};
350355
let check = |i: u64, mess: SharedMessage| {
351356
assert_eq!(mess.header().stream_key(), &stream_key);
352357
assert_eq!(mess.header().shard_id(), &shard);
353-
assert_eq!(mess.header().sequence(), &i);
358+
assert_eq!(*mess.header().sequence() as u64, i);
354359
assert_eq!(
355360
mess.message().as_str().unwrap(),
356361
format!("{}-{}", stream_key.name(), i)

sea-streamer-kafka/src/producer.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub use rdkafka::{consumer::ConsumerGroupMetadata, producer::FutureRecord, Topic
1212
use sea_streamer_runtime::spawn_blocking;
1313
use sea_streamer_types::{
1414
export::futures::FutureExt, runtime_error, Buffer, MessageHeader, Producer, ProducerOptions,
15-
ShardId, StreamErr, StreamKey, StreamResult, StreamerUri, Timestamp,
15+
SeqNo, ShardId, StreamErr, StreamKey, StreamResult, StreamerUri, Timestamp,
1616
};
1717

1818
#[derive(Clone)]
@@ -373,7 +373,7 @@ impl Future for SendFuture {
373373
Ok((part, offset)) => Ok(MessageHeader::new(
374374
self.stream_key.take().expect("Must have stream_key"),
375375
ShardId::new(part as u64),
376-
offset as u64,
376+
offset as SeqNo,
377377
Timestamp::now_utc(),
378378
)),
379379
Err((err, _)) => Err(stream_err(err)),

sea-streamer-redis/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ runtime-async-std = ["async-std", "redis/async-std-comp", "sea-streamer-runtime/
4040
runtime-tokio = ["tokio", "redis/tokio-comp", "sea-streamer-runtime/runtime-tokio"]
4141
runtime-async-std-native-tls = ["runtime-async-std", "redis/async-std-native-tls-comp"]
4242
runtime-tokio-native-tls = ["runtime-tokio", "redis/tokio-native-tls-comp"]
43+
nanosecond-timestamp = ["sea-streamer-types/wide-seq-no"]
4344

4445
[[bin]]
4546
name = "consumer"

sea-streamer-redis/src/consumer/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::{fmt::Debug, future::Future, sync::Arc, time::Duration};
1616

1717
use crate::{
1818
from_seq_no, get_message_id, host_id, MessageId, RedisCluster, RedisErr, RedisResult,
19-
DEFAULT_TIMEOUT, MAX_MSG_ID,
19+
TimestampFormat, DEFAULT_TIMEOUT, MAX_MSG_ID,
2020
};
2121
use sea_streamer_runtime::{spawn_task, timeout};
2222
use sea_streamer_types::{
@@ -50,6 +50,7 @@ pub struct RedisConsumerOptions {
5050
batch_size: usize,
5151
shard_ownership: ShardOwnership,
5252
mkstream: bool,
53+
pub(crate) timestamp_format: TimestampFormat,
5354
}
5455

5556
#[derive(Debug)]

sea-streamer-redis/src/consumer/node.rs

+15-12
Original file line numberDiff line numberDiff line change
@@ -544,20 +544,22 @@ impl Node {
544544
log::trace!("XREAD ...");
545545
assert!(self.buffer.is_empty());
546546
match conn.req_packed_command(&cmd).await {
547-
Ok(value) => match StreamReadReply::from_redis_value(value) {
548-
Ok(StreamReadReply(mut mess)) => {
549-
log::trace!("Read {} messages", mess.len());
550-
if mess.is_empty() {
551-
// If we receive an empty reply, it means if we were reading the pending list
552-
// then the list is now empty
553-
self.group.pending_state = false;
547+
Ok(value) => {
548+
match StreamReadReply::from_redis_value(value, self.options.timestamp_format) {
549+
Ok(StreamReadReply(mut mess)) => {
550+
log::trace!("Read {} messages", mess.len());
551+
if mess.is_empty() {
552+
// If we receive an empty reply, it means if we were reading the pending list
553+
// then the list is now empty
554+
self.group.pending_state = false;
555+
}
556+
mess.reverse();
557+
self.buffer = mess;
558+
Ok(ReadResult::Msg(self.buffer.len()))
554559
}
555-
mess.reverse();
556-
self.buffer = mess;
557-
Ok(ReadResult::Msg(self.buffer.len()))
560+
Err(err) => self.send_error(err).await,
558561
}
559-
Err(err) => self.send_error(err).await,
560-
},
562+
}
561563
Err(err) => {
562564
let kind = err.kind();
563565
if kind == ErrorKind::Moved {
@@ -675,6 +677,7 @@ impl Node {
675677
value,
676678
claiming.stream.0.clone(),
677679
claiming.stream.1,
680+
self.options.timestamp_format,
678681
) {
679682
Ok(AutoClaimReply(mut mess)) => {
680683
log::trace!(

sea-streamer-redis/src/consumer/options.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{constants::*, ConsumerConfig, RedisConsumerOptions};
2-
use crate::{RedisErr, RedisResult};
2+
use crate::{RedisErr, RedisResult, TimestampFormat};
33
use sea_streamer_types::{ConsumerGroup, ConsumerId, ConsumerMode, ConsumerOptions, StreamErr};
44
use std::time::Duration;
55

@@ -83,6 +83,7 @@ impl ConsumerOptions for RedisConsumerOptions {
8383
},
8484
shard_ownership: ShardOwnership::Shared,
8585
mkstream: false,
86+
timestamp_format: TimestampFormat::default(),
8687
}
8788
}
8889

0 commit comments

Comments
 (0)