शनिवार, 21 फ़रवरी 2026

MySQL + Neo4j AI वर्कलोड्स के लिए: क्यों रिलेशनल डेटाबेस अभी भी महत्वपूर्ण हैं

This article was originally published in English at AnotherMySQLDBA.

तो मैंने सोचा कि अब समय आ गया है कि मैं दस्तावेजीकृत करूं कि कैसे पहले से ज्ञात डेटाबेस का उपयोग करके AI एजेंट्स के लिए पर्सिस्टेंट मेमोरी बनाई जाए। वेक्टर डेटाबेस नहीं - MySQL और Neo4j।

यह सैद्धांतिक नहीं है। मैं इस आर्किटेक्चर का रोजाना उपयोग करता हूं, कई प्रोजेक्ट्स में AI एजेंट मेमोरी को हैंडल करने के लिए। यह रहा वास्तव में काम करने वाला स्कीमा और क्वेरी पैटर्न।

द आर्किटेक्चर

AI एजेंट्स को दो प्रकार की मेमोरी की आवश्यकता होती है:

  • संरचित मेमोरी - क्या हुआ, कब, क्यों (MySQL)
  • पैटर्न मेमोरी - क्या किससे जुड़ा है (Neo4j)

वेक्टर डेटाबेस समानता खोज के लिए होते हैं। वे वर्कफ्लो स्टेट या निर्णय इतिहास को ट्रैक करने के लिए नहीं हैं। इसके लिए आपको ACID ट्रांजेक्शन्स और उचित रिलेशनशिप्स की आवश्यकता होती है।

MySQL स्कीमा

यह रहा AI एजेंट पर्सिस्टेंट मेमोरी के लिए वास्तविक स्कीमा:

-- Architecture decisions the AI made
CREATE TABLE architecture_decisions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    title VARCHAR(255) NOT NULL,
    decision TEXT NOT NULL,
    rationale TEXT,
    alternatives_considered TEXT,
    status ENUM('accepted', 'rejected', 'pending') DEFAULT 'accepted',
    decided_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    tags JSON,
    INDEX idx_project_date (project_id, decided_at),
    INDEX idx_status (status)
) ENGINE=InnoDB;

-- Code patterns the AI learned
CREATE TABLE code_patterns (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    category VARCHAR(50) NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    code_example TEXT,
    language VARCHAR(50),
    confidence_score FLOAT DEFAULT 0.5,
    usage_count INT DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_project_category (project_id, category),
    INDEX idx_confidence (confidence_score)
) ENGINE=InnoDB;

-- Work session tracking
CREATE TABLE work_sessions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    session_id VARCHAR(255) UNIQUE NOT NULL,
    project_id INT NOT NULL,
    started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    ended_at DATETIME,
    summary TEXT,
    context JSON,
    INDEX idx_project_session (project_id, started_at)
) ENGINE=InnoDB;

-- Pitfalls to avoid (learned from mistakes)
CREATE TABLE pitfalls (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    category VARCHAR(50),
    title VARCHAR(255) NOT NULL,
    description TEXT,
    how_to_avoid TEXT,
    severity ENUM('critical', 'high', 'medium', 'low'),
    encountered_count INT DEFAULT 1,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_project_severity (project_id, severity)
) ENGINE=InnoDB;

फॉरेन कीज। चेक कंस्ट्रेंट्स। उचित इंडेक्सिंग। यही रिलेशनल डेटाबेस की ताकत है।

क्वेरी पैटर्न्स

यह रहा AI एजेंट मेमोरी के लिए इसे वास्तव में कैसे क्वेरी करें:

-- Get recent decisions for context
SELECT title, decision, rationale, decided_at
FROM architecture_decisions
WHERE project_id = ?
  AND decided_at > DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY decided_at DESC
LIMIT 10;

-- Find high-confidence patterns
SELECT category, name, description, code_example
FROM code_patterns
WHERE project_id = ?
  AND confidence_score >= 0.80
ORDER BY usage_count DESC, confidence_score DESC
LIMIT 20;

-- Check for known pitfalls before implementing
SELECT title, description, how_to_avoid
FROM pitfalls
WHERE project_id = ?
  AND category = ?
  AND severity IN ('critical', 'high')
ORDER BY encountered_count DESC;

-- Track session context across interactions
SELECT context
FROM work_sessions
WHERE session_id = ?
ORDER BY started_at DESC
LIMIT 1;

ये सीधे-सादे SQL क्वेरीज़ हैं। EXPLAIN सटीक रूप से अपेक्षित इंडेक्स उपयोग दिखाता है। कोई सरप्राइज नहीं।

Neo4j लेयर

MySQL संरचित डेटा को हैंडल करता है। Neo4j रिलेशनशिप्स को हैंडल करता है:

// Create nodes for decisions
CREATE (d:Decision {
  id: 'dec_123',
  title: 'Use FastAPI',
  project_id: 1,
  embedding: [0.23, -0.45, ...]  // Vector for similarity
})

// Create relationships
CREATE (d1:Decision {id: 'dec_123', title: 'Use FastAPI'})
CREATE (d2:Decision {id: 'dec_45', title: 'Used Flask before'})
CREATE (d1)-[:SIMILAR_TO {score: 0.85}]->(d2)
CREATE (d1)-[:CONTRADICTS]->(d3:Decision {title: 'Avoid frameworks'})

// Query: Find similar past decisions
MATCH (current:Decision {id: $decision_id})
MATCH (current)-[r:SIMILAR_TO]-(similar:Decision)
WHERE r.score > 0.80
RETURN similar.title, r.score
ORDER BY r.score DESC

// Query: What outcomes followed this pattern?
MATCH (d:Decision)-[:LEADS_TO]->(o:Outcome)
WHERE d.title CONTAINS 'Redis'
RETURN d.title, o.type, o.success_rate

वे एक साथ कैसे काम करते हैं

फ्लो कुछ इस तरह दिखता है:

  1. AI एजेंट कंटेंट जेनरेट करता है या निर्णय लेता है
  2. MySQL में संरचित डेटा स्टोर करें (क्या, कब, क्यों, पूरा संदर्भ)
  3. एम्बेडिंग जेनरेट करें, Neo4j में समान आइटम्स के साथ रिलेशनशिप्स के साथ स्टोर करें
  4. अगली सेशन: Neo4j प्रासंगिक समान निर्णय ढूंढता है
  5. MySQL उन निर्णयों का पूरा विवरण प्रदान करता है

MySQL सत्य का स्रोत है। Neo4j पैटर्न फाइंडर है।

क्यों सिर्फ वेक्टर डेटाबेस नहीं?

मैंने टीम्स को सिर्फ Pinecone या Weaviate से AI एजेंट मेमोरी बनाने की कोशिश करते देखा है। यह अच्छा काम नहीं करता क्योंकि:

वेक्टर DBs अच्छे हैं:

  • क्वेरी से समान दस्तावेज ढूंढने के लिए
  • सीमांटिक सर्च (RAG)
  • "इस तरह की चीजें"

वेक्टर DBs बुरे हैं:

  • "15 मार्च को हमने क्या निर्णय लिया?"
  • "मुझे वे निर्णय दिखाएं जो आउटेज का कारण बने"
  • "इस वर्कफ्लो की वर्तमान स्थिति क्या है?"
  • "किन पैटर्न्स का कॉन्फिडेंस > 0.8 AND usage_count > 10 है?"

ये क्वेरीज़ को संरचित फिल्टरिंग, जॉइन्स, और ट्रांजेक्शन्स की आवश्यकता होती है। यह रिलेशनल डेटाबेस का क्षेत्र है।

MCP और भविष्य

मॉडल कॉन्टेक्स्ट प्रोटोकॉल (MCP) AI सिस्टम्स द्वारा कॉन्टेक्स्ट को हैंडल करने के तरीके को स्टैंडर्डाइज कर रहा है। शुरुआती MCP इम्प्लीमेंटेशन्स वो खोज रही हैं जो हम पहले से जानते थे: आपको संरचित स्टोरेज और ग्राफ रिलेशनशिप्स दोनों की आवश्यकता है।

``````html

MySQL MCP "resources" और "tools" कैटलॉग को हैंडल करता है। Neo4j संदर्भ आइटम्स के बीच "relationships" को हैंडल करता है। Vector embeddings सिर्फ पहेली का एक टुकड़ा हैं।

Production Notes

वर्तमान सिस्टम जो इस आर्किटेक्चर को चला रहा है:

  • MySQL 8.0, 48 tables, ~2GB data
  • Neo4j Community, ~50k nodes, ~200k relationships
  • Query latency: MySQL <10ms, Neo4j <50ms
  • Backup: Standard mysqldump + neo4j-admin dump
  • Monitoring: Same Percona tools I've used for years

परिचालन जटिलता कम है क्योंकि ये परिपक्व databases हैं जिनके ऑपरेशनल पैटर्न अच्छी तरह समझे जाते हैं।

When to Use What

Use CaseDatabase
Workflow state, decisions, audit trailMySQL/PostgreSQL
Pattern detection, similarity, relationshipsNeo4j
Semantic document search (RAG)Vector DB (optional)

State के लिए MySQL से शुरू करें। Pattern recognition की जरूरत हो तो Neo4j जोड़ें। केवल semantic document retrieval कर रहे हों तो ही vector DBs जोड़ें।

Summary

AI agents को persistent memory की जरूरत है। सिर्फ vector database में embeddings नहीं - structured, relational, temporal memory जिसमें pattern recognition हो।

MySQL structured state को हैंडल करता है। Neo4j graph relationships को हैंडल करता है। साथ में ये वही प्रदान करते हैं जो vector databases अकेले नहीं कर सकते।

AI workloads के लिए relational databases को न छोड़ें। हर काम के लिए सही tool इस्तेमाल करें, जो दोनों को साथ में इस्तेमाल करना है।

इस आर्किटेक्चर के AI agent perspective पर और जानने के लिए, 3k1o पर companion post देखें।

MySQL 8.0 JSON Functions: व्यावहारिक उदाहरण और इंडेक्सिंग

This article was originally published in English at AnotherMySQLDBA.

यह पोस्ट MySQL 8.0 के JSON functions का हाथों-हाथ walkthrough कवर करती है। JSON support MySQL में 5.7 से है, लेकिन 8.0 ने एक सार्थक सुधारों का सेट जोड़ा — बेहतर indexing strategies, नए functions, और multi-valued indexes — जो JSON data के साथ काम करना काफी अधिक व्यावहारिक बनाते हैं। निम्नलिखित सबसे आमतौर पर आवश्यक patterns को दस्तावेज करता है, जिसमें EXPLAIN output और performance observations शामिल हैं जो जानने लायक हैं।

यह "JSON vs. relational" बहस पोस्ट नहीं है। यदि आप MySQL में JSON स्टोर कर रहे हैं, तो आपके पास पहले से ही आपके कारण हैं। यहाँ का लक्ष्य यह सुनिश्चित करना है कि आप उपलब्ध tooling का प्रभावी ढंग से उपयोग कर रहे हैं।

परिवेश

mysql> SELECT @@version, @@version_comment\G
*************************** 1. row ***************************
        @@version: 8.0.36
@@version_comment: MySQL Community Server - GPL

टेस्टिंग एक VM पर की गई जिसमें 8GB RAM था और innodb_buffer_pool_size को 4G पर सेट किया गया था। एक housekeeping नोट जो उल्लेख करने लायक है: query_cache_type 8.0 में अप्रासंगिक है क्योंकि query cache को पूरी तरह हटा दिया गया था। यदि आपने 5.7 instance को migrate किया है और आपके my.cnf में अभी भी वह variable है, तो इसे हटा दें — MySQL 8.0 startup error फेंकेगा।

टेस्ट टेबल सेटअप करना

टेस्ट टेबल एक काफी सामान्य pattern को simulate करती है — एक application जो user profile data और event metadata को JSON blobs के रूप में स्टोर करती है:

CREATE TABLE user_events (
  id          INT UNSIGNED NOT NULL AUTO_INCREMENT,
  user_id     INT UNSIGNED NOT NULL,
  event_data  JSON NOT NULL,
  created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  INDEX idx_user (user_id)
) ENGINE=InnoDB;

INSERT INTO user_events (user_id, event_data) VALUES
(1, '{"action":"login","ip":"192.168.1.10","tags":["mobile","vpn"],"score":88}'),
(1, '{"action":"purchase","ip":"192.168.1.10","tags":["desktop"],"score":72,"amount":49.99}'),
(2, '{"action":"login","ip":"10.0.0.5","tags":["mobile"],"score":91}'),
(3, '{"action":"logout","ip":"10.0.0.9","tags":["desktop","vpn"],"score":65}'),
(2, '{"action":"purchase","ip":"10.0.0.5","tags":["mobile"],"score":84,"amount":129.00}');

बेसिक एक्सट्रैक्शन: JSON_VALUE बनाम JSON_EXTRACT

JSON_VALUE() को MySQL 8.0.21 में पेश किया गया था और यह scalar values को निकालने का साफ़-सुथरा तरीका है जिसमें built-in type casting है। उसके पहले, आप JSON_EXTRACT() (या -> shorthand) का उपयोग कर रहे थे और manually casting कर रहे थे, जो काम करता है लेकिन आपकी queries में noise जोड़ता है।

-- Pre-8.0.21 approach
SELECT user_id,
       JSON_EXTRACT(event_data, '$.action') AS action,
       CAST(JSON_EXTRACT(event_data, '$.score') AS UNSIGNED) AS score
FROM user_events;

-- Cleaner 8.0.21+ approach
SELECT user_id,
       JSON_VALUE(event_data, '$.action') AS action,
       JSON_VALUE(event_data, '$.score' RETURNING UNSIGNED) AS score
FROM user_events;

दूसरी query का आउटपुट:

+---------+----------+-------+
| user_id | action   | score |
+---------+----------+-------+
|       1 | login    |    88 |
|       1 | purchase |    72 |
|       2 | login    |    91 |
|       3 | logout   |    65 |
|       2 | purchase |    84 |
+---------+----------+-------+
5 rows in set (0.00 sec)

RETURNING clause वास्तव में उपयोगी है। यह awkward double-cast pattern को समाप्त करता है और बाद में query code पढ़ते समय intent को स्पष्ट बनाता है।

Multi-Valued Indexes: असली गेम चेंजर

यहीं 8.0 ने वास्तव में JSON workloads के लिए सुई को हिलाया। Multi-valued indexes, जो MySQL 8.0.17 से उपलब्ध हैं, आपको JSON column के अंदर array elements को सीधे index करने की अनुमति देते हैं। यह practice में कैसा दिखता है:

ALTER TABLE user_events
  ADD INDEX idx_tags ((CAST(event_data->'$.tags' AS CHAR(64) ARRAY)));

यहाँ EXPLAIN tag value से filter करने वाली query पर before और after दिखाता है:

-- Without the multi-valued index:
EXPLAIN SELECT * FROM user_events
WHERE JSON_CONTAINS(event_data->'$.tags', '"vpn"')\G

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user_events
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 5
     filtered: 100.00
        Extra: Using where

-- After adding the multi-valued index:
EXPLAIN SELECT * FROM user_events
WHERE JSON_CONTAINS(event_data->'$.tags', '"vpn"')\G

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: user_events
   partitions: NULL
         type: range
possible_keys: idx_tags
          key: idx_tags
      key_len: 67
          ref: NULL
         rows: 2
     filtered: 100.00
        Extra: Using where

Full table scan से range scan तक। 5 rows पर यह trivial है, लेकिन लाखों rows वाली table और frequent tag-based filtering पर, वह अंतर महत्वपूर्ण है। सुधार table size और query frequency के साथ सीधे scale करता है।

एक महत्वपूर्ण gotcha: MEMBER OF() और JSON_OVERLAPS() भी multi-valued indexes से लाभान्वित होते हैं, लेकिन JSON_SEARCH() नहीं। यह design time पर query pattern चुनते समय मायने रखता है:

-- This WILL use the multi-valued index:
SELECT * FROM user_events
WHERE 'vpn' MEMBER OF (event_data->'$.tags');

-- This will NOT use it:
SELECT * FROM user_events
WHERE JSON_SEARCH(event_data->'$.tags', 'one', 'vpn') IS NOT NULL;

JSON को Aggregate और Transform करना

कुछ aggregation functions जो अच्छी तरह जानने लायक हैं:

``````html
-- Build a JSON array of actions per user
SELECT user_id,
       JSON_ARRAYAGG(JSON_VALUE(event_data, '$.action')) AS actions
FROM user_events
GROUP BY user_id;

+---------+----------------------+
| user_id | actions              |
+---------+----------------------+
|       1 | ["login","purchase"] |
|       2 | ["login","purchase"] |
|       3 | ["logout"]           |
+---------+----------------------+
3 rows in set (0.01 sec)

-- Summarize into a JSON object keyed by action
SELECT user_id,
       JSON_OBJECTAGG(
         JSON_VALUE(event_data, '$.action'),
         JSON_VALUE(event_data, '$.score' RETURNING UNSIGNED)
       ) AS score_by_action
FROM user_events
GROUP BY user_id;

+---------+--------------------------------+
| user_id | score_by_action                |
+---------+--------------------------------+
|       1 | {"login": 88, "purchase": 72}  |
|       2 | {"login": 91, "purchase": 84}  |
|       3 | {"logout": 65}                 |
+---------+--------------------------------+
3 rows in set (0.00 sec)

JSON_OBJECTAGG() त्रुटि फेंकेगा यदि किसी समूह में डुप्लिकेट कुंजियाँ हों। यह जानना उपयोगी है इससे पहले कि आप इसे प्रोडक्शन ETL पाइपलाइन में सामना करें। उस स्थिति में, आपको अपस्ट्रीम में डुप्लिकेट हटाने होंगे या एप्लिकेशन लॉजिक में इसे हैंडल करना होगा इससे पहले कि डेटा इस एग्रीगेशन स्टेप तक पहुँचे।

JSON-हैवी क्वेरीज़ के बाद SHOW STATUS चेक करना

क्वेरी पैटर्न का मूल्यांकन करते समय, हैंडलर मेट्रिक्स चेक करना एक उपयोगी आदत है:

FLUSH STATUS;

SELECT * FROM user_events
WHERE JSON_VALUE(event_data, '$.score' RETURNING UNSIGNED) > 80;

SHOW STATUS LIKE 'Handler_read%';

+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Handler_read_first         | 1     |
| Handler_read_key           | 0     |
| Handler_read_last          | 0     |
| Handler_read_next          | 4     |
| Handler_read_prev          | 0     |
| Handler_read_rnd           | 0     |
| Handler_read_rnd_next      | 6     |
+----------------------------+-------+
7 rows in set (0.00 sec)

Handler_read_rnd_next मान पूर्ण स्कैन की पुष्टि करता है — आश्चर्य की कोई बात नहीं क्योंकि score मान पर कोई फंक्शनल इंडेक्स नहीं है। स्केल पर score-आधारित फिल्टरिंग के लिए, एक जेनरेटेड कॉलम जिसमें इंडेक्स हो, सही उत्तर है:

ALTER TABLE user_events
  ADD COLUMN score_val TINYINT UNSIGNED
    GENERATED ALWAYS AS (JSON_VALUE(event_data, '$.score' RETURNING UNSIGNED)) VIRTUAL,
  ADD INDEX idx_score (score_val);

उसे जोड़ने के बाद, वही क्वेरी एक उचित इंडेक्स रेंज स्कैन पर आ जाती है। JSON फील्ड्स पर जेनरेटेड कॉलम MySQL 8.0 और Percona Server 8.0 दोनों में उपलब्ध हैं, और वे किसी भी सार्थक स्केल पर स्केलर JSON फील्ड फिल्टरिंग के लिए सबसे विश्वसनीय मार्ग बने रहते हैं।

यदि आप Percona Server चला रहे हैं, तो Percona Toolkit से pt-query-digest अभी भी सबसे व्यावहारिक तरीका है जिससे आप यह पहचान सकें कि कौन सी JSON-हैवी क्वेरीज़ वास्तव में प्रोडक्शन में दर्द पैदा कर रही हैं इससे पहले कि आप सट्टेबाजी में इंडेक्स जोड़ना शुरू करें।

व्यावहारिक अवलोकन

  • मल्टी-वैल्यूड इंडेक्स (8.0.17+) एक लंबे समय से लंबित सुधार हैं और अच्छा काम करते हैं जब आपके क्वेरी पैटर्न JSON_CONTAINS() या MEMBER OF() के साथ संरेखित हों
  • JSON_VALUE() with RETURNING (8.0.21+) पुराने cast-after-extract पैटर्न से साफ-सुथरा है और इसे लगातार अपनाना चाहिए
  • जेनरेटेड कॉलम प्लस इंडेक्स स्केल पर स्केलर JSON फील्ड फिल्टरिंग के लिए सबसे विश्वसनीय मार्ग बने रहते हैं
  • ग्रुप्ड डेटा में JSON_OBJECTAGG() डुप्लिकेट कुंजी त्रुटियों पर नजर रखें — यह ETL पाइपलाइनों में कठोर त्रुटि के रूप में उभरता है और टेस्टिंग में आसानी से छूट सकता है यदि आपका सैंपल डेटा संयोग से साफ हो
  • हमेशा EXPLAIN से इंडेक्स उपयोग की जाँच करें — ऑप्टिमाइज़र हमेशा जटिल WHERE क्लॉज़ में मल्टी-वैल्यूड इंडेक्स को नहीं चुनता, और मान लेने के बजाय पुष्टि करना उचित है

सारांश

MySQL 8.0 के JSON सुधार वास्तव में उपयोगी हैं, विशेष रूप से मल्टी-वैल्यूड इंडेक्स और JSON_VALUE() टाइप कास्टिंग के साथ। वे अच्छे स्कीमा डिज़ाइन की जगह नहीं लेते, लेकिन उन मामलों के लिए जहाँ JSON स्टोरेज उचित है या विरासत में मिला है, आपके पास अब वास्तविक टूल्स हैं काम करने के लिए बजाय सिर्फ आशा करने के कि ऑप्टिमाइज़र इसे समझ लेगा। विशेष रूप से जेनरेटेड कॉलम पैटर्न का मूल्यांकन जल्दी करना चाहिए यदि आपको पता है कि कुछ JSON फील्ड्स WHERE क्लॉज़ में नियमित रूप से उपयोग होंगे।

उपयोगी संदर्भ: