LoginSignup
0
0

More than 1 year has passed since last update.

k6やってみた(WordPressはどれだけの負荷に耐えられるのか?!)

Posted at

テスト対象

  • Raspberry Pi4上に構築したWordPress
  • サンプルで作成されるHello Worldのページを使用

192.168.1.113_(1920x1080).png

※環境構築手順は末尾です。

テスト実行

1. とりあえず実行

JavaScriptでテスト用のコードを書いて実行します。

script_v1.js
import http from 'k6/http';
import { check, sleep, } from 'k6';

export default function () {
    const BASE_URL = 'http://192.168.1.113/'

    const res = http.get(BASE_URL, { tags: { name: 'base' } })

    check(res, {
        'status was 200': (r) => {
            return r.status == 200
        }
    })

    const responses = http.batch([
        ['GET', `${BASE_URL}/wp-includes/blocks/navigation/style.min.css?ver=6.0`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/js/wp-emoji-release.min.js?ver=6.0`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/style.css?ver=1.2`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/blocks/navigation/view.min.js?ver=009e29110e016c14bac4ba0ecc809fcd`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-transparent-d.png`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/favicon.ico`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/images/w-logo-blue-white-bg.png`, null, { tags: { name: 'png' } }],
    ]);

    responses.forEach(res => {
        check(res, {
            'status was 200': (r) => {
                return r.status == 200
            }
        })
    });
    
    sleep(1);
}

ブラウザからのアクセスを想定し、htmlの取得とページ中のjsやcssなども続けて取得する形にしてみました。
これを実行してみます。

$ k6 run script_v1.js 
          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: script_v1.js
     output: -

  scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
           * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)


running (00m01.5s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m01.5s/10m0s  1/1 iters, 1 per VU

     ✓ status was 200

     checks.........................: 100.00% ✓ 9        ✗ 0  
     data_received..................: 646 kB  426 kB/s
     data_sent......................: 1.3 kB  887 B/s
     http_req_blocked...............: avg=5.93ms   min=5.97µs   med=8.02ms   max=11.12ms  p(90)=10.77ms  p(95)=10.94ms 
     http_req_connecting............: avg=2.43ms   min=0s       med=0s       max=10.62ms  p(90)=8.1ms    p(95)=9.36ms  
     http_req_duration..............: avg=74.13ms  min=8.42ms   med=18.62ms  max=349.01ms p(90)=155.72ms p(95)=252.37ms
       { expected_response:true }...: avg=74.13ms  min=8.42ms   med=18.62ms  max=349.01ms p(90)=155.72ms p(95)=252.37ms
     http_req_failed................: 0.00%   ✓ 0        ✗ 10 
     http_req_receiving.............: avg=18.1ms   min=212.95µs med=1.23ms   max=101.51ms p(90)=50.78ms  p(95)=76.14ms 
     http_req_sending...............: avg=159.56µs min=28.54µs  med=101.63µs max=415.48µs p(90)=383.11µs p(95)=399.29µs
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=55.87ms  min=7.83ms   med=14.87ms  max=321.85ms p(90)=152.4ms  p(95)=237.12ms
     http_reqs......................: 10      6.590794/s
     iteration_duration.............: avg=1.51s    min=1.51s    med=1.51s    max=1.51s    p(90)=1.51s    p(95)=1.51s   
     iterations.....................: 1       0.659079/s
     vus............................: 1       min=1      max=1
     vus_max........................: 1       min=1      max=1

1回のアクセスを行い、結果が出力されます。1回だけなので負荷はかかってないです。

2. 繰り返し実行させる

繰り返し実行させるにはoptionsを指定します。スモークテストを例にすると、vus(仮想ユーザー)が1で、1m(1分間)連続でアクセスし、99%のレスポンスが1.5秒以内だと成功となります。

script_v2.js
import http from 'k6/http';
import { check, sleep, } from 'k6';

// Smoke testing
export const options = {
    vus: 1, // 1 user looping for 1 minute
    duration: '1m',
    thresholds: {
        http_req_duration: ['p(99)<1500'], // 99% of requests must complete below 1.5s
    },
};

export default function () {
    const BASE_URL = 'http://192.168.1.113/'

    const res = http.get(BASE_URL, { tags: { name: 'base' } })

    check(res, {
        'status was 200': (r) => {
            return r.status == 200
        }
    })

    const responses = http.batch([
        ['GET', `${BASE_URL}/wp-includes/blocks/navigation/style.min.css?ver=6.0`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/js/wp-emoji-release.min.js?ver=6.0`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/style.css?ver=1.2`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/blocks/navigation/view.min.js?ver=009e29110e016c14bac4ba0ecc809fcd`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-transparent-d.png`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/favicon.ico`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/images/w-logo-blue-white-bg.png`, null, { tags: { name: 'png' } }],
    ]);

    responses.forEach(res => {
        check(res, {
            'status was 200': (r) => {
                return r.status == 200
            }
        })
    });

    sleep(1);
}
k6 run script_v2.js 
          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: script_v2.js
     output: -

  scenarios: (100.00%) 1 scenario, 1 max VUs, 1m30s max duration (incl. graceful stop):
           * default: 1 looping VUs for 1m0s (gracefulStop: 30s)


running (1m00.2s), 0/1 VUs, 42 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  1m0s

     ✓ status was 200

     checks.........................: 100.00% ✓ 378      ✗ 0  
     data_received..................: 27 MB   451 kB/s
     data_sent......................: 57 kB   938 B/s
     http_req_blocked...............: avg=111.51µs min=1.6µs   med=6.95µs  max=10.75ms  p(90)=15.2µs   p(95)=17.09µs 
     http_req_connecting............: avg=74.84µs  min=0s      med=0s      max=7.39ms   p(90)=0s       p(95)=0s      
   ✓ http_req_duration..............: avg=71.68ms  min=5.41ms  med=25.2ms  max=327.56ms p(90)=226.82ms p(95)=277.36ms
       { expected_response:true }...: avg=71.68ms  min=5.41ms  med=25.2ms  max=327.56ms p(90)=226.82ms p(95)=277.36ms
     http_req_failed................: 0.00%   ✓ 0        ✗ 420
     http_req_receiving.............: avg=21.83ms  min=48.98µs med=3.04ms  max=205.44ms p(90)=92.69ms  p(95)=111.21ms
     http_req_sending...............: avg=41.36µs  min=8.52µs  med=32.74µs max=526.69µs p(90)=59.85µs  p(95)=95.62µs 
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s      max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=49.81ms  min=4.13ms  med=14.97ms max=303.21ms p(90)=197.59ms p(95)=258.42ms
     http_reqs......................: 420     6.972959/s
     iteration_duration.............: avg=1.43s    min=1.4s    med=1.42s   max=1.5s     p(90)=1.45s    p(95)=1.48s   
     iterations.....................: 42      0.697296/s
     vus............................: 1       min=1      max=1
     vus_max........................: 1       min=1      max=1

3. Grafanaで可視化

コンソールに出力される結果だけだと分かりづらいので、InfluxDBにデータを投入し、Grafanaで可視化ができます。
実行時に--outで宛先を指定します。InfluxDBはv1にしか対応していないようです。

k6 run --out influxdb=http://localhost:8086/myk6db script_v2.js

localhost_3000_d_c7TyZPX7z_k6-load-testing-results_orgId=1&refresh=5s&from=now-15m&to=now(1920x1080).png

4. どれだけのユーザー数に耐えられるか検証

以下の条件で試験を実施しました。

  • 15秒ごとに1ユーザーずつ増加
  • p95のリクエストが1.5秒以内に返却できることをしきい値として設定

optionsthresholdsを追加しています。abortOnFailなどの指定により、条件を満たさなくなった際にテストが終了します。

script_v3.js
import http from 'k6/http';
import { check, sleep, } from 'k6';

export const options = {
    stages: [
        { duration: '30m', target: 120 }, // below normal load
        { duration: '10m', target: 0 }, // scale down. Recovery stage.
    ],
    thresholds: {
        http_req_duration: [{ threshold: 'p(95)<1500', abortOnFail: true, delayAbortEval: '1m' }],
    },
};

export default function () {
    const BASE_URL = 'http://192.168.1.113/'; // make sure this is not production

    const res = http.get(BASE_URL, { tags: { name: 'base' } })

    check(res, {
        'status was 200': (r) => {
            return r.status == 200
        }
    })

    const responses = http.batch([
        ['GET', `${BASE_URL}/wp-includes/blocks/navigation/style.min.css?ver=6.0`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/js/wp-emoji-release.min.js?ver=6.0`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/style.css?ver=1.2`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/blocks/navigation/view.min.js?ver=009e29110e016c14bac4ba0ecc809fcd`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-transparent-d.png`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/favicon.ico`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/images/w-logo-blue-white-bg.png`, null, { tags: { name: 'png' } }],
    ]);

    responses.forEach(res => {
        check(res, {
            'status was 200': (r) => {
                return r.status == 200
            }
        })
    });

    sleep(1);
}

結果は、36ユーザーでしきい値に達しました。(これが多いのか、少ないのか、はどちらなんでしょう)

localhost_3000_d_c7TyZPX7z_k6-load-testing-results_orgId=1&refresh=5s&from=now-15m&to=now(1920x1080) (1).png

5. ソークテストもやってみた。

10分でソークテストなのかわかりませんが、段階的にアクセスを増やしてみます。

script_v4.js
import http from 'k6/http';
import { check, sleep, fail } from 'k6';
import { Counter } from 'k6/metrics';

export const options = {
    stages: [
        { duration: '2m', target: 10 }, // below normal load
        { duration: '10m', target: 10 }, // below normal load
        { duration: '2m', target: 15 }, // below normal load
        { duration: '10m', target: 15 }, // below normal load
        { duration: '2m', target: 20 }, // below normal load
        { duration: '10m', target: 20 }, // below normal load
        { duration: '2m', target: 25 }, // below normal load
        { duration: '10m', target: 25 }, // below normal load
        { duration: '2m', target: 30 }, // below normal load
        { duration: '10m', target: 30 }, // below normal load
        { duration: '10m', target: 0 }, // scale down. Recovery stage.
    ],
    thresholds: {
        // fail and abort the test if 95th percentile response goes above 500ms
        // delay the threshold evaluation for 1 min to gather enought data
        http_req_duration: [{ threshold: 'p(95)<1500', abortOnFail: true, delayAbortEval: '1m' }],
    },
};



http.setResponseCallback(http.expectedStatuses({ min: 200, max: 399 }));

const errors = new Counter('errors');

export default function () {
    const BASE_URL = 'http://192.168.1.113/'; // make sure this is not production

    const res = http.get(BASE_URL, { tags: { name: 'base' } })

    check(res, {
        'status was 200': (r) => {
            return r.status == 200
        }
    })
    check(res, {
        'error code': (r) => {
            if (r.error_code != 0) {
                errors.add(1, { error_code: r.error_code })
                fail('error code was *not* 0');
            }
            return r.error_code == 0
        }
    })

    const responses = http.batch([
        ['GET', `${BASE_URL}/wp-includes/blocks/navigation/style.min.css?ver=6.0`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/js/wp-emoji-release.min.js?ver=6.0`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/style.css?ver=1.2`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/blocks/navigation/view.min.js?ver=009e29110e016c14bac4ba0ecc809fcd`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/assets/images/flight-path-on-transparent-d.png`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-content/themes/twentytwentytwo/assets/fonts/source-serif-pro/SourceSerif4Variable-Roman.ttf.woff2`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/favicon.ico`, null, { tags: { name: 'png' } }],
        ['GET', `${BASE_URL}/wp-includes/images/w-logo-blue-white-bg.png`, null, { tags: { name: 'png' } }],
    ]);

    responses.forEach(res => {
        check(res, {
            'status was 200': (r) => {
                return r.status == 200
            }
        })
        check(res, {
            'error code': (r) => {
                if (r.error_code != 0) {
                    errors.add(1, { error_code: r.error_code })
                    fail('error code was *not* 0');
                }
                return r.error_code == 0
            }
        })
    });

    sleep(1);
}

うまく行きそうでしたが最後の最後でしきい値を超えましたね、残念。

localhost_3000_d_c7TyZPX7z_k6-load-testing-results_orgId=1&refresh=5s&from=now-15m&to=now(1920x1080) (3).png

参考情報(レスポンスタイムについて)

k6のサイトにはNielsen Norman Groupからの引用として以下の内容が記載されていました。

  • 0.1秒は、システムが瞬時に反応しているとユーザーに感じさせる限界です。つまり、結果を表示する以外に特別なフィードバックは必要ありません。
  • 1.0秒は、ユーザーが遅延に気付いたとしても、ユーザーの思考の流れが途切れないようにするための限界です。通常、0.1秒以上1.0秒未満の遅延の間は特別なフィードバックは必要ありませんが、ユーザーはデータを直接操作している感覚を失います。
  • 10秒は、ユーザーの注意を対話に集中させ続けるための制限についてです。より長い遅延の場合、ユーザーはコンピューターが終了するのを待っている間に他のタスクを実行したいので、コンピューターがいつ完了すると予想されるかを示すフィードバックをユーザーに提供する必要があります。応答時間が大きく変動する可能性がある場合、遅延中のフィードバックは特に重要です。これは、ユーザーが何を期待できるかわからないためです。

なるほどという思いと、結構シビアだなという思い。

環境構築

テストされる側(Raspberry Pi4)

docker-compose.yaml
version: '3.1'

services:

  nginx:
    image: nginx
    restart: always
    ports:
      - 80:80
    volumes:
      - wordpress:/var/www/html:ro
      - ./default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - wordpress
      - db

  wordpress:
    image: wordpress:6.0.0-fpm
    restart: always
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress:/var/www/html
    depends_on:
      - db

  db:
    image: mariadb
    restart: always
    environment:
      MARIADB_DATABASE: exampledb
      MARIADB_USER: exampleuser
      MARIADB_PASSWORD: examplepass
      MARIADB_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:

テストする側

k6のインストール

sudo mkdir -p /root/.gnupg/
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install -y k6

InfluxDBとGrafanaのインストール

InfluxDBは1.x系しか対応していないようです。

docker-compose.yaml
version: '3'

services:
  influxdb:
    image: influxdb:1.2
    ports:
      - "8086:8086"
    expose:
      - 8086
    volumes:
      - ./influxdb:/var/lib/influxdb
    networks:
      - influx-grafana-network
  grafana:
    image: grafana/grafana-oss
    ports:
      - "3000:3000"
    volumes:
      - ./grafana:/var/lib/grafana
    networks:
      - influx-grafana-network
    user: "0"

networks:
  influx-grafana-network:
    external:
      name: influx-grafana-network

Grafanaのダッシュボードはこちら(2587)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0