blob: 23f523c8bfbac67cb27622207cfc9b98eb1d21d4 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::BucketId;
use crate::bucketing::BucketingFunction;
use crate::client::metadata::Metadata;
use crate::client::write::bucket_assigner::{
BucketAssigner, HashBucketAssigner, RoundRobinBucketAssigner, StickyBucketAssigner,
};
use crate::client::write::sender::Sender;
use crate::client::{RecordAccumulator, ResultHandle, WriteRecord};
use crate::config::Config;
use crate::config::NoKeyAssigner;
use crate::error::{Error, Result};
use crate::metadata::{PhysicalTablePath, TableInfo};
use bytes::Bytes;
use dashmap::DashMap;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
#[allow(dead_code)]
pub struct WriterClient {
config: Config,
max_request_size: i32,
accumulate: Arc<RecordAccumulator>,
shutdown_tx: mpsc::Sender<()>,
sender_join_handle: JoinHandle<()>,
metadata: Arc<Metadata>,
bucket_assigners: DashMap<Arc<PhysicalTablePath>, Arc<dyn BucketAssigner>>,
}
impl WriterClient {
pub fn new(config: Config, metadata: Arc<Metadata>) -> Result<Self> {
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
let accumulator = Arc::new(RecordAccumulator::new(config.clone()));
let mut sender = Sender::new(
metadata.clone(),
accumulator.clone(),
config.writer_request_max_size,
30_000,
Self::get_ack(&config)?,
config.writer_retries,
);
let join_handle = tokio::spawn(async move {
tokio::select! {
_ = sender.run() => {
// do-nothing
},
_ = shutdown_rx.recv() => {
sender.close().await
}
}
});
Ok(Self {
max_request_size: config.writer_request_max_size,
config,
shutdown_tx,
sender_join_handle: join_handle,
accumulate: accumulator,
metadata,
bucket_assigners: Default::default(),
})
}
fn get_ack(config: &Config) -> Result<i16> {
let acks = config.writer_acks.as_str();
if acks.eq_ignore_ascii_case("all") {
Ok(-1)
} else {
acks.parse::<i16>().map_err(|e| Error::IllegalArgument {
message: format!("invalid writer ack '{acks}': {e}"),
})
}
}
pub fn send(&self, record: &WriteRecord<'_>) -> Result<ResultHandle> {
let physical_table_path = &record.physical_table_path;
let cluster = self.metadata.get_cluster();
let bucket_key = record.bucket_key.as_ref();
let (bucket_assigner, bucket_id) =
self.assign_bucket(&record.table_info, bucket_key, physical_table_path)?;
let mut result = self.accumulate.append(
record,
bucket_id,
&cluster,
bucket_assigner.abort_if_batch_full(),
)?;
if result.abort_record_for_new_batch {
let prev_bucket_id = bucket_id;
bucket_assigner.on_new_batch(&cluster, prev_bucket_id);
let bucket_id = bucket_assigner.assign_bucket(bucket_key, &cluster)?;
result = self.accumulate.append(record, bucket_id, &cluster, false)?;
}
if result.batch_is_full || result.new_batch_created {
// todo: wakeup
}
Ok(result.result_handle.expect("result_handle should exist"))
}
fn assign_bucket(
&self,
table_info: &Arc<TableInfo>,
bucket_key: Option<&Bytes>,
table_path: &Arc<PhysicalTablePath>,
) -> Result<(Arc<dyn BucketAssigner>, BucketId)> {
let cluster = self.metadata.get_cluster();
let bucket_assigner = {
if let Some(assigner) = self.bucket_assigners.get(table_path) {
assigner.clone()
} else {
let assigner = Self::create_bucket_assigner(
table_info,
Arc::clone(table_path),
bucket_key,
&self.config,
)?;
self.bucket_assigners
.insert(Arc::clone(table_path), Arc::clone(&assigner));
assigner
}
};
let bucket_id = bucket_assigner.assign_bucket(bucket_key, &cluster)?;
Ok((bucket_assigner, bucket_id))
}
pub async fn close(self) -> Result<()> {
self.shutdown_tx
.send(())
.await
.map_err(|e| Error::UnexpectedError {
message: format!("Failed to close write client: {e:?}"),
source: None,
})?;
self.sender_join_handle
.await
.map_err(|e| Error::UnexpectedError {
message: format!("Failed to close write client: {e:?}"),
source: None,
})?;
Ok(())
}
pub async fn flush(&self) -> Result<()> {
self.accumulate.begin_flush();
self.accumulate.await_flush_completion().await?;
Ok(())
}
pub fn create_bucket_assigner(
table_info: &Arc<TableInfo>,
table_path: Arc<PhysicalTablePath>,
bucket_key: Option<&Bytes>,
config: &Config,
) -> Result<Arc<dyn BucketAssigner>> {
if bucket_key.is_some() {
let datalake_format = table_info.get_table_config().get_datalake_format()?;
let function = <dyn BucketingFunction>::of(datalake_format.as_ref());
Ok(Arc::new(HashBucketAssigner::new(
table_info.num_buckets,
function,
)))
} else {
match config.writer_bucket_no_key_assigner {
NoKeyAssigner::Sticky => Ok(Arc::new(StickyBucketAssigner::new(table_path))),
NoKeyAssigner::RoundRobin => Ok(Arc::new(RoundRobinBucketAssigner::new(
table_path,
table_info.num_buckets,
))),
}
}
}
}