Start HereOrdered learning track

Cluster, Database, Schema, Role, and Search Path Boundaries

Learn PostgreSQL in Action - Part 004

Deep production model of PostgreSQL cluster, database, schema, role, ownership, privileges, search_path, namespace boundaries, tenant design, and Java application implications.

14 min read2748 words
PrevNext
Lesson 0435 lesson track0106 Start Here
#postgresql#database#schema#security+3 more

Part 004 — Cluster, Database, Schema, Role, and Search Path Boundaries

1. Tujuan Bagian Ini

Bagian ini membahas boundary paling sering disalahpahami di PostgreSQL:

  • cluster;
  • database;
  • schema;
  • role;
  • ownership;
  • privileges;
  • search_path;
  • extension placement;
  • application runtime identity;
  • migration identity;
  • tenant boundary.

Banyak bug production bukan berasal dari query yang salah, tetapi dari boundary yang kabur:

  • aplikasi menulis ke schema yang salah karena search_path;
  • migration tool membuat object dengan owner yang salah;
  • role aplikasi punya privilege DDL terlalu luas;
  • extension dibuat di public tanpa kontrol;
  • tenant dipisah dengan schema tetapi search path bocor;
  • privilege diberikan ke user langsung, bukan role group;
  • CI test lewat karena user superuser, production gagal karena least privilege;
  • backup/restore gagal memulihkan ownership/default privilege dengan benar.

Target part ini adalah membuat model yang defensible untuk production Java system.

2. Mental Model Hierarchy

PostgreSQL memiliki hirarki konseptual seperti ini:

Important distinction:

LevelScopeContohCatatan
Cluster / instancesatu data directory/serverroles, databases, tablespacesroles bersifat cluster-wide
Databasenamespace besar dalam clusterappdb, reportingkoneksi client masuk ke satu database
Schemanamespace object dalam databaseapp, audit, exttable/function/type hidup di schema
Objectentity databasetable, view, index, sequence, functionpunya owner dan privilege
Roleidentity/permission principalapp_runtime, app_migratorbisa login atau non-login group

Kalimat kuncinya:

Database membatasi koneksi dan catalog scope. Schema membatasi namespace object. Role membatasi identitas dan privilege.

3. Cluster Bukan HA Cluster

Dalam dokumentasi PostgreSQL, “database cluster” sering berarti kumpulan database yang dikelola oleh satu PostgreSQL server instance dalam satu data directory.

Ini berbeda dari:

  • Kubernetes cluster;
  • Patroni cluster;
  • cloud managed HA cluster;
  • physical replication topology;
  • Citus/sharding cluster.

Satu PostgreSQL cluster/instance dapat memiliki banyak database:

select datname, datallowconn, datistemplate
from pg_database
order by datname;

Namun role bersifat global di cluster:

select rolname, rolcanlogin, rolsuper, rolcreatedb, rolcreaterole
from pg_roles
order by rolname;

Implikasi:

  • Membuat role untuk satu aplikasi tetap terlihat di cluster yang sama.
  • Default privileges harus dipikirkan per database/schema/object owner.
  • Memisahkan aplikasi dengan database berbeda tidak otomatis memisahkan role namespace.
  • CREATE DATABASE bukan operasi ringan seperti CREATE SCHEMA.

4. Database Boundary

Koneksi PostgreSQL selalu masuk ke satu database tertentu.

jdbc:postgresql://host:5432/appdb

Di dalam satu koneksi normal, query tidak bisa langsung join table dari database lain seperti:

select * from appdb.public.orders o
join analytics.public.order_facts f on f.order_id = o.id;

Itu bukan model PostgreSQL biasa. Untuk akses lintas database, opsi yang biasa digunakan adalah:

  • foreign data wrapper;
  • dblink;
  • logical replication;
  • ETL/ELT pipeline;
  • aplikasi melakukan dua koneksi;
  • memodelkan ulang agar object berada dalam database yang sama.

4.1 Kapan Memakai Database Terpisah?

Database terpisah masuk akal ketika:

  • lifecycle backup/restore harus berbeda;
  • ownership dan access boundary benar-benar berbeda;
  • workload sangat berbeda;
  • extension/config/database-level setting berbeda;
  • aplikasi memang tidak perlu join lintas boundary;
  • restore sebagian aplikasi lebih penting daripada query lintas domain.

Database terpisah kurang cocok ketika:

  • domain sering join secara transactional;
  • migration cross-domain harus atomik;
  • reporting butuh consistent snapshot lintas domain;
  • aplikasi Java akhirnya membuka banyak koneksi dan melakukan join manual;
  • constraint referential integrity lintas boundary dibutuhkan.

4.2 Anti-Pattern: Database per Microservice Tanpa Boundary Real

“Database per service” sering benar sebagai prinsip ownership. Tetapi jika service masih melakukan join konseptual, foreign key konseptual, dan transaksi bisnis lintas database, maka boundary itu hanya memindahkan kompleksitas ke aplikasi.

Pertanyaan arsitektural yang lebih tepat:

Apakah data ini punya lifecycle, owner, scaling pressure, consistency requirement, dan recovery boundary yang berbeda?

Bukan:

Apakah diagram microservice terlihat lebih bersih jika setiap kotak punya database sendiri?

5. Schema Boundary

Schema adalah namespace object di dalam database.

Contoh:

create schema app;
create schema audit;
create schema ext;

Object dengan nama sama bisa ada di schema berbeda:

create table app.events (
    id bigint generated always as identity primary key,
    payload jsonb not null
);

create table audit.events (
    id bigint generated always as identity primary key,
    actor text not null,
    action text not null,
    created_at timestamptz not null default now()
);

Mereka berbeda karena fully qualified name-nya berbeda:

select * from app.events;
select * from audit.events;

5.1 Schema untuk Apa?

Gunakan schema untuk:

  • memisahkan namespace aplikasi (app, billing, case_management);
  • memisahkan audit object (audit);
  • menaruh extension (ext);
  • menaruh internal job/queue object (job);
  • menaruh staging/import object (staging);
  • menaruh read model/materialized view (read_model);
  • menaruh tenant jika desain schema-per-tenant memang dipilih.

Jangan gunakan schema hanya untuk membuat struktur terlihat “rapi” jika konsekuensi privilege, search path, migration, dan observability tidak dipikirkan.

5.2 Schema Tidak Sama dengan Security Boundary Penuh

Schema membantu privilege boundary, tetapi bukan sandbox penuh. Jika role punya privilege luas atau search_path tidak aman, schema boundary bisa bocor secara operasional.

Security boundary membutuhkan kombinasi:

  • role design;
  • ownership design;
  • privileges;
  • default privileges;
  • row level security jika perlu;
  • controlled search_path;
  • migration discipline;
  • testing dengan user non-superuser.

6. Role Model

Di PostgreSQL, user dan group secara modern sama-sama direpresentasikan sebagai role. Role bisa memiliki LOGIN, bisa tidak. Role bisa mewarisi privilege dari role lain.

Pattern production yang sehat:

6.1 Role Kategori

RoleLOGIN?FungsiCatatan
owner rolenomemiliki objecttidak dipakai aplikasi runtime
runtime roleyesdipakai Java serviceprivilege minimal untuk read/write
migrator roleyesdipakai Flyway/LiquibaseDDL terbatas dan terkontrol
readonly roleyesanalytics/supportread-only, sering memakai replica
admin rolemaybe/nobreak-glass/opsaudit ketat

Mengapa owner role sebaiknya berbeda dari runtime role?

Karena owner object bisa melakukan banyak hal terhadap object miliknya. Jika runtime role juga owner, aplikasi yang compromise memiliki kemampuan lebih besar daripada yang dibutuhkan.

6.2 Create Role Example

create role app_owner no login;
create role app_runtime login password 'change-me';
create role app_migrator login password 'change-me';
create role app_readonly login password 'change-me';

Di production, password tidak ditulis manual seperti ini di migration biasa. Gunakan secret manager/cloud IAM/mechanism resmi. Contoh ini hanya untuk lab.

7. Ownership vs Privileges

Ini salah satu perbedaan paling penting.

  • Owner adalah pemilik object.
  • Privilege adalah hak yang diberikan kepada role tertentu.

Owner biasanya bisa mengubah/drop/grant object. Role yang diberi privilege hanya bisa melakukan aksi sesuai grant.

Contoh:

alter schema app owner to app_owner;

create table app.orders (
    id bigint generated always as identity primary key,
    customer_id bigint not null,
    status text not null,
    created_at timestamptz not null default now()
);

alter table app.orders owner to app_owner;

grant usage on schema app to app_runtime;
grant select, insert, update, delete on app.orders to app_runtime;

7.1 Default Privileges

Masalah umum: migration membuat table baru, tetapi runtime role tidak punya akses karena grant hanya diberikan ke table lama.

Solusinya gunakan default privileges untuk object baru yang dibuat oleh owner/migrator tertentu.

Contoh:

alter default privileges for role app_owner in schema app
    grant select, insert, update, delete on tables to app_runtime;

alter default privileges for role app_owner in schema app
    grant usage, select, update on sequences to app_runtime;

Penting: default privileges berlaku untuk object yang dibuat setelah aturan dibuat, dan terkait role pembuat object. Ini sering menjadi sumber kebingungan.

8. Privilege Layer

Untuk table biasa, minimal yang sering kita pikirkan:

ObjectPrivilege
databaseCONNECT, CREATE, TEMPORARY
schemaUSAGE, CREATE
tableSELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER
sequenceUSAGE, SELECT, UPDATE
function/procedureEXECUTE
type/domainUSAGE

Kesalahan umum:

grant all privileges on database appdb to app_runtime;

Ini sering terlalu luas dan tidak menyelesaikan privilege table/schema secara detail.

Lebih defensible:

revoke create on schema public from public;
revoke all on database appdb from public;

grant connect on database appdb to app_runtime;
grant usage on schema app to app_runtime;
grant select, insert, update, delete on all tables in schema app to app_runtime;
grant usage, select, update on all sequences in schema app to app_runtime;

9. The public Schema

PostgreSQL database biasanya memiliki schema public. Banyak contoh tutorial membuat object langsung di public karena mudah.

Untuk production, kita perlu lebih disiplin.

Masalah dengan public:

  • mudah menjadi tempat campur-aduk object;
  • tutorial/extension/default behavior sering mengarah ke sana;
  • privilege default bisa berbeda antar versi/config;
  • search path biasanya menyertakan public;
  • object shadowing bisa terjadi jika search path tidak dikontrol.

Pattern yang lebih aman:

create schema app authorization app_owner;
create schema ext authorization app_owner;

revoke create on schema public from public;
revoke all on database appdb from public;

Lalu aplikasi memakai schema eksplisit:

select * from app.orders where id = $1;

Atau set search path secara controlled untuk role:

alter role app_runtime in database appdb set search_path = app, pg_catalog;

10. Search Path Mental Model

search_path menentukan urutan schema yang dipakai PostgreSQL ketika object disebut tanpa schema.

Jika query:

select * from orders;

PostgreSQL akan mencari orders di schema sesuai search path.

Contoh:

show search_path;
select current_schemas(true);

Jika search path adalah:

app, public

Maka orders akan dicari sebagai:

  1. app.orders;
  2. public.orders.

10.1 Search Path sebagai Bug Source

Bug bisa terjadi ketika:

  • migration membuat table di schema berbeda dari runtime query;
  • test memakai public, production memakai app;
  • extension function ditemukan dari schema yang tidak diharapkan;
  • tenant schema disetel per request tetapi connection pool mengembalikan connection dengan search path tenant sebelumnya;
  • function security definer memakai search path tidak aman.

10.2 Rule Praktis

Untuk sistem production:

  • prefer fully qualified object name di migration penting;
  • set search_path role secara eksplisit;
  • jangan bergantung pada default environment;
  • reset connection state saat memakai pool;
  • hindari search path dinamis untuk tenant kecuali benar-benar disiplin;
  • function SECURITY DEFINER harus mengatur search path aman.

Contoh function dengan search path lebih aman:

create or replace function app.safe_function()
returns void
language plpgsql
security definer
set search_path = app, pg_catalog
as $$
begin
    -- function body
end;
$$;

11. Java Application Implications

11.1 JDBC URL Tidak Menentukan Schema dengan Aman Jika Tidak Dipahami

Beberapa driver/setup memungkinkan opsi seperti currentSchema, tetapi engineer tetap harus memahami efeknya terhadap session state.

Contoh pgJDBC:

jdbc:postgresql://localhost:5432/appdb?currentSchema=app

Ini nyaman, tetapi production policy tetap harus jelas:

  • apakah query menggunakan fully qualified names?
  • apakah migration tool menggunakan schema yang sama?
  • apakah pool membersihkan session state?
  • apakah tenant switching mengubah search path?
  • apakah SET ROLE atau SET search_path pernah dipakai runtime?

11.2 HikariCP dan Session State

Connection pool mempertahankan koneksi fisik. Artinya session state bisa bertahan antar request jika tidak di-reset.

State yang bisa bocor:

  • search_path;
  • role;
  • timezone;
  • prepared statements;
  • temporary tables;
  • advisory locks;
  • transaction settings;
  • GUC lokal jika transaksi tidak selesai bersih.

Karena itu, hindari pola seperti:

set search_path = tenant_123, app, pg_catalog;

lalu lupa reset sebelum connection kembali ke pool.

Jika schema-per-tenant digunakan, pastikan ada wrapper yang:

  1. meminjam koneksi;
  2. memulai transaksi;
  3. set local search path;
  4. menjalankan operasi;
  5. commit/rollback;
  6. memastikan state tidak bocor.

Lebih aman menggunakan SET LOCAL di dalam transaksi:

begin;
set local search_path = tenant_123, app, pg_catalog;
select * from orders;
commit;

SET LOCAL berlaku sampai transaksi selesai.

11.3 Hibernate Default Schema

Hibernate/JPA dapat disetel dengan default schema. Risiko umum:

  • entity mapping tidak sama dengan Flyway schema;
  • native query lupa schema;
  • test H2 tidak menangkap search path issue;
  • migration membuat sequence di schema berbeda;
  • hibernate.hbm2ddl.auto dipakai di environment yang salah.

Untuk production, migration tool harus menjadi source of truth schema. Hibernate tidak boleh menjadi schema owner tanpa kontrol.

12. Migration Identity

Flyway/Liquibase sering dijalankan oleh role khusus.

Pattern yang baik:

app_migrator: boleh membuat/mengubah object saat deploy
app_runtime: hanya boleh menjalankan operasi aplikasi normal
app_owner: owner object, no-login jika memungkinkan

Ada dua strategi ownership:

12.1 Migrator sebagai Owner

Migration tool membuat object sebagai app_migrator, lalu object owner adalah app_migrator.

Kelebihan:

  • sederhana;
  • migration berikutnya mudah mengubah object.

Kekurangan:

  • role login memiliki ownership luas;
  • compromise credential migrator lebih berbahaya;
  • lifecycle object bergantung pada role login.

12.2 Migrator SET ROLE ke Owner

Migration login role melakukan SET ROLE app_owner untuk membuat object dimiliki owner no-login.

Kelebihan:

  • ownership lebih bersih;
  • runtime dan migrator tidak menjadi owner langsung;
  • policy lebih defensible.

Kekurangan:

  • setup lebih kompleks;
  • perlu grant role membership dan audit;
  • migration tool harus konsisten.

Contoh:

grant app_owner to app_migrator;

-- migration session
set role app_owner;
create table app.orders (...);
reset role;

13. Extension Schema

Extension sebaiknya tidak sembarangan dibuat di public.

Pattern:

create schema if not exists ext authorization app_owner;
create extension if not exists pgcrypto with schema ext;

Lalu panggil function extension secara qualified jika perlu:

select ext.gen_random_uuid();

Catatan: tidak semua extension mendukung dipindahkan atau dibuat bebas di schema tertentu dengan cara yang sama. Selalu cek dokumentasi extension yang digunakan.

14. Tenant Boundary Design

PostgreSQL mendukung beberapa pola multi-tenancy.

PatternBentukKelebihanRisiko
shared table + tenant_idsatu schema, kolom tenantsederhana, efisienbutuh RLS/constraint disiplin
schema per tenanttenant_a.orders, tenant_b.ordersnamespace terpisahmigration/search_path kompleks
database per tenantsatu database per tenantrestore/isolation kuatoperasional berat, sulit agregasi
cluster per tenantinstance terpisahisolation tinggibiaya dan operasi tinggi

14.1 Shared Table + Tenant ID

Cocok untuk banyak tenant kecil/menengah.

Wajib pikirkan:

  • composite unique key dengan tenant_id;
  • index prefix tenant;
  • row level security jika threat model membutuhkan;
  • query harus selalu menyaring tenant;
  • backup per tenant sulit;
  • noisy neighbor perlu mitigasi.

Contoh:

create table app.orders (
    tenant_id uuid not null,
    id bigint generated always as identity,
    status text not null,
    created_at timestamptz not null default now(),
    primary key (tenant_id, id)
);

create index orders_tenant_status_created_idx
on app.orders (tenant_id, status, created_at desc);

14.2 Schema per Tenant

Cocok jika jumlah tenant tidak terlalu besar dan ada kebutuhan namespace/config yang cukup berbeda.

Risiko:

  • migration harus dijalankan ke banyak schema;
  • search path harus sangat disiplin;
  • connection pool state bisa bocor;
  • query observability terfragmentasi;
  • jumlah object besar memperberat catalog;
  • DDL rollout lebih rumit.

14.3 Database per Tenant

Cocok jika tenant sedikit tetapi besar, atau ada kebutuhan restore/isolation kuat.

Risiko:

  • connection management berat;
  • migration banyak database;
  • monitoring dan backup lebih kompleks;
  • query agregat lintas tenant lebih mahal;
  • role/default privilege harus konsisten di banyak database.

Untuk banyak Java OLTP service internal, baseline yang sehat:

Database: appdb
Schemas:
  app       -> transactional application tables
  audit     -> audit/event append-only tables
  ext       -> extensions
  staging   -> import/backfill temporary persistent objects, optional
  read_model -> materialized/read model objects, optional

Roles:
  app_owner    NOLOGIN owns schemas/objects
  app_runtime  LOGIN minimal DML
  app_migrator LOGIN controlled DDL via deploy pipeline
  app_readonly LOGIN read-only support/reporting

Search path:
  app_runtime in appdb: app, pg_catalog
  app_migrator in appdb: app, ext, pg_catalog, or fully qualified migrations

Public:
  no CREATE for PUBLIC
  avoid application objects in public

SQL sketch:

-- roles
create role app_owner no login;
create role app_runtime login password 'change-me';
create role app_migrator login password 'change-me';
create role app_readonly login password 'change-me';

-- schemas
create schema app authorization app_owner;
create schema audit authorization app_owner;
create schema ext authorization app_owner;

-- harden defaults
revoke all on database appdb from public;
revoke create on schema public from public;

grant connect on database appdb to app_runtime, app_migrator, app_readonly;

grant usage on schema app to app_runtime, app_readonly;
grant usage on schema audit to app_runtime, app_readonly;
grant usage on schema ext to app_runtime, app_readonly;

grant select, insert, update, delete on all tables in schema app to app_runtime;
grant select, insert on all tables in schema audit to app_runtime;
grant select on all tables in schema app, audit to app_readonly;

grant usage, select, update on all sequences in schema app, audit to app_runtime;

alter role app_runtime in database appdb set search_path = app, pg_catalog;
alter role app_readonly in database appdb set search_path = app, pg_catalog;

16. Anti-Patterns

16.1 Application Runs as Superuser

Ini mematikan validitas test. Jika aplikasi berjalan sebagai superuser di dev/test, banyak bug privilege tidak pernah muncul sampai production.

16.2 Runtime Role Owns All Tables

Jika runtime role owner, aplikasi punya kemampuan DDL/drop/alter yang tidak dibutuhkan. Ini memperbesar blast radius.

16.3 Semua Object di public

Bukan selalu fatal, tetapi sering menunjukkan boundary tidak dipikirkan. Untuk sistem besar, object di public menjadi tempat sampah namespace.

16.4 Search Path Tidak Eksplisit

Jika search path bergantung default, hasil bisa berbeda antar environment, role, migration tool, dan test.

16.5 Grant ke User Individual

Lebih baik grant ke role group, lalu assign user/role login ke group. Ini membuat audit dan rotasi lebih mudah.

16.6 Migration dan Runtime Memakai Credential Sama

Credential runtime seharusnya tidak bisa menjalankan DDL. Jika aplikasi terkena SQL injection atau bug, dampaknya lebih kecil.

17. Hands-On Lab

Gunakan database lab dari Part 002.

17.1 Inspect Database and Role

select current_database();
select current_user;
select session_user;
show search_path;
select current_schemas(true);

17.2 Buat Role dan Schema Lab

Jalankan sebagai superuser lab:

create role lab_owner no login;
create role lab_runtime login password 'lab_runtime';
create role lab_migrator login password 'lab_migrator';
create role lab_readonly login password 'lab_readonly';

create schema lab_app authorization lab_owner;
create schema lab_audit authorization lab_owner;
create schema lab_ext authorization lab_owner;

revoke create on schema public from public;

grant connect on database appdb to lab_runtime, lab_migrator, lab_readonly;
grant usage on schema lab_app, lab_audit, lab_ext to lab_runtime, lab_readonly;

17.3 Buat Object dengan Owner Benar

set role lab_owner;

create table lab_app.case_file (
    id bigint generated always as identity primary key,
    case_number text not null unique,
    status text not null,
    created_at timestamptz not null default now()
);

create table lab_audit.case_event (
    id bigint generated always as identity primary key,
    case_file_id bigint not null,
    event_type text not null,
    payload jsonb not null,
    created_at timestamptz not null default now()
);

reset role;

17.4 Grant Runtime Access

grant select, insert, update, delete on lab_app.case_file to lab_runtime;
grant select, insert on lab_audit.case_event to lab_runtime;
grant select on lab_app.case_file, lab_audit.case_event to lab_readonly;

grant usage, select, update on all sequences in schema lab_app, lab_audit to lab_runtime;
grant usage, select on all sequences in schema lab_app, lab_audit to lab_readonly;

alter role lab_runtime in database appdb set search_path = lab_app, pg_catalog;
alter role lab_readonly in database appdb set search_path = lab_app, pg_catalog;

17.5 Test sebagai Runtime Role

psql "postgresql://lab_runtime:lab_runtime@localhost:5432/appdb"
show search_path;

insert into case_file(case_number, status)
values ('CASE-001', 'OPEN');

select * from case_file;

-- should fail if readonly/migration boundary is correct
create table should_not_work(id bigint);

Expected:

  • insert/select berhasil;
  • create table gagal untuk runtime role.

17.6 Test Search Path Collision

Sebagai superuser lab:

create table public.case_file (
    id bigint,
    fake text
);

Sebagai lab_runtime:

select * from case_file;
select * from public.case_file;

Tujuan:

  • melihat bahwa unqualified case_file mengikuti search_path;
  • memahami kenapa fully qualified name penting di migration dan operasi sensitif.

18. Diagnostic Queries

18.1 Object Owner

select
    n.nspname as schema_name,
    c.relname as object_name,
    c.relkind,
    pg_get_userbyid(c.relowner) as owner
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname not in ('pg_catalog', 'information_schema')
order by schema_name, object_name;

18.2 Schema Privileges

select
    nspname,
    has_schema_privilege('app_runtime', oid, 'USAGE') as runtime_usage,
    has_schema_privilege('app_runtime', oid, 'CREATE') as runtime_create
from pg_namespace
where nspname not like 'pg_%'
  and nspname <> 'information_schema'
order by nspname;

18.3 Table Privileges

select
    table_schema,
    table_name,
    privilege_type,
    grantee
from information_schema.table_privileges
where table_schema not in ('pg_catalog', 'information_schema')
order by table_schema, table_name, grantee, privilege_type;

18.4 Role Attributes

select
    rolname,
    rolcanlogin,
    rolsuper,
    rolcreatedb,
    rolcreaterole,
    rolreplication,
    rolbypassrls
from pg_roles
order by rolname;

18.5 Effective Search Path

show search_path;
select current_schemas(true);

19. Design Review Checklist

Gunakan checklist ini saat review database setup aplikasi Java.

19.1 Boundary

  • Apakah aplikasi benar-benar butuh database terpisah, atau cukup schema terpisah?
  • Apakah ada kebutuhan join/transaction lintas boundary?
  • Apakah backup/restore boundary sudah jelas?
  • Apakah tenant boundary dipilih berdasarkan kebutuhan, bukan gaya arsitektur?

19.2 Role

  • Apakah runtime role bukan superuser?
  • Apakah runtime role bukan owner object?
  • Apakah migration role terpisah dari runtime role?
  • Apakah readonly/support role benar-benar read-only?
  • Apakah grant diberikan ke group role, bukan user individual jika memungkinkan?

19.3 Schema

  • Apakah application object tidak campur-aduk di public?
  • Apakah extension punya schema yang jelas?
  • Apakah audit/read-model/staging dipisah secara sengaja?
  • Apakah schema naming konsisten antar environment?

19.4 Search Path

  • Apakah search_path role disetel eksplisit?
  • Apakah migration memakai fully qualified name?
  • Apakah function SECURITY DEFINER memakai search path aman?
  • Apakah connection pool tidak membocorkan session state?

19.5 Migration

  • Apakah object baru mendapat default privileges yang benar?
  • Apakah owner object konsisten?
  • Apakah CI menjalankan migration dengan role yang menyerupai production?
  • Apakah rollback/roll-forward tidak membutuhkan privilege runtime yang berlebihan?

20. Self-Correction Checklist

Sebelum lanjut ke Part 005, pastikan bisa menjawab:

  • Apa perbedaan cluster, database, dan schema di PostgreSQL?
  • Mengapa role bersifat cluster-wide?
  • Mengapa database terpisah bukan solusi gratis untuk microservice boundary?
  • Apa perbedaan owner dan privilege?
  • Mengapa runtime role sebaiknya tidak menjadi owner table?
  • Apa risiko object di schema public?
  • Bagaimana search_path bisa menyebabkan bug production?
  • Mengapa connection pool dapat membocorkan session state?
  • Apa bedanya SET search_path dan SET LOCAL search_path dalam konteks transaksi?
  • Bagaimana mendesain role untuk app runtime, migration, readonly, dan owner?
  • Kapan schema-per-tenant masuk akal dan kapan berbahaya?

21. Takeaways

  • Cluster, database, schema, role, owner, dan privilege adalah boundary berbeda. Jangan mencampurnya.
  • Database boundary kuat untuk koneksi dan lifecycle, tetapi tidak nyaman untuk join/transaction lintas domain.
  • Schema adalah namespace, bukan security boundary penuh.
  • Role design adalah bagian dari architecture, bukan hanya tugas DBA.
  • Runtime Java service harus memakai privilege minimal.
  • Migration role harus terpisah dari runtime role.
  • search_path harus eksplisit, terutama saat memakai connection pool, migration tool, function, dan multi-tenant schema.
  • public schema sebaiknya dikontrol, bukan dibiarkan menjadi default dumping ground.
  • Desain boundary yang baik membuat migration, debugging, audit, dan recovery lebih mudah.

22. Referensi Utama

  • PostgreSQL 18 Documentation — Schemas and search_path.
  • PostgreSQL 18 Documentation — Database Roles.
  • PostgreSQL 18 Documentation — Privileges.
  • PostgreSQL 18 Documentation — CREATE SCHEMA, CREATE ROLE, GRANT, ALTER DEFAULT PRIVILEGES.
  • PostgreSQL 18 Documentation — System information functions such as current_schemas, current_user, and privilege-checking functions.
  • pgJDBC Documentation — connection URL and schema-related connection properties.
Lesson Recap

You just completed lesson 04 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.