技术细节

  • 近日,MySQL发布了8.4版本,这是一个新的稳定版。在MySQL版本规划中,在2026年8.0.x生命周期结束后,将成为下一个主流稳定版本。

    目前为止,看到该版本并没有特别大的改进。部分改变包括改进了直方图统计信息更新、并行复制、组复制(GR)等,完整的更新可以参考:Changes in MySQL 8.4.0 (2024-04-30, LTS Release)

    MySQL 8.4@OCI性能测试(vs MySQL 8.0)

    Oracle Cloud上也第一时间支持了该版本,于是也通过性能测试的方式,第一时间“尝鲜”了一下该版本。性能测试的趋势图如下:

    注意到,在该Sysbench测试模式下:

    • 当前MySQL 8.4在性能上相对于8.0版本,要低21%(以16并发为参考)
    • 并在超高并发时(并发高于192),性能出现了严重的退化

    作为一个稳定版本,期待官方尽快解决。

    (more…)
  • 在Oracle Cloud Infrastructure(简称OCI,也就是Oracle云)上购买MySQL实例,也会有第三代CPU和第四代CPU规格的选择,分别是:MySQL.VM.Standard.E4.2.32GBMySQL.VM.Standard.E3.2.32GB。本文对比两个版本规格的价格与性能,以供参考。

    结论概述

    E4(AMD EPYC 7J13)、E3(AMD EPYC 7742)同属于AMD系列的CPU,E4似乎主要是在OCI平台,E3较为通用。从性能测试上,可以看到,E4相比于E3有着较为明显的性能优势,以常见的16并发时数据为参考,则E4(MySQL.VM.Standard.E4.2.32GB)相比于E3(MySQL.VM.Standard.E3.2.32GB)性能要高11%。

    这也与之前的,“新一代CPU总是有着更高的性能”的结论一致。

    (more…)
  • 个人的一些脚本和代码,经常会分散在不同的地方,管理起来并不方便,例如给WordPress编写的Plugin、测试MySQL时使用的一些脚本等,所以打算全部使用GitHub管理起来。对于个人使用,GitHub提供了私人仓库以存储代码,可以较为方便的管理一些还没有公开的个人代码。

    建立个人Git和GitHub环境

    GitHub CLI是一个具体简单交互式操作的命令行,可以完成与GitHub相关的一些交互与操作。对应的软件包/命令是gh

    安装gh-cli

    参考:Installing gh on Linux and BSD。Amazon Linux 2上安装:

    sudo yum-config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
    sudo yum install gh

    使用gh配置GitHub授权

    接着,就可以使用gh auth login命令来进行GitHub的认证了(gh cli manual)。这是一个简单的交互式命令,这里使用https+token的方式完成认证(也可以使用浏览器的方式辅助完成命令行认证):

    gh auth login
    ? What account do you want to log into? GitHub.com
    ? What is your preferred protocol for Git operations on this host? HTTPS
    ? Authenticate Git with your GitHub credentials? Yes
    ? How would you like to authenticate GitHub CLI? Paste an authentication token
    Tip: you can generate a Personal Access Token here https://github.com/settings/tokens
    The minimum required scopes are 'repo', 'read:org', 'workflow'.
    ? Paste your authentication token: *********************************************************************************************
    - gh config set -h github.com git_protocol https
    ✓ Configured git protocol
    ! Authentication credentials saved in plain text
    ✓ Logged in as orczhou

    关于Token的配置与获取,可以参考:GitHub->Settings->Developer Settings ,这里不再详述。注意,Token意味着分配的所有的仓库权限,必须妥善保管,否则可能会带来巨大的安全隐患。

    如果要登出的话,则可以简单的使用如下命令:

    gh auth logout

    在本地pull与push仓库

    • 首先,在git中配置本地身份(用户名与)
    git config --global user.name "orczhou"
    git config --global user.email "orczhou@orczhou"
    • 首先,新建一个本地模板,并使用git命令初始化
    mkdir  terraform && cd terraform
    git init
    • 配置远端(remote)分支;并拉取远端代码
    git remote add origin https://github.com/orczhou/cloud-mysql-benchmark.git
    git pull origin main

    向远端push代码

    这时,如果修改了仓库中的代码,则可以使用push命令向远端发起提交请求。

    修改、测试并本地提交代码:

    vi gcp_rds_ins/all_in_one/README.md
    git add gcp_rds_ins/all_in_one/README.md
    git commit -m "gcp readme updated"

    向远端push修改:

    git push -u origin main

    该操作会向远端仓库的main分支,提交代码。

    向main分之合并代码

    可以在GitHub仓库页面,对比并创建一个pull request。

    发起pr之后,代码仓库则可以进行merge操作,将代码合并到main分之。

    在新增远程代码库(origin)

    git remote add origin https://github.com/orczhou/testing-delete-repo-if-u-like.git

    将本地代码,提交到远程代码库(origin)的main分支:

    git push -u origin main

    上面的,-u origin main ,其中-u参数表示push的目标代码库-u | --set-upstream

    在现有仓库的main分之上开发

    经常需要做这个动作,常用的命令比较简单,这里记录如下:

    mkdir repo_bench && cd repo_bench
    git init
    git branch -M main
    git add remote origin https://...
    git pull origin main
    
    

    直接修改本地main中的代码并提交到源端:

    cat "..." > README.md
    git add README.md
    git commit -m "first commit" --dry-run
    git commit -m "first commit"
    git push -u origin main

    使用gitignore忽略文件

    在代码开发过程中,由于编译、运行等原因会产生很多的中间文件,而这些中间文件时无需提交到代码仓库的。这时候,需要使用gitignore来忽略这部分文件。详细完整的gitignore的使用可以参考man gitignore

    常用的gitignore是可以通过.gitignore文件来实现,即在代码的根目录中新建该文件,那么在代码处理时,就会根据根据该文件的规则进行忽略。例如Terraform脚本常用的gitignore文件可以参考:

    所以,一个Terraform脚本的.gitignore可以依次作参考:

    # Compiled files
    *.tfstate
    *.tfstate.backup
    *.tfstate.lock.info
    
    # Directories
    .terraform/
    .vagrant/
    
    # SSH Keys
    *.pem
    
    # Ignored Terraform files
    *gitignore*.tf

    master分支与main分支

    在搜索git/github的相关资料的时候,经常还会搜索到master分支作为主分支的资料或者仓库。在2020年的George Floyd的案件发生后,美国的Black_Lives_Matter运动达到了前所未有的高度,最终也影响到在计算机领域的master/slave 一词的使用。更多的参考:Renaming the default branch from master@GitHubWhy GitHub renamed its master branch to main@theserverside

    不过,git在本地默认还是使用默认的master分支,所以如果没有手动切换分支,则还是会经常“默认的”创建master分支。

    查看未提交的修改

    git面向的场景就是分布式、多任务的开发代码管理,其独特的”three tree“模型可以很巧妙的实现这些能力。这也给初学者带来了很多理解上的障碍。

    git diffgit diff HEAD

    如果,想要查看自上次commit以来的所有变更,则需要试用git diff HEAD命令,通常HEAD指向的是,最后一次commit时的位置。

    git diff 工作区 vs 暂存区

    git diff –cached 暂存区 vs 版本库

    参考链接

  • MySQL在上个月正式EOL,意味着官方不会再发布新版本去修复Bug或者安全漏洞。一般来说,云厂商对老版本支持要更就一些,那么一起来看看各个云厂商对5.7的支持情况。概述:

    • AWS:首先,AWS RDS MySQL将提供额外的、免费的半年标准支持,期间提供bug修复、漏洞修复等;2024年2月后,AWS RDS将提供付费的扩展支持。届时,如果不选择付费扩展支持的实例,将会自动(可以理解为“强制”)被升级到8.0版本,“付费”的扩展支持应该持续3年。(注:Aurora的免费支持时间会更长一些,到2024年11月)
    • 阿里云:目前,对于5.7还没有看到正式的说明。但,对于5.6版本,阿里云数据库曾发通告,将提供额外的三年维护时间。不过鉴于5.7实例体量巨大(因为8.0版本拖的时间太长),对于5.7版本支持的时间可能会更长。
    • 其他云厂商:目前都比较模糊,可以理解为不会有任何动作,用户可以正常使用。

    AWS对MySQL 5.7的支持

    AWS有Aurora MySQL 5.7(即Aurora 2.x系列)、RDS MySQL 5.7两个版本。在Oracle MySQL官方到达EOL后,AWS额外提供了半年(Aurora约一年)的免费标准支持,以及三年的付费支持。即,AWS RDS将免费支持5.7到2024年02月底,Aurora到2024年11月底;另外,还将提供三年的付费支持,约到2027年。是的,要收费的(参考)。这段时间,AWS将继续提供安全相关的Bug修复、以及重要的功能Bug修复。

    这个收费策略应该是开了云厂商对于旧版本数据库支持的先河。维护老的版本,需要额外的技术人员投入,维护一个过期的版本的投入从长时间来看,是低效的,所以,用户是需要为这部分服务付费的。

    AWS 扩展支持的收费方式与费率

    收费方式是按照vCPU * HourACU * Hour计费,不同的区域会有不同,不同的时间也会不同,离官方EOL时间越长,费用会越贵。不过,在官方版本到达EOL之后,AWS预留了4个月~一年的免费时间,让用户安排升级。以RDS MySQL 5.7为例,自2024年2月底之后,以新加坡地区为例,前两年付费扩展支持价格为 $0.12/(vCPU * Hour),第三年的费用为$0.24/(vCPU * Hour)。以一个4c16g(db.m5.xlarge)的实例计算,一个月费扩展支持费用约为$345。而该实例本身的价格约为(两节点高可用):$677 = 0.47*24*30*2。所以,AWS的策略是,价格摆在这里了,升不升级,由用户自己定夺。

    (more…)
  • 这是一个系列文章,旨在了解如何使用Flex/Lex和Yacc/Bison进行词法和语法解析。这个系列,分成了几个部分,包括

    • flex的基本用法
    • 使用flex/bison实现一个简单的计算器
    • 实现一个带有条件判断与循环的计算程序

    了解这个系列需要一定的编译原理知识作为背景知识,了解程序如何从字符串先解析成Token,而后使用语法解析器生成解析树,最后执行该解析树。

    概述

    lex/flex可以按照“词法文件”的定义,将文本解析成单个的Token,然后通过执行“词法文件”中定义的Action来完成一些操作,一般,flex的输出会通过函数/变量将结果传递给yacc/bison进行进一步的语法解析。为了简化,本文将仅通过独立的“词法文件”完成一些操作,以了解flex的基础使用。

    这里完成的程序是一个简单的“count”程序,输入是一个文件,程序输出文件中包含的字符数、词语数、以及行数。

    安装flex并编写词法文件

    1. 安装lex: yum/apt-get install flex

    2. 编写如下词法文档:

    %{                                       //
            int characters = 0;              //    %{ ... }% 之间的部分是"Declarations"
            int words = 0;                   //    Declarations 部分声明的变量,是可以在全局使用的
            int lines = 0;                   //    例如,在该示例的main程序中,就通过extern声明的方式
    %}                                       //    使用了这些变量
    %%                                       //
    \n      {                                //    从这里开始是Translations阶段
                    ++lines;                 //    这里定了Token,以及遇到了Token之后
                    ++characters;            //    应该采取/执行什么,例如这里遇到了\n字符
            }                                //    则,将lines/characters变量都加1
    [ \t]+          characters += yyleng;    //
    [^ \t\n]+ {                              //    注释部分的文本需要删除,程序才能正常编译 
                    ++words;                 //    删除注释的vim命令:1,$s/\/\/.*$//g 
                    characters += yyleng;    //
            }                                //
                                             //
    %%

    直接使用如上代码的话,后面就会在gcc编译的时候遇到如下错误:

    $ lex zzx.l
    $ gcc lex.yy.c wc.c -o wc.out
    /tmp/cc1SPYm2.o:In function yylex':
    lex.yy.c:(.text+0x42f):undefined reference toyywrap'
    /tmp/cc1SPYm2.o:In function input':
    lex.yy.c:(.text+0xf73):undefined reference toyywrap'
    collect2: ld returned 1 exit status
    

    如果你也遇到了这个错误,不用担心,你并不孤单,在Stackoverflow上看到解决该失败的的答案一共有150点赞(up),就知道大家都一样了(参考@Stackoverflow)。因为默认的,lex生成的词法解析程序中,在最后是需要调用的yywrap函数的(关于yywrap),如果不打算提供该函数,则可以使用lex选项 %option noyywrap 禁用该调用。那么上面的代码就需要修改为:

    $ cat zzx.l 
    %{
            int characters = 0;
            int words = 0;
            int lines = 0;
    %}
    %option noyywrap
    %%
    \n      {
                    ++lines;
                    ++characters;
            }
    [ \t]+          characters += yyleng;
    [^ \t\n]+ {
                    ++words;
                    characters += yyleng;
            }
    
    %%

    编写入口函数并调用yylex

    词法文件需要使用工具flex将其编译生成一个c语言文件,然后再使用gcc将其编译成一个可执行文件。编译前,我们需要先编写一个简单的main函数. 再编写一个程序的入口函数(main),并调用yylex()就可以了。具体如下:

    $ cat wc.c
    #include <stdio.h>
    
    int yylex(void);
    
    int main(void)
    {
            extern int characters, words, lines;
    
            yylex();
            printf("%d characters, ", characters);
            printf("%d words, ", words);
            printf("%d lines\n", lines);
            return 0;
    }

    这里需要注意:在程序中,我们通过调用yylex()完成了实际的词法解析过程,并获得执行结果。这是一个非常简单的示例,实际过程比这要更加复杂,在词法文件中,每一次rule解析完成后,再起action部分,通常都会有return语句结束本次yylex调用,所以会是一个反复调用yylex的过程。

    编译并执行

    $ lex zzx.l    
    $ gcc lex.yy.c wc.c -o wc.out
    $ chmod +x wc.out
    $ cat s.txt
    this is a input file.
    this is a input file.
    $ ./wc.out < zzx.l
    404 characters, 36 words, 18 lines
    $ ./wc.out < s.txt
    44 characters, 10 words, 2 lines

    好了,至此,我们就完成一个词法解析的任务,因为这个任务不涉及任何语法(yyac)解析,所以比较适合初学者学习词法解析工具lex。

    补充关于Definitions

    为了再略微增强该示例的,这里对上面的示例又做了一个小调整,新增一行“Definitions”,有时候为了增强可读性,会对一些expression定义一个名称,如下,将\n定义为NL:

    %{                                        //
            int characters = 0;               //   %{ ... }% 之间的部分是"Declarations"
            int words = 0;                    //   Declarations 部分声明的变量,是可以在全局使用的
            int lines = 0;                    //   例如,在该示例的main程序中,就通过extern声明的方式
    %}                                        //   使用了这些变量
                                              //
    NL \n                                     //   这里新增了一行,这是一行 Definitions
                                              //   将\n用字母NL定义,所以下面的\n也就可以使用NL
                                              //   试想,如果表达式很复杂用这种方式,可读性会增强很多
    %%                                        //
    NL      {                                 //   从这里开始是Translations阶段
                    ++lines;                  //   这里定了Token,以及遇到了Token之后
                    ++characters;             //   应该采取/执行什么,例如这里遇到了\n字符
            }                                 //   则,将lines/characters变量都加1
    [ \t]+          characters += yyleng;     //
    [^ \t\n]+ {                               //   注释部分的文本需要删除,程序才能正常编译        
                    ++words;                  //   删除注释的vim命令:1,$s/\/\/.*$//g 
                    characters += yyleng;     //
            }                                 //
                                              //
    %%           

    自此,我们就了解一个词法解析文件的几个主要部分:Definitions、Declarations、rule(以及rule对应的Action)。

    参考资料:

    更多说明

    • Flex / Lex程序通常与Yacc/Bison一起使用,flex负责词法解析,bison则负责语法解析
    • flex与bison接口的函数,就是上面的调用 yylex()函数,该函数每次基于某个规则(rule)解析到一个新的Token的时候,则会执行对应的“Action”(也就是每个Token后面的代码)部分的代码。例如,上面的程序中会执行++lines++words代码。
    • 在rule action部分,我们看到使用了一个yyleng的“变量”用于获取当前被解析的原始字符串的长度。类似的“变量”有:yyleng、yytext、yyin等,完整的列表可以参考:Values Available To the User。另外,这些“变量”并不是真的变量,大部分都是一些“宏”,例如,yytext的真实定义可能是这样的:#define yytext (((struct yyguts_t*)yyscanner)->yytext_r)。了解这一点,有利于理解这些”变量”并不能在外部直接引用。
      • yyin yylex函数处理的字符串来源,默认情况是标准输入,在你的程序,例如可以定义为一个打开的文件描述符(在Posix中,一起都是文件)
      • yylength 用于记录,当前读取的Token的长度
      • yytext 用于记录当前读取的文本
    • 一般的,flex的“Action部分”,会包含一个return,例如如果遇到一个整数,可能会看到类似这样的代码:return INTEGER; 这时候,yylex()遇到一个对应的字符就会返回INTEGER
    • 在实践中,则是按照如下方式实现:
      • 在 yacc/bison的语法文件中定义Token,例如整数为 INTEGER,语法为 %token INTEGER
      • 使用yacc/bison命令生成对应的头文件,头文件则会包含关于 INTEGER的预定义:#define INTEGER 257
      • 只需要在flex词法文件中包含该头文件,就可以使用这里的预定义 INTEGER
      • 那么较为完整的代码看起来就是这样
    cat cal.y
    ...
    %token INTEGER
    ...
    cat cal.tab.h //这是bison生成的文件
    ...
    #define INTEGER 257
    ...
    cat cal.l
    ...
    [[:digit:]]+  { return INTEGER; }
    ...
    • 我们在考虑另一个问题:在lex的rule action可以使用本地的“变量”(其实是“宏”),也会通过return语句给yyparse()中调用yylex时,返回当前Token的类型。如果一个Token是一个[:digit:]+的时候,我们除了需要知道这个Token是一个整数之外,至少yyparse()还需要知道这个是一个什么整数,具体数值是多少,当然并不是所有的token都需要,一般identifier都是需要的。而,前面的yytext都是yylex本地的“变量”。这时候,通常会使用yylvalyylval是一个由yacc/bison定义的变量(默认是int类型),用于存储词法解析需要传递给yyparse的数据,在yacc/bison的语句处理的Action阶段,可以使用变量,以获得词法解析阶段的一些值。例如,一个Token是一个整数、字符串(并非keyword)的时候,我们会将对应的值存储在yylval中。所以,yylval通常会被定义为一个联合体(union类型),用于存储不同类型的值。

    关于这几个概念的更详细细致的解释可以参考最前面提到的“IBM的z/OS系统的文档中关于lex和yacc的介绍”(参考:Tutorial on using lex and yacc)。

  • 最近,Google Cloud SQL(Google云上的RDS)做了一次大的产品调整与发布:将原来的Cloud SQL分为了两个版本,分别为”Enterprise”和”Enterprise Plus”版本。本文概述了两个版本的异同,以帮助大家更好的了解Google Cloud SQL。

    关于”Enterprise”和”Enterprise Plus”版的Cloud SQL实例

    整体上:

    • Google Cloud SQL相当于AWS或阿里云平台的RDS,Cloud SQL for MySQL就相当于AWS RDS for MySQL
    • 这次调整之后,原来的Cloud SQL for MySQL/PostgreSQL全部变更为Enterprise版本,价格和产品本身没有什么改动,只是名称发生了变化
    • 新增的Enterprise Plus版本提供了更高的产品能力,例如通过硬件/软件优化提供的Data Cache能力,可以提升MySQL的读取和写入性能;Enterprise Plus还提供了更大的规格、更灵活的CPU/内存配比等能力
    • Enterprise Plus资源的价格比Enterprise版本贵约30%,Data Cache空间是额外计费的
    • 本质上,猜测,Enterprise Plus使用了更新更强的机型,具备非常强的本地存储能力(本地可能使用NVMe SSD或者其他存储设备),在数据库层则利用该能力使得用户获得更好的读与写的能力
    • 从产品发布节奏来看,今年GCP(Google云)增加了在Cloud SQL上的投入,这一点与阿里云现在应该是比较类似的。而在以前,在Google数据产品体系中,Spanner、AlloyDB或者BigTable才是投入最大的产品,而Cloud SQL似乎不受重视。现在大概发现,无论Spanner、AlloyDB架构多领先,最简单的Cloud SQL才一直都是更多用户的首选。
    • 这次更名调整,感觉是比较混乱的。对于Cloud SQL for MySQL来说,”Enterprise”和”Enterprise Plus”之间的区别还是比较明显,一个有Data Cache、另一个没有。但对于PostgreSQL来说,两者的区别更像是对资源多少的使用的一种简单限制(最大规格、cpu内存比、PITR的时间限制等)。另外,当前SQL Server又并不区分这两个类型。所以,整体给人感觉比较混乱。而像AWS Aurora新增一个I/O-Optimized选项的做法可能会更简单一点。GCP这种”化简为繁”做法,也许和业务的压力有一定的关系,更像是为了概念而做的产品决策,最终让用户对混乱的产品名称和定位买单,这种事情在大公司的企业级产品中,还是经常出现的。事情做不了什么突破,那么就在产品名称上做一些突破吧(这是一段不负责任的“臆断”,谨慎参考)。
    (more…)