verify-read-concurrency.mjs 4.6 KB

12345678910111213141516171819202122232425262728293031323334
  1. #!/usr/bin/env node
  2. // Explicit isolated runner; downloads belong to a separate temporary npm prefix, not this repo.
  3. import {createRequire} from 'node:module';import {mkdtempSync,readFileSync,writeFileSync} from 'node:fs';import {tmpdir} from 'node:os';import {join} from 'node:path';import {randomBytes} from 'node:crypto';import {createServer} from 'node:net';import assert from 'node:assert/strict';
  4. const runtime=process.env.XIAOSHU_EMBEDDED_PG_DIR||'/tmp/xiaoshu-postgres-validation';
  5. const require=createRequire(join(runtime,'package.json'));
  6. const {default:EmbeddedPostgres}=await import(require.resolve('embedded-postgres'));
  7. const probe=createServer();await new Promise(r=>probe.listen(0,'127.0.0.1',r));const port=probe.address().port;await new Promise(r=>probe.close(r));
  8. const cluster=new EmbeddedPostgres({databaseDir:mkdtempSync(join(tmpdir(),'xiaoshu-read-pg-')),port,user:'validation',password:randomBytes(24).toString('hex'),persistent:false,createPostgresUser:false,postgresFlags:['-h','127.0.0.1','-c','max_connections=80'],onLog:()=>{},onError:()=>{}});
  9. const connections=[];const connect=async()=>{const c=cluster.getPgClient('postgres','127.0.0.1');await c.connect();connections.push(c);return c;};
  10. const report={isolated:true,productionTouched:false,checks:[]};
  11. try{
  12. await cluster.initialise();await cluster.start();const [a,b,c]=await Promise.all([connect(),connect(),connect()]);
  13. report.version=(await c.query('SELECT version() v')).rows[0].v;
  14. await c.query('CREATE TABLE "LessonRecord"(company text,id int);CREATE TABLE "CommonModel"(company text,id int);');
  15. await c.query(readFileSync(new URL('./sql/read-revisions.sql',import.meta.url),'utf8'));
  16. const version=async()=>(await c.query("SELECT xs_read_version('co','lessons') v")).rows[0].v;
  17. for(const isolation of ['READ COMMITTED','REPEATABLE READ']){
  18. await a.query('BEGIN ISOLATION LEVEL '+isolation);await a.query("INSERT INTO \"LessonRecord\" VALUES('co',1)");
  19. await b.query('BEGIN');await b.query("INSERT INTO \"LessonRecord\" VALUES('co',2)");await b.query('COMMIT');const earlier=await version();
  20. await a.query('COMMIT');const later=await version();assert.notEqual(earlier,later);report.checks.push({name:'late-commit-'+isolation,passed:true});
  21. }
  22. await a.query('BEGIN');await b.query('BEGIN');await a.query("INSERT INTO \"LessonRecord\" VALUES('co',10)");await b.query("INSERT INTO \"CommonModel\" VALUES('co',20)");
  23. await a.query("SET LOCAL statement_timeout='2s'");await a.query("INSERT INTO \"CommonModel\" VALUES('co',21)");await Promise.all([a.query('COMMIT'),b.query('COMMIT')]);report.checks.push({name:'former-revision-lock-cycle',passed:true});
  24. for(const x of [a,b,c])await x.query('BEGIN');
  25. const lock=async(x,slot)=>(await x.query('SELECT pg_try_advisory_xact_lock(1736200,$1) acquired',[slot])).rows[0].acquired;
  26. assert.equal(await lock(a,1),true);assert.equal(await lock(b,1),false);assert.equal(await lock(b,2),true);assert.equal(await lock(c,1),false);assert.equal(await lock(c,2),false);await a.query('ROLLBACK');assert.equal(await lock(c,1),true);await Promise.all([b.query('ROLLBACK'),c.query('ROLLBACK')]);report.checks.push({name:'global-two-slots-and-rollback-release',passed:true});
  27. await a.query('BEGIN');await a.query("SET LOCAL statement_timeout='30ms'");await lock(a,1);await assert.rejects(a.query('SELECT pg_sleep(1)'),e=>e.code==='57014');await a.query('ROLLBACK');await b.query('BEGIN');assert.equal(await lock(b,1),true);await b.query('ROLLBACK');report.checks.push({name:'timeout-rollback-releases-slot',passed:true});
  28. const clients=await Promise.all(Array.from({length:50},()=>connect()));
  29. let active=0,maxActive=0;
  30. const result=await Promise.all(clients.map(async client=>{await client.query('BEGIN');try{let allowed=false;for(const slot of [1,2])if(await lock(client,slot)){allowed=true;break;}if(!allowed)return 'busy';active++;maxActive=Math.max(maxActive,active);await client.query('SELECT pg_sleep(0.03)');active--;return 'success';}finally{await client.query('ROLLBACK');}}));
  31. assert.ok(maxActive<=2);report.checks.push({name:'50-connection-overload',passed:true,maxActive,success:result.filter(v=>v==='success').length,busy:result.filter(v=>v==='busy').length,note:'Intentional saturation test, not a successful 50-user capacity acceptance'});
  32. const before=await version();await c.query('SELECT xs_prune_read_revisions()');assert.equal(await version(),before);report.checks.push({name:'prune-preserves-version',passed:true});
  33. writeFileSync('perf/cloud-concurrency-2026-11-15/postgres-concurrency.json',JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2));
  34. }finally{await Promise.all(connections.map(c=>c.end().catch(()=>{})));await cluster.stop();}