SYM's Tech Knowledge Index & Creation Records

「INPUT:OUTPUT=1:1以上」を掲げ構築する Tech Knowledge Stack and Index. by SYM@設計者足るため孤軍奮闘する IT Engineer.

はてなブログ投稿自動化 メモ

はてなブログ投稿自動化 メモ

hatena blog API (Atom)

Doc: https://kanaxx.hatenablog.jp/entry/hatena-entry-update

注意点

  • <updated>タグを入れないと、更新日がスクリプト実行日に変わってしまう。作成日も一緒に変わってしまう。
  • <category>タグを常に入れて送らないとカテゴリーがクリアされてしまう。
  • 送信するxmlデータに埋め込む記事本文はエスケープが必要 (エラーレスポンスの内容ではこれが原因だとすぐ分からない)。

サンプルソース群 (python)

  • 送信する記事データ(xml)作成
from datetime import datetime
from xml.sax.saxutils import escape

__BLOG_ENTRY_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:app="http://www.w3.org/2007/app">
  <title>{title}</title>
  <author><name>{author}</name></author>
  <content type="text/x-markdown">{content}</content>
  <updated>{update_time}</updated>
  <category term="{category}" />
  <app:control>
    <app:draft>{draft}</app:draft>
  </app:control>
</entry>"""

ENTRY_DATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"


def resolve_entry_current_time() -> str:
    return datetime.now().strftime(ENTRY_DATE_TIME_FORMAT)


def __replace_xml_escape(content: str) -> str:
    return escape(content)  # escape: <, &, >,


def build_hatena_blog_entry_xml_body(hatena_id: str, title: str, category: str, content: str,
                                     is_draft: bool = True) -> str:
    entry_xml = __BLOG_ENTRY_TEMPLATE.format(
        title=title,
        author=hatena_id,
        content=__replace_xml_escape(content),
        update_time=resolve_entry_current_time(),
        category=category,
        draft='yes' if is_draft else 'no'  # yes or no
    )
    return entry_xml
  • リクエストヘッダ生成(wsse使用)
import base64
import hashlib
import random
from datetime import datetime


class HatenaBlogApiExecutor:
    def __init__(self, blog_config: BlogConfig):
        self.__blog_conf = blog_config

    def build_request_header(self):
        def __build_wsse(blog_config: BlogConfig):
            user_name = blog_config.hatena_id
            api_key = blog_config.api_key
            created_time = datetime.now().isoformat() + "Z"
            b_nonce = hashlib.sha1(str(random.random()).encode()).digest()
            b_password_digest = hashlib.sha1(b_nonce + created_time.encode() + api_key.encode()).digest()
            wsse = f'UsernameToken Username={user_name}, ' +
            f'PasswordDigest={base64.b64encode(b_password_digest).decode()}, ' +
            f'Nonce={base64.b64encode(b_nonce).decode()}, ' +
            f'Created={created_time}'

        return wsse

    return {
        'X-WSSE': __build_wsse(self.__blog_conf)
    }
  • レスポンス解析ソース (xmlから欲しい情報だけ抜き出し ※突貫コード)
import xml.etree.ElementTree as ET
from datetime import datetime
from typing import List, Optional


# for debug
def print_xml_children(root: ET.Element):
    """
    for debug
    """
    for child in root:
        print(child.tag)


def __get_tag_head(root: ET.Element, root_tag: str = 'feed') -> str:
    tag_head = root.tag[:-len(root_tag)]  # tag example: {http://www.w3.org/2005/Atom}feed
    return tag_head


def get_next_page_url(xml_string: str) -> Optional[str]:
    url = None
    root = ET.fromstring(xml_string)
    for link in root.iter(__get_tag_head(root) + 'link'):
        if link.attrib['rel'] == 'next':
            url = link.attrib['href']
            break
    return url


def parse_blog_entry_xml(xml_string_opt: str) -> Optional[BlogEntry]:
    if xml_string_opt is None:
        return None
    root = ET.fromstring(xml_string_opt)
    tag_head = __get_tag_head(root, 'entry')
    return __parse_blog_entry_xml(root, tag_head, [])


def __parse_blog_entry_xml(entry_node: ET.Element, tag_head: str, exclude_ids: List[str]) -> Optional[BlogEntry]:
    # id example: tag:blog.hatena.ne.jp,2013:blog-Sympathia-17680117126980108518-13574176438048806685
    # entry id is last sequence
    entry_id = entry_node.find(tag_head + 'id').text.rsplit('-', 1)[1]
    if entry_id in exclude_ids:
        return None

    title = entry_node.find(tag_head + 'title').text
    content = ''
    for cont in entry_node.iter(tag_head + 'content'):
        if cont.attrib['type'] == 'text/x-markdown':
            content = cont.text
            break

    updated_opt = entry_node.find(tag_head + 'updated')
    last_update_time = None
    if updated_opt is not None:
        # format: 2013-09-02T11:28:23+09:00
        last_update_time = datetime.strptime(updated_opt.text, "%Y-%m-%dT%H:%M:%S%z")
    app_edited_opt = entry_node.find('{http://www.w3.org/2007/app}edited')  # app:edited
    if app_edited_opt is not None:
        # format: 2013-09-02T11:28:23+09:00
        app_edited_time = datetime.strptime(app_edited_opt.text, "%Y-%m-%dT%H:%M:%S%z")
        if last_update_time < app_edited_time:
            last_update_time = app_edited_time

    url_link = ''
    for link in entry_node.iter(tag_head + 'link'):
        if link.attrib['rel'] == 'alternate':
            url_link = link.attrib['href']
            break

    categories = []
    for category in entry_node.iter(tag_head + 'category'):
        categories.append(category.attrib['term'])
    return BlogEntry(entry_id, title, content, url_link, last_update_time, categories)

ref (参考にしたもの)

はてなサービスにおけるWSSE認証

pythonでwsse認証を用いて、はてなブログにエントリーを投稿する

はてなブログ、フォトライフのAPIを使って投稿を自動化する

WordPressの記事をはてなブログに自動で連携【AtomPubを使う】

マイクロサービスアーキテクチャ メモ

マイクロサービスアーキテクチャ メモ

アーキテクチャは組織に引きづられる(コンウェイの法則)

  • それを解決する術がマイクロサービスアーキテクチャ
  • 分ける粒度はドメイン
  • 他サービスとの連携に、ドメインイベント、イベントソーシング等が活躍すると思われる
  • システムを設計するあらゆる組織がその組織構造に習った構造を持つ設計を生み出す。システム設計が組織を変える(逆コンウェイの法則)

比較

マイクロサービス&モノリス

モノリス

  • 概要
    • モノリシック(一枚岩)なアプリケーション
    • 必要なコンポーネントが全て1つのアプリケーションに含まれるような構成
  • 利点
    • 開発しやすい
    • 大きな変更を加えやすい
    • テストしやすい
    • デプロイしやすい
    • 障害調査しやすい
  • 欠点/課題
    • 時間と共に肥大化、それに伴い…
    • 修正の影響範囲の特定が難しい
    • デプロイまでに時間がかかる(コンパイル等)
    • リリース時の調整コスト大
    • リソース効率が悪い
    • 信頼性が低い(システム全体に影響)
    • 技術スタックが陳腐化する

マイクロサービス

  • 概要
    • アプリケーションの構成要素を独立したサービス群に分割、それらを連携させ1つのアプリケーションとして組み立てる
    • 直さないものを小さく分割しても意味がない
    • 必要な機能を必要なサイズに分割するのが大事
    • 分割はデータごと分割する
  • 利点
    • 修正範囲がサービスに限定される(そのように分割されていれば)
    • CI/CDが必要なテストが容易になる
    • 素早くリリースできる
    • サービスに合わせたスケールができる
    • 障害分離しやすい
    • 新しい技術を試しやすい
  • 欠点/課題
    • サービスの適切な分割範囲を見極めるのが難しい
    • アプリケーションの設計が複雑になる
    • 複数のサービスを同時に修正しないと実現できないような機能の場合、デプロイ/リリースの調整要
    • アプリケーション全体のテストが難しくなる

マイクロサービス & SOA

SOA(Service Oriented Architecture): サービス指向設計

システムの機能単位で決めていく設計(業務分析必須)

|SOA マイクロサービス
アーキテクチャ リソース共有(同じサーバ)|独立したサービス(サーバも複数)
コンポーネント共有 共有する|共有しない
サービス粒度 比較的大きい|非常に小さい
サービス間通信 ESB(Enterprise Service Bus)|メッセージブローカー
通信プロトコル SOAP|REST/gRPC

マイクロサービス化する時の設計面の課題

  • サービス間通信
  • 外部公開
  • アプリ設計
  • データ整合性
  • セキュリティ
  • テスト
  • デプロイ/リリース
  • CI/CDパイプライン
  • サービス正常性確認
  • 可観測性(障害発生時の原因特定/対処)

サービス間通信

連携方法一覧

- 1対1 1対多
同期通信 REST/gRPC 無し
非同期通信 バッチ メッセージブローカー/DB共有
REST
  • 一般的、どの言語でもサポート
  • 1リクエストで複数リソース操作できない
  • バッファリングなしに直接接続するため可用性が下がる
gRPC
  • HTTP/2 を利用したバイナリベースのプロトコル(.protoファイルにIF定義)
  • バイナリ+HTTP/2で高速通信
  • (通信がバイナリのため)監視が難しい
  • 対応していない言語も一部あり(swift)
  • 詳細&実装例:

参考

バッチ
  • 定量の処理を非同期でまとめて実行
  • 頻度を上げると負荷増(実行タイミングは負荷低の時に限定される)
  • 反映までに時間がかかる
メッセージブローカー
  • 非同期にメッセージを交換する仕組み。確実に後続サービスに通知
  • 双方が疎結合になる
  • メッセージのバッファリングができる(瞬間的に大量のアクセスがある場合に有効)
  • (メッセージブローカーで問題が起きると)パフォーマンスのボトルネック/単一障害点になる懸念
  • 運用複雑度の上昇
  • 方式は以下2つ
- 利点 欠点
キュー方式(Pull方式) 処理量が多くても対応可能(=バッチ処理に対応可能) 多少タイムラグが出る
Pub/Sub方式(Push方式) 軽い処理に向く(リアルタイムに近づけられる) Consumer側(Sub側)のリソースを考慮する必要がある
DB共有
  • Viewやテーブルを公開してデータ連携
  • 大量のデータを移す必要がない
  • 反映したデータが即時扱える
  • データ変更を伴う修正の影響範囲が広がる
各々の使い所の尺度
  • データ量: DB共有/ファイル連携 > 非同期API > 同期API
  • 鮮度: DB共有 > 同期API > 非同期API > ファイル連携
課題

サービス分割により、

  • 直列で多段になったきのうは稼働率が下がる
    • 対策:同期処理が不要な機能は切り離す(メッセージブローカーで切り離す)
  • 可用性が下がる(経路上のどこかのサービスが停止するとタイムアウトまで待たされる=ユーザビリティの著しい低下)
    • 対策:一定回数通信に失敗した際に、遮断する仕組みを入れる(サーキットブレーカー)
  • サービスの負荷状況に応じて、インスタンス(サーバ)が動的に生成・破棄される = アクセス先のIPアドレス/ポート番号が分からない
    • 対策:オートスケール+ロードバランサーもありだが、マイクロサービスの場合は接続先を管理する仕組み(サービスディスカバリ)を導入する
サーキットブレイカ

サービス同士が接続しあい、複雑なネットワークを構成している場合に、局所的な障害がシステム全体に波及させない仕組み。 ブレーカー(Breaker)を落とすようにネットワーク上の回路(Circuit)を遮断する

カスケード障害(ネットワークの一部の障害が要因となり,連鎖的に障害がシステム全体へ波及する現象)を起さないための手段

参考

サービスディスカバリ

中央にサービスレジストリというアクセス先をまとめるものを持つ。インスタンス(サーバ) が立ち上がった時にサービスレジストリに自身のIPを登録。クライアントはサービスレジストリに対して、アクセスしたいサービスを元に問い合わせし、IPアドレスをもらってそれでサーバにアクセスする

参考

外部公開

課題

  • アクセス先のエンドポイントが複数になる(どのアドレスを呼び出せばいいか不明)
  • 認証認可をそれぞれ実装すると冗長になる
APIゲートウェイ
  • 入口を一元化

リバースプロキシの役割を担い、リクエストを各APIに振り分ける。公開点を限定するためセキュリティ向上にも繋がる

代表:Kong

データ整合性

課題

  • DBが分散した状態でシステム全体としてデータ整合性を担保する必要がある (2層コミットは通信が同期で可用性が下がるためマイクロサービスではNG)
    • 対策:サーガ を使って担保
  • 複数サービスに分割されたデータを結合する必要がある
    • 対策:API Composition
  • (API Compositionの場合) 1つのサービスで大量データを収集・結合するとオーバーヘッドが高くなる(大量データ読み取り時)
    • 対策:CQRS
サーガ

Sagaパターンについて

API Composition

ある1つのサービスが中心となってデータを収集・結合する。

ただし、分かりやすい反面、以下の欠点有

  • (1サービスに責任集中&結合したデータが必要なサービスが複数あればアクセス集中するため) オーバーヘッドが高くなる
  • (API Compositionを担うサービスがダウンしたら、結合したデータを必要とする他サービスにも影響が出るため) 可用性が下がる
CQRS

コマンドクエリ責任分離(Command & Query Responsibility Segregation)

  • 書き込み側はORM等で従来のやり方
  • 読み込み側はクエリ実行で必要なデータを取れる状態をあらかじめ作っておく(必要なデータをAPI通信/メッセージブローカーを使ってあらかじめ取得/保持しておく)

セキュリティ

  • 認証:人の特定
  • 認可:アクセス制御

OAuth/OpenID Connect:認可の仕組み

OAuthはサービスが持つ各APIにアクセス許可、OpenID Connectはユーザ情報特化のイメージ。

OpenID Connect = OAuth 2.0 + Identity Layer(ID Token + UserInfo API)

OpenID Connect の場合はアクセストークンに加えIDトークン(=JWT)も受け取る

参考:


マイクロサービスの場合

認可サーバを用意して、

  • アクセスがあればAPIゲートウェイがクライアントとなり認可サーバからユーザ情報とスコープを取得
  • ユーザ情報とスコープをHTTPヘッダ等に埋め込み各サービスにアクセス。各サービスは埋め込まれた情報を元に実行してよいかを判断

テスト

  • 単体テスト:サービス内のクラスに対する自動テスト
  • 結合テスト:依存関係のあるサービスのみを結合したテスト
  • 受入テスト:関連サービスすべてを結合したテスト/受け入れテスト
  • E2Eテスト:すべてのサービスを連携させEnd to Endで動作確認

課題

  • モック/スタブが大量に必要
    • 対策:ツールの利用(例:OpenAPI、Mockito、nockなど)
  • テストデータ洗い替えが頻繁に必要
    • コンテナの利用(例:Docker、cri-o、OpenShiftなど)
  • リリース物が増えると手動ではやりきれない
    • CI/CDの導入(Jenkins、CircleCIなど)

参考:

ビルド/デプロイ/リリース

  • ビルド:ソースコードを配布可能/実行可能なファイルに変換
  • デプロイ:システムを利用可能な状態にする
  • リリース:サービスとして公開する

デプロイ方法

デプロイ方法 概要 アーティファクトに含むもの 利点 欠点
アーティファクト配置 資材を物理マシンや仮想マシンへ配置する アプリ/データ アプリ開発チームにとっては単純 インフラ管理が大変
VMイメージ 資材配置が終わった仮想マシンのイメージ作成/展開/起動 アプリ/データ/ミドル/ランタイム/OS インフラがイミュータブルにできる 仮想イメージを使ってサーバを作るのは重い
コンテナ 資材配置が終わったコンテナを作成/起動 アプリ/データ/ミドル/ランタイム OS部分が共通化でき、軽くなる ミドル/ランタイムが新しい物が出ると作り直す必要有
サーバーレス クラウド事業者が提供する環境に直接資材を投入 アプリ/データ 管理範囲を減らせる(クラウド事業者に委譲)
サーバーレス クラウド事業者が提供する環境に直接資材を投入 アプリ/データ 管理範囲を減らせる(クラウド事業者に委譲)

下の物ほど、調整事を減らせ、リリースまでのリードタイムを減らせる

リリース手法

方法 デプロイ先 ダウンタイム 特徴
サービス停止&デプロイ 稼働中サーバ 閉塞期間 ユーザが利用できない時間が発生する
ブルー/グリーンデプロイ 新規サーバ ほぼゼロ サービス停止せず、リリース後は新しい環境を使う。切り戻しが容易
イミュータブルデプロイ 新規サーバ ほぼゼロ 上記と同じだが、元のサーバは削除する。切り戻しする際は、古いバージョンをリリースしなおす
ローリングデプロイ 新規サーバ ゼロ 数台ずつイミュータブルデプロイ

サービスメッシュ

横断的機能(例:サーキットブレ―カー、認証認可、通信暗号化、モニタリング)はサービスとは別に外付け機能として実装 ⇒ サービスメッシュ

マイクロサービスごとにプロキシを配置し、プロキシを経由して他のサービスと通信させることでサービスの依存関係やトラフィックを制御する仕組み

参考:

代表:

コンテナオーケストレーション

課題:

  • サービスが増えたことでインフラ管理する対象も増えるため、増えたものを効率よく管理する必要がある

対策

  • 1パッケージにおさめる(カプセル化)=コンテナ化
  • 複数のコンテナを統合的に管理できるツールを活用(コンテナオーケストレーション=デプロイ制御ができる)

代表

CI/CDパイプライン

目的:リリースの頻度を上げる

CI(Continuous Integration):実装 > コミット > マージ > 自動ビルド > 自動テスト CD(Continuous Delivery): 上記 > デプロイ > リリース

運用/監視

ヘルスチェック

課題:各サービスの正常動作を判断する必要がある (例:GET /health /hc /healthz)

対策:稼働状態を返す専用のAPIを公開する

  • ステータスコードで判定できるようにする
  • レスポンスボディを付ける場合は詳細情報
  • 必要ならDBへアクセスできるかもチェック

可観測性 (Observability)

課題:障害発生箇所の分析や障害原因の特定が難しい

対策:複雑なシステムを横断的に監視できるようにする

手段 概要 補足 ツール
ログ収集 各サービスで発生するイベントの記録 ログは1箇所に収集/集約。ログ送信はサービスが多い場合バッファリングも検討 Elasticsearch Kibana/Fluentd
メトリクス 各サービスの定量数値情報の記録(増減の確認) モニタリングとアラートのために収集。収集自体はログ収集と同じやり方
分散トレーシング サービスが呼ばれた順序やその時の状態を分析/可視化 Trace ID/Span ID/Parent Span IDを保存 Zipkin/Jaeger/AWSX-Ray

利用用途(要件)に合わせたツールを選定

  • KPI分析
  • 監視/アラート etc.

マイクロサービスへの移行

段階的に進めるべき。

ポイント

  • アセスメント
  • 組織/体制の変革
  • 段階的な分割/再構築
  • 共通の仕組み作り

アセスメント

目的をはっきりさせる

  • What:事業の目的や目標は何か
  • 目的や目標の達成手段としてマイクロサービスが適切か

新規事業よりもある程度成熟しドメイン境界がはっきりした事業に向く(モノリシックでは効率が悪くなってきたときにマイクロサービスへの移行を考えるもの)

組織/体制の変革

レビンの変革プロセス

  • 解答:関係者に働きかけ変革の必要性を関係者に理解してもらう
  • 変革:目指す姿の共有/アジャイル体制へ変更(適宜改善)
  • 再凍結:共有化された新たな開発手法の定着

ジョン・コッターのリーダーシップ論に学ぶ『変革時代で生き残るためには?』(変革へ至るための8つプロセス)

段階的な分割/再構築

  • ドメインモデル作成: サービス境界及び優先順を定義するため高レベルのドメインモデル作成
  • 優先順位付け:分解による利益と分解しやすさを軸に決める
  • チーム再編成/システム再編成:分解するサービスに合わせてチーム再編成/チームで必要とするスキル見直し/再教育
  • 効果測定:チェックポイントを設けて定量/定性評価
    • 定量:リリースまでの日数/デプロイ数/障害率
    • 定性:フィードバック

分割方法

アプリ分割

方法 メリット デメリット
プロキシを利用した分割 モノリスに対する修正が減らせる URL単位である程度分割できないと利用できない
I/F定義を利用した分割 複雑化したコードでも分割できる モノリスに対する修正要、リリース調整要
  • プロキシを利用した分割
    • プロキシの挿入(最初は素通り)
    • 新サービス実装(最初はAPIだけの中身がないもの、デプロイプロセスに慣れつつ、徐々に機能実装)
    • カナリアリリース検討
    • 切り替え
  • I/F定義を利用した分割
    • ドメインモデル毎に処理を分割
    • サービス化したい処理をI/F定義(切り替えフラグを差し込んでおく)
    • 新サービス実装
    • 後始末(使わないコードは削除)

データ分割

データベースの分割

物理分割:DBサーバを分割

  • サーバを物理的に分け障害時の影響を下げる
  • DBの種類によってはライセンス費がかかる可能性有り(利用するDBの見直しも検討)

論理分割:DBサーバは共有し、スキーマを分割

  • スキーマを分けることでデータ主管をサービスに紐づけ
  • DBサーバ1つに対して接続元が増えるためコネクション数に注意が必要
分割方法
方法 メリット デメリット
データから分割 データに対して分割に問題がないか確認しやすい マイクロサービスの効果を体感しづらい
アプリから分割 マイクロサービス化の開発プロセスやメリットを体感しやすい 意識的にデータ分割まで行わないと潜在リスクを抱えたままになる
アプリとデータ同時分割 マイクロサービスとしての理想形にいきなり到達可能 修正コード量が増えるためマイクロサービスの効果を体感するまで時間を要する

直感

  • マイクロサービスの恩恵への理解がなければ、アプリから分割
  • 恩恵への理解はあるが慣れがなければ、データから分割
  • 恩恵も慣れもあれば一気に、アプリとデータ同時分割

※プロダクトの特性も考慮して考える必要はあるはず

データから分割
  • スキーマ分割
    • 新しいスキーマへは書き込むだけ
      • 既存には新しいスキーマへの書き込み処理実装
      • このタイミングでデータに整合性の問題がないか等チェック
  • 真とするデータの切り替え(旧スキーマの参照は許容)
    • データ同期や整合性に対する処理が必要になるため実装
      • データ同期:DB共有(Viewの共有)、同期/非同期API、ファイル教諭等検討
      • データ整合性:サーガ、CQRSの利用等検討
  • アプリ分割
    • 新サービスを作成(関連する操作を全て新サービスで行うよう切り替え)
アプリから分割
アプリとデータ同時分割
  • アプリとデータを同時に分割
    • 最初は新スキーマには書き込みのみ
    • スキーマをメインに読み書き実施(これまで通り)
  • 真とするデータの切り替え

共通の仕組み作り

  • DevOpsツールの導入:ソースコード管理、CI/CD、静的解析、単体テスト、メトリクス、分散トレースツールなど
  • 共通機能の導入:APIゲートウェイ、メッセージブローカー、サービスメッシュなど
  • 開発標準の準備:ドキュメント更新ツール、共通利用するツールの使い方、ログ出力のルールなど

ルール/仕組みがある上で初めて自由が許される世界

その他

GraphQLについてまとめる

GraphQLについてまとめる

概要

目的を一言で表現するならば

  • 1つのエンドポイントでシステムが管理するリソースから欲しい情報のみを柔軟かつ正確に取得できるようにする

特徴

  • サーバから必要なデータを必要な分だけ、過不足なく(指定した物だけを)取得する
  • 1度のリクエストで(関連のある)複数リソースのデータを取得可能
  • 1 EndPoint。Queryで指定

以下、REST API の課題を解消する狙い

  • 過剰なデータ取得(使わない属性も取得する)
  • 過少なデータ取得(ネストした情報を取るには複数エンドポイントから取得要)
  • (拡張時に) End point の追加が必要になる

(GraphQLドキュメントより翻訳)

GraphQLはRESTに代わる、より効率的で強力かつ柔軟な代替手段を提供する新しいAPI標準です。GraphQLは、そのコアで、クライアントがAPIから必要なデータを正確に指定できる宣言型データフェッチを可能にします。固定データ構造を返す複数のエンドポイントの代わりに、GraphQLサーバーは単一のエンドポイントのみを公開し、クライアントが要求したデータで正確に応答します。

REST API vs GraphQL

REST API:リソースベース

  • メリット
    • リソースが容易に分かる
    • シンプルで一貫性のある設計 (URIと情報の操作が対応する)
  • デメリット
    • リソース毎にエンドポイントを提供、固定データ構造を返却
      • クライアントが必要としていないデータも返却
      • 必要なデータを揃えるために複数エンドポイントの結果を組み合わせが必要なケース発生
      • 必要な関連データも一括で返すようにするとレスポンス肥大化
    • 融通が利かないため柔軟に要求に対応不可
      • 無理に対応すれば上記に繋がり、余分にリソースを消費、レスポンス遅延等に繋がりかねない

GraphQL:ユースケースベース(でqueryを定義するのが良いとされている)

  • メリット
    • 少ないリクエストで複数のリソースにアクセス可能
    • 最小限のデータのみを取得
    • 既存のクエリを破壊せずAPIの更新が可能 (要求に柔軟に対応可能)
  • デメリット
    • キャッシュが複雑になる
    • クエリーの自由度の高さ故に負荷予測や対策がし辛い
    • クエリのパース処理の実装コスト&難易度は高いためライブラリ依存になる
    • 画像や動画などの大容量バイナリの扱いが難しい

GraphQLが真価を発揮するケース

間違いなく言えるのは、管理しているデータが網目状に関連を持ち、(ユースケース的に)色々なルートでその関連を辿る必要がある場合

分かりやすい例:

これをREST APIで対応しようとすると

  • APIでまとめて関連データも返そうとするとレスポンス肥大化
  • APIで対象リソースの関連データについてはIDのみ返すようにしても1ユースケースを満たすために複数API実行

となり、RESTの悪い点を踏む

RESTを使った方が良い時(個人の所感)

「GraphQLの方が色んな面で優れているため、RESTよりGraphQLを使った方が良い」とはならないケースはあるはず。

REST APIの優位性は、シンプルさと統一性(HTTPメソッドとリソースに対応するURI)。

GraphQLサーバ と REST API サーバ の利用者目線(利用者≠フロントエンド。単純にAPIを打つ人という位置づけ)で考えた時に、

GraphQL は、必ずクエリを指定し、対象のリソースや欲しい情報を指定しなければならない。つまり、利用者にその負担を強いる(REST API も更新系は概ねリクエストボディで情報を送る必要があるため手間はそこまで変わらないかもしれないが、参照系はそうでもない)。

複数のサービスから、1リソースの情報を取得しようとした時にどちらが扱いやすいかを考えるとREST API に軍配が上がるだろう(GraphQLだと、複数サービスで同じユースケース(とあるリソース情報を取得する)があれば、複数サービスで同じクエリを持つことになる)。

ただ、RESTでは要求に柔軟に対応することも、リソース間の関連が多いようなデータを扱うのは不得意なため

  • リソース間の関連がさほど多くなく
  • あまり変更が入らないようなデータ

は、REST API の方が良いのではないか。DBで管理するマスターデータ、トランザクションデータのうちの、マスターデータにあたるような物は、データ構造次第ではREST APIの方が扱いやすい可能性があると思われる。

Relay Server Specification

GraphQL拡張仕様

  • Relay Node (Globally unique ID)
  • Relay Connection (Edge, Node, cursor based pagination)
  • Relay Mutation

https://relay.dev/docs/guides/graphql-server-specification/

GraphQLが果たす役割

Todo

GraphQL Client & Server

Client

  • サーバへのクエリの実行とレスポンスの確認 Server
  • クエリに基づいてデータを返す
    • イメージ的には、DBからデータを取得する際には SELECT はせず、アプリケーション側でSELECTすることで指定されたものだけを返すイメージ

Client と Server どちらも用意できるのが Apollo

Apollo Server は、REST APIをデータソースとすることもできるため、Apollo Serverをプロキシとし、GraphQLのインターフェースを提供することができる。つまり、段階的移行が可能である。

Apollo ServerとPrismaではじめるGraphQL API開発入門

Apollo なら爆速で GraphQL サーバーと GraphQL クライアントアプリが作れる

N+1問題への対応

GraphQL で N+1 問題を解決する 4 つのアプローチ

React 副作用/メモ化/レデューサー (TypeScriptコード例付)

React 副作用/メモ化/レデューサー (TypeScriptコード例付)

副作用/メモ化/レデューサー

副作用:描画の一部ではない処理。UI構築に関する以外の処理

レデューサー:同じ引数の場合、必ず同じ戻り値を返さなければならない

  • useReducer:ステート更新ロジックの抽出、複雑なステート管理

メモ化

  • useMemo:パフォーマンス改善のための計算結果をキャッシュ、値のメモ化値
    • 依存配列の値が変わった場合のみ渡された関数実行。
  • useCallback:パフォーマンス改善、関数のメモ化
  • memo: 関数コンポーネントのメモ化(コンポーネントのパフォーマンス改善)

※なんでもかんでもメモ化すればいい訳ではない。メモ化にもコストはかかる

Reactは初めからパフォーマンスを年頭に置いて設計されている。

  • パフォーマンスチューニングを実施するにはゴール設定が重要
  • 静的な方法ではなく、実際に実行し、描画がもたついたり止まったりする箇所が何に時間を要しているかを確認して対処

レンダリング戦略

  1. stateを持つ位置を工夫し、変更される範囲を限定する
  2. memo等でレンダリングをおさえる

ref:

useEffect

export const CheckBox: VFC = () => {
  const [checked, setChecked] = useState<boolean>(false);

  useEffect(() => {
    alert(`checked: ${checked.toString()}`);
  });

  // alert(`checked: ${checked.toString()}`); // OK押下されるまで↓の処理が実行されない

  return (
    <>
      <input
        type="checkbox" checked={checked}
        onChange={() => setChecked(checked => !checked)}
      />
      {checked ? "checked" : "non checked"}
    </>
  );
}

第二引数の配列:依存配列

依存配列

副作用が実行される条件を指定(設定した値が更新された時に実行されるようになる)

const [value, setValue] = useState("");

useEffect(() => {
  console.log(`typing ${value}`)
});
const [value, setValue] = useState("");

useEffect(() => {
  console.log(`typing ${value}`)
}, []);
  • 依存配列:値有り -> 値更新時のみ実行(※)
const [value, setValue] = useState("");

useEffect(() => {
  console.log(`typing ${value}`)
}, [value]);

(※)同一性チェックで値が同じか判定している

  • プリミティブ値(string, number, boolean):値が同じかどうかで評価
  • 配列、オブジェクト、関数:参照同一性で評価するため常にfalseに
const a = "test";
const b = "test";
console.log(a === b); // true

const arr1 = [1,2,3];
const arr2 = [1,2,3];
console.log(arr1 === arr2); // false

依存配列に、配列、オブジェクト、関数、を指定した場合は、コンポーネント再描画時に(これらのインスタンスは再生成され、非同一と判定されるため)useEffectが常時実行されてしまう。

それを防ぎ、かつ中身が変わった時のみ実行できる手段

  • useMemo:配列、オブジェクト
  • useCallback:関数

useMemo/useCallback

ref: React.memo / useCallback / useMemo の使い方、使い所を理解してパフォーマンス最適化をする

useLayoutEffect

例:

export const useWindowSize = () => {
  const [width, setWidth] = useState<number>();
  const [height, setHeight] = useState<number>();

  const resize = () => {
    setWidth(window.innerWidth);
    setHeight(window.innerHeight);
  }

  useLayoutEffect(() => {
    window.addEventListener("resize", resize);
    return () => window.removeEventListener("resize", resize);
  }, []);

  return [width, height];
}
  • マウス座標の追跡
export const useMousePosition = () => {
  const [x, setX] = useState<number>(0);
  const [y, setY] = useState<number>(0);

  type SetPositionInput = { x: number, y: number };
  const setPosition = ({ x, y }: SetPositionInput) => {
    setX(x);
    setY(y);
  }

  useLayoutEffect(() => {
    window.addEventListener("mousemove", setPosition)
    return () => window.removeEventListener("mousemove", setPosition);
  }, []);

  return [x, y]
}

useReducer

ステート更新のロジックを抽象化可能

export const CheckBox: VFC = () => {
  const [checked, toggle] = useReducer(checked => !checked, false);

  return (
    <>
      <input
        type="checkbox" checked={checked}
        onChange={toggle}
      />
      {checked ? "checked" : "non checked"}
    </>
  );
}
  • 複数値を包含するステート値の部分更新
const firstUser = {
  id: "",
  firstName: "SYM",
  lastName: "THY",
  city: "Tokyo",
  state: "Japan",
  admin: false,
}

type UserData = typeof firstUser;

export const User: VFC = () => {
  const [user, setUser] = useReducer((user: UserData, newDetails: Partial<UserData>) =>
    ({ ...user, ...newDetails }), firstUser);

  // Reducer を使わずにやろうとすると以下にする必要がでてくる
  //const onClick = () => setUser({ ...user, admin: true })
  const onClick = () => setUser({ admin: true });

  return (
    <div>
      <h1>{user.firstName}{user.lastName} - {user.admin ? "Admin" : "User"}</h1>
      <p>Location: {user.city}, {user.state}</p>
      <button onClick={onClick}>Make Admin</button>
    </div>
  );
}

memo関数

メモ化したコンポーネントは、プロパティが変更されない限り再描画されない

  • プロパティが関数の場合は(依存配列と同じ理屈で)毎回描画されてしまう。
  • 第2引数に条件指定することで回避可能

第2引数がfalseの時のみ実行される

const OnceRenderCat = memo(Cat, () => true); // 初回のみ描画
const AlwaysRenderCat = memo(Cat, () => false); // 毎回描画
export const App = () => {
  const [cats, setCats] = useReducer(
    (cats: string[], newCats: string[]) => [...cats, ...newCats], hadCats
  );

  const onAddCat = (name: string) => {
    setCats(name ? [name] : []);
  };

  return (
    <>
      {cats.map((name, i) => {
        <PureCat key={i} name={name} meow={() => console.log(`${name} meowed`)} />
      })}
      <AddCatForm addCat={onAddCat} />
    </>
  );
}
type CatProps = {
  name: string,
  meow: (name: string) => void
}

const Cat: VFC<CatProps> = ({ name, meow = fn => fn }) => {
  console.log(`rendering cat: ${name}`);
  return <p onClick={() => meow(name)}>cat: {name}</p>
};

export const PureCat = memo(
  Cat,
  (prevProps, nextProps) => prevProps.name === nextProps.name
);
type AddCatFormProps = {
  addCat: (name: string) => void
}

export const AddCatForm: VFC<AddCatFormProps> = ({ addCat }) => {
  const [name, setName] = useState<string>("");

  const onSubmit: (event: React.FormEvent<HTMLFormElement>) => void = event => {
    event.preventDefault();
    addCat(name);
    setName("");
  }

  const onChange: (event: React.ChangeEvent<HTMLInputElement>) => void = event => {
    setName(event.target.value);
  }

  return (
    <form>
      <input
        value={name} onChange={onChange}
        type="text" placeholder="input name..." required
      />
      <button>Add Cat</button>
    </form>
  )
}

React 基本概念等

React 基本概念等

コンセプト

  • Declarative(宣言的)
    • 宣言的プログラミング:出力の性質やあるべき状態を記述してプログラムを構成する。関数型プログラミング含む。
  • Component-Based(コンポーネントベース)

  • Just The UI(UI にしか関知しない)

  • Virtual DOM(仮想DOM)

  • One-Way Dataflow(単方向データフロー)

  • Learn Once, Write Anywhere(ひとたび習得すれば、あらゆるプラットフォームで開発できる)

stateのリストアップ

子に親のstateを更新する関数を譲渡、子の変更=直接親のstate更新。(子→親の向けなので)リフトアップ

例:フォームを実装

コンポーネントとprops

props = properties。{ 属性名: 属性値 } の形式。

関数コンポーネント推奨。

コンポーネントライフサイクル

  1. Mounting フェーズ: コンポーネント初期化、仮想DOMにマウントされるまでのフェーズ。このフェーズで初めてコンポーネントレンダリングされる
  2. Updating フェーズ: 差分検出処理エンジンが変更を検知してコンポーネントが再レンダリングされるフェーズ
  3. Unmounting フェーズ: コンポーネントが仮想DOMから削除されるフェーズ
  4. Error Handling フェーズ: 子孫コンポーネントのエラーを検知、捕捉するフェーズ

コンポーネントライフサイクル図

※ 16.3以降は componentWillMount, componentWillReceiveProps, componentWillUpdate 非推奨(いずれ完全削除予定

Presentational Component / Container Component

Presentational Component はスタイルガイドと共存させやすく (スタイルガイドに登録することでデザインの運用に活用できてるため) 再利用性が高まる

公式が推奨するコンポーネントの正しい作り方

  1. デザインモックを作成、そのUIをコンポーネントの構造に分解 (Presentational)
  2. (ロジックを除外した) 静的に動作するバージョンを作成 (Presentational)
  3. UI を表現するために最低限必要な「状態」を特定 (Container)
  4. 3 の「状態」の配置場所を決定 (Container)
  5. (階層構造を逆のぼって考え) データが上階層から下階層に流れるようにする (Container)
観点 Presentational Container
関心点 どのように見えるか どのように機能するか
マークアップ 内部にDOMマークアップを多く持つ DOMマークアップを可能な限り持たない
データ/振舞い propsとして一方的に受け取る 他のコンポーネントに受け渡す
Flux依存度 (Fluxの)store等に依存しない (Fluxの)actionを実行したり、storeに依存する
状態 自身の状態を持たない(UIの状態は持つ) データの状態を持つ
データ更新 データの変更に介入しない データの変更に介入し任意の処理を行う
実装 関数コンポーネントで表現されることが多い (関数コンポーネントでも可能だが)HOC、Render Props、Hooksを使うことが多い

Hooks (関数コンポーネント合体強化パーツ)

HOC:高階関数コンポーネント (High Order Component)

type Props = { target: string };
const HelloComponent: FC<Props> = ({ target }) => <h1>Hello {target}!</h1>;
export default withTarget(HelloComponent);

Render Props:レンダリングのための関数をprops として受け取る

type Props = { target: string };
const HelloComponent: FC<Props> = ({ target }) => <h1>Hello {target}!</h1>;
<TargetProvider render={HelloComponent} />
const TargetProvider: FC<{ render: FC<Props> }> = ({ render }) => render({ target: 'Patty' });

HOC、render propsの問題

  • 共通して抱えていた問題は、ロジックの追加が著しくコンポーネントツリーを汚染してしまうこと
  • HOC もrender props も状態を持つロジックを分離できても、積極的に再利用できるほどには抽象化できなかった

Hooks

  • 状態を持ったロジックを完全に任意のコンポーネントから分離できる
  • それ単独でテストや別コンポーネントでの再利用が簡単にできる
  • コンポーネントの階層構造を変えることなく、状態を伴った再利用可能なロジックを追加できる

  • (ライフサイクルメソッドの反省のもとに)時間によって切り分けるのではなく、機能ごとに副作用を伴う処理をまとめて記述できる仕組みを提供

  • 機能ごとにまとまっているため、それをコンポーネントから切り離して別コンポーネントで再利用するのことが容易に可能

Hooks実装

  • statusを扱う : useState()
  • 副作用を扱る: useEffect()
  • メモ化: useMemo() 計算結果をコンポーネントシステムの外に保存しておくことでコンピュータリソース削減

実装(基本)

チュートリアル

サンプル

create-react-app すると以下ができるイメージ

.
|--package.json
|--package-lock.json
|--public
|  |--index.htm
|--src
|  |--App.js
|  |--index.js
|  |--styles.css
// index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);
// App.js
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

JSX

HTML記述

返却するHTML要素は、1タグで囲われていなければならない

  • 単一
import React from 'react';
import ReactDom from "react-dom";

const App = () => {
  return <h1>Hello</h1>;
}

ReactDom.render(<App />, document.getElementById("root"));
  • 複数

<div>で囲うと余計な要素がレンダリングされる。その時は<React.Fragment>で囲う。

<React.Fragment><>~</>で省略可

import React from 'react';
import ReactDom from "react-dom";

const App = () => {
  return (
    <>
      <h1>Hello</h1>
      <p>topic</p>
    </>
  );
}

ReactDom.render(<App />, document.getElementById("root"));

コンポーネント

コンポーネント名は必ず先頭を大文字。推奨:パスカルケース

// App.jsx
import React from 'react';

const App = () => {
  return (
    <>
      <h1>Hello</h1>
      <p>topic</p>
    </>
  );
}

export App;
// index.js
import React from 'react';
import ReactDom from "react-dom";
import App from "./App";

ReactDom.render(<App />, document.getElementById("root"));

イベント実装

import React from 'react';

const App = () => {
  const onClickHandler = () => alert();
  return (
    <>
      <h1>Hello</h1>
      <button onClick={onClickHandler}></button>
    </>
  );
}

export App;

スタイル実装

CSSのプロパティ名もキャメルケースで書く

// App.jsx
import React from 'react';

const App = () => {
  const textStyle = {
    color: 'blue',
    fontSize: '16px'
  };
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <p style={textStyle}>topic</p>
    </>
  );
}

export App;

props

  • コンポーネントに渡す引数のようなもの。
  • props で条件などを渡して表示を切り替える。
// components/Message.jsx  受け取る側
import React from 'react';

const Message = (props) => {
  const textStyle = {
    color: props.color,
    fontSize: '16px'
  };
  return (
    <p style={textStyle}>{props.message}</p>
  );
}

export Message;
// App.jsx  渡す側
import React from 'react';
import Message from './components/Message';

const App = () => {
  const onClickHandler = () => alert();
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <Message color="blue" message="topic" />
      <button onClick={onClickHandler}></button>
    </>
  );
}

export default App;
  • コンポーネントタグで囲った値は props.children で渡る
  • propsには分割代入を使うべし
// components/MessageTwo.jsx  受け取る側
import React from 'react';

const MessageTwo = (props) => {
  const { color, children } = props;
  const textStyle = {
    color,  // プロパティ名と変数名同じなら省略可
    fontSize: '16px'
  };
  return (
    <p style={textStyle}>{children}</p>
  );
}

export default MessageTwo;
// App.jsx  渡す側
import React from 'react';
import MessageTwo from './components/MessageTwo';

const App = () => {
  const onClickHandler = () => alert();
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <MessageTwo color="green">topic2</MessageTwo>
      <button onClick={onClickHandler}></button>
    </>
  );
}

export App;

State (useState)

コンポーネントの状態。状態が変わると再レンダリングされる。

  • useState(): State(値)とセッターを取得する

例:カウントアップ

// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [num, setNum] = useState(0);
  const onClickCountUp = () => {
    setNum(num + 1);
  };
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickCountUp}>count up</button>
    </>
  );
}

export App;

レンダリングと抑止 (useEffect)

コンポーネントが再レンダリングされる条件

バグる例

以下をベースに

// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [isShow, setShowFlag] = useState(true);
  const [num, setNum] = useState(0);
  const onClickCountUp = () => {
    setNum(num + 1);
  };
  const onClickChange = () => {
    setShowFlag(!isShow);
  };
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickChange}>on/off</button>
      {isShow && <p>★★★</p>}
    </>
  );
}

export App;

機能追加する:3の倍数の時に表示(Too many re-renders.)

// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [isShow, setShowFlag] = useState(true);
  const [num, setNum] = useState(0);
  const onClickCountUp = () => {
    setNum(num + 1);
  };
  const onClickChange = () => {
    setShowFlag(!isShow);
  };

  if (num % 3 === 0) { // stateの値が変わるので無限再レンダリング
    setShowFlag(true);
  } else {
    setShowFlag(false);
  }

  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickCountUp}>count up</button>
      <br />
      <button onClick={onClickChange}>on/off</button>
      {isShow && <p>★★★</p>}
    </>
  );
}

export App;
  • 対策後 (true -> true, false -> false の無駄な上書きしないようにする)
    • だが、on/off が利かなくなる
// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [isShow, setShowFlag] = useState(true);
  const [num, setNum] = useState(0);
  const onClickCountUp = () => {
    setNum(num + 1);
  };
  const onClickChange = () => {
    setShowFlag(!isShow);
  };

  if (num > 0) {  // 対策後
    if (num % 3 === 0) {
      isShow || setShowFlag(true);
    } else {
      isShow && setShowFlag(false);
    }
  }

  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickCountUp}>count up</button>
      <br>
      <button onClick={onClickChange}>on/off</button>
      {isShow && <p>★★★</p>}  // ボタン押下 → 再レンダリング. isShowはnumに応じて決まるため機能しない
    </>
  );
}

export App;

別の機能が影響してバグる(num表示とon/off機能)。その場合はuseEffectを使って関心を分離する必要がある。

useEffect で関心を分離

  • 最初の一回のみ実行(第二引数を空配列にする)
useEffect(() => {
  // ここに書いた処理は最初の一回しか実行されない
}, []);
  • stateの値が変わった時のみ実行
const [num, setNum] = useState(0);

useEffect(() => {
  // num が変わった時だけ処理実行
}, [num]);
// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [isShow, setShowFlag] = useState(true);
  const onClickChange = () => {
    setShowFlag(!isShow);
  };

  useEffect(() => {
    if (num > 0) {
      if (num % 3 === 0) {
        isShow || setShowFlag(true);
      } else {
        isShow && setShowFlag(false);
      }
    }
  }, [num]);

  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickCountUp}>count up</button>
      <br>
      <button onClick={onClickChange}>on/off</button>
      {isShow && <p>★★★</p>}
    </>
  );
}

export App;

※上記だと、Eslint で以下エラーになる /* eslint react-hooks/exhaustive-deps */

クラスコンポーネントと関数コンポーネント

実践メモ

input の入力値をstateに即時反映

const App = () => {
  const [inputTodoText, setInputTodoText] = useState("");
  
  const onChangeTodoText = (event) => setInputTodoText(event.target.value);

  return (
    <input id="js-add-text" value={inputTodoText} onChange={onChangeTodoText} className="ul-brd ul-pad-6-16" type="text" placeholder="Todo入力" />
  );
}

イベントハンドラに渡すのは関数

onClick={onClickDelete(index)} としてしまうと即時実行される

<ul>
  {incompleteTodos.map((todo, index) => {
    <button className="ul-brd" onClick={() => onClickDelete(index)}>削除</button>
  })}
</ul>

default export と export

export推奨

  • default export

使う側で名前付けするので、タイポに気付きにくい

import Sample from './components/SampleComp';
  • export

使用時は分割代入必須かつ定義された名前以外はエラーになる=タイポ防止

import { SampleComp } from './components/SampleComp';

一定量メモリを消費させるshell

定量メモリを消費させるshell

stress コマンドが使えるならば、お手軽にできるかもしれない。

使えない環境のため、以下で実施した。その記録

  • 理由が分かっていないが、指定したバイトの2倍取られた
  • 一度に大きい量を一気に消費はできないのでループ
  • 消費させるメモリ量が増えれば、その分時間がとてもかかるようになる(1GBで数分レベル)

ご利用は計画的に、もっといい方法がありそう。

#! /bin/bash

INIT_ALLOC_MEM=209715200  # 200MB * 2 消費
LOOP_NUM=2  # INIT_ALLOC_MEM * N
REPEAT_ALLOC_MEM=10485760  # 10MB * 2 消費

echo PID=$$
echo "# start: init memory allocate."
for i in `seq ${LOOP_NUM}`
do
  eval a$i'=$(head --bytes ${INIT_ALLOC_MEM} /dev/zero |cat -v)'
  echo -n " $i"
done
echo
echo "# end."

echo -n "[ Enter : add 20 MB ] , [ Ctrl+d : stop ]"
c=0
while read byte; do
   eval b$c'=$(head --bytes ${REPEAT_ALLOC_MEM} /dev/zero |cat -v)'
   c=$(($c+1))
   echo -n ">"
done
echo

以下、メモリ量確認用shell (freeコマンドの結果から抽出)

mem_free=`free -m | awk '/Mem:/ {print $4}'`
swap_free=`free -m | awk '/Swap:/ {print $4}'`
buff_cache=`free -m | awk '/Mem:/ {print $6}'`
total_virtual_mem=`expr ${mem_free} \+ ${swap_free} \+ ${buff_cache}`

echo "Mem Free:   ${mem_free} MB"
echo "Swap Free:  ${mem_free} MB"
echo "Buff/Cache: ${mem_free} MB"
echo "Total:      ${total_virtual_mem} MB"

ref: Linuxで手軽にCPU/メモリの負荷をかける方法