22using kcenon::common::make_error;
23using kcenon::common::ok;
28constexpr int kInvalidConfiguration = -100;
29constexpr int kInstanceNotFound = -101;
30constexpr int kMigrationFailed = -102;
31constexpr int kTierNotAvailable = -103;
32constexpr int kIntegrityError = -104;
41 std::unique_ptr<storage_interface> warm_tier,
42 std::unique_ptr<storage_interface> cold_tier,
44 : hot_tier_(std::move(hot_tier)),
45 warm_tier_(std::move(warm_tier)),
46 cold_tier_(std::move(cold_tier)),
49 throw std::invalid_argument(
"hot_tier cannot be nullptr");
60 if (sop_uid.empty()) {
61 return make_error<std::monostate>(
62 kInvalidConfiguration,
"Missing SOP Instance UID",
"hsm_storage");
66 auto result = hot_tier_->store(dataset);
67 if (!result.is_ok()) {
72 std::unique_lock lock(mutex_);
81 auto tier = find_tier(sop_instance_uid);
82 if (!tier.has_value()) {
83 return make_error<core::dicom_dataset>(
85 "Instance not found: " + std::string(sop_instance_uid),
90 auto* storage = get_storage(*tier);
91 if (storage ==
nullptr) {
92 return make_error<core::dicom_dataset>(
93 kTierNotAvailable,
"Tier storage not available",
"hsm_storage");
96 auto result = storage->retrieve(sop_instance_uid);
97 if (!result.is_ok()) {
102 if (config_.track_access_time) {
103 std::unique_lock lock(mutex_);
104 update_access_time(sop_instance_uid);
112 auto tier = find_tier(sop_instance_uid);
113 if (!tier.has_value()) {
119 auto* storage = get_storage(*tier);
120 if (storage ==
nullptr) {
121 return make_error<std::monostate>(
122 kTierNotAvailable,
"Tier storage not available",
"hsm_storage");
125 auto result = storage->remove(sop_instance_uid);
126 if (!result.is_ok()) {
131 std::unique_lock lock(mutex_);
132 remove_metadata(sop_instance_uid);
138 std::shared_lock lock(mutex_);
139 return metadata_index_.contains(std::string(sop_instance_uid));
144 std::vector<core::dicom_dataset> combined_results;
147 std::vector<storage_interface*> tiers = {hot_tier_.get(), warm_tier_.get(),
150 for (
auto* tier : tiers) {
151 if (tier ==
nullptr) {
155 auto result = tier->find(query);
156 if (result.is_ok()) {
157 auto& datasets = result.value();
158 combined_results.insert(combined_results.end(), datasets.begin(),
163 return combined_results;
169 std::shared_lock lock(
mutex_);
175 std::set<std::string> studies;
176 std::set<std::string> series;
177 std::set<std::string> patients;
179 for (
auto* tier : tiers) {
180 if (tier ==
nullptr) {
184 auto tier_stats = tier->get_statistics();
185 stats.total_instances += tier_stats.total_instances;
186 stats.total_bytes += tier_stats.total_bytes;
191 if (!meta.study_instance_uid.empty()) {
192 studies.insert(meta.study_instance_uid);
194 if (!meta.series_instance_uid.empty()) {
195 series.insert(meta.series_instance_uid);
199 stats.studies_count = studies.size();
200 stats.series_count = series.size();
207 std::vector<std::pair<storage_tier, storage_interface*>> tiers = {
212 for (
const auto& [tier, storage] : tiers) {
213 if (storage ==
nullptr) {
217 auto result = storage->verify_integrity();
218 if (!result.is_ok()) {
219 return make_error<std::monostate>(
221 "Integrity check failed for " + std::string(
to_string(tier)) +
222 " tier: " + std::string(result.error().message),
228 std::shared_lock lock(mutex_);
229 for (
const auto& [
uid, meta] : metadata_index_) {
230 auto* storage = get_storage(meta.current_tier);
231 if (storage ==
nullptr) {
235 if (!storage->exists(
uid)) {
236 return make_error<std::monostate>(
238 "Metadata references non-existent instance: " +
uid,
251 -> std::optional<storage_tier> {
252 std::shared_lock lock(mutex_);
253 auto it = metadata_index_.find(std::string(sop_instance_uid));
254 if (it == metadata_index_.end()) {
257 return it->second.current_tier;
261 -> std::optional<tier_metadata> {
262 std::shared_lock lock(mutex_);
263 auto it = metadata_index_.find(std::string(sop_instance_uid));
264 if (it == metadata_index_.end()) {
273 auto current_tier_opt = find_tier(sop_instance_uid);
274 if (!current_tier_opt.has_value()) {
275 return make_error<std::monostate>(
277 "Instance not found: " + std::string(sop_instance_uid),
281 auto current_tier = *current_tier_opt;
282 if (current_tier == target_tier) {
287 return migrate_instance(sop_instance_uid, current_tier, target_tier);
292 -> std::vector<tier_metadata> {
293 std::vector<tier_metadata> candidates;
295 std::shared_lock lock(mutex_);
296 for (
const auto& [
uid, meta] : metadata_index_) {
297 if (meta.current_tier == from_tier &&
298 meta.should_migrate(config_.policy, to_tier)) {
299 candidates.push_back(meta);
304 std::sort(candidates.begin(), candidates.end(),
306 return a.stored_at < b.stored_at;
314 auto start_time = std::chrono::steady_clock::now();
321 for (
const auto& meta : candidates) {
323 config_.policy.max_instances_per_cycle) {
326 if (result.
bytes_migrated >= config_.policy.max_bytes_per_cycle) {
330 auto migrate_result = migrate_instance(
333 if (migrate_result.is_ok()) {
337 result.
failed_uids.push_back(meta.sop_instance_uid);
347 for (
const auto& meta : candidates) {
349 config_.policy.max_instances_per_cycle) {
352 if (result.
bytes_migrated >= config_.policy.max_bytes_per_cycle) {
356 auto migrate_result = migrate_instance(
359 if (migrate_result.is_ok()) {
363 result.
failed_uids.push_back(meta.sop_instance_uid);
369 if (!warm_tier_ && cold_tier_) {
373 for (
const auto& meta : candidates) {
375 config_.policy.max_instances_per_cycle) {
378 if (result.
bytes_migrated >= config_.policy.max_bytes_per_cycle) {
382 auto migrate_result = migrate_instance(
385 if (migrate_result.is_ok()) {
389 result.
failed_uids.push_back(meta.sop_instance_uid);
394 auto end_time = std::chrono::steady_clock::now();
395 result.
duration = std::chrono::duration_cast<std::chrono::milliseconds>(
396 end_time - start_time);
402 std::shared_lock lock(
mutex_);
407 std::unique_lock lock(
mutex_);
414 std::shared_lock lock(
mutex_);
416 std::set<std::string> hot_studies, hot_series;
417 std::set<std::string> warm_studies, warm_series;
418 std::set<std::string> cold_studies, cold_series;
421 switch (meta.current_tier) {
423 stats.hot.instance_count++;
424 stats.hot.total_bytes += meta.size_bytes;
425 if (!meta.study_instance_uid.empty()) {
426 hot_studies.insert(meta.study_instance_uid);
428 if (!meta.series_instance_uid.empty()) {
429 hot_series.insert(meta.series_instance_uid);
433 stats.warm.instance_count++;
434 stats.warm.total_bytes += meta.size_bytes;
435 if (!meta.study_instance_uid.empty()) {
436 warm_studies.insert(meta.study_instance_uid);
438 if (!meta.series_instance_uid.empty()) {
439 warm_series.insert(meta.series_instance_uid);
443 stats.cold.instance_count++;
444 stats.cold.total_bytes += meta.size_bytes;
445 if (!meta.study_instance_uid.empty()) {
446 cold_studies.insert(meta.study_instance_uid);
448 if (!meta.series_instance_uid.empty()) {
449 cold_series.insert(meta.series_instance_uid);
455 stats.hot.study_count = hot_studies.size();
456 stats.hot.series_count = hot_series.size();
457 stats.warm.study_count = warm_studies.size();
458 stats.warm.series_count = warm_series.size();
459 stats.cold.study_count = cold_studies.size();
460 stats.cold.series_count = cold_series.size();
467 return get_storage(tier);
475 -> std::optional<storage_tier> {
476 std::shared_lock lock(mutex_);
477 auto it = metadata_index_.find(std::string(sop_instance_uid));
478 if (it != metadata_index_.end()) {
479 return it->second.current_tier;
485 if (hot_tier_ && hot_tier_->exists(sop_instance_uid)) {
488 if (warm_tier_ && warm_tier_->exists(sop_instance_uid)) {
491 if (cold_tier_ && cold_tier_->exists(sop_instance_uid)) {
501 return hot_tier_.get();
503 return warm_tier_.get();
505 return cold_tier_.get();
513 std::string
uid(sop_instance_uid);
518 meta.
stored_at = std::chrono::system_clock::now();
534 it->second.last_accessed = std::chrono::system_clock::now();
544 auto* source = get_storage(from_tier);
545 auto* target = get_storage(to_tier);
547 if (source ==
nullptr) {
548 return make_error<std::monostate>(
550 "Source tier not available: " + std::string(
to_string(from_tier)),
553 if (target ==
nullptr) {
554 return make_error<std::monostate>(
556 "Target tier not available: " + std::string(
to_string(to_tier)),
561 auto retrieve_result = source->retrieve(
uid);
562 if (!retrieve_result.is_ok()) {
563 return make_error<std::monostate>(
565 "Failed to retrieve from source: " +
566 std::string(retrieve_result.error().message),
570 auto& dataset = retrieve_result.value();
573 auto store_result = target->store(dataset);
574 if (!store_result.is_ok()) {
575 return make_error<std::monostate>(
577 "Failed to store to target: " +
578 std::string(store_result.error().message),
583 if (config_.verify_after_migration) {
584 if (!target->exists(
uid)) {
585 return make_error<std::monostate>(
587 "Verification failed: instance not found in target tier",
593 if (config_.delete_after_migration) {
594 auto remove_result = source->remove(
uid);
601 std::unique_lock lock(mutex_);
602 auto it = metadata_index_.find(std::string(
uid));
603 if (it != metadata_index_.end()) {
604 it->second.current_tier = to_tier;
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
auto get_tier(std::string_view sop_instance_uid) const -> std::optional< storage_tier >
Get the current tier of an instance.
std::unique_ptr< storage_interface > warm_tier_
Warm tier storage backend (may be nullptr)
std::shared_mutex mutex_
Mutex for thread-safe access.
std::unordered_map< std::string, tier_metadata > metadata_index_
Tier metadata index (SOP Instance UID -> metadata)
auto migrate(std::string_view sop_instance_uid, storage_tier target_tier) -> VoidResult
Manually migrate an instance to a different tier.
void update_metadata(std::string_view sop_instance_uid, storage_tier tier, const core::dicom_dataset &dataset)
Update tier metadata after store/retrieve.
void update_access_time(std::string_view sop_instance_uid)
Update last access time for an instance.
auto get_tier_policy() const -> tier_policy
Get the current tier policy.
auto retrieve(std::string_view sop_instance_uid) -> Result< core::dicom_dataset > override
Retrieve a DICOM dataset by SOP Instance UID.
auto migrate_instance(std::string_view uid, storage_tier from_tier, storage_tier to_tier) -> VoidResult
Migrate a single instance between tiers.
void remove_metadata(std::string_view sop_instance_uid)
Remove tier metadata.
auto remove(std::string_view sop_instance_uid) -> VoidResult override
Remove a DICOM dataset from all tiers.
std::unique_ptr< storage_interface > hot_tier_
Hot tier storage backend.
auto get_tier_metadata(std::string_view sop_instance_uid) const -> std::optional< tier_metadata >
Get tier metadata for an instance.
auto run_migration_cycle() -> migration_result
Run a single migration cycle.
auto verify_integrity() -> VoidResult override
Verify storage integrity across all tiers.
auto find_tier(std::string_view sop_instance_uid) const -> std::optional< storage_tier >
Find which tier contains an instance.
hsm_storage(std::unique_ptr< storage_interface > hot_tier, std::unique_ptr< storage_interface > warm_tier, std::unique_ptr< storage_interface > cold_tier, const hsm_storage_config &config={})
Construct HSM storage with three tier backends.
void set_tier_policy(const tier_policy &policy)
Set the tier policy.
hsm_storage_config config_
HSM configuration.
auto exists(std::string_view sop_instance_uid) const -> bool override
Check if a DICOM instance exists in any tier.
auto get_storage(storage_tier tier) const -> storage_interface *
Get the storage backend for a tier.
auto get_tier_storage(storage_tier tier) const -> storage_interface *
Get the storage backend for a specific tier.
auto get_migration_candidates(storage_tier from_tier, storage_tier to_tier) const -> std::vector< tier_metadata >
Get instances eligible for migration.
auto get_hsm_statistics() const -> hsm_statistics
Get HSM-specific statistics.
auto find(const core::dicom_dataset &query) -> Result< std::vector< core::dicom_dataset > > override
Find DICOM datasets matching query criteria across all tiers.
auto store(const core::dicom_dataset &dataset) -> VoidResult override
Store a DICOM dataset to the hot tier.
std::unique_ptr< storage_interface > cold_tier_
Cold tier storage backend (may be nullptr)
auto get_statistics() const -> storage_statistics override
Get combined storage statistics from all tiers.
Compile-time constants for commonly used DICOM tags.
Hierarchical Storage Management (HSM) for multi-tier DICOM storage.
storage_tier
Storage tier classification.
@ hot
Hot tier - Recent, frequently accessed data (SSD/NVMe)
@ cold
Cold tier - Archive, rarely accessed data (S3/Glacier)
@ warm
Warm tier - Older, occasionally accessed data (HDD)
auto to_string(annotation_type type) -> std::string
Convert annotation_type to string.
Combined statistics for all HSM tiers.
Configuration for HSM storage.
tier_policy policy
Tier migration policy.
Result of a migration operation.
std::chrono::milliseconds duration
Duration of the migration operation.
std::size_t bytes_migrated
Total bytes migrated.
std::size_t instances_migrated
Number of instances successfully migrated.
std::vector< std::string > failed_uids
SOP Instance UIDs that failed to migrate.
Storage statistics structure.