Changeset 7e474f7 in Klonkt


Ignore:
Timestamp:
08/14/2026 03:12:04 AM (4 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
bfadc15
Parents:
47f1183
Message:

De publieke vertaaltabel laadde alles om er twintig te tonen

Robin, ter herinnering: honderd miljoen Klonkt-gebruikers. Terecht, en het
wees meteen een echte fout aan in wat ik vandaag bouwde.

migrationItems deed SELECT zonder LIMIT. buildMigration bouwde daar objecten
van en liet pagedCollection er twintig uit snijden. Bij een gebruiker met
honderdduizend berichten betekent dat: honderdduizend rijen uit de database,
honderdduizend objecten in geheugen, 99,98 procent daarvan direct de prullenbak
in. Per verzoek.

En het is geen intern verzoek. Dit endpoint is publiek EN FEP-1580 schrijft
derden voor het herhaaldelijk op te halen tot migrationComplete waar is. Een
ontbrekende LIMIT is daar geen inefficientie maar een hefboom.

Nu in SQL gepagineerd, met dezelfde alGesneden-aanpak die de outbox al had.

DE KALE COLLECTIE DRAAGT GEEN RIJEN MEER, alleen de telling en first/last. Dat
is niet alleen goedkoper maar ook veiliger: een kale collectie MET items was
precies de valstrik waar onze eigen ingest vanmorgen in liep. Hij zag items,
sloeg first over, vond geen next en dacht klaar te zijn bij 18 van de 35.

Changed files:
src/services/MigrationService.js

  • migrationItems kent limit en offset
  • buildMigration telt apart en haalt alleen de gevraagde pagina op

test/fep1580-migration.test.js

  • de kale collectie heeft een telling en geen rijen
  • 45 items geven pagina's van 20/20/5, zonder overlap en zonder verlies

remarks: de rate-limiter dekt /ap al, dus die kant zat goed. Wat NIET opgelost
is en bij deze schaal het echte plafond blijft: buildArchive bouwt de hele zip
in geheugen, inclusief elk audiobestand. Die grens van 512 MB is nog steeds een
beredeneerde gok en geen meting. Streamen staat als shaer-190t genoteerd; bij
honderd miljoen gebruikers is dat geen nice-to-have meer. Suite 1014 groen,
tegenproef gedaan.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/services/MigrationService.js

    r47f1183 r7e474f7  
    3636import crypto from 'crypto';
    3737import db from '../config/database.js';
    38 import { AP_CONTEXT, actorId, pagedCollection } from './ap-core.js';
     38import { AP_CONTEXT, actorId, pagedCollection, PAGINA_GROOTTE } from './ap-core.js';
    3939
    4040// ── Vertaaltabel ──────────────────────────────────────────────────
     
    6666 * de URIs van je fan-only posts opsomt is een lek, ook zonder de inhoud.
    6767 */
    68 export function migrationItems(slug, { alles = false } = {}) {
     68export function migrationItems(slug, { alles = false, limit = null, offset = 0 } = {}) {
    6969  try {
     70    // IN SQL pagineren, niet in geheugen. Dit is een PUBLIEK endpoint dat
     71    // derden volgens FEP-1580 juist herhaaldelijk ophalen tot migrationComplete
     72    // waar is. Alles laden om er twintig te tonen is dan geen inefficientie
     73    // maar een hefboom: bij honderdduizend berichten bouwt elke poll
     74    // honderdduizend objecten die meteen de prullenbak in gaan.
    7075    const sql = `SELECT origin, target, source_actor FROM ap_migration
    71                  WHERE slug = ?${alles ? '' : ' AND is_public = 1'} ORDER BY id DESC`;
    72     return db.prepare(sql).all(slug);
     76                 WHERE slug = ?${alles ? '' : ' AND is_public = 1'}
     77                 ORDER BY id DESC${limit ? ' LIMIT ? OFFSET ?' : ''}`;
     78    return limit ? db.prepare(sql).all(slug, limit, offset) : db.prepare(sql).all(slug);
    7379  } catch { return []; }
    7480}
     
    151157  const me = actorId(base, site.slug);
    152158  const id = `${me}/migration`;
    153   const rows = migrationItems(site.slug, { alles });
     159  const totaal = migrationCount(site.slug, { alles });
     160  // Zonder pagina: alleen de omslag met eerste/laatste en de telling. Zo hoeft
     161  // de kale collectie geen enkele rij aan te raken, en dat is precies wat een
     162  // consument als eerste opvraagt.
     163  const nr = page ? Math.max(1, Math.floor(Number(page)) || 1) : false;
     164  const rows = nr ? migrationItems(site.slug, { alles, limit: PAGINA_GROOTTE, offset: (nr - 1) * PAGINA_GROOTTE }) : [];
    154165  const items = rows.map((r) => ({
    155166    type: 'Move',
     
    159170  }));
    160171  return pagedCollection(id, items, {
    161     page,
     172    page: nr,
     173    totalItems: totaal,
     174    alGesneden: true,
    162175    extra: {
    163176      attributedTo: me,
  • test/fep1580-migration.test.js

    r47f1183 r7e474f7  
    149149  Mig.recordMigrated('ik', { origin: `${BRON}/notes/1`, target: `${IK}/notes/a`, sourceActor: BRON });
    150150  Mig.recordMigrated('ik', { origin: `${BRON}/notes/2`, target: `${IK}/notes/b`, sourceActor: BRON });
    151   const coll = Mig.buildMigration('https://nieuw.example', s);
     151  // De KALE collectie draagt alleen de telling en de wegwijzers. Bewust: dit is
     152  // een publiek endpoint dat derden herhaaldelijk pollen, en alle rijen laden om
     153  // er twintig te tonen is bij honderdduizend berichten een hefboom. Bovendien
     154  // was een kale collectie MET items precies de valstrik waar onze eigen ingest
     155  // in liep: hij zag items, sloeg `first` over en miste de helft.
     156  const kaal = Mig.buildMigration('https://nieuw.example', s);
     157  assert.equal(kaal.totalItems, 2, 'de telling staat er wel');
     158  assert.equal((kaal.orderedItems || []).length, 0, 'maar de rijen niet');
     159  assert.ok(kaal.first, 'wie ze wil volgt first');
     160
     161  const coll = Mig.buildMigration('https://nieuw.example', s, { page: 1 });
    152162  assert.equal(coll.totalItems, 2);
    153163  const items = coll.orderedItems || coll.items;
     164  assert.equal(items.length, 2);
    154165  assert.equal(items[0].origin, `${BRON}/notes/2`, 'omgekeerd chronologisch op aanmaakmoment HIER');
    155166  assert.equal(items[0].type, 'Move');
    156167  assert.equal(items[0].target, `${IK}/notes/b`);
     168});
     169
     170test('de publieke vertaaltabel laadt niet alles om twintig te tonen', () => {
     171  // Robins herinnering: honderd miljoen Klonkt-gebruikers. Dit endpoint is
     172  // publiek en derden pollen het tot migrationComplete waar is, dus een query
     173  // zonder LIMIT is hier geen inefficientie maar een hefboom.
     174  const s = site();
     175  for (let i = 0; i < 45; i++) {
     176    Mig.recordMigrated('ik', { origin: `${BRON}/notes/n${i}`, target: `${IK}/notes/n${i}` });
     177  }
     178  const p1 = Mig.buildMigration('https://nieuw.example', s, { page: 1 });
     179  assert.equal(p1.totalItems, 45, 'de telling klopt over het geheel');
     180  assert.equal(p1.orderedItems.length, 20, 'maar er komt een PAGINA uit, geen bak van 45');
     181  assert.ok(p1.next, 'en er is een volgende');
     182  const p3 = Mig.buildMigration('https://nieuw.example', s, { page: 3 });
     183  assert.equal(p3.orderedItems.length, 5, 'de laatste pagina is de rest');
     184  assert.equal(p3.next, undefined, 'en die biedt geen volgende meer aan');
     185  // De pagina's overlappen niet en samen zijn ze het geheel.
     186  const p2 = Mig.buildMigration('https://nieuw.example', s, { page: 2 });
     187  const alle = [...p1.orderedItems, ...p2.orderedItems, ...p3.orderedItems].map((x) => x.origin);
     188  assert.equal(new Set(alle).size, 45, 'geen dubbele en niets kwijt');
    157189});
    158190
Note: See TracChangeset for help on using the changeset viewer.