あいつの日誌β

働きながら旅しています。

memoize するときに array in array な table を作りたい

最近英語の勉強するために英語でアルゴリズムを解説している動画を youtube で見ています。 計算量が増えないように memorize する事が多いようです。 さて、memoize 用の連想配列を頑張って書くのいやなので([[],[],[],[]]みたいなの)関数にしてみました。

こんな感じで良いかな? m, n が 0 から始まるのか 1 から始まるのかによって m, n の箇所が変わると思いますが、まあ少しぐらい多めにマッピングしておいてもいいのかな。もっと良い書き方、もしくはライブラリ化されているのをご存知の方はぜひご教授ください。

const _ = require('lodash');

const getArrayInArray = (m, n) => _.times(m + 1, () =>
  _.times(n + 1, _.constant())
);

const memoTable = getArrayInArray(2, 3); 

memoTable[2][3] = "memo";
console.log(memoTable[2][3]);

Redux について思う事

あらすじ

最近 React 案件の商談が多いのですが「Redux で書かれたビジネスロジックのテストもお願いしたい」とか言われて、んん?となったのでなんとなくブログにします。

ビジネスロジック と Redux が混在する?何故?

Redux はおおざっぱにいうと以下の概念で動きます。

Actions -> Reducers -> Store

そして Redux の3原則は以下のとおり

  • Single source of truth
  • State is read-only
  • Changes are made with pure functions

そう、Redux は基本的に pure function を使います。そして pure function は以下のような関数の事です

function square(x) {
  return x * x;
}

function squareAll(items) {
  return items.map(square);
}

そして以下は pure function ではないです。

function square(x) {
  updateXInDatabase(x);
  return x * x;
}

function suareAll(items) {
  for (let i = 0; i < items.length; i++) {
    items[i] = square(items[i]);
  }
}

これは作者が動画で説明しています

https://egghead.io/lessons/react-redux-pure-and-impure-functions

pure function だと何がどれぐらいうれしいのか、という話は私はあまり関心がないので気になる方はリンク先の動画をたくさんご覧ください。

いずれにせよ reducer も action も pure function になると私は思っているので「Redux で書かれたビジネスロジック」と言われてもピンとこない。私の解釈だとビジネスロジックは Action の前で実行を終えているはず。

[Bussiness Logic] -> [Actions -> Reducers -> Store]

なので Bussiness Logic は分離してテストを書けば良いのではないかなあと思うわけです。つまり actions.js には pure function だけを書く。

ただし、私の考えが公式サイトの Tutorial とそぐわない

公式サイトにある Async function の Tutorial には fetchPosts という関数が actions.js に書かれています。

https://redux.js.org/advanced/async-actions

そもそも非同期処理は pure function なのか?

そもそも非同期処理は Pure function ではないはず。なのでそれを dispatch するのは問題なのではないかなあと思います。非同期処理が終わってから結果を dispatch すればいいはず。なんですがそれをせずに middleware で Redux の非同期処理については私は疑問があります。似た考え方をする人もいるようですし

React+Reduxで簡単な勉強会イベント検索アプリを作って公開してみる - Qiita

そうでない方もいると思います。

Reduxのmiddlewareを積極的に使っていく - Qiita

メリットとデメリットの捉え方が人それぞれだし状況も違うのでどちらが優れているとも思いませんが、この辺りって2018年現在ではどのような論調になってるんでしょう?

redux-saga

私が redux を使わないで Single Page Application を作る Tutorial - Qiita を書いたのはこれがきっかけです。どうしても redux-saga とはそりが合いませんでした。

もしかすると冒頭の「Redux で書かれたビジネスロジックのテストもお願いしたい」というのは redux-saga の事だったのかなあ?それはいやだなあ。

もちろんメリットがあるのであれば頑張りますが、物事を複雑にしがちだし、対して得られる恩恵が少なそう、というのが私の redux-saga に対する見解です。こういう事書くと使いこなせいお前が悪い、という話にもなりそうですがそう思って頂いて結構です。

まとめ

そんなわけで Redux はすごくシンプルなのでミニマリスト志向な私はとても好きな概念なんですが非同期処理とかビジネスロジックとかがやってきて私の好きな Redux で無くなるのがいや、そんな感じ。

Youtube で MIT OpenCourseWare でやっている Programming for the Puzzled が面白い

あらすじ

そろそろ英語の勉強しないといけないのでとりあえず Youtube で英語に慣れよう、エンジニアだしMITの動画でも見てみようかな。と思って見たらえらい面白かったので講義の内容をなんとなくブログにします。

www.youtube.com

問題: coin row game

コインを集めるゲームが題材です。例えば以下のようにコインが並んでいるとすると全部集めるといくらになるでしょうか?

[14, 3, 27, 4, 5, 15, 1]

答えは以下の通りです。

const _ = require('lodash');
const result = _.sum([14, 3, 27, 4, 5, 15, 1]);
console.log(result); // 69

はえいこれではゲームにならないので制約を加えます。拾ったコインのすぐ次のコインは拾えない です。この制約下においてもっとも大きな値となるようにコインを集める関数を考える、というゲームです。

coins([14, 3, 27, 4, 5, 15, 1]) // 14 + 27 + 15 = 56
coins([14, 3, 27, 4, 5, 15, 11]) // 14 + 27 + 5 + 11 = 57

動画では考え方と解法が解説されています。言語は Python ですが私は Node.js で書いています。

サブセットを用意する

サブセットという概念を説明します。たとえば [1, 2] からは 2の2乗のサブセットが作成できます。

[1, 2] => {}, {1}, {2}, {1, 2}

[1, 2, 3] からだと2の3乗のサブセットができます。コードで説明したほうが分かりやすいので以下をご覧ください。

function getSubsets(items) {
  var subset = [];
  helper(items, subset, 0);
}

function helper(items, subset, i) {
  // n 番目の階層に到着したら標準出力
  if (items.length === i) {
    console.log(subset);
  }
  // 分岐が 
  else {
    // empty の場合
    subset[i] = null;
    helper(items, subset, i + 1);

    // 数値を代入する場合
    subset[i] = items[i];
    helper(items, subset, i + 1);
  }
}

getSubsets([1, 2, 3])

上記を実行すると以下のようになります。これを見ればコインを拾ったか、スキップしたかがわかると思います。

[ null, null, null ]
[ null, null, 3 ]
[ null, 2, null ]
[ null, 2, 3 ]
[ 1, null, null ]
[ 1, null, 3 ]
[ 1, 2, null ]
[ 1, 2, 3 ]

Pick or Skip

この coin row game で実行できる事は基本的に Pick するか、 Skip するかの2つしかありません。これを 0 と 1 で表現します。

Skip: 0
Pick: 1

Pick or Skip を2進数で表現すると以下のようにすることができます。

0000
0001
0101
1111

さて、先頭の数字から順番に処理する際に、基本的に以下の選択肢があります。

  • 直前のコインを Pick している場合 => Skip only
  • 直前のコインを Pick していない場合 => Pick or Skip

上記の条件を考えると先ほど2進数で表現した Pick or Skip では制約に違反しているものがあります。一番下の 1111 です。

という事で並んだコインを Pick or Skip したサブセットを全て取得して、制約に違反していないサブセットから拾ったコインの合計値を求めると問題を解決できそうです。 このロジックでも問題は解決できますが、コインの数が増えれば増えるほど分岐する tree が深くなっていきます。ネズミ算式に増えるイメージです。

もしコインが 50 個並んでいたらサブセットの数は以下の通りです。

> 2 ** 50
1125899906842624

必要なサブセットだけを取得する

今回のゲームは コインを Pick した後は必ずスキップ する必要がありますので取得しなくても良いサブセットが存在します。

ということでこれを踏まえてサブセットを取得する方法を考えます。

完成

いわゆる再帰呼び出しを使って以下のように書けます。

function coins(row, table) {

  // no coins
  if (row.length == 0) {
    table[0] = 0;
    return {result: 0, table}
  // one coin
  } else if (row.length == 1) {
    table[1] = row[0];
    return {result: row[0], table};
  }

  // pick the coin, and skip next coin -> slice(2)
  const pick = row[0] + coins(row.slice(2), table).result

  // skip the coin -> slice(1)
  const skip = coins(row.slice(1), table).result;

  const result = _.max([pick, skip]);
  table[row.length] = result;

  return { result, table };
}

const coinRow = [14, 3, 27, 4, 5, 15, 1];
const {result, table} = coins(coinRow, {});

console.log({result, table});

結果は以下の通り。result が求めている答えです。

{ 
  result: 56,
  table: { '0': 0,
     '1': 1,
     '2': 15,
     '3': 15,
     '4': 19,
     '5': 42,
     '6': 42,
     '7': 56 
  } 
}

動画では traceback する関数を用意して table を解析しています。興味がある方は動画をご覧ください。

まとめ

MIT の動画はとても面白いのでおすすめです。

ブロックチェーンはじめました

あらすじ

スマートコントラクトからマネーのオイニーがしてきたので素振りしておきました。

やってみた

以下を参考にやってみた

Full Stack Hello World Voting Ethereum Dapp Tutorial — Part 1 | by Mahesh Murthy | Medium

mkdir practice-dapp && cd $_
yarn init -y
yarn add solc --save
yarn add web3@0.20.6 --save
mkdir contracts script
touch contracts/Voting.sol script/vote.js

create contracts/Voting.sol

pragma solidity ^0.4.18;
// We have to specify what version of compiler this code will compile with

contract Voting {
  /* mapping field below is equivalent to an associative array or hash.
  The key of the mapping is candidate name stored as type bytes32 and value is
  an unsigned integer to store the vote count
  */

  mapping (bytes32 => uint8) public votesReceived;

  /* Solidity doesn't let you pass in an array of strings in the constructor (yet).
  We will use an array of bytes32 instead to store the list of candidates
  */

  bytes32[] public candidateList;

  /* This is the constructor which will be called once when you
  deploy the contract to the blockchain. When we deploy the contract,
  we will pass an array of candidates who will be contesting in the election
  */
  function Voting(bytes32[] candidateNames) public {
    candidateList = candidateNames;
  }

  // This function returns the total votes a candidate has received so far
  function totalVotesFor(bytes32 candidate) view public returns (uint8) {
    require(validCandidate(candidate));
    return votesReceived[candidate];
  }

  // This function increments the vote count for the specified candidate. This
  // is equivalent to casting a vote
  function voteForCandidate(bytes32 candidate) public {
    require(validCandidate(candidate));
    votesReceived[candidate] += 1;
  }

  function validCandidate(bytes32 candidate) view public returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {
      if (candidateList[i] == candidate) {
        return true;
      }
    }
    return false;
  }
}

create script/vote.js

const fs = require('fs');
const solc = require('solc');
const Web3 = require("web3");

main();

function sleep(ms = 0) {
  return new Promise(r => setTimeout(r, ms));
}

async function main(err, blockchain) {

  // ganeche を起動しておく
  const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"));

  // 登録済みのアカウントを確認
  const accounts = web3.eth.accounts;
  console.log(accounts);

  // compile 開始
  const code = fs.readFileSync('./contracts/Voting.sol').toString();
  const compiledCode = solc.compile(code);
  const abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface);
  const byteCode = compiledCode.contracts[':Voting'].bytecode;

  // deploy 開始
  const VotingContract = web3.eth.contract(abiDefinition);
  const deployedContract = VotingContract.new(
    ['Rama','Nick','Jose'],
    { data: byteCode, from: web3.eth.accounts[0], gas: 4700000 }
  )

  // deploy 中
  while (!deployedContract.address) {
    console.log("wait a minute...");
    await sleep(1000);
  }

  // deploy 完了
  console.log(deployedContract.address);

  // 投票開始
  const contractInstance = VotingContract.at(deployedContract.address)

  // 投票数を見る view なので gas を消費しない(transaction が発生しない)
  let result = contractInstance.totalVotesFor.call('Rama');
  console.log(result);

  // transaction が発生する
  contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]});
  contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]});
  contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]});
  result = contractInstance.totalVotesFor.call('Rama').toLocaleString();
  console.log(result);
}

感想

version 指定しないで yarn add web3 すると 1.0 の version をインストールするけどこれは不安定なのでお気をつけ下さい。 そして web3 の 0.2.x 系のドキュメントはここ

JavaScript API · ethereum/wiki Wiki · GitHub

gyp ERR! stack Error: Python executable \"/usr/local/bin/python\" is v3.6.4, which is not supported by gyp.

yarn add sha3 しようとすると gyp が Python のバージョンに対して不服がある模様。

gyp ERR! stack Error: Python executable \"/usr/local/bin/python\" is v3.6.4, which is not supported by gyp.

https://github.com/Homebrew/homebrew-core/issues/24869#issuecomment-371765216

上記のコメントを参考にして npmrc に追記した。

echo "python = /usr/bin/python" >> ~/.npmrc

おしまい

DynamoDB の特性を理解しないで開発していたら pagination の実装でつまづいた

前置き

私の勘違いが書かれている可能性がありますが、ご指摘頂けると大変喜びます。特に「いや、それはこうやったらできるよ」という情報をお待ちしております。

あらすじ

Serverless Framework 使って個人サイトを作ろうとしたら DynamoDB で若干手間取ったので後世のエンジニアの為に備忘録を残します。

DynamoDB について

DynamoDB は優れた分散データベースです。バックアップやスケールアップなどに時間を割く必要がないように工夫されたサービスです。なんですが、これまでの RDS や KVS とは注力されている箇所が違うで少し使いづらい部分があります。

パーティションキーの概念

DynamoDB では Pagination がしづらいです。できなくは無いのですが RDS と少し違うところがあります。検索条件にインデックスが含まれていないと正しい件数を取得できませんそしてインデックスを作成するには必ずパーティショニングする必要があります

というわけで実際に動作させて説明したいと思います。

準備

Serveless Framework の migration 機能が便利(再起動するたびにデータがセットされる)なのでこれを利用します。

環境は以下の通り

% node -v
v8.11.1

% sls -v
1.26.1

雛形を作成

sls create --template aws-nodejs --path practice-dynamodb && cd $_
touch example1.js example2.js
mkdir migrations
touch migrations/locations.json

migrations/locations.json を作成

[
  {
    "country": "JP",
    "cityEn": "Shibuya-ku",
    "stateEn": "Tokyo",
    "cityJp": "渋谷区",
    "createdAt": "2018-04-19 20:10:43",
    "stateJp": "東京都",
    "id": "1028337572",
    "name": "Xiang ni cafe(シャンニーカフェ)"
  },
  {
    "country": "JP",
    "cityEn": "Shibuya-ku",
    "stateEn": "Tokyo",
    "cityJp": "渋谷区",
    "createdAt": "2018-04-18 20:08:45",
    "stateJp": "東京都",
    "id": "620336124833357",
    "name": "岡本肉店"
  },
  {
    "country": "JP",
    "cityEn": "Shibuya-ku",
    "stateEn": "Tokyo",
    "cityJp": "渋谷区",
    "createdAt": "2018-04-17 15:31:09",
    "stateJp": "東京都",
    "id": "429313840761469",
    "name": "香港ロジ 原宿店"
  },
  {
    "country": "JP",
    "cityEn": "Sapporo-shi",
    "stateEn": "Hokkaido",
    "cityJp": "札幌市",
    "createdAt": "2018-04-16 14:07:07",
    "stateJp": "北海道",
    "id": "5841572",
    "name": "5坪 gotsubo"
  },
  {
    "country": "JP",
    "cityEn": "Sapporo-shi",
    "stateEn": "Hokkaido",
    "cityJp": "札幌市",
    "createdAt": "2018-04-15 14:07:07",
    "stateJp": "北海道",
    "id": "3518106",
    "name": "SAKANOVA"
  },
  {
    "country": "JP",
    "cityEn": "Toshima-ku",
    "stateEn": "Tokyo",
    "cityJp": "豊島区",
    "createdAt": "2018-04-15 14:07:07",
    "stateJp": "東京都",
    "id": "447856545563610",
    "name": "大衆酒場 かぶら屋池袋7号店"
  },
  {
    "country": "JP",
    "cityEn": "Sapporo-shi",
    "stateEn": "Hokkaido",
    "cityJp": "札幌市",
    "createdAt": "2018-04-15 14:07:07",
    "stateJp": "北海道",
    "id": "52232",
    "name": "菜もっきりや"
  },
  {
    "country": "JP",
    "cityEn": "Sumida-ku",
    "stateEn": "Tokyo",
    "cityJp": "墨田区",
    "createdAt": "2018-04-14 14:07:07",
    "stateJp": "東京都",
    "id": "8588811",
    "name": "立ち呑み 粋"
  },
  {
    "country": "JP",
    "cityEn": "Kawasaki-shi",
    "stateEn": "Kanagawa",
    "cityJp": "川崎市",
    "createdAt": "2018-04-14 14:07:07",
    "stateJp": "神奈川県",
    "id": "1032997013",
    "name": "和食と立喰い寿司 ナチュラ"
  },
  {
    "country": "JP",
    "cityEn": "Shibuya-ku",
    "stateEn": "Tokyo",
    "cityJp": "渋谷区",
    "createdAt": "2018-04-14 14:07:07",
    "stateJp": "東京都",
    "id": "896121",
    "name": "Bistro ひつじや"
  },
  {
    "country": "JP",
    "cityEn": "Takamatsu-shi",
    "stateEn": "Kagawa",
    "cityJp": "高松市",
    "createdAt": "2018-04-13 14:07:07",
    "stateJp": "香川県",
    "id": "1028420342",
    "name": "たべごとや 艸 - そう"
  },
  {
    "country": "JP",
    "cityEn": "Osaka-shi",
    "stateEn": "Osaka",
    "cityJp": "大阪市",
    "createdAt": "2018-04-12 14:06:28",
    "stateJp": "大阪府",
    "id": "242874420",
    "name": "魚屋ひでぞう 立ち呑み"
  },
  {
    "country": "JP",
    "cityEn": "Sapporo-shi",
    "stateEn": "Hokkaido",
    "cityJp": "札幌市",
    "createdAt": "2018-04-11 14:06:28",
    "stateJp": "北海道",
    "id": "1021458277",
    "name": "鮨角打ち 裏・小樽酒商たかの札幌駅前店"
  }
]

serverless.yml を修正

service: practice-dynamodb
plugins:
   - serverless-dynamodb-local

custom:
  dynamodb:
    start:
      port: 8000
      inMemory: true
      migrate: true
      seed: true
    seed:
      development:
        sources:
          - table: locations
            sources: [./migrations/locations.json]

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  region: ap-northeast-1

resources:
  Resources:
    ArticlesTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: locations
        AttributeDefinitions:
         - AttributeName: id
           AttributeType: S
         - AttributeName: country
           AttributeType: S
         - AttributeName: stateEn
           AttributeType: S
         - AttributeName: cityEn
           AttributeType: S
         - AttributeName: createdAt
           AttributeType: S
        KeySchema:
         - AttributeName: id
           KeyType: HASH
        GlobalSecondaryIndexes:
          - IndexName: country-index
            KeySchema:
             - AttributeName: country
               KeyType: HASH
             - AttributeName: createdAt
               KeyType: RANGE
            Projection:
              NonKeyAttributes:
               - stateEn
               - cityEn
              ProjectionType: INCLUDE
            ProvisionedThroughput:
              ReadCapacityUnits: '1'
              WriteCapacityUnits: '1'
          - IndexName: stateEn-index
            KeySchema:
             - AttributeName: stateEn
               KeyType: HASH
            Projection:
              NonKeyAttributes:
               - id
              ProjectionType: INCLUDE
            ProvisionedThroughput:
              ReadCapacityUnits: '1'
              WriteCapacityUnits: '1'
          - IndexName: cityEn-index
            KeySchema:
             - AttributeName: cityEn
               KeyType: HASH
             - AttributeName: createdAt
               KeyType: RANGE
            Projection:
              NonKeyAttributes:
               - id
              ProjectionType: INCLUDE
            ProvisionedThroughput:
              ReadCapacityUnits: '1'
              WriteCapacityUnits: '1'
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

dynamodb を local に install して動作確認します。

sls dynamodb install

動作確認します。13件のアイテムを取得できていればOKです。

aws dynamodb scan --table-name locations --endpoint-url http://localhost:8000 | jq .Count
13

というわけで locations テーブルにはいくつかのインデックスが存在します。それぞれのインデックスで出来る事と出来ない事を確認します。

Just Do it

dynamoDB を参照するコマンドは大きく分けて scan, get, query の3種類です。 まずはプライマリーキーに対してコマンドを実行してみます。

example1: scan, get, query

create example1.js

const aws = require('aws-sdk');

function getDocClient() {
  return new aws.DynamoDB.DocumentClient({
    region: 'ap-northeast-1',
    endpoint: "http://localhost:8000"
  });
}

function main() {

  // Good
  getDocClient().scan({
    TableName: 'locations',
  }).promise().then(console.log).catch(console.error);

 // Good
  getDocClient().get({
    TableName: 'locations',
    Key: {
      id: "1032997013"
    }
  }).promise().then(console.log).catch(console.error);

  // Good
  getDocClient().query({
    TableName: 'locations',
    KeyConditionExpression: 'id = :id',
    ExpressionAttributeValues: {
      ':id': "1032997013"
    },
    Limit: 1
  }).promise().then(console.log).catch(console.error);

  // No Good
  getDocClient().query({
    TableName: 'locations',
    KeyConditionExpression: 'begins_with(id, :id)',
    ExpressionAttributeValues: {
      ':id': "103"
    }
  }).promise().then(console.log).catch(console.error);
}

main();

scan は全レコードをシーケンシャルに取得します。get は一意のユニークキーからレコードを一つだけ取得します。

DynamoDB のテーブル設計で考えないといけないのは query です。上記の結果からHash Key に対して部分一致の query が発行できないことが分かります。

DynamoDB では HASH Key を country:state:city のような構造にして先頭部分から一致したものを取得することはできません。その場合は RANGE KEY にする必要があります。

example2: セカンダリインデックス

今度は都道府県別に locations を取得したい場合は以下のようにします。 index を使用するので IndexName に 'stateEn-index' と cityEn-index を指定します。

create example2.js

const aws = require('aws-sdk');

function getDocClient() {
  return new aws.DynamoDB.DocumentClient({
    region: 'ap-northeast-1',
    endpoint: "http://localhost:8000"
  });
}

function main() {

  // Good
  getDocClient().query({
    TableName: 'locations',
    IndexName: 'stateEn-index',
    KeyConditionExpression: 'stateEn = :stateEn',
    ExpressionAttributeValues: {
      ':stateEn': "Hokkaido"
    }
  }).promise().then(console.log).catch(console.error);

  // Good
  getDocClient().query({
    TableName: 'locations',
    IndexName: 'cityEn-index',
    KeyConditionExpression: 'cityEn = :cityEn AND createdAt >= :createdAt',
    ExpressionAttributeValues: {
      ':cityEn': "Sapporo-shi",
      ':createdAt': "2018-04-16"
    }
  }).promise().then(console.log).catch(console.error);

  // Good
  getDocClient().query({
    TableName: 'locations',
    IndexName: 'cityEn-index',
    KeyConditionExpression: 'cityEn = :cityEn AND begins_with(createdAt, :createdAt)',
    ExpressionAttributeValues: {
      ':cityEn': "Sapporo-shi",
      ':createdAt': "2018-04"
    }
  }).promise().then(console.log).catch(console.error);
  
  // Good
  getDocClient().query({
    TableName: 'locations',
    IndexName: 'country-index',
    KeyConditionExpression: 'country = :country',
    ExpressionAttributeValues: {
      ':country': "JP",
    },  
    ScanIndexForward: false
  }).promise().then(console.log).catch(console.error);
  
}

main();

stateEn-indexHASH, cityEn-indexHASH + RANGE で作成されています。 stateEn-index は createdAt を含まないのでソート出来ないのですが、 cityEn-index はcreatedAt でソートされています。

このように Pagination が必要となる場合は HASH + RANGE でインデックスを作成する必要があります。

ただし KeyConditionExpression: 'cityEn = :cityEn AND begins_with(createdAt, :createdAt)' のように必ず HASH Key を指定しなければいけません。

このように DynamoDB ではデータがパーティションで区切られる前提で設計されています。もし全件をソートするなら country-index のように全データを同一のパーティションに含める必要があります。たぶんその辺りの事を理解した上で DynamoDB を利用する、しないを考えた方が良いでしょう。この例だとすでに Index を 3つ追加しているので RDS を検討した方が良いのかもしれません。

まとめ

DynamoDB は我々エンジニアにとっては強力な補助ツールとなるでしょう。ただし利用する場合はその特性を良く理解する必要があります。

近年では Serverless Framework という選択肢が出てきていますが、案件によってはそのような選択をできる様にする為に DynamoDB の Table 設計に一度慣れておいた方が良いと思います。

あとそれから

その DynamoDB で個人サイトつくりました。 飲み歩くのが好きなので飲み歩いたお店を記録するサイトです。

http://drunkard.tokyo/

こちらからは以上です。

MacOSX で使用されているポートを指定して kill する

やり方

lsof -i :8000 -t | xargs kill

ところで

local Dynamodb を使って開発していて dynamoDB を再起動しようとするといつも port が開きっぱなしなので lsof -i :8000 してプロセスを特定してから kill してから再起動しているんだけど serverless で dynamoDB を使っている人たちは普段どうやっているんだろう...

lsof -i :8000 -t | xargs kill && sls dynamodb start