Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ICP-archive): migrate icp archive to stable structures #3910

Open
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

maciejdfinity
Copy link
Contributor

@maciejdfinity maciejdfinity commented Feb 11, 2025

The archive blocks are stored in stable log. Since migration requires only ~24B instructions, the whole migration is performed in post_upgrade. The archive has a new argument that can be used to specify a new archive size limit. The argument can be skipped.

@maciejdfinity maciejdfinity changed the title migrate icp archive to stable structures feat(ICP-archive): migrate icp archive to stable structures Feb 11, 2025
@github-actions github-actions bot added the feat label Feb 11, 2025
@maciejdfinity maciejdfinity marked this pull request as ready for review February 17, 2025 10:39
@maciejdfinity maciejdfinity requested a review from a team as a code owner February 17, 2025 10:39
Copy link
Member

@mbjorkqvist mbjorkqvist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @maciejdfinity!

Comment on lines 35 to 40
const MAX_MEMORY_SIZE_BYTES_MEMORY_ID: MemoryId = MemoryId::new(0);
const BLOCK_HEIGHT_OFFSET_MEMORY_ID: MemoryId = MemoryId::new(1);
const TOTAL_BLOCK_SIZE_MEMORY_ID: MemoryId = MemoryId::new(2);
const LEDGER_CANISTER_ID_MEMORY_ID: MemoryId = MemoryId::new(3);
const BLOCK_LOG_INDEX_MEMORY_ID: MemoryId = MemoryId::new(4);
const BLOCK_LOG_DATA_MEMORY_ID: MemoryId = MemoryId::new(5);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the ICRC archive, we have a single ArchiveConfig in a StableCell. What's the rationale for having these as separate MemoryIds, and additionally, for the thread_local! _CACHE RefCells? Is it e.g., easier to add new fields if they are all separate cells? What about the cache, is that faster/more efficient in terms of instructions, or otherwise simplifying things?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rationale behind separate cells was that I didn't want to copy the whole state to read one integer. I am not sure how expensive that would be, since we only have 4 fields (although, the canister id is slightly larger). Maybe there is some better way, where we keep some local copy of the state and return a reference... As for the cache, I am thinking the since TOTAL_BLOCK_SIZE is modified often, the cache might not be that beneficial. However, the other fields are almost never changed, so it should be helpful there. I could run some simple benchmarks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the explanation! I suppose we would use a similar approach for the ledger state if we move that to stable structures also (to eventually be able to get rid of the pre- and post-upgrade serialization&deserialization dance? I tried to see what is done in other canisters, and I saw that in the governance canister, there is a State struct that is used to manage the different data structures stored in individual memories in stable memory. Would it make sense to have something like that for the archive also (and later for the ledger)? It's maybe not so critical for the archive, since as you point out, we don't have so many fields, but on the other hand, maybe here it would be easier to come up with a design that we could later reuse for the ledger? On the other hand, from skimming through the governance canister, it seems they do perform non-trivial initialization in the post-upgrade, so if we want to get rid of that, maybe their approach anyway wouldn't work for us?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointer! I will have a closer look!

Copy link
Contributor Author

@maciejdfinity maciejdfinity Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to have ARCHIVE_STATE_CACHE and access it similarly as we do in the ICRC ledger, i.e. Access::with_archive_state(|state| {...}) and Access::with_archive_state_mut. The mut version would also need to update the underlying stable cell that contains the whole state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or not use the cache and read the state directly. I guess if the state is large, reading the whole state might be expensive, but I don't have the exact numbers.

Comment on lines +89 to +95
if max_memory_size_bytes < total_block_size() {
ic_cdk::trap(&format!(
"Cannot set max_memory_size_bytes to {}, because it is lower than total_block_size {}.",
max_memory_size_bytes,
total_block_size()
));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to have some upper limit on the max memory size? It's probably less critical for the ICP ledger archive compared to the ICRC ledger archive in the sense that whoever sets this should know what they're doing. Maybe having an alert if the size grows too big is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we will set the limit to something more conservative (<50G ?). We could have an alert when size reaches e.g. 50% of the limit and decide what to do then.

setup.upgrade(Some(2 * encoded_block_size), None);
setup.assert_remaining_capacity(0);

setup.upgrade(Some(u64::MAX), None);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding having a ceiling on the max size - it may be better to have something like 500GB that would allow the archive to return a more reasonable error to the ledger, so that the archive returns an error before the canister limit is reached, which may be more troublesome to handle (and test)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants