[{"data":1,"prerenderedAt":831},["ShallowReactive",2],{"content-/turso-migration-multi-tenant-db-design":3,"all-pages-for-dir":829,"og-image-/turso-migration-multi-tenant-db-design":830},{"id":4,"title":5,"body":6,"category":807,"description":808,"extension":809,"meta":810,"navigation":811,"ogImage":812,"path":813,"project_name":759,"published":814,"publishedAt":815,"seo":816,"stem":817,"tags":818,"todo":827,"unpublished":814,"updatedAt":812,"__hash__":828},"pages/2026-04/2026-04-10/turso-migration-multi-tenant-db-design.md","Turso DB移行計画とマルチテナントDBアーキテクチャ設計 - FTS5検証からRLSまで",{"type":7,"value":8,"toc":783},"minimark",[9,14,18,21,26,30,33,36,39,62,64,68,71,75,78,84,88,91,96,100,103,108,111,122,126,141,143,146,149,153,209,215,219,222,227,233,238,300,303,308,310,314,317,321,324,330,333,405,410,414,471,477,483,487,494,497,503,507,510,515,518,587,596,606,611,732,737,744,747,749,752,779],[10,11,13],"h1",{"id":12},"turso-db移行計画とマルチテナントdbアーキテクチャ設計","Turso DB移行計画とマルチテナントDBアーキテクチャ設計",[15,16,17],"p",{},"book-knowledge-baseのSQLite（書籍OCRデータ16冊分、FTS5全文検索付き）をTurso（libSQL）に載せ替える計画を立てた。Codexにレビューを投げたら致命的な指摘が3つ返ってきて、計画を書き直すことになった。そこから「会計データもTursoに入れるか？」という話に流れ、クライアント単位のDB分割、RLS、シャーディングまで一気に掘り下げた。",[19,20],"hr",{},[22,23,25],"h2",{"id":24},"移行計画の策定-sqlite-turso","移行計画の策定: SQLite → Turso",[27,28,29],"h3",{"id":29},"背景",[15,31,32],{},"book-knowledge-baseは書籍をOCRして構造化したデータを16冊分持っている。FTS5で全文検索できるようにしてあり、ローカルのSQLiteファイルで運用中。これをTursoに移すと、Embedded Replicaでローカル/クラウドの同期が手に入る。どの端末からでも同じデータにアクセスでき、ローカルのSQLiteファイルがレプリカとして残るのでオフラインでも読める。",[27,34,35],{"id":35},"最初の計画",[15,37,38],{},"3フェーズで組んだ。",[40,41,42,50,56],"ol",{},[43,44,45,49],"li",{},[46,47,48],"strong",{},"Phase 1",": Turso DBを作成し、既存のSQLiteデータをマイグレーション",[43,51,52,55],{},[46,53,54],{},"Phase 2",": Embedded Replicaの設定とアプリケーション側の接続切り替え",[43,57,58,61],{},[46,59,60],{},"Phase 3",": CI/CDパイプラインの整備とモニタリング",[19,63],{},[22,65,67],{"id":66},"codexレビューで3つの致命的指摘","Codexレビューで3つの致命的指摘",[15,69,70],{},"計画をCodex（GPT-5.4）に投げてレビューしてもらった。「瑣末な点は無視しろ、致命的な点だけ指摘しろ」と指示したところ、3つ返ってきた。",[27,72,74],{"id":73},"指摘1-fts5の互換性が未検証","指摘1: FTS5の互換性が未検証",[15,76,77],{},"TursoはlibSQL（SQLiteフォーク）だが、FTS5拡張がそのまま動く保証がない。移行してからFTS5が使えないと判明したら全部巻き戻しになる。",[15,79,80,83],{},[46,81,82],{},"対応",": Phase 0（FTS5検証フェーズ）を計画の先頭に追加した。Turso上でFTS5テーブルを作成し、MATCH構文でクエリが通るかを確認する。ここで詰まったら移行自体を見送る判断ができる。",[27,85,87],{"id":86},"指摘2-データサイズとtursoの無料枠","指摘2: データサイズとTursoの無料枠",[15,89,90],{},"16冊分のOCRデータがTursoの無料枠（9GB）に収まるか確認していなかった。",[15,92,93,95],{},[46,94,82],{},": 移行前にローカルのSQLiteファイルサイズを計測し、Turso側のストレージ使用量を見積もるステップを追加。",[27,97,99],{"id":98},"指摘3-ロールバック手順の欠如","指摘3: ロールバック手順の欠如",[15,101,102],{},"移行後に問題が見つかった場合の切り戻し手順がなかった。",[15,104,105,107],{},[46,106,82],{},": 各フェーズにロールバック手順を明記。Embedded Replicaのローカルファイルが残るので、接続先をローカルに戻すだけで切り戻せる設計にした。",[27,109,110],{"id":110},"修正後の計画",[112,113,118],"pre",{"className":114,"code":116,"language":117},[115],"language-text","Phase 0: FTS5互換性検証（Turso上でFTS5テーブル作成 → MATCHクエリ実行）\nPhase 1: データサイズ見積もり → Turso DB作成 → マイグレーション\nPhase 2: Embedded Replica設定 → アプリ接続切り替え\nPhase 3: CI/CD整備 → モニタリング → ロールバック手順テスト\n","text",[119,120,116],"code",{"__ignoreMap":121},"",[27,123,125],{"id":124},"phase-0に着手したが積み残し","Phase 0に着手したが積み残し",[15,127,128,129,132,133,136,137,140],{},"Phase 0を開始して、Turso上でFTS5テーブルのCREATE文を流すところまで進めた。しかし、接続に必要な",[119,130,131],{},".env","の設定（",[119,134,135],{},"TURSO_DATABASE_URL","と",[119,138,139],{},"TURSO_AUTH_TOKEN","）がまだ済んでおらず、実際のクエリ検証は翌日に持ち越しとなった。",[19,142],{},[22,144,145],{"id":145},"クラウド会計データの格納先設計",[15,147,148],{},"移行計画と並行して、クラウド会計データ（会計ソフトA・会計ソフトBから取得した仕訳・財務データ）をどこに置くかの議論に入った。",[27,150,152],{"id":151},"_3つの選択肢を比較","3つの選択肢を比較",[154,155,156,172],"table",{},[157,158,159],"thead",{},[160,161,162,166,169],"tr",{},[163,164,165],"th",{},"格納先",[163,167,168],{},"向いている用途",[163,170,171],{},"向かない用途",[173,174,175,187,198],"tbody",{},[160,176,177,181,184],{},[178,179,180],"td",{},"スプレッドシート",[178,182,183],{},"人が目で確認・手で修正する",[178,185,186],{},"プログラムが大量にCRUDする",[160,188,189,192,195],{},[178,190,191],{},"ローカルSQLite",[178,193,194],{},"単一マシンで完結する処理",[178,196,197],{},"複数端末からのアクセス",[160,199,200,203,206],{},[178,201,202],{},"Turso",[178,204,205],{},"プログラムがCRUDし、複数端末で同期する",[178,207,208],{},"人がGUIで直接いじりたい",[15,210,211,214],{},[46,212,213],{},"結論",": 人が見るデータはスプレッドシートに残し、プログラムが扱うデータはTursoに入れる。両方に同じデータを持たせるのではなく、用途で分ける。",[27,216,218],{"id":217},"クライアント単位のdb分割をどうするか","クライアント単位のDB分割をどうするか",[15,220,221],{},"会計データはクライアント（顧問先）ごとに存在する。2つの選択肢を検討した。",[15,223,224],{},[46,225,226],{},"案A: クライアントごとにDBを分ける（DB-per-tenant）",[112,228,231],{"className":229,"code":230,"language":117},[115],"client_tanaka.db → Turso DB 1つ目\nclient_suzuki.db → Turso DB 2つ目\n",[119,232,230],{"__ignoreMap":121},[15,234,235],{},[46,236,237],{},"案B: 1つのDBにclient_idカラムで分ける（共有DB）",[112,239,243],{"className":240,"code":241,"language":242,"meta":121,"style":121},"language-sql shiki shiki-themes vitesse-light vitesse-light","SELECT * FROM journals WHERE client_id = 'tanaka' AND fiscal_year = 2025;\n","sql",[119,244,245],{"__ignoreMap":121},[246,247,250,254,258,261,265,268,271,274,278,282,285,288,291,293,297],"span",{"class":248,"line":249},"line",1,[246,251,253],{"class":252},"sHkkW","SELECT",[246,255,257],{"class":256},"stQ0i"," *",[246,259,260],{"class":252}," FROM",[246,262,264],{"class":263},"sG7-3"," journals ",[246,266,267],{"class":252},"WHERE",[246,269,270],{"class":263}," client_id ",[246,272,273],{"class":256},"=",[246,275,277],{"class":276},"sMJiu"," '",[246,279,281],{"class":280},"sdGka","tanaka",[246,283,284],{"class":276},"'",[246,286,287],{"class":252}," AND",[246,289,290],{"class":263}," fiscal_year ",[246,292,273],{"class":256},[246,294,296],{"class":295},"sM54T"," 2025",[246,298,299],{"class":263},";\n",[15,301,302],{},"最初は「クライアント数が数十件だからDBを分けるのは管理コストが高い」と思ったが、後のマルチテナント設計の議論で考えが変わった。TursoはSQLiteベースでRLSがない。共有DBでWHEREを忘れると全クライアントのデータが丸見えになる。DB-per-tenantなら接続先のDBにそのクライアントのデータしか存在しないので、WHERE忘れの事故が構造的に起きない。",[15,304,305,307],{},[46,306,213],{},": DB-per-tenant方式を採用。TursoのDB作成コストはほぼゼロで、数十件のクライアントならFree枠（500DB）に十分収まる。スキーマ変更の手間は自動化スクリプトで対処できる。安全側に倒すならDB分離が正解。",[19,309],{},[22,311,313],{"id":312},"マルチテナントdbアーキテクチャの深掘り","マルチテナントDBアーキテクチャの深掘り",[15,315,316],{},"クライアント単位のDB分割の議論から、マルチテナントアーキテクチャ全般の話に発展した。",[27,318,320],{"id":319},"db-per-tenant-vs-共有dbrls-デフォルトの挙動で選ぶ","DB-per-tenant vs 共有DB+RLS — 「デフォルトの挙動」で選ぶ",[15,322,323],{},"この議論の核心は「何もしなかったとき、何が起きるか」に集約される。",[112,325,328],{"className":326,"code":327,"language":117},[115],"DB-per-tenant:    書かないとデータに到達できない（デフォルト拒否）\n共有DB（RLSあり）: DBが自動でフィルタする（デフォルト拒否）\n共有DB（RLSなし）: 書かないと全部見える（デフォルト許可 = 危険）\n",[119,329,327],{"__ignoreMap":121},[15,331,332],{},"DB-per-tenantでは、接続先のDBにそのテナントのデータしか物理的に存在しない。WHEREを書き忘れても他社のデータは返らない。守るべきポイントは「接続先のルーティング」1箇所だけ。一方、共有DBでWHEREを忘れると全テナントのデータが丸見えになる。RLSはこの穴をDB側で塞ぐ仕組みだ。",[154,334,335,348],{},[157,336,337],{},[160,338,339,342,345],{},[163,340,341],{},"観点",[163,343,344],{},"DB-per-tenant",[163,346,347],{},"共有DB + RLS",[173,349,350,361,372,383,394],{},[160,351,352,355,358],{},[178,353,354],{},"データ分離",[178,356,357],{},"物理的に分離。WHERE不要",[178,359,360],{},"論理的に分離。RLSポリシーで制御",[160,362,363,366,369],{},[178,364,365],{},"スキーマ変更",[178,367,368],{},"全DBに個別適用が必要",[178,370,371],{},"1回のマイグレーションで済む",[160,373,374,377,380],{},[178,375,376],{},"スケール",[178,378,379],{},"DB数が増えると管理が煩雑",[178,381,382],{},"1DBが巨大化するとシャーディングが必要",[160,384,385,388,391],{},[178,386,387],{},"コスト",[178,389,390],{},"Turso Free: 500DB、Developer: ~1000DB",[178,392,393],{},"DB数は1つで済むが、RLSの実装コスト",[160,395,396,399,402],{},[178,397,398],{},"安全側に倒れるか",[178,400,401],{},"YES（何もしなくても安全）",[178,403,404],{},"YES（設定が正しければ）",[15,406,407],{},[46,408,409],{},"SQLite（Turso）にはRLSがないからDB-per-tenant。PostgreSQL（Supabase等）にはRLSがあるから共有DB。DBエンジンのセキュリティ機能が推奨アーキテクチャを決めている。",[27,411,413],{"id":412},"b2b-vs-b2c-最初のdb選択で分ける","B2B vs B2C — 最初のDB選択で分ける",[154,415,416,432],{},[157,417,418],{},[160,419,420,423,426,429],{},[163,421,422],{},"ユースケース",[163,424,425],{},"推奨アーキテクチャ",[163,427,428],{},"推奨サービス",[163,430,431],{},"月額目安",[173,433,434,455],{},[160,435,436,442,444,446],{},[178,437,438,441],{},[46,439,440],{},"B2B","（テナント数 数百〜数千）",[178,443,344],{},[178,445,202],{},[178,447,448,454],{},[119,449,453],{"className":450},[451,452],"language-math","math-inline","0〜","5",[160,456,457,463,465,468],{},[178,458,459,462],{},[46,460,461],{},"B2C","（ユーザー数 数万〜数百万）",[178,464,347],{},[178,466,467],{},"Supabase, Neon, Railway",[178,469,470],{},"$5〜",[15,472,473,476],{},[46,474,475],{},"B2Bなら最初からTurso","。テナント数が限られるのでDB-per-tenantの無料枠（500DB）で十分回る。SQLiteベースでDB作成コストがほぼゼロだから、テナントが増えてもコストが跳ねない。企業顧客がデータ分離を契約で求める場面でも、物理的に別DBなので説明しやすい。",[15,478,479,482],{},[46,480,481],{},"B2Cなら最初からPostgreSQL","。ユーザー数が万単位に膨らむ見込みがある場合、Tursoで始めて後からPostgreSQLに移行するのは二度手間になる。最初からRLS付きの共有DBで設計しておけば、ユーザーが増えてもアーキテクチャを変える必要がない。",[27,484,486],{"id":485},"シャーディングとsalesforceの事例","シャーディングとSalesforceの事例",[15,488,489,490,493],{},"共有DBが巨大化したときの対処がシャーディング。同じ構造のDBを複数作り、テナントを振り分ける。ルーティングテーブル（",[119,491,492],{},"tenant_id → shard_id","）で接続先を決める。Salesforceは数十万社を共有DBに入れつつ、シャード内はRLSでアクセス制御している。",[15,495,496],{},"スケール時の成長パスは「最初はDB-per-tenant、壁にぶつかったら移行」で十分。",[112,498,501],{"className":499,"code":500,"language":117},[115],"10社:      DB-per-tenant（Turso Free）→ $0\n500社:     DB-per-tenant（Turso Scaler）→ $29\n5,000社:   Enterprise交渉 or シャーディング検討\n10,000社+: PostgreSQL系に移行 or 専任チームが再設計\n",[119,502,500],{"__ignoreMap":121},[27,504,506],{"id":505},"supabase-rlsの設定漏れ事故-4つのパターン","Supabase RLSの設定漏れ事故 — 4つのパターン",[15,508,509],{},"Supabaseは「フロントエンドから直接DBを叩く」設計のため、RLSの設定ミスが即データ漏洩に直結する。従来型（Railway等）ではバックエンドがDBの前に立つのでRLSがなくても多層防御が効くが、Supabaseではバックエンドが存在しないためRLSが唯一の防壁になる。",[15,511,512],{},[46,513,514],{},"パターン1: RLS有効化忘れ",[15,516,517],{},"SQLやマイグレーションで作ったテーブルはRLSがデフォルト無効。Dashboard経由なら有効だが、コードで作ると忘れやすい。2025年1月にノーコードツールLovableで170以上のアプリがRLS無効のまま公開されていた事故が発覚した。",[112,519,521],{"className":240,"code":520,"language":242,"meta":121,"style":121},"CREATE TABLE invoices (id serial, client_id text, amount numeric);\n-- ↓ これを忘れると anon key で全データが丸見え\nALTER TABLE invoices ENABLE ROW LEVEL SECURITY;\n",[119,522,523,555,562],{"__ignoreMap":121},[246,524,525,528,531,535,538,541,544,546,549,552],{"class":248,"line":249},[246,526,527],{"class":252},"CREATE",[246,529,530],{"class":252}," TABLE",[246,532,534],{"class":533},"senZ8"," invoices",[246,536,537],{"class":263}," (id ",[246,539,540],{"class":256},"serial",[246,542,543],{"class":263},", client_id ",[246,545,117],{"class":256},[246,547,548],{"class":263},", amount ",[246,550,551],{"class":256},"numeric",[246,553,554],{"class":263},");\n",[246,556,558],{"class":248,"line":557},2,[246,559,561],{"class":560},"sxvE3","-- ↓ これを忘れると anon key で全データが丸見え\n",[246,563,565,568,570,573,576,579,582,585],{"class":248,"line":564},3,[246,566,567],{"class":252},"ALTER",[246,569,530],{"class":252},[246,571,572],{"class":263}," invoices ",[246,574,575],{"class":252},"ENABLE",[246,577,578],{"class":252}," ROW",[246,580,581],{"class":252}," LEVEL",[246,583,584],{"class":252}," SECURITY",[246,586,299],{"class":263},[15,588,589],{},[46,590,591,592,595],{},"パターン2: ",[119,593,594],{},"service_role"," キーのフロントエンド露出",[15,597,598,599,602,603,605],{},"Supabaseには",[119,600,601],{},"anon"," key（RLSに従う）と",[119,604,594],{}," key（RLSをバイパス）の2種類がある。後者をフロントエンドに埋め込むと、RLSを設定してあっても全データにアクセスできてしまう。",[15,607,608],{},[46,609,610],{},"パターン3: SELECTポリシーの条件が甘い",[112,612,614],{"className":240,"code":613,"language":242,"meta":121,"style":121},"-- ダメ: 認証済みなら誰でも全行読める\nCREATE POLICY \"read all\" ON posts\n  FOR SELECT USING (auth.role() = 'authenticated');\n-- 正しい: 自分のデータだけ\nCREATE POLICY \"own data\" ON posts\n  FOR SELECT USING (auth.uid() = user_id);\n",[119,615,616,621,643,683,689,707],{"__ignoreMap":121},[246,617,618],{"class":248,"line":249},[246,619,620],{"class":560},"-- ダメ: 認証済みなら誰でも全行読める\n",[246,622,623,625,628,631,634,637,640],{"class":248,"line":557},[246,624,527],{"class":252},[246,626,627],{"class":252}," POLICY",[246,629,630],{"class":276}," \"",[246,632,633],{"class":280},"read all",[246,635,636],{"class":276},"\"",[246,638,639],{"class":252}," ON",[246,641,642],{"class":263}," posts\n",[246,644,645,648,651,654,657,661,664,667,671,674,676,679,681],{"class":248,"line":564},[246,646,647],{"class":252},"  FOR",[246,649,650],{"class":252}," SELECT",[246,652,653],{"class":252}," USING",[246,655,656],{"class":263}," (",[246,658,660],{"class":659},"snbK4","auth",[246,662,663],{"class":263},".",[246,665,666],{"class":659},"role",[246,668,670],{"class":669},"shFtX","()",[246,672,673],{"class":256}," =",[246,675,277],{"class":276},[246,677,678],{"class":280},"authenticated",[246,680,284],{"class":276},[246,682,554],{"class":263},[246,684,686],{"class":248,"line":685},4,[246,687,688],{"class":560},"-- 正しい: 自分のデータだけ\n",[246,690,692,694,696,698,701,703,705],{"class":248,"line":691},5,[246,693,527],{"class":252},[246,695,627],{"class":252},[246,697,630],{"class":276},[246,699,700],{"class":280},"own data",[246,702,636],{"class":276},[246,704,639],{"class":252},[246,706,642],{"class":263},[246,708,710,712,714,716,718,720,722,725,727,729],{"class":248,"line":709},6,[246,711,647],{"class":252},[246,713,650],{"class":252},[246,715,653],{"class":252},[246,717,656],{"class":263},[246,719,660],{"class":659},[246,721,663],{"class":263},[246,723,724],{"class":659},"uid",[246,726,670],{"class":669},[246,728,673],{"class":256},[246,730,731],{"class":263}," user_id);\n",[15,733,734],{},[46,735,736],{},"パターン4: Realtime + RLSの設定不足",[15,738,739,740,743],{},"リアルタイム更新（チャット等）でもSELECTポリシーが必要。加えて、更新前のデータ（old record）を受け取るには",[119,741,742],{},"REPLICA IDENTITY FULL","の設定が要るが、RLS有効時はold recordに主キーしか含まれない制約がある。",[15,745,746],{},"RLSは設定さえ正しければデータを守れるが、「設定が正しいこと」を検証し続ける仕組み（CIでのポリシーテスト、Supabase Security Advisor等）がないと穴が開く。",[19,748],{},[22,750,751],{"id":751},"今日の判断まとめ",[753,754,755,764,770,776],"ul",{},[43,756,757,760,761,763],{},[46,758,759],{},"book-knowledge-base",": Turso移行計画をCodexレビューで修正し、Phase 0（FTS5検証）から着手。",[119,762,131],{},"設定が残っており翌日に持ち越し",[43,765,766,769],{},[46,767,768],{},"会計データ格納",": 人が見る→スプレッドシート、プログラムが扱う→Turso。クライアントごとにDBを分離（DB-per-tenant）",[43,771,772,775],{},[46,773,774],{},"マルチテナント設計",": B2B（Turso/SQLite）→DB-per-tenant、B2C（PostgreSQL）→共有DB+RLS。自分の用途（会計顧問先数十件）はTurso + DB-per-tenantで安全側に倒す",[43,777,778],{},"議論内容をマークダウンに保存し、メールで自分宛に送信して記録を残した",[780,781,782],"style",{},"html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .snbK4, html code.shiki .snbK4{--shiki-default:#A65E2B;--shiki-dark:#A65E2B}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}",{"title":121,"searchDepth":557,"depth":557,"links":784},[785,789,796,800,806],{"id":24,"depth":557,"text":25,"children":786},[787,788],{"id":29,"depth":564,"text":29},{"id":35,"depth":564,"text":35},{"id":66,"depth":557,"text":67,"children":790},[791,792,793,794,795],{"id":73,"depth":564,"text":74},{"id":86,"depth":564,"text":87},{"id":98,"depth":564,"text":99},{"id":110,"depth":564,"text":110},{"id":124,"depth":564,"text":125},{"id":145,"depth":557,"text":145,"children":797},[798,799],{"id":151,"depth":564,"text":152},{"id":217,"depth":564,"text":218},{"id":312,"depth":557,"text":313,"children":801},[802,803,804,805],{"id":319,"depth":564,"text":320},{"id":412,"depth":564,"text":413},{"id":485,"depth":564,"text":486},{"id":505,"depth":564,"text":506},{"id":751,"depth":557,"text":751},"dev","書籍OCRデータ16冊分のSQLiteをTurso（libSQL）に移行する計画を策定し、Codexレビューで3点の致命的指摘を受けて修正。さらにクラウド会計データのDB設計でマルチテナントアーキテクチャを掘り下げた記録","md",{},true,null,"/turso-migration-multi-tenant-db-design",false,"2026-04-10T00:00:00.000Z",{"title":5,"description":808},"2026-04/2026-04-10/turso-migration-multi-tenant-db-design",[202,819,820,821,822,823,824,825,826],"libSQL","SQLite","FTS5","マルチテナント","RLS","Embedded Replica","DB設計","Supabase","active","CSP_SyH9o4pxc0OQnuT0Fo0ZYv6dMQIMzywd-5kYasU",[],"https://log.eurekapu.com/og/blog/turso-migration-multi-tenant-db-design.png?v=2026-04-10T00%3A00%3A00.000Z&title=Turso%20DB%E7%A7%BB%E8%A1%8C%E8%A8%88%E7%94%BB%E3%81%A8%E3%83%9E%E3%83%AB%E3%83%81%E3%83%86%E3%83%8A%E3%83%B3%E3%83%88DB%E3%82%A2%E3%83%BC%E3%82%AD%E3%83%86%E3%82%AF%E3%83%81%E3%83%A3%E8%A8%AD%E8%A8%88%20-%20FTS5%E6%A4%9C%E8%A8%BC%E3%81%8B%E3%82%89RLS%E3%81%BE%E3%81%A7&author=Kei%20Komatsu&sig=c13c80710def020f",1782528825269]