好きこそものの上手なるby yukichi

プログラミング学習の備忘録。万歳車輪の再発明。

Lambda レイヤーを Amazon Linux 2で作成する

最近Discord上で動く時差変換botを作って、lambdaで動かすようにした。

GitHub - kumo2kumo/time_converter

 

その際lambda_function.pyで

Unable to import module 'lambda_function': No module named 'nacl._sodium

と出て読み込めない。

lambdaのランタイムはpython3.10に指定。

 


やり方は

Python Lambda 関数の「モジュールをインポートできません」エラーを解決する | AWS re:Post

を参考にした。


まずリンクの通りIAMポリシー作成→ロールにアタッチー>ロールをEC2にアタッチ

AmazonLinux2は初期でPython 3.7.16

リンクに従ってamazon-linux-extraでインストールしようとしても

amazon-linux-extras list | grep python
44  python3.8                       available    [ =stable ]

の通り3.8までしかできない。

ので3.10は自身でインストールする。

これを参考に

Amazon Linux 2にPython3.10をインストール | fragment

 

完了したらpynaclをインストールしてlayerに追加

mkdir python
python3.8 -m pip install pynacl -t python/
zip -r layer.zip python
aws lambda publish-layer-version --layer-name hoge --zip-file fileb://layer.zip --compatible-runtimes python3.10 --region  ap-north-east-1(tokyo regionの場合)

最後にlambdaでhoge layerを追加

で完了できた😄

AWS lambdaを使ってみる

元々タスクスケジューラで動かしていたDiscord botAWS lambdaに移行してみたので、メモ📝

1,ライブラリを特定のフォルダにインストールする ↓の場合my_lambda を指定 py -m pip install -r requirements.txt -t ./my_lambda 

2,lambda_function.pyに処理を記載。 function名はlambda_functionとする。

3,lambda_function.pyもmy_lambda フォルダに移動してzipで固める

4、aws lambdaにアップロードする

5,aws上で環境変数を登録する。

環境変数をコード上で呼び出すためには

import os
cousumer_key = os.environ['CONSUMER_KEY']

のように書く AWS Lambda 環境変数の使用 - AWS Lambda

コードはこんな感じ。特定のTwitterアカウントのツイートを取得してbotで表示するもの

import tweepy
import requests
from discordwebhook import Discord
import os

consumer_key = os.environ['CONSUMER_KEY']
consumer_secret = os.environ['CONSUMER_SECRET']
access_token = os.environ['ACCESS_TOKEN']
access_secret = os.environ['ACCESS_SECRET']
webhook_url = os.environ['WEBHOOK_URL']

class TwitterApi:
    def get_latest_tweet(self, account_name):
        auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
        auth.set_access_token(access_token, access_secret)
        api = tweepy.API(auth, wait_on_rate_limit=True)

        tweet = api.user_timeline(id=account_name, count=1)[0]
        screen_name = tweet.user.screen_name
        return tweet.text


def lambda_handler(event, context):   
    api = TwitterApi()
    text = api.get_latest_tweet("@xxxxxxx")
    discord = Discord(url=webhook_url)
    discord.post(content=text)

6,デプロイする&テストしてみる。 ここで 401 Unauthorized\n32 - Could not authenticate you が出る。あら認証されてない、おや(;'∀')

7,Twitterの開発者ポータル見ると

This App has violated Twitter Rules and policies. As a result, it can no longer be accessed. For assistance, submit a support ticket

のエラーが。プロジェクト作り直して諸々のキーを再取得

8,環境変数変えてテスト→成功

9,トリガーをイベントで設定 今回は毎日0時に投稿したいので、 cron(00 15 * * ? *) と記載。この時間はUTC time https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-cloudwatchevents-expressions.html

10,無事0時に表示、おめでとう㊗

めっちゃ便利やないか、、、今までタスクスケジューラでやってたから、PC起動していないときは実行されなかったんだよね。 やってみようと思ったのは応用情報のシステムアーキテクチャ解いててFaaSについて出てきて、ふむFaaSでbot実行できるのでは? FaaSには

AWS Lambda

Google Cloud Functions

Microsoft Azure Functions

などがあるのね、awsアカウント持ってるしやってみよう、ってなったから。 次はもう一個Discord上で動かしたい処理があるからそれも作ってみよー

Pythonでcsvファイル内を探索する

Pythoncsvファイル内の検索を行ったときに意図した結果が得られなった話

例えば

list = apple grape lemon kiwi banana green leaf phone

って項があって、

re.search(r'\sapple\s| ^apple\s, list):

って書いてもappleが引っかからなかった。なぜなら

|と^apple\s の間に空白を入れていたから。

丁寧に␣appleを検索してくれていたのよね、そりゃそうだーー

見やすいかなと思って何も考えずにスペース入れていたよ、ごめんありがとうPython

batファイルをタスクスケジューラで動かす時

先日twitterの投稿をdiscordに自動で投げるbotpythonで作っていたのだが、

作ったbatファイルをタスクスケジューラに設定しても実行してくれず、さて結局どうしたかっていう話。というかbatファイルって初めて書いた、今まで拡張子がbatって何だと思っていたーシェルファイルのwindows版と理解(ふわっ

 

結論から言うと

タスクスケジューラでバッチが実行されない原因は?

こちらの通りで、開始のところにbatファイルがあるフォルダの絶対パスをきちんと書きましょうー

ということ

自分は最初はプログラム/スクリプトのところにbatファイルまでの絶対パスC:\hoge\main.bat)を書いていたのですが、動かなかったのですねー

それはどうやら開始欄が空欄だとC:\Windows\system32\C:\hoge\main.bat

と思われてしまうらしく、そんなんないねんとお怒りになっていた模様

 

無事開始欄にパスを書いて、botが毎日起動してますーやった!

 

余談ですが10月から大学理工学部の科目等履修生にお邪魔することにした。

回路とか離散数学とか興味の向くままニューワールドに飛び込んでいくよ、どこに行く自分

 

 

【試して理解】Linuxのしくみ 読んだメモ

忘れないようメモ📝LinuxのシステムをC言語でプログラム書いて色々覗いてみようという本。 個人的に見よう見まねでCを書けて楽しかったし、メモリ操作とかあってハードウェアに近い言語なんだなって体感した。 Cをもう少しやってみたいので、東大でCの基礎を学ぶ講義が公表されているのでこれをやってみようと思います。

1

2. ユーザーモード

3. プロセス管理

  • プロセス生成は2種類
    • 同じプログラムの処理を複数プロセスに分けて処理
      • fork()(内部的にはclone()):発行したプロセスを元に、新たにプロセスを1つ生成。
    • 全く別のプログラムを生成 ‐ プロセス数が増えるのではなく、あるプロセスを他のプロセスで上書きする
      • execve():
    • プログラム実行の流れ:プログラムファイルの情報(補助的情報・コード領域・データ領域)に基づいてメモリ上にマップされる → エントリポイントからプログラムを実行する
    • プロセス終了時は_exit()関数を使用。直接呼び出すことは少なく、Cならexit()呼び出しで終了

4. システムスケジューラ

  • 論理CPU上である瞬間に動作できるプロセスは1つのみ
  • 複数プロセス実行中の場合、途中でプロセスを切り替えている→切り替えをコンテキストスイッチという
  • プロセスのスリープ状態時はCPUはアイドリング状態(使っていない)
  • レイテンシ:処理の終了までの所要時間
  • スループット:単位時間当たりの総仕事量
  • 論理CPU0と、論理CPU/2の番号のものは独立している
  • プロセス数を論理CPU数より多くしても、スループットは上がらない
  • nice(): 特定のプロセスに優先度を設定する。-19(高い)~20(低い)。優先度を上げられるのはrootのみ

5. メモリ管理

  • メモリ使用量が増えて身動きが取れない事:OOM(out of memory), メモリ管理システムはこの時適当なプロセスをkillしてメモリを開放する機能がある。
  • sysctlのvm.panic_on_oomが0→デフォルト、killer発動, 1->OOM時にシステム強制終了
  • プログラムはメモリが断片化していると、使用できないこともある
  • 仮想記憶:物理メモリにプロセスから直接アクセスするのではなく、仮想アドレスを用いて間接的にアクセスすること。仮想アドレス=プロセスから見えるメモリのアドレス。プロセスから物理アドレスに直接アクセスする方法はなし。
  • 仮想アドレス≠物理アドレス
  • 仮想アドレスと物理 -アドレスの対応表をページテーブルという。サイズは4KB(x86_64アーキテクチャ).
  • メモリ割り当て方法
    1. 必要な領域を物理メモリに割り当て、必要なデータをそこにコピー
    2. カーネルのメモリ内にページテーブルを作って、仮想物理アドレス空間を紐づける
    3. 所定のアドレスから実行
  • mmap関数:ページ単位で仮想メモリ取得、大きめメモリをプールしておく→その後malloc関数:バイト単位でメモリ取得
  • ファイルマップ:mapp()を所定の方法で呼び出すことで、ストレージデバイス内の該当ファイルをメモリに呼び出す→その空間を仮想アドレス空間マッピングできる
  • デマンドページング:
    1. プロセス生成時に仮想アドレス空間内の領域を獲得、が物理アドレスには未割当
    2. プログラムがエントリポイントから実行されると、CPUにてページフォルトが発生し、カーネルページフォルトハンドラが物理メモリにアドレスを割り当てる…続く
  • コピーオンライト;fork()時は、親プロセスのページテーブルを子プロセスにコピーする(一緒の物理アドレスを指している)この時書き込み権限は無効化されている
    • 親・子どちらかがページを更新したいとき
      1. カーネルページフォルトハンドラがアクセスされたページを別の場所にコピーして、書き込みしようとしたプロセスに和知宛てる
      2. 親・子それぞれ共有が解除されたページに対応するページテーブルを書き換える
      3. 書き込みしたい側は新たな物理ページと紐づけて、書き込みの許可をする。
      4. もう片方の当該ページも書き込みの許可をする
        親と子で共有されているメモリは、それぞれのプロセスで二重計上される
  • スワップ:ストレージデバイスの一部を一時的にメモリの代わりとして使用する。物理メモリが枯渇した際、使用の一部をストレージに退避させる。メモリを退避させる領域をスワップ領域(windowsでは仮想メモリ)、退避させることをスワップアウトという。取り戻すことをスワップイン、合わせてスワッピングという
    • どの領域を退避させるかの判断はカーネルのアルゴによる
    • スワッピングが頻繁に行われることをスラッシングといい、重くなる。
    • スワッピングのようにストレージへのアクセスが発生するページフォルトメジャーフォルトという
  • ヒュージページ;プロセスのべージテーブルに必要なメモリ量を減らせる、データベースなど仮想メモリを大量に使用するときに検討すると吉

6. 記憶階層

  • コンピュータの動作の流れ
    1. 命令をもとに、メモリからレジスタにデータを読み出す
    2. レジスタ上のデータをもとに計算する
    3. 計算結果をメモリに戻す
  • レジスタのメモリのやり取りのところで時間がかかる→キャッシュメモリを利用
    • キャッシュメモリ:基本CPU内にある。メモリのデータをキャッシュメモリにキャッシュする
      • レジスタの値が変えられたら、まず変更データをキャッシュメモリに上書きして、ダーティという印をつける→バックグラウンド処理としてメモリに書き戻される(キャッシュメモリはダーティでなくなる)
      • 階層型でレベルが存在する。一番レジスタに近く、容量が少なく、高速なのがL1
      • /sys/devices/system/cpu/cpu0/cache/index0/:cpu0のL1キャッシュの情報
    • ページキャッシュ:ストレージのデータをメモリにキャッシュすること ‐ プロセスがファイルのデータを読み出す場合、まずカーネルのメモリ上のページキャッシュ領域にコピーして情報を保持→そのデータをプロセスのメモリにコピーする.再度呼び出された場合早く呼び出せる。
      • プロセスにより更新されたら、ページキャッシュにデータを書き込んでダーティマークをつける ‐ ダーティページが多い&メモリ不足なシステムライトバック(ページキャッシュからストレージへの書き込み)が頻発するため遅くなる
        • sysctl vm.dirty_writeback_centisecs: ダーティページのライトバック周期センチ秒(1/100秒)
        • sysctl vm.dirty_background_radio: 全メモリの内、表示された割合のダーティページが超えた場合、ライトバック処理が走る
      • ページキャッシュ&バッファキャッシュ=ストレージ内のデータをメモリ上に置いておく仕組み(ざっくり)
      • echo 3 > /proc/sys/vm/drop_caches: システムのページキャッシュを削除する
  • ハイパースレッド機能:CPUコア内のレジスタなどを複数用意して、それぞれを論理CPUと認識されるようにするハードウェア機構スループットが単純に倍になるという訳でもない。
    • /sys/devices/system/cpu/cpuNum/topology/thread_siblings_list: ペアとなるスレッドの表示

7. ファイルシステム

  • どこにどんなデータがあり、どこに空き領域があるか管理する仕組み
  • linuxは複数のファイルシステムを扱える(ext4, XFS, Btrfs... ストレージデバイス上に存在する)
  • データ(データの中身・内容)とメタデータ(種類・時刻・権限情報)の2種類がある
  • ファイルシステムごとに使用できる容量を制限する機能があり、クォータという ‐ ユーザクォータ、ディレクトリクォータ…
  • システム不整合を防ぐ方法
    • ジャーナリング(for ext4, XFS): 更新に必要な処理一覧をジャーナル領域(ファイルシステム内のメタデータ)に書き出す→更新中にシャットダウンしたら、ジャーナルログを最初からもう一度再生する
    • コピーオンライト(for Btrfs)
      • 前提としてコピーオンライト型のファイルシステムは、ファイルに更新が入ったら書き換えられた部分のみを別の場所にコピーする
  • tmpfs: メモリ上に作成するファイルシステム、再起動後はデータが消えている
    • mount | grep ^tmpfs
    • freeコマンドのsharedフィールドの値はtmpfsによって使用されているメモリ量(KB)
  • ネットワークファイルシステムリモートホスト上のファイルにアクセス。windowsはcifs, UNIX系はnfs

8. ストレージデバイス

  • HDD
    • プラッタという磁気ディスク
    • セクタ単位で読み書きする
    • スイングアームという針のようなものが上下することでセクタを選び、先端の磁気ヘッドで読み書きする
    • ブロックデバイス:ランダムアクセス可能で、一定量(セクタ)ごとにアクセス可能なデバイスの事
      • 大体ファイルシステムを介してアクセス
      • 各種ブロックデバイスに共通の処理はカーネル内のブロックデバイス層で対応
        • I/Oスケジューラ:ブロックデバイス層にある機能。マージ(複数の連続セクタのI/O要求を1つにまとめる)、ソート(複数の不連続なセクタへのI/O要求をセクタ番号順に並び変える)を行う ‐ 先読み機能:ある領域にアクセスした時、それに続く領域も読み取ること。もし使われなければデータを捨てる
  • SSD
    • データへのアクセスに機械的な動作がなく、電気的な動作だけで済む→HDDより高速
    • I/O支援機能により速さが下がることも。複数のI/O要求をためておく処理のオーバーヘッドがSSDでは無視できないため ‐ point
      1. ファイル内のデータを連続する or 近い領域に配置する
      2. 連続する領域へのアクセスは、複数回に分けずにまとめる
      3. ファイルにはなるべく大きなサイズでシーケンシャルアクセスする

使ったコマンド💻

  • strace: プロセスが呼び出すシステムコールをみる
  • sar: プロセスがユーザーモードカーネルモードのどちらで実行しているのかみる
    • sar -P ALL 1(1秒ごとに見る) sar -P ALL 1 1(1秒ごとに1回測定)
    • ユーザーモード→%userと%niceの合計 ‐ カーネルモード→%system
    • sar -q 1 1: runq-szフィールド→実行中or実行待ちプロセス数
    • sar -r 1: 1秒ごとのメモリ状況。kbmemused->物理メモリ使用量、kbswpused->スワップ領域の使用量、kbpgtbl->ページテーブルに使用している量, kbcached->ページキャッシュの総量(kb)
    • sar -W 1: ``ごとのスラッシング確認、pswpin/sがスワップイン ‐ sar -d -p 1: ストレージデバイスに対するI/O量
  • ldd: プログラムがどのライブラリにリンクしているかみる
    • ldd /bin/echo ‐ readelf
    • readelf -h /bin/sleep: 開始アドレスを表示
  • /proc/pid/maps: プログラム実行時に作成されたプロセスのメモリマップ表示、仮想アドレスを表示
  • taskset: 指定の論理CPUで動作させる
    • task -c 0 ./sched
  • ps ax: 動かしているプロセスの確認
    • STAT: R->実行or実行待ち状態、S, D->スリープ状態(シグナルによって実行状態に戻るのがS), Z-> ゾンビ状態(親プロセスの終了待ち)
  • ps -eo pid,comm,etime,time: 経過時間(elapsed)と使用時間(time)
  • grep -c processor /proc/cpuinfo:論理CPUの数
  • timeコマンド:CPUの経過時間(real)と使用時間(user + sys,全コアの合計使用時間)がみれる ‐ time taskset -c 0 ./sched 1 10000 10000
  • nice -n 5 echo hello: 優先度5でecho helloする
  • free: memory容量確認(kバイト)
  • swap --show: システムのスワップ領域確認
  • cat /sys/kernel/mm/transparent_hugepage/enabled: トランスペアレントヒュー次ページの使用を確認
  • df: ファイルシステムのストレージ使用量
  • /proc/pid: 各プロセスの情報を得る
    • maps, cmdline, stat
    • /procにはその他ハード情報が盛りだくさん、詳しくはman procで ‐ /sys: procのようなカーネルのプロセスに関するもの以外の雑多な情報

prismaが生成するqueryをログに出す

prisma.service.tsを変更

import {
  INestApplication,
  Injectable,
  OnModuleInit,
  Logger,
} from '@nestjs/common';
import { PrismaClient, Prisma } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel> implements OnModuleInit
{
  private readonly logger = new Logger(PrismaService.name);
  constructor() {
    super({ log: ['query', 'info', 'warn', 'error'] });
  }
  async onModuleInit() {
    this.$on('query', (event) => {
      this.logger.log(
        `Query: ${event.query}`,
        `Params: ${event.params}`,
        `Duration: ${event.duration} ms`,
      );
    });
    this.$on('info', (event) => {
      this.logger.log(`message: ${event.message}`);
    });
    this.$on('error', (event) => {
      this.logger.log(`error: ${event.message}`);
    });
    this.$on('warn', (event) => {
      this.logger.log(`warn: ${event.message}`);
    });
    await this.$connect();
  }
  async onModuleDestroy() {
    await this.$disconnect()
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on('beforeExit', async () => {
      await app.close()
    })
  }
}

graqhQLのtutorialやってみたメモ📝

situation

最近NestJSとprisma, graphQLを使ったAPI構築を勉強していて、 いまいちprismaとgraphQLの繋がりがふわふぁっとしていたので、howtogrqphQLという公式のtutorialをやってみた。 やっぱり公式はわかりやすい。。。 リマインドのために、サイトの訳(By 翻訳サイト)と自分の所感の雑記

公式サイト

memo

TS関係

  • @ts-ignore コメントをすると、次の行の型チェックが無視される
  • index.jsindex.ts はnodeJSではデフォルトのエントリーポイントと見なされる、最初に読み込まれるよ
  • 実行コマンド:npx ts-node src/script.ts

graqhQLのschemaって?

  • GraphQL schemas はGraphQL Schema Definition Language (SDL)の中で定義される
  • それぞれのGraphQL schemaは3つのオリジナルのroot typeを持つよ: Query, Mutation, Subscription
  • GraphQLは5このデフォルトのscalar typesがあるよ: Int, Float, String, Boolean and ID

Nexusライブラリについて

  • type-safe GraphQL schemasをcode-first approachで生成するライブラリ
    • (´-`).。oO:graphQL schemaのtypeとかrootTypeをライブラリを使って書いて生成していくのがcode-first approachって言うのかしらね。逆はSDL firstって言うらしいから、これは直接SDLを書きこむんだろうなぁ。

Nexusを使って、graphQLのtypeやroot object typesを記述していく。 objectTypeはGraphQLの中で新しいtypeを作るために作られる。 ↓Userタイプの例

export const User = objectType({
    name: "User",
    definition(t) {
        t.nonNull.int("id");
        t.nonNull.string("name");
        t.nonNull.string("email");
    }
})

②コマンドnpx ts-node --transpile-only src/schemaでGraphQL SDL とtypesを生成する。-> typeとかQuery, Mutationは見た目的に完成

③追加されたフィールドに対応するresolverを実装する

resolverって?

  • resolverはGraphQLフィールドの実装です。各タイプのすべてのフィールド(ルートタイプを含む)は、そのフィールドに対応するデータを返す役割を持つresolverが用意されています。
    • (´-`).。oO: resolverでデータのCRUD処理を実際にするイメージ。 例えばLinkというTypeとfeedというQueryがあった場合
type Link {
  createdAt: DateTime!
  description: String!
  id: Int!
  postedBy: User
  url: String!
  voters: [User!]!
}
type Query {
  link(id: Int!): Link
}
export const LinkQuery = extendType({
  type: "Query",
  t.field("link", {
            type: "Link",
            args: {
                id: nonNull(intArg())
            },  // ここまでlinkという名前のqueryを定義
        // ここから実際にlinkQueryがどういう処理をするか定義(Prisma利用)
            resolve(parent, args, context, info){
                // console.log(args) //{ id: 1 }
                const { id } = args
                return context.prisma.findUnique({
                    where: {
                        id: id
                    }
                })
            }
        })
  },
  • GraphQLサーバーがすべきことは、クエリに含まれるフィールドに対してすべてのresolverを呼び出し、クエリの形状(この場合Link)に従ってレスポンスをパッケージ化することである。このように、クエリーの解決はresolverの呼び出しをオーケストレーションするプロセスに過ぎないのである。
  • (´-`).。oO: GraphQLのQueryは該当するresolverを呼び出して、うまくまとめて返してくれるのね
  • all GraphQL resolver functionsは常に4つの引き数を受け取る: parent, args, context, info
    • contextとは?: context 引数は JavaScript のプレーンなオブジェクトで、リゾルバーチェーン内のすべての リゾルバーが読み込みと書き込みを行うことができる。したがって、これは基本的にリゾルバが通信するための手段である
    • Prisma Clientをcontextにぶっこむと、resolver内からPrisma Clientにアクセスできます!
    • Prisma Clientには、データベースに対するクエリーを実行するために必要なものがすべて含まれています。
    • Prismaのクエリは、非同期なのでPromiseオブジェクトを返します。
    • parentのくだり

PrismaとgraphQLの関係

  • (´-`).。oO: GraphQLがこーゆー名前のqueryもしくはmutationの処理あるよ、それは何を返すよって、宣言して、実際そのデータのCRUD処理を行うのがresolverで、データベースを使うときにはprismaを使ってCRUD処理のやりとりをdbとしてるって感じ。
  • (´-`).。oO: 例えばこーゆタイプ(User)があるよ、その中でこういうリレーションがあるよって示しているのがSDLで、実際にそのリレーションのデータのやり取りをするのがresolveって感じ
  • objectType内でもrelationがあったらresolveする。↓
// in User.ts>User = objectType

t.nonNull.list.nonNull.field("links", {    // 1
            type: "Link",
            resolve(parent, args, context) {   // 2
                return context.prisma.user  // 3
                    .findUnique({ where: { id: parent.id } })
                    .links();
            },
        }); 
  • 上について公式曰く: リンクフィールドのリゾルバは自明ではないので、データベースから返されるUserオブジェクトが自動的にリンクタイプを含まないため、GraphQLは自動的にそれを推論することができません。このため、Userタイプの他のフィールドとは異なり、リンクのリゾルバを明示的に定義する必要があります。
  • 上のコードの.links()について:prisma.shcemaのUser modelに記述している
links     Link[]        @relation(name: "PostedBy")

のlinksのことを指しているっぽい

  • (´-`).。oO: prisma shemaに書いても、objectTypeに記述しないと、graphQLには反映されないんだなぁ
  • 多対多のrelationならそれぞれのobjectTypeにその旨記載

その他

  • idArg: string, intArg(): intで詰まる
  • [User!]!の意味:空のリストか、NULLでないUserオブジェクトのみを含むリストを受け取るの意
  • pagination: Prisma Client APIは、findManyクエリの追加オプションとしてskiptakeの引数を受け取り、それに応じてリンクレコードを返します。

参考できるもの

github.com