젊은 프로젝트에서 작업할 때 나중에 확장하기 더 쉽거나 더 어렵게 만드는 결정을 끊임없이 내립니다. 때로는 더 빨리 배송할 수 있도록 약간의 기술적 부채를 쌓기 위해 단기 이익을 선택하는 것이 좋습니다. 그러나 다른 경우에는 대안이 있는지 몰랐기 때문에 기술적인 부채를 떠안게 됩니다.
내가 이렇게 말할 수 있는 이유는 여기 Honeybadger에서 우리가 삶을 원래보다 훨씬 더 힘들게 만드는 몇 가지 일을 했기 때문입니다. 몇 가지 핵심 사항을 이해했다면 스케일링이 훨씬 덜 고통스러웠을 것입니다.
UUID 사용
"기본 키"라고 하면 대부분의 사람들이 자동 증가하는 숫자를 생각합니다. 이것은 소규모 시스템에서 잘 작동하지만 확장함에 따라 큰 문제가 발생합니다.
주어진 순간에 단 하나의 데이터베이스 서버만 기본 키를 생성할 수 있습니다. 이는 모두를 의미합니다. 쓰기는 단일 서버를 거쳐야 합니다. 초당 수천 개의 쓰기 작업을 수행하려는 경우 나쁜 소식입니다.
UUID를 기본 키로 사용하면 이 문제를 피할 수 있습니다. 익숙하지 않은 경우 UUID는 다음과 같은 고유 식별자입니다. 123e4567-e89b-12d3-a456-426655440000
.
Wikipedia에서 설명하는 방법은 다음과 같습니다.
<블록 인용>표준 방법에 따라 생성될 때 UUID는 실제 목적을 위해 고유하며 중앙 등록 기관이나 생성 당사자 간의 조정이 필요하지 않습니다. UUID가 복제될 확률은 0이 아니지만 무시할 수 있을 정도로 0에 가깝습니다.
따라서 누구나 UUID를 생성하고 이를 사용하여 식별자가 이미 다른 것을 식별하기 위해 생성된 것과 중복되지 않고 미래에 중복되지 않을 것이라는 거의 확신을 가지고 무언가를 식별할 수 있습니다. 따라서 독립 당사자가 UUID로 레이블을 지정한 정보는 식별자 간의 충돌을 해결할 필요 없이 나중에 단일 데이터베이스로 결합되거나 동일한 채널에서 전송될 수 있습니다.
UUID를 기본 키로 사용하면 모든 쓰기가 더 이상 단일 데이터베이스를 통과할 필요가 없습니다. 대신 여러 서버에 배포할 수 있습니다.
또한 이전 레코드의 ID를 생성하는 등의 작업을 유연하게 수행할 수 있습니다. 데이터베이스에 저장됩니다. 이것은 레코드를 캐시나 검색 서버로 보내고 싶지만 데이터베이스 트랜잭션이 완료될 때까지 기다리지 않으려는 경우에 유용할 수 있습니다.
Rails 앱에서 기본적으로 UUID를 활성화하는 것은 쉽습니다. 구성 파일을 수정하기만 하면 됩니다.
# config/application.rb
config.active_record.primary_key = :uuid
Rails 마이그레이션과 함께 개별 테이블에 UUID를 사용할 수 있습니다.
create_table :users, id: :uuid do |t|
t.string :name
end
개발을 시작할 때 활성화하면 확장을 시도할 때 문제의 세계를 구할 수 있는 간단한 구성 옵션입니다. 추가 보너스로 봇과 악의적인 사용자가 개인 URL을 추측하기가 더 어려워집니다.
카운트 및 카운터
일단 찾기 시작하면 카운트와 카운터가 도처에 있습니다. 이메일 클라이언트는 읽지 않은 이메일의 수를 표시합니다. 블로그에는 총 게시물 수를 사용하여 페이지 수를 계산하는 페이지 매김 바닥글이 있습니다.
카운터에는 두 가지 크기 조정 문제가 있습니다.
select count(*) from users
와 같은 데이터베이스 쿼리 본질적으로 느립니다. 문자 그대로 레코드 집합의 각 레코드를 반복하여 결과를 생성합니다. 백만 개의 레코드가 있으면 시간이 걸립니다.- "카운터 캐시"를 사용하여 카운터 속도를 높이려는 시도는 효과가 있지만 많은 데이터베이스 서버에 쓰기를 분산하는 기능을 제한합니다.
가장 쉬운 해결책은 가능한 카운터를 사용하지 않는 것입니다. 이것은 초기 디자인을 할 때 훨씬 쉽습니다.
예를 들어 개수 대신 날짜 범위로 페이지를 선택하도록 선택할 수 있습니다. 나중에 생성할 지옥 같은 약간 유용한 통계를 표시하지 않도록 선택할 수 있습니다. 당신은 아이디어를 얻을.
만료 및 창고 데이터
보유하고 있는 RAM, CPU 및 디스크 IO의 양을 고려할 때 단일 postgres 테이블에 저장할 수 있는 데이터 양에는 상한선이 있습니다. 즉, 기본 테이블에서 오래된 데이터를 이동해야 하는 시점이 올 것입니다.
간단한 경우를 살펴보자. 몇 기가바이트 크기의 데이터베이스에서 1년 이상 된 레코드를 삭제하려고 합니다.
이전에 이 문제를 처리한 적이 없다면 다음과 같이 하고 싶은 유혹을 받을 수 있습니다.
MyRecords.where("created_at < ?", 1.year.ago).destroy
문제는 이 쿼리를 실행하는 데 며칠 또는 몇 주가 걸린다는 것입니다. 데이터베이스가 너무 큽니다.
이것은 특히 고통스러운 문제입니다. 너무 늦을 때까지 자신이 가지고 있다는 사실을 깨닫지 못하는 경우가 많기 때문입니다. 회사가 젊고 데이터베이스에 1,000개의 레코드가 있을 때 데이터 제거 전략에 대해 생각하는 사람은 거의 없습니다.
미리 계획을 세우면 쉬운 해결책이 있습니다. 테이블을 파티션하기만 하면 됩니다. 모든 데이터를 my_records
에 쓰는 대신 이번 주 데이터를 my_records_1
에 씁니다. 다음 주 데이터를 my_records_2
로 . 지난 주를 삭제할 때가 되면 drop table my_records_1
하세요. . 삭제와 달리 이 쿼리는 매우 빠르게 완료됩니다.
날짜 이외의 필드로 파티션을 나누는 것도 가능합니다. 귀하의 사용 사례에 의미가 있는 것은 무엇이든 상관없습니다.
모든 세부 사항을 처리하고 코드 줄을 변경하지 않고 데이터베이스를 분할할 수 있는 pg_partman이라는 postgres 확장도 있습니다. 또는 Ruby에서 분할 관리를 선호하는 경우 분할 가능이라는 편리한 보석이 있습니다.
이별의 말
다음 번에 처음부터 프로젝트를 빌드하는 자신을 발견하면 잠시 시간을 내어 확장에 대해 생각해 보시기 바랍니다. 그것에 집착하지 마십시오. HAML을 사용할지 ERB를 사용할지 고민하느라 며칠을 보내지 마세요. 그러나 단순히 미리 계획함으로써 얻을 수 있는 쉬운 승리가 없는지 자문해 보십시오.