Skip to content

Commit ebf2c16

Browse files
feat: allow ingestion with custom time partition (#790)
This PR updates the time partition logic to check if time partition field value is not less than a month old then partition based on time partition field also, if static schema is provided check if static schema has time partition field at the time of log creation This PR is partly related to #752
1 parent 62aea42 commit ebf2c16

File tree

14 files changed

+318
-229
lines changed

14 files changed

+318
-229
lines changed

server/src/catalog.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::handlers::http::base_path_without_preceding_slash;
2323
use crate::option::CONFIG;
2424
use crate::{
2525
catalog::manifest::Manifest,
26+
event::DEFAULT_TIMESTAMP_KEY,
2627
query::PartialTimeFilter,
2728
storage::{object_storage::manifest_path, ObjectStorage, ObjectStorageError},
2829
};
@@ -73,14 +74,17 @@ impl ManifestFile for manifest::File {
7374
}
7475
}
7576

76-
fn get_file_bounds(file: &manifest::File) -> (DateTime<Utc>, DateTime<Utc>) {
77+
fn get_file_bounds(
78+
file: &manifest::File,
79+
partition_column: String,
80+
) -> (DateTime<Utc>, DateTime<Utc>) {
7781
match file
7882
.columns()
7983
.iter()
80-
.find(|col| col.name == "p_timestamp")
84+
.find(|col| col.name == partition_column)
8185
.unwrap()
8286
.stats
83-
.clone()
87+
.as_ref()
8488
.unwrap()
8589
{
8690
column::TypedStatistics::Int(stats) => (
@@ -98,8 +102,19 @@ pub async fn update_snapshot(
98102
) -> Result<(), ObjectStorageError> {
99103
// get current snapshot
100104
let mut meta = storage.get_object_store_format(stream_name).await?;
105+
let meta_clone = meta.clone();
101106
let manifests = &mut meta.snapshot.manifest_list;
102-
let (lower_bound, _) = get_file_bounds(&change);
107+
let time_partition: Option<String> = meta_clone.time_partition;
108+
let lower_bound = match time_partition {
109+
Some(time_partition) => {
110+
let (lower_bound, _) = get_file_bounds(&change, time_partition);
111+
lower_bound
112+
}
113+
None => {
114+
let (lower_bound, _) = get_file_bounds(&change, DEFAULT_TIMESTAMP_KEY.to_string());
115+
lower_bound
116+
}
117+
};
103118
let pos = manifests.iter().position(|item| {
104119
item.time_lower_bound <= lower_bound && lower_bound < item.time_upper_bound
105120
});
@@ -242,6 +257,7 @@ pub async fn get_first_event(
242257
// get current snapshot
243258
let mut meta = storage.get_object_store_format(stream_name).await?;
244259
let manifests = &mut meta.snapshot.manifest_list;
260+
let time_partition = meta.time_partition;
245261
if manifests.is_empty() {
246262
log::info!("No manifest found for stream {stream_name}");
247263
return Err(ObjectStorageError::Custom("No manifest found".to_string()));
@@ -260,7 +276,17 @@ pub async fn get_first_event(
260276
));
261277
};
262278
if let Some(first_event) = manifest.files.first() {
263-
let (lower_bound, _) = get_file_bounds(first_event);
279+
let lower_bound = match time_partition {
280+
Some(time_partition) => {
281+
let (lower_bound, _) = get_file_bounds(first_event, time_partition);
282+
lower_bound
283+
}
284+
None => {
285+
let (lower_bound, _) =
286+
get_file_bounds(first_event, DEFAULT_TIMESTAMP_KEY.to_string());
287+
lower_bound
288+
}
289+
};
264290
first_event_at = lower_bound.with_timezone(&Local).to_rfc3339();
265291
}
266292
}

server/src/event.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use std::sync::Arc;
2929
use self::error::EventError;
3030
pub use self::writer::STREAM_WRITERS;
3131
use crate::metadata;
32+
use chrono::NaiveDateTime;
3233

3334
pub const DEFAULT_TIMESTAMP_KEY: &str = "p_timestamp";
3435
pub const DEFAULT_TAGS_KEY: &str = "p_tags";
@@ -41,19 +42,30 @@ pub struct Event {
4142
pub origin_format: &'static str,
4243
pub origin_size: u64,
4344
pub is_first_event: bool,
45+
pub parsed_timestamp: NaiveDateTime,
46+
pub time_partition: Option<String>,
4447
}
4548

4649
// Events holds the schema related to a each event for a single log stream
4750
impl Event {
4851
pub async fn process(self) -> Result<(), EventError> {
49-
let key = get_schema_key(&self.rb.schema().fields);
50-
let num_rows = self.rb.num_rows() as u64;
52+
let mut key = get_schema_key(&self.rb.schema().fields);
53+
if self.time_partition.is_some() {
54+
let parsed_timestamp_to_min = self.parsed_timestamp.format("%Y%m%dT%H%M").to_string();
55+
key = format!("{key}{parsed_timestamp_to_min}");
56+
}
5157

58+
let num_rows = self.rb.num_rows() as u64;
5259
if self.is_first_event {
5360
commit_schema(&self.stream_name, self.rb.schema())?;
5461
}
5562

56-
Self::process_event(&self.stream_name, &key, self.rb.clone())?;
63+
Self::process_event(
64+
&self.stream_name,
65+
&key,
66+
self.rb.clone(),
67+
self.parsed_timestamp,
68+
)?;
5769

5870
metadata::STREAM_INFO.update_stats(
5971
&self.stream_name,
@@ -80,8 +92,9 @@ impl Event {
8092
stream_name: &str,
8193
schema_key: &str,
8294
rb: RecordBatch,
95+
parsed_timestamp: NaiveDateTime,
8396
) -> Result<(), EventError> {
84-
STREAM_WRITERS.append_to_local(stream_name, schema_key, rb)?;
97+
STREAM_WRITERS.append_to_local(stream_name, schema_key, rb, parsed_timestamp)?;
8598
Ok(())
8699
}
87100
}
@@ -90,7 +103,7 @@ pub fn get_schema_key(fields: &[Arc<Field>]) -> String {
90103
// Fields must be sorted
91104
let mut hasher = xxhash_rust::xxh3::Xxh3::new();
92105
for field in fields.iter().sorted_by_key(|v| v.name()) {
93-
hasher.update(field.name().as_bytes())
106+
hasher.update(field.name().as_bytes());
94107
}
95108
let hash = hasher.digest();
96109
format!("{hash:x}")

server/src/event/format.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,20 @@ pub trait EventFormat: Sized {
4141
fn to_data(
4242
self,
4343
schema: HashMap<String, Arc<Field>>,
44-
time_partition: Option<String>,
4544
static_schema_flag: Option<String>,
45+
time_partition: Option<String>,
4646
) -> Result<(Self::Data, EventSchema, bool, Tags, Metadata), AnyError>;
4747
fn decode(data: Self::Data, schema: Arc<Schema>) -> Result<RecordBatch, AnyError>;
4848
fn into_recordbatch(
4949
self,
5050
storage_schema: HashMap<String, Arc<Field>>,
51-
time_partition: Option<String>,
5251
static_schema_flag: Option<String>,
52+
time_partition: Option<String>,
5353
) -> Result<(RecordBatch, bool), AnyError> {
5454
let (data, mut schema, is_first, tags, metadata) = self.to_data(
5555
storage_schema.clone(),
56-
time_partition,
5756
static_schema_flag.clone(),
57+
time_partition.clone(),
5858
)?;
5959

6060
if get_field(&schema, DEFAULT_TAGS_KEY).is_some() {
@@ -96,10 +96,11 @@ pub trait EventFormat: Sized {
9696
)));
9797

9898
// prepare the record batch and new fields to be added
99-
let new_schema = Arc::new(Schema::new(schema));
99+
let mut new_schema = Arc::new(Schema::new(schema));
100100
if !Self::is_schema_matching(new_schema.clone(), storage_schema, static_schema_flag) {
101101
return Err(anyhow!("Schema mismatch"));
102102
}
103+
new_schema = update_field_type_in_schema(new_schema, time_partition);
103104
let rb = Self::decode(data, new_schema.clone())?;
104105
let tags_arr = StringArray::from_iter_values(std::iter::repeat(&tags).take(rb.num_rows()));
105106
let metadata_arr =
@@ -143,3 +144,30 @@ pub trait EventFormat: Sized {
143144
true
144145
}
145146
}
147+
148+
pub fn update_field_type_in_schema(
149+
schema: Arc<Schema>,
150+
time_partition: Option<String>,
151+
) -> Arc<Schema> {
152+
if time_partition.is_none() {
153+
return schema;
154+
}
155+
let field_name = time_partition.unwrap();
156+
let new_schema: Vec<Field> = schema
157+
.fields()
158+
.iter()
159+
.map(|field| {
160+
if *field.name() == field_name {
161+
if field.data_type() == &DataType::Utf8 {
162+
let new_data_type = DataType::Timestamp(TimeUnit::Millisecond, None);
163+
Field::new(field.name().clone(), new_data_type, true)
164+
} else {
165+
Field::new(field.name(), field.data_type().clone(), true)
166+
}
167+
} else {
168+
Field::new(field.name(), field.data_type().clone(), true)
169+
}
170+
})
171+
.collect();
172+
Arc::new(Schema::new(new_schema))
173+
}

server/src/event/format/json.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ impl EventFormat for Event {
4545
fn to_data(
4646
self,
4747
schema: HashMap<String, Arc<Field>>,
48-
time_partition: Option<String>,
4948
static_schema_flag: Option<String>,
49+
time_partition: Option<String>,
5050
) -> Result<(Self::Data, Vec<Arc<Field>>, bool, Tags, Metadata), anyhow::Error> {
51-
let data = flatten_json_body(self.data, time_partition)?;
51+
let data = flatten_json_body(self.data, None, false)?;
5252
let stream_schema = schema;
5353

5454
// incoming event may be a single json or a json array
@@ -68,7 +68,12 @@ impl EventFormat for Event {
6868
let schema = match derive_arrow_schema(&stream_schema, fields) {
6969
Ok(schema) => schema,
7070
Err(_) => match infer_json_schema_from_iterator(value_arr.iter().map(Ok)) {
71-
Ok(infer_schema) => {
71+
Ok(mut infer_schema) => {
72+
let new_infer_schema = super::super::format::update_field_type_in_schema(
73+
Arc::new(infer_schema),
74+
time_partition,
75+
);
76+
infer_schema = Schema::new(new_infer_schema.fields().clone());
7277
if let Err(err) = Schema::try_merge(vec![
7378
Schema::new(stream_schema.values().cloned().collect::<Fields>()),
7479
infer_schema.clone(),

server/src/event/writer.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ use std::{
2525
sync::{Arc, Mutex, RwLock},
2626
};
2727

28-
use crate::utils;
29-
3028
use self::{errors::StreamWriterError, file_writer::FileWriter, mem_writer::MemWriter};
29+
use crate::utils;
3130
use arrow_array::{RecordBatch, TimestampMillisecondArray};
3231
use arrow_schema::Schema;
32+
use chrono::NaiveDateTime;
3333
use chrono::Utc;
3434
use derive_more::{Deref, DerefMut};
3535
use once_cell::sync::Lazy;
@@ -48,6 +48,7 @@ impl Writer {
4848
stream_name: &str,
4949
schema_key: &str,
5050
rb: RecordBatch,
51+
parsed_timestamp: NaiveDateTime,
5152
) -> Result<(), StreamWriterError> {
5253
let rb = utils::arrow::replace_columns(
5354
rb.schema(),
@@ -56,7 +57,8 @@ impl Writer {
5657
&[Arc::new(get_timestamp_array(rb.num_rows()))],
5758
);
5859

59-
self.disk.push(stream_name, schema_key, &rb)?;
60+
self.disk
61+
.push(stream_name, schema_key, &rb, parsed_timestamp)?;
6062
self.mem.push(schema_key, rb);
6163
Ok(())
6264
}
@@ -72,29 +74,34 @@ impl WriterTable {
7274
stream_name: &str,
7375
schema_key: &str,
7476
record: RecordBatch,
77+
parsed_timestamp: NaiveDateTime,
7578
) -> Result<(), StreamWriterError> {
7679
let hashmap_guard = self.read().unwrap();
7780

7881
match hashmap_guard.get(stream_name) {
7982
Some(stream_writer) => {
80-
stream_writer
81-
.lock()
82-
.unwrap()
83-
.push(stream_name, schema_key, record)?;
83+
stream_writer.lock().unwrap().push(
84+
stream_name,
85+
schema_key,
86+
record,
87+
parsed_timestamp,
88+
)?;
8489
}
8590
None => {
8691
drop(hashmap_guard);
8792
let mut map = self.write().unwrap();
8893
// check for race condition
8994
// if map contains entry then just
9095
if let Some(writer) = map.get(stream_name) {
91-
writer
92-
.lock()
93-
.unwrap()
94-
.push(stream_name, schema_key, record)?;
96+
writer.lock().unwrap().push(
97+
stream_name,
98+
schema_key,
99+
record,
100+
parsed_timestamp,
101+
)?;
95102
} else {
96103
let mut writer = Writer::default();
97-
writer.push(stream_name, schema_key, record)?;
104+
writer.push(stream_name, schema_key, record, parsed_timestamp)?;
98105
map.insert(stream_name.to_owned(), Mutex::new(writer));
99106
}
100107
}

server/src/event/writer/file_writer.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ use std::collections::HashMap;
2424
use std::fs::{File, OpenOptions};
2525
use std::path::PathBuf;
2626

27-
use crate::storage::staging::StorageDir;
28-
2927
use super::errors::StreamWriterError;
28+
use crate::storage::staging::StorageDir;
29+
use chrono::NaiveDateTime;
3030

3131
pub struct ArrowWriter {
3232
pub file_path: PathBuf,
@@ -43,6 +43,7 @@ impl FileWriter {
4343
stream_name: &str,
4444
schema_key: &str,
4545
record: &RecordBatch,
46+
parsed_timestamp: NaiveDateTime,
4647
) -> Result<(), StreamWriterError> {
4748
match self.get_mut(schema_key) {
4849
Some(writer) => {
@@ -54,7 +55,8 @@ impl FileWriter {
5455
// entry is not present thus we create it
5556
None => {
5657
// this requires mutable borrow of the map so we drop this read lock and wait for write lock
57-
let (path, writer) = init_new_stream_writer_file(stream_name, schema_key, record)?;
58+
let (path, writer) =
59+
init_new_stream_writer_file(stream_name, schema_key, record, parsed_timestamp)?;
5860
self.insert(
5961
schema_key.to_owned(),
6062
ArrowWriter {
@@ -79,9 +81,10 @@ fn init_new_stream_writer_file(
7981
stream_name: &str,
8082
schema_key: &str,
8183
record: &RecordBatch,
84+
parsed_timestamp: NaiveDateTime,
8285
) -> Result<(PathBuf, StreamWriter<std::fs::File>), StreamWriterError> {
8386
let dir = StorageDir::new(stream_name);
84-
let path = dir.path_by_current_time(schema_key);
87+
let path = dir.path_by_current_time(schema_key, parsed_timestamp);
8588
std::fs::create_dir_all(dir.data_path)?;
8689

8790
let file = OpenOptions::new().create(true).append(true).open(&path)?;

0 commit comments

Comments
 (0)