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

ST sequences: respect allocated amount on restart #1532

Merged
merged 6 commits into from
Jul 24, 2024

Conversation

gefjon
Copy link
Contributor

@gefjon gefjon commented Jul 19, 2024

Description of Changes

Prior to this change, a weird special case in build_sequence_state which ignored system tables' sequences
made it impossible to add new database objects (tables, indexes, &c) due to unique constraint violations in the system tables.

Changing this uncovered a cascade of other failures, as our autoinc and bootstrapping sequences had a variety of tightly-coupled counterintuitive behaviors, each papering over a previous (mis-)behavior. In particular:

  • We must not replace the sequence_id autoinc column when updating st_sequence rows in MutTxId::get_next_sequence_value, because a system sequence has ID 0. This means calling insert_row_internal rather than insert.
  • We should not pre-allocate sequence values during bootstrap, because doing (combined with the above patches) causes system sequences to advance one additional time, resulting in values starting from 8192 instead of 4096.
  • From @kim : uniformly handle updates to either running or non-running hosts, core: Fix module update #1538 .
  • From @kim : refactor to avoid mismatching program hashes and program bytes, core: Load program hash alongside bytes #1539 .

API and ABI breaking changes

N/a

Expected complexity level and risk

3 - autoinc handling is very fiddly, as evidenced by the series of intertwined bugs fixed by sequential commits in this PR.

Testing

  • Published a BitCraft hotfix on the alpha staging server that added a new index, and it seems to have worked.
  • Our test suite passes.

Prior to this change, a weird special case in `build_sequence_state`
which ignored system tables' sequences
made it impossible to add new database objects (tables, indexes, &c)
due to unique constraint violations in the system tables.

I (pgoldman 2024-07-19) am unable to determine why `build_sequence_state`
carved out this special case.
Absent any obvious reason or any tests which fail with this patch,
I see no reason to preserve the old behavior, which was causing problems.
@gefjon
Copy link
Contributor Author

gefjon commented Jul 19, 2024

Preliminary investigation of the test failure suggests:

  • The sequence seq_st_table_table_id_primary_key_auto gets ID 0.
  • When we attempt to allocate values from that sequence, we read its row from st_sequences and then write it back.
  • Because that row has the value 0, it triggers an auto-inc replacement and gets the next value in the sequence (namely 8194; where this specific value comes from is beyond my current understanding).
  • But st_column still refers to it by ID 0, so the next time we try to look it up, we get error: no such sequence!

The specific flow is:

  • create_table
    • insert on st_table
      • get_next_sequence_value on sequence 0, seq_st_table_table_id_primary_key_auto
        • delete on st_sequences of the row for seq_st_table_table_id_primary_key_auto.
        • insert on st_sequences of the row for seq_st_table_table_id_primary_key_auto.
          • get_next_sequence_value on sequence 3, seq_st_sequences_sequence_id_primary_key_auto.
            • insert, delete on st_sequences of the row for seq_st_sequences_sequence_id_primary_key_auto; this keeps its nice ID of 3.
            • Return 8194 as next value for seq_st_sequences_sequence_id_primary_key_auto.
          • Replace st_sequence row for seq_st_table_id_primary_key_auto's ID with 8194.
          • insert_row_internal on st_sequences with row that has ID 8194.

And from there we're screwed.

@gefjon
Copy link
Contributor Author

gefjon commented Jul 22, 2024

Updates:

The above comment's bug is fixed by calling insert_row_internal rather than insert in get_next_sequence_value, i.e. by skipping autoinc and unique constraint validation. These are already performed in create_sequence, and should not be repeated.

@gefjon gefjon added bitcraft issue Active issue for the BitCraft team bugfix Fixes something that was expected to work differently backward-compatible labels Jul 22, 2024
@gefjon gefjon self-assigned this Jul 22, 2024
…ting.

Also use `pretty_assertions` in some tests,
because I was having trouble debugging small differences in large structures.
Notably does not use `pretty_assertions` in our whole test suite,
only in the tests broken by the previous commits in this PR.

Prior to this commit, we pre-allocated 4096 values for each sequence during bootstrap.
For user sequences, these were 0..=4096; for system sequences, they were 4097..=8192.
This did not play nicely with restoring after a restart;
we would either incorrectly re-use values starting from 4097 after restart,
or would spuriously allocate after bootstrap without a restart
and begin with values starting from 8193.

With this commit, we do not pre-allocate sequence values during bootstrap.
Each user table sequence starts with `value: 1, allocation: 0`,
and each system sequence with `value: 4097, allocation: 4096`.
This means that the first access to a sequence, either after bootstrap or after a restart,
will perform an allocation.
This is in contrast to previously, where accesses after restart would allocate,
but accesses after bootstrap would not.

Additionally, the logic for determining whether an allocation was necessary
in `MutTxId::get_next_sequence_value` contained an off-by-one value
which caused the last value before an allocation to be skipped.
This commit fixes that off-by-one error, making it so that yielding value `4096`
when `allocation == 4096` is possible, though it does result in a new allocation.
Previously, we would yield 4095 without allocation, skip 4096,
then allocate and yield 4097.
@gefjon gefjon force-pushed the phoebe/system-sequence-respect-allocated branch from ce16317 to dcb134a Compare July 22, 2024 17:04
@gefjon
Copy link
Contributor Author

gefjon commented Jul 22, 2024

After the most recent commit, I am still able to add a new index to a recently-restarted BitCraft module when publishing without -c.

@bfops bfops added the release-any To be landed in any release window label Jul 22, 2024
bfops pushed a commit that referenced this pull request Jul 22, 2024
…t pre-allocate seqs during bootstrap; fix off-by-ones when allocating.

Also use `pretty_assertions` in some tests,
because I was having trouble debugging small differences in large structures.
Notably does not use `pretty_assertions` in our whole test suite,
only in the tests broken by the previous commits in this PR.

Prior to this commit, we pre-allocated 4096 values for each sequence during bootstrap.
For user sequences, these were 0..=4096; for system sequences, they were 4097..=8192.
This did not play nicely with restoring after a restart;
we would either incorrectly re-use values starting from 4097 after restart,
or would spuriously allocate after bootstrap without a restart
and begin with values starting from 8193.

With this commit, we do not pre-allocate sequence values during bootstrap.
Each user table sequence starts with `value: 1, allocation: 0`,
and each system sequence with `value: 4097, allocation: 4096`.
This means that the first access to a sequence, either after bootstrap or after a restart,
will perform an allocation.
This is in contrast to previously, where accesses after restart would allocate,
but accesses after bootstrap would not.

Additionally, the logic for determining whether an allocation was necessary
in `MutTxId::get_next_sequence_value` contained an off-by-one value
which caused the last value before an allocation to be skipped.
This commit fixes that off-by-one error, making it so that yielding value `4096`
when `allocation == 4096` is possible, though it does result in a new allocation.
Previously, we would yield 4095 without allocation, skip 4096,
then allocate and yield 4097.
Copy link
Contributor

@cloutiertyler cloutiertyler left a comment

Choose a reason for hiding this comment

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

I just left one small nit, but otherwise I'm good to approve this.

And shuffle some comments around.
@gefjon gefjon enabled auto-merge July 23, 2024 14:29
@gefjon gefjon disabled auto-merge July 23, 2024 14:30
@gefjon
Copy link
Contributor Author

gefjon commented Jul 23, 2024

Going to try my hand at writing a smoketest, then will merge.

@gefjon gefjon enabled auto-merge July 23, 2024 15:37
@gefjon gefjon disabled auto-merge July 23, 2024 16:28
@gefjon
Copy link
Contributor Author

gefjon commented Jul 23, 2024

Well damn, that new smoketest is actually broken. To recreate without the Smoketest driver:

  • spacetime start.
  • In another window, publish a module with the following code:
    use spacetimedb::spacetimedb;
    
    #[spacetimedb(table)]
    pub struct T1 {
        id: u64,
    }
    
    #[spacetimedb(table)]
    pub struct T2 {
        id: u64,
    }
    
    #[spacetimedb(init)]
    pub fn init() {
        for id in 0..1_000 {
            T1::insert(T1 { id });
            T2::insert(T2 { id });
        }
    }
  • Run spacetime subscribe test_add_index 'select T1.* from T1 join T2 on T1.id = T2.id where T2.id = 1001' --print-initial-update -n 0, see expected error.
  • Restart SpacetimeDB.
  • Add indexes so the module code is now:
    use spacetimedb::spacetimedb;
    
    #[spacetimedb(table)]
    #[spacetimedb(index(btree, name = "id", id))]
    pub struct T1 {
        id: u64,
    }
    
    #[spacetimedb(table)]
    #[spacetimedb(index(btree, name = "id", id))]
    pub struct T2 {
        id: u64,
    }
    
    #[spacetimedb(init)]
    pub fn init() {
        for id in 0..1_000 {
            T1::insert(T1 { id });
            T2::insert(T2 { id });
        }
    }
  • Publish without -c.
  • Run the above subscribe again and see the same error. It should succeed.
  • Be very sad.

@gefjon
Copy link
Contributor Author

gefjon commented Jul 23, 2024

Ok, what's really weird is that if you do:

  • Publish without index.
  • Republish with index.
  • Republish without index.
  • Restart.
  • Publish with index.
  • Subscribe.

Then it works.

@kim kim mentioned this pull request Jul 24, 2024
1 task
@kim
Copy link
Contributor

kim commented Jul 24, 2024

Pushed #1538 #1539 onto this.
For reference:

#1538:

When updating a host, that host may not currently be running, in which case it is launched. However, it may (or may not) be initialized already. In either case, it is initialized before updating.

However, the host controller handled the not-running case without considering the auto-initialization -- that is, it took the module the host was launched with to perform the update, instead of the new module.

This meant that the schema was the same, so no actual update was performed.

This patch fixes this by handling both cases uniformly, which always instantiates a module from the supplied program bytes.

It also passes the correct program hash to launch_module, so as to fix the diagnostic output.

#1539:

Passing around the pair of (hash, bytes) in the host controller proved confusionary. Also, the TxDatastore::program_bytes method would in one instance require to compute the hash unnecessarily.

Thus, refactor to always load the hash and bytes together, and introduce a named pair Program containing both.

@gefjon gefjon enabled auto-merge July 24, 2024 14:37
@gefjon gefjon added this pull request to the merge queue Jul 24, 2024
Merged via the queue into master with commit 1d26575 Jul 24, 2024
8 checks passed
gefjon added a commit that referenced this pull request Jul 24, 2024
…: Don't pre-allocate seqs during bootstrap; fix off-by-ones when allocating."

This reverts commit abf443a.
gefjon added a commit that referenced this pull request Jul 24, 2024
Co-authored-by: Kim Altintop <[email protected]>

Kinda a sketchy rebase, tbh. Some smoketests failing locally.
kim added a commit that referenced this pull request Jul 31, 2024
Refactor to use `Program` (introduced in #1532) throughout.

Also avoid cloning the program bytes to instantiate a wasm module.
This makes the `AnyBytes` type unused, so remove it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backward-compatible bitcraft issue Active issue for the BitCraft team bugfix Fixes something that was expected to work differently release-any To be landed in any release window
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants