pgvector Tutorial: เพิ่ม Vector Search ให้ PostgreSQL สำหรับ RAG และ Semantic Search

ถ้าคุณกำลังสร้างระบบ RAG หรือแอปพลิเคชันที่ต้องการ Semantic Search คำถามแรกที่ต้องตัดสินใจคือจะเก็บ Embedding ไว้ที่ไหน Vector Database เฉพาะทางอย่าง Pinecone, Qdrant หรือ Weaviate เป็นตัวเลือกหนึ่ง แต่สำหรับทีมที่ใช้ PostgreSQL อยู่แล้ว pgvector คือทางเลือกที่เร็วกว่า ถูกกว่า และดูแลง่ายกว่า

pgvector คือ Extension แบบ Open Source สำหรับ PostgreSQL ที่เพิ่ม Type ข้อมูล vector พร้อม Similarity Search Operator และ Index แบบ HNSW/IVFFlat เข้าไปใน Database ที่มีอยู่เดิม Embedding ของคุณอยู่ใน Database เดียวกับข้อมูลแอปพลิเคชัน Query ด้วย SQL มาตรฐาน รองรับ ACID ของ Postgres เต็มรูปแบบ

บทความนี้ครอบคลุมตั้งแต่การติดตั้งจนถึง RAG Query ระดับ Production พร้อมโค้ดจริงทุกขั้นตอน

สิ่งที่คุณจะได้: ระบบ Semantic Search ที่เก็บ Document Embedding ใน PostgreSQL และดึงข้อมูลที่เกี่ยวข้องที่สุดสำหรับ Query ที่กำหนด — เป็น Retrieval Layer ของ RAG Pipeline

Stack ที่ใช้:

  • PostgreSQL 15+ พร้อม pgvector 0.7+
  • Python 3.11
  • openai หรือ ollama สำหรับ Embedding
  • psycopg2 / asyncpg สำหรับเชื่อมต่อ Database

pgvector คืออะไร และทำไมถึงควรใช้?

Vector Embedding คือลิสต์ของตัวเลข Floating-Point — โดยทั่วไป 768 ถึง 3,072 มิติ — ที่เข้ารหัสความหมายเชิงความหมายของข้อความ รูปภาพ หรือข้อมูลอื่นๆ ข้อความสองชิ้นที่มีความหมายใกล้เคียงกันจะให้ Vector ที่อยู่ใกล้กันในพื้นที่หลายมิตินั้น

pgvector เพิ่มความสามารถในการเก็บ Vector เหล่านี้ใน Postgres และรันการคำนวณระยะทางได้อย่างมีประสิทธิภาพ:

การดำเนินการ SQL
Cosine Similarity embedding <=> query_vector
L2 Distance embedding <-> query_vector
Inner Product embedding <#> query_vector

เมื่อไหร่ที่ pgvector เหมาะสม:

  • คุณใช้ PostgreSQL อยู่แล้ว
  • จำนวน Vector น้อยกว่า ~5 ล้าน
  • ต้องการ JOIN Embedding กับข้อมูล Relational (User ID, Document Metadata, Access Control)
  • ต้องการ ACID Transaction ครอบคลุมทั้งข้อมูลแอปและ Embedding
  • ไม่ต้องการจัดการ Infrastructure เพิ่มเติม

เมื่อไหร่ที่ควรใช้ Vector DB เฉพาะทางแทน:

  • มี Vector มากกว่า 10 ล้านรายการ และต้องการ Latency ต่ำกว่า 10ms
  • ต้องการ Advanced Filtering ที่ Scale ขนาดใหญ่
  • ทีมไม่มี Infrastructure ของ Postgres อยู่เดิม

สำหรับโปรเจกต์ RAG ขององค์กรในไทยและอาเซียน — การค้นหาเอกสารภายใน ระบบสัญญา ระบบปฏิบัติตาม PDPA — pgvector เพียงพอและดูแลง่ายกว่ามาก


ภาพรวมสถาปัตยกรรม

flowchart TD
    A["เอกสาร\n(PDF, ข้อความ, ฐานข้อมูล)"] --> B["การแบ่ง Chunk\n(ประมาณ 500 Token ต่อส่วน)"]
    B --> C["Embedding Model\n(OpenAI หรือ Ollama หรือ Local)"]
    C --> D["pgvector\n(PostgreSQL)"]
    E["คำถามของผู้ใช้"] --> F["แปลงคำถามเป็น Embedding"]
    F --> G["Similarity Search\n(Cosine หรือ L2)"]
    D --> G
    G --> H["Chunk ที่เกี่ยวข้อง Top-k"]
    H --> I["LLM\n(GPT หรือ Claude หรือ Local)"]
    I --> J["คำตอบพร้อมแหล่งอ้างอิง"]

ขั้นตอนที่ 1 — ติดตั้ง pgvector

ตัวเลือก A: Docker (แนะนำสำหรับ Development)

docker run -d \
  --name pgvector-dev \
  -e POSTGRES_PASSWORD=secret \
  -e POSTGRES_DB=ragdb \
  -p 5432:5432 \
  pgvector/pgvector:pg16

Image นี้ติดตั้ง pgvector ไว้ล่วงหน้าแล้ว ไม่ต้อง Build Extension เอง

ตัวเลือก B: ติดตั้งบน PostgreSQL ที่มีอยู่

Ubuntu / Debian:

sudo apt install postgresql-16-pgvector

macOS (Homebrew):

brew install pgvector

เปิดใช้งาน Extension ใน Database

CREATE EXTENSION IF NOT EXISTS vector;

-- ตรวจสอบการติดตั้ง
SELECT * FROM pg_extension WHERE extname = 'vector';

ขั้นตอนที่ 2 — สร้าง Schema

-- ตาราง Documents: เก็บข้อความต้นฉบับและ Metadata
CREATE TABLE documents (
    id          BIGSERIAL PRIMARY KEY,
    title       TEXT NOT NULL,
    source_url  TEXT,
    lang        VARCHAR(5) DEFAULT 'th',
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

-- ตาราง Chunks: เก็บส่วนข้อความและ Embedding
CREATE TABLE document_chunks (
    id          BIGSERIAL PRIMARY KEY,
    document_id BIGINT REFERENCES documents(id) ON DELETE CASCADE,
    chunk_index INTEGER NOT NULL,
    content     TEXT NOT NULL,
    token_count INTEGER,
    embedding   vector(1536),  -- ต้องตรงกับ Embedding Model ที่ใช้
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

มิติของ Embedding ตาม Model:

Model มิติ
OpenAI text-embedding-3-small 1536
OpenAI text-embedding-3-large 3072
Ollama nomic-embed-text 768
Ollama mxbai-embed-large 1024
BGE-M3 (รองรับหลายภาษา รวมถึงภาษาไทย) 1024

Column vector(1536) ต้องตรงกันทุกครั้ง ไม่สามารถผสมมิติใน Column เดียวกันได้


ขั้นตอนที่ 3 — นำเข้าเอกสารและสร้าง Embedding

import os
import psycopg2
from openai import OpenAI
import tiktoken

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
DB_URL = "postgresql://postgres:secret@localhost:5432/ragdb"

def chunk_text(text: str, max_tokens: int = 500, overlap: int = 50) -> list[str]:
    """แบ่งข้อความเป็น Chunk ที่ทับซ้อนกันตามจำนวน Token"""
    enc = tiktoken.get_encoding("cl100k_base")
    tokens = enc.encode(text)
    chunks = []
    start = 0
    while start < len(tokens):
        end = min(start + max_tokens, len(tokens))
        chunks.append(enc.decode(tokens[start:end]))
        start += max_tokens - overlap
    return chunks

def get_embedding(text: str) -> list[float]:
    """ดึง Embedding Vector จาก OpenAI"""
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text.replace("\n", " ")
    )
    return response.data[0].embedding

def ingest_document(title: str, content: str, source_url: str = None, lang: str = "th"):
    """แบ่ง Chunk เอกสาร สร้าง Embedding และเก็บลง PostgreSQL"""
    conn = psycopg2.connect(DB_URL)
    cur = conn.cursor()
    try:
        cur.execute(
            "INSERT INTO documents (title, source_url, lang) VALUES (%s, %s, %s) RETURNING id",
            (title, source_url, lang)
        )
        doc_id = cur.fetchone()[0]
        chunks = chunk_text(content)
        print(f"กำลังนำเข้า '{title}': {len(chunks)} chunks")
        for i, chunk in enumerate(chunks):
            embedding = get_embedding(chunk)
            cur.execute(
                """INSERT INTO document_chunks
                   (document_id, chunk_index, content, token_count, embedding)
                   VALUES (%s, %s, %s, %s, %s)""",
                (doc_id, i, chunk, len(chunk.split()), embedding)
            )
        conn.commit()
        print(f"เสร็จสิ้น: document_id={doc_id}")
        return doc_id
    except Exception as e:
        conn.rollback()
        raise e
    finally:
        cur.close()
        conn.close()

# ตัวอย่างการใช้งาน
ingest_document(
    title="นโยบายความปลอดภัยข้อมูลส่วนบุคคล (PDPA) ฉบับ 3.2",
    content=open("pdpa_policy.txt").read(),
    source_url="https://internal.company.co.th/policies/pdpa",
    lang="th"
)

ใช้ Ollama สำหรับ Local Embedding (ไม่ต้องใช้ API Key)

import httpx

def get_embedding_ollama(text: str, model: str = "nomic-embed-text") -> list[float]:
    """ดึง Embedding จาก Ollama ที่รันในเครื่อง"""
    response = httpx.post(
        "http://localhost:11434/api/embeddings",
        json={"model": model, "prompt": text}
    )
    return response.json()["embedding"]

เปลี่ยน get_embedding() เป็น get_embedding_ollama() และเปลี่ยนมิติ Vector เป็น 768 ใน Schema ถ้าใช้ nomic-embed-text


ขั้นตอนที่ 4 — สร้าง Index สำหรับ Similarity Search ที่รวดเร็ว

HNSW (แนะนำสำหรับกรณีทั่วไป)

CREATE INDEX ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
Parameter ค่าเริ่มต้น ผลกระทบ
m 16 การเชื่อมต่อต่อ Node สูงกว่า = Recall ดีกว่า ใช้ Memory มากกว่า
ef_construction 64 ความกว้างการค้นหาตอน Build สูงกว่า = Recall ดีกว่า Build ช้ากว่า

IVFFlat (สำหรับ Dataset ขนาดใหญ่มาก)

CREATE INDEX ON document_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

เปรียบเทียบ:

HNSW IVFFlat
Recall สูงกว่า ต่ำกว่า (ปรับได้)
เวลา Build ช้ากว่า เร็วกว่า
Memory สูงกว่า ต่ำกว่า
เหมาะสำหรับ Vector < 2 ล้าน Vector > 2 ล้าน

ขั้นตอนที่ 5 — รัน Similarity Search

def semantic_search(
    query: str,
    top_k: int = 5,
    lang_filter: str = None,
    min_similarity: float = 0.7
) -> list[dict]:
    """ค้นหา Chunk ที่มีความหมายใกล้เคียงกับคำถามมากที่สุด"""
    query_embedding = get_embedding(query)
    conn = psycopg2.connect(DB_URL)
    cur = conn.cursor()
    try:
        cur.execute("SET hnsw.ef_search = 100")
        sql = """
            SELECT
                dc.id, dc.content, dc.chunk_index,
                d.title, d.source_url, d.lang,
                1 - (dc.embedding <=> %s::vector) AS similarity
            FROM document_chunks dc
            JOIN documents d ON d.id = dc.document_id
            WHERE 1 - (dc.embedding <=> %s::vector) >= %s
            {lang_clause}
            ORDER BY dc.embedding <=> %s::vector
            LIMIT %s
        """.format(lang_clause="AND d.lang = %s" if lang_filter else "")
        params = [query_embedding, query_embedding, min_similarity]
        if lang_filter:
            params.append(lang_filter)
        params.extend([query_embedding, top_k])
        cur.execute(sql, params)
        return [
            {"id": r[0], "content": r[1], "chunk_index": r[2],
             "title": r[3], "source_url": r[4], "lang": r[5],
             "similarity": float(r[6])}
            for r in cur.fetchall()
        ]
    finally:
        cur.close()
        conn.close()

# ตัวอย่าง
results = semantic_search(
    query="นโยบายรหัสผ่านสำหรับบัญชีผู้ดูแลระบบคืออะไร",
    top_k=5,
    lang_filter="th",
    min_similarity=0.75
)
for r in results:
    print(f"[{r['similarity']:.3f}] {r['title']} — chunk {r['chunk_index']}")

ขั้นตอนที่ 6 — สร้าง RAG Pipeline ครบวงจร

def rag_query(question: str, lang: str = "th", top_k: int = 5) -> dict:
    """RAG Pipeline ครบวงจร: ดึง Chunk และสร้างคำตอบที่มีการอ้างอิง"""
    chunks = semantic_search(question, top_k=top_k, lang_filter=lang)
    if not chunks:
        return {"answer": "ไม่พบเอกสารที่เกี่ยวข้องกับคำถามนี้", "sources": []}

    context = "\n\n".join([f"[{i+1}] {c['content']}" for i, c in enumerate(chunks)])
    sources = [{"title": c["title"], "url": c["source_url"], "similarity": c["similarity"]}
               for c in chunks]

    system_prompt = """คุณเป็นผู้ช่วยที่เป็นประโยชน์ ตอบคำถามโดยใช้เฉพาะ Context ที่ให้มาเท่านั้น
หากไม่มีคำตอบใน Context ให้ระบุว่า "ไม่พบข้อมูลนี้ในเอกสารที่มี"
อ้างอิงหมายเลขแหล่งข้อมูล [1], [2] ฯลฯ เสมอเมื่ออ้างอิงข้อมูล"""

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"Context:\n{context}\n\nคำถาม: {question}\n\nคำตอบ:"}
        ],
        temperature=0.1
    )
    return {"answer": response.choices[0].message.content, "sources": sources}

# ตัวอย่าง
result = rag_query("ข้อกำหนด PDPA สำหรับการเก็บข้อมูลลูกค้าคืออะไร", lang="th")
print(result["answer"])

ขั้นตอนที่ 7 — Hybrid Search (Vector + Full-Text)

การค้นหาด้วย Vector อย่างเดียวบางครั้งพลาด Keyword ที่ตรงกัน Hybrid Search รวม Vector Similarity กับ PostgreSQL Full-Text Search เพื่อ Recall ที่ดีกว่า:

ALTER TABLE document_chunks ADD COLUMN content_tsv tsvector
    GENERATED ALWAYS AS (to_tsvector('english', content)) STORED;

CREATE INDEX ON document_chunks USING gin(content_tsv);
def hybrid_search(query: str, top_k: int = 10, rrf_k: int = 60) -> list[dict]:
    """
    Reciprocal Rank Fusion: รวมการจัดอันดับ Vector Search และ Full-Text Search
    RRF Score = 1/(k + rank_vector) + 1/(k + rank_fts)
    """
    query_embedding = get_embedding(query)
    conn = psycopg2.connect(DB_URL)
    cur = conn.cursor()
    try:
        cur.execute("SET hnsw.ef_search = 100")
        cur.execute("""
            WITH vector_ranked AS (
                SELECT id, content, document_id,
                       ROW_NUMBER() OVER (ORDER BY embedding <=> %s::vector) AS rank
                FROM document_chunks ORDER BY embedding <=> %s::vector LIMIT 20
            ),
            fts_ranked AS (
                SELECT id, content, document_id,
                       ROW_NUMBER() OVER (ORDER BY ts_rank(content_tsv, query) DESC) AS rank
                FROM document_chunks, plainto_tsquery('english', %s) query
                WHERE content_tsv @@ query LIMIT 20
            ),
            rrf AS (
                SELECT COALESCE(v.id, f.id) AS id,
                       COALESCE(v.content, f.content) AS content,
                       COALESCE(v.document_id, f.document_id) AS document_id,
                       COALESCE(1.0/(%s+v.rank),0) + COALESCE(1.0/(%s+f.rank),0) AS rrf_score
                FROM vector_ranked v FULL OUTER JOIN fts_ranked f ON v.id = f.id
            )
            SELECT r.id, r.content, d.title, d.source_url, r.rrf_score
            FROM rrf r JOIN documents d ON d.id = r.document_id
            ORDER BY rrf_score DESC LIMIT %s
        """, [query_embedding, query_embedding, query, rrf_k, rrf_k, top_k])
        return [{"id": r[0], "content": r[1], "title": r[2],
                 "source_url": r[3], "rrf_score": float(r[4])}
                for r in cur.fetchall()]
    finally:
        cur.close()
        conn.close()

Hybrid Search ปรับปรุง Recall ได้ 15–30% สำหรับ Corpus เฉพาะทางที่ผู้ใช้ผสม Keyword และ Semantic Query เหมาะมากสำหรับเอกสารที่มีรหัสผลิตภัณฑ์ หมายเลขกฎระเบียบ หรือชื่อเฉพาะ


การปรับแต่ง Performance

-- เพิ่ม Memory สำหรับ Build Index เร็วขึ้น
SET maintenance_work_mem = '1GB';
CREATE INDEX ON document_chunks USING hnsw (embedding vector_cosine_ops);

-- ปรับ ef_search สูงขึ้นสำหรับ Recall ที่ดีกว่า (ค่าเริ่มต้น 40)
SET hnsw.ef_search = 100;

-- Index แยกตามภาษาสำหรับระบบ Multi-Language
CREATE INDEX ON document_chunks USING hnsw (embedding vector_cosine_ops)
WHERE document_id IN (SELECT id FROM documents WHERE lang = 'th');
-- ตรวจสอบ Query Plan ว่าใช้ Index หรือไม่
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, content, embedding <=> '[0.1, 0.2, ...]'::vector AS distance
FROM document_chunks ORDER BY distance LIMIT 5;

คำถามที่พบบ่อย

pgvector รองรับกี่ Vector?

ในทางปฏิบัติ pgvector จัดการ 1–5 ล้าน Vector ได้ดีบน Hardware มาตรฐาน (RAM 16 GB, SSD) เกินนั้น Latency จะเริ่มสูงขึ้น หากมีมากกว่า 10 ล้าน Vector ควรพิจารณา Qdrant หรือ Weaviate

ควรใช้ Cosine Similarity หรือ L2 Distance?

Cosine Similarity (<=>) เหมาะกว่าสำหรับ Text Embedding เพราะวัดมุมระหว่าง Vector โดยไม่สนขนาด L2 Distance (<->) เหมาะกับ Image Embedding สำหรับ RAG กับข้อความ ให้ใช้ Cosine เสมอ

เก็บ Embedding หลายภาษาใน Table เดียวได้ไหม?

ได้ ถ้า Embedding Model รองรับหลายภาษา BGE-M3 และ multilingual-e5-large ทำงานได้ดีกับภาษาไทย ญี่ปุ่น และจีน เก็บภาษาใน Column lang และ Filter ตอน Query

ขนาด Chunk ที่เหมาะสมคือเท่าไหร่?

500 Token พร้อม Overlap 50 Token เป็นจุดเริ่มต้นที่ดี เล็กเกินไป (< 100 Token) สูญเสีย Context ใหญ่เกินไป (> 1,000 Token) ลด Relevance สำหรับเอกสารที่มีโครงสร้าง เช่น สัญญา หรือเอกสารกฎหมาย PDPA ควร Chunk ตามขอบเขตหัวข้อแทนการนับ Token ตายตัว

pgvector กับ Pinecone ต่างกันอย่างไร?

ถ้าใช้ Postgres อยู่แล้ว ใช้ pgvector ได้เลย คุณได้ SQL JOIN, Transaction, ไม่ต้องจัดการ Infrastructure เพิ่ม และไม่มีค่าใช้จ่าย Per-Query สำหรับโปรเจกต์ RAG ขององค์กรในช่วง < 5 ล้าน Vector pgvector คือทางเลือกที่คุ้มค่าที่สุด


Checklist ก่อน Production

  • [ ] สร้าง HNSW หรือ IVFFlat Index บน Column Embedding แล้ว
  • [ ] ตั้ง maintenance_work_mem อย่างน้อย 512MB สำหรับ Build Index
  • [ ] ปรับ hnsw.ef_search (เริ่มที่ 100 แล้ว Benchmark Recall vs Latency)
  • [ ] สร้าง Partial Index แยกตามภาษาหรือ Tenant ถ้า Filter หนัก
  • [ ] ใช้ Connection Pooling (PgBouncer) หน้า Postgres
  • [ ] การสร้าง Embedding เป็นแบบ Async ผ่าน Queue ไม่ใช่ Inline กับ Request ผู้ใช้
  • [ ] ตั้ง min_similarity Threshold (0.7–0.8) เพื่อกรอง Match คุณภาพต่ำ
  • [ ] Monitor pg_stat_activity สำหรับ Vector Query ที่ช้า
  • [ ] รัน VACUUM ANALYZE บน document_chunks หลัง Bulk Ingest

ขั้นตอนถัดไป

บทความนี้ครอบคลุม Retrieval Layer สำหรับ RAG Stack ที่สมบูรณ์:

  • สำหรับการนำเข้าเอกสารภาษาไทยและญี่ปุ่นด้วย Multilingual Embedding — ดูคู่มือ: LlamaIndex + pgvector: Production RAG for Thai and Japanese Business Documents
  • สำหรับการเลือก Hardware รัน Local Embedding Model โดยไม่พึ่ง OpenAI — ดู: Choosing Hardware for Local LLMs in 2026
  • สำหรับความเข้าใจว่า RAG เข้ากับ Private AI Architecture อย่างไร — ดู: Private AI vs ChatGPT: ต่างกันอย่างไรและธุรกิจของคุณต้องการอะไร

กำลังสร้างระบบ RAG สำหรับเอกสารภายใน Workflow ด้านการปฏิบัติตาม PDPA หรือระบบ Support ลูกค้า? ติดต่อเราได้ที่ hello@simplico.net — เราให้บริการ Deploy Production RAG Stack สำหรับองค์กรในไทยและญี่ปุ่น

pgvector Tutorial: Add Vector Search to PostgreSQL for RAG and Semantic Search

If you’re building a RAG pipeline or any application that needs semantic search, you’ll eventually need to decide where to store your embeddings. Dedicated vector databases (Pinecone, Qdrant, Weaviate) are one option. But for most teams — especially those already running PostgreSQL — pgvector is the faster, cheaper, and operationally simpler path.

Continue reading “pgvector Tutorial: Add Vector Search to PostgreSQL for RAG and Semantic Search”

你的员工有24个密码,你的企业就有24个攻击面

大多数企业不会意识到自身存在身份管理问题——直到安全事故发生之后。

离职员工的账户仍在三个系统中保持活跃,因为没有人更新离职操作清单。某外包人员能够访问财务门户,因为六个月前申请了"临时"访问权限,工单从未关闭。一次网络钓鱼攻击得逞——不是因为安全防护薄弱,而是因为团队在管理24套独立的登录系统,没有人注意到其中一个根本没有启用MFA。

Continue reading “你的员工有24个密码,你的企业就有24个攻击面”

พนักงานของคุณมี 24 รหัสผ่าน ธุรกิจของคุณมี 24 ช่องโหว่

บริษัทส่วนใหญ่ไม่รู้ว่าตัวเองมีปัญหาด้านการจัดการตัวตนดิจิทัล — จนกว่าจะเกิดเหตุ

บัญชีพนักงานที่ลาออกยังค้างอยู่ในสามระบบ เพราะไม่มีใครอัปเดต Checklist การปิดบัญชี ผู้รับเหมาได้สิทธิ์เข้าถึง Portal ฝ่ายการเงิน เพราะต้องการสิทธิ์ "ชั่วคราว" เมื่อหกเดือนก่อนและไม่มีใครปิดใบงาน การโจมตี Phishing สำเร็จ — ไม่ใช่เพราะระบบรักษาความปลอดภัยบกพร่อง แต่เพราะทีมไอทีต้องดูแลระบบล็อกอิน 24 ระบบแยกกัน และไม่มีใครสังเกตว่าระบบหนึ่งไม่มี MFA

Continue reading “พนักงานของคุณมี 24 รหัสผ่าน ธุรกิจของคุณมี 24 ช่องโหว่”

Your Staff Have 24 Passwords. Your Business Has 24 Attack Surfaces.

Most companies don’t discover their identity problem until after the breach.

A departing employee’s account stays active in three systems because nobody updated the offboarding checklist. A contractor gets access to the finance portal because they needed "temporary" access six months ago and the ticket was never closed. A phishing attack succeeds not because your security was weak — but because your team was managing 24 separate login systems and nobody noticed one of them had no MFA.

Continue reading “Your Staff Have 24 Passwords. Your Business Has 24 Attack Surfaces.”

エンジニアリング組織に潜む静かなセキュリティリスク

多くのエンジニアリング組織が同じ無言の脆弱性を抱えています。アイデンティティはすべての人の問題でありながら、誰の責任でもない状態です。CTOやエンジニアリングマネージャーがこの問題をどう解決しているか、そして何を得ているかをご説明します。

Continue reading “エンジニアリング組織に潜む静かなセキュリティリスク”

ความเสี่ยงด้านความปลอดภัยที่ซ่อนอยู่ในองค์กรวิศวกรรมของคุณ

องค์กรวิศวกรรมส่วนใหญ่มีจุดอ่อนที่ไม่ค่อยมีใครพูดถึง: เรื่องของ identity เป็นปัญหาของทุกคน แต่ไม่มีใครรับผิดชอบโดยตรง ต่อไปนี้คือวิธีที่ CTO และผู้จัดการฝ่ายวิศวกรรมกำลังแก้ไขปัญหานี้ และสิ่งที่พวกเขาได้รับจากการดำเนินการดังกล่าว

Continue reading “ความเสี่ยงด้านความปลอดภัยที่ซ่อนอยู่ในองค์กรวิศวกรรมของคุณ”