Skip to content

Under the hood

Latest

Choose a tag to compare

@DerKezorm DerKezorm released this 02 Sep 13:34

Under the hood. No new feature in this one, and that is deliberate. It repairs a setting
that turned itself around behind your back, makes the lists a great deal faster, and clears out
what had quietly piled up in the database.

Fixed

  • A storage limit of 0 turned itself back into "unlimited" on every restart. Setting an
    account to 0 GB means: this account may not request anything. On the next start Nexview
    rewrote that to unlimited, the exact opposite, and said nothing about it. The cause was a
    one-time migration from 0.19, when a stored 0 changed meaning, that kept running on every
    start instead of once.

    Nexview now keeps a book of which migrations have already run in this database. For an
    existing installation the entries are not guessed but derived from what the database looks
    like on arrival, read before the schema is touched, because the schema update itself destroys
    the evidence. A migration that breaks off halfway leaves an open entry behind and is picked up
    on the next start, instead of a gap that the next run fills in wrongly.

    What already flipped in the past cannot be recovered: a rewritten 0 is indistinguishable from
    an unlimited set on purpose. Affected installations get one warning in the log naming how many
    accounts could be involved, and nothing is changed automatically.

  • The badge on the admin menu never went away. It counted findings, and a finding stays true
    as long as it is true, so the same digit sat there for good even after somebody had looked. It
    counts unread now. Merely polling the endpoint does not count as seen, or the badge would
    clear before anyone read it.

Security

  • Four dependencies moved up, and a check that will notice the next one. The pinned versions
    were correct on the day they were written and had aged quietly since. Four of the eleven
    carried published advisories, among them the libraries responsible for sign-in and encryption.
    All four are raised, including one across four major versions.

    From now on backend/tools/abhaengigkeiten_pruefen.py holds the pins against the public
    vulnerability database on every build and stops the build rather than warning. It ships with
    an empty exception list on purpose: the first entry anyone adds will be a deliberate act, not
    an inheritance.

  • A guard meant to find the next unguarded door did not find it. To test the watchers that
    walk the whole route table, a backdoor was built on purpose: an endpoint that switches off any
    account, taking its target from the request body. It passed all 2,482 tests without a single
    failure. Four watchers had holes of the same kind, and one had silently stopped seeing 90
    modules.

    All four are repaired, a fifth now covers the whole backend instead of the routers alone, and
    each one carries a floor check that fails if it did not actually examine anything. That was
    the real defect: a watcher that examines nothing is green forever.

Under the hood

  • Lists fetch their data in one go instead of row by row. The pending requests list asked the
    database once per row for that row's rating: 157 queries for 144 rows, and it grew with the
    archive. It needs 11 now, the user list dropped from 22 to 9, the dashboard from 42 to 25. The
    answers are unchanged byte for byte, checked across 14 address variants against a copy of a
    real database.

    A scale in the test run keeps it that way. It does not ask whether the number is small but
    whether it grows when the data doubles. Arming it immediately turned up three more of the same
    kind that the targeted repair had missed.

  • The database gives deleted space back on its own. Expired cache rows were deleted but their
    pages stayed in the file, so it only ever grew: 172 MB of file for 32 MB of content. It now
    converts once at startup and reclaims gradually afterwards. On the measured database that was
    180 MB before and 34 MB after. The first start takes a little longer for it, once, and says so
    in the log.

  • Backups and restores no longer collide. The hourly backup beat moved off the event loop,
    which made it possible for it to run into a restore. Both take turns on a shared lock now, and
    the restore left the event loop as well: until now it blocked every other request while it ran.

  • The settings are read once per request instead of up to eight times. A page like the
    dashboard fetched them eight times over, deriving the encryption key afresh for every stored
    credential each time. Measured with NEXVIEW_SECRET_KEY set, one such request drops from
    10.7 ms to 1.3 ms.

  • The release notes no longer travel with every page load. The texts behind "Everything
    that's new" were the largest single block in the language catalogue, read once by one
    administrator after an update. They arrive on demand now. First load: 822 kB down to 792 kB.

  • A switched-off foreign key check could survive in the connection pool. Connections are
    reused, and the setting that makes deletes cascade was applied when one was created, not when
    it was handed out. In the test suite this surfaced as a deleted account leaving its tickets
    behind, three files away, with no visible connection. It is set on every checkout now.

  • The build checks the Python code, and the test run stopped waiting for bcrypt. Ruff runs as
    its own step and stops the build instead of warning; ESLint joined with its threshold at zero.
    Password hashing is deliberately slow, and the test run paid that price 2,482 times over. Tests
    hash with a low cost now while everything else keeps the full one. The whole build went from
    40 minutes to 7.