본문 바로가기
Ruby on Rails

dynamoid gem으로 LSI 사용하기

by 혜리루 2021. 6. 20.

 

dynamoid는 dynamodb의 table을 ruby on rails의 active record와 비슷한 문법으로 쿼리할 수 있게 해주는 gem입니다.

그런데 rails 프로젝트에서 dynamoid gem을 사용하려고 보니 아무리 찾아도 LSI로 쿼리를 하는법을 찾을 수가 없었습니다.

 

공식문서에도 정확한 방법이 나와있지 않았는데요, 수일간의 삽질을 통해 발견한 LSI의 사용법을 남기려고 합니다.

 

 

initializers/dynamoid.rb

Dynamoid.configure do |config|
  config.access_key = Rails.application.credentials.dig(:aws_account, :access_key_id)
  config.secret_key = Rails.application.credentials.dig(:aws_account, :secret_access_key)
  config.region = Rails.application.credentials.dig(:aws_account, :region)
  config.namespace = ''
end

 

먼저 initializer에서 aws credential을 설정해줍니다.

namespace를 지정하지않으면 기본으로 dynamoid_table 이라는 namespace가 붙는데요,

dynamodb에 미리 생성해놓은 테이블이 있다면 namespace를 빈 string으로 지정해주어야합니다.

 

 

model/test_model.rb

class TestModel
  include Dynamoid::Document

  table name: :TestTable, key: :TablePK, range_key: :TableSK
  
  end

그리고 dynamodb 테이블의 모델 클래스를 작성해줍니다.

table의 이름, key(partition key), 존재한다면  range_key(sort key)를 명시해줍니다.

 

가장 중요한 부분은 key를 꼭 작성해주어야한다는 점입니다.

 

table의 base key만 사용하거나 GSI를 사용할때는 key의 명시 여부가 크게 중요하지않은데요,

LSI는 table과 같은 partition key를 사용하기 때문에 LSI를 사용할때는 dynamoid가 table의 key 속성을 먼저 확인합니다.

때문에 key가 꼭 필요합니다. 잊지 말고 작성해줍시다. 이 부분 때문에 한참 헤맸네요..

 

class TestModel
  include Dynamoid::Document

  table name: :TestTable, key: :TablePK, range_key: :TableSK

  field :TableSK
  field :Type

  local_secondary_index name: 'TablePK-Type-index', range_key: :Type, projected_attributes: :all
  
end

key를 포함한 dynamodb의 field도 설정해줍니다.

필드는 default로 string으로 설정되기 때문에 다른 type을 사용하고있다면 type을 따로 명시해주어야합니다.

설정 방법은 dynamoid 공식 문서에서 참고할 수 있습니다.

 

LSI 설정은 위처럼 똑같이 해주면 됩니다.

partition key는 꼭 작성해줄 필요업지만 name과 range_key는 필수입니다.

그리고 주의해야할 점은 projected_attributes옵션인데요,  active record처럼 where 절을 이용해서 인덱스를 쿼리하고싶다면 

projected_attributes 옵션을 꼭 all로 설정해주어야합니다.

index에 따라서 모든 attribute를 project하지않고 key만, 혹은 일부 attribute만 project를 할 수도 있는데요, 이 경우에는 안타깝게도 where 절을 이용한 orm 스러운..! 쿼리가 불가능합니다.

 

The only caveat with this method is that because it is also used for general querying, it WILL NOT use a GSI unless it explicitly has defined projected_attributes: :all on the GSI in your model. This is because GSIs that do not have all attributes projected will only contain the index keys and therefore will not return objects with fully resolved field values. It currently opts to provide the complete results rather than partial results unless you've explicitly looked up the data.

Future TODO could involve implementing select in chaining as well as resolving the fields with a second query against the table since a query against GSI then a query on base table is still likely faster than scan on the base table

출처: dynamoid 공식문서 https://github.com/Dynamoid/dynamoid

 

즉 projected_attributes를 all로 설정해놓지않으면 dynamoid는 해당 index를 타지않고 엄한 index를 타거나 그냥 테이블을 scan 해버릴 것입니다. 이후 업데이트 버전에서는 전체 attribute가 아니라 일부 attribute를 project하는 옵션을 지원해줄 것으로 보이니 그때까지 기다려보(거나 커밋을 시도해보도록 합)시다.

 

 

허무하게도 이게 끝입니다. 하하...

이제 모델에 where절을 써보면 잘 작동할 것입니다.

 

TestModel.where(TablePK: 'pk1', Type: 'type1')
=> #<Dynamoid::Criteria::Chain:0x00007f85d94ed390
 @consistent_read=false,
 @key_fields_detector=
  #<Dynamoid::Criteria::KeyFieldsDetector:0x00007f85d94ec3a0
   @forced_index_name=nil,
   @query=
    #<Dynamoid::Criteria::KeyFieldsDetector::Query:0x00007f85d94ec350
     @fields=["TablePK", "Type"],
     @fields_with_operator=["TablePK", "Type"],
     @query_hash={:TablePK=>"pk1", :Type=>"type1"}>,
   @result={:hash_key=>:TablePK, :range_key=>:Type, :index_name=>"TablePK-Type-index"},
   @source=TestTable>,
 @query={:TablePK=>"RECEIVER#pushdome", :Type=>"SINSANGTALK:1623654867.69781"},
 @scan_index_forward=true,
 @source=TestTable>

위처럼 partition key와 sort key를 각각 where절 안에 써주면 dynamodb는 이 정보를 model에 설정해둔 인덱스와 비교하고,

일치하는 인덱스가 있다면 사용할 index, 필드, 정렬 방향등이 담긴 Dynamoid 모듈의 chain 오브제트를 반환합니다.

하지만 dynamoid도 기본적으로 lazy loading을 지원하기때문에 where절만 사용했을때는 실제로 쿼리가 날아가진않습니다.

 

 

first 메소드로 실제 값을 받아봅시다.

test_model = TestModel.where(TablePK: 'key1', Type: 'type1').firstpe: 'aa').first
//[Aws::DynamoDB::Client 200 2.561447 0 retries] describe_table(table_name:"TestTable")  

//[Aws::DynamoDB::Client 200 0.057141 0 retries] query(consistent_read:false,scan_index_forward:true,index_name:"TablePK-Type-index",limit:1,table_name:"TestTable",key_conditions:{"TablePK"=>{comparison_operator:"EQ",attribute_value_list:[{s:"aa"}]},"Type"=>{comparison_operator:"EQ",attribute_value_list:[{s:"aa"}]}},query_filter:{},attributes_to_get:nil)  

쿼리를 실제로 날리면 로그에서 사용한 index와 필드등에 대한 정보를 확인할 수 있습니다.

 

test_model.class

=> TestModel

test_model.Type

=> "type1"

class도 model 명으로 잘 나오고 메소드를 이용해서 필드의 값도 잘 확인이 되네요.

 

이렇게 결과만 놓고 확인해보니 별 내용은 없었는데요, 공식문서에는 model에 key를 꼭 명시해줘야한다는 내용이 없어서 한참을 헤맸던 것 같습니다. 역시 이럴때는 그냥 코드를 뜯어보는게 최고인것 같아요...

 

제 삽질이 누군가에게 또 도움이 된다면 좋겠습니다. 그럼 이만

댓글