π Tipe Data & Penggunaannya
Referensi detail untuk Programmer dan DBA β diperkaya dengan pola penggunaan nyata, studi kasus industri, dan penjelasan mendalam kapan dan mengapa setiap tipe dipilih.
π― Mengapa Pemilihan Tipe Data Sangat Penting?
Tipe data bukan sekadar formalitas deklarasi. Keputusan ini memengaruhi:
- Performa Query & Indeks β Indeks pada
BIGINTlebih lambat daripadaINT;VARCHARtanpa batas membuat key length besar. - Penyimpanan & Biaya Cloud β 1 miliar baris
TINYINTvsINTmenghemat ~3 GB. - Integritas Data β
DECIMALmencegah kesalahan pembulatan uang;DATEmemvalidasi rentang otomatis. - Kompatibilitas API β Serialisasi JSON/XML bergantung pada tipe (misal: angka vs string).
π’ 1. Tipe Data Primitif (Skalar) β Detail & Praktik Terbaik
Tipe primitif adalah atom penyusun aplikasi. Setiap sub-bagian kini dilengkapi dengan **konteks penggunaan sehari-hari** dan **pertimbangan desain yang sering muncul di production**.
1.1 Integer β Kapan Memilih Ukuran Tertentu?
| Tipe | Byte | Rentang Signed | Penggunaan Sangat Umum |
|---|---|---|---|
TINYINT | 1 | -128..127 | Status kode (0-5), flag boolean, rating bintang (1-5) |
SMALLINT | 2 | -32.768..32.767 | Tahun (cukup), jumlah item keranjang, usia (0-120) |
INT | 4 | Β±2,1 milyar | ID internal pengguna, jumlah view, foreign key di sistem menengah |
BIGINT | 8 | Β±9,2 Γ 10ΒΉβΈ | Primary key di sistem besar (Twitter: snowflake ID), timestamp epoch ms |
BIGINT (sering auto-increment) atau UUID. Namun, jika Anda memilih INT karena alasan performa, pastikan Anda memiliki strategi migrasi jika melewati 2 miliar baris (misal: sharding lebih awal).
Java / Go / SQL// Java: Entity ID standar
public class User {
private Long id; // BIGINT di DB
private Short age; // SMALLINT
private Byte status; // TINYINT
}
-- MySQL: DDL efisien
CREATE TABLE orders (
order_id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL, -- asumsi user < 2M
quantity SMALLINT NOT NULL DEFAULT 1
);
1.2 Floating Point & Decimal β Studi Kasus Keuangan
Detail: FLOAT/DOUBLE menggunakan basis 2, sehingga pecahan desimal seperti 0.1 tidak dapat direpresentasikan secara eksak. Dalam aplikasi fintech, sekali transaksi mungkin aman, tetapi akumulasi jutaan transaksi menyebabkan selisih (drift) yang tidak dapat ditoleransi.
| Skenario | Tipe Salah | Tipe Benar | Alasan |
|---|---|---|---|
| Saldo rekening / e-wallet | FLOAT | DECIMAL(18,2) | Presisi absolut, tanpa pembulatan biner |
| Harga saham (5 digit desimal) | DOUBLE | DECIMAL(15,5) | Regulasi bursa menuntut presisi eksak |
| Koordinat GPS | DECIMAL | DOUBLE / FLOAT8 | Butuh rentang besar, presisi ~15 digit cukup |
| Persentase diskon | DOUBLE | DECIMAL(5,2) | 0.00 - 100.00, tidak butuh floating point |
DOUBLE untuk saldo karena lebih sederhana. Setelah audit, mereka harus melakukan migrasi besar-besaran ke NUMERIC. Mulailah dengan benar sejak awal.
Python & SQL# Python: JANGAN
saldo = 1000.50 + 2000.30 # float, berisiko
# LAKUKAN
from decimal import Decimal
saldo = Decimal('1000.50') + Decimal('2000.30')
-- PostgreSQL: tipe uang khusus
CREATE TABLE dompet (
user_id INT,
balance NUMERIC(18, 2) NOT NULL CHECK (balance >= 0)
);
1.3 Boolean β Penggunaan di API dan Database
Pola sering dipakai: Flag seperti is_active, is_verified, is_deleted. Di REST API, boolean direpresentasikan sebagai true/false JSON. Di database, gunakan BOOLEAN (PostgreSQL) atau TINYINT(1) (MySQL). Jangan gunakan CHAR(1) 'Y'/'N' kecuali Anda di Oracle.
(is_active, last_login) bisa sangat efektif untuk query "pengguna aktif yang terakhir login > 30 hari".
1.4 String & Teks β Panduan Ukuran dan Encoding
Detail penting: VARCHAR(255) sering dipakai karena dulu batas maksimum indeks MySQL, namun kini tidak relevan. Selalu pilih ukuran berdasarkan domain data:
| Data | Tipe Rekomendasi | Alasan |
|---|---|---|
VARCHAR(254) | RFC 5321, bagian lokal 64 + @ + domain 255 | |
| Username | VARCHAR(30) | Twitter 15, GitHub 39, ambil tengahnya |
| No. Telepon | VARCHAR(20) | Maks internasional 15 digit + prefix '+' |
| Alamat lengkap | TEXT | Bisa > 255, jarang di-search, tidak usah diindeks penuh |
| Slug URL | VARCHAR(100) | Judul artikel, biasanya < 100 karakter |
| Kode verifikasi | CHAR(6) | Fixed-length, lebih cepat dibanding VARCHAR |
Encoding: Gunakan UTF-8 (utf8mb4 di MySQL) untuk mendukung emoji dan karakter internasional. Perhatikan bahwa CHAR(10) dengan utf8mb4 bisa memakan 40 byte! Di PostgreSQL, cukup gunakan VARCHAR atau TEXT (encoding UTF-8 otomatis).
Deklarasi Tepat-- MySQL / MariaDB
CREATE TABLE users (
email VARCHAR(254) NOT NULL UNIQUE,
phone VARCHAR(20) NULL,
otp_code CHAR(6) NULL,
bio TEXT NULL -- tidak diindeks langsung
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.5 Tanggal & Waktu β Zona Waktu, Epoch, dan Query Rentang
Penggunaan paling sering: created_at, updated_at, deleted_at, birth_date. Dua pola utama:
- Timestamp UTC: Simpan semua waktu dalam UTC, konversi di frontend. Gunakan
TIMESTAMPTZ(PG) atauDATETIMEdengan kesadaran UTC. - Event lokal: Janji temu dokter pukul 09:00 WIB harus tetap 09:00 meskipun zona berubah. Gunakan
TIME+ kolom zona terpisah.
created_at sangat umum. Gunakan tipe TIMESTAMP (4 byte) jika rentang mencukupi (1970-2038), atau DATETIME/TIMESTAMPTZ untuk cakupan lebih luas. Hindari menyimpan waktu sebagai string VARCHAR β Anda akan kehilangan semua fungsi temporal.
π§© 2. Tipe Data Komposit β Struktur Data dalam Aplikasi Modern
Setiap tipe komposit memiliki peran spesifik. Berikut penjelasan berdasarkan kapan mereka muncul di kode produksi.
π Array / Slice
Penggunaan: Daftar tags, kategori, gambar produk. Di PostgreSQL INTEGER[] untuk menyimpan banyak foreign key tanpa tabel pivot (normalisasi vs performa).
Detail: Array di database menghilangkan kebutuhan JOIN, namun menyulitkan referential integrity. Gunakan jika data jarang berubah dan tidak perlu di-query secara relasional.
ποΈ JSON/JSONB (Object)
Paling sering: Konfigurasi fitur, metadata produk, payload dari webhook eksternal yang skemanya tidak stabil.
Detail: PostgreSQL JSONB mendukung indexing GIN sehingga query @> (contains) sangat cepat. Jangan gunakan JSONB untuk data yang selalu di-join ke tabel lain.
π Map / Dictionary
Penggunaan: Cache in-memory (Redis hash), konfigurasi key-value per pengguna. Di Python, dict adalah tulang punggung semua ORM dan framework.
π¦ Struct / Dataclass
Penggunaan: DTO (Data Transfer Object), model domain. Di Go, struct digunakan untuk decoding JSON request dengan aman.
Detail: Di database PostgreSQL, Anda bisa membuat COMPOSITE TYPE sendiri, misal address_type (street, city, zip) dan menggunakannya sebagai tipe kolom.
Go & PostgreSQL Composite// Go struct untuk request API
type CreateOrderReq struct {
UserID int64 `json:"user_id"`
Items []string `json:"items"`
Total float64 `json:"total"` // seharusnya pakai decimal
}
-- PostgreSQL composite type
CREATE TYPE alamat AS (
jalan VARCHAR(100),
kota VARCHAR(50),
kode_pos VARCHAR(5)
);
CREATE TABLE kantor (
id INT,
lokasi alamat
);
2.1 Array β Lebih dari Sekadar Daftar
Array adalah struktur data paling fundamental untuk menyimpan koleksi elemen bertipe sama. Penggunaannya sangat luas mulai dari logika bisnis hingga komunikasi API. Pemahaman mendalam tentang array di berbagai bahasa dan database akan menghindari jebakan desain.
π Array di Bahasa Pemrograman β Perbandingan Fitur
| Bahasa | Deklarasi | Sifat | Operasi Kunci |
|---|---|---|---|
| Python | list = [1, 2, 3] | Dinamis, heterogen | append(), slice, list comprehension |
| Java | int[] arr = new int[5]; atau ArrayList<Integer> | Fixed-size (array), dinamis (List) | Arrays.copyOf, stream() |
| Go | arr := [3]int{1,2,3} | Fixed-size (array), slice = referensi dinamis | append(), copy(), slice capacity |
| JavaScript | let arr = [1,2,3]; | Dinamis, sparse diperbolehkan | map(), filter(), reduce() |
| C# | int[] arr = new int[3]; atau List<int> | Fixed atau dinamis | LINQ, List.Add() |
Penggunaan nyata:
- Python: Membaca file CSV menjadi list of dict, kemudian diproses dengan list comprehension.
- Go: Slice digunakan sebagai buffer untuk membaca data dari stream atau sebagai koleksi hasil query database (dengan
sqlx). - Java: API response sering dikembalikan sebagai
List<UserDTO>dari controller.
ποΈ Array di PostgreSQL β Query dan Manipulasi
PostgreSQL menyediakan tipe data array native. Ini sangat berguna untuk menyimpan data yang jarang di-relasikan secara penuh, misalnya tag produk, daftar permission, atau koordinat.
PostgreSQL ArrayCREATE TABLE artikel (
id SERIAL PRIMARY KEY,
judul VARCHAR(200),
tags TEXT[] -- array of text
);
INSERT INTO artikel (judul, tags) VALUES
('Belajar SQL', ARRAY['sql','database','pemula']);
-- Query: cari artikel yang punya tag 'database'
SELECT * FROM artikel WHERE 'database' = ANY(tags);
-- Menambah tag baru
UPDATE artikel SET tags = array_append(tags, 'latihan') WHERE id = 1;
-- Indeks GIN untuk pencarian array
CREATE INDEX idx_tags ON artikel USING GIN (tags);
ANY atau CONTAINS. Jika Anda perlu JOIN ke tabel lain, rekomendasi foreign key, atau agregasi per tag, lebih baik pakai tabel pivot (normalisasi).
π§© Array di Bahasa Modern β Studi Kasus: Transformasi Data
Dalam microservices, data sering dikirim sebagai array JSON. Di backend, kita melakukan transformasi: filter, map, reduce. Contoh di Python:
Python List Manipulation# Data dari API eksternal
orders = [
{"product": "Buku", "price": 15000},
{"product": "Pulpen", "price": 3000},
{"product": "Buku", "price": 20000}
]
# Total pendapatan dari buku saja
buku_revenue = sum(o['price'] for o in orders if o['product'] == 'Buku')
Di Go, dengan slice dan struct:
Go Slicetype Order struct { Product string; Price int }
orders := []Order{{"Buku", 15000}, {"Pulpen", 3000}, {"Buku", 20000}}
var total int
for _, o := range orders {
if o.Product == "Buku" { total += o.Price }
}
ποΈ 3. Tipe Data Database β Panduan DBA dengan Contoh Nyata
Tambahan penting: bagaimana tipe data dipilih dalam siklus desain database, beserta dampaknya pada storage engine.
3.1 Memilih Auto-Increment vs UUID sebagai Primary Key
π’ Auto-Increment BIGINT
Kelebihan: Cepat untuk insert berurutan, indeks rapat, mudah dibaca manusia.
Kekurangan: Bocor informasi (ID user terlihat), tidak cocok untuk sistem terdistribusi tanpa koordinasi.
Penggunaan: Hampir semua aplikasi monolitik, ERP internal.
π UUID / ULID
Kelebihan: Dapat di-generate di client, unik global, aman diekspos.
Kekurangan: Indeks lebih besar (16 byte vs 8 byte BIGINT), fragmentasi halaman jika tidak diurutkan (gunakan ULID untuk temporal ordering).
Penggunaan: Microservices, aplikasi mobile dengan offline capability, public API.
PostgreSQL ULID vs UUIDCREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE orders (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
-- atau ULID (sebagai CHAR(26))
ulid CHAR(26) PRIMARY KEY
);
3.2 JSONB di PostgreSQL β Kapan dan Bagaimana
Studi kasus umum: Marketplace menyimpan spesifikasi produk yang berbeda per kategori (baju: ukuran, warna; laptop: RAM, CPU). Daripada membuat puluhan tabel, simpan atribut dalam JSONB dengan indeks GIN. Query: "cari laptop dengan RAM 16GB" tetap cepat.
Index GIN untuk JSONBCREATE INDEX idx_produk_spec ON produk USING GIN (data jsonb_path_ops);
SELECT * FROM produk
WHERE data @> '{"spesifikasi": {"ram": 16}}';
3.3 Tipe Data Spesial: ENUM, BIT, dan Domain
Penggunaan: ENUM di MySQL/PostgreSQL untuk status yang terdefinisi jelas (misal: ENUM('pending','shipped','delivered')). Keuntungan: penyimpanan efisien (1-2 byte), validasi otomatis. Kerugian: sulit diubah jika perlu menambah status baru tanpa ALTER TABLE.
Alternatif modern: Gunakan SMALLINT dengan tabel referensi (lookup table) untuk fleksibilitas lebih, atau CHECK constraint di PostgreSQL.
3.4 AUTO_INCREMENT / SERIAL / IDENTITY β Jantung Surrogate Key
Auto-increment adalah mekanisme database untuk menghasilkan nilai integer unik secara otomatis, biasanya digunakan sebagai primary key pengganti (surrogate key). Setiap RDBMS memiliki sintaks berbeda, namun prinsipnya sama: nilai bertambah secara atomik setiap kali baris baru dimasukkan.
| RDBMS | Sintaks Standar | Tipe Data Pendukung | Catatan |
|---|---|---|---|
| MySQL / MariaDB | INT AUTO_INCREMENT | TINYINT, SMALLINT, INT, BIGINT | Hanya satu kolom AUTO_INCREMENT per tabel; nilai dapat di-reset dengan ALTER TABLE. |
| PostgreSQL | SERIAL / BIGSERIAL | INTEGER (SERIAL), BIGINT (BIGSERIAL) | SERIAL adalah makro yang membuat SEQUENCE dan menetapkan DEFAULT nextval(). |
| SQL Server | INT IDENTITY(1,1) | TINYINT, SMALLINT, INT, BIGINT | IDENTITY(seed, increment) β seed nilai awal, increment langkah. |
| Oracle | Tidak ada AUTO_INCREMENT asli; gunakan SEQUENCE + trigger, atau IDENTITY di Oracle 12c+. | ||
π Memilih Tipe Data untuk Auto-Increment
TINYINT(max 127 signed) β hanya untuk tabel referensi kecil (misal: kategori < 100).SMALLINT(32.767) β masih berisiko untuk tabel transaksi yang cepat membesar.INT(2,1 milyar) β cukup untuk banyak aplikasi menengah. Namun, jika sehari ada 1 juta transaksi, habis dalam 5-6 tahun.BIGINT(9,2 Γ 10ΒΉβΈ) β standar emas untuk sistem modern.
INT untuk tabel log atau event sourcing yang tumbuh cepat, suatu hari sistem akan mogok. Gunakan BIGINT sejak awal untuk tabel yang akan terus tumbuh.
Contoh Auto-Increment di Berbagai DB-- MySQL
CREATE TABLE transactions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
amount DECIMAL(12,2)
);
-- PostgreSQL
CREATE TABLE transactions (
id BIGSERIAL PRIMARY KEY,
amount NUMERIC(12,2)
);
-- SQL Server
CREATE TABLE transactions (
id BIGINT IDENTITY(1,1) PRIMARY KEY,
amount DECIMAL(12,2)
);
3.5 JOIN β Fondasi Query Relasional yang Kuat
JOIN adalah operasi fundamental yang menggabungkan baris dari dua atau lebih tabel berdasarkan kondisi relasi. Pemahaman yang mendalam tentang berbagai tipe JOIN, dampak tipe data, dan strategi optimasi sangat penting untuk performa database.
π Jenis-Jenis JOIN dan Kapan Menggunakannya
| Tipe JOIN | Deskripsi | Hasil | Contoh Use Case |
|---|---|---|---|
| INNER JOIN | Hanya baris yang cocok di kedua tabel | Irisan | Dapatkan user yang sudah melakukan order |
| LEFT JOIN | Semua baris dari tabel kiri, cocokan dari kanan (NULL jika tak ada) | Kiri + irisan | Daftar semua user beserta order terakhirnya (jika ada) |
| RIGHT JOIN | Semua baris dari tabel kanan | Kanan + irisan | Jarang dipakai; biasanya setara LEFT JOIN dengan urutan tabel dibalik |
| FULL OUTER JOIN | Gabungan semua baris dari kedua tabel | Gabungan | Mencari data yang tidak cocok di kedua sisi (rekonsiliasi) |
| CROSS JOIN | Produk Kartesian | n Γ m baris | Generate matrix kombinasi (hati-hati ledakan baris) |
π Penggunaan JOIN dalam Praktik β Multi-Table, Self-Join, Non-Equal
1. Multi-Table JOIN: Menggabungkan lebih dari dua tabel untuk laporan kompleks.
Multi-Table JOINSELECT u.name, o.order_date, p.product_name
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN order_items oi ON o.id = oi.order_id
INNER JOIN products p ON oi.product_id = p.id
WHERE o.order_date >= CURRENT_DATE - 7;
2. Self-Join: Menghubungkan tabel dengan dirinya sendiri, misal untuk hierarki karyawan-manajer.
Self-JoinSELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
3. Non-Equi JOIN: Kondisi selain kesamaan (=), misal rentang atau <.
Non-Equi JOINSELECT a.name, b.name
FROM products a
JOIN products b ON a.price < b.price AND a.category_id = b.category_id;
π£ Masalah Klasik: Tipe Data Kolom JOIN Tidak Cocok
Bayangkan users.id bertipe BIGINT, sedangkan orders.user_id bertipe INT. Saat Anda melakukan JOIN, database mungkin harus mengonversi tipe secara implisit. Akibatnya:
- Indeks tidak digunakan β database melakukan full table scan.
- CPU overhead β konversi tipe per baris sangat mahal.
- Hasil tidak konsisten β bisa terjadi pemotongan nilai atau error overflow.
Contoh Masalah JOIN karena Tipe Tidak Cocok-- Tabel users (id BIGINT)
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(100)
);
-- Tabel orders (user_id INT) β
CREATE TABLE orders (
order_id INT PRIMARY KEY,
user_id INT, -- harusnya BIGINT
amount DECIMAL(10,2)
);
-- Query JOIN β lambat!
EXPLAIN SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;
-- MySQL akan menunjukkan 'Using where; Using index' atau full scan.
orders.user_id menjadi BIGINT agar cocok dengan users.id. Ini akan mengembalikan penggunaan indeks dan performa optimal.
β Aturan Emas JOIN dan Tipe Data
π― Primary Key = Foreign Key
Selalu gunakan tipe data yang persis sama (termasuk signed/unsigned, panjang VARCHAR, charset) untuk primary key dan foreign key yang merujuknya.
π Indeks Kolom Foreign Key
Buat indeks pada setiap foreign key. Tanpa indeks, operasi JOIN atau ON DELETE CASCADE bisa menjadi bencana performa.
π§Ή Hindari JOIN pada Kolom Terkalkulasi
Jangan: ON UPPER(a.name) = UPPER(b.name) β indeks tidak akan terpakai. Simpan data dalam bentuk yang sudah dinormalisasi.
π VARCHAR JOIN: Perhatikan Charset & Collation
Dua kolom VARCHAR(50) tetapi dengan collation berbeda (utf8mb4_general_ci vs latin1_swedish_ci) tidak bisa di-JOIN tanpa konversi. Pastikan charset identik.
Praktik Benar: JOIN dengan Tipe Cocok-- Perbaikan: orders.user_id BIGINT
CREATE TABLE orders_new (
order_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2),
INDEX idx_user (user_id), -- indeks foreign key
FOREIGN KEY (user_id) REFERENCES users(id)
);
SELECT u.name, o.amount
FROM users u
JOIN orders_new o ON u.id = o.user_id; -- menggunakan indeks
π Visualisasi: Performa JOIN dengan Tipe Cocok vs Tidak Cocok
Pada tabel users 10 juta baris dan orders 50 juta baris, JOIN dengan tipe BIGINT cocok membutuhkan ~2 detik menggunakan indeks. Jika tidak cocok (konversi implisit), query bisa memakan waktu > 2 menit dan membebani CPU.
EXPLAIN (MySQL/PostgreSQL) atau SET STATISTICS IO ON (SQL Server) untuk memeriksa apakah JOIN menggunakan indeks. Cari kata "Using index", "Index Scan", vs "Seq Scan" / "Full Table Scan".
π JOIN dengan JSON dan Array di PostgreSQL
Anda bisa melakukan JOIN antara kolom array dengan tabel lain menggunakan UNNEST atau antara data JSONB dengan kolom biasa.
JOIN Array dengan UNNEST-- Tabel produk dengan array kategori
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(50),
category_ids INT[]
);
SELECT p.name, c.category_name
FROM products p
CROSS JOIN UNNEST(p.category_ids) AS cat_id
JOIN categories c ON c.id = cat_id;
π 4. Konversi Tipe Data & Bahaya Implicit Coercion
Detail skenario yang sering menyebabkan bug di production:
| Kasus | Contoh | Akibat |
|---|---|---|
| String + Int di JavaScript | "5" + 2 β "52" | Kesalahan logika total |
| Perbandingan long vs int | Java: Long(128) == Long(128) false (cache -128..127) | Bug caching tidak terduga |
| SQL VARCHAR banding INT | WHERE id = '10' (id INT) β MySQL mengonversi string ke int, indeks tetap jalan, tapi jika string berisi teks bisa lambat | Performa menurun drastis |
| Float ke Decimal | Decimal(0.1) menghasilkan Decimal('0.1000000000000000055511...') | Presisi rusak |
Solusi: Selalu eksplisit. Di JavaScript gunakan Number(), parseInt() dengan radix. Di SQL, gunakan CAST atau ::. Di bahasa strongly-typed, percayakan compiler.
π 5. Best Practice β Dari Pengalaman Industri
Berikut adalah kumpulan aturan yang sudah teruji di berbagai sistem production skala besar.
π 1. Right-sizing: Hemat Penyimpanan = Hemat Biaya
Di cloud, IOPS dan storage mahal. Memilih INT (4 byte) untuk kolom yang hanya menampung 0-100 adalah pemborosan 3 byte per baris. Untuk 1 miliar baris, itu berarti ~3 GB ruang + backup + replikasi.
π 2. Keuangan: Always DECIMAL
Di sistem perbankan, DECIMAL(18,4) adalah standar. Bahkan untuk e-commerce, gunakan DECIMAL(12,2) untuk harga. Jangan pernah FLOAT.
π
3. Waktu: UTC + TIMESTAMPTZ adalah Teman
Aplikasi global (SaaS) wajib menyimpan waktu dalam UTC. PostgreSQL TIMESTAMPTZ menyimpan UTC, menampilkan sesuai sesi timezone. Hindari DATETIME polos tanpa zona.
π 4. Foreign Key: Cocokkan Tipe Persis
Jika users.id adalah BIGINT, maka orders.user_id harus BIGINT. Ketidakcocokan menyebabkan full table scan saat join, bahkan jika indeks ada.
π 5. String: Jangan VARCHAR(MAX) sembarangan
Batas yang masuk akal mencegah data sampah dan mempercepat indeks. Email 254, nama 100, URL 2048. Gunakan TEXT hanya jika benar-benar perlu.
ποΈ 6. JSONB untuk Fleksibilitas, Bukan Pengganti Relasional
Gunakan JSONB untuk atribut yang bervariasi antar entitas (spesifikasi produk). Data yang selalu ada dan di-join (user, order) tetap di tabel normal.
β 7. Constraint Database > Validasi Aplikasi
Tambahkan CHECK (umur >= 0), UNIQUE, FOREIGN KEY di database sebagai lapisan pertahanan terakhir. Aplikasi bisa saja di-bypass.
π§Ή 8. Hindari NULL yang Tidak Perlu
NULL memperumit agregasi dan logika. Gunakan NOT NULL DEFAULT ... untuk kolom seperti is_active (default true). NULL hanya untuk data yang benar-benar opsional.
π Cheat Sheet Praktis: Tipe Data untuk Kebutuhan Umum
| Kebutuhan | Tipe Ideal | Catatan |
|---|---|---|
| ID unik global | UUID atau BIGSERIAL | UUID untuk distributed, Serial untuk monolith |
| Harga produk | NUMERIC(12,2) | 2 desimal cukup untuk sebagian besar mata uang |
| Stok barang | INT atau SMALLINT | Jangan pakai BIGINT kecuali stok > 2M |
| Rating (1-5) | TINYINT | 1 byte, validasi CHECK (rating BETWEEN 1 AND 5) |
| Nama depan | VARCHAR(50) | Nama terpanjang di dunia ~40 karakter |
| Alamat email | VARCHAR(254) | Indeks unique, lowercase simpan |
| Log aktivitas | TEXT | Hindari indeks penuh, cukup indeks timestamp |
| Token JWT | VARCHAR(500) | Token bisa panjang, jangan CHAR |
| Gambar (path) | VARCHAR(500) | Jangan simpan blob di DB, simpan di object storage |
| Status pesanan | ENUM atau SMALLINT + lookup | ENUM lebih ketat, lookup lebih fleksibel |
β Kesimpulan: Fondasi Aplikasi Kokoh Dimulai dari Tipe Data
Pemilihan tipe data adalah keputusan arsitektural, bukan sekadar teknis. Dengan mengikuti panduan yang telah diperkaya studi kasus di atas, Anda akan menghasilkan sistem yang:
- Performa optimal sejak hari pertama β indeks ramping, query efisien
- Biaya infrastruktur lebih rendah β penyimpanan dan memori lebih hemat
- Integritas data terjamin β tidak ada selisih saldo atau bug zona waktu
- Mudah di-scale β tipe data yang tepat memudahkan sharding dan replikasi
- Kode yang mendokumentasikan diri sendiri β tipe eksplisit menjelaskan domain
βDetail kecil dalam tipe data adalah pembeda antara sistem yang bertahan 10 tahun dan yang harus ditulis ulang setelah 6 bulan.β