Allow different kinds of items to be sent to database insert queue
This commit is contained in:
@@ -4,7 +4,7 @@ pub mod store;
|
||||
|
||||
use self::conn::FindIocStream;
|
||||
use self::store::DataStore;
|
||||
use crate::store::CommonInsertItemQueue;
|
||||
use crate::store::{CommonInsertItemQueue, QueryItem};
|
||||
use conn::CaConn;
|
||||
use err::Error;
|
||||
use futures_util::StreamExt;
|
||||
@@ -275,29 +275,77 @@ async fn spawn_scylla_insert_workers(
|
||||
let fut = async move {
|
||||
let mut i1 = 0;
|
||||
while let Ok(item) = recv.recv().await {
|
||||
stats.store_worker_item_recv_inc();
|
||||
let insert_frac = insert_frac.load(Ordering::Acquire);
|
||||
if i1 % 1000 < insert_frac {
|
||||
match crate::store::insert_item(item, &data_store, &stats).await {
|
||||
Ok(_) => {
|
||||
stats.store_worker_item_insert_inc();
|
||||
match item {
|
||||
QueryItem::Insert(item) => {
|
||||
stats.store_worker_item_recv_inc();
|
||||
let insert_frac = insert_frac.load(Ordering::Acquire);
|
||||
if i1 % 1000 < insert_frac {
|
||||
match crate::store::insert_item(item, &data_store, &stats).await {
|
||||
Ok(_) => {
|
||||
stats.store_worker_item_insert_inc();
|
||||
}
|
||||
Err(e) => {
|
||||
stats.store_worker_item_error_inc();
|
||||
// TODO introduce more structured error variants.
|
||||
if e.msg().contains("WriteTimeout") {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
} else {
|
||||
// TODO back off but continue.
|
||||
error!("insert worker sees error: {e:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stats.store_worker_item_drop_inc();
|
||||
}
|
||||
Err(e) => {
|
||||
stats.store_worker_item_error_inc();
|
||||
// TODO introduce more structured error variants.
|
||||
if e.msg().contains("WriteTimeout") {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
} else {
|
||||
// TODO back off but continue.
|
||||
error!("insert worker sees error: {e:?}");
|
||||
break;
|
||||
i1 += 1;
|
||||
}
|
||||
QueryItem::Mute(item) => {
|
||||
let values = (
|
||||
(item.series & 0xff) as i32,
|
||||
item.series as i64,
|
||||
item.ts as i64,
|
||||
item.ema,
|
||||
item.emd,
|
||||
);
|
||||
let qres = data_store
|
||||
.scy
|
||||
.query(
|
||||
"insert into muted (part, series, ts, ema, emd) values (?, ?, ?, ?, ?)",
|
||||
values,
|
||||
)
|
||||
.await;
|
||||
match qres {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
stats.store_worker_item_error_inc();
|
||||
}
|
||||
}
|
||||
}
|
||||
QueryItem::Ivl(item) => {
|
||||
let values = (
|
||||
(item.series & 0xff) as i32,
|
||||
item.series as i64,
|
||||
item.ts as i64,
|
||||
item.ema,
|
||||
item.emd,
|
||||
);
|
||||
let qres = data_store
|
||||
.scy
|
||||
.query(
|
||||
"insert into item_recv_ivl (part, series, ts, ema, emd) values (?, ?, ?, ?, ?)",
|
||||
values,
|
||||
)
|
||||
.await;
|
||||
match qres {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
stats.store_worker_item_error_inc();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stats.store_worker_item_drop_inc();
|
||||
}
|
||||
i1 += 1;
|
||||
}
|
||||
};
|
||||
tokio::spawn(fut);
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::store::DataStore;
|
||||
use crate::bsread::ChannelDescDecoded;
|
||||
use crate::ca::proto::{CreateChan, EventAdd, HeadInfo};
|
||||
use crate::series::{Existence, SeriesId};
|
||||
use crate::store::{CommonInsertItemQueueSender, InsertItem};
|
||||
use crate::store::{CommonInsertItemQueueSender, InsertItem, IvlItem, MuteItem, QueryItem};
|
||||
use err::Error;
|
||||
use futures_util::stream::FuturesOrdered;
|
||||
use futures_util::{Future, FutureExt, Stream, StreamExt, TryFutureExt};
|
||||
@@ -62,9 +62,10 @@ struct CreatedState {
|
||||
ts_msp_grid_last: u32,
|
||||
inserted_in_ts_msp: u64,
|
||||
insert_item_ivl_ema: IntervalEma,
|
||||
item_recv_ivl_ema: IntervalEma,
|
||||
insert_recv_ivl_last: Instant,
|
||||
insert_next_earliest: Instant,
|
||||
#[allow(unused)]
|
||||
fast_warn_count: u32,
|
||||
muted_before: u32,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
@@ -114,9 +115,9 @@ pub struct CaConn {
|
||||
name_by_cid: BTreeMap<u32, String>,
|
||||
poll_count: usize,
|
||||
data_store: Arc<DataStore>,
|
||||
insert_item_queue: VecDeque<InsertItem>,
|
||||
insert_item_queue: VecDeque<QueryItem>,
|
||||
insert_item_sender: CommonInsertItemQueueSender,
|
||||
insert_item_send_fut: Option<async_channel::Send<'static, InsertItem>>,
|
||||
insert_item_send_fut: Option<async_channel::Send<'static, QueryItem>>,
|
||||
fut_get_series:
|
||||
FuturesOrdered<Pin<Box<dyn Future<Output = Result<(u32, u32, u16, u16, Existence<SeriesId>), Error>> + Send>>>,
|
||||
remote_addr_dbg: SocketAddrV4,
|
||||
@@ -271,8 +272,10 @@ impl CaConn {
|
||||
ts_msp_grid_last: 0,
|
||||
inserted_in_ts_msp: u64::MAX,
|
||||
insert_item_ivl_ema: IntervalEma::new(),
|
||||
item_recv_ivl_ema: IntervalEma::new(),
|
||||
insert_recv_ivl_last: Instant::now(),
|
||||
insert_next_earliest: Instant::now(),
|
||||
fast_warn_count: 0,
|
||||
muted_before: 0,
|
||||
});
|
||||
let scalar_type = ScalarType::from_ca_id(data_type)?;
|
||||
let shape = Shape::from_ca_count(data_count)?;
|
||||
@@ -351,7 +354,7 @@ impl CaConn {
|
||||
val: ev.value,
|
||||
ts_msp_grid,
|
||||
};
|
||||
item_queue.push_back(item);
|
||||
item_queue.push_back(QueryItem::Insert(item));
|
||||
self.stats.insert_item_create_inc();
|
||||
Ok(())
|
||||
}
|
||||
@@ -398,23 +401,26 @@ impl CaConn {
|
||||
return Err(format!("no series id on insert").into());
|
||||
}
|
||||
};
|
||||
let ts = {
|
||||
let ts = SystemTime::now();
|
||||
let epoch = ts.duration_since(std::time::UNIX_EPOCH).unwrap();
|
||||
epoch.as_secs() * SEC + epoch.subsec_nanos() as u64
|
||||
};
|
||||
let tsnow = Instant::now();
|
||||
if tsnow >= st.insert_next_earliest {
|
||||
st.muted_before = 0;
|
||||
st.insert_item_ivl_ema.tick(tsnow);
|
||||
let em = st.insert_item_ivl_ema.ema();
|
||||
let ema = em.ema();
|
||||
let mm = self.insert_ivl_min.load(Ordering::Acquire);
|
||||
let mm = (mm as f32) * 1e-6;
|
||||
let dt = (mm - ema) / em.k();
|
||||
let ivl_min = self.insert_ivl_min.load(Ordering::Acquire);
|
||||
let ivl_min = (ivl_min as f32) * 1e-6;
|
||||
let dt = (ivl_min - ema).max(0.) / em.k();
|
||||
st.insert_next_earliest = tsnow
|
||||
.checked_add(Duration::from_micros((dt * 1e6) as u64))
|
||||
.ok_or_else(|| Error::with_msg_no_trace("time overflow in next insert"))?;
|
||||
let ts_msp_last = st.ts_msp_last;
|
||||
let inserted_in_ts_msp = st.inserted_in_ts_msp;
|
||||
// TODO get event timestamp from channel access field
|
||||
let ts = SystemTime::now();
|
||||
let epoch = ts.duration_since(std::time::UNIX_EPOCH).unwrap();
|
||||
let ts = epoch.as_secs() * SEC + epoch.subsec_nanos() as u64;
|
||||
let ts_msp_grid = (ts / (SEC * 10 * 6 * 2)) as u32 * (6 * 2);
|
||||
let ts_msp_grid = if st.ts_msp_grid_last != ts_msp_grid {
|
||||
st.ts_msp_grid_last = ts_msp_grid;
|
||||
@@ -435,6 +441,28 @@ impl CaConn {
|
||||
)?;
|
||||
} else {
|
||||
self.stats.channel_fast_item_drop_inc();
|
||||
if tsnow.duration_since(st.insert_recv_ivl_last) >= Duration::from_millis(2000) {
|
||||
st.insert_recv_ivl_last = tsnow;
|
||||
let ema = st.insert_item_ivl_ema.ema();
|
||||
let item = IvlItem {
|
||||
series: series.id(),
|
||||
ts,
|
||||
ema: ema.ema(),
|
||||
emd: ema.emv().sqrt(),
|
||||
};
|
||||
self.insert_item_queue.push_back(QueryItem::Ivl(item));
|
||||
}
|
||||
if false && st.muted_before == 0 {
|
||||
let ema = st.insert_item_ivl_ema.ema();
|
||||
let item = MuteItem {
|
||||
series: series.id(),
|
||||
ts,
|
||||
ema: ema.ema(),
|
||||
emd: ema.emv().sqrt(),
|
||||
};
|
||||
self.insert_item_queue.push_back(QueryItem::Mute(item));
|
||||
}
|
||||
st.muted_before = 1;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -586,8 +614,10 @@ impl CaConn {
|
||||
ts_msp_grid_last: 0,
|
||||
inserted_in_ts_msp: u64::MAX,
|
||||
insert_item_ivl_ema: IntervalEma::new(),
|
||||
item_recv_ivl_ema: IntervalEma::new(),
|
||||
insert_recv_ivl_last: Instant::now(),
|
||||
insert_next_earliest: Instant::now(),
|
||||
fast_warn_count: 0,
|
||||
muted_before: 0,
|
||||
});
|
||||
// TODO handle error in different way. Should most likely not abort.
|
||||
let cd = ChannelDescDecoded {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::bsread::ChannelDescDecoded;
|
||||
use crate::errconv::ErrConv;
|
||||
use err::Error;
|
||||
#[allow(unused)]
|
||||
use log::*;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio_postgres::Client as PgClient;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -41,21 +40,29 @@ pub async fn get_series_id(pg_client: &PgClient, cd: &ChannelDescDecoded) -> Res
|
||||
all.push(series);
|
||||
}
|
||||
let rn = all.len();
|
||||
let tsbeg = Instant::now();
|
||||
if rn == 0 {
|
||||
use md5::Digest;
|
||||
let mut h = md5::Md5::new();
|
||||
h.update(facility.as_bytes());
|
||||
h.update(channel_name.as_bytes());
|
||||
h.update(format!("{:?} {:?}", scalar_type, shape).as_bytes());
|
||||
let f = h.finalize();
|
||||
let mut series = u64::from_le_bytes(f.as_slice()[0..8].try_into().unwrap());
|
||||
if series > i64::MAX as u64 {
|
||||
series &= 0x7fffffffffffffff;
|
||||
}
|
||||
for _ in 0..2000 {
|
||||
if series < 1 || series > i64::MAX as u64 {
|
||||
h.update(format!("{:?}", scalar_type).as_bytes());
|
||||
h.update(format!("{:?}", shape).as_bytes());
|
||||
for _ in 0..200 {
|
||||
h.update(tsbeg.elapsed().subsec_nanos().to_ne_bytes());
|
||||
let f = h.clone().finalize();
|
||||
let mut series = u64::from_le_bytes(f.as_slice()[0..8].try_into().unwrap());
|
||||
if series > i64::MAX as u64 {
|
||||
series &= 0x7fffffffffffffff;
|
||||
}
|
||||
if series == 0 {
|
||||
series = 1;
|
||||
}
|
||||
if series <= 0 || series > i64::MAX as u64 {
|
||||
return Err(Error::with_msg_no_trace(format!(
|
||||
"attempt to insert bad series id {series}"
|
||||
)));
|
||||
}
|
||||
let res = pg_client
|
||||
.execute(
|
||||
concat!(
|
||||
@@ -76,9 +83,8 @@ pub async fn get_series_id(pg_client: &PgClient, cd: &ChannelDescDecoded) -> Res
|
||||
);
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
series += 1;
|
||||
}
|
||||
error!("tried to insert {series:?} for {facility} {channel_name} {scalar_type:?} {shape:?} but it failed");
|
||||
error!("tried to insert new series id for {facility} {channel_name} {scalar_type:?} {shape:?} but failed");
|
||||
Err(Error::with_msg_no_trace(format!("get_series_id can not create and insert series id {facility:?} {channel_name:?} {scalar_type:?} {shape:?}")))
|
||||
} else {
|
||||
let series = all[0] as u64;
|
||||
|
||||
@@ -96,13 +96,36 @@ pub struct InsertItem {
|
||||
pub val: CaDataValue,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MuteItem {
|
||||
pub series: u64,
|
||||
pub ts: u64,
|
||||
pub ema: f32,
|
||||
pub emd: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IvlItem {
|
||||
pub series: u64,
|
||||
pub ts: u64,
|
||||
pub ema: f32,
|
||||
pub emd: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryItem {
|
||||
Insert(InsertItem),
|
||||
Mute(MuteItem),
|
||||
Ivl(IvlItem),
|
||||
}
|
||||
|
||||
pub struct CommonInsertItemQueueSender {
|
||||
sender: async_channel::Sender<InsertItem>,
|
||||
sender: async_channel::Sender<QueryItem>,
|
||||
}
|
||||
|
||||
impl CommonInsertItemQueueSender {
|
||||
#[inline(always)]
|
||||
pub fn send(&self, k: InsertItem) -> async_channel::Send<InsertItem> {
|
||||
pub fn send(&self, k: QueryItem) -> async_channel::Send<QueryItem> {
|
||||
self.sender.send(k)
|
||||
}
|
||||
|
||||
@@ -113,8 +136,8 @@ impl CommonInsertItemQueueSender {
|
||||
}
|
||||
|
||||
pub struct CommonInsertItemQueue {
|
||||
sender: async_channel::Sender<InsertItem>,
|
||||
recv: async_channel::Receiver<InsertItem>,
|
||||
sender: async_channel::Sender<QueryItem>,
|
||||
recv: async_channel::Receiver<QueryItem>,
|
||||
}
|
||||
|
||||
impl CommonInsertItemQueue {
|
||||
@@ -132,7 +155,7 @@ impl CommonInsertItemQueue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receiver(&self) -> async_channel::Receiver<InsertItem> {
|
||||
pub fn receiver(&self) -> async_channel::Receiver<QueryItem> {
|
||||
self.recv.clone()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user