feat(wal): Make `wal` WriteOpEntryDecoder an interator

Also, implement drop on `wal_inspect`'s LineProtoWriter and
bubble up flush to the caller.
pull/24376/head
Fraser Savage 2023-05-15 20:46:45 +01:00
parent 6cdc95e49d
commit fcd80060be
No known key found for this signature in database
GPG Key ID: DE47C33CE8C5C446
2 changed files with 98 additions and 73 deletions

View File

@ -585,25 +585,31 @@ pub struct WriteOpEntryDecoder {
reader: ClosedSegmentFileReader, reader: ClosedSegmentFileReader,
} }
impl WriteOpEntryDecoder { impl From<ClosedSegmentFileReader> for WriteOpEntryDecoder {
/// Creates a decoder which will use the closed segment file of `reader` to /// Creates a decoder which will use the closed segment file of `reader` to
/// decode write ops from their on-disk format. /// decode write ops from their on-disk format.
pub fn from_closed_segment(reader: ClosedSegmentFileReader) -> Self { fn from(reader: ClosedSegmentFileReader) -> Self {
Self { reader } Self { reader }
} }
}
impl Iterator for WriteOpEntryDecoder {
type Item = Result<Vec<WriteOpEntry>, DecodeError>;
/// Reads a collection of write op entries in the next WAL entry batch from the /// Reads a collection of write op entries in the next WAL entry batch from the
/// underlying closed segment. A returned Ok(None) indicates that there are no /// underlying closed segment. A returned Ok(None) indicates that there are no
/// more entries to be decoded from the underlying segment. A zero-length vector /// more entries to be decoded from the underlying segment. A zero-length vector
/// may be returned if there are no writes in a WAL entry batch, but does not /// may be returned if there are no writes in a WAL entry batch, but does not
/// indicate the decoder is consumed. /// indicate the decoder is consumed.
pub fn next_write_op_entry_batch(&mut self) -> Result<Option<Vec<WriteOpEntry>>, DecodeError> { fn next(&mut self) -> Option<Self::Item> {
match self.reader.next_batch().context(FailedToReadWalSnafu)? { match self.reader.next_batch().context(FailedToReadWalSnafu) {
Some(batch) => Ok(batch Ok(Some(batch)) => Some(
batch
.into_iter() .into_iter()
.filter_map(|sequenced_op| match sequenced_op.op { .filter_map(|sequenced_op| match sequenced_op.op {
WalOp::Write(w) => Some(w), WalOp::Write(w) => Some(w),
_ => None, WalOp::Delete(..) => None,
WalOp::Persist(..) => None,
}) })
.map(|w| -> Result<WriteOpEntry, DecodeError> { .map(|w| -> Result<WriteOpEntry, DecodeError> {
Ok(WriteOpEntry { Ok(WriteOpEntry {
@ -612,9 +618,10 @@ impl WriteOpEntryDecoder {
.context(UnableToCreateMutableBatchSnafu)?, .context(UnableToCreateMutableBatchSnafu)?,
}) })
}) })
.collect::<Result<Vec<WriteOpEntry>, DecodeError>>()? .collect::<Self::Item>(),
.into()), ),
None => Ok(None), Ok(None) => None,
Err(e) => Some(Err(e)),
} }
} }
} }
@ -783,15 +790,15 @@ mod tests {
let (closed, _) = wal.rotate().unwrap(); let (closed, _) = wal.rotate().unwrap();
let mut decoder = WriteOpEntryDecoder::from_closed_segment( let decoder = WriteOpEntryDecoder::from(
wal.reader_for_segment(closed.id) wal.reader_for_segment(closed.id)
.expect("failed to open reader for closed WAL segment"), .expect("failed to open reader for closed WAL segment"),
); );
let mut write_op_entries = vec![]; let write_op_entries = decoder
while let Ok(Some(mut entry_batch)) = decoder.next_write_op_entry_batch() { .into_iter()
write_op_entries.append(&mut entry_batch); .flat_map(|r| r.expect("unexpected bad entry"))
} .collect::<Vec<_>>();
// The decoder should find 2 entries, each containing a single table write // The decoder should find 2 entries, each containing a single table write
assert_eq!(write_op_entries.len(), 2); assert_eq!(write_op_entries.len(), 2);
assert_matches!(write_op_entries.get(0), Some(got_op1) => { assert_matches!(write_op_entries.get(0), Some(got_op1) => {

View File

@ -43,13 +43,38 @@ pub enum WriteError {
/// Provides namespaced write functionality from table-based mutable batches /// Provides namespaced write functionality from table-based mutable batches
/// to namespaced line protocol output. /// to namespaced line protocol output.
#[derive(Debug)] #[derive(Debug)]
pub struct LineProtoWriter<W, F> { pub struct LineProtoWriter<W, F>
where
W: Write,
{
namespaced_output: HashMap<NamespaceId, W>, namespaced_output: HashMap<NamespaceId, W>,
new_write_sink: F, new_write_sink: F,
table_name_index: HashMap<TableId, String>, table_name_index: HashMap<TableId, String>,
} }
impl<W, F> LineProtoWriter<W, F>
where
W: Write,
{
/// Flushes all write destinations opened by the [`LineProtoWriter`].
pub fn flush(&mut self) -> Result<(), WriteError> {
for w in self.namespaced_output.values_mut() {
w.flush()?
}
Ok(())
}
}
impl<W, F> Drop for LineProtoWriter<W, F>
where
W: Write,
{
fn drop(&mut self) {
_ = self.flush()
}
}
impl<W, F> LineProtoWriter<W, F> impl<W, F> LineProtoWriter<W, F>
where where
W: Write, W: Write,
@ -80,13 +105,7 @@ where
.entry(ns) .entry(ns)
.or_insert((self.new_write_sink)(ns)?); .or_insert((self.new_write_sink)(ns)?);
match write_batches_as_line_proto(sink, &self.table_name_index, table_batches) { write_batches_as_line_proto(sink, &self.table_name_index, table_batches)
Ok(_) => sink.flush().map_err(WriteError::IoError),
Err(e) => {
_ = sink.flush();
Err(e)
}
}
} }
} }
@ -127,7 +146,7 @@ mod tests {
iox::wal::v1::sequenced_wal_op::Op, pbdata::v1::DatabaseBatch, iox::wal::v1::sequenced_wal_op::Op, pbdata::v1::DatabaseBatch,
}; };
use mutable_batch_lp::lines_to_batches; use mutable_batch_lp::lines_to_batches;
use wal::{DecodeError, SequencedWalOp, WriteOpEntryDecoder}; use wal::{DecodeError, SequencedWalOp, WriteOpEntry, WriteOpEntryDecoder};
use super::*; use super::*;
@ -163,32 +182,31 @@ mod tests {
// Rotate the WAL and create the translator. // Rotate the WAL and create the translator.
let (closed, _) = wal.rotate().expect("failed to rotate WAL"); let (closed, _) = wal.rotate().expect("failed to rotate WAL");
let mut decoder = WriteOpEntryDecoder::from_closed_segment( let decoder = WriteOpEntryDecoder::from(
wal.reader_for_segment(closed.id()) wal.reader_for_segment(closed.id())
.expect("failed to open reader for closed segment"), .expect("failed to open reader for closed segment"),
); );
let mut writer = LineProtoWriter::new(|_| Ok(Vec::<u8>::new()), table_name_index); let mut writer = LineProtoWriter::new(|_| Ok(Vec::<u8>::new()), table_name_index);
let mut decoded_entries = 0; let decoded_entries = decoder
let mut decoded_ops = 0; .into_iter()
while let Some(new_entries) = decoder .map(|r| r.expect("unexpected bad entry"))
.next_write_op_entry_batch() .collect::<Vec<_>>();
.expect("decoder error should not occur") assert_eq!(decoded_entries.len(), 1);
{ let decoded_ops = decoded_entries
decoded_entries += 1; .into_iter()
decoded_ops += new_entries.len(); .flatten()
.collect::<Vec<WriteOpEntry>>();
assert_eq!(decoded_ops.len(), 3);
for entry in new_entries { for entry in decoded_ops {
writer writer
.write_namespaced_table_batches(entry.namespace, entry.table_batches) .write_namespaced_table_batches(entry.namespace, entry.table_batches)
.expect("batch write should not fail"); .expect("batch write should not fail");
} }
}
// The WAL has been given a single entry containing three write ops // The WAL has been given a single entry containing three write ops
assert_eq!(decoded_entries, 1);
assert_eq!(decoded_ops, 3);
let results = writer.namespaced_output; let results = &writer.namespaced_output;
// Assert that the namespaced writes contain ONLY the following: // Assert that the namespaced writes contain ONLY the following:
// //
@ -269,36 +287,36 @@ mod tests {
} }
// Create the translator and read as much as possible out of the bad segment file // Create the translator and read as much as possible out of the bad segment file
let mut decoder = WriteOpEntryDecoder::from_closed_segment( let decoder = WriteOpEntryDecoder::from(
wal.reader_for_segment(closed.id()) wal.reader_for_segment(closed.id())
.expect("failed to open reader for closed segment"), .expect("failed to open reader for closed segment"),
); );
let mut writer = LineProtoWriter::new(|_| Ok(Vec::<u8>::new()), table_name_index); let mut writer = LineProtoWriter::new(|_| Ok(Vec::<u8>::new()), table_name_index);
let mut decoded_entries = 0; // The translator should be able to read all 2 good entries containing 4 write ops
let mut decoded_ops = 0; let decoded_entries = decoder
loop { .into_iter()
match decoder.next_write_op_entry_batch() { .map_while(|r| {
// If the translator returns `None` indicating successful translation r.map_err(|e| match e {
// then something is broken. DecodeError::FailedToReadWal { .. } => None::<()>,
Ok(v) => assert_matches!(v, Some(new_entries) => { _ => panic!("unexpected error"),
decoded_entries += 1; })
decoded_ops += new_entries.len(); .ok()
for entry in new_entries { })
writer.write_namespaced_table_batches(entry.namespace, entry.table_batches).expect("batch write should not fail"); .collect::<Vec<_>>();
assert_eq!(decoded_entries.len(), 2);
let decoded_ops = decoded_entries
.into_iter()
.flatten()
.collect::<Vec<WriteOpEntry>>();
assert_eq!(decoded_ops.len(), 4);
for entry in decoded_ops {
writer
.write_namespaced_table_batches(entry.namespace, entry.table_batches)
.expect("batch write should not fail");
} }
}),
Err(e) => {
assert_matches!(e, DecodeError::FailedToReadWal { .. });
break;
}
};
}
// The translator should have read all 2 good entries containing 4 write ops
assert_eq!(decoded_entries, 2);
assert_eq!(decoded_ops, 4);
let results = writer.namespaced_output; let results = &writer.namespaced_output;
// Assert that the namespaced writes contain ONLY the following: // Assert that the namespaced writes contain ONLY the following:
// //