<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Postgresql on K-Life Hack | システムアーキテクチャ &amp; DevOps</title><link>https://klifehack.com/tags/postgresql/</link><description>Recent content in Postgresql on K-Life Hack | システムアーキテクチャ &amp; DevOps</description><generator>Hugo -- gohugo.io</generator><language>ja</language><lastBuildDate>Wed, 03 Jun 2026 18:03:03 +0900</lastBuildDate><atom:link href="https://klifehack.com/tags/postgresql/index.xml" rel="self" type="application/rss+xml"/><item><title>Spring Boot 3.3とReact 18を用いた画像共有SNSのMVP実装設計</title><link>https://klifehack.com/p/spring-react-sns-mvp-design/</link><pubDate>Wed, 03 Jun 2026 18:03:03 +0900</pubDate><guid>https://klifehack.com/p/spring-react-sns-mvp-design/</guid><description>&lt;h1 id="instagramクローン開発におけるシステムアーキテクチャと実装ロードマップ"&gt;Instagramクローン開発におけるシステムアーキテクチャと実装ロードマップ
&lt;/h1&gt;&lt;p&gt;画像共有、ソーシャルグラフ、およびリアルタイムのインタラクションを核としたSNSの開発における、アーキテクチャ設計と実装ロードマップを定義します。本ドキュメントでは、Spring Boot 3.3およびReact 18を基盤としたMVP（Minimum Viable Product）の構築に焦点を当てます。&lt;/p&gt;
&lt;h2 id="1-プロジェクトの定義とmvpスコープ"&gt;1. プロジェクトの定義とMVPスコープ
&lt;/h2&gt;&lt;p&gt;本プロジェクトの目的は、ユーザーが写真をアップロードし、フォローしているユーザーの投稿を時系列で閲覧し、「いいね」や「コメント」を通じて交流できるWebサービスを構築することです。開発の複雑性を制御するため、フェーズ1（MVP）では以下の機能にスコープを限定します。&lt;/p&gt;
&lt;h3 id="mvp機能マトリクス"&gt;MVP機能マトリクス
&lt;/h3&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style="text-align: left"&gt;カテゴリ&lt;/th&gt;
					&lt;th style="text-align: left"&gt;MVP実装範囲&lt;/th&gt;
					&lt;th style="text-align: left"&gt;フェーズ2以降の検討事項&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style="text-align: left"&gt;&lt;b&gt;アカウント&lt;/b&gt;&lt;/td&gt;
					&lt;td style="text-align: left"&gt;サインアップ、ログイン、プロフィール管理&lt;/td&gt;
					&lt;td style="text-align: left"&gt;OAuth2.0、2要素認証（2FA）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style="text-align: left"&gt;&lt;b&gt;関係性&lt;/b&gt;&lt;/td&gt;
					&lt;td style="text-align: left"&gt;フォロー / アンフォロー&lt;/td&gt;
					&lt;td style="text-align: left"&gt;ブロック機能、非公開アカウント&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style="text-align: left"&gt;&lt;b&gt;投稿&lt;/b&gt;&lt;/td&gt;
					&lt;td style="text-align: left"&gt;単一画像アップロード、キャプション、削除&lt;/td&gt;
					&lt;td style="text-align: left"&gt;動画、複数画像（カルーセル）、フィルタ&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style="text-align: left"&gt;&lt;b&gt;フィード&lt;/b&gt;&lt;/td&gt;
					&lt;td style="text-align: left"&gt;フォロー中ユーザーの時系列リスト&lt;/td&gt;
					&lt;td style="text-align: left"&gt;AIレコメンデーション、無限スクロール最適化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td style="text-align: left"&gt;&lt;b&gt;インタラクション&lt;/b&gt;&lt;/td&gt;
					&lt;td style="text-align: left"&gt;いいね、コメント（作成・一覧）&lt;/td&gt;
					&lt;td style="text-align: left"&gt;返信（スレッド）、ブックマーク&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="2-技術スタックの選定理由decoupled-architecture"&gt;2. 技術スタックの選定理由：Decoupled Architecture
&lt;/h2&gt;&lt;p&gt;SNS特有の動的なUX（ページ遷移なしのインタラクション）を実現するため、Spring Boot + Thymeleafのモノリス構成ではなく、Spring Boot + Reactの分離構成を採用します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;UXの向上&lt;/b&gt;: ReactによるSPA（Single Page Application）構成により、無限スクロールや非同期の「いいね」処理など、アプリライクな操作感を提供します。&lt;/li&gt;
&lt;li&gt;&lt;b&gt;拡張性&lt;/b&gt;: 将来的なモバイルアプリ（React Native等）への展開を見据え、バックエンドをREST APIとして独立させます。&lt;/li&gt;
&lt;li&gt;&lt;b&gt;開発コスト&lt;/b&gt;: 初期設定（CORS、JWT、DTO設計）のコストは増加しますが、長期的なメンテナンス性とスケーラビリティにおいて優位性があります。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-データモデリングとerd設計"&gt;3. データモデリングとERD設計
&lt;/h2&gt;&lt;p&gt;リレーショナルデータベース（PostgreSQL）を用いた、コアエンティティの設計仕様です。&lt;/p&gt;
&lt;h3 id="主要テーブル構造"&gt;主要テーブル構造
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;b&gt;users&lt;/b&gt;: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;password_hash&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;, &lt;code&gt;avatar_url&lt;/code&gt;, &lt;code&gt;bio&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;follows&lt;/b&gt;: &lt;code&gt;follower_id&lt;/code&gt;, &lt;code&gt;following_id&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt; (複合主キー)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;posts&lt;/b&gt;: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;image_url&lt;/code&gt;, &lt;code&gt;caption&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;likes&lt;/b&gt;: &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;post_id&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt; (複合主キー)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;comments&lt;/b&gt;: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;post_id&lt;/code&gt;, &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;body&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="フィード生成ロジックmvp版"&gt;フィード生成ロジック（MVP版）
&lt;/h3&gt;&lt;p&gt;初期段階では、以下のクエリロジックによりフィードを生成します。ユーザー規模が拡大した場合は、Redisを用いたタイムラインキャッシュへの移行を検討します。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; p.&lt;span style="color:#f92672"&gt;*&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; posts p
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;JOIN&lt;/span&gt; follows f &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; p.user_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; f.following_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WHERE&lt;/span&gt; f.follower_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; :currentUserId
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ORDER&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;BY&lt;/span&gt; p.created_at &lt;span style="color:#66d9ef"&gt;DESC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;LIMIT&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;OFFSET&lt;/span&gt; :&lt;span style="color:#66d9ef"&gt;offset&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="4-バックエンド実装仕様-spring-boot-33"&gt;4. バックエンド実装仕様 (Spring Boot 3.3)
&lt;/h2&gt;&lt;h3 id="プロジェクト構造"&gt;プロジェクト構造
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;src/main/java/com/example/instagram
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── config // SecurityConfig, WebMvcConfig, CloudConfig
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── controller // AuthController, PostController, UserController
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── dto // Request/Response DTOs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── entity // JPA Entities (User, Post, Follow, etc.)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── repository // JpaRepository Interfaces
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── service // Business Logic (AuthService, PostService)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="セキュリティと認証-jwt"&gt;セキュリティと認証 (JWT)
&lt;/h3&gt;&lt;p&gt;ステートレスな認証を実現するため、JWT（JSON Web Token）を採用します。パスワードはBCryptでハッシュ化し、トークンは以下のライフサイクルで運用します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Access Token&lt;/b&gt;: &lt;code&gt;Authorization: Bearer&lt;/code&gt; ヘッダーで送信。有効期限は15分〜1時間。&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Refresh Token&lt;/b&gt;: &lt;code&gt;HttpOnly&lt;/code&gt; クッキーに保存。有効期限は7日〜14日。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; String &lt;span style="color:#a6e22e"&gt;generateAccessToken&lt;/span&gt;(UserDetails userDetails) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Jwts.&lt;span style="color:#a6e22e"&gt;builder&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;setSubject&lt;/span&gt;(userDetails.&lt;span style="color:#a6e22e"&gt;getUsername&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;setIssuedAt&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;setExpiration&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date(System.&lt;span style="color:#a6e22e"&gt;currentTimeMillis&lt;/span&gt;() &lt;span style="color:#f92672"&gt;+&lt;/span&gt; ACCESS_TOKEN_EXPIRY))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;signWith&lt;/span&gt;(SignatureAlgorithm.&lt;span style="color:#a6e22e"&gt;HS512&lt;/span&gt;, secretKey)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;compact&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="5-パフォーマンスと運用の最適化"&gt;5. パフォーマンスと運用の最適化
&lt;/h2&gt;&lt;h3 id="jpa-n1問題の回避"&gt;JPA N+1問題の回避
&lt;/h3&gt;&lt;p&gt;フィード取得時、投稿ごとにユーザー情報を取得するN+1問題を回避するため、&lt;code&gt;fetch join&lt;/code&gt;または&lt;code&gt;EntityGraph&lt;/code&gt;を適用します。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@Query&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;SELECT p FROM Post p JOIN FETCH p.user WHERE p.user.id IN :followingIds&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;List&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;post&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;findAllByUserIdInOrderByCreatedAtDesc&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;@Param&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;followingIds&amp;#34;&lt;/span&gt;) List&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;long&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; followingIds);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="画像ストレージの管理"&gt;画像ストレージの管理
&lt;/h3&gt;&lt;p&gt;画像ファイルはDBに直接保存せず、AWS S3またはローカルストレージに保存し、DBにはそのURLのみを格納します。アップロード時には、最大5MBの制限と、jpg/png/webpの拡張子バリデーションを実装します。&lt;/p&gt;
&lt;h2 id="6-開発ロードマップ-6週間"&gt;6. 開発ロードマップ (6週間)
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;b&gt;Week 1: Foundation&lt;/b&gt;: 🛠️ Docker環境構築、Flywayによるスキーマ設計、JWT認証の実装。&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Week 2: Profile &amp;amp; Posts&lt;/b&gt;: 🛠️ 画像アップロードロジック、プロフィールCRUD、投稿APIの実装。&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Week 3: Social Graph&lt;/b&gt;: 🛠️ フォロー/アンフォロー機能、フィード生成サービスの構築。&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Week 4: Interaction&lt;/b&gt;: 🛠️ いいね・コメントAPI、カウントロジックの実装。&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Week 5: Integration&lt;/b&gt;: 🛠️ Reactフロントエンドとの結合、Optimistic UI（楽観的更新）の適用。&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Week 6: Deployment&lt;/b&gt;: 🚀 HTTPS設定、ロギング、バックアップ体制の整備とデプロイ。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="operational-notes"&gt;Operational Notes
&lt;/h2&gt;&lt;p&gt;💡 MVP開発において最も避けるべきは「オーバーエンジニアリング」です。初期段階でマイクロサービス化や複雑なAIアルゴリズムを導入せず、まずはモノリスなバックエンドとSPAの疎結合構成を安定させることに注力します。フィードのパフォーマンスがボトルネックとなった時点で、インデックスの最適化やキャッシュ戦略を段階的に導入するのが現実的なアプローチです。&lt;/long&gt;&lt;/post&gt;&lt;/p&gt;</description></item><item><title>WASMベースPostgreSQL PGliteのNodejs組み込みと検証</title><link>https://klifehack.com/p/pglite-wasm-nodejs-integration/</link><pubDate>Mon, 01 Jun 2026 08:12:26 +0900</pubDate><guid>https://klifehack.com/p/pglite-wasm-nodejs-integration/</guid><description>&lt;h2 id="概要とアーキテクチャの特徴"&gt;概要とアーキテクチャの特徴
&lt;/h2&gt;&lt;p&gt;PGliteは、WebAssembly (WASM) にコンパイルされ、クライアントサイドJavaScriptライブラリとしてパッケージ化された、完全に動作する軽量なPostgreSQLエンジンです。ElectricSQLによって開発されたPGliteを使用することで、開発者は個別のPostgreSQLサーバー、Dockerコンテナ、または外部デーモンを必要とせずに、Node.jsランタイム、Webブラウザ、またはモバイル環境（React Native/Capacitor経由）でPostgreSQLデータベースを直接実行できます。&lt;/p&gt;
&lt;p&gt;本稿では、Node.js環境におけるPGliteの初期化、操作、ベンチマーク、およびハイブリッド同期ワークフローのシミュレーション手順について解説します。&lt;/p&gt;
&lt;h3 id="主なアーキテクチャの特徴"&gt;主なアーキテクチャの特徴
&lt;/h3&gt;&lt;p&gt;💡 &lt;b&gt;WASM駆動のPostgreSQLエンジン:&lt;/b&gt; WebAssemblyにコンパイルされた実際のPostgreSQLエンジンを実行するため、SQL構文の互換性が維持されます。&lt;/p&gt;
&lt;p&gt;🛠️ &lt;b&gt;環境に応じたストレージ抽象化:&lt;/b&gt; Node.js環境ではローカルファイルシステムを自動的に利用してデータを永続化し、ブラウザ環境ではIndexedDBにフォールバックします。&lt;/p&gt;
&lt;p&gt;⚡ &lt;b&gt;ネットワークオーバーヘッドの排除:&lt;/b&gt; データベースクエリがインプロセスで実行されるため、従来のデータベース接続に伴うネットワーク遅延が発生しません。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="プロジェクトのセットアップと環境設定"&gt;プロジェクトのセットアップと環境設定
&lt;/h2&gt;&lt;p&gt;ES Modulesを使用するように構成されたNode.js環境を構築し、必要なPGlite依存関係をインストールします。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. プロジェクトディレクトリの作成と移動&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir pglite-demo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd pglite-demo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. package.jsonの初期化とES Modulesの 有効化&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm init -y
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm pkg set type&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;module&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3. PGliteライブラリのインストール&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install @electric-sql/pglite
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="実装コード-indexjs"&gt;実装コード (&lt;code&gt;index.js&lt;/code&gt;)
&lt;/h2&gt;&lt;p&gt;プロジェクトのルートディレクトリに &lt;code&gt;index.js&lt;/code&gt; ファイルを作成し、データベースの初期化、トランザクションを使用した一括挿入ベンチマーク、標準的なCRUD操作、およびオフラインファーストの同期サイクルのシミュレーションを実行するロジックを実装します。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;PGlite&lt;/span&gt; } &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@electric-sql/pglite&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// PGliteインスタンスの初期化
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ローカルの &amp;#34;./pgdata&amp;#34; ディレクトリに永続的なPostgreSQLデータベースを作成またはロードします。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Node.js環境ではローカルファイルシステムを対象とし、ブラウザ環境ではIndexedDBがデフォルトとなります。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PGlite&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;./pgdata&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;🚀 PGlite (Postgres in WASM) 起動中...\n&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. テーブルの初期化 (DDLの実行)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exec&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; CREATE TABLE IF NOT EXISTS notes (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; id SERIAL PRIMARY KEY,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; title TEXT NOT NULL,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; content TEXT,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; synced BOOLEAN DEFAULT false,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; created_at TIMESTAMP DEFAULT NOW()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; `&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;✅ &amp;#39;notes&amp;#39; テーブル準備完了 (Postgres Engine 稼働)\n&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ---------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. パフォーマンスベンチマーク: バルクデータの挿入
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ---------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;📊 [ベンチマーク] メモ100件の挿入テスト開始...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;performance&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;now&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// トランザクションブロックを使用して操作をバッチ処理します。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ネットワークオーバーヘッドがないため、メモリ内のWASM実行は高速です。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;transaction&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gt&lt;/span&gt;; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;lt&lt;/span&gt;; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;INSERT INTO notes (title, content) VALUES ($1, $2)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#e6db74"&gt;`メモ #&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;`これは &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; 回目のテストメモです。`&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;performance&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;now&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`⚡ 完了! 所要時間: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;toFixed&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;ms`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;` (1件あたりの平均処理速度: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;) &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;toFixed&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;ms)\n`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ---------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. CRUDシナリオの実行
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ---------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;📝 [CRUD シナリオ] 実行&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// [Create] - 単一レコードを挿入し、作成された行を返す
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;newNote&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;INSERT INTO notes (title, content) VALUES ($1, $2) RETURNING *&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;重要ミーティング&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;午後2時: Q3ロードマップ議論&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;1. メモ作成:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;newNote&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// [Update] - 作成されたレコードの内容を更新
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;updatedNote&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE notes SET content = $1 WHERE id = $2 RETURNING *&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;午後3時に変更: Q3ロードマップ議論&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;newNote&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;2. メモ修正:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;updatedNote&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rows&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// [Read] - 直近のレコードを3件取得
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;list&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;SELECT * FROM notes ORDER BY created_at DESC LIMIT 3&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;3. 直近のメモ参照 (Top 3):&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;table&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;list&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rows&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gt&lt;/span&gt;; ({ &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;title&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;content&lt;/span&gt; })));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ---------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 4. ハイブリッド同期のシミュレーション
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ---------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;\n🔄 [同期] バックエンド同期シミュレーション...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 未同期のローカルレコード (synced = false) を取得
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;unsyncedParams&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;SELECT * FROM notes WHERE synced = false&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;unsyncedCount&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;unsyncedParams&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rows&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;unsyncedCount&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gt&lt;/span&gt;; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;` -&amp;amp;gt; 同期対象: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;unsyncedCount&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;件検出`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// リモートサーバーへのデータ送信を模したネットワーク遅延 (500ms) のシミュレーション
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Promise(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gt&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;setTimeout&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34; -&amp;amp;gt; ☁️ バックエンド送信完了 (Mock Server)&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ローカルデータベースの状態を更新し、同期済みとしてマーク
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ids&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;unsyncedParams&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rows&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gt&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;UPDATE notes SET synced = true WHERE id = ANY($1)&amp;#34;&lt;/span&gt;, [&lt;span style="color:#a6e22e"&gt;ids&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34; -&amp;amp;gt; ✅ ローカルDBステータスを &amp;#39;Synced&amp;#39; に更新完了&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34; -&amp;amp;gt; 同期するデータはありません。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ---------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 実行終了
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ---------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;\n🎉 すべてのデモが正常に完了しました。&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;().&lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) &lt;span style="color:#f92672"&gt;=&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gt&lt;/span&gt;; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;❌ エラー発生:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="実行手順と期待される出力"&gt;実行手順と期待される出力
&lt;/h2&gt;&lt;p&gt;スクリプトを実行するには、ターミナルで以下のコマンドを実行します。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;node index.js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="期待されるコンソール出力例"&gt;期待されるコンソール出力例
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;🚀 PGlite (Postgres in WASM) 起動中...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;✅ &amp;#39;notes&amp;#39; テーブル準備完了 (Postgres Engine 稼働)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;📊 [ベンチマーク] メモ100件の挿入テスト開始...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;⚡ 完了! 所要時間: 42.50ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; (1件あたりの平均処理速度: 0.43ms)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;📝 [CRUD シナリオ] 実行
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1. メモ作成: { id: 101, title: &amp;#39;重要ミーティング&amp;#39;, ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2. メモ修正: { id: 101, title: &amp;#39;重要ミーティング&amp;#39;, content: &amp;#39;午後3時に変更...&amp;#39;, ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3. 直近のメモ参照 (Top 3):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;┌─────────┬──────────────┬────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ (index) │ id │ title │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├─────────┼──────────────┼────────────────────────────┤
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ 0 │ 101 │ &amp;#39;重要ミーティング&amp;#39; │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ 1 │ 100 │ &amp;#39;メモ #99&amp;#39; │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ 2 │ 99 │ &amp;#39;メモ #98&amp;#39; │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└─────────┴──────────────┴────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;🔄 [同期] バックエンド同期シミュレーション...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;amp;gt; 同期対象: 101件検出
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;amp;gt; ☁️ バックエンド送信完了 (Mock Server)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;amp;gt; ✅ ローカルDBステータスを &amp;#39;Synced&amp;#39; に更新完了
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;🎉 すべてのデモが正常に完了しました。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="アーキテクチャおよび性能の検証ポイント"&gt;アーキテクチャおよび性能の検証ポイント
&lt;/h2&gt;&lt;p&gt;この実装により、PGliteの3つの重要な機能特性が実証されます。&lt;/p&gt;
&lt;h3 id="1-インプロセス実行による低遅延"&gt;1. インプロセス実行による低遅延
&lt;/h3&gt;&lt;p&gt;トランザクションブロック内で100件のレコードを順次挿入する処理は、数十ミリ秒（例: &lt;b&gt;約42.50ms&lt;/b&gt;、1レコードあたり&lt;b&gt;約0.43ms&lt;/b&gt;）で完了します。従来のクライアント・サーバー型データベース構成では、ネットワークの往復、TCPハンドシェイク、およびコネクションプーリングのオーバーヘッドにより、同様の処理に秒単位の時間を要することがあります。PGliteは、データベースエンジンをインプロセスで実行することで、極めて低いオーバーヘッドを実現します。&lt;/p&gt;
&lt;h3 id="2-postgresql構文との互換性"&gt;2. PostgreSQL構文との互換性
&lt;/h3&gt;&lt;p&gt;軽量なキーバリューストアや簡易的なSQL風エンジンとは異なり、PGliteは実際のPostgreSQLエンジンを動作させています。そのため、以下を含む標準的なPostgreSQLのSQL構文をそのままサポートします。&lt;/p&gt;
&lt;p&gt;・ 複雑なDDL (&lt;code&gt;CREATE TABLE IF NOT EXISTS&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;・ トランザクション制御 (&lt;code&gt;db.transaction&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;・ RETURNING句を伴うデータ操作 (&lt;code&gt;INSERT ... RETURNING *&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;・ 配列パラメータを用いた高度なクエリ演算 (&lt;code&gt;WHERE id = ANY($1)&lt;/code&gt;)&lt;/p&gt;
&lt;h3 id="3-データの永続性"&gt;3. データの永続性
&lt;/h3&gt;&lt;p&gt;このスクリプトを複数回実行すると、データが &lt;code&gt;./pgdata&lt;/code&gt; ディレクトリ内に永続化されていることが確認できます。自動インクリメントされる主キー（&lt;code&gt;id&lt;/code&gt;）は1にリセットされず、実行をまたいで増加し続けます（例: 2回目以降の実行では101から開始）。これにより、PGliteが単なるメモリ内の一時的なモックではなく、永続的で信頼性の高いデータベースエンジンとして機能していることが証明されます。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="operational-notes"&gt;Operational Notes
&lt;/h2&gt;&lt;p&gt;💡 &lt;b&gt;ストレージの選択:&lt;/b&gt; Node.js環境ではファイルシステムが使用されますが、ブラウザ環境で動作させる場合は、自動的にIndexedDBが選択されます。環境ごとのストレージアダプタの挙動に留意してください。&lt;/p&gt;
&lt;p&gt;⚠️ &lt;b&gt;リソース消費:&lt;/b&gt; WASM上でフル機能のPostgreSQLを実行するため、メモリ消費量は一般的なキーバリューストアよりも大きくなります。リソース制限の厳しいエッジ環境や古いモバイル端末に展開する場合は、実機でのメモリプロファイリングを推奨します。&lt;/p&gt;
&lt;p&gt;⚠️ &lt;b&gt;同時実行制御:&lt;/b&gt; PGliteはシングルプロセス指向の設計となっています。同一 of データディレクトリに対して複数のNode.jsプロセスから同時に書き込みアクセスを行うと、ロック競合やデータ破損の原因となるため、適切なアクセス制御の設計が必要です。&lt;/p&gt;</description></item></channel></rss>