Zarr قالب و کتابخانهای برای ذخیرهسازی آرایههای چندبعدی (N-dimensional arrays) است که با تقسیم داده به قطعههای مستقل (chunk)، فشردهسازی شفاف، متادیتای خود-توصیفگر و پشتیبانی از backendهای گوناگون (فایلسیستم، شیء-ذخیره مانند S3/GCS، zip و حافظه) امکان مقیاسپذیری و دسترسی تصادفی کارآمد را فراهم میکند.
Zarr چیست و کجا به کار میآید؟
Zarr یک فرمت ذخیرهسازی «ساده اما قدرتمند» برای آرایههای بزرگ است که بهویژه در دادههای علمی، سنجشازدور، مدلهای اقلیمی، تصویربرداری، ژنومیک و یادگیری ماشین کاربرد دارد. ایدهٔ مرکزی Zarr این است که هر آرایه به شبکهای از قطعهها (chunks) تقسیم میشود؛ هر chunk مستقل فشرده و بهصورت کلید/مقدار ذخیره میگردد. این طراحی چند مزیت کلیدی دارد:
- دسترسی تصادفی سریع: فقط chunkهای موردنیاز خوانده/نوشته میشوند.
- مقیاسپذیری در ابر: هر chunk یک شیء مستقل است؛ مناسب برای S3/GCS.
- انعطاف در فشردهسازی/فیلترها: انتخاب codecها متناسب با داده و الگوی دسترسی.
- سادگی استقرار: روی دیسک محلی، zip یا object storage بهخوبی کار میکند.
Zarr در نسخهٔ 2 (v2) بهصورت گسترده در اکوسیستم Python استفاده میشود. مشخصات نسخهٔ 3 (v3) ویژگیهای جدیدی معرفی کرده و بهصورت تدریجی در ابزارها پشتیبانی میشود. برای سازگاری گسترده، تولید دادهٔ v2 همچنان گزینهٔ ایمنتری است.
مفاهیم پایه و مدل دادهای Zarr
Array و Group
- Array: یک آرایهٔ چندبعدی با ویژگیهایی مانند
shape،chunks،dtype،compressorوfilters. متادیتای آن در مسیر آرایه نگهداری میشود. - Group: ظرفی سلسلهمراتبی برای آرایهها و گروههای دیگر؛ مشابه پوشهها. برای سازماندهی مجموعهٔ دادهها (مثلاً multiscale، نسخهها یا متغیرهای مختلف) استفاده میشود.
Chunk
- هر chunk شامل بلوکی مستطیلی از داده است (مثلاً 256×256). اندازهٔ chunk باید با الگوی دسترسی معمول و منابع سامانه متناسب طراحی شود.
- هر chunk یک کلید مستقل در Store است؛ این استقلال اساس موازیسازی و همزمانی کارآمد است.
Compressor و Filters
- فشردهسازها (مثلاً
Bloscباzstd،lz4، یاgzip) حجم و هزینهٔ انتقال/ذخیره را کاهش میدهند. - فیلترها (مثلاً
Delta،BitShuffle،Shuffle) میتوانند قبل از فشردهسازی الگوی داده را بهبود دهند.
Attributes
- هر Array/Group میتواند
attrsداشته باشد (متادیتای سبک JSON-compatible) برای واحدها، توصیف، نسخهٔ schema و غیره.
در Zarr v2 انتخاب dimension_separator بین "/" و "." اهمیت دارد. استفاده از "/" معمولاً برای سازگاری با ابزارهای جدید توصیه میشود.
انتخاب Store و Backend مناسب
| Store | توضیح کوتاه | مناسب برای | نکتهها |
|---|---|---|---|
DirectoryStore |
پوشهٔ محلی؛ هر chunk یک فایل. | توسعهٔ محلی، SSD/NAS | ساده و سریع روی دیسک سریع؛ مراقب تعداد فایلها باشید. |
FSStore (fsspec) |
نگاشت فایلسیستمهای گوناگون (S3/GCS/HTTP/…) | ابر و فایلسیستمهای مجازی | برای S3 از s3fs استفاده کنید؛ key_separator را تنظیم کنید. |
ZipStore |
آرشیو فشردهٔ ZIP با دسترسی تصادفی | توزیع یکفایله | نوشتن ممکن است کندتر باشد؛ مناسب برای خواندن. |
MemoryStore |
کلید/مقدار در حافظه | تست، محاسبات موقت | دائمی نیست؛ با Cache ترکیب میشود. |
DBMStore |
پشتوانهٔ dbm (پایگاهداده کلید/مقدار) |
تعداد فایل کمتر | کارایی بسته به پیادهسازی dbm متفاوت است. |
در object storage (مانند S3/GCS) تعداد بسیار زیاد objectهای کوچک (chunkهای ریز) باعث هزینهٔ list/get بالا و کارایی ضعیف میشود. اندازهٔ chunk و الگوی دسترسی را با دقت طراحی کنید.
فشردهسازی و Filters در عمل
برای دادههای عمومی، Blosc با zstd و BITSHUFFLE ترکیبی متداول است. برای دادههای صحیح با تفاوتهای کوچک، فیلتر Delta میتواند موثر باشد.
import numpy as np
import zarr
from numcodecs import Blosc, Delta, BitShuffle
# Store محلی
store = zarr.DirectoryStore('example.zarr')
root = zarr.open_group(store=store, mode='w')
# فشردهساز پیشنهادی عمومی
comp = Blosc(cname='zstd', clevel=5, shuffle=Blosc.BITSHUFFLE)
# آرایهٔ اعشاری با فشردهسازی
arr_f = root.create_dataset(
'float_data', shape=(10000, 10000), chunks=(512, 512), dtype='f4',
compressor=comp, dimension_separator='/'
)
arr_f.attrs['description'] = 'نمونهٔ دادهٔ اعشاری'
arr_f[0:512, 0:512] = np.random.random((512, 512)).astype('f4')
# آرایهٔ صحیح با فیلتر Delta و BitShuffle
filters = [Delta(dtype='i4'), BitShuffle()]
arr_i = root.create_dataset(
'int_data', shape=(1000000,), chunks=(100000,), dtype='i4',
compressor=Blosc(cname='lz4', clevel=5, shuffle=Blosc.NOSHUFFLE),
filters=filters, dimension_separator='/'
)
arr_i[:] = np.arange(1_000_000, dtype='i4')
فیلتر Delta باید با dtype سازگار باشد. برای دادههای اعشاری معمولاً از فیلترهای ترتیبی مثل BitShuffle/Shuffle همراه با فشردهساز استفاده کنید.
طراحی Chunking بهینه
راهنمای عملی
- حجم هر chunk (پس از فشردهسازی) را در محدودهٔ چند صد کیلوبایت تا چند مگابایت هدفگذاری کنید؛ برای object storage معمولاً 1–10MB فشرده مناسب است.
- ابعاد chunk را با الگوی دسترسی منطبق کنید: اگر اغلب برشهای زمانی میخوانید،
chunks=(time_small, y_block, x_block). - تعداد chunkها را در هر بعد متعادل نگه دارید تا از میلیونها شیء کوچک جلوگیری شود.
- برای دادههای تصویری،
256×256یا512×512معمول است؛ برای 3بعدی، بلوکهای آجرگونه (brick) مانند(1, 256, 256)یا(4, 128, 128).
برآورد اندازهٔ chunk
import numpy as np
def estimate_chunk_bytes(chunks, dtype):
return int(np.prod(chunks)) * np.dtype(dtype).itemsize
print(estimate_chunk_bytes((1, 256, 256), 'f4')) # ~ 256 KiB خام
chunk خیلی بزرگ => تأخیر زیاد و انتقال بیمورد؛ chunk خیلی کوچک => فراخوانیهای زیاد و سربار فراوان. با داده و زیرساخت خود اندازهٔ بهینه را اندازهگیری کنید.
خواندن/نوشتن: از لوکال تا Cloud
نصب و آمادهسازی
pip install zarr numcodecs fsspec s3fs dask xarray
ایجاد Dataset محلی
import numpy as np
import zarr
from numcodecs import Blosc
store = zarr.DirectoryStore('local_data.zarr')
root = zarr.open_group(store=store, mode='w')
comp = Blosc(cname='zstd', clevel=5, shuffle=Blosc.BITSHUFFLE)
A = root.create_dataset(
'A', shape=(365, 1800, 3600), chunks=(1, 256, 256), dtype='f4',
compressor=comp, dimension_separator='/'
)
A.attrs.update({'units': 'K', 'long_name': 'daily surface temperature'})
# نوشتن منطقهای (region writes)
for t in range(0, 365):
tile = np.random.random((1, 256, 256)).astype('f4')
# نمونه: یک کاشی در گوشهٔ بالا-چپ
A[t:t+1, 0:256, 0:256] = tile
کار با S3 از طریق FSStore
import s3fs
from zarr.storage import FSStore
from zarr.convenience import consolidate_metadata
fs = s3fs.S3FileSystem(anon=False)
# مسیر بهصورت bucket/prefix؛ کلیدها با '/' جدا شوند
s3_store = FSStore('my-bucket/my-dataset.zarr', fs=fs, key_separator='/')
root_s3 = zarr.open_group(store=s3_store, mode='w')
# ایجاد آرایه مستقیماً روی S3
A_s3 = root_s3.create_dataset(
'A', shape=(365, 1800, 3600), chunks=(1, 256, 256), dtype='f4',
compressor=comp, dimension_separator='/'
)
A_s3.attrs['note'] = 'written directly to S3'
# پس از اتمام، متادیتا را یکپارچه کنید تا بازشدن سریعتر شود (برای برخی خوانندگان)
consolidate_metadata(s3_store)
برای بهبود کارایی خواندن از ابر میتوانید از Cache استفاده کنید:
from zarr.storage import LRUStoreCache
cached_s3 = LRUStoreCache(s3_store, max_size=2**29) # ~512MB
root_cached = zarr.open_group(cached_s3, mode='r')
همزمانی، قفلها و پایداری نوشتن
هر chunk مستقل نوشته میشود؛ بنابراین نوشتن موازی chunkهای متفاوت امن است. برای جلوگیری از شرایط رقابتی روی همان متادیتا یا chunk، از Synchronizer استفاده کنید.
from zarr.sync import ThreadSynchronizer
import zarr
sync = ThreadSynchronizer()
root = zarr.open_group('local_data.zarr', mode='a', synchronizer=sync)
A = root['A']
# اکنون چند نخ میتوانند روی بخشهای متفاوت بنویسند
- برای نخها:
ThreadSynchronizer. برای فرایندها:ProcessSynchronizer(هماهنگی بینفرایندی). - نوشتن متادیتا معمولاً کوچک اما حساس است؛ از نوشتن همزمان متادیتای یک Array/Group خودداری کنید.
در object storage عملیات list و ایجاد کلیدها گران است. نوشتن موازی با chunkهای بسیار کوچک میتواند باعث کندی شدید شود. اندازهٔ chunk را افزایش دهید یا تعداد کارگران را محدود کنید.
متادیتا، Attributes و مدیریت Schema
- برای هر Array/Group،
attrsرا برای واحدها، مقیاسها، نسخهٔ schema و تاریخ تولید پر کنید. dimension_separator: برای سازگاری بهتر، از'/'استفاده کنید؛ این امر مسیر chunkها را سلسلهمراتبی میکند.- یکپارچهسازی متادیتا (consolidated metadata) تعداد درخواستهای فهرستسازی را کاهش میدهد. پس از نوشتن مجموعهٔ بزرگ:
from zarr.convenience import consolidate_metadata
consolidate_metadata(s3_store)
- برای خواندن با Xarray/Dask (در صورت نیاز):
xr.open_zarr(..., consolidated=True). - Endian و
dtype: بسیاری از مصرفکنندگان little-endian انتظار دارند. در صورت داشتن دادهٔ big-endian:
# اطمینان از little-endian
import numpy as np
arr = arr.byteswap().newbyteorder('<') if arr.dtype.byteorder == '>' else arr
مثال واقعی: دادهٔ آبوهوا (زمان × عرض × طول)
هدف: ذخیرهٔ دمای روزانهٔ سطح زمین با ابعاد (time=365, lat=1800, lon=3600)، chunk بهینه برای دسترسی زمانی و برش مکانی.
import numpy as np
import zarr
from numcodecs import Blosc
comp = Blosc(cname='zstd', clevel=5, shuffle=Blosc.BITSHUFFLE)
root = zarr.open_group('climate.zarr', mode='w')
T = root.create_dataset(
'tas', shape=(365, 1800, 3600), chunks=(1, 256, 256), dtype='f4',
compressor=comp, dimension_separator='/'
)
T.attrs.update({'units': 'K', 'long_name': 'near-surface air temperature'})
# فرض: داده را قطعهقطعه تولید/خوانده و مینویسیم
for t in range(365):
# اینجا دادهٔ واقعی را بارگذاری/محاسبه کنید
block = np.random.random((1, 256, 256)).astype('f4')
# کاشیهای مکانی را بهصورت موزاییکی بنویسید
for y0 in range(0, 1800, 256):
for x0 in range(0, 3600, 256):
T[t:t+1, y0:y0+256, x0:x0+256] = block # نمونهٔ ساده
ادغام با Dask و Xarray
- Dask میتواند Zarr را بهصورت تنبل (lazy) بخواند و عملیات را موازی کند:
import dask.array as da
x = da.from_zarr('climate.zarr/tas') # یا از شیٔ zarr.Array
print(x.mean(axis=0).compute())
- Xarray خواندن/نوشتن Datasetهای چندمتغیره را ساده میکند:
import xarray as xr
# خواندن از S3 با متادیتای یکپارچه
# ds = xr.open_zarr(s3_store, consolidated=True)
# نوشتن از Xarray به Zarr (مثال):
# ds.to_zarr('out.zarr', mode='w', consolidated=True)
اگر بعداً به تغییر chunking نیاز داشتید، ابزارهایی مانند rechunker میتوانند آرایهها را به طرح chunk جدید تبدیل کنند بدون بارگذاری کامل در حافظه.
عیبیابی و خطاهای رایج
- کارایی ضعیف روی S3: chunk خیلی کوچک، فقدان consolidated metadata، عدم استفاده از Cache. راهحل: chunk بزرگتر (۱–۱۰MB فشرده)، اجرای
consolidate_metadata،LRUStoreCache. - KeyError یا فقدان
.zarray: مسیر نادرست یا بازکردن group بهجای array. راهحل: ازopen_groupاستفاده کنید و مسیر array را بررسی کنید. - ناسازگاری endianness: خطا یا نتایج نادرست در برخی خوانندگان. راهحل: تبدیل به little-endian پیش از نوشتن.
- Race condition در نوشتن: نوشتن همزمان روی یک chunk/متادیتا. راهحل: تقسیم کار روی chunkهای متفاوت، استفاده از
Synchronizer. - تعداد فایل/شیء بسیار زیاد: انتخاب chunk نامناسب. راهحل: افزایش اندازهٔ chunk، ادغام دادههای کوچک، استفاده از backend مناسب.
- 403/AccessDenied روی S3: مجوز ناکافی. راهحل: پیکربندی اعتبارنامهها و سیاستهای bucket.
تغییر dtype یا chunks یک آرایهٔ موجود بهصورت درجا پشتیبانی نمیشود. برای تغییر schema، آرایهٔ جدید بسازید و داده را منتقل کنید.
جمعبندی
Zarr با معماری chunkمحور و فشردهسازی شفاف، پلی میان دادههای عظیم و محاسبات کارآمد میسازد. با انتخاب درست backend، طراحی سنجیدهٔ chunking و مدیریت دقیق متادیتا، میتوانید جریانکار قابلاعتماد و مقیاسپذیری برای علم داده و HPC ایجاد کنید.
- الگوی دسترسی را مستند و بر اساس آن
chunksرا طراحی کنید. - فشردهساز/فیلترها را با سنجش کارایی روی نمونهٔ واقعی انتخاب کنید.
- در cloud: از
FSStore،dimension_separator='/'وconsolidate_metadataاستفاده کنید. - Attributes را برای خود-توصیفگری کامل پر کنید.
- برای نوشتن موازی امن، از
Synchronizerو تقسیم کار chunk-aware بهره ببرید. - برای خواندن تحلیلی، ادغام با Dask/Xarray را مدنظر قرار دهید.