Redis ile konum verisi saklamak için GEOADD komutuyla bir anahtara boylam, enlem ve üye adı üçlüleri eklersiniz; yakındaki noktaları bulmak için de GEOSEARCH komutunu bir merkez (bir üye ya da koordinat) ve bir şekil (yarıçaplı daire ya da dikdörtgen kutu) ile çağırırsınız. İki nokta arasındaki mesafe GEODIST, saklanan koordinatlar GEOPOS, standart geohash metni ise GEOHASH ile alınır. Bu yazıda bu komutların argümanlarını, yanıt biçimlerini, hassasiyet sınırlarını ve sık yapılan hataları resmî belgelere dayanarak anlatıyoruz.

Konu, bisiklet istasyonu, kurye, mağaza ya da etkinlik gibi "bana en yakın olanlar hangileri" sorusunu soran uygulamalarda karşınıza çıkar. Redis bu iş için ayrı bir sunucu ya da eklenti istemez; coğrafi indeks aslında sıradan bir sorted set üzerine kuruludur. Redis'in ne olduğunu ve kurulumunu bilmiyorsanız önce Redis nedir yazısına, serinin tamamı için Redis öğrenme rehberine bakabilirsiniz.

Coğrafi indeks aslında bir sorted set

GEOADD sayfası, verinin anahtara bir sorted set olarak yazıldığını açıkça söyler. Redis, enlem ve boylamın bitlerini iç içe geçirerek 52 bitlik tek bir tamsayı üretir; bu, geohash tekniğinin bir çeşididir. Sorted set skorları çift duyarlıklı kayan noktalı sayı olduğu için 52 bitlik bir tamsayıyı kayıpsız taşıyabilir. Böylece her konum, üye adı ve bu tamsayı skoruyla birlikte saklanır.

Bu tasarımın pratik sonuçları önemlidir. Birincisi, ayrı bir GEODEL komutu yoktur; bir noktayı silmek için ZREM kullanırsınız. İkincisi, toplam nokta sayısını ZCARD, tüm üyeleri ZRANGE ile görebilirsiniz; çünkü elinizdeki şey gerçekten bir sorted settir. Sorted set komutlarının ayrıntısı için sorted set yazısına bakın.

Arama tarafında Redis, istenen daireyi ya da kutuyu kapsayan 1+8 alanı hesaplar. Her alan için skorun düşük anlamlı bitlerinden yeterince atıp sorted set içinde bir skor aralığı sorgular, ardından şeklin dışında kalan öğeleri eler. Yani arama, tüm kümeyi taramak yerine skor aralıkları üzerinden çalışır; bu da sorted setin sıralı yapısından gelir.

GEOADD: noktaları eklemek

Sözdizimi GEOADD key [NX | XX] [CH] longitude latitude member [longitude latitude member ...] biçimindedir. Komut x,y düzenini izler; bu yüzden önce boylam, sonra enlem yazılır. Belgedeki Sicilya örneği tek komutta iki şehir ekler ve eklenen yeni öğe sayısını döndürür:

text
> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2

Geçerli değerler EPSG:900913 / EPSG:3785 standardına göre sınırlıdır: boylam -180 ile 180 derece arasında, enlem ise -85.05112878 ile 85.05112878 derece arasında olmalıdır. Kutuplara çok yakın bölgeler bu yüzden indekslenemez ve aralık dışındaki bir koordinat hata ile reddedilir.

Seçenekler sorted setteki ZADD mantığına benzer. NX var olan üyelere dokunmaz, yalnızca yenilerini ekler. XX tersini yapar: yalnızca var olan üyelerin koordinatını günceller, yeni üye eklemez. Bu ikisi birlikte kullanılamaz. CH ise dönüş değerinin anlamını değiştirir: normalde yalnızca yeni eklenen öğeler sayılır; CH verildiğinde koordinatı değişen mevcut öğeler de sayıya katılır. Koordinatı aynı kalan öğeler hiçbir durumda sayılmaz.

GEOSEARCH: yakındaki noktaları bulmak

GEOSEARCH: yakındaki noktaları bulmak: Merkez, Şekil, Seçenekler

GEOSEARCH, eski GEORADIUS komutunu genişletir ve dairenin yanında dikdörtgen alanda aramayı da destekler. Bir aramayı üç parça tanımlar:

  • Merkez: FROMMEMBER member indeksteki bir üyenin konumunu, FROMLONLAT longitude latitude verilen koordinatı merkez alır. İkisinden yalnızca biri kullanılır.
  • Şekil: BYRADIUS radius M|KM|FT|MI daire, BYBOX width height M|KM|FT|MI eksenlere hizalı bir kutu tanımlar. Yine ikisinden biri seçilir.
  • Seçenekler: ASC / DESC mesafeye göre sıralar, COUNT count [ANY] sonuç sayısını sınırlar, WITHCOORD, WITHDIST ve WITHHASH yanıta ek bilgi koyar.

Veri tipi rehberindeki bisiklet kiralama örneğinde üç istasyon eklenir ve belirli bir noktanın 5 kilometre çevresindeki istasyonlar mesafeleriyle birlikte istenir:

text
> GEOADD bikes:rentable -122.27652 37.805186 station:1
(integer) 1
> GEOADD bikes:rentable -122.2674626 37.8062344 station:2
(integer) 1
> GEOADD bikes:rentable -122.2469854 37.8104049 station:3
(integer) 1
> GEOSEARCH bikes:rentable FROMLONLAT -122.2612767 37.7936847 BYRADIUS 5 km WITHDIST
1) 1) "station:1"
   2) "1.8523"
2) 1) "station:2"
   2) "1.4979"
3) 1) "station:3"
   2) "2.2441"

Yanıta dikkatle bakın: 1.4979 kilometredeki station:2, 1.8523 kilometredeki station:1 sonrasında geliyor. Sıralama seçeneği verilmediğinde sonuçların mesafeye göre dizilmesini beklememelisiniz; eski GEORADIUS sayfası da varsayılanın sırasız dönüş olduğunu belirtir. "En yakın" listesine ihtiyacınız varsa ASC eklemelisiniz. WITHDIST ile dönen mesafe, şekilde kullandığınız birimdedir (burada km).

Komut sayfasındaki örnek, sıralı daire aramasını ve kutu aramasını yan yana gösterir. Önce iki kenar noktası daha eklenir:

text
> GEOADD Sicily 12.758489 38.788135 "edge1"   17.241510 38.788135 "edge2"
(integer) 2
> GEOSEARCH Sicily FROMLONLAT 15 37 BYRADIUS 200 km ASC
1) "Catania"
2) "Palermo"
> GEOSEARCH Sicily FROMLONLAT 15 37 BYBOX 400 400 km ASC WITHCOORD WITHDIST
1) 1) "Catania"
   2) "56.4413"
   3) 1) "15.087267458438873"
      2) "37.50266842333162"
2) 1) "Palermo"
   2) "190.4424"
   3) 1) "13.361389338970184"
      2) "38.1155563954963"
3) 1) "edge2"
   2) "279.7403"
   3) 1) "17.241510450839996"
      2) "38.78813451624225"
4) 1) "edge1"
   2) "279.7405"
   3) 1) "12.75848776102066"
      2) "38.78813451624225"

200 kilometrelik dairede kenar noktaları yoktur; 400x400 kilometrelik kutuda ise dört nokta da çıkar. Kutunun köşelerine yakın noktalar merkezden 279 kilometre uzakta olduğu hâlde kutunun içinde kalır; çünkü kutu, merkezden her yöne genişliğin ve yüksekliğin yarısı kadar uzanır ve köşegen boyunca mesafe yarıçaptan büyüktür. Daireyle kutu arasında seçim yaparken bu farkı düşünün: ekranda dikdörtgen bir harita görünümünü dolduracaksanız BYBOX, "şu kadar kilometre içinde" sorusu için BYRADIUS daha doğal sonuç verir.

Yanıtın biçimi

Hiçbir WITH* seçeneği yoksa yanıt yalnızca üye adlarından oluşan bir dizidir. Bir ya da daha fazla WITH* varsa her öğe kendi alt dizisine dönüşür: ilk eleman üye adı, ardından sırasıyla mesafe, geohash tamsayısı ve koordinat çifti gelir; yalnızca istediğiniz alanlar yer alır. Koordinatlar her zaman boylam, enlem sırasıyla döner. İstemci kodunda yanıtı ayrıştırırken bu sabit sırayı kullanabilirsiniz.

COUNT ve ANY

COUNT 5 en fazla beş sonuç döndürür. ANY olmadan sunucu, alandaki tüm eşleşmeleri bulup sıralar ve sonra ilk beşi verir; dolayısıyla çok geniş bir alanda küçük bir COUNT istemek bile yavaş olabilir. ANY eklendiğinde komut yeterli sayıda eşleşme bulduğu anda döner. Bu daha az iş demektir, ancak dönen sonuçlar merkeze en yakın olanlar olmayabilir ve sırasız gelebilir. Yakınlık sırası önemliyse ANY kullanmayın; "bölgede en az bir müsait araç var mı" gibi sorularda ise ANY iyi bir seçimdir.

GEOSEARCHSTORE: sonucu başka bir anahtara yazmak

GEOSEARCHSTORE: sonucu başka bir anahtara yazmak

GEOSEARCHSTORE destination source ... aynı aramayı yapar ama sonucu istemciye göndermek yerine destination anahtarına yazar ve sonuçtaki öğe sayısını döndürür. Varsayılan olarak hedef de coğrafi bilgi taşıyan bir sorted settir, yani üzerinde yeniden GEOSEARCH çalıştırabilirsiniz. STOREDIST verildiğinde skor olarak geohash yerine merkeze olan mesafe yazılır:

text
> GEOSEARCHSTORE key2 Sicily FROMLONLAT 15 37 BYBOX 400 400 km ASC COUNT 3 STOREDIST
(integer) 3
> ZRANGE key2 0 -1 WITHSCORES
1) "Catania"
2) "56.4412578701582"
3) "Palermo"
4) "190.44242984775784"
5) "edge2"
6) "279.7403417843143"

Mesafeyi skor olarak saklamak, sonucu sonradan ZRANGE ... BYSCORE ile mesafe aralığına göre süzmenizi kolaylaştırır. Ancak bu anahtar artık coğrafi indeks değildir; skorlar konum değil mesafe olduğu için üzerinde GEOSEARCH anlamlı sonuç vermez. Komut sayfası, bu komutun cluster ortamında farklı davrandığını not eder; çünkü kaynak ve hedef olmak üzere iki anahtara dokunur. Çok anahtarlı komutların cluster kuralları hash slot yazısında anlatılıyor.

GEODIST, GEOPOS ve GEOHASH

GEODIST key member1 member2 [M|KM|FT|MI] iki üye arasındaki mesafeyi metin olarak kodlanmış bir sayı halinde döndürür. Birim verilmezse metre kullanılır. Üyelerden biri ya da ikisi yoksa yanıt boştur:

text
> GEODIST Sicily Palermo Catania
"166274.1516"
> GEODIST Sicily Palermo Catania km
"166.2742"
> GEODIST Sicily Palermo Catania mi
"103.3182"
> GEODIST Sicily Foo Bar
(nil)

GEOPOS verdiğiniz her üye için bir boylam-enlem çifti döndürür; tek üye sorsanız bile yanıt bir dizidir. Olmayan üyeler dizide boş eleman olarak görünür. Koordinatlar 52 bitlik geohash'e çevrilerek saklandığı için geri okunan değerler, eklediğiniz değerlerle birebir aynı olmayabilir:

text
> GEOPOS Sicily Palermo Catania NonExisting
1) 1) "13.361389338970184"
   2) "38.1155563954963"
2) 1) "15.087267458438873"
   2) "37.50266842333162"
3) (nil)

Palermo için 13.361389 girdiniz, 13.361389338970184 geri aldınız. Bu fark çok küçüktür ama "eklediğim değeri aynen geri alırım" varsayımıyla yazılmış eşitlik testleri bozulur. Kaynak koordinatın kendisi sizin için önemliyse onu ayrıca, örneğin bir hash alanında saklayın; bunun için hash yazısına bakabilirsiniz.

GEOHASH, Redis'in iç gösterimini standart geohash metnine çevirir. Redis'in iç kodlaması, kodlamada kullanılan başlangıç sınırları farklı olduğu için standart geohash ile aynı değildir; bu komut dışarıdaki araçlarla uyumlu, 11 karakterlik metinler üretir:

text
> GEOHASH Sicily Palermo Catania
1) "sqc8b49rny0"
2) "sqdtr74hyu0"

Belgeye göre bu metinlerin iki özelliği vardır. Sağdan karakter silerek kısaltabilirsiniz; hassasiyet azalır ama metin hâlâ aynı bölgeyi gösterir. Ortak öneki olan metinler birbirine yakındır, ancak tersi her zaman doğru değildir: farklı önekli iki metin de yakın konumları gösterebilir. Bu yüzden "aynı önekle başlayanlar komşudur, diğerleri değildir" diye bir yakınlık testi yazmak yanlış sonuç verir; yakınlık için GEOSEARCH kullanın.

Hassasiyet: Dünya küre kabul ediliyor

Mesafeler Haversine formülüyle hesaplanır ve bu formül Dünya'yı mükemmel bir küre varsayar. Dünya tam bir küre olmadığı için sonuç bir yaklaşımdır. Belgeye göre en kötü durumda hata yüzde 0,5'e kadar çıkabilir. Sosyal ağlar ya da yakındaki mağazaları listeleyen uygulamalar için bu sorun değildir; ölçüm hatasının kritik olduğu uygulamalarda başka sistemleri değerlendirmeniz önerilir.

Bu hata payını somut düşünmek işinizi kolaylaştırır: 2 kilometrelik bir mesafede yüzde 0,5, on metre mertebesindedir. Bir sınırın tam üstündeki nokta bir aramada içeride, diğerinde dışarıda kalabilir. Fatura, ceza ya da hukuki sonuç doğuran kararları bu mesafeye bağlamayın; Redis'i ön eleme için kullanıp kesin hesabı uygulamada yapmak daha güvenli bir yoldur.

Eski komutlar: GEORADIUS ve GEORADIUSBYMEMBER

Eski komutlar: GEORADIUS ve GEORADIUSBYMEMBER: GEORADIUS key lon lat 5 km, GEORADIUSBYMEMBER key uye 5 km, GEORADIUS ... STORE hedef, GEORADIUS ... STOREDIST hedef

Eski kodlarda sık görülen GEORADIUS ve GEORADIUSBYMEMBER, belgede kullanımdan kaldırılmış (deprecated) olarak işaretlidir; yerlerine GEOSEARCH ve GEOSEARCHSTORE kullanılmalıdır. Eski komutların STORE ve STOREDIST seçenekleri olduğu için komut tablosunda yazma komutu sayılırlar. Bu nedenle salt okunur replikalar bu komutları reddeder, cluster replikaları da onları ana düğüme yönlendirir. Bu sorunu aşmak için GEORADIUS_RO ve GEORADIUSBYMEMBER_RO eklendi; bunlar saklama seçeneklerini kabul etmez. Yeni kodda bu ayrımla uğraşmanıza gerek yoktur: okuma için GEOSEARCH, saklama için GEOSEARCHSTORE zaten ayrı komutlardır.

Eski kullanımYeni karşılığı
GEORADIUS key lon lat 5 kmGEOSEARCH key FROMLONLAT lon lat BYRADIUS 5 km
GEORADIUSBYMEMBER key uye 5 kmGEOSEARCH key FROMMEMBER uye BYRADIUS 5 km
GEORADIUS ... STORE hedefGEOSEARCHSTORE hedef key ...
GEORADIUS ... STOREDIST hedefGEOSEARCHSTORE hedef key ... STOREDIST
Dikdörtgen alan (yok)GEOSEARCH key ... BYBOX genislik yukseklik km

Uygulamalı mini örnek: en yakın üç kurye

Bir teslimat uygulamasında kuryelerin son konumunu tuttuğunuzu düşünün. Aşağıdaki komutlar belgelerdeki sözdizimine göre yazılmıştır; koordinatlar örnek amaçlıdır, bu yüzden yanıt satırlarını göstermiyoruz ve her yanıtın ne anlama geldiğini komut sayfalarındaki dönüş bilgisine göre açıklıyoruz.

text
> GEOADD kurye:konum 28.9784 41.0082 kurye:17 29.0121 40.9900 kurye:23 28.9500 41.0300 kurye:31
> GEOADD kurye:konum XX CH 28.9790 41.0090 kurye:17
> GEOSEARCH kurye:konum FROMLONLAT 28.9800 41.0050 BYRADIUS 3 km ASC COUNT 3 WITHDIST
> ZREM kurye:konum kurye:31

İlk komut üç kurye ekler ve tamsayı yanıt olarak eklenen yeni öğe sayısını verir. İkinci komut yalnızca var olan kurye:17 kaydını günceller; CH sayesinde koordinat değiştiyse yanıt 1 olur. Üçüncü komut, müşterinin konumuna 3 kilometre içindeki en fazla üç kuryeyi yakından uzağa sıralar ve her birinin mesafesini kilometre cinsinden ekler. Son komut, vardiyası biten kuryeyi indeksten çıkarır; coğrafi indeks bir sorted set olduğu için silme işini ZREM yapar. Kuryenin indekste kalma süresini sınırlamak isterseniz, tek tek üyelere süre verilemeyeceğini unutmayın; süre anahtarın tamamına uygulanır. Bunun için EXPIRE ve TTL yazısına bakın.

Sık yapılan hatalar

  • Enlem ve boylamı ters yazmak. Komut önce boylam ister. İstanbul gibi enlemi 85 dereceden küçük bir yerde iki değer de geçerli aralıkta kaldığı için ters yazım hata vermeyebilir ve nokta sessizce başka bir yere kaydedilir.
  • Sıralama beklemek. ASC ya da DESC olmadan sonuç sırası mesafeye göre değildir.
  • COUNT ... ANY ile en yakını aramak. ANY hız için doğruluğu bırakır; en yakın sonuçları garanti etmez.
  • Geri okunan koordinatı eşitlik testinde kullanmak. GEOPOS küçük yuvarlama farkları içerir.
  • STOREDIST sonucunu coğrafi indeks sanmak. Skorlar mesafedir; bu anahtar üzerinde yeniden coğrafi arama yapılmaz.
  • Silmek için GEODEL aramak. Böyle bir komut yoktur; ZREM kullanılır.
  • Coğrafi indeksi başka verilerle aynı anahtarda karıştırmak. Aynı sorted sete normal skorlu üyeler eklerseniz, bu skorlar da konum gibi yorumlanır.

Coğrafi veri tipi mi, Redis Search mü?

Veri tipi sayfası, bu komutların Redis Search içindeki coğrafi özelliklerle karıştırılmaması gerektiğini vurgular. Coğrafi veri tipi basit kullanım senaryoları içindir ve Search'teki biçim seçeneklerini ve sorgu çeşitliliğini sunmaz. Konumu, ürünün kategorisi, fiyatı ya da metin alanlarıyla aynı sorguda birleştirmek istiyorsanız Search tarafındaki coğrafi alanlar daha uygundur; bu konuya FT.CREATE ve FT.SEARCH yazısında giriş yapılıyor. Yalnızca "yakındakiler" sorusu için GEOSEARCH yeterlidir. Hangi işe hangi tipin uygun olduğuna genel bakış için veri tipi seçimi yazısını okuyabilirsiniz.

Sık Sorulan Sorular

Coğrafi indeksteki bir noktayı nasıl silerim?

ZREM anahtar üye ile. Coğrafi indeks bir sorted set olduğu için ayrı bir silme komutu yoktur.

Mesafeler hangi birimde döner?

GEODIST varsayılan olarak metre döndürür; KM, MI ya da FT verebilirsiniz. GEOSEARCH ile WITHDIST kullandığınızda mesafe, yarıçap ya da kutu için yazdığınız birimdedir.

Kutuplara yakın konumları saklayabilir miyim?

Hayır. Enlem -85.05112878 ile 85.05112878 derece arasında olmalıdır; bu aralığın dışındaki koordinatlar hata ile reddedilir.

GEORADIUS hâlâ çalışıyor mu?

Belgede kullanımdan kaldırılmış olarak işaretlidir. Yeni kodda GEOSEARCH ve GEOSEARCHSTORE kullanın; eski kodu taşırken yukarıdaki karşılık tablosu işinizi görür.

Sonuçlar ne kadar doğru?

Hesaplama küre modeline dayanır; en kötü durumda hata yüzde 0,5'e kadar çıkabilir. Yakınlık listeleri için yeterlidir, kesin ölçüm gerektiren işler için uygun değildir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Redis öğrenme rehberi · Sorted set komutları · Redis Search · Redis veri tipi seçimi · Redis nedir