[js] localStorage 키별 용량 제약 처리

용량제약의 문제

localStorage는 도메인별 용량 제한이 있습니다. 보통 5메가 정도를 부여받는데 이는 브라우저별로 매우 상이합니다. 특히 localStorage, sessionStorage, globalStorage 의 정책도 전부 제각각입니다. 도메인에 할당된 용량은 객체에서 직접 지원하는 메서드를 통해 확인할 수 있습니다.

console.log( localStorage.remainingSpace );

헌데 전혀 다른 문제가 존재합니다. 도메인 전체에 할당된 용량제약만 있는 것이 아니라 개별 키에도 용량제약이 존재한다는 사실입니다. 정확한 용량제약의 기준도 없고 이를 확인할 수 있는 메서드도 없습니다. 그저 setItem을 할 때 예외를 토하고 죽어버립니다.

var value = "상당히 큰 text";
localStorage.setItem( 'test', value );

위의 소스에서 자바스크립트가 멈춰버리고 전체가 작동중지됩니다. 브라우저마다 하나의 키에 넣을 수 있는 용량에 대한 정확한 제약조건을 알 수는 없는 노릇입니다.

 
 

예외를 이용하여 쪼개서 저장하기

일단은 전체 스크립트가 죽어버리므로 setItem은 함부로 할 수 없습니다.

try{
	localStorage.setItem( 'test', value );
}catch(e){

}

 

이 구조를 바탕으로 실패하면(catch에 들어오면) 용량을 점점 쪼개본다 라는 전략을 사용해보죠. 알고리즘은 다음과 같이 간단히 구성합니다.

  1. 무한루프를 걸고
  2. try가 무사히 처리되면 break로 루프를 빠져나온다.
  3. catch에 걸릴때마다 점점 더 잘게 나눠서 저장한다.
  4. 나눠서 저장한 경우는 해당 키에 특수한 형태로 쪼개져있다는 정보를 남겨준다.

뭐 간단합니다. 기본골격부터 만들죠

//무한루프
while(1){

	try{
		if( 쪼개진 적이 없다면 ){

			 localStorage.setItem( 'test', value );

		}else{

			//해당키에는 특수한 값을 넣고 쪼개진 데이터를 별도로 나눠서 저장함

		}
		break; //루프를 빠져나감

	}catch(e){
		//더 쪼개는 장치
	}
}

어느정도 골격이 잡혔으니 쪼개는 전략을 고민해보면 가장 단순하게는 절반으로 나눠서 넣어보고 안되면 1/3으로 나누고 안되면 1/4로 나누는 식으로 짜볼 수 있습니다. 분모를 점점 증가시켜가면 되는거죠.

//분모설정
var deno = 0; //최초는 쪼개지 않음

while(1){
	try{
		if( !deno ){//쪼개진적이 없음
			localStorage.setItem( 'test', value );
		}else{
			//특수한 값으로 저장 '--분모' 형태
			localStorage.setItem( 'test', '--' + deno );

			//분모를 통해 한번에 저장할 크기를 얻는다
			var partSize = Math.ceil( value / deno );

			//분모만큼 루프를 돌면서 분할하여 저장
			for( var i = 0 ; i < deno ; i++ ){
				localStorage.setItem( 'test::' + i, value.substr( i * partSize, partSize );
			}

			break;
	}catch(e){
		//실패시마다 분모를 증가시킨다
		deno++;
	}
}

위 코드는 결국 try catch가 성공할때까지 루프를 돌며 점점 작게 쪼개서 넣게 됩니다. 실제 사이즈가 커서 쪼개지면 다음과 같은 형태로 저장될 것입니다.

'test' = '--3'
'test::0' = '....'
'test::1' = '....'
'test::2' = '....'

즉 쪼개져있는 경우는 해당 키를 얻으면 –로 시작하는 값이므로 이를 바탕으로 값을 모아 조합하는 힌트가 됩니다.

 
 

쪼개진 값을 다시 조합하기

위의 힌트를 이용해 재조립만 하면 되므로 사실 간단합니다.

var data = localStorage.getItem('test');

if( data.substr(0,2) == '--' ){ //특수한 형태다!
	var temp = '', i, j;
	for( i = 0, j = parseInt(data.substr(2)) ; i < j ; i++ ){

		//쪼개진 조각을 한데 모아서 합체!
		temp += localStorage.getItem('test::'+i);
	}
	data = temp;
}

console.log( data );

setItem할때 사용한 프로토콜을 이용해 복원코드를 넣으면 간단히 해결됩니다. 마찬가지로 삭제도 간단히 처리됩니다.

var data = localStorage.getItem('test');

if( data.substr(0,2) == '--' ){ //특수한 형태다!
	for( var i = 0, j = parseInt(data.substr(2)) ; i < j ; i++ ){

		//쪼개진 조각을 전부 삭제!
		localStorage.removeItem('test::'+i);
	}
}

//최종적으로 키도 삭제
localStorage.removeItem('test');

 
 

결론

평화롭고 짧막한 로컬스토리지 사용이 키별 용량제약으로 순식간에 지옥이 되고 맙니다. IE11의 경우 하나의 키에 들어갈 수 있는 용량이 얼마 안되기 때문에 상당히 괴롭습니다.
부디 고통 받는 영혼들이 계시면 작은 도움이 될 수 있길 바랍니다. 함수형태로 간단히 정리된 코드는 다음과 같습니다.

function setItem( k, v ){
	var deno = 0, i, j;
	while(1){
		try{
			if( !deno ) localStorage.setItem( k, v );
			else{
				localStorage.setItem( k, '--' + deno );
				j = Math.ceil( v / deno );
				for( i = 0 ; i < deno ; i++ )
					localStorage.setItem( k + '::' + i, v.substr( i * j, j );
			}
			break;
		}catch(e){
			deno++;
		}
	}
}
function getItem(k){
	var data = localStorage.getItem(k), temp, i, j;
	if( data.substr(0,2) == '--' ){
		for( temp = '', i = 0, j = parseInt(data.substr(2)) ; i < j ; i++ )
			temp += localStorage.getItem( k + '::' + i );
		data = temp;
	}
	return data;
}
function removeItem(k){
	var data = localStorage.getItem(k), i, j;
	if( data.substr(0,2) == '--' ){
		for( i = 0, j = parseInt(data.substr(2)) ; i < j ; i++ )
			localStorage.removeItem( k + '::' + i );
	}
	localStorage.removeItem(k);
}