-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement record based Crucible reference counting (#6805)
Crucible volumes are created by layering read-write regions over a hierarchy of read-only resources. Originally only a region snapshot could be used as a read-only resource for a volume. With the introduction of read-only regions (created during the region snapshot replacement process) this is no longer true! Read-only resources can be used by many volumes, and because of this they need to have a reference count so they can be deleted when they're not referenced anymore. The region_snapshot table uses a `volume_references` column, which counts how many uses there are. The region table does not have this column, and more over a simple integer works for reference counting but does not tell you _what_ volume that use is from. This can be determined (see omdb's validate volume references command) but it's information that is tossed out, as Nexus knows what volumes use what resources! Instead, record what read-only resources a volume uses in a new table. As part of the schema change to add the new `volume_resource_usage` table, a migration is included that will create the appropriate records for all region snapshots. In testing, a few bugs were found: the worst being that read-only regions did not have their read_only column set to true. This would be a problem if read-only regions are created, but they're currently only created during region snapshot replacement. To detect if any of these regions were created, find all regions that were allocated for a snapshot volume: SELECT id FROM region WHERE volume_id IN (SELECT volume_id FROM snapshot); A similar bug was found in the simulated Crucible agent. This commit also reverts #6728, enabling region snapshot replacement again - it was disabled due to a lack of read-only region reference counting, so it can be enabled once again.
- Loading branch information
Showing
42 changed files
with
5,963 additions
and
1,519 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
use super::impl_enum_type; | ||
use crate::schema::volume_resource_usage; | ||
use uuid::Uuid; | ||
|
||
impl_enum_type!( | ||
#[derive(SqlType, Debug, QueryId)] | ||
#[diesel( | ||
postgres_type(name = "volume_resource_usage_type", schema = "public") | ||
)] | ||
pub struct VolumeResourceUsageTypeEnum; | ||
|
||
#[derive(Copy, Clone, Debug, AsExpression, FromSqlRow, PartialEq, Eq, Hash)] | ||
#[diesel(sql_type = VolumeResourceUsageTypeEnum)] | ||
pub enum VolumeResourceUsageType; | ||
|
||
ReadOnlyRegion => b"read_only_region" | ||
RegionSnapshot => b"region_snapshot" | ||
); | ||
|
||
/// Crucible volumes are created by layering read-write regions over a hierarchy | ||
/// of read-only resources. Originally only a region snapshot could be used as a | ||
/// read-only resource for a volume. With the introduction of read-only regions | ||
/// (created during the region snapshot replacement process) this is no longer | ||
/// true. | ||
/// | ||
/// Read-only resources can be used by many volumes, and because of this they | ||
/// need to have a reference count so they can be deleted when they're not | ||
/// referenced anymore. The region_snapshot table used a `volume_references` | ||
/// column, which counts how many uses there are. The region table does not have | ||
/// this column, and more over a simple integer works for reference counting but | ||
/// does not tell you _what_ volume that use is from. This can be determined | ||
/// (see omdb's validate volume references command) but it's information that is | ||
/// tossed out, as Nexus knows what volumes use what resources! Instead of | ||
/// throwing away that knowledge and only incrementing and decrementing an | ||
/// integer, record what read-only resources a volume uses in this table. | ||
/// | ||
/// Note: users should not use this object directly, and instead use the | ||
/// [`VolumeResourceUsage`] enum, which is type-safe and will convert to and | ||
/// from a [`VolumeResourceUsageRecord`] when interacting with the DB. | ||
#[derive( | ||
Queryable, Insertable, Debug, Clone, Selectable, PartialEq, Eq, Hash, | ||
)] | ||
#[diesel(table_name = volume_resource_usage)] | ||
pub struct VolumeResourceUsageRecord { | ||
pub usage_id: Uuid, | ||
|
||
pub volume_id: Uuid, | ||
|
||
pub usage_type: VolumeResourceUsageType, | ||
|
||
pub region_id: Option<Uuid>, | ||
|
||
pub region_snapshot_dataset_id: Option<Uuid>, | ||
pub region_snapshot_region_id: Option<Uuid>, | ||
pub region_snapshot_snapshot_id: Option<Uuid>, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub enum VolumeResourceUsage { | ||
ReadOnlyRegion { region_id: Uuid }, | ||
|
||
RegionSnapshot { dataset_id: Uuid, region_id: Uuid, snapshot_id: Uuid }, | ||
} | ||
|
||
impl VolumeResourceUsageRecord { | ||
pub fn new(volume_id: Uuid, usage: VolumeResourceUsage) -> Self { | ||
match usage { | ||
VolumeResourceUsage::ReadOnlyRegion { region_id } => { | ||
VolumeResourceUsageRecord { | ||
usage_id: Uuid::new_v4(), | ||
volume_id, | ||
usage_type: VolumeResourceUsageType::ReadOnlyRegion, | ||
|
||
region_id: Some(region_id), | ||
|
||
region_snapshot_dataset_id: None, | ||
region_snapshot_region_id: None, | ||
region_snapshot_snapshot_id: None, | ||
} | ||
} | ||
|
||
VolumeResourceUsage::RegionSnapshot { | ||
dataset_id, | ||
region_id, | ||
snapshot_id, | ||
} => VolumeResourceUsageRecord { | ||
usage_id: Uuid::new_v4(), | ||
volume_id, | ||
usage_type: VolumeResourceUsageType::RegionSnapshot, | ||
|
||
region_id: None, | ||
|
||
region_snapshot_dataset_id: Some(dataset_id), | ||
region_snapshot_region_id: Some(region_id), | ||
region_snapshot_snapshot_id: Some(snapshot_id), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl TryFrom<VolumeResourceUsageRecord> for VolumeResourceUsage { | ||
type Error = String; | ||
|
||
fn try_from( | ||
record: VolumeResourceUsageRecord, | ||
) -> Result<VolumeResourceUsage, String> { | ||
match record.usage_type { | ||
VolumeResourceUsageType::ReadOnlyRegion => { | ||
let Some(region_id) = record.region_id else { | ||
return Err("valid read-only region usage record".into()); | ||
}; | ||
|
||
Ok(VolumeResourceUsage::ReadOnlyRegion { region_id }) | ||
} | ||
|
||
VolumeResourceUsageType::RegionSnapshot => { | ||
let Some(dataset_id) = record.region_snapshot_dataset_id else { | ||
return Err("valid region snapshot usage record".into()); | ||
}; | ||
|
||
let Some(region_id) = record.region_snapshot_region_id else { | ||
return Err("valid region snapshot usage record".into()); | ||
}; | ||
|
||
let Some(snapshot_id) = record.region_snapshot_snapshot_id | ||
else { | ||
return Err("valid region snapshot usage record".into()); | ||
}; | ||
|
||
Ok(VolumeResourceUsage::RegionSnapshot { | ||
dataset_id, | ||
region_id, | ||
snapshot_id, | ||
}) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.