2024-03-09 07:09:15 +01:00
|
|
|
use crate::error::Error;
|
|
|
|
use crate::kani;
|
|
|
|
use crate::state::*;
|
20240613 performance improvements (#2844)
Thanks to @Seba-T's work with Orca, we were able to identify a number of performance issues in certain high load conditions.
This commit contains fixes for the following issues
* Unbounded Memory Growth - due to how ARCache works, to maintain temporal consistency it must retain copies of keys (not values) in a special data set for tracking. The Filter Resolve Cache was using unresolved filters as keys. This caused memory explosions when refint or memberof were updating a group with a large number of members because they would emit a query with hundreds of filter terms that would only be used once and never again, causing the ARCache haunted set to grow without bound. To limit this, we no longer cache large/complex queries for resolution, and in future we may implement some other methods to reduce this like sha256/hmac of the queries.
* When creating a new account, dyngroups would be engaged to add the account as a member due to the matching scope. However the change to the dyngroup was triggering an update of all the dyngroups *members* related memberof attributes. This would mean that adding an account would trigger every other account to be loaded an updated.
* When memberof would iterate over leaf entries and update them one at a time. This mean a large number of small fragmented queries in the case of a lot of leaf entries being updated. Now leaf entries are updated in a single stripe once groups are stabilised.
* Member of would always trigger it's members to always update. Instead, we should only update members where a difference is observed, or all members if the group's memberof itself has changed since this needs to propogate to all leaf entries. This significantly reduces the amount of writes and operations to examine the changed member of set.
* Referential integrity would examine all reference uuids on entries for validity rather than just the reference uuids that were altered within the transaction. This change means that only uuids that were *added* are validated during an operation.
* During async write backs (delayed actions) these were performed one at a time. Instead, when possible this should be done in a single transaction as the write transaction caches all writes in memory until the commit meaning that by batching we reduce overall latency.
* In the server there can only be one write transaction and many readers. These are guarded by tokio semaphores that act as fair queues - first in gets the lock next. Due to the design of the server readers would be blocked on the *database* semaphore, and writers would block on the write semaphore and THEN the database semaphore. This arrangement was creating a situation which unfairly advantaged readers over writers, as any write would first have to become the head of it's queue, and then compete with all readers to access a db transaction. Instead, we now have a reader semaphore with size threads minus 1, clamped at a minimum of 1. This means that provided there are two or more threads, then a writer will *always* have a database handle available, and readers will pre-queue with each other before queueing on the db ticket. If there is only one thread, then writes and reads will alternate between each other fairly.
2024-06-20 04:50:00 +02:00
|
|
|
use std::collections::VecDeque;
|
|
|
|
|
|
|
|
use std::sync::atomic::{AtomicU32, Ordering};
|
|
|
|
use tokio::sync::Mutex;
|
2024-03-09 07:09:15 +01:00
|
|
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
async fn apply_flags(client: Arc<kani::KanidmOrcaClient>, flags: &[Flag]) -> Result<(), Error> {
|
|
|
|
for flag in flags {
|
|
|
|
match flag {
|
|
|
|
Flag::DisableAllPersonsMFAPolicy => client.disable_mfa_requirement().await?,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn preflight_person(
|
|
|
|
client: Arc<kani::KanidmOrcaClient>,
|
|
|
|
person: Person,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
debug!(?person);
|
|
|
|
|
|
|
|
if client.person_exists(&person.username).await? {
|
|
|
|
// Do nothing? Do we need to reset them later?
|
2024-06-13 01:48:49 +02:00
|
|
|
return Ok(());
|
2024-03-09 07:09:15 +01:00
|
|
|
} else {
|
|
|
|
client
|
|
|
|
.person_create(&person.username, &person.display_name)
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
match &person.credential {
|
|
|
|
Credential::Password { plain } => {
|
|
|
|
client
|
2024-04-17 01:35:16 +02:00
|
|
|
.person_set_primary_password_only(&person.username, plain)
|
2024-03-09 07:09:15 +01:00
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-17 01:35:16 +02:00
|
|
|
// For each role we are part of, did we have other permissions required to fufil that?
|
|
|
|
for role in &person.roles {
|
|
|
|
if let Some(need_groups) = role.requires_membership_to() {
|
|
|
|
for group_name in need_groups {
|
|
|
|
client
|
2024-04-23 02:30:38 +02:00
|
|
|
.group_add_members(group_name, &[person.username.as_str()])
|
2024-04-17 01:35:16 +02:00
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn preflight_group(client: Arc<kani::KanidmOrcaClient>, group: Group) -> Result<(), Error> {
|
|
|
|
if client.group_exists(group.name.as_str()).await? {
|
|
|
|
// Do nothing? Do we need to reset them later?
|
|
|
|
} else {
|
|
|
|
client.group_create(group.name.as_str()).await?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We can submit all the members in one go.
|
|
|
|
|
|
|
|
let members = group.members.iter().map(|s| s.as_str()).collect::<Vec<_>>();
|
|
|
|
|
|
|
|
client
|
|
|
|
.group_set_members(group.name.as_str(), members.as_slice())
|
|
|
|
.await?;
|
|
|
|
|
2024-03-09 07:09:15 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn preflight(state: State) -> Result<(), Error> {
|
|
|
|
// Get the admin client.
|
|
|
|
let client = Arc::new(kani::KanidmOrcaClient::new(&state.profile).await?);
|
|
|
|
|
|
|
|
// Apply any flags if they exist.
|
|
|
|
apply_flags(client.clone(), state.preflight_flags.as_slice()).await?;
|
|
|
|
|
2024-06-13 01:48:49 +02:00
|
|
|
let state_persons_len = state.persons.len();
|
20240613 performance improvements (#2844)
Thanks to @Seba-T's work with Orca, we were able to identify a number of performance issues in certain high load conditions.
This commit contains fixes for the following issues
* Unbounded Memory Growth - due to how ARCache works, to maintain temporal consistency it must retain copies of keys (not values) in a special data set for tracking. The Filter Resolve Cache was using unresolved filters as keys. This caused memory explosions when refint or memberof were updating a group with a large number of members because they would emit a query with hundreds of filter terms that would only be used once and never again, causing the ARCache haunted set to grow without bound. To limit this, we no longer cache large/complex queries for resolution, and in future we may implement some other methods to reduce this like sha256/hmac of the queries.
* When creating a new account, dyngroups would be engaged to add the account as a member due to the matching scope. However the change to the dyngroup was triggering an update of all the dyngroups *members* related memberof attributes. This would mean that adding an account would trigger every other account to be loaded an updated.
* When memberof would iterate over leaf entries and update them one at a time. This mean a large number of small fragmented queries in the case of a lot of leaf entries being updated. Now leaf entries are updated in a single stripe once groups are stabilised.
* Member of would always trigger it's members to always update. Instead, we should only update members where a difference is observed, or all members if the group's memberof itself has changed since this needs to propogate to all leaf entries. This significantly reduces the amount of writes and operations to examine the changed member of set.
* Referential integrity would examine all reference uuids on entries for validity rather than just the reference uuids that were altered within the transaction. This change means that only uuids that were *added* are validated during an operation.
* During async write backs (delayed actions) these were performed one at a time. Instead, when possible this should be done in a single transaction as the write transaction caches all writes in memory until the commit meaning that by batching we reduce overall latency.
* In the server there can only be one write transaction and many readers. These are guarded by tokio semaphores that act as fair queues - first in gets the lock next. Due to the design of the server readers would be blocked on the *database* semaphore, and writers would block on the write semaphore and THEN the database semaphore. This arrangement was creating a situation which unfairly advantaged readers over writers, as any write would first have to become the head of it's queue, and then compete with all readers to access a db transaction. Instead, we now have a reader semaphore with size threads minus 1, clamped at a minimum of 1. This means that provided there are two or more threads, then a writer will *always* have a database handle available, and readers will pre-queue with each other before queueing on the db ticket. If there is only one thread, then writes and reads will alternate between each other fairly.
2024-06-20 04:50:00 +02:00
|
|
|
let mut tasks = VecDeque::with_capacity(state_persons_len);
|
2024-04-17 01:35:16 +02:00
|
|
|
|
|
|
|
// Create persons.
|
2024-03-09 07:09:15 +01:00
|
|
|
for person in state.persons.into_iter() {
|
|
|
|
let c = client.clone();
|
20240613 performance improvements (#2844)
Thanks to @Seba-T's work with Orca, we were able to identify a number of performance issues in certain high load conditions.
This commit contains fixes for the following issues
* Unbounded Memory Growth - due to how ARCache works, to maintain temporal consistency it must retain copies of keys (not values) in a special data set for tracking. The Filter Resolve Cache was using unresolved filters as keys. This caused memory explosions when refint or memberof were updating a group with a large number of members because they would emit a query with hundreds of filter terms that would only be used once and never again, causing the ARCache haunted set to grow without bound. To limit this, we no longer cache large/complex queries for resolution, and in future we may implement some other methods to reduce this like sha256/hmac of the queries.
* When creating a new account, dyngroups would be engaged to add the account as a member due to the matching scope. However the change to the dyngroup was triggering an update of all the dyngroups *members* related memberof attributes. This would mean that adding an account would trigger every other account to be loaded an updated.
* When memberof would iterate over leaf entries and update them one at a time. This mean a large number of small fragmented queries in the case of a lot of leaf entries being updated. Now leaf entries are updated in a single stripe once groups are stabilised.
* Member of would always trigger it's members to always update. Instead, we should only update members where a difference is observed, or all members if the group's memberof itself has changed since this needs to propogate to all leaf entries. This significantly reduces the amount of writes and operations to examine the changed member of set.
* Referential integrity would examine all reference uuids on entries for validity rather than just the reference uuids that were altered within the transaction. This change means that only uuids that were *added* are validated during an operation.
* During async write backs (delayed actions) these were performed one at a time. Instead, when possible this should be done in a single transaction as the write transaction caches all writes in memory until the commit meaning that by batching we reduce overall latency.
* In the server there can only be one write transaction and many readers. These are guarded by tokio semaphores that act as fair queues - first in gets the lock next. Due to the design of the server readers would be blocked on the *database* semaphore, and writers would block on the write semaphore and THEN the database semaphore. This arrangement was creating a situation which unfairly advantaged readers over writers, as any write would first have to become the head of it's queue, and then compete with all readers to access a db transaction. Instead, we now have a reader semaphore with size threads minus 1, clamped at a minimum of 1. This means that provided there are two or more threads, then a writer will *always* have a database handle available, and readers will pre-queue with each other before queueing on the db ticket. If there is only one thread, then writes and reads will alternate between each other fairly.
2024-06-20 04:50:00 +02:00
|
|
|
// While writes are single threaded in Kanidm, searches (such as .exists)
|
|
|
|
// and credential updates are concurrent / parallel. So these parts can be
|
|
|
|
// called in parallel, so we divide up into workers.
|
|
|
|
tasks.push_back(preflight_person(c, person))
|
2024-03-09 07:09:15 +01:00
|
|
|
}
|
|
|
|
|
20240613 performance improvements (#2844)
Thanks to @Seba-T's work with Orca, we were able to identify a number of performance issues in certain high load conditions.
This commit contains fixes for the following issues
* Unbounded Memory Growth - due to how ARCache works, to maintain temporal consistency it must retain copies of keys (not values) in a special data set for tracking. The Filter Resolve Cache was using unresolved filters as keys. This caused memory explosions when refint or memberof were updating a group with a large number of members because they would emit a query with hundreds of filter terms that would only be used once and never again, causing the ARCache haunted set to grow without bound. To limit this, we no longer cache large/complex queries for resolution, and in future we may implement some other methods to reduce this like sha256/hmac of the queries.
* When creating a new account, dyngroups would be engaged to add the account as a member due to the matching scope. However the change to the dyngroup was triggering an update of all the dyngroups *members* related memberof attributes. This would mean that adding an account would trigger every other account to be loaded an updated.
* When memberof would iterate over leaf entries and update them one at a time. This mean a large number of small fragmented queries in the case of a lot of leaf entries being updated. Now leaf entries are updated in a single stripe once groups are stabilised.
* Member of would always trigger it's members to always update. Instead, we should only update members where a difference is observed, or all members if the group's memberof itself has changed since this needs to propogate to all leaf entries. This significantly reduces the amount of writes and operations to examine the changed member of set.
* Referential integrity would examine all reference uuids on entries for validity rather than just the reference uuids that were altered within the transaction. This change means that only uuids that were *added* are validated during an operation.
* During async write backs (delayed actions) these were performed one at a time. Instead, when possible this should be done in a single transaction as the write transaction caches all writes in memory until the commit meaning that by batching we reduce overall latency.
* In the server there can only be one write transaction and many readers. These are guarded by tokio semaphores that act as fair queues - first in gets the lock next. Due to the design of the server readers would be blocked on the *database* semaphore, and writers would block on the write semaphore and THEN the database semaphore. This arrangement was creating a situation which unfairly advantaged readers over writers, as any write would first have to become the head of it's queue, and then compete with all readers to access a db transaction. Instead, we now have a reader semaphore with size threads minus 1, clamped at a minimum of 1. This means that provided there are two or more threads, then a writer will *always* have a database handle available, and readers will pre-queue with each other before queueing on the db ticket. If there is only one thread, then writes and reads will alternate between each other fairly.
2024-06-20 04:50:00 +02:00
|
|
|
let tasks = Arc::new(Mutex::new(tasks));
|
|
|
|
let counter = Arc::new(AtomicU32::new(0));
|
|
|
|
let par = std::thread::available_parallelism().unwrap();
|
|
|
|
|
|
|
|
let handles: Vec<_> = (0..par.into())
|
|
|
|
.map(|_| {
|
|
|
|
let tasks_q = tasks.clone();
|
|
|
|
let counter_c = counter.clone();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
loop {
|
|
|
|
let maybe_task = async {
|
|
|
|
let mut guard = tasks_q.lock().await;
|
|
|
|
guard.pop_front()
|
|
|
|
}
|
|
|
|
.await;
|
|
|
|
|
|
|
|
if let Some(t) = maybe_task {
|
|
|
|
let _ = t.await;
|
|
|
|
let was = counter_c.fetch_add(1, Ordering::Relaxed);
|
|
|
|
if was % 1000 == 999 {
|
|
|
|
let order = was + 1;
|
|
|
|
eprint!("{}", order);
|
|
|
|
} else if was % 100 == 99 {
|
|
|
|
// Since we just added one, this just rolled over.
|
|
|
|
eprint!(".");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// queue drained.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
for handle in handles {
|
|
|
|
handle.await.map_err(|tokio_err| {
|
|
|
|
error!(?tokio_err, "Failed to join task");
|
|
|
|
Error::Tokio
|
|
|
|
})?;
|
|
|
|
}
|
2024-06-13 01:48:49 +02:00
|
|
|
|
20240613 performance improvements (#2844)
Thanks to @Seba-T's work with Orca, we were able to identify a number of performance issues in certain high load conditions.
This commit contains fixes for the following issues
* Unbounded Memory Growth - due to how ARCache works, to maintain temporal consistency it must retain copies of keys (not values) in a special data set for tracking. The Filter Resolve Cache was using unresolved filters as keys. This caused memory explosions when refint or memberof were updating a group with a large number of members because they would emit a query with hundreds of filter terms that would only be used once and never again, causing the ARCache haunted set to grow without bound. To limit this, we no longer cache large/complex queries for resolution, and in future we may implement some other methods to reduce this like sha256/hmac of the queries.
* When creating a new account, dyngroups would be engaged to add the account as a member due to the matching scope. However the change to the dyngroup was triggering an update of all the dyngroups *members* related memberof attributes. This would mean that adding an account would trigger every other account to be loaded an updated.
* When memberof would iterate over leaf entries and update them one at a time. This mean a large number of small fragmented queries in the case of a lot of leaf entries being updated. Now leaf entries are updated in a single stripe once groups are stabilised.
* Member of would always trigger it's members to always update. Instead, we should only update members where a difference is observed, or all members if the group's memberof itself has changed since this needs to propogate to all leaf entries. This significantly reduces the amount of writes and operations to examine the changed member of set.
* Referential integrity would examine all reference uuids on entries for validity rather than just the reference uuids that were altered within the transaction. This change means that only uuids that were *added* are validated during an operation.
* During async write backs (delayed actions) these were performed one at a time. Instead, when possible this should be done in a single transaction as the write transaction caches all writes in memory until the commit meaning that by batching we reduce overall latency.
* In the server there can only be one write transaction and many readers. These are guarded by tokio semaphores that act as fair queues - first in gets the lock next. Due to the design of the server readers would be blocked on the *database* semaphore, and writers would block on the write semaphore and THEN the database semaphore. This arrangement was creating a situation which unfairly advantaged readers over writers, as any write would first have to become the head of it's queue, and then compete with all readers to access a db transaction. Instead, we now have a reader semaphore with size threads minus 1, clamped at a minimum of 1. This means that provided there are two or more threads, then a writer will *always* have a database handle available, and readers will pre-queue with each other before queueing on the db ticket. If there is only one thread, then writes and reads will alternate between each other fairly.
2024-06-20 04:50:00 +02:00
|
|
|
eprintln!("done");
|
2024-03-09 07:09:15 +01:00
|
|
|
|
|
|
|
// Create groups.
|
20240613 performance improvements (#2844)
Thanks to @Seba-T's work with Orca, we were able to identify a number of performance issues in certain high load conditions.
This commit contains fixes for the following issues
* Unbounded Memory Growth - due to how ARCache works, to maintain temporal consistency it must retain copies of keys (not values) in a special data set for tracking. The Filter Resolve Cache was using unresolved filters as keys. This caused memory explosions when refint or memberof were updating a group with a large number of members because they would emit a query with hundreds of filter terms that would only be used once and never again, causing the ARCache haunted set to grow without bound. To limit this, we no longer cache large/complex queries for resolution, and in future we may implement some other methods to reduce this like sha256/hmac of the queries.
* When creating a new account, dyngroups would be engaged to add the account as a member due to the matching scope. However the change to the dyngroup was triggering an update of all the dyngroups *members* related memberof attributes. This would mean that adding an account would trigger every other account to be loaded an updated.
* When memberof would iterate over leaf entries and update them one at a time. This mean a large number of small fragmented queries in the case of a lot of leaf entries being updated. Now leaf entries are updated in a single stripe once groups are stabilised.
* Member of would always trigger it's members to always update. Instead, we should only update members where a difference is observed, or all members if the group's memberof itself has changed since this needs to propogate to all leaf entries. This significantly reduces the amount of writes and operations to examine the changed member of set.
* Referential integrity would examine all reference uuids on entries for validity rather than just the reference uuids that were altered within the transaction. This change means that only uuids that were *added* are validated during an operation.
* During async write backs (delayed actions) these were performed one at a time. Instead, when possible this should be done in a single transaction as the write transaction caches all writes in memory until the commit meaning that by batching we reduce overall latency.
* In the server there can only be one write transaction and many readers. These are guarded by tokio semaphores that act as fair queues - first in gets the lock next. Due to the design of the server readers would be blocked on the *database* semaphore, and writers would block on the write semaphore and THEN the database semaphore. This arrangement was creating a situation which unfairly advantaged readers over writers, as any write would first have to become the head of it's queue, and then compete with all readers to access a db transaction. Instead, we now have a reader semaphore with size threads minus 1, clamped at a minimum of 1. This means that provided there are two or more threads, then a writer will *always* have a database handle available, and readers will pre-queue with each other before queueing on the db ticket. If there is only one thread, then writes and reads will alternate between each other fairly.
2024-06-20 04:50:00 +02:00
|
|
|
let counter = Arc::new(AtomicU32::new(0));
|
2024-04-17 01:35:16 +02:00
|
|
|
let mut tasks = Vec::with_capacity(state.groups.len());
|
|
|
|
|
|
|
|
for group in state.groups.into_iter() {
|
|
|
|
let c = client.clone();
|
2024-06-13 01:48:49 +02:00
|
|
|
// Write operations are single threaded in Kanidm, so we don't need to attempt
|
|
|
|
// to parallelise that here.
|
|
|
|
// tasks.push(tokio::spawn(preflight_group(c, group)))
|
|
|
|
tasks.push(preflight_group(c, group))
|
2024-04-17 01:35:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for task in tasks {
|
2024-06-13 01:48:49 +02:00
|
|
|
task.await?;
|
20240613 performance improvements (#2844)
Thanks to @Seba-T's work with Orca, we were able to identify a number of performance issues in certain high load conditions.
This commit contains fixes for the following issues
* Unbounded Memory Growth - due to how ARCache works, to maintain temporal consistency it must retain copies of keys (not values) in a special data set for tracking. The Filter Resolve Cache was using unresolved filters as keys. This caused memory explosions when refint or memberof were updating a group with a large number of members because they would emit a query with hundreds of filter terms that would only be used once and never again, causing the ARCache haunted set to grow without bound. To limit this, we no longer cache large/complex queries for resolution, and in future we may implement some other methods to reduce this like sha256/hmac of the queries.
* When creating a new account, dyngroups would be engaged to add the account as a member due to the matching scope. However the change to the dyngroup was triggering an update of all the dyngroups *members* related memberof attributes. This would mean that adding an account would trigger every other account to be loaded an updated.
* When memberof would iterate over leaf entries and update them one at a time. This mean a large number of small fragmented queries in the case of a lot of leaf entries being updated. Now leaf entries are updated in a single stripe once groups are stabilised.
* Member of would always trigger it's members to always update. Instead, we should only update members where a difference is observed, or all members if the group's memberof itself has changed since this needs to propogate to all leaf entries. This significantly reduces the amount of writes and operations to examine the changed member of set.
* Referential integrity would examine all reference uuids on entries for validity rather than just the reference uuids that were altered within the transaction. This change means that only uuids that were *added* are validated during an operation.
* During async write backs (delayed actions) these were performed one at a time. Instead, when possible this should be done in a single transaction as the write transaction caches all writes in memory until the commit meaning that by batching we reduce overall latency.
* In the server there can only be one write transaction and many readers. These are guarded by tokio semaphores that act as fair queues - first in gets the lock next. Due to the design of the server readers would be blocked on the *database* semaphore, and writers would block on the write semaphore and THEN the database semaphore. This arrangement was creating a situation which unfairly advantaged readers over writers, as any write would first have to become the head of it's queue, and then compete with all readers to access a db transaction. Instead, we now have a reader semaphore with size threads minus 1, clamped at a minimum of 1. This means that provided there are two or more threads, then a writer will *always* have a database handle available, and readers will pre-queue with each other before queueing on the db ticket. If there is only one thread, then writes and reads will alternate between each other fairly.
2024-06-20 04:50:00 +02:00
|
|
|
let was = counter.fetch_add(1, Ordering::Relaxed);
|
|
|
|
if was % 1000 == 999 {
|
|
|
|
let order = was + 1;
|
|
|
|
eprint!("{}", order);
|
|
|
|
} else if was % 100 == 99 {
|
|
|
|
// Since we just added one, this just rolled over.
|
|
|
|
eprint!(".");
|
|
|
|
}
|
2024-04-17 01:35:16 +02:00
|
|
|
}
|
2024-03-09 07:09:15 +01:00
|
|
|
|
20240613 performance improvements (#2844)
Thanks to @Seba-T's work with Orca, we were able to identify a number of performance issues in certain high load conditions.
This commit contains fixes for the following issues
* Unbounded Memory Growth - due to how ARCache works, to maintain temporal consistency it must retain copies of keys (not values) in a special data set for tracking. The Filter Resolve Cache was using unresolved filters as keys. This caused memory explosions when refint or memberof were updating a group with a large number of members because they would emit a query with hundreds of filter terms that would only be used once and never again, causing the ARCache haunted set to grow without bound. To limit this, we no longer cache large/complex queries for resolution, and in future we may implement some other methods to reduce this like sha256/hmac of the queries.
* When creating a new account, dyngroups would be engaged to add the account as a member due to the matching scope. However the change to the dyngroup was triggering an update of all the dyngroups *members* related memberof attributes. This would mean that adding an account would trigger every other account to be loaded an updated.
* When memberof would iterate over leaf entries and update them one at a time. This mean a large number of small fragmented queries in the case of a lot of leaf entries being updated. Now leaf entries are updated in a single stripe once groups are stabilised.
* Member of would always trigger it's members to always update. Instead, we should only update members where a difference is observed, or all members if the group's memberof itself has changed since this needs to propogate to all leaf entries. This significantly reduces the amount of writes and operations to examine the changed member of set.
* Referential integrity would examine all reference uuids on entries for validity rather than just the reference uuids that were altered within the transaction. This change means that only uuids that were *added* are validated during an operation.
* During async write backs (delayed actions) these were performed one at a time. Instead, when possible this should be done in a single transaction as the write transaction caches all writes in memory until the commit meaning that by batching we reduce overall latency.
* In the server there can only be one write transaction and many readers. These are guarded by tokio semaphores that act as fair queues - first in gets the lock next. Due to the design of the server readers would be blocked on the *database* semaphore, and writers would block on the write semaphore and THEN the database semaphore. This arrangement was creating a situation which unfairly advantaged readers over writers, as any write would first have to become the head of it's queue, and then compete with all readers to access a db transaction. Instead, we now have a reader semaphore with size threads minus 1, clamped at a minimum of 1. This means that provided there are two or more threads, then a writer will *always* have a database handle available, and readers will pre-queue with each other before queueing on the db ticket. If there is only one thread, then writes and reads will alternate between each other fairly.
2024-06-20 04:50:00 +02:00
|
|
|
eprintln!("done");
|
|
|
|
|
2024-03-09 07:09:15 +01:00
|
|
|
// Create integrations.
|
|
|
|
|
|
|
|
info!("Ready to 🛫");
|
|
|
|
Ok(())
|
|
|
|
}
|