分类: database

  • 记一次 TiDB Cloud Serverless 超额导致的博客超时故障

    记一次 TiDB Cloud Serverless 超额导致的博客超时故障

    今天早上起来,习惯性地刷新博客统计页面,发现 502。这可不妙,好不容易我坚持到现在终于有点流量,于是赶紧想办法修复。

    博客基础架构

    我的博客架构大体是:

    • 小主机,本地跑 php8.2-fpm 和 nginx
    • 使用 nginx 代理提供服务
    • 数据库使用 TiDB Cloud Serverless
    • 外面套上腾讯云 CDN

    仔细一看,错误页面的 Nginx 是 1.18.0,正是我服务器上的版本,貌似是本地上游 php-fpm 的问题,不是腾讯云 CDN 的问题。ssh 登录服务器,基本正常,验证了判断。查看错误日志,tail /var/log/nginx/blog-error.log,大量的 upstream 错误。top 查看进程负载,服务器本身很闲,但是 5 个 php-fpm 进程虽然不忙,但是都跑了很久。

    重启治百病,先升级系统

    以前没见过这种情况,本着遇事不决先重启的思路,我想先升级重启试试。于是 apt update & apt upgrade 升级系统,接着 reboot。停了一会儿打开网页还是不行。

    登录服务器 service --status-all 查看服务,貌似都正常。查看博客服务器的配置文件,也没写错。输入 service nginx restartservice php8.2-fpm restart 重启服务,还是不行。

    然后 Google PHP8.2、php-fpm、502 等关键词,限制最近一周,看看会不会是新版本引入了新 bug。没有找到结果,应该也不是。

    会不会是 TiDB Cloud 没钱了?

    看过前面博客:💪 WordPress 使用 TiDB Cloud 替换 MySQL 💪 的同学可能知道,我不久前把博客从本地 MySQL 迁移到了 TiDB Cloud Serverless。会不会是欠费停机了?我觉得不应该呀,免费额度 5GB,我的博客数据库前几天看过才区区 300+MB,不会突然就突破限制。

    不过我还是打开了 TiDB Cloud,姑且看一眼吧。没成想,果然是 TiDB Cloud 额度用完了,不过不是容量问题,而是 RU(Request Units,请求单位,TiDB 的某个计费单位)超限。TiDB Serverless Tier 每个月有 50M 的免费 RU 额度,我当时已经用掉 59.6M,所以被停止服务了。

    不过这里有个问题:我的 php-fpm 整个被卡死了,因为 TiDB Cloud 没有及时断开连接,导致我的 PHP 一直在等待数据库响应,但实际上数据库是不会响应的,相当于我的 PHP 线程都在守活寡。直到超时,才得到解脱。这样一来,我的服务器也无法响应任何其它 PHP 请求,包括 phpinfo() 等未使用 TiDB Cloud,甚至那些只有简单 PHP 功能的请求。

    按理说我超额,他们应该拒绝连接,让我看到数据库连接失败的错误,而不是卡住我直到超时。这样一来,

    1. 至少我的服务器还可以响应别的 PHP 请求
    2. 有了数据库连接失败的错误提示,我也能尽快找到问题根源。

    上调预算

    TiDB 还是蛮大方的,不需要预付款、预充值,只要将来按用量结账即可。于是我把预算上调到 $5/月,然后,重启了服务器。结果,又发生了第二个意外。

    我以为上调预算之后,连接、请求就能恢复,结果没有,还是白屏。而且,从 TiDB Cloud 的统计页面来看,在上调预算之后,数据库经历了一大波密集的请求:

    直到约 30 分钟之后,我的博客才渐渐缓过来,我也才有机会写这篇复盘文章。

    我不太确定这波请求是哪里产生的,是我的博客还是 TiDB 的网关,这个有待进一步研究学习。

    请教 TiDB 大佬

    我带着问题在推上请教了大 V 能哥。他告诉我,按照目前的产品策略,超额用户也可以以很低的频率访问数据库,因为有人是占了太多空间,需要有机会让他删数据。

    那这就是彼之蜜糖,吾之砒霜了。我的情况是存储量少(不到400mB),但是请求数高(我看的时候为 60M),所以除非调高预算,我不存在继续使用的可能性。但是他们又不会拒绝我的服务,于是就变成了我服务器上的 php-fpm 被卡死在连接数据库阶段,无法响应任何 PHP 请求。

    我把这个问题反馈给了 TiDB 的社区工作人员,期待他们能解决这个问题。

    下一步:解决方案

    这次故障的修复过程对我来说还是挺新鲜的,也还算顺利。给我在系统设计领域提供了新的经验,所以我拿出来分享给大家。

    不过话说回来,从白嫖的角度来看,迁移到 TiDB Cloud Serverless Tier 上似乎不是很明智。按照 WordPress Jetpack 的统计,我现在每月访问量大约是 7k UV,10k PV,如果这个级别就要花钱,而且动辄几刀的话,那我可能还是迁回去用本地数据库好一些。

    当然,解决方案还是有的。

    首先,Serverless Tier 可以创建多个节点,每个账号的最初的 5 个节点都有 5GB 空间+50M RUs/M 的免费用量。所以我可以写一个自动化脚本,每周将数据同步到下一个节点,然后自动修改链接类到新的节点。嘿嘿嘿,不知道这样的作品能不能参加 Hackathon 😂😂

    其次,好好查查 slow sql,找找优化点。减少每次请求产生的 RU 消耗。

    再次,升级 CDN 配置。我目前的网页都是 30d 缓存,按理说不应该有很多消耗才对。后台只有我一个人在用,不应该有很多数据库访问。我怀疑还是这块儿没搞好。应该有不小的优化空间。

    总结

    TiDB 正在举办新一年的 Hackathon,我也厚着脸皮去要了 $100 的赞助,白嫖大业应该还能再坚持一段时间。如果你也对数据库应用开发感兴趣,我强烈推荐你也来参加这次 Hackathon:【TiDB Future App Hackathon 2023 】TiDB 首届全球黑客马拉松,开发者的狂欢夏日盛会!快来一起 Coding 吧!这次 Hackathon 针对应用层,基本上使用 TiDB Cloud Serverless 就可以,适合各个领域的开发者参加。

    无论如何,感谢 TiDB 提供给我们免费、好用的数据库产品,也推荐大家使用 TiDB Cloud Serverless。如果大家有什么意见建议,欢迎留言讨论。

  • 💪 WordPress 使用 TiDB Cloud 替换 MySQL 💪

    💪 WordPress 使用 TiDB Cloud 替换 MySQL 💪

    白嫖使我快乐。一直白嫖,一直快乐,😊。感谢 TiDB,感谢 TiDB Cloud,你们让我的博客内容更丰富多彩。

    前言

    我这个博客从 2011 年开始写,如今已经 12 年。最早,从 ZOL 离职后,我需要换个新平台写博客;另一方面,我也想学习 Linux、PHP、MySQL,这些原本不熟悉的技术,于是选择了 WordPress。这么多年来,服务器从共享主机搬迁到 VPS,又升级到云主机;PHP 从 5.5、5.6 升级到 7,又升级到 8;Apache 被换成 Nginx;唯独数据库没有变化,基本一套老框架沿用至今。

    我前阵子发现:因为编码问题,无法在标题里或正文里插入表情符号。于是升级数据库也被提上日程。刚好我一直关注的 TiDB 开始提供云数据库服务,采用 Serverless 模式,Free Tier 有 5GB 可以用,足够我写博客。于是我就准备趁此机会切换到 TiDB Cloud 上。

    注册 TiDB Cloud

    打开 tidbcloud.com 注册即可。

    TiDB Cloud 很大方,不需要绑卡,不需要繁琐的操作,直接第三方登录就可以使用。Serverless(Free Tier)只支持 AWS 机房,可选的位置也不多,因为我 ECS 买在美西,所以就把数据库也买在美西,这样速度应该会快一些。

    每个账号可以创建若五个 Serverless 节点,有免费额度,超过限制用量则开始收费。只要稍稍点两下,就建好了,体验很流畅,这里不再赘述。接下来开始使用。

    连接 TiDB Cloud

    数据库准备就绪之后,我们可以进入数据库详情页。点击右上角的“Connect”按钮,即可打开连接信息窗口。

    TiDB 贴心的准备了各种客户端、各种平台的连接方式,对于我这种数据库准小白来说非常有帮助。第一次使用,需要点击右下角的“Reset password”按钮生成数据库密码,生成后,这个按钮会变成复制密码。记得要妥善保存密码,因为我们不能再次查看或者获取。

    这里发现一个设计缺陷:点击 Reset password 按钮没有确认过程,所以我的数据库密码直接就被改掉了……这种破坏性操作还是应该多一个确认比较好,回头反馈给 TiDB 的工作人员。

    导入数据

    TiDB Cloud 只支持导入 CSV 文件,比较难用。可能因为我数据库知识储备不足,我甚至想象不出 CSV 该怎么支持表结构😅。不过没关系,我有 JetBrains 全家桶,命令行操作也还凑合,所以直接从本地跑就行。

    首先在服务器上执行 mysqldump -u USER -p --database BLOG_DB > backup.sql 把整个博客数据库包含数据结构都导出到 backup.sql 文件里。

    打开 sql 文件,把数据库的编码全部改成 utf8mb4 或者 utf8mb4_unicode_ci,这样就可以支持表情符号咯 🎉🎉。

    打开 DataGrip,按照上一节介绍建立数据库连接,右键,选择“SQL Scripts”,然后执行刚才的 SQL,等一会儿,数据即可完成导入。当然,实际过程肯定没有这么顺利,不过云数据库嘛,有问题就直接整个节点干掉再重建就好。不留脏数据。

    WordPress 连接

    TiDB Cloud 要求必须使用 TLS 安全连接,所以我们需要修改 wp-config.php

    /** WordPress数据库的名称 */
    define('DB_NAME', 'blog');
    
    /** MySQL数据库用户名 */
    define('DB_USER', '用户名');
    
    /** MySQL数据库密码 */
    define('DB_PASSWORD', '密码');
    
    /** MySQL主机 */
    define('DB_HOST', '主机域名:端口');
    
    /** 使用 SSL 连接 */
    define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL);
    

    因为我的服务器是 Ubuntu 22.04 系统,证书放在默认位置,可以自动加载。如果你使用其它系统,可能需要修改一下证书路径。

    解决 SQL_CALC_FOUND_ROWS 导致翻页丢失的问题

    完成上述操作,打开博客,500 😱。查看 error.log,原来是不支持 SQL_CALC_FOUND_ROWS 导致报错。我并不知道这个东西是干嘛用的,丢到 Google 里搜索,找到这个 issue,原来 WordPress 的 SQL 要使用这个函数,但是 TiDB 并不支持。

    解决方案是在数据库里执行 SET GLOBAL tidb_enable_noop_functions=1。之后 WordPress 不会再报错,但是相应的,翻页功能也没有了,因为 WordPress 无法统计博文数量。

    继续搜索。看起来 SQL_CALC_FOUND_ROWS 并不是什么好东西,不知道为什么 WordPress 至今都不愿意把它移除。还好,WP 留有开关,我们可以关闭这个函数的使用。我自己创建了一个 WP 小插件,用来给博客加广告、调整页面,所以我就在里面添加函数,关掉 SQL_CALC_FOUND_ROWS

    if ( ! function_exists( 'wpartisan_set_no_found_rows' ) ) :
      function wpartisan_set_no_found_rows( \WP_Query $wp_query ) {
        $wp_query->set( 'no_found_rows', true );
      }
    endif;
    add_filter( 'pre_get_posts', 'wpartisan_set_no_found_rows', 10, 1 );
    
    if ( ! function_exists( 'wpartisan_set_found_posts' ) ) :
      function wpartisan_set_found_posts( $clauses, \WP_Query $wp_query ) {
        // Don't proceed if it's a singular page.
        if ( $wp_query->is_singular()  ) {
          return $clauses;
        }
    
        global $wpdb;
    
        // Check if they're set.
        $where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
        $join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
        $distinct = isset( $clauses[ 'distinct' ] ) ? $clauses[ 'distinct' ] : '';
    
        // Construct and run the query. Set the result as the 'found_posts'
        // param on the main query we want to run.
        $wp_query->found_posts = $wpdb->get_var( "SELECT $distinct COUNT(*) FROM {$wpdb->posts} $join WHERE 1=1 $where" );
    
        // Work out how many posts per page there should be.
        $posts_per_page = ( ! empty( $wp_query->query_vars['posts_per_page'] ) ? absint( $wp_query->query_vars['posts_per_page'] ) : absint( get_option( 'posts_per_page' ) ) );
    
        // Set the max_num_pages.
        $wp_query->max_num_pages = ceil( $wp_query->found_posts / $posts_per_page );
    
        // Return the $clauses so the main query can run.
        return $clauses;
      }
    endif;
    add_filter( 'posts_clauses', 'wpartisan_set_found_posts', 10, 2 );

    调整完毕,翻页功能恢复正常。

    使用体验小结

    Serverless 讲究一个即用即起,平时是休眠状态,有人用才会启动。所以最好避免冷启动,否则第一个用的人要等很长时间,体验很差。作为博客,更简单的方式是配置 CDN,给首页外的页面添加长期缓存,就能改善大部分用户的体验。

    实际上我目前使用了一个多月,并没有冷启动的感觉。我的博客日均几百的访问量,感觉相当可以。另外一个实例,作为 NoCoDB 的数据库,因为访问量很小,所以冷启动感觉很明显。

    其它方面,速度和效率都不比本地数据库差,以后我再搞产品,应该都不会自己数据库了,嘿嘿嘿。


    后记

    我已经两次报名参加 TiDB Hackathon,第一次止步外围筛选第二次虽然进入决赛圈,但是因为对云数据库不了解,挑战开发 NoCoDB+TiDB Cloud 失败,铩羽而归。

    但是借用群里同学的说法:人生没有白走的路,每一步,都算数。虽然 Hackathon 没能获得好成绩,但是我获得了更多关于 TiDB Cloud 的知识,于是这次可以顺利完成迁移。

    目前博客网站使用体验良好,速度不比之前使用本地数据库慢。前两天,我发现 TiDB Cloud 开放了 Data Service,提供 HTTP Endpoint,这表示着他们向 DaaS(Database as a Service)更近一步,也意味着我们在 Edge Function 里使用 TiDB Cloud 成为可能。那么接下来,我打算好好利用一下这个薅羊毛机会,把几个 Side Project 的数据端放在 TiDB Cloud 上,跟 Supabase 比一比,看哪个更好用,更适合新项目、小公司从零到一。

    到时候也会写成更多文章,或许做成视频,跟大家分享。

    如果各位读者老爷对数据库、云开发、薅羊毛感兴趣,欢迎留言讨论,可提出问题,亦可指点一二,均非常欢迎。