masalibの日記

システム開発、運用と猫の写真ブログです

mysqスロークエリの解析について

設定について

スロークエリとは実行時間が長いSQLクエリを記録する機能であり、パフォーマンスの分析やチューニングに役立ちます。

  1. MySQLサーバーの設定ファイルを編集する:

    • MySQLの設定ファイル(my.cnfやmy.ini)の場所は、インストールされた環境によって異なります。Linuxでは通常/etc/my.cnfや/etc/mysql/my.cnfに、WindowsではC:\ProgramData\MySQL\MySQL Server x.x\内にあります。
    • 設定ファイルをテキストエディタで開き、以下の設定を追加または変更します(x.xはバージョン番号)。
  2. スロークエリログを有効にする設定:

    • スロークエリログを有効にするには、slow_query_logを1に設定します。

    [mysqld] slow_query_log = 1 slow_query_log_file = /path/to/your/log-file long_query_time = 2 log_queries_not_using_indexes = 1

    • slow_query_log_fileは、スロークエリログのファイルパスです。
    • long_query_timeは、この秒数以上かかるクエリをスロークエリとして記録します。上記の例では2秒に設定されています。
    • log_queries_not_using_indexesは、インデックスを使用していないクエリもログに記録するかどうかを設定します。必要に応じて1に設定してください。
  3. MySQLサーバーを再起動する:

    • 設定を適用するためには、MySQLサーバーを再起動する必要があります。Linuxでは通常、sudo systemctl restart mysqlやsudo service mysql restartで再起動できます。Windowsでは、サービス管理ツールを使用して「MySQL」サービスを再起動します。
  4. スロークエリログの確認:

    • スロークエリログが有効になっていると、指定したファイルに長いクエリが記録されます。ログファイルを定期的にチェックして、パフォーマンスに影響を与えている可能性のあるクエリを特定しましょう。

これらの設定を行うことで、MySQLのパフォーマンス分析と最適化に役立つ情報を得ることができます。 ただし、スロークエリログはディスクスペースを消費するため、 本番環境での使用には注意が必要です。適切なログローテーションの設定もする必要があります

解析について

mysqlslowdumpは、スロークエリログを解析してサマリーとして出力するコマンドラインツールというのがあるのですがこれは使いません。 古い人間が多いのでエクセルで管理したいです。

簡単な流れは
・スロークエリログを解析ツールでJSONにする
JSONをエクセルで読み込む

ツールの前提としてはpythonは3系です。Windows環境でも動かすためにutf8を指定しています。

スロークエリログを解析ツールの概要

  1. 指定されたディレクトリ内のすべてのファイルからスロークエリログを解析し、それらのクエリを集計して結果を出力します。

  2. まず、parse_slow_query_log関数は指定されたディレクトリ内のすべてのファイルを開き、各行を解析します。各行が特定の文字列で始まる場合、その行の情報は現在のクエリの辞書に追加されます。すべてのクエリが解析されると、この関数はスロークエリのリストを返します。

  3. 次に、save_queries_to_file関数は、解析されたスロークエリをJSON形式でファイルに保存します。

  4. summarize_queries関数は、解析されたスロークエリのリストを取り、各クエリタイプ(SELECT、INSERT、UPDATE、DELETE、またはその他)の統計情報を集計します。これには、クエリの数、合計および最大クエリ時間、合計および最大ロック時間、合計行の調査数が含まれます。集計が完了すると、この関数はクエリタイプごとの統計情報の辞書を返します。

  5. 最後に、このコードは指定されたディレクトリからスロークエリを解析し、結果をファイルに保存し、クエリの統計情報を出力します。

スロークエリログを解析ツールのソース

import os
import re
import json
from collections import defaultdict

def parse_slow_query_log(directory):
    """
    Parses the slow query log files in the specified directory and returns a list of dictionaries,
    where each dictionary represents a slow query entry with its corresponding information.

    Args:
        directory (str): The directory path where the slow query log files are located.

    Returns:
        list: A list of dictionaries, where each dictionary represents a slow query entry.
              Each dictionary contains the following keys:
              - 'Time': The timestamp of the slow query.
              - 'SET timestamp': The timestamp when the query was executed.
              - 'use': The database schema used for the query.
              - 'User@Host': The user and host information.
              - 'Schema': The database schema associated with the query.
              - 'Query_time': The execution time of the query.
              - 'Lock_time': The time spent waiting for locks.
              - 'Rows_sent': The number of rows sent.
              - 'Rows_examined': The number of rows examined.
              - 'Rows_affected': The number of rows affected.
              - 'Bytes_sent': The number of bytes sent.
              - 'Query': The SQL query itself.
    """
    slow_queries = []
    file_paths = [os.path.join(directory, file) for file in os.listdir(directory)]
    
    for file_path in file_paths:
        with open(file_path, 'r', encoding="utf-8") as file:
            lines = file.readlines()
        
        current_query = {}
        capture_query = False  
        for line in lines:
            if line.startswith('# Time:'):
                if current_query:
                    slow_queries.append(current_query)
                    current_query = {}
                capture_query = True
                current_query['Time'] = line.split('# Time:')[1].strip()
            elif line.startswith('SET timestamp='):
                current_query['SET timestamp'] = line.strip()
            elif line.startswith('use '):
                current_query['use'] = line.strip()
            elif line.startswith('# User@Host:'):
                current_query['User@Host'] = line.split('# User@Host:')[1].strip()
            elif line.startswith('# Schema:'):
                current_query['Schema'] = line.split('# Schema:')[1].split()[0].strip()
            elif line.startswith('# Query_time:'):
                current_query['Query_time'] = line.split('Query_time:')[1].split()[0].strip()
                current_query['Lock_time'] = line.split('Lock_time:')[1].split()[0].strip()
                current_query['Rows_sent'] = line.split('Rows_sent:')[1].split()[0].strip()
                current_query['Rows_examined'] = line.split('Rows_examined:')[1].split()[0].strip()
                current_query['Rows_affected'] = line.split('Rows_affected:')[1].split()[0].strip()
            elif line.startswith('# Bytes_sent:'):
                current_query['Bytes_sent'] = line.split('Bytes_sent:')[1].split()[0].strip()
            elif not line.startswith('#') and line.strip() and capture_query:
                if 'Query' not in current_query:
                    current_query['Query'] = line.strip()
                else:
                    current_query['Query'] += ' ' + line.strip()
        
        # last query
        if current_query:
            slow_queries.append(current_query)
    
    return slow_queries
    
def save_queries_to_file(slow_queries, output_file):
    with open(output_file, 'w',encoding="utf-8") as file:
        json.dump(slow_queries, file, indent=4)

def summarize_queries(slow_queries):
    print("summarize start")

    query_summary = defaultdict(lambda: {'count': 0, 'total_query_time': 0, 'max_query_time': 0,
                                         'total_lock_time': 0, 'max_lock_time': 0, 'total_rows_examined': 0})
    for query_info in slow_queries:
        # SET timestamp=sanitize
        #if 'SET timestamp=' in query_info['Query']:
        #    continue

        # querytype(SELECT, INSERT, UPDATE, DELETE)
        match = re.search(r'^(SELECT|INSERT|UPDATE|DELETE)', query_info['Query'], re.IGNORECASE)
        if match:
            query_type = match.group(1).upper()
        else:
            query_type = 'OTHER'
        
        summary = query_summary[query_type]
        summary['count'] += 1
        query_time = float(query_info['Query_time'])
        lock_time = float(query_info['Lock_time'])
        rows_examined = int(query_info['Rows_examined'])

        summary['total_query_time'] += query_time
        summary['max_query_time'] = max(summary['max_query_time'], query_time)

        summary['total_lock_time'] += lock_time
        summary['max_lock_time'] = max(summary['max_lock_time'], lock_time)

        summary['total_rows_examined'] += rows_examined
    
    # summary result
    for query_type, stats in query_summary.items():
        if stats['count'] > 0:
            stats['average_query_time'] = stats['total_query_time'] / stats['count']
            stats['average_lock_time'] = stats['total_lock_time'] / stats['count']
        else:
            stats['average_query_time'] = 0
            stats['average_lock_time'] = 0

    return dict(query_summary)

# sample
directory = './slowQuerylogs/'
output_file = './output_file.json'
slow_queries = parse_slow_query_log(directory)

# output sample
save_queries_to_file(slow_queries, output_file)

print("Done. Parsed slow queries are saved ")

# summary do
summary = summarize_queries(slow_queries)

# output summary
for query_type, stats in summary.items():
    print(f"{query_type}:")
    print(f"  Count: {stats['count']}")
    print(f"  Average Query Time: {stats['average_query_time']:.2f}s")
    print(f"  Max Query Time: {stats['max_query_time']:.2f}s")
    print(f"  Average Lock Time: {stats['average_lock_time']:.2f}s")
    print(f"  Max Lock Time: {stats['max_lock_time']:.2f}s")
    print(f"  Total Rows Examined: {stats['total_rows_examined']}")
    print()

出力されたJSONをエクセルで読み込む

手順は以下のページが参考になります

https://qiita.com/afukuma/items/65c6e96bd15b319e160f

結果のイメージは以下になります

これならエクセルおじさん(自分)でも解析ができます

オラクルクラウドの無料枠のみダウン

安いのもあってオラククラウドを使っています。だいたい2CPUと4Gがインスタンスが4000円ぐらいです

比較的に安定していたのですが珍しいサーバーの応答がないという状況が発生しました しかもインスタンス一覧では正常に動いている状況でした

色々調べたところ、無料枠のみダウンしている事が発覚しました

無料枠は1CPUと1Gのインスタンスが無料で使えます。 ロードバランサーとして使っていました。 理由としてはロードバランサーはそこまでCPUやメモリを使わないので最適でした

対応

ネットワークが接続できない最悪の事態だったのでインスタンスを作り直して対応しました

1・インスタンスからのカスタムイメージを作成する (サーバーが停止されるので注意)

2・カスタムイメージからインスタンスを作成する (プライベートIPが選択できないことが発覚してネットワークの設定を再作成した)

連絡をうけて3時間ぐらいかかった。ネットワーク障害はまじで原因がわからないので対応を決めるにも時間がかかる

最終的にネットワークが復旧した

無料枠なので文句を言いづらいがネットワーク遮断は原因がわかりずらいのでやめてほしい。告知ぐらいしてほしい

【VSCode】console.logって打つの面倒だよねって話

qiita.com

の記事を見て、確かにめんどくさいと思った。
ただこの記事はIntelliJの記事だった。私はVisual Studio Code (VSCode) を使っているので調べて対応した。

Visual Studio Code (VSCode) で 自動補完されるようにするためには 対応するユーザースニペットを利用します

コマンドパレットを開く:

VSCode 上で Ctrl+Shift+P (Windows/Linux) または Cmd+Shift+P (Mac) を押して、コマンドパレットを開きます。

ユーザースニペットを検索:

コマンドパレットに 「Configure User Snippets」 と入力し、表示されたオプションからそれを選択します。

言語の選択:

この場合は javascript または javascriptreact(もし React を使用している場合)のいずれか、または両方でスニペットを利用したい場合、それぞれの言語のスニペットファイルを選択します。

スニペットの追加(例1):

スニペットファイルを開いたら、以下のスニペットをファイルに追加します。

"Rocket Log": {
    "prefix": "log",
    "body": [
        "console.log(\"🚀====${TM_FILENAME}: L: ${TM_LINE_NUMBER} ====🚀\");",
        "console.log(\"$1: \", $1);"
    ],
    "description": "Log output to console with filename and line number"
}

この定義では、log と入力して展開することで、最初の行にはファイル名と行番号を含むロケットマークのデコレーションが、次の行には変数のログ出力が挿入されます。$1 は カーソルが最初に置かれる位置で、ここに変数名を入力すると、その変数の内容がログに出力されるようになります。

補足: VSCodeスニペットでは、ファイル名を ${TM_FILENAME}、行番号を ${TM_LINE_NUMBER} という形です

スニペットの追加(例2):

スニペットファイルを開いたら、以下のスニペットをファイルに追加します。このスニペット定義では、cons と入力することで console.log("$VARIABLES$: ", $VARIABLES$) を挿入するように設定しています。

"Print to console": {
    "prefix": "cons",
    "body": [
        "console.log(\"$1: \", $1);"
    ],
    "description": "Log output to console"
}

このスニペットでは、$1 はカーソルが移動する位置を示しており、cons と打った後にスニペットが展開されると、自動的に最初の変数の位置にカーソルが移動します。変数名を入力した後に Tab キーを押すと、同じ変数名が $VARIABLES$ の位置にも挿入されます。

保存して終了:

スニペットを追加したら、ファイルを保存して閉じます。

これで設定は完了です。logやcons と入力して Tab キーを押すことで、定義したスニペットが展開されるようになります。もし、スニペットが期待通りに動作しない場合は、VSCode を再起動してください。

ツールの実行中にSSHセッションが切れてしまう場合の対応

私の家のネットワーク環境は顧客対応が悪いで有名なJ◯omさんです。
普通にネットワークが繋がらない事があります。しかも告知がありません。
それはさておき、サーバーにSSH接続してツールを実行している時にいきなりセッションが切断されると 困るので時間がかかるツールに関してはセッションが切れてもいいようにしています。
その対応を記載します

SSH接続が途切れてもサーバー上でツールが実行し続けるためには、セッション管理ツールを使用するのが一般的な方法です。screenやtmuxなどのツールを使えば、セッションが切断された後もプロセスをバックグラウンドで実行し続けることができます。また、nohupコマンドを使用する方法もあります。ここでは、これらの方法について説明します。

screenを使用する方法

screenは、複数のセッションを同時に開いたり、セッションを切断しても作業を継続できるようにするツールです。

  • screenをインストールしていない場合は、まずインストールします。

  • screenセッションを開始します。

    screen

  • 必要なコマンドを実行します。

  • セッションから一時的にデタッチするには、Ctrl + a、続けてdを押します。

  • セッションに再接続するには、screen -rコマンドを使用します。

2. tmuxを使用する方法

tmuxもscreenと同様の機能を提供するツールですが、よりモダンで機能が豊富です。

  • tmuxをインストールしていない場合は、まずインストールします。

  • tmuxセッションを開始します。

    tmux

  • 必要なコマンドを実行します。

  • セッションからデタッチするには、Ctrl + b、続けてdを押します。

  • セッションに再接続するには、tmux attach-sessionコマンドを使用します。

3. nohupを使用する方法

nohupは、ハングアップシグナルを無視してコマンドを実行し、セッションが切断されてもプロセスを継続させるコマンドです。

  • nohupを使用してコマンドを実行します。

    nohup コマンド &

    • ここで、コマンドは実行したいコマンドに置き換えてください。&はコマンドをバックグラウンドで実行するためのものです。
  • 実行したコマンドの出力は、デフォルトでnohup.outに保存されます。

これらの方法を使えば、SSHセッションが切断された後もサーバー上でツールを実行し続けることが可能です。 どのツールを使用するかは、個人の好みや具体的な要件に応じて選択してください。

自分はインストールがめんどくさいのでnohupで対応しています。 セッションなどが残ってしまう可能性があるので最後はプロセスを殺して対応しています

ps -ef | grep main.py
kill -9 {pid}

【python】絶対パスで指定されたフォルダにある全てのファイルを削除する方法

指定されたフォルダ内の全てのファイルを削除するPythonプログラムの例を以下に示します。このプログラムはosとglobモジュールを使用して、指定されたフォルダ内の全てのファイルを見つけ、それらを削除します。

import os
import glob

def delete_files_in_folder(folder_path):
    # フォルダ内の全ファイルの絶対パスを取得
    files = glob.glob(os.path.join(folder_path, '*'))
    
    # 各ファイルを削除
    for file in files:
        try:
            os.remove(file)
            print(f'削除成功: {file}')
        except Exception as e:
            print(f'削除失敗: {file}, エラー: {e}')

# 絶対パスで指定されたフォルダ
folder_path = '/path/to/your/folder'

# 関数を呼び出してフォルダ内のファイルを削除
delete_files_in_folder(folder_path)

このコードを実行する前に、folder_path変数に削除したいファイルがあるフォルダの絶対パスを設定してください。また、このスクリプトはフォルダ内の全てのファイルを削除しますが、サブフォルダ内のファイルは削除されません。サブフォルダも含めて全てを削除したい場合は、追加のロジックが必要になります。

重要なファイルが含まれている可能性があるため、このスクリプトを実行する前に、削除対象のフォルダ内のファイルを慎重に確認してください。 パーミッションによっては消せないのでご注意してください

アプリ(ビルド済みのアプリケーション)のみを納品してもらい、それをApp Storeにアップロードする方法

前提

Appstoreに公開する場合はXcodeからアプリをアップロードするのが一般的です

状況

外部の会社に開発を依頼し、ソースコードではなくアプリ(ビルド済みのアプリケーション)のみを 納品してもらい、それをApp Storeにアップロードする場合、 いくつかのステップを踏む必要があります。基本的な流れは以下の通りです:

1. アプリの受け取り:

開発会社からビルド済みの.ipaファイルを受け取ります。このファイルは、App Storeへのアップロードに必要なアプリケーションのパッケージです。

2. Apple Developer Programへの登録:

アプリをApp Storeにアップロードするには、Apple Developer Programに登録している必要があります。 年間費用がかかりますが、これによりXcodeApp Store Connectへのアクセス権が得られます。 →これはうちの会社はあるのでOK

3. App Store Connectの設定:

App Store Connectでは、アプリのメタデータ(名前、説明、スクリーンショットなど)を設定し、 アプリのバージョンや価格設定を行います。新しいアプリを作成し、必要な情報を入力します。 →これはフォーマットに入力するだけ、多言語対応する時がめんどくさい。フランスに配信する場合は特にめんどくさい

qiita.com

4. Transporterアプリの利用:

以前はXcodeやApplication Loaderを 使ってアプリをアップロードしていましたが、 現在はAppleが提供する「Transporter」というアプリを使う方法が一般的です。 TransporterはMac App Storeからダウンロードできます。 Transporterを使うと、ビルド済みの.ipaファイルをApp Store Connectにアップロードできます。

qiita.com

5. アップロード後のプロセス:

アプリがアップロードされると、 自動的にApp Storeの審査プロセスが開始されます。 審査には通常数日から1週間ほどかかります。 審査が完了し、問題がなければ、アプリを公開することができます。

アップロードプロセスにはいくつかの技術的なステップが含まれますが、 これらは基本的には開発会社が行うビルドプロセスの延長線上にあります。 開発会社がApp Storeの規定に沿った適切なビルド設定でアプリを準備していることが前提となります。 また、アプリの公開に際しては、適切なプライバシーポリシーや利用規約が設定されている必要があります デベロッパーに登録しているとテストアップや審査にうつるとメールがもらえます

IOSでバンドルIDのリスト取得って可能なのか?

iOSで端末にインストールされているアプリのバンドルIDリストを取得することは、セキュリティとプライバシーの観点から、直接的にはできません。Appleはアプリ間の相互作用を厳しく制限しており、他のアプリの詳細情報(特にインストールされているかどうか)を直接的に取得する方法を提供していません。

  1. URLスキーム: iOSアプリは、特定のURLスキームを登録して、他のアプリから直接起動されることが可能です。UIApplicationcanOpenURL:メソッドを使って、特定のURLスキームが利用可能かどうかをチェックすることで、関連するアプリがインストールされているかどうかを間接的に推測することができます。ただし、この方法を使うには、あらかじめInfo.plistに確認したいURLスキームをLSApplicationQueriesSchemesキーとして登録する必要があります。

URLスキームの調べ方 blog.thetheorier.com

  1. Universal Links: 特定のウェブサイトへのリンクをアプリで直接開くための仕組みです。この機能を通じて、特定のウェブリンクをアプリで開こうとすると、そのアプリがインストールされていればアプリが起動します。これもアプリの存在を確認する間接的な方法の一つですが、アプリ間の直接的な情報交換とは異なります。

これらの方法は、特定のアプリがインストールされているかどうかを確認するためのものですが、 インストールされている全アプリのリストを取得することはできません。iOSのセキュリティモデルは、ユーザーのプライバシー保護を重視しており、 アプリ間での情報共有を最小限に抑えるよう設計されています。

もし特定のアプリの存在を確認したい具体的なケースがあれば、 そのケースに合わせたアプローチを検討する必要があります。 例えば、特定の機能やサービスを提供するアプリ間での連携を実現したい場合などです。 それでも、ユーザーのプライバシーに配慮し、Appleガイドラインに従うことが重要。

androidがゆるすぎだけ??