224 lines
6.6 KiB
Rust
224 lines
6.6 KiB
Rust
use crate::collab_indexer::IndexerProvider;
|
|
use crate::entity::{EmbeddingRecord, UnindexedCollab};
|
|
use crate::scheduler::{batch_insert_records, IndexerScheduler};
|
|
use crate::thread_pool::ThreadPoolNoAbort;
|
|
use crate::vector::embedder::Embedder;
|
|
use collab::core::collab::DataSource;
|
|
use collab::core::origin::CollabOrigin;
|
|
use collab::preclude::Collab;
|
|
use collab_entity::CollabType;
|
|
use database::collab::{CollabStorage, GetCollabOrigin};
|
|
use database::index::stream_collabs_without_embeddings;
|
|
use futures_util::stream::BoxStream;
|
|
use futures_util::StreamExt;
|
|
use rayon::iter::ParallelIterator;
|
|
use rayon::prelude::IntoParallelIterator;
|
|
use sqlx::pool::PoolConnection;
|
|
use sqlx::Postgres;
|
|
use std::sync::Arc;
|
|
use std::time::{Duration, Instant};
|
|
use tracing::{error, info, trace};
|
|
use uuid::Uuid;
|
|
|
|
#[allow(dead_code)]
|
|
pub(crate) async fn index_workspace(scheduler: Arc<IndexerScheduler>, workspace_id: Uuid) {
|
|
let weak_threads = Arc::downgrade(&scheduler.threads);
|
|
let mut retry_delay = Duration::from_secs(2);
|
|
loop {
|
|
let threads = match weak_threads.upgrade() {
|
|
Some(threads) => threads,
|
|
None => {
|
|
info!("[Embedding] thread pool is dropped, stop indexing");
|
|
break;
|
|
},
|
|
};
|
|
|
|
let conn = scheduler.pg_pool.try_acquire();
|
|
if conn.is_none() {
|
|
tokio::time::sleep(retry_delay).await;
|
|
// 4s, 8s, 16s, 32s, 60s
|
|
retry_delay = retry_delay.saturating_mul(2);
|
|
if retry_delay > Duration::from_secs(60) {
|
|
error!("[Embedding] failed to acquire db connection for 1 minute, stop indexing");
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
retry_delay = Duration::from_secs(2);
|
|
let mut conn = conn.unwrap();
|
|
let mut stream =
|
|
stream_unindexed_collabs(&mut conn, workspace_id, scheduler.storage.clone(), 50).await;
|
|
|
|
let batch_size = 5;
|
|
let mut unindexed_collabs = Vec::with_capacity(batch_size);
|
|
while let Some(Ok(collab)) = stream.next().await {
|
|
if unindexed_collabs.len() < batch_size {
|
|
unindexed_collabs.push(collab);
|
|
continue;
|
|
}
|
|
|
|
index_then_write_embedding_to_disk(
|
|
&scheduler,
|
|
threads.clone(),
|
|
std::mem::take(&mut unindexed_collabs),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
if !unindexed_collabs.is_empty() {
|
|
index_then_write_embedding_to_disk(&scheduler, threads.clone(), unindexed_collabs).await;
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn index_then_write_embedding_to_disk(
|
|
scheduler: &Arc<IndexerScheduler>,
|
|
threads: Arc<ThreadPoolNoAbort>,
|
|
unindexed_collabs: Vec<UnindexedCollab>,
|
|
) {
|
|
info!(
|
|
"[Embedding] process batch {:?} embeddings",
|
|
unindexed_collabs
|
|
.iter()
|
|
.map(|v| v.object_id.clone())
|
|
.collect::<Vec<_>>()
|
|
);
|
|
|
|
if let Ok(embedder) = scheduler.create_embedder() {
|
|
let start = Instant::now();
|
|
let embeddings = create_embeddings(
|
|
embedder,
|
|
&scheduler.indexer_provider,
|
|
threads.clone(),
|
|
unindexed_collabs,
|
|
)
|
|
.await;
|
|
scheduler
|
|
.metrics
|
|
.record_gen_embedding_time(embeddings.len() as u32, start.elapsed().as_millis());
|
|
|
|
let write_start = Instant::now();
|
|
let n = embeddings.len();
|
|
match batch_insert_records(&scheduler.pg_pool, embeddings).await {
|
|
Ok(_) => trace!(
|
|
"[Embedding] upsert {} embeddings success, cost:{}ms",
|
|
n,
|
|
write_start.elapsed().as_millis()
|
|
),
|
|
Err(err) => error!("{}", err),
|
|
}
|
|
|
|
scheduler
|
|
.metrics
|
|
.record_write_embedding_time(write_start.elapsed().as_millis());
|
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
|
} else {
|
|
trace!("[Embedding] no embeddings to process in this batch");
|
|
}
|
|
}
|
|
|
|
async fn stream_unindexed_collabs(
|
|
conn: &mut PoolConnection<Postgres>,
|
|
workspace_id: Uuid,
|
|
storage: Arc<dyn CollabStorage>,
|
|
limit: i64,
|
|
) -> BoxStream<Result<UnindexedCollab, anyhow::Error>> {
|
|
let cloned_storage = storage.clone();
|
|
stream_collabs_without_embeddings(conn, workspace_id, limit)
|
|
.await
|
|
.map(move |result| {
|
|
let storage = cloned_storage.clone();
|
|
async move {
|
|
match result {
|
|
Ok(cid) => match cid.collab_type {
|
|
CollabType::Document => {
|
|
let collab = storage
|
|
.get_encode_collab(GetCollabOrigin::Server, cid.clone().into(), false)
|
|
.await?;
|
|
|
|
Ok(Some(UnindexedCollab {
|
|
workspace_id: cid.workspace_id,
|
|
object_id: cid.object_id,
|
|
collab_type: cid.collab_type,
|
|
collab,
|
|
}))
|
|
},
|
|
// TODO(nathan): support other collab types
|
|
_ => Ok::<_, anyhow::Error>(None),
|
|
},
|
|
Err(e) => Err(e.into()),
|
|
}
|
|
}
|
|
})
|
|
.filter_map(|future| async {
|
|
match future.await {
|
|
Ok(Some(unindexed_collab)) => Some(Ok(unindexed_collab)),
|
|
Ok(None) => None,
|
|
Err(e) => Some(Err(e)),
|
|
}
|
|
})
|
|
.boxed()
|
|
}
|
|
|
|
async fn create_embeddings(
|
|
embedder: Embedder,
|
|
indexer_provider: &Arc<IndexerProvider>,
|
|
threads: Arc<ThreadPoolNoAbort>,
|
|
unindexed_records: Vec<UnindexedCollab>,
|
|
) -> Vec<EmbeddingRecord> {
|
|
unindexed_records
|
|
.into_par_iter()
|
|
.flat_map(|unindexed| {
|
|
let indexer = indexer_provider.indexer_for(&unindexed.collab_type)?;
|
|
let collab = Collab::new_with_source(
|
|
CollabOrigin::Empty,
|
|
&unindexed.object_id,
|
|
DataSource::DocStateV1(unindexed.collab.doc_state.into()),
|
|
vec![],
|
|
false,
|
|
)
|
|
.ok()?;
|
|
|
|
let chunks = indexer
|
|
.create_embedded_chunks_from_collab(&collab, embedder.model())
|
|
.ok()?;
|
|
if chunks.is_empty() {
|
|
trace!("[Embedding] {} has no embeddings", unindexed.object_id,);
|
|
return Some(EmbeddingRecord::empty(
|
|
unindexed.workspace_id,
|
|
unindexed.object_id,
|
|
unindexed.collab_type,
|
|
));
|
|
}
|
|
|
|
let result = threads.install(|| match indexer.embed(&embedder, chunks) {
|
|
Ok(embeddings) => embeddings.map(|embeddings| EmbeddingRecord {
|
|
workspace_id: unindexed.workspace_id,
|
|
object_id: unindexed.object_id,
|
|
collab_type: unindexed.collab_type,
|
|
tokens_used: embeddings.tokens_consumed,
|
|
contents: embeddings.params,
|
|
}),
|
|
Err(err) => {
|
|
error!("Failed to embed collab: {}", err);
|
|
None
|
|
},
|
|
});
|
|
|
|
if let Ok(Some(record)) = &result {
|
|
trace!(
|
|
"[Embedding] generate collab:{} embeddings, tokens used: {}",
|
|
record.object_id,
|
|
record.tokens_used
|
|
);
|
|
}
|
|
|
|
result.unwrap_or_else(|err| {
|
|
error!("Failed to spawn a task to index collab: {}", err);
|
|
None
|
|
})
|
|
})
|
|
.collect::<Vec<_>>()
|
|
}
|