Redis Cluster mode에서 mget, mset, pipeline과 같은 멀티 키 명령어 사용하기.

데이터베이스/Nosql|2019. 5. 13. 22:48

redis에 만약 200 ~ 300개가 넘는 캐시 정보를 계속 request를 날리면 레이턴시가 발생할 가능성이 크기 때문에 이런경우에 mget, mset, pipeline 등 멀티키 명령어를 사용한다.

하지만 redis가 single mode일 때는 아무 상관이 없지만 cluster mode인경우에는 다음과 같은 오류를 발생 시킨다.

"CROSSSLOT Keys in request don't hash to the same slot"

무슨 오류일까?? 처음에 redis가 싱글모드로 돌고 있던 stage에서 테스트를 해서 정상적으로 멀티키 명령어가 잘 되는줄 알고 배포 했다가 라이브에서 위와 같은 오류가 발생했다...

너무 당황해서 바로 수정했다.

무엇이 문제 였을까? 하면서 문서를 찾아보니 redis에는 16384의 키 슬롯이 있는데 클러스터 모드인 경우 이 키 슬롯들이 각 node에 분할되기 때문에 멀티키 명령어를 사용할 수 없다고 한다.

만약 멀티키 명령어를 사용하고 싶다면 그 키들은 모두 같은 키 슬롯에 들어있어야 한다.

방법은 다음과 같다.

 

같은 키 슬롯에 데이터 넣기


동일한 키 슬롯에 저장하고 싶으면 강제로 지정하는 해야한다. 이때 저장하고자 하는 키들 앞에 {..}의 값을 붙여주고 저장하는 것이다. 예를 들어 저장하고자 하는 데이터에 {product}.products1, {product}.products2로 저장하면 이 두개는 동일한 키 슬롯에 포함되게 된다.

이는 aws elasticcache페이지에 상세히 나와있으니 참고하면 된다.

https://aws.amazon.com/ko/premiumsupport/knowledge-center/elasticache-crossslot-keys-error-redis/

 

ElastiCache 오류 "CROSSSLOT Keys in request don't hash to the same slot" 해결

이 오류는 키가 동일한 노드가 아니라, 동일한 해시 슬롯에 있어야 하기 때문에 발생합니다. 샤딩된 Redis ElastiCache 클러스터(클러스터 모드 활성화됨)에서 다중 키 작업을 구현하려면 키는 동일한 해시 슬롯으로 해시되어야 합니다. 해시 태그를 사용하여 키를 동일한 해시 슬롯에 강제로 배치할 수 있습니다. 이 예제에서는 "myset2" 및 "myset" 세트가 동일한 노드에 있습니다. 이 오류를 해결하려면 해시 태그를 사용하여 키를 동일한 해시

aws.amazon.com

하지만 여기서 중요한건, 우리가 클러스터 모드를 사용하는 이유는 한군데이 집중시키지 않고 값을 분산시켜서 부하를 막기 위해서인데 이를 하나의 키 슬롯에 데이터를 모두 집어 넣을 경우 의미가 없어진다.

그럼 키를 각 노드마다 분산시키면서 멀티키 명령어를 사용할 수 있는 방법이 있을까?

 

클러스터 모드에서 각 노드별 key slot에 데이터 삽입하여 멀티 명령어 사용하기


생각을 바꿔보았다. 싱글모드에서는 키 슬롯 16384개가 존재하고 mset, mget, pipeline등의 명령어가 사용 가능했다. 

그럼 각 노드를 하나의 싱글모드의 레디스라고 생각하면 어떨까? 하는 생각으로 전략을 바꿔봤다.

처음에는 가능할지 모르고 작업을 진행했는데 정상적으로 동작해서 기분이 좋았다.

#작업 순서 (명령어는 ioredis를 기준으로 작성하였다.)

1. 클러스터 모드에서 동작중인 master node에 대한 정보를 가져온다. (cluster mode에는 master 노드와 slave노드가 있는데 slave node는 단순 readonly이고 마스터 노드 값을 가지고 있기 때문에 가져올 필요 없다.)

const masterNodes = redis.nodes('master')

2. 각 노드별 key slot 현황을 확인하고 masterNode와 매핑한다. key slot 현황을 확인하기 위해서는 cluster slots 사용한다. ioredis에서는 redis.cluster('slots')

127.0.0.1:7001> cluster slots
1) 1) (integer) 0
   2) (integer) 4095
   3) 1) "127.0.0.1"
      2) (integer) 7000
   4) 1) "127.0.0.1"
      2) (integer) 7004
2) 1) (integer) 12288
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 7003
   4) 1) "127.0.0.1"
      2) (integer) 7007
3) 1) (integer) 4096
   2) (integer) 8191
   3) 1) "127.0.0.1"
      2) (integer) 7001
   4) 1) "127.0.0.1"
      2) (integer) 7005
4) 1) (integer) 8192
   2) (integer) 12287
   3) 1) "127.0.0.1"
      2) (integer) 7002
   4) 1) "127.0.0.1"
      2) (integer) 7006
const slots = await redis.cluster('slots');

// 각 슬롯정보에 있는 ip, port정보를 확인하여 각 master와 슬롯의 범위를 정리한다.
{
 'ip' : ,
 'port' : ,
 'node' : masterNode[0],
 'slotStart' : 0
 'slotEnd': 4460
},
{
 'ip' : ,
 'port' : ,
 'node' : masterNode[1],
 'slotStart' : 4461
 'slotEnd': 8845
},
.
..
...
....

3. 찾고자하는 키들의 slot 정보를 정리하고 slot 범위에 맞는 오브젝트에 키 값을 배열에 정리한다. slot 정보를 뽑는 알고리즘이 CRC16이기 때문에 이를 이용해서 구할수도 있고 npm에 모듈로 나와있는게 있으니 활용해도 된다. (https://www.npmjs.com/package/cluster-key-slot)

{
 'ip' : ,
 'port' : ,
 'node' : masterNode[0],
 'slotStart' : 0
 'slotEnd': 4460,
 'keys': ['product::1', 'product::2', 'product::5'] // 범위가 0 ~ 4460에 속하고 masterNode[0]에 존재하는 키 슬롯에 저장되는 키들
},
{
 'ip' : ,
 'port' : ,
 'node' : masterNode[1],
 'slotStart' : 4461
 'slotEnd': 8845,
 'keys': ['product::4', 'product::3', 'product::6'] // 범위가 4461 ~ 8845에 속하고 masterNode[1]에 존재하는 키 슬롯에 저장되는 키들
},
.
..
...
....

4. 그리고 정리된 Object를 순회하면서 각 키에 있는 데이터를 조회하기 위한 pipeline을 생성하고 get명령어를 매핑하고 실행시킨다. 없는 데이터는 pipeline을 set으로 설정하고 exec 실행시킨다.

const objs = [{
 'ip' : ,
 'port' : ,
 'node' : masterNode[0],
 'slotStart' : 0
 'slotEnd': 4460,
 'keys': ['product::1', 'product::2', 'product::5']
},
{
 'ip' : ,
 'port' : ,
 'node' : masterNode[1],
 'slotStart' : 4461
 'slotEnd': 8845,
 'keys': ['product::4', 'product::3', 'product::6']
}];

for(const obj of objs) {
 const pipeline = obj.node.pipeline();
 
 obj.keys.forEach(keySlot => pipeline.get(key => pipeline.get);
 
 // 결과
 const ret = await keys.exec();
 
 //..... 없으면 없는 것들은 set로 pipeline 열어서 설정(예를 들어서 keys 배열에 1, 2번째 값이 없다고 가정)
 const setPipeline = obj.node.pipeline();
 
 pipeline.set(obj.keys[1]);
 pipeline.set(obj.keys[2]);
 
 await setPipeline.exec();
}

 

소스를 모두 다 적기에는 양도 많기 때문에 간단하게 내가 진행한 방식에 대한 설명만 적었다.

이 글을 보고 방법을 찾고자 하는 사람들에게 도움이 되지 않을수도 있지만  클러스터 모드에서 레디스의 멀티키 사용법에 대해 자료가 없어서 고민을 많이 했었기 때문에 작은 힌트라도 되었으면 좋겠다.

댓글()

sequelize에서 조인 시 left join이 되지 않을 때 처리하는 방법

web/node.js|2018. 12. 19. 21:57

sequelize에서 조인을 할때는 include를 사용해서 다음과 같이 한다.


1
2
3
4
5
6
await This.User.findOne({
  attributes: ['id', ['name''userName']],
  include: [
    { model: this.Dept}
  ]
});
cs

물론 기존에 Model을 define할 때 연관관계를 설정을 해놓은 상태여야 하고 이렇게 할 경우에 나는 left join이 아니라 inner join으로 다음과 같이 되었다.


SELECT id, name as userName FROM User u inner join Dept d on u.userId = d.userId;

그래서 검색해서 알아보다보니 required 옵션을 부여하게 되면 정상적으로 left join이 된다고 알게되었다. 

그래서 붙이니 정상적으로 되었다.


1
2
3
4
5
6
await This.User.findOne({
  attributes: ['id', ['name''userName']],
  include: [
    { model: this.Dept, required: false}
  ]
});
cs


별개로 조인을 하려고 하는데 계속 서브쿼리가 만들어진다면 subquery:false 옵션을 주면 문제를 해결 할 수 있다.

댓글()

sequelize 사용시 테이블 이름 변동없이 고정 Alias 사용방법

web/node.js|2018. 12. 19. 21:38

sequelize는 마찬가지로 ORM을 사용하다보니 직접적으로 쿼리를 사용하는 것보다 정확하게 알지못하면 역시 개발속도도 늦어지고 문제가 많아지는 단점이 있다.


이번에는 sequelize를 사용하는데 조인할 때 테이블 이름이 갑자기 User에서 Users로 바뀌는 이슈가 발생했다.


이 이슈를 해결하기 위해서 sequelize Document를 검색했고 거기서 freeTableName 옵션을 발견했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const Bar = sequelize.define('bar', { /* bla */ }, {
  // don't add the timestamp attributes (updatedAt, createdAt)
  timestamps: false,
  // don't delete database entries but set the newly added attribute deletedAt
  // to the current date (when deletion was done). paranoid will only work if
  // timestamps are enabled
  paranoid: true,
 
  // don't use camelcase for automatically added attributes but underscore style
  // so updatedAt will be updated_at
  underscored: true,
  // disable the modification of table names; By default, sequelize will automatically
  // transform all passed model names (first parameter of define) into plural.
  // if you don't want that, set the following
  freezeTableName: true,
 
  // define the table's name
  tableName: 'my_very_custom_table_name',
  // Enable optimistic locking.  When enabled, sequelize will add a version count attribute
  // to the model and throw an OptimisticLockingError error when stale instances are saved.
  // Set to true or a string with the attribute name you want to use to enable.
  version: true
})
cs

설명 그대로 이름이 plural로 바뀌는 것을 방지 하고 singular로 사용할 수 있게 해주는 옵션이다.



댓글()

node js에서 stream pipe 사용시 에러 처리 방법

web/node.js|2018. 11. 9. 22:28

node js에서 데이터를 stream을 사용하여 처리하고 pipe를 사용해서 계속해서 stream을 가지고 작업을 이어나갈 수 있다. 그런데 pipe를 통해서 작업을 진행하다 보니까 중간에 오류가 발생했을 때 try / catch 로는 정상적으로 처리하지 못하는 경우가 발생했다. 

나에 경우에는 에러가 발생했을 때 try / catch에서 잡히지 않아서 프로그램이 Unhandled Promise Rejections를 출력 하며 죽어버렸다.

그 예는 다음과 같이 request를 통해서 받은 이미지를 sharp 라이브러리를 통해서 이미지 크기를 변경하려고 할 때 발생했다.

1
try {
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').pipe(transformer).pipe(writeStream);
} catch (e) {
console.error(e);
}
cs


그래서 이를 처리하기 위해서 알아봤는데 각 파이프라인에서 발생하는 에러를 처리하기 위해서는 try/catch로만 잡을 수가 없다. 그래서 이를 해결하기 위해서 각 파이프 앞단에서 error 이벤트를 잡는 설정을 해줘야한다.


1
2
3
4
5
6
7
8
// 파일로 쓰기
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').on('error', function (e) {
  console.error(e);
}).pipe(transformer).on('error', function (e) {
  console.error(e);
}).pipe(writeStream).on('error', function (e) {
  console.error(e);
});
cs


하지만 이렇게만 하면 에러는 잡을 수 있어도 pipe에서 행이 걸리는 경우가 발생된다. 그래서 에러가 발생했을 때 행 걸리지 않고 다음 로직으로 정상적으로 처리되도록 하기 위해서는 this.emit('end')를 넣어줘야 한다.


1
2
3
4
5
6
7
8
9
10
11
// 파일로 쓰기
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').on('error', function (e) {
  console.error(e);
  this.emit('end');
}).pipe(transformer).on('error', function (e) {
  console.error(e);
  this.emit('end');
}).pipe(writeStream).on('error', function (e) {
  console.error(e);
  this.emit('end');
});
cs


참고

https://stackoverflow.com/questions/21771220/error-handling-with-node-js-streams


댓글()

Elasticsearch query string 조회시 parse exception 에러 처리

elasticsearch에서 query_string로 데이터 조회시에 쿼리문으로 ) 특수문자가 포함하여 조회했다. 하지만 다음과 같이 문제가 발생했다.


1
2
3
4
5
6
7
8
9
10
11
{
  "error": {
    "root_cause": [
      {
        "type": "parse_exception",
        "reason": "parse_exception: Encountered \" \")\" \") \"\" at line 1, column 11.\nWas expecting one of:\n    <EOF> \n    <AND> ...\n    <OR> ...\n    <NOT> ...\n    \"+\" ...\n    \"-\" ...\n    <BAREOPER> ...\n    \"(\" ...\n    \"*\" ...\n    \"^\" ...\n    <QUOTED> ...\n    <TERM> ...\n    <FUZZY_SLOP> ...\n    <PREFIXTERM> ...\n    <WILDTERM> ...\n    <REGEXPTERM> ...\n    \"[\" ...\n    \"{\" ...\n    <NUMBER> ...\n    "
      }
    ],
    "type": "search_phase_execution_exception",
    "reason": "all shards failed",
    "phase": "query",
cs


확인해보니 + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ / 포함된 문장을 query_string 통해서 조회하려고 하면 에러를 발생시킨다. 그래서 이를 해결하기 위해서 위에 reserved character들이 들어간 단어는 \\를 붙여주어야 한다.


이를 위한 자바스크립트는 다음과 같다.

1
2
3
async escapeReservedCharacter(query) {
  return query.replace(/([!*+&|()<>[\]{}^~?:\-="/\\])/g, '\\$1');
}
cs


이를 해결해서 query_string을 사용하면 문제가 해결된다.


참고 : https://stackoverflow.com/questions/26431958/escaping-lucene-characters-using-javascript-regex




댓글()

maxmind의 geoLite2를 이용해서 접속한 사용자의 지역정보 가져오기

web/node.js|2018. 10. 16. 23:57

서비스를 운영하다보면 사용자 아이피에 따라 장소에 맞는 상품을 추천해줘야 할 때가 있다.

그럴때 사용하는게 geoIp인데 이런 서비스를 제공하는 회사는 대표적으로 maxmind, db-ip, ipstack, ip2location 등이 존재한다.


그 중에 무료로 사용하기에 geoLite2가 좋다. geoLite2는 62%가 일치하고 geoIp2는 66% 일치한 정보를 제공한다. 그래서 무료로 사용할 수 있는 geoLite2를 사용해보자.


데이터베이스 다운로드

먼저 지역정보를 보관하고 있는 데이터베이스를 다운받아야 한다. 데이터베이스는 csv와 mmDB를 제공한다.  

링크 : https://dev.maxmind.com/geoip/geoip2/geolite2/ 


mmDB에서 데이터를 확인하기 어렵기 때문에 csv 파일에서 제공하는 정보를 보자. 우리가 필요한 정보는 사용자가 접속한 city (도시)정보가 필요한데 그 필드가 geoname_id로 되어 있다. 그 geoname_id는 지역에 고유 아이디로 이 아이디에 맞는 city 정보를 찾는 방법은 아래 주소를 통해 xml 형태로 받아 볼 수 있다. username 값은 회원가입을 통해 등록 할 수있다.

1
http://ws.geonames.org/get?geonameId=${geoname_id}&style=full&username=${userName}
cs


그럼 node.js에서 이 데이터베이스와 사용자 ip를 사용하여 city 정보를 추출하는 방법을 확인해보자.


우선 아래 주소에서 maxmind npm을 다운로드 받는다.

https://www.npmjs.com/package/maxmind


그리고 maxmind 라이브러리를 사용해서 데이터베이스를 로드하고 ip를 사용하여 지오로케이션 정보를 조회 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'use strict';
 
const maxmind = require('maxmind');
const path = require('path');
 
class MaxMindService {
 
  getIp(userIp) {
    const cityLookUp = maxmind.openSync(path.join(__dirname, './db/GeoLite2-City.mmdb'));
    const city = cityLookUp.get(userIp);
 
    return city;
  }
 
};
 
 
module.exports = new MaxMindService();
cs


테스트 코드를 만들어서 결과를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
'use strict';
 
const geoIp = require('../../lib/maxMind');
 
 
describe('geo IP', function () {
  it('아이피 정보로 지역정보 찾아보기', () => {
    console.log(geoIp.getIp('121.131.27x.1xx'));
  });
 
});
cs


결과를 보면 ip에 맞는 위치정보가 출력된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
{ city: { geoname_id: 1841277, names: { en: 'Mapo-gu' } },
  continent: 
   { code: 'AS',
     geoname_id: 6255147,
     names: 
      { de: 'Asien',
        en: 'Asia',
        es: 'Asia',
        fr: 'Asie',
        ja: 'アジア',
        'pt-BR''Ásia',
        ru: 'Азия',
        'zh-CN''亚洲' } },
  country: 
   { geoname_id: 1835841,
     iso_code: 'KR',
     names: 
      { de: 'Südkorea',
        en: 'Republic of Korea',
        es: 'Corea del Sur',
        fr: 'Corée du Sud',
        ja: '大韓民国',
        'pt-BR''Coreia do Sul',
        ru: 'Южная Корея',
        'zh-CN''大韩民国' } },
  location: 
   { accuracy_radius: 10,
     latitude: 37.5544,
     longitude: 126.9093,
     time_zone: 'Asia/Seoul' },
  registered_country: 
   { geoname_id: 1835841,
     iso_code: 'KR',
     names: 
      { de: 'Südkorea',
        en: 'Republic of Korea',
        es: 'Corea del Sur',
        fr: 'Corée du Sud',
        ja: '大韓民国',
        'pt-BR''Coreia do Sul',
        ru: 'Южная Корея',
        'zh-CN''大韩民国' } },
  subdivisions: [ { geoname_id: 1835847, iso_code: '11', names: [Object] } ] }
Process finished with exit code 0
 
cs


댓글()

nodejs 비동기 프로그래밍을 위한 deferred

web/javaScript|2018. 10. 6. 23:57

node.js에서 비동기 프로그래밍을 위해서 사용할 수 있는 deferred 라이브러리를 정리해보자.


우선 deferred의 경우에는 이전글에 작성했던 Promise와 동일한 개념이다. (https://wedul.tistory.com/508) promise와 마찬가지로 deferred는 비동기로 작업을 진행하고 비동기 처리가 완료되고 resolve, reject 메소드를 실행해서 비동기 동작 이후에 결과를 전달 할 수 있다.


우선 필요한 라이브러리를 다운로드 받아보자. 

https://www.npmjs.com/package/deferred

1
npm i deferred
cs


그리고 라이브러리 예제에 나와있는대로 deferred 라이브러리를 사용해서 비동기 프로그램을 실행시켜보면 간단하게 promise와 동일 한 방식으로 진행할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
'use strict';
 
const deferred = require('deferred');
const promiseTest = require('./promise-test');
const deferredTest = module.exports;
 
deferredTest.deferTest = () => {
  return () => {
    const def = deferred();
 
    setTimeout(async () => {
      try {
        let dd = await promiseTest.proTest(true);
 
        if (dd.message == 'pnpsecure') {
          def.reject('Error');
        } else {
          def.resolve(dd);
        }
      } catch (e) {
        def.reject(e);
      }
    }, 1000);
 
    return def.promise;
  };
};
 
 
 
// 테스트 
const deferredTest = require('../../lib/libraryTest/deferred-test');
 
 it('deferred Test', async () => {
    try {
      let dd = await deferredTest.deferTest()();
      console.log(dd);
    } catch (e) {
      console.log(e);
    }
 
  });
cs


댓글()

node.js에서 aws s3 스토리지에 이미지 저장하기

web/node.js|2018. 10. 6. 01:55

AWS에서 제공하는 S3 스토리지는 다양한 파일을 bucket에 보관할 수 있다. 자세한 내용은 생활코딩 강의 참고하면 좋다. https://opentutorials.org/course/608/3006


그럼 우선 aws sdk를 설치하여야 한다.

1
npm i aws-sdk --save
cs


그리고 AWS IAM에서 생성한 사용자가 있어야 한다. 사용자는 S3에 대한 권한을 가지고 있어야 한다.


사용자를 생성하고 권한을 부여하는 기능은 어렵지 않기때문에 검색해서 적용하면 된다.

그리고 해당 사용자를 생성하면 csv 파일로 secretkey를 받을 수 있다. 이를 AWS S3에 접근하여 사용하기 위해서 사용된다.




기본 정보가 담긴 config.json 생성

우선 부여 받은 사용자 accessKeyId와 secretAccessKey 그리고 지역정보가 담긴 config.json 파일을 만든다.

1
2
3
4
5
{
  "accessKeyId""<accessKeyId>",
  "secretAccessKey""<secretAccessKey>",
  "region""ap-northeast-2"
}
cs


그리고 aws-sdk 라이브러리를 로드하면서 위에 생성한 config.json 파일을 추가해준다. 그리고 S3 객체의 경우 로드한 aws-sdk에서 new와 함께 s3() 메소드를 실행하여 만들 수 있다.

1
2
3
4
5
6
'use strict';
 
const AWS = require('aws-sdk');
const path = require('path');
AWS.config.loadFromPath(path.join(__dirname, 'config.json'));
const S3 = new AWS.S3();
cs


그리고 이미지 파일을 업로드 하기위해서 제공하는 메소드인 upload에는 bucket 이름, 파일의 고유 키, 파일이 담긴 Body, Body의 contentType 정보가 필요하다. 나는 이미지를 올리려고 하기 때문에 contentType은 image/jpg로 하였다.

1
2
3
4
5
   const params = {};
    params.Key = `image/${md5(url)}.jpg`;
    params.Bucket = bucket;
    params.Body = await request(url);
    params.contentType = 'image/jpg';
cs

생성된 최종 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
'use strict';
 
const AWS = require('aws-sdk');
const path = require('path');
AWS.config.loadFromPath(path.join(__dirname, 'config.json'));
const S3 = new AWS.S3();
const md5 = require('md5');
const request = require('request-promise');
const bucket = 's3-buckets';
 
class AwsS3 {
 
  async upload(url)  {
    const params = {};
    params.Key = `image/${md5(url)}.jpg`;
    params.Bucket = bucket;
    params.Body = await request(url);
    params.contentType = 'image/jpg';
 
    S3.upload(params, function (err, data) {
      console.log(err);
      console.log(data);
    });
  }
 
}
 
module.exports = new AwsS3();
 
cs


그리고 테스트 코드를 작성하여 동작 시켜보면 정상적으로 파일이 올라간것을 확인 할 수 있다.

1
2
3
4
5
6
7
8
9
'use strict';
 
const s3 = require('../../lib/s3');
 
describe('s3 test', () => {
  it('upload', async () => {
    await s3.upload('http://ticketimage.interpark.com/Play/image/large/18/18009670_p.gif');
  });
});
cs


 이런 문제가 발생하면 config.json에 기재한 키 정보를 다시한번 확인해봐야한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{ InvalidAccessKeyId: The AWS Access Key Id you provided does not exist in our records.
    at Request.extractError (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/services/s3.js:580:35)
    at Request.callListeners (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/sequential_executor.js:109:20)
    at Request.emit (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/sequential_executor.js:81:10)
    at Request.emit (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/request.js:683:14)
    at Request.transition (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.<anonymous> (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/request.js:685:12)
    at Request.callListeners (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/sequential_executor.js:119:18)
    at Request.emit (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/sequential_executor.js:81:10)
    at Request.emit (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/request.js:683:14)
    at Request.transition (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.<anonymous> (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/request.js:685:12)
    at Request.callListeners (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/sequential_executor.js:119:18)
    at callNextListener (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/sequential_executor.js:99:12)
    at IncomingMessage.onEnd (/Users/wedul/Documents/repository/nodejs/node_modules/aws-sdk/lib/event_listeners.js:294:13)
    at emitNone (events.js:111:20)
    at IncomingMessage.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1064:12)
    at _combinedTickCallback (internal/process/next_tick.js:139:11)
    at process._tickDomainCallback (internal/process/next_tick.js:219:9)
  message: 'The AWS Access Key Id you provided does not exist in our records.',
  code: 'InvalidAccessKeyId',
  region: null,
  time: 2018-10-01T15:53:23.570Z,
  cfId: undefined,
  statusCode: 403,
  retryable: false,
  retryDelay: 32.0943451721504 }
 
cs


참고 URL
https://www.npmjs.com/package/aws-sdk
https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/s3-example-creating-buckets.html

https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-json-file.html

댓글()