ã¯ããã« PGliteã®æŠèŠ PGliteã®ç¹åŸŽ PGliteã詊ã ãã©ãŠã¶ã§äœ¿ã PGliteã®éåºŠèšæž¬ ãŸãšã ã¯ããã« ããã«ã¡ã¯ïŒãšã³ãžãã¢ïŒå¹Žç®ã®TKDSã§ãïŒ ä»åã¯PGliteã«ã€ããŠèª¿ã¹ãŠã¿ãŸããïŒ æŠèŠã»äœ¿ãæ¹ã»é床å®éšã»ãŸãšãã®å
容ã§èšäºã¯æ§æãããŠããŸãã 䜿ã£ãŠã¿ãçµæãšããŠã軜éé«éã§ããè²ã
䜿ãã¿ã¡ããããããªããŒã«ã ãšæããŸããã ãã²æåŸãŸã§èªãã§ããã ãããšå¹žãã§ãã PGliteã®æŠèŠ PGliteã¯ã PostgreSQL ãWebAssemblyïŒWASMïŒã« ã³ã³ãã€ã« ãã軜éãªããŒã¿ããŒã¹ãšã³ãžã³ã§ãã ããã«ããããã©ãŠã¶ãNode.jsãBunãDenoãªã©ã§ PostgreSQL ã®æ©èœãå©çšã§ããéçºè
ã¯ããŒã«ã«ããµãŒããŒã¬ã¹ç°å¢ã§ããŒã¿ããŒã¹æäœãè¡ãããšãå¯èœã§ãã PGliteã¯ãã€ã³ã¡ã¢ãªããŒã¿ããŒã¹ã ãã¡ã€ã«ã·ã¹ãã ïŒNode.jsãBunïŒãIndexedDBïŒãã©ãŠã¶ïŒã§ã®æ°žç¶åããµããŒãããŸãã PGliteã®ç¹åŸŽ å
¬åŒãµã€ã ã«ãããšã 1. Lightweight 2. Extendable 3. Reactive äžèšã3ã€ã®ç¹åŸŽãæããããŠããŸããã 1ïŒPGliteã®WASMãã€ããªãå§çž®ç¶æ
ã§3MBçšåºŠã§ããããš 2ïŒ PostgreSQL ã® æ¡åŒµæ©èœ ãé©çšå¯èœã§ããããš 3ïŒã«é¢ããŠã¯ããŒãã«ã倿Žããããšãã«æŽæ°ãããçµæãåãåãæ©èœããµããŒãããŠããããš ããæ¥ãŠããããã§ãã 3ã¯CDCïŒChange Data CaptureïŒã®æ©èœã«äŒŒãŠãŸããïŒ ããã³ããšã³ãã¯è©³ãããªãã®ã§ãããç¶æ
管çãªã©ã«ã䜿ããã®ã§ã¯ïŒãªã©æãæµ®ãã³ãŸããã PGliteã詊ã ããã¥ã¡ã³ããã¿ã€ã€ç°å¢æ§ç¯ããŠã¿ãŸãããã æè»œã«è©Šãããå Žåã¯ã ãã¡ãã®ãªã³ã¯ ãã詊ãããšãã§ããŸãã ä»åã¯æå
ã§è©ŠããŠã¿ãŸãã npm install @electric-sql/pglite application.jsã«äžèšã®å
å®¹ãæžã蟌ã¿ãŸãã const { PGlite } = require ( '@electric-sql/pglite' ) ; ( async () => { try { const db = new PGlite () ; await db . exec ( ` CREATE TABLE IF NOT EXISTS todo ( id SERIAL PRIMARY KEY, task TEXT, done BOOLEAN DEFAULT false ); INSERT INTO todo (task, done) VALUES ('Install PGlite from NPM', true); INSERT INTO todo (task, done) VALUES ('Load PGlite', true); INSERT INTO todo (task, done) VALUES ('Create a table', true); INSERT INTO todo (task) VALUES ('Update a task'); ` ) ; const ret = await db . query ( ` SELECT * from todo WHERE id = 1; ` ) ; console . log ( "Query Result:" , ret . rows ) ; } catch ( error ) { console . error ( "Error executing query:" , error ) ; } })() ; ããã¥ã¡ã³ãã®å
容ããã®ãŸãŸã³ããŒããŠãåããªãã®ã§ãå°ã
ä¿®æ£ããŠãããŸãã çµæã¯ä»¥äžã®ãšããã§ãã ç¡äºã«åããããšãã§ããŸããã éã«æéãæž¬ã£ãŠã¿ããšèµ·åãã SQL å®è¡ãŸã§2.08ç§ã»ã©ã§å®äºããŠããŸããéåžžã«é«éã§ããïŒ ãã©ãŠã¶ã§äœ¿ã ã³ãã³ãã©ã€ã³ ã§æ±ºãŸã£ã SQL ãå®è¡ãã以å€ã«ãããã©ãŠã¶ã§REPLã䜿ã£ãŠã察話çã«PGliteã«ã¢ã¯ã»ã¹ã§ããŸãã ããã¥ã¡ã³ãã« ã³ãŒã ãèšèŒãããŠããŸããã2024/8/18æç¹ã§ã¯ããã®ãŸãŸäœ¿çšã§ããŸããã éçºè
ããŒã«ã§ã¿ããšã Failed to load resource: the server responded with a status of 404 () ãšã¡ãã»ãŒãžã衚瀺ãããŠããŸãã ãœãŒã¹ãããã¡ã€ã«ãéããš Couldn't find the requested file /dist-webcomponent/Repl.js in @electric-sql/pglite. ãšè¡šç€ºãããŠããŸãã ããããããã¥ã¡ã³ãã®ã³ãŒãã®ãªã³ã¯ãééã£ãŠãããã§ãã JSDELIVER ã§èª¿ã¹ãŠã¿ãŸãããã 調ã¹ãŠã¿ããšãäžèšç»åã®ããã«pglite-replããããŸããïŒãã®ãã¹ã«å€æŽããã°ããããã§ãã 念ã®ããäžèº«ããã§ãã¯ããŠãã䜿çšããŸããã ã§ã¯ã以äžã®ã³ãŒãããã¡ã€ã«ã«æžãããã©ãŠã¶ã§éããŠãã ããã <!doctype html> < html lang = "ja" > < head > < meta charset = "UTF-8" /> < meta name = "viewport" content = "width=device-width, initial-scale=1.0" /> < title > PGlite REPL Example </ title > < script src = "https://cdn.jsdelivr.net/npm/@electric-sql/pglite-repl@0.2.1/dist-webcomponent/Repl.js" type = "module" ></ script > </ head > < body > < h1 > PGlite REPL </ h1 > <!-- PGlite REPLã³ã³ããŒãã³ããããã«è¡šç€ºãããŸã --> < pglite-repl id = "repl" ></ pglite-repl > < script type = "module" > import { PGlite } from "https://cdn.jsdelivr.net/npm/@electric-sql/pglite/dist/index.js" ; // PGliteã€ã³ã¹ã¿ã³ã¹ãäœæ const pg = new PGlite () ; // REPLèŠçŽ ãååŸ const repl = document . getElementById ( "repl" ) ; // PGliteã€ã³ã¹ã¿ã³ã¹ãREPLã«èšå® repl . pg = pg ; </ script > </ body > </ html > REPLãéãããšãã§ãå
¥åãã§ããŸããïŒ PGliteã®éåºŠèšæž¬ 次ã«PGliteã®éåºŠãæž¬ã£ãŠã¿ãŸãã SELECTã INSERTãUPDATEãDELETEã«ã€ããŠãããããå®éšããŸãã 以äžã®ã³ãŒãã§å®éšããŸãã åDBæäœãè¡ãã1000, 2000, 3000, 4000, 5000, 10000ãšæ±ãä»¶æ°ãå¢ããŠãããŸãã import { PGlite } from "@electric-sql/pglite" ; import { performance } from "perf_hooks" ; const measureDbStartup = () => { const dbStartTime = performance . now () ; // DBèµ·åæéã®æž¬å®éå§ const db = new PGlite () ; // ããŒã¿ããŒã¹ã€ã³ã¹ã¿ã³ã¹ãåæå const dbEndTime = performance . now () ; // DBèµ·åæéã®æž¬å®çµäº const dbStartupTime = dbEndTime - dbStartTime ; console . log ( `DB startup time: ${ dbStartupTime . toFixed ( 3 )} ms` ) ; return db ; } ; const initDb = async ( db , numRows ) => { await db . exec ( ` CREATE TABLE IF NOT EXISTS test_table ( id SERIAL PRIMARY KEY, name TEXT, value INTEGER ); ` ) ; const values = [] ; for ( let i = 0 ; i < numRows ; i ++ ) { values . push ( `('name_ ${ i } ', ${ i } )` ) ; } await db . exec ( `INSERT INTO test_table(name, value) VALUES ${ values . join ( ", " )} ` , ) ; } ; const measureOperationTime = async ( operation , db , numRows ) => { let totalTime = 0 ; for ( let trial = 1 ; trial <= 3 ; trial ++ ) { const startTime = performance . now () ; await operation ( db , numRows ) ; const endTime = performance . now () ; const duration = endTime - startTime ; totalTime += duration ; console . log ( `Trial ${ trial } , ${ operation . name . toUpperCase ()} ${ numRows } rows: ${ duration . toFixed ( 3 )} ms` , ) ; } const averageTime = totalTime / 3 ; console . log ( `Average ${ operation . name . toUpperCase ()} time for ${ numRows } rows: ${ averageTime . toFixed ( 3 )} ms` , ) ; console . log ( `Time per row: ${( averageTime / numRows ) . toFixed ( 6 )} ms/row` ) ; } ; const selectOperation = async ( db , numRows ) => { await db . query ( `SELECT * FROM test_table LIMIT ${ numRows } ` ) ; } ; const insertOperation = async ( db , numRows ) => { const values = [] ; for ( let i = numRows ; i < numRows * 2 ; i ++ ) { values . push ( `('name_ ${ i } ', ${ i } )` ) ; } await db . exec ( `INSERT INTO test_table(name, value) VALUES ${ values . join ( ", " )} ` , ) ; } ; const updateOperation = async ( db , numRows ) => { await db . exec ( `UPDATE test_table SET value = value + 1 WHERE id <= ${ numRows } ` , ) ; } ; const deleteOperation = async ( db , numRows ) => { await db . exec ( `DELETE FROM test_table WHERE id <= ${ numRows } ` ) ; } ; // å®è¡ const main = async () => { const operationCounts = [ 1000 , 2000 , 3000 , 4000 , 5000 , 10000 ] ; const operations = [ selectOperation , insertOperation , updateOperation , deleteOperation , ] ; for ( const count of operationCounts ) { for ( const operation of operations ) { console . log ( `\nRunning ${ operation . name . toUpperCase ()} test with ${ count } rows:` , ) ; const db = measureDbStartup () ; // DBèµ·åæéã®æž¬å®ãšã€ã³ã¹ã¿ã³ã¹äœæ await initDb ( db , count ) ; // DBåæåãšããŒã¿æ¿å
¥ await measureOperationTime ( operation , db , count ) ; // æäœã®ããã©ãŒãã³ã¹æž¬å® } } } ; main () . then (() => console . log ( "All tests completed" )) . catch (( err ) => console . error ( "Error:" , err )) ; çµæã以äžã«ãŸãšããŸãã è¡æ° å¹³åèµ·åæé (ms) SELECT (ms) INSERT (ms) UPDATE (ms) DELETE (ms) SELECT (ms/row) INSERT (ms/row) UPDATE (ms/row) DELETE (ms/row) 1000 0.141 2.539 3.651 4.893 0.476 0.002539 0.003651 0.004893 0.000476 2000 0.051 2.801 7.267 9.216 0.701 0.001401 0.003633 0.004608 0.000350 3000 0.071 3.867 10.362 13.177 0.835 0.001289 0.003454 0.004392 0.000278 4000 0.035 5.588 14.742 17.931 1.776 0.001397 0.003685 0.004483 0.000444 5000 0.047 7.007 19.127 23.373 2.102 0.001401 0.003825 0.004675 0.000420 10000 0.036 10.971 37.880 43.966 3.535 0.001097 0.003788 0.004397 0.000354 èµ·åæéã¯æŠã1msãããããé«éã§ããããšãããããŸãã ããŒã¿æäœãé«éã§ãã 1äžä»¶ã§ãäžçªé
ããŠãUPDATEã®43msã®ããããã¹ãçšéãªã©ã§ããã°ååå®çšã«èããã®ã§ã¯ãªãããšæããŸããã ãŸãšã ä»åã¯PGliteãåãããŸã§ã®æ¹æ³ãšè©Šè¡é¯èª€ã«ã€ããŠæžããŸããã ãŸã éçºäžãšããããšããããè²ã
äžæ£ç¢ºãªããšãæ
å ±ããªã倧å€ã§ããã PGliteèªäœã¯éåžžã«é«éã§REPLãçšæãããŠãããããŒã«ãšããŠéåžžã«é
åçã§ããïŒ ä»åã¯éåžžã«ç°¡åãªäœ¿ãæ¹ã ãã ã£ãã®ã§ãä»åŸã¯èµ·åç¶æ
ãç¶æããã¯ã©ã€ã¢ã³ãã©ã€ãã©ãªããæ¥ç¶ããŠäœ¿ã£ãŠã¿ããããã¹ãã§ã®DBãšããŠäœ¿ã£ãŠã¿ãããšèããŠããŸãã ãããŸã§èªãã§ããã ãããããšãããããŸããïŒ