[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6D1T42HE4XY1P8AT5FD20X5":3,"skill-same:01M6D1T42HE4XY1P8AT5FD20X5":86},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":40,"linkOnly":41,"markdownContent":42,"markdownBody":43,"frontMatter":44,"refName":45,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":50,"ownerHtmlUrl":51,"ownerBlogUrl":50,"ownerDescription":52,"updatedAt":53,"install":54,"resources":67},"01M6D1T42HE4XY1P8AT5FD20X5","database-operations",null,"Running a database: automated backups and tested restores, point-in-time recovery, replicas and failover, connection pooling, monitoring the signals that matter, routine maintenance, upgrades, security and least privilege, data retention and deletion, seeds and local development. Read before setting up, operating or moving a database.","enowdev\u002Fenowxcli","enowdev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F179420349?v=4","01M6D1MVX2KC0X3Z9NESE7P22K","Enow Dev.","01M6D1MW1FMZGBDKWAJY4GQX81",67,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fdatabase-operations\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],"database","operations","database operations","security","development","monitoring","data","recovery","read","upgrades","up","maintenance","time","local","signals","connection pooling","2026-11-10 10:12:30",false,"---\nname: database-operations\ndescription: \"Running a database: automated backups and tested restores, point-in-time recovery, replicas and failover, connection pooling, monitoring the signals that matter, routine maintenance, upgrades, security and least privilege, data retention and deletion, seeds and local development. Read before setting up, operating or moving a database.\"\n---\n\n# Running a database\n\nThe generated setup: a nightly `pg_dump` onto the same disk as the database,\nnever restored; the port open to the internet with the default superuser; a\npool of 50 connections in every serverless function; no alert until the\ndisk is full; a major upgrade tried first in production; personal data kept\nforever and copied to laptops for debugging. This skill is how a database is\nrun so its data survives mistakes, failures and audits. Engine details are\nin `database-postgres`, `database-mysql`, `database-sqlite` and\n`database-mongodb`.\n\n## 1. Decide the targets first\n\n- **RPO**: how much data may be lost. **RTO**: how long until it is back.\n  Agree them with the owner in writing (\"at most 5 minutes of data, back\n  within an hour\"); they choose the tools.\n\n| Target | Needs |\n|---|---|\n| RPO a day, RTO hours | Daily logical dumps, off-site, restored on a schedule |\n| RPO minutes | Point-in-time recovery: base backups plus continuous WAL or binlog archiving |\n| RTO minutes | A standby ready to promote (managed high availability), as well as backups |\n| Undoing a mistake (a bad `DELETE`) | PITR to just before it; replicas copy the mistake at once |\n\n## 2. Backups\n\n- Automated, encrypted, off-site (another account or provider, never the\n  same disk or cloud account as the database), with retention written down,\n  for example 7 to 14 daily, 4 to 8 weekly and 6 to 12 monthly, adjusted to\n  legal requirements.\n\n| Engine | Logical | Physical and point in time |\n|---|---|---|\n| PostgreSQL | `pg_dump -Fc`, `pg_dumpall --globals-only` for roles | pgBackRest, Barman or WAL-G, or the host's PITR |\n| MySQL | `mysqldump --single-transaction`, MySQL Shell `util.dumpInstance()` | Percona XtraBackup plus binary logs |\n| MariaDB | `mariadb-dump` | `mariadb-backup` plus binary logs |\n| SQLite | `VACUUM INTO`, `.backup` | Litestream |\n| MongoDB | `mongodump`, small databases only | Atlas backup, Percona Backup for MongoDB, snapshots |\n\n- Managed databases: switch PITR on and check the window (RDS keeps up to 35\n  days). Back up what a dump leaves out: roles and grants, the list of\n  extensions, the configuration, and the key that decrypts the backups,\n  stored apart from them.\n- A stream straight to object storage leaves no plain copy on disk:\n\n```bash\n#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail   # a failed pg_dump fails the job\nstamp=$(date -u +%Y%m%dT%H%M%SZ)\npg_dump -Fc -d \"$DATABASE_URL\" \\\n  | age -r \"$BACKUP_AGE_RECIPIENT\" \\\n  | aws s3 cp - \"s3:\u002F\u002F$BACKUP_BUCKET\u002Fpostgres\u002Fapp-$stamp.dump.age\"\n```\n\n- The job reports success or failure to monitoring; a backup that silently\n  stopped weeks ago is the usual discovery during an incident.\n\n## 3. Restores, tested\n\nA backup never restored is a hope. Restore on a schedule (monthly at least,\nand after any change to the backup setup) into a scratch instance,\nscripted:\n\n```bash\ncreatedb app_restore_test\nage -d -i \"$BACKUP_AGE_KEY_FILE\" app-20260929T020000Z.dump.age \\\n  | pg_restore --no-owner --no-privileges -d app_restore_test\npsql -d app_restore_test -c \"SELECT count(*), max(created_at) FROM orders;\"\n```\n\n- Check what matters: row counts of the key tables against production, the\n  newest timestamp (the RPO you really have), the app starting against it\n  with a smoke test, and the total time taken (the RTO you really have).\n  Record the results.\n- Rehearse point-in-time recovery to a chosen moment\n  (`recovery_target_time` in Postgres, `mysqlbinlog --stop-datetime` for\n  MySQL) before you need it.\n- A runbook: where the backups are, who holds the key, the commands, the\n  expected duration.\n\n## 4. Replicas and failover\n\n- High availability from the provider (RDS Multi-AZ, Cloud SQL HA, Atlas\n  replica sets) or Patroni for self-hosted Postgres; never hand-written\n  failover scripts.\n- Clients survive a failover: reconnect with backoff, retry idempotent work,\n  connect through the provider's endpoint (short DNS TTL) rather than an\n  instance address. libpq takes several hosts with\n  `target_session_attrs=read-write`.\n- Fail over on purpose (RDS reboot with failover) and time the app's\n  recovery.\n- Replicas lag: send them only reads that tolerate it, and keep reads that\n  decide writes on the primary (`database-transactions`). A replica is not a\n  backup; it copies a `DROP TABLE` within milliseconds.\n\n## 5. Connection pooling\n\n- Instances times pool size, plus workers and admin, stays under the\n  server's limit with headroom. Pools of 5 to 20 per instance, a connect\n  timeout of a few seconds, an idle timeout, and a maximum lifetime below the\n  server's or proxy's idle cut-off.\n- Serverless and edge: a pooler or an HTTP driver (PgBouncer or the\n  provider's pooler, Neon's serverless driver, Prisma Accelerate, Cloudflare\n  Hyperdrive, RDS Proxy, PlanetScale's serverless driver) and a pool of 1 per\n  function instance. Transaction-mode pitfalls are in `database-postgres`.\n\n## 6. Monitoring\n\nAlert on what users feel and on resources that end in an outage:\n\n| Signal | Postgres; MySQL | Alert when |\n|---|---|---|\n| Connections | `pg_stat_activity`; `Threads_connected` | Over 80% of the limit |\n| Slow queries | `pg_stat_statements`; slow log, `sys.statement_analysis` | Top total time or p95 jumps after a deploy |\n| Locks | `pg_blocking_pids()`, `deadlocks` in `pg_stat_database`; `sys.innodb_lock_waits` | Sustained waits, deadlock spikes |\n| Long transactions | `xact_start`; `information_schema.innodb_trx` | Open for more than a few minutes |\n| Replication lag | `replay_lag` in `pg_stat_replication`; `Seconds_Behind_Source` | Beyond what reads tolerate, for example 30s |\n| Disk and IOPS | The host's metrics | 80% full, or on course to fill within two weeks |\n| Cache hit ratio | `pg_statio_user_tables`; buffer pool reads | Below 0.99 for OLTP |\n| Vacuum, wraparound | `n_dead_tup`, `age(datfrozenxid)` | Autovacuum behind; age over 1 billion |\n| Backups | The backup job | Missed run, failed verification |\n\n```sql\n-- who blocks whom\nSELECT pid, pg_blocking_pids(pid) AS blocked_by, wait_event_type, left(query, 60) AS query\nFROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;\n-- cache hit ratio\nSELECT round(sum(heap_blks_hit) \u002F nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0), 4)\nFROM pg_statio_user_tables;\n-- largest tables, indexes included\nSELECT relname, pg_size_pretty(pg_total_relation_size(relid)) AS size\nFROM pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;\n```\n\nTools: the host's dashboards, postgres_exporter or mysqld_exporter with\nPrometheus and Grafana, pganalyze, Percona Monitoring and Management.\n\n## 7. Maintenance\n\n- Postgres: autovacuum tuned for big tables, `ANALYZE` after bulk loads,\n  `REINDEX INDEX CONCURRENTLY` and `pg_repack` for bloat. MySQL:\n  `ANALYZE TABLE` after big changes, `OPTIMIZE TABLE` rarely (it rebuilds).\n  SQLite: `PRAGMA optimize`, an occasional `VACUUM`.\n- Cleanup jobs for expired rows (sessions, tokens, soft-deleted records past\n  retention, old events): scheduled, idempotent, in batches. A single\n  `DELETE` of millions of rows holds locks, bloats the table and floods the\n  replicas; time-partitioned tables drop partitions instead.\n\n```sql\n-- repeat until it deletes 0 rows, pausing briefly between runs\nDELETE FROM sessions\nWHERE id IN (\n  SELECT id FROM sessions WHERE expires_at \u003C now() - interval '7 days'\n  ORDER BY id LIMIT 5000\n);\n```\n\n- Minor versions applied as released, after reading the notes (a few ask\n  for a reindex).\n\n## 8. Upgrades\n\n| Method | Downtime | Use |\n|---|---|---|\n| Dump and restore | Grows with size | Small databases, changing hosts |\n| `pg_upgrade --link` | Minutes | Same server; run with `--check` first |\n| Logical replication to the new major, then switch | Seconds | Large Postgres; RDS Blue\u002FGreen |\n| MySQL in-place | Minutes | 8.0 to 8.4, after MySQL Shell's `util.checkForServerUpgrade()` |\n| MongoDB rolling | Little or none | One major at a time, then `setFeatureCompatibilityVersion` |\n\n- Rehearse on a restored copy: the test suite and the slowest queries, since\n  plans can change between majors. Write the rollback before starting, and\n  keep a backup of the old version until the new has run cleanly for a\n  while.\n- After a Postgres upgrade, refresh statistics\n  (`vacuumdb --all --analyze-in-stages`; from 18 `pg_upgrade` keeps most, and\n  `vacuumdb --missing-stats-only` fills the rest) and update extensions\n  (`ALTER EXTENSION ... UPDATE`).\n\n## 9. Security\n\n- Private network only: no public address, or an allow-list of fixed ones if\n  it cannot be avoided. On a VPS, Docker's published ports bypass ufw: bind\n  to `127.0.0.1:5432:5432` or do not publish the port.\n- TLS for every connection leaving the machine, with the server verified\n  (`sslmode=verify-full` in Postgres, `--ssl-mode=VERIFY_IDENTITY` in\n  MySQL).\n- A role per purpose (migration owner, app with rows only, read-only for\n  reports and support, one per service); no shared admin password, no\n  superuser in any app (`database-postgres` has the grants).\n- Credentials from a secret manager or the platform's environment, rotated,\n  never in the repository, logs or error messages.\n- Encryption at rest (default on managed hosts; LUKS or encrypted cloud\n  disks when self-hosted) and backups encrypted with their own key.\n- Audit logs where required (pgaudit, or the host's): who read or changed\n  sensitive tables. OS and database patches kept current.\n\n## 10. Retention and deletion\n\n- A retention period per kind of data, written down and enforced by the\n  cleanup jobs: for example sessions 30 days, request logs 90 days, invoices\n  as long as tax law requires.\n- Deleting personal data on request (GDPR, Indonesia's UU PDP No. 27\u002F2022\n  and similar laws): find every table and service that holds it, delete or\n  anonymise, record that it was done. Backups age out on their own schedule;\n  the privacy policy says so, rather than anyone editing backups.\n- Staging and analytics get anonymised or synthetic data, never a raw\n  production copy: mask names, emails, phones, addresses and free text\n  (PostgreSQL Anonymizer, or a scripted transform during the restore),\n  keeping ids so relations still hold. Nothing from production on laptops.\n\n## 11. Seeds and local development\n\n- A seed script in the repository: deterministic (fixed ids, or a fixed\n  faker seed such as `faker.seed(42)`), idempotent (upserts, safe to run\n  twice), clearly fake (`Sample shop 1`, `owner@example.com`), covering the\n  states the UI shows (empty, one, many, each status), and refusing to run\n  when the URL points at production.\n- Locally: Docker Compose with a named volume, the production major\n  version, bound to localhost (the file is in `database-postgres`). Init\n  scripts in `\u002Fdocker-entrypoint-initdb.d` run only on an empty volume;\n  `docker compose down -v` starts over.\n- Every developer and CI run the same migrations from empty; nobody shares a\n  development database.\n\n## Check it\n\n- This month's restore test exists, with counts, the newest timestamp and\n  the time it took.\n- From outside the private network the port does not answer\n  (`nc -vz \u003Cpublic-ip> 5432` fails).\n- The app's role cannot create tables or read other schemas.\n- Alerts exist for disk, connections, replication lag and failed backups,\n  and one was fired in a test.\n- Cleanup jobs run and the tables they clean stay flat in size.\n- The report says plainly what could not be checked (no console access, no\n  restore performed).\n\n## Avoid\n\nBackups on the same disk or account as the database; backups never\nrestored; a replica called a backup; the port open to the internet or\npublished by Docker past the firewall; a superuser in the app; secrets in\nthe repository or logs; pools that add up past the connection limit; no\nalert until the disk is full; million-row `DELETE`s in one statement; a\nmajor upgrade first tried in production; personal data kept forever or\ncopied to laptops and staging; seeds that can run against production.\n","# Running a database\n\nThe generated setup: a nightly `pg_dump` onto the same disk as the database,\nnever restored; the port open to the internet with the default superuser; a\npool of 50 connections in every serverless function; no alert until the\ndisk is full; a major upgrade tried first in production; personal data kept\nforever and copied to laptops for debugging. This skill is how a database is\nrun so its data survives mistakes, failures and audits. Engine details are\nin `database-postgres`, `database-mysql`, `database-sqlite` and\n`database-mongodb`.\n\n## 1. Decide the targets first\n\n- **RPO**: how much data may be lost. **RTO**: how long until it is back.\n  Agree them with the owner in writing (\"at most 5 minutes of data, back\n  within an hour\"); they choose the tools.\n\n| Target | Needs |\n|---|---|\n| RPO a day, RTO hours | Daily logical dumps, off-site, restored on a schedule |\n| RPO minutes | Point-in-time recovery: base backups plus continuous WAL or binlog archiving |\n| RTO minutes | A standby ready to promote (managed high availability), as well as backups |\n| Undoing a mistake (a bad `DELETE`) | PITR to just before it; replicas copy the mistake at once |\n\n## 2. Backups\n\n- Automated, encrypted, off-site (another account or provider, never the\n  same disk or cloud account as the database), with retention written down,\n  for example 7 to 14 daily, 4 to 8 weekly and 6 to 12 monthly, adjusted to\n  legal requirements.\n\n| Engine | Logical | Physical and point in time |\n|---|---|---|\n| PostgreSQL | `pg_dump -Fc`, `pg_dumpall --globals-only` for roles | pgBackRest, Barman or WAL-G, or the host's PITR |\n| MySQL | `mysqldump --single-transaction`, MySQL Shell `util.dumpInstance()` | Percona XtraBackup plus binary logs |\n| MariaDB | `mariadb-dump` | `mariadb-backup` plus binary logs |\n| SQLite | `VACUUM INTO`, `.backup` | Litestream |\n| MongoDB | `mongodump`, small databases only | Atlas backup, Percona Backup for MongoDB, snapshots |\n\n- Managed databases: switch PITR on and check the window (RDS keeps up to 35\n  days). Back up what a dump leaves out: roles and grants, the list of\n  extensions, the configuration, and the key that decrypts the backups,\n  stored apart from them.\n- A stream straight to object storage leaves no plain copy on disk:\n\n```bash\n#!\u002Fusr\u002Fbin\u002Fenv bash\nset -euo pipefail   # a failed pg_dump fails the job\nstamp=$(date -u +%Y%m%dT%H%M%SZ)\npg_dump -Fc -d \"$DATABASE_URL\" \\\n  | age -r \"$BACKUP_AGE_RECIPIENT\" \\\n  | aws s3 cp - \"s3:\u002F\u002F$BACKUP_BUCKET\u002Fpostgres\u002Fapp-$stamp.dump.age\"\n```\n\n- The job reports success or failure to monitoring; a backup that silently\n  stopped weeks ago is the usual discovery during an incident.\n\n## 3. Restores, tested\n\nA backup never restored is a hope. Restore on a schedule (monthly at least,\nand after any change to the backup setup) into a scratch instance,\nscripted:\n\n```bash\ncreatedb app_restore_test\nage -d -i \"$BACKUP_AGE_KEY_FILE\" app-20260929T020000Z.dump.age \\\n  | pg_restore --no-owner --no-privileges -d app_restore_test\npsql -d app_restore_test -c \"SELECT count(*), max(created_at) FROM orders;\"\n```\n\n- Check what matters: row counts of the key tables against production, the\n  newest timestamp (the RPO you really have), the app starting against it\n  with a smoke test, and the total time taken (the RTO you really have).\n  Record the results.\n- Rehearse point-in-time recovery to a chosen moment\n  (`recovery_target_time` in Postgres, `mysqlbinlog --stop-datetime` for\n  MySQL) before you need it.\n- A runbook: where the backups are, who holds the key, the commands, the\n  expected duration.\n\n## 4. Replicas and failover\n\n- High availability from the provider (RDS Multi-AZ, Cloud SQL HA, Atlas\n  replica sets) or Patroni for self-hosted Postgres; never hand-written\n  failover scripts.\n- Clients survive a failover: reconnect with backoff, retry idempotent work,\n  connect through the provider's endpoint (short DNS TTL) rather than an\n  instance address. libpq takes several hosts with\n  `target_session_attrs=read-write`.\n- Fail over on purpose (RDS reboot with failover) and time the app's\n  recovery.\n- Replicas lag: send them only reads that tolerate it, and keep reads that\n  decide writes on the primary (`database-transactions`). A replica is not a\n  backup; it copies a `DROP TABLE` within milliseconds.\n\n## 5. Connection pooling\n\n- Instances times pool size, plus workers and admin, stays under the\n  server's limit with headroom. Pools of 5 to 20 per instance, a connect\n  timeout of a few seconds, an idle timeout, and a maximum lifetime below the\n  server's or proxy's idle cut-off.\n- Serverless and edge: a pooler or an HTTP driver (PgBouncer or the\n  provider's pooler, Neon's serverless driver, Prisma Accelerate, Cloudflare\n  Hyperdrive, RDS Proxy, PlanetScale's serverless driver) and a pool of 1 per\n  function instance. Transaction-mode pitfalls are in `database-postgres`.\n\n## 6. Monitoring\n\nAlert on what users feel and on resources that end in an outage:\n\n| Signal | Postgres; MySQL | Alert when |\n|---|---|---|\n| Connections | `pg_stat_activity`; `Threads_connected` | Over 80% of the limit |\n| Slow queries | `pg_stat_statements`; slow log, `sys.statement_analysis` | Top total time or p95 jumps after a deploy |\n| Locks | `pg_blocking_pids()`, `deadlocks` in `pg_stat_database`; `sys.innodb_lock_waits` | Sustained waits, deadlock spikes |\n| Long transactions | `xact_start`; `information_schema.innodb_trx` | Open for more than a few minutes |\n| Replication lag | `replay_lag` in `pg_stat_replication`; `Seconds_Behind_Source` | Beyond what reads tolerate, for example 30s |\n| Disk and IOPS | The host's metrics | 80% full, or on course to fill within two weeks |\n| Cache hit ratio | `pg_statio_user_tables`; buffer pool reads | Below 0.99 for OLTP |\n| Vacuum, wraparound | `n_dead_tup`, `age(datfrozenxid)` | Autovacuum behind; age over 1 billion |\n| Backups | The backup job | Missed run, failed verification |\n\n```sql\n-- who blocks whom\nSELECT pid, pg_blocking_pids(pid) AS blocked_by, wait_event_type, left(query, 60) AS query\nFROM pg_stat_activity WHERE cardinality(pg_blocking_pids(pid)) > 0;\n-- cache hit ratio\nSELECT round(sum(heap_blks_hit) \u002F nullif(sum(heap_blks_hit) + sum(heap_blks_read), 0), 4)\nFROM pg_statio_user_tables;\n-- largest tables, indexes included\nSELECT relname, pg_size_pretty(pg_total_relation_size(relid)) AS size\nFROM pg_statio_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 10;\n```\n\nTools: the host's dashboards, postgres_exporter or mysqld_exporter with\nPrometheus and Grafana, pganalyze, Percona Monitoring and Management.\n\n## 7. Maintenance\n\n- Postgres: autovacuum tuned for big tables, `ANALYZE` after bulk loads,\n  `REINDEX INDEX CONCURRENTLY` and `pg_repack` for bloat. MySQL:\n  `ANALYZE TABLE` after big changes, `OPTIMIZE TABLE` rarely (it rebuilds).\n  SQLite: `PRAGMA optimize`, an occasional `VACUUM`.\n- Cleanup jobs for expired rows (sessions, tokens, soft-deleted records past\n  retention, old events): scheduled, idempotent, in batches. A single\n  `DELETE` of millions of rows holds locks, bloats the table and floods the\n  replicas; time-partitioned tables drop partitions instead.\n\n```sql\n-- repeat until it deletes 0 rows, pausing briefly between runs\nDELETE FROM sessions\nWHERE id IN (\n  SELECT id FROM sessions WHERE expires_at \u003C now() - interval '7 days'\n  ORDER BY id LIMIT 5000\n);\n```\n\n- Minor versions applied as released, after reading the notes (a few ask\n  for a reindex).\n\n## 8. Upgrades\n\n| Method | Downtime | Use |\n|---|---|---|\n| Dump and restore | Grows with size | Small databases, changing hosts |\n| `pg_upgrade --link` | Minutes | Same server; run with `--check` first |\n| Logical replication to the new major, then switch | Seconds | Large Postgres; RDS Blue\u002FGreen |\n| MySQL in-place | Minutes | 8.0 to 8.4, after MySQL Shell's `util.checkForServerUpgrade()` |\n| MongoDB rolling | Little or none | One major at a time, then `setFeatureCompatibilityVersion` |\n\n- Rehearse on a restored copy: the test suite and the slowest queries, since\n  plans can change between majors. Write the rollback before starting, and\n  keep a backup of the old version until the new has run cleanly for a\n  while.\n- After a Postgres upgrade, refresh statistics\n  (`vacuumdb --all --analyze-in-stages`; from 18 `pg_upgrade` keeps most, and\n  `vacuumdb --missing-stats-only` fills the rest) and update extensions\n  (`ALTER EXTENSION ... UPDATE`).\n\n## 9. Security\n\n- Private network only: no public address, or an allow-list of fixed ones if\n  it cannot be avoided. On a VPS, Docker's published ports bypass ufw: bind\n  to `127.0.0.1:5432:5432` or do not publish the port.\n- TLS for every connection leaving the machine, with the server verified\n  (`sslmode=verify-full` in Postgres, `--ssl-mode=VERIFY_IDENTITY` in\n  MySQL).\n- A role per purpose (migration owner, app with rows only, read-only for\n  reports and support, one per service); no shared admin password, no\n  superuser in any app (`database-postgres` has the grants).\n- Credentials from a secret manager or the platform's environment, rotated,\n  never in the repository, logs or error messages.\n- Encryption at rest (default on managed hosts; LUKS or encrypted cloud\n  disks when self-hosted) and backups encrypted with their own key.\n- Audit logs where required (pgaudit, or the host's): who read or changed\n  sensitive tables. OS and database patches kept current.\n\n## 10. Retention and deletion\n\n- A retention period per kind of data, written down and enforced by the\n  cleanup jobs: for example sessions 30 days, request logs 90 days, invoices\n  as long as tax law requires.\n- Deleting personal data on request (GDPR, Indonesia's UU PDP No. 27\u002F2022\n  and similar laws): find every table and service that holds it, delete or\n  anonymise, record that it was done. Backups age out on their own schedule;\n  the privacy policy says so, rather than anyone editing backups.\n- Staging and analytics get anonymised or synthetic data, never a raw\n  production copy: mask names, emails, phones, addresses and free text\n  (PostgreSQL Anonymizer, or a scripted transform during the restore),\n  keeping ids so relations still hold. Nothing from production on laptops.\n\n## 11. Seeds and local development\n\n- A seed script in the repository: deterministic (fixed ids, or a fixed\n  faker seed such as `faker.seed(42)`), idempotent (upserts, safe to run\n  twice), clearly fake (`Sample shop 1`, `owner@example.com`), covering the\n  states the UI shows (empty, one, many, each status), and refusing to run\n  when the URL points at production.\n- Locally: Docker Compose with a named volume, the production major\n  version, bound to localhost (the file is in `database-postgres`). Init\n  scripts in `\u002Fdocker-entrypoint-initdb.d` run only on an empty volume;\n  `docker compose down -v` starts over.\n- Every developer and CI run the same migrations from empty; nobody shares a\n  development database.\n\n## Check it\n\n- This month's restore test exists, with counts, the newest timestamp and\n  the time it took.\n- From outside the private network the port does not answer\n  (`nc -vz \u003Cpublic-ip> 5432` fails).\n- The app's role cannot create tables or read other schemas.\n- Alerts exist for disk, connections, replication lag and failed backups,\n  and one was fired in a test.\n- Cleanup jobs run and the tables they clean stay flat in size.\n- The report says plainly what could not be checked (no console access, no\n  restore performed).\n\n## Avoid\n\nBackups on the same disk or account as the database; backups never\nrestored; a replica called a backup; the port open to the internet or\npublished by Docker past the firewall; a superuser in the app; secrets in\nthe repository or logs; pools that add up past the connection limit; no\nalert until the disk is full; million-row `DELETE`s in one statement; a\nmajor upgrade first tried in production; personal data kept forever or\ncopied to laptops and staging; seeds that can run against production.\n",{"name":5,"description":7},"main","38293fe1b096e6bf3646c7344c38786ed688f464","crates\u002Fenowx-core\u002Fskills\u002Fdatabase-operations",21,"https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli","https:\u002F\u002Fenowx.ai","https:\u002F\u002Fgithub.com\u002Fenowdev","Pretending to be a Developer","2026-10-06 02:05:35",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":66},[56,59,62],{"tool":57,"command":58},"npx","npx skills add enowdev\u002Fenowxcli --skill database-operations",{"tool":60,"command":61},"bunx","bunx skills add enowdev\u002Fenowxcli --skill database-operations",{"tool":63,"command":64},"pnpm","pnpm dlx skills add enowdev\u002Fenowxcli --skill database-operations","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fdatabase-operations\u002FSKILL.md",true,[68,74,78,82],{"path":69,"resourceType":70,"resourceTypeDesc":71,"sizeBytes":72,"sourceHtmlUrl":73},"composition.md","REFERENCE","参考文档",2227,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcursor\u002Favalonia-viewmodels-zafiro\u002Fcomposition.md",{"path":75,"resourceType":70,"resourceTypeDesc":71,"sizeBytes":76,"sourceHtmlUrl":77},"navigation_sections.md",1299,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcursor\u002Favalonia-viewmodels-zafiro\u002Fnavigation_sections.md",{"path":79,"resourceType":70,"resourceTypeDesc":71,"sizeBytes":80,"sourceHtmlUrl":81},"viewmodels.md",1765,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcursor\u002Favalonia-viewmodels-zafiro\u002Fviewmodels.md",{"path":83,"resourceType":70,"resourceTypeDesc":71,"sizeBytes":84,"sourceHtmlUrl":85},"wizards.md",1563,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcursor\u002Favalonia-viewmodels-zafiro\u002Fwizards.md",[87,101,126,142,169,191],{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":96,"publishedAt":40},"01M6D2HQYSQD9N7MTNDT4A7CBM","demo-skill","Project-scope demo skill for tests.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fworkspace\u002F.agents\u002Fskills\u002Fdemo-skill\u002FSKILL.md",[93],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[97,98,99,100,32],"demo skill","demo","tests","project",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":115,"publishedAt":40},"01M6D2HM0GCPM0TM2HPG9HHR2R","writing","Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fwriting\u002FSKILL.md",[107,110],{"categoryNo":108,"categoryName":109,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":111,"categoryName":112,"taxonomyType":113,"taxonomyTypeDesc":114},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[103,116,117,118,119,120,32,121,122,123,124,125],"text","emails","empty","drop","document","page","voice","notifications","documentation","translate",{"skillNo":127,"name":128,"nameZh":6,"description":129,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":130,"categories":131,"keywords":135,"publishedAt":40},"01M6D2HG07YS49NM196Q0R3AZ8","ui","Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui\u002FSKILL.md",[132],{"categoryNo":133,"categoryName":134,"taxonomyType":113,"taxonomyTypeDesc":114},"01JBSEEDCAT000000000000032","设计师",[128,136,137,138,139,121,140,32,141],"icons","components","SET","responsive","layout","before building",{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":146,"categories":147,"keywords":154,"publishedAt":40},"01M6D2HB59HZHWG1RYZDV4SJ91","ui-themes","Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-themes\u002FSKILL.md",[148,151],{"categoryNo":149,"categoryName":150,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":152,"categoryName":153,"taxonomyType":113,"taxonomyTypeDesc":114},"01JBSEEDCAT000000000000030","开发工程师",[128,155,156,157,158,159,160,161,162,32,141,163,121,164,165,166,136,167,168],"ui themes","switching","photos","tokens","drawing","focus","maps","Art","flash","code","Charts","content","screenshots","logo",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":173,"categories":174,"keywords":178,"publishedAt":40},"01M6D2H5R5KRAE524C8QX0T7Q0","ui-structure","Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-structure\u002FSKILL.md",[175,176,177],{"categoryNo":108,"categoryName":109,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":152,"categoryName":153,"taxonomyType":113,"taxonomyTypeDesc":114},{"categoryNo":111,"categoryName":112,"taxonomyType":113,"taxonomyTypeDesc":114},[179,128,180,137,181,182,164,183,184,185,140,158,186,187,188,32,141,121,189,190],"structure","ui structure","pricing","dashboard","Blueprint","article","anatomy","styling","primitives","list","why","how",{"skillNo":192,"name":193,"nameZh":6,"description":194,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":195,"categories":196,"keywords":204,"publishedAt":40},"01M6D2H0ZZBJG77MBGPZHA6MWD","ui-stack-vue","Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue\u002FSKILL.md",[197,200,203],{"categoryNo":198,"categoryName":199,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":201,"categoryName":202,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":133,"categoryName":134,"taxonomyType":113,"taxonomyTypeDesc":114},[128,205,206,207,208,209,210,211,140,100,212,213,32,214,215,216,217,218,219,220],"vue","stack","ui stack vue","forms","pages","watch","runtime","transitions","fetch","pinia","nuxt","template","server","config","directory","testing"]