Database Architecture
MySQL and Qdrant database architecture for WordPress projects and vector-based AI search.
Overview
WDG uses two database systems:
- MySQL: WordPress data storage
- Qdrant: Vector embeddings for AI search
MySQL Architecture
Single MySQL Instance
All projects share one MySQL container with isolated databases:
MySQL Container wdg-mysql (host port 6306 → container 3306)
├── mysql (system database)
├── information_schema
├── performance_schema
├── wp_my_site (project database)
├── wp_client_website (project database)
└── wp_demo (project database)Database Naming Convention
- Project databases:
wp_{project-name} - Underscores replace hyphens:
my-site→wp_my_site - Lowercase only
Configuration
# docker-compose.yml
mysql:
image: mysql:8.0
container_name: wdg-mysql
command: --default-authentication-plugin=mysql_native_password --require_secure_transport=OFF
ports:
- "${MYSQL_PORT}:3306" # default host 6306
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${WP_DB_NAME}
MYSQL_USER: ${WP_DB_USER}
MYSQL_PASSWORD: ${WP_DB_PASSWORD}
volumes:
- ./data/mysql:/var/lib/mysql # host bind mount, not a named volumeThe mysql service mounts only its data directory (./data/mysql); it does not mount a custom my.cnf. Tuning is supplied via the command: flags above (--require_secure_transport=OFF allows non-TLS client connections, which the local WordPress containers use).
A
my.cnfdoes exist atservices/wordpress/my.cnf, but it is baked into the WordPress image as the MySQL client configuration — it is not mounted into thewdg-mysqlserver container. There is noservices/mysql/directory.
WordPress Database Schema
Standard Tables
Each project database includes:
-- Core tables
wp_posts
wp_postmeta
wp_users
wp_usermeta
wp_terms
wp_term_taxonomy
wp_term_relationships
wp_comments
wp_commentmeta
wp_options
-- Multisite (if enabled)
wp_blogs
wp_blog_versions
wp_site
wp_sitemetaProject-Specific Tables
Custom tables created by themes/plugins:
-- Example custom tables
wp_wikit_blocks
wp_wikit_patterns
wp_custom_post_types
wp_user_preferencesIndexing Strategy
-- Performance indexes
CREATE INDEX idx_post_type ON wp_posts(post_type, post_status);
CREATE INDEX idx_post_author ON wp_posts(post_author);
CREATE INDEX idx_post_date ON wp_posts(post_date);
CREATE INDEX idx_meta_key ON wp_postmeta(meta_key(191));Qdrant Vector Database
Collection Architecture
Qdrant Container wdg-qdrant (host ports 6333 REST / 6334 gRPC)
├── wdg_framework (Wikit code)
├── platform (CLI, MCP server, indexer, dashboard, docs)
├── project_my_site
└── project_client_websiteVector counts and sizes vary by codebase. Collection names map one-to-one to scopes: wdg_framework, platform, and project_{name} (hyphens in the project name become underscores, e.g. my-site → project_my_site).
Vector Schema
The payload is a flat dict (no nested metadata object). Each point's id is md5("{file_path}:{line_number}:{name}"). Core keys:
{
"id": "md5(file_path:line_number:name)",
"vector": [0.1, 0.2, 0.3, "..."],
"payload": {
"file_path": "wp-content/themes/custom/functions.php",
"line_number": 45,
"name": "register_custom_post_types",
"component_type": "function",
"language": "php",
"content": "function register_custom_post_types() {...}",
"project": "my-site"
}
}The indexer also stores a few descriptive keys (file_extension, is_wikit, repository) and component-specific optional fields (e.g. calls, extends, implements, ACF and hook metadata). There is no line_start/line_end, commit_hash, branch, or indexed_at — only the single line_number above.
Collection Configuration
Collections are created on demand by the indexer with a single fixed config: 384-dimensional vectors and Cosine distance (size follows the loaded model). The indexer does not set custom optimizer or HNSW parameters — Qdrant defaults apply.
# How the indexer creates a collection (indexer/indexer.py)
client.create_collection(
collection_name="project_my_site",
vectors_config=VectorParams(
size=EMBEDDING_DIM, # 384 for all-MiniLM-L6-v2
distance=Distance.COSINE
)
)Database Operations
MySQL Operations
Create Project Database
# Via CLI
wdg db create my-site
# Manual
docker exec wdg-mysql mysql -uroot -p$MYSQL_ROOT_PASSWORD -e \
"CREATE DATABASE wp_my_site CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"Backup Database
# Export
wdg db export my-site backup.sql
# Manual
docker exec wdg-mysql mysqldump \
-uroot -p$MYSQL_ROOT_PASSWORD \
wp_my_site > backup.sqlImport Database
# Import
wdg db import my-site backup.sql
# Manual
docker exec -i wdg-mysql mysql \
-uroot -p$MYSQL_ROOT_PASSWORD \
wp_my_site < backup.sqlSearch & Replace
# Update domain
wdg db search-replace my-site "old-domain.com" "new-domain.com"
# Via WP-CLI
wdg wp my-site search-replace \
"http://localhost" \
"https://production.com" \
--all-tablesQdrant Operations
Query Vectors
# Semantic search
results = client.search(
collection_name="project_my_site",
query_vector=query_embedding,
limit=10,
with_payload=True
)Filter by Metadata
# Search only PHP functions
results = client.search(
collection_name="project_my_site",
query_vector=query_embedding,
query_filter={
"must": [
{"key": "language", "match": {"value": "php"}},
{"key": "component_type", "match": {"value": "function"}}
]
},
limit=10
)Performance Optimization
MySQL Tuning
# Check status
docker exec wdg-mysql mysql -uroot -p -e "SHOW STATUS LIKE 'Threads_connected';"
# Optimize tables
wdg wp my-site db optimize
# Analyze tables
docker exec wdg-mysql mysqlcheck -uroot -p wp_my_site --analyzeQdrant Optimization
# Optimize collection
curl -X POST http://localhost:6333/collections/project_my_site/optimizers
# Check collection info
curl http://localhost:6333/collections/project_my_siteBackup Strategies
Automated Backups
#!/bin/bash
# backup-databases.sh
DATE=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="backups/$DATE"
mkdir -p $BACKUP_DIR
# Backup all MySQL databases
for db in $(docker exec wdg-mysql mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "SHOW DATABASES" | grep ^wp_); do
echo "Backing up $db..."
docker exec wdg-mysql mysqldump -uroot -p$MYSQL_ROOT_PASSWORD $db > "$BACKUP_DIR/$db.sql"
done
# Backup Qdrant collections
curl -X POST http://localhost:6333/collections/snapshot -d '{}' > "$BACKUP_DIR/qdrant.snapshot"
# Compress
tar -czf "backups/full-backup-$DATE.tar.gz" $BACKUP_DIR
rm -rf $BACKUP_DIR
echo "Backup complete: backups/full-backup-$DATE.tar.gz"Monitoring
MySQL Monitoring
# Connection count
docker exec wdg-mysql mysql -uroot -p -e "SHOW STATUS LIKE 'Threads_connected';"
# Query performance
docker exec wdg-mysql mysql -uroot -p -e "SHOW FULL PROCESSLIST;"
# Database sizes
docker exec wdg-mysql mysql -uroot -p -e "
SELECT
table_schema AS 'Database',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)'
FROM information_schema.tables
WHERE table_schema LIKE 'wp_%'
GROUP BY table_schema;
"Qdrant Monitoring
# Collection stats
curl http://localhost:6333/collections/project_my_site
# Cluster status
curl http://localhost:6333/cluster
# Metrics
curl http://localhost:6333/metricsTroubleshooting
MySQL Issues
# Connection refused
docker logs wdg-mysql
docker restart wdg-mysql
# Corrupted table
wdg wp my-site db repair
# Slow queries
docker exec wdg-mysql mysql -uroot -p -e "SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10;"Qdrant Issues
# Collection not found
curl http://localhost:6333/collections
# Recreate collection
wdg collections delete project_my_site
wdg index my-site
# Check logs
docker logs wdg-qdrantSee Also: