{"id":4630,"date":"2026-09-20T09:01:11","date_gmt":"2026-09-20T03:31:11","guid":{"rendered":"https:\/\/cpanelfree.com\/blog\/zero-downtime-database-schema-migrations-with-pt-online-schema-change-and-gh-ost\/"},"modified":"2026-09-20T09:01:11","modified_gmt":"2026-09-20T03:31:11","slug":"zero-downtime-database-schema-migrations-with-pt-online-schema-change-and-gh-ost","status":"publish","type":"post","link":"https:\/\/cpanelfree.com\/blog\/zero-downtime-database-schema-migrations-with-pt-online-schema-change-and-gh-ost\/","title":{"rendered":"Zero-Downtime Database Schema Migrations with pt-online-schema-change and gh-ost"},"content":{"rendered":"<p>Executing large-scale DDL operations on terabyte-scale relational databases without degrading OLTP latency is one of the most hazardous engineering challenges in production operations. At <a href=\"https:\/\/cpanelfree.com\">CpanelFree<\/a>, maintaining millisecond query responsiveness requires eliminating table locks and metadata locking bottlenecks entirely during live database alters. By replacing legacy blocking DDL queries with asynchronous, lock-free migration frameworks, infrastructure teams can safely perform schema updates on mission-critical databases without risking query timeouts or connection spikes.<\/p>\n<p><!-- more --><\/p>\n<h2>Understanding Zero-Downtime Online Schema Changes (OSC)<\/h2>\n<div style=\"background:#1e293b;border-left:4px solid #10b981;padding:16px 20px;margin:24px 0;border-radius:0 8px 8px 0;color:#e2e8f0\"><strong style=\"color:#10b981\">Direct Answer:<\/strong> Zero-downtime schema migration alters relational tables without locking readers or writers by replicating data into a ghost table asynchronously. While pt-online-schema-change relies on synchronous database triggers, gh-ost utilizes asynchronous MySQL binary log (binlog) stream parsing, completely avoiding lock contention and metadata locks during high-throughput OLTP workloads.<\/div>\n<p>In standard MySQL and MariaDB deployments, executing an <code>ALTER TABLE<\/code> statement triggers an exclusive metadata lock (MDL) or forces a full table copy. Even with the introduction of InnoDB Online DDL (<code>ALGORITHM=INPLACE<\/code>), many operations\u2014such as modifying column data types, adding generated columns, or reorganizing clustered indexes\u2014still demand substantial exclusive locking phases or cause extreme I\/O spikes that exhaust server IOPS. Under heavy concurrent traffic, an incoming DDL statement waiting for an MDL queues all subsequent incoming <code>SELECT<\/code>, <code>INSERT<\/code>, and <code>UPDATE<\/code> queries behind it, causing rapid connection pool exhaustion, application request pileups, and severe operational outages.<\/p>\n<p>To circumvent table-level and metadata locks, the database engineering community developed two primary paradigms for Online Schema Change (OSC): <strong>trigger-based replication<\/strong> (championed by Percona&#8217;s <code>pt-online-schema-change<\/code>) and <strong>binary log stream inspection<\/strong> (engineered by GitHub in <code>gh-ost<\/code>). Both utilities build a new ghost table reflecting the desired schema definition, backfill existing records in manageable chunk sizes, synchronize live modifications occurring during the migration window, and execute an atomic table swap to complete the cutover.<\/p>\n<h2>Architectural Comparison: pt-online-schema-change vs. gh-ost<\/h2>\n<p>Selecting the optimal migration utility requires understanding their underlying mechanisms for capturing real-time mutations, managing transaction overhead, and interacting with the database engine&#8217;s concurrency model.<\/p>\n<table style=\"width:100%;border-collapse:collapse;margin:24px 0;background:#1e293b;color:#e2e8f0;font-size:14px;border-radius:8px;overflow:hidden\">\n<thead style=\"background:#0f172a;color:#38bdf8\">\n<tr>\n<th style=\"padding:12px 16px;border-bottom:2px solid #334155;text-align:left\">Architectural Dimension<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #334155;text-align:left\">pt-online-schema-change (Percona)<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #334155;text-align:left\">gh-ost (GitHub)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;font-weight:600\">Mutation Capture Method<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155\">Synchronous Triggers (AFTER INSERT, UPDATE, DELETE)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;color:#10b981;font-weight:600\">Asynchronous Binlog Streaming (Replication Protocol)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;font-weight:600\">OLTP Write Latency Impact<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155\">Amplified: Triggers execute in primary transaction context<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;color:#10b981;font-weight:600\">Near Zero: Non-intrusive stream processing via socket<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;font-weight:600\">Lock Contention Risk<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155\">Moderate to High: Trigger creation requires metadata lock<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;color:#10b981;font-weight:600\">Minimal: No triggers installed on source tables<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;font-weight:600\">Throttling &amp; Pause Capability<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155\">Coarse: Pauses chunk copying; cannot stop trigger writes<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;color:#10b981;font-weight:600\">Dynamic: Interactive runtime throttling &amp; full pause<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;font-weight:600\">Replication Lag Sensitivity<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155\">Can cascade lag to replicas due to trigger-induced row events<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;color:#10b981;font-weight:600\">Active replication lag throttling via heartbeat injection<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;font-weight:600\">Foreign Key Support<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155\">Supported with constraint rebuilding (drop_swap\/rebuild)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #334155;color:#f59e0b;font-weight:600\">Strictly Prohibited: Rejects tables with foreign keys<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div style=\"background:#1e293b;border-left:4px solid #38bdf8;padding:16px 20px;margin:24px 0;border-radius:0 8px 8px 0;color:#e2e8f0\"><strong style=\"color:#38bdf8\">Architecture Note:<\/strong> The decisive flaw of trigger-based solutions like <code>pt-online-schema-change<\/code> is transactional amplification. Every application write operation to the original table executes synchronous trigger code that writes to the ghost table within the exact same InnoDB transaction boundary. If your database experiences an unexpected write spike, trigger executions double the write workload, potentially pushing database threads into lock wait timeouts and thread exhaustion.<\/div>\n<h2>Kernel &amp; Storage Tuning for High-Throughput Migrations<\/h2>\n<p>During backfill operations involving millions of rows, background read and write pressure can saturate storage controllers and displace cached hot working sets from the InnoDB buffer pool. To mitigate I\/O thrashing and ensure stable kernel-level page flushing, implement the following production sysctl configuration file before running data migrations.<\/p>\n<pre><code style=\"background:#0f172a;color:#38bdf8;padding:16px;border-radius:8px;display:block;font-family:monospace;font-size:13px;line-height:1.6\"># \/etc\/sysctl.d\/99-mysql-migration.conf\n# Linux Kernel I\/O and Concurrency Hardening for Database Migrations\n\n# Prevent kernel page cache from aggressively monopolizing RAM during bulk copying\nvm.dirty_background_ratio = 5\nvm.dirty_ratio = 10\n\n# Extend dirty page expire time to allow steady, continuous background flushing\nvm.dirty_expire_centisecs = 3000\nvm.dirty_writeback_centisecs = 500\n\n# Minimize swapping aggressive behavior on database hosts\nvm.swappiness = 1\n\n# Increase network socket backlog to absorb cutover connection bursts\nnet.core.somaxconn = 65535\nnet.ipv4.tcp_max_syn_backlog = 16384\nnet.core.netdev_max_backlog = 10000\n\n# Protect local port exhaustion during intense replica health-checking\nnet.ipv4.ip_local_port_range = 10240 65535\nnet.ipv4.tcp_fin_timeout = 15\nnet.ipv4.tcp_tw_reuse = 1\n\n# Optimize file descriptor limits for high connection and table-open counts\nfs.file-max = 2097152<\/code><\/pre>\n<p>Apply these parameters dynamically using <code>sysctl -p \/etc\/sysctl.d\/99-mysql-migration.conf<\/code>. Tuning dirty ratios ensures that asynchronous chunk copies do not saturate the Linux page cache, which would otherwise force synchronous blocking writes at the block device layer.<\/p>\n<h2>Deploying gh-ost: Binlog-Driven Production Migration<\/h2>\n<p><code>gh-ost<\/code> operates as an external client connecting to your database cluster. It can stream binary logs directly from a replica server while writing chunked data to the primary writer instance, completely isolating migration read overhead from your primary transactional workload.<\/p>\n<h3>Prerequisites for gh-ost<\/h3>\n<ul>\n<li><strong>Row-Based Replication:<\/strong> The MySQL server must run with <code>binlog_format=ROW<\/code> and <code>binlog_row_image=FULL<\/code>.<\/li>\n<li><strong>Explicit Primary Key:<\/strong> The table must have an explicit integer or unique primary key to facilitate deterministic row chunking.<\/li>\n<li><strong>Absence of Foreign Keys:<\/strong> Tables referencing foreign key constraints are intentionally not supported due to triggerless binlog limitations.<\/li>\n<\/ul>\n<div style=\"background:#1e293b;border-left:4px solid #10b981;padding:16px 20px;margin:24px 0;border-radius:0 8px 8px 0;color:#e2e8f0\"><strong style=\"color:#10b981\">Operational Best Practice:<\/strong> Always execute <code>gh-ost<\/code> with dynamic replica inspection. In this topology, <code>gh-ost<\/code> reads binary logs and validates replication lag against a secondary replica while directing backfill writes to the primary database. This guarantees that your primary database never expends CPU cycles decoding binlogs.<\/div>\n<h3>Automated Systemd Service for gh-ost Migrations<\/h3>\n<p>To ensure migrations run within a controlled process supervisor with full resource management, structured logging, and automated failure signaling, encapsulate your migration jobs in a parameterized systemd service.<\/p>\n<pre><code style=\"background:#0f172a;color:#38bdf8;padding:16px;border-radius:8px;display:block;font-family:monospace;font-size:13px;line-height:1.6\"># \/etc\/systemd\/system\/ghost-migration@.service\n[Unit]\nDescription=gh-ost Schema Migration Runner for %I\nAfter=network.target mysql.service\nWants=mysql.service\n\n[Service]\nType=simple\nUser=mysql\nGroup=mysql\nWorkingDirectory=\/var\/log\/ghost\nExecStart=\/usr\/local\/bin\/gh-ost \\\n  --conf=\/etc\/ghost\/%i.conf \\\n  --execute \\\n  --verbose\n\n# Process resiliency &amp; resource accounting\nRestart=no\nTimeoutSec=86400\nLimitNOFILE=65536\nCPUQuota=150%\nMemoryHigh=4G\nMemoryMax=6G\n\n# Security sandboxing\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=\/var\/log\/ghost \/tmp\nNoNewPrivileges=true\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n<h3>Production Configuration File for gh-ost<\/h3>\n<p>Create the instance-specific configuration file referenced by the systemd unit. This decouples database credentials, throttling thresholds, and table identifiers from your command-line interface.<\/p>\n<pre><code style=\"background:#0f172a;color:#38bdf8;padding:16px;border-radius:8px;display:block;font-family:monospace;font-size:13px;line-height:1.6\"># \/etc\/ghost\/production_users.conf\n# Production gh-ost configuration for schema alteration\n\nhost=127.0.0.1\nport=3306\nuser=migration_svc\npassword=SecretEnterpriseVaultKey99!\ndatabase=app_production\ntable=users\nalter=ADD COLUMN two_factor_enforced TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, ADD INDEX idx_users_2fa (two_factor_enforced)\n\n# Concurrency and chunking parameters\nchunk-size=2500\nmax-lag-millis=1200\nmax-load=Threads_running=35,Threads_connected=400\ncritical-load=Threads_running=70,Threads_connected=800\nthrottle-control-replicas=192.168.10.21:3306,192.168.10.22:3306\n\n# Dynamic control socket &amp; inspection\nserve-socket-file=\/tmp\/ghost.app_production.users.sock\ninitially-drop-ghost-table=true\ninitially-drop-old-table=false\ncut-over=atomic\ncut-over-lock-timeout-seconds=3\napprove-renamed-columns=true<\/code><\/pre>\n<h2>Deploying pt-online-schema-change: Trigger-Based Migration<\/h2>\n<p>While <code>gh-ost<\/code> is ideal for large OLTP tables, <code>pt-online-schema-change<\/code> remains the industry standard when dealing with tables bounded by foreign keys or environments where row-based binary logging is unavailable. When executing Percona&#8217;s tool, strict safeguards must be enforced to prevent trigger-induced locking cascades.<\/p>\n<pre><code style=\"background:#0f172a;color:#38bdf8;padding:16px;border-radius:8px;display:block;font-family:monospace;font-size:13px;line-height:1.6\">#!\/usr\/bin\/env bash\n# \/usr\/local\/bin\/run-pt-osc.sh\n# Hardened pt-online-schema-change execution wrapper\n\nset -euo pipefail\n\nDB_NAME=\"app_production\"\nTABLE_NAME=\"orders\"\nALTER_STMT=\"ADD COLUMN fulfillment_status VARCHAR(32) NOT NULL DEFAULT 'unfulfilled', ADD INDEX idx_orders_fulfillment (fulfillment_status)\"\nAUTH_FILE=\"\/etc\/mysql\/migration_auth.cnf\"\n\necho \"[$(date --iso-8601=seconds)] Initializing pt-online-schema-change dry run...\"\n\n# Step 1: Mandatory Dry-Run Verification\npt-online-schema-change \\\n  --defaults-file=\"${AUTH_FILE}\" \\\n  --host=127.0.0.1 \\\n  --database=\"${DB_NAME}\" \\\n  --table=\"${TABLE_NAME}\" \\\n  --alter=\"${ALTER_STMT}\" \\\n  --dry-run \\\n  --print\n\necho \"[$(date --iso-8601=seconds)] Dry run passed. Executing live migration...\"\n\n# Step 2: Live Execution with Load Throttling\npt-online-schema-change \\\n  --defaults-file=\"${AUTH_FILE}\" \\\n  --host=127.0.0.1 \\\n  --database=\"${DB_NAME}\" \\\n  --table=\"${TABLE_NAME}\" \\\n  --alter=\"${ALTER_STMT}\" \\\n  --execute \\\n  --chunk-size=1500 \\\n  --chunk-size-limit=3.5 \\\n  --max-load=\"Threads_running=30\" \\\n  --critical-load=\"Threads_running=60\" \\\n  --max-lag=1 \\\n  --check-interval=1s \\\n  --recursion-method=\"processlist\" \\\n  --alter-foreign-keys-method=\"auto\" \\\n  --preserve-triggers \\\n  --set-vars=\"innodb_lock_wait_timeout=3,lock_wait_timeout=5\" \\\n  --print\n\necho \"[$(date --iso-8601=seconds)] Migration successfully completed.\"<\/code><\/pre>\n<div style=\"background:#1e293b;border-left:4px solid #f59e0b;padding:16px 20px;margin:24px 0;border-radius:0 8px 8px 0;color:#e2e8f0\"><strong style=\"color:#f59e0b\">Safety Warning:<\/strong> Notice the <code>--set-vars=\"innodb_lock_wait_timeout=3,lock_wait_timeout=5\"<\/code> setting in the script above. This forces <code>pt-online-schema-change<\/code> to fail immediately if it cannot acquire metadata locks during trigger installation or cutover, rather than waiting and queuing subsequent incoming production queries behind it.<\/div>\n<h2>The Atomic Cutover Mechanism Explained<\/h2>\n<p>Both tools conclude the data migration phase by executing a cutover. Understanding the cutover mechanism is critical to guaranteeing zero dropped connections and complete data consistency.<\/p>\n<p>In <code>pt-online-schema-change<\/code>, the swap is achieved via MySQL&#8217;s atomic multi-table rename statement:<\/p>\n<pre><code style=\"background:#0f172a;color:#38bdf8;padding:16px;border-radius:8px;display:block;font-family:monospace;font-size:13px;line-height:1.6\">RENAME TABLE `app_production`.`orders` TO `app_production`.`_orders_old`,\n             `app_production`.`_orders_new` TO `app_production`.`orders`;<\/code><\/pre>\n<p>MySQL executes this rename as a single atomic operation. However, acquiring the exclusive metadata lock required for this rename can be blocked by long-running <code>SELECT<\/code> queries. If blocked, the rename statement will queue behind the slow query, blocking all subsequent incoming reads and writes on the table.<\/p>\n<p><code>gh-ost<\/code> solves this lock acquisition hazard through a dual-connection atomic cutover strategy:<\/p>\n<ol style=\"color:#cbd5e1;line-height:1.8;padding-left:24px\">\n<li><strong>Lock Phase (Connection A):<\/strong> <code>gh-ost<\/code> issues <code>LOCK TABLES users WRITE, `_users_gho` WRITE<\/code>. While this holds the lock, incoming application writes to <code>users<\/code> are blocked and queue harmlessly.<\/li>\n<li><strong>Rename Phase (Connection B):<\/strong> <code>gh-ost<\/code> opens a second connection and issues <code>RENAME TABLE users TO `_users_del`, `_users_gho` TO users<\/code>. This statement waits for the lock held by Connection A.<\/li>\n<li><strong>Release Phase (Connection A):<\/strong> <code>gh-ost<\/code> closes Connection A. MySQL immediately prioritizes the waiting <code>RENAME<\/code> query from Connection B before any queued application writes are granted access. The swap executes instantaneously, and the queued queries resume on the newly altered table without dropping a single packet.<\/li>\n<\/ol>\n<h2>Frequently Asked Questions<\/h2>\n<details style=\"background:#1e293b;border:1px solid #334155;border-radius:8px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#38bdf8\">Why does pt-online-schema-change cause thread pool spikes during traffic surges?<\/summary>\n<p style=\"margin-top:10px;color:#cbd5e1\">Because pt-online-schema-change uses synchronous MySQL triggers, every single write (INSERT, UPDATE, DELETE) made to the original table must synchronously execute the trigger code to mirror the mutation to the ghost table within the exact same transaction. When write throughput spikes, this synchronous overhead doubles the lock footprint and write IOPS, quickly saturating MySQL threads_running and causing connection pool exhaustion.<\/p>\n<\/details>\n<details style=\"background:#1e293b;border:1px solid #334155;border-radius:8px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#38bdf8\">Can gh-ost migrate tables without binary logging enabled?<\/summary>\n<p style=\"margin-top:10px;color:#cbd5e1\">No. gh-ost is fundamentally architected as a binary log stream consumer. It requires MySQL to have binary logging enabled with row-based formatting (<code>binlog_format=ROW<\/code>). If your database instance cannot enable binary logging, you must utilize pt-online-schema-change or MySQL native Online DDL instead.<\/p>\n<\/details>\n<details style=\"background:#1e293b;border:1px solid #334155;border-radius:8px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#38bdf8\">How does gh-ost handle foreign key constraints?<\/summary>\n<p style=\"margin-top:10px;color:#cbd5e1\">gh-ost strictly disallows running migrations on tables with foreign keys. Because it relies on asynchronous binlog inspection rather than database-level triggers, it cannot reliably maintain foreign key referential integrity cascades across tables during live migrations. If your schema uses foreign keys, you must use pt-online-schema-change with appropriate <code>--alter-foreign-keys-method<\/code> settings.<\/p>\n<\/details>\n<details style=\"background:#1e293b;border:1px solid #334155;border-radius:8px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#38bdf8\">What happens if a migration is abruptly killed halfway through?<\/summary>\n<p style=\"margin-top:10px;color:#cbd5e1\">Both utilities are designed to fail safely without corrupting production data. In gh-ost, terminating the process simply leaves the ghost table (<code>_table_gho<\/code>) and binlog tracking table intact; your production table is never modified until the final cutover. In pt-online-schema-change, terminating the process leaves triggers and the temporary table in place, requiring cleanup via <code>DROP TRIGGER<\/code> and <code>DROP TABLE<\/code> before starting a new run.<\/p>\n<\/details>\n<div style=\"background:linear-gradient(135deg, #0f172a 0%, #1e293b 100%);border:1px solid #334155;border-radius:12px;padding:32px;margin:40px 0;text-align:center\">\n<h3 style=\"color:#ffffff;margin-top:0;font-size:22px\">Ready to Deploy High-Performance Infrastructure?<\/h3>\n<p style=\"color:#cbd5e1;font-size:16px;line-height:1.6;max-width:680px;margin:12px auto 24px auto\">Experience blazing-fast NVMe storage, unmetered bandwidth, and enterprise LiteSpeed caching on CpanelFree.<\/p>\n<p>  <a href=\"https:\/\/cpanelfree.com\" style=\"background:#38bdf8;color:#0f172a;font-weight:700;padding:12px 28px;border-radius:6px;text-decoration:none;display:inline-block;font-size:15px\">Get Started with Free Cloud Hosting &rarr;<\/a>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Master zero-downtime MySQL schema migrations at scale using pt-online-schema-change and gh-ost with production configurations, benchmarks, and cutover safety.<\/p>\n","protected":false},"author":1,"featured_media":4629,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[57,177,87,101],"class_list":["post-4630","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-hosting-news","tag-almalinux","tag-databases-performance","tag-devops","tag-sysadmin"],"_links":{"self":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4630","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/comments?post=4630"}],"version-history":[{"count":0,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4630\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media\/4629"}],"wp:attachment":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media?parent=4630"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/categories?post=4630"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/tags?post=4630"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}