Kiedy do Redis? Kiedy do MongoDB? [Zamknięte]


466

Nie chcę porównania między Redis i MongoDB. Wiem, że są inni; wydajność i API są zupełnie inne.

Redis jest bardzo szybki, ale API jest bardzo „atomowy”. MongoDB zje więcej zasobów, ale interfejs API jest bardzo łatwy w użyciu i jestem z niego bardzo zadowolony.

Oba są niesamowite i chcę używać Redis do wdrażania tak dużo, jak tylko mogę, ale trudno jest napisać kod. Chcę używać MongoDB w rozwoju tak bardzo, jak tylko mogę, ale potrzebuje drogiej maszyny.

Co sądzisz o wykorzystaniu ich obu? Kiedy wybrać Redis? Kiedy wybrać MongoDB?

Odpowiedzi:


308

Powiedziałbym, że zależy to od rodzaju zespołu programistów i potrzeb aplikacji.

Na przykład, jeśli potrzebujesz dużo zapytań , oznacza to w większości przypadków, że programiści skorzystaliby z Redis, gdzie dane mogą być przechowywane w różnych wyspecjalizowanych strukturach danych, dostosowanych do każdego typu obiektu w celu zwiększenia wydajności. W MongoDB te same zapytania mogą być łatwiejsze, ponieważ struktura jest bardziej spójna dla danych. Z drugiej strony, w Redis, sama szybkość odpowiedzi na te zapytania jest korzyścią za dodatkową pracę związaną z różnorodnością struktur, w których mogą być przechowywane dane.

MongoDB oferuje prostotę, znacznie krótszą krzywą uczenia się dla programistów z tradycyjnym doświadczeniem w DB i SQL. Jednak nietradycyjne podejście Redis wymaga większego wysiłku w nauce, ale większej elastyczności.

Na przykład. Cache warstwa może prawdopodobnie być lepiej realizowane w Redis. Aby uzyskać więcej danych zgodnych ze schematem, MongoDB jest lepszy. [Uwaga: zarówno MongoDB, jak i Redis są technicznie bez schematów]

Jeśli mnie zapytasz, moim osobistym wyborem jest Redis dla większości wymagań.

Wreszcie mam nadzieję, że do tej pory widziałeś http://antirez.com/post/MongoDB-and-Redis.html


16
fyi, mongodb jest bez schematu.
Özgür

18
MogoDB jest bez schematu. a ponieważ dane przechowywane w bazie danych stają się coraz większe, MongoDB udowadnia, że ​​jest znacznie szybszy niż Redis. Redis jest szybszy tylko wtedy, gdy przechowywane dane są małe.
Anderson

4
Podoba mi się podejście MongoDB do bycia bez schematów, a następnie pozostawienie autorom ORM wdrożenia schematów dla tych, którzy ich potrzebują. Mongoose to świetna ORM, która wprowadza łatwe w użyciu schematy, jeśli ich potrzebujesz :)
Chev

18
Powinieneś wiedzieć, że rozmiar bazy danych redis jest ograniczony ilością pamięci RAM w komputerze. Jakikolwiek większy niż ten i musisz pomyśleć o grupowaniu, które jest ręczne i intensywne.
Akash Agrawal

4
MongoDB nie wymusza schematu, ale chciałbym zobaczyć przypadek, w którym ktoś używa go bez schematu ... to wszystko jak definiujesz słowo schemat
Robbie Guilfoyle

236

Właśnie zauważyłem, że to pytanie jest dość stare. Niemniej jednak uważam, że warto dodać następujące aspekty:

  • Użyj MongoDB, jeśli jeszcze nie wiesz, w jaki sposób będziesz sprawdzać swoje dane.

    MongoDB nadaje się do hackathonów, startupów lub za każdym razem, gdy nie wiesz, jak zapytasz o wstawione dane. MongoDB nie przyjmuje żadnych założeń dotyczących podstawowego schematu. Chociaż MongoDB jest bez schematów i nie ma relacji, nie oznacza to wcale, że nie ma schematu. Oznacza to po prostu, że Twój schemat musi zostać zdefiniowany w Twojej aplikacji (np. Za pomocą Mongoose). Poza tym MongoDB jest świetny do prototypowania lub testowania. Jego wydajność nie jest tak świetna i nie można jej porównać do Redisa.

  • Użyj Redis, aby przyspieszyć istniejącą aplikację.

    Redis można łatwo zintegrować jako pamięć podręczną LRU . Używanie Redis jako samodzielnego systemu bazy danych jest bardzo rzadkie (niektóre osoby wolą nazywać go „sklepem klucz-wartość”). Strony internetowe takie jak Craigslist używają Redis obok swojej podstawowej bazy danych . Antirez (twórca Redis) wykazał za pomocą Lamernews, że rzeczywiście można używać Redis jako samodzielnego systemu baz danych.

  • Redis nie przyjmuje żadnych założeń na podstawie twoich danych.

    Redis zapewnia kilka przydatnych struktur danych (np. Zestawy, skróty, listy), ale musisz wyraźnie zdefiniować sposób przechowywania danych. Krótko mówiąc, można użyć Redis i MongoDB w celu osiągnięcia podobnych celów. Redis jest po prostu szybszy, ale nie nadaje się do prototypowania. To jeden przypadek użycia, w którym zwykle wolisz MongoDB. Poza tym Redis jest naprawdę elastyczny. Podstawowymi strukturami danych, które zapewnia, są elementy składowe wysokowydajnych systemów DB.

Kiedy stosować Redis?

  • Buforowanie

    Buforowanie przy użyciu MongoDB po prostu nie ma większego sensu. To byłoby zbyt wolne.

  • Jeśli masz wystarczająco dużo czasu, aby pomyśleć o swoim projekcie DB.

    Nie możesz po prostu wrzucić swoich dokumentów do Redis. Musisz pomyśleć o tym, w jaki sposób chcesz przechowywać i organizować swoje dane. Jednym z przykładów są skróty w Redis. Różnią się one od „tradycyjnych” zagnieżdżonych obiektów, co oznacza, że ​​będziesz musiał przemyśleć sposób przechowywania zagnieżdżonych dokumentów. Jednym z rozwiązań byłoby przechowywanie w haszowaniu odwołania do innego skrótu (coś w rodzaju klucza: [identyfikator drugiego skrótu] ). Innym pomysłem byłoby przechowywanie go jako JSON, co wydaje się sprzeczne z intuicją dla większości osób z * tłem SQL.

  • Jeśli potrzebujesz naprawdę wysokiej wydajności.

    Pokonanie wydajności, którą zapewnia Redis, jest prawie niemożliwe. Wyobraź sobie, że twoja baza danych jest tak szybka jak twoja pamięć podręczna. Tak to jest korzystać z Redis jako prawdziwej bazy danych.

  • Jeśli nie zależy ci tak bardzo na skalowaniu.

    Skalowanie Redisa nie jest tak trudne, jak kiedyś. Na przykład można użyć pewnego rodzaju serwera proxy w celu dystrybucji danych między wieloma instancjami Redis. Replikacja master-slave nie jest tak skomplikowana, ale dystrybucja kluczy pomiędzy wiele instancji Redis musi odbywać się w witrynie aplikacji (np. Za pomocą funkcji skrótu, Modulo itp.). Skalowanie MongoDB przez porównanie jest znacznie prostsze.

Kiedy stosować MongoDB

  • Prototypowanie, startupy, hackatony

    MongoDB doskonale nadaje się do szybkiego prototypowania. Niemniej wydajność nie jest tak dobra. Pamiętaj również, że najprawdopodobniej będziesz musiał zdefiniować jakiś schemat w swojej aplikacji.

  • Gdy trzeba szybko zmienić schemat.

    Ponieważ nie ma schematu! Zmiana tabel w tradycyjnym, relacyjnym systemie DBMS jest boleśnie droga i powolna. MongoDB rozwiązuje ten problem, nie przyjmując zbyt wielu założeń dotyczących bazowych danych. Niemniej jednak próbuje zoptymalizować, o ile to możliwe, bez konieczności definiowania schematu.

TL; DR - Użyj Redis, jeśli wydajność jest ważna i chcesz poświęcić czas na optymalizację i organizację swoich danych. - Użyj MongoDB, jeśli chcesz zbudować prototyp, nie martwiąc się zbytnio o swoją bazę danych.

Dalsza lektura:


3
Jeśli masz wystarczająco dużo czasu, aby pomyśleć o swoim projekcie DB. Aby to zrealizować: załóżmy, że chcesz przechowywać dane SO. W Mongo : po prostu zrzuć pełne pytania z zagnieżdżonymi odpowiedziami i komentarzami, ale w redis musisz wykonać następujące czynności: SO on redis
Abhishek Gupta

223

Redis. Powiedzmy, że napisałeś witrynę w php; z jakiegokolwiek powodu staje się popularny i wyprzedza swój czas lub ma na nim porno. Zdajesz sobie sprawę, że ten php jest tak cholernie powolny: „Stracę moich fanów, ponieważ po prostu nie będą czekać 10 sekund na stronę”. Nagle zdajesz sobie sprawę, że strona internetowa ma stały adres URL (nigdy się nie zmienia, whoa), klucz podstawowy, jeśli chcesz, a potem przypominasz sobie, że pamięć jest szybka, gdy dysk jest wolny, a php jeszcze wolniejszy. :( Następnie projektujesz mechanizm pamięci za pomocą pamięci i tego adresu URL, który nazywasz „kluczem”, a treść strony, którą decydujesz się nazwać „wartością”. To wszystko, co masz - klucz i treść. Nazywasz to „pamięcią podręczną”. Lubisz Richarda Dawkinsa, bo jest niesamowity. Buforujesz swój html, jak wiewiórki zbijają orzechy. Nie musisz przepisywać bzdurnego kodu php. Jesteś szczęśliwy. Potem widzisz, że inni to zrobili - ale wybierasz Redis, ponieważ drugi ma mylące zdjęcia kotów, niektóre z kłami.

Mongo Napisałeś stronę. Heck, napisałeś wiele w dowolnym języku. Zdajesz sobie sprawę, że większość czasu spędzasz na pisaniu tych śmierdzących klauzul SQL. Nie jesteś dba, a jednak jesteś, pisząc głupie instrukcje SQL ... nie tylko jeden, ale dziwaczny wszędzie. „wybierz to, wybierz to”. Ale w szczególności pamiętasz irytującą klauzulę WHERE. Gdzie nazwisko to „thornton”, a film to „zły święty mikołaj”. Urgh. Myślisz: „dlaczego te bazy danych po prostu nie wykonują swojej pracy i nie udostępniają mi procedur przechowywanych?” Potem zapominasz o niewielkim polu, takim jak nazwa środkowa, a następnie musisz upuścić tabelę, wyeksportować wszystkie 10G dużych danych i utworzyć kolejne z tym nowym polem, i zaimportować dane - i to trwa 10 razy w ciągu następnych 14 dni zapamiętaj bzdury jak pozdrowienie, tytuł, plus dodanie klucza obcego z adresami. Zatem uważasz, że nazwisko powinno być ostatnie. Prawie jedna zmiana dziennie. Więc mówisz darnit. Muszę wejść i napisać stronę internetową / system, nie wspominając o tym modelu danych bs. Więc wy google: „Nienawidzę pisać SQL, proszę nie SQL, przestań”, ale wyskakuje „nosql”, a potem czytasz jakieś rzeczy i mówi, że po prostu zrzuca dane bez żadnego schematu. Pamiętasz fiasko z zeszłego tygodnia zrzucające więcej stolików i uśmiech. Następnie wybierasz mongo, ponieważ niektórzy duzi faceci, tacy jak „airbud”, używa go na stronie wynajmu apt. Słodkie. Nigdy więcej zmian modelu danych, ponieważ masz model, który ciągle zmieniasz.


co rozumiesz przez to You don't need to rewrite your crap php code?, jak sklep KV rozwiązuje to? :)
Roy Lee

13
@Roylee ma na myśli, że powolny i gówniany php wyświetla stronę w formacie HTML. Zamiast żmudnego przepisywania kodu, aby był szybszy / bardziej wydajny, uruchamiasz php raz na początku, a potem na zawsze, po prostu przywołaj gotową stronę w html, używając swojego sklepu kv.
Mikepote,

Sposób, w jaki opowiedziałeś tę historię, pomógł mi w końcu zrozumieć, dlaczego bez schematu jest niesamowity! Właśnie zaoszczędziłem mi kilku lat konieczności radzenia sobie z SQL, aby zrozumieć moc.
Nick Pineda

4
„Nigdy więcej zmian modelu” nie oddaje w rzeczywistości sytuacji. O ile nie napiszesz kodu ruchu danych, aby zaktualizować wszystkie istniejące wpisy, to bardziej przypomina to, że masz „N” nieco inne modele, wszystkie żyjące w tym samym DB w tym samym czasie, i kod musi ustalić, z którym modelem ma do czynienia, kiedy czyta coś z DB.
Terry Coatta

2
Jedna z absolutnie najlepszych odpowiedzi, jakie kiedykolwiek widziałem. Ma świetną zawartość i sprawia, że ​​śmieję się głośno (dosłownie nie lol)
colbyJax


11

Trudne pytanie, na które należy odpowiedzieć - tak jak w przypadku większości rozwiązań technologicznych, tak naprawdę zależy to od twojej sytuacji, a ponieważ nie opisałeś problemu, który próbujesz rozwiązać, jak ktoś może zaproponować rozwiązanie?

Musisz przetestować je oba, aby zobaczyć, które z nich spełniają Twoje potrzeby.

Powiedziawszy to, MongoDB nie wymaga drogiego sprzętu. Jak każde inne rozwiązanie bazodanowe, będzie działać lepiej z większą ilością procesora i pamięci, ale z pewnością nie jest wymogiem - szczególnie w przypadku wczesnego programowania.


10

Redis to magazyn danych w pamięci , który może zachować swój stan na dysku (aby umożliwić odzyskiwanie po ponownym uruchomieniu). Jednak bycie magazynem danych w pamięci oznacza, że ​​rozmiar magazynu danych (w jednym węźle) nie może przekroczyć całkowitej ilości pamięci w systemie (fizyczna pamięć RAM + przestrzeń wymiany). W rzeczywistości będzie to o wiele mniej, ponieważ Redis dzieli tę przestrzeń z wieloma innymi procesami w systemie, a jeśli wyczerpie przestrzeń pamięci systemowej, prawdopodobnie zostanie zabity przez system operacyjny.

Mongo jest dyskowym magazynem danych, który jest najbardziej wydajny, gdy jego działający zestaw mieści się w fizycznej pamięci RAM (jak każde oprogramowanie). Dane dyskowe oznaczają, że nie ma żadnych wewnętrznych ograniczeń wielkości bazy danych Mongo, jednak opcje konfiguracji, dostępne miejsce na dysku i inne obawy mogą oznaczać, że rozmiary baz danych powyżej pewnego limitu mogą stać się niepraktyczne lub nieefektywne.

Zarówno Redis, jak i Mongo można łączyć w klastry w celu zapewnienia wysokiej dostępności, tworzenia kopii zapasowych i zwiększenia ogólnego rozmiaru magazynu danych.


10

Wszystkie odpowiedzi (w momencie pisania tego tekstu) zakładają, że każda z Redis, MongoDB i być może relacyjna baza danych oparta na SQL są zasadniczo tym samym narzędziem: „przechowuj dane”. W ogóle nie biorą pod uwagę modeli danych.

MongoDB: złożone dane

MongoDB to magazyn dokumentów. Aby porównać z relacyjną bazą danych opartą na SQL: relacyjne bazy danych upraszczają indeksowane pliki CSV, przy czym każdy plik jest tabelą; magazyny dokumentów upraszczają indeksowane pliki JSON, przy czym każdy plik jest dokumentem, z wieloma plikami zgrupowanymi razem.

Pliki JSON mają podobną strukturę do plików XML i YAML oraz do słowników jak w Pythonie, więc pomyśl o swoich danych w takiej hierarchii. Podczas indeksowania kluczem jest struktura: dokument zawiera nazwane klucze, które zawierają kolejne dokumenty, tablice lub wartości skalarne. Rozważ poniższy dokument.

{
  _id:  0x194f38dc491a,
  Name:  "John Smith",
  PhoneNumber:
    Home: "555 999-1234",
    Work: "555 999-9876",
    Mobile: "555 634-5789"
  Accounts:
    - "379-1111"
    - "379-2574"
    - "414-6731"
}

Powyższy dokument ma klucz PhoneNumber.Mobile, który ma wartość 555 634-5789. Możesz przeszukiwać zbiór dokumentów, w których klucz PhoneNumber.Mobilema pewną wartość; są indeksowane.

Ma także tablicę, w Accountsktórej znajduje się wiele indeksów. Możliwe jest zapytanie o dokument, w którym Accountsznajduje się dokładnie pewien podzbiór wartości, cały podzbiór wartości lub dowolny podzbiór wartości. Oznacza to, że możesz wyszukiwać Accounts = ["379-1111", "379-2574"]i nie znajdować powyższych; możesz wyszukać Accounts includes ["379-1111"]i znaleźć powyższy dokument; i możesz wyszukać Accounts includes any of ["974-3785","414-6731"]i znaleźć powyższy dokument oraz dowolny dokument zawierający konto „974-3785”, jeśli istnieje.

Dokumenty sięgają tak głęboko, jak chcesz. PhoneNumber.Mobilemoże pomieścić tablicę lub nawet dokument podrzędny ( PhoneNumber.Mobile.Worki PhoneNumber.Mobile.Personal). Jeśli Twoje dane są wysoce ustrukturyzowane, dokumenty stanowią duży krok naprzód w stosunku do relacyjnych baz danych.

Jeśli Twoje dane są głównie płaskie, relacyjne i mają sztywną strukturę, lepiej jest z relacyjną bazą danych. Ponownie, wielkim znakiem jest to, czy Twoje modele danych najlepiej nadają się do zbioru powiązanych ze sobą plików CSV, czy zbioru plików XML / JSON / YAML.

W przypadku większości projektów trzeba pójść na kompromis, akceptując drobne obejście w niektórych małych obszarach, w których nie pasuje ani SQL, ani Dokumenty; w przypadku niektórych dużych, złożonych projektów przechowujących szeroki zakres danych (wiele kolumn; wiersze są nieistotne), sensowne będzie przechowywanie niektórych danych w jednym modelu, a innych danych w innym modelu. Facebook używa zarówno SQL, jak i graficznej bazy danych (gdzie dane są umieszczane w węzłach, a węzły są połączone z innymi węzłami); Craigslist zwykł używać MySQL i MongoDB, ale starał się całkowicie przenieść na MongoDB. Są to miejsca, w których rozpiętość i relacja danych napotykają znaczne utrudnienia, jeśli są objęte jednym modelem.

Redis: klucz-wartość

Redis to przede wszystkim sklep z kluczowymi wartościami. Redis pozwala ci dać klucz i wyszukać pojedynczą wartość. Redis może przechowywać ciągi, listy, skróty i kilka innych rzeczy; jednak wyszukuje tylko według nazwy.

Unieważnienie pamięci podręcznej jest jednym z trudnych problemów informatycznych; drugi nazywa rzeczy. Oznacza to, że będziesz używać Redis, jeśli chcesz uniknąć setek nadmiaru odnośników do zaplecza, ale będziesz musiał dowiedzieć się, kiedy potrzebujesz nowego wyszukiwania.

Najbardziej oczywistym przypadkiem unieważnienia jest aktualizacja przy zapisie: jeśli czytasz user:Simon:lingots = NOTFOUND, możesz SELECT Lingots FROM Store s INNER JOIN UserProfile u ON s.UserID = u.UserID WHERE u.Username = Simoni zapisać wynik 100, jak SET user:Simon:lingots = 100. Wtedy, kiedy przyzna Simon 5 Lingots, można przeczytać user:Simon:lingots = 100, SET user:Simon:lingots = 105i UPDATE Store s INNER JOIN UserProfile u ON s.UserID = u.UserID SET s.Lingots = 105 WHERE u.Username = Simon. Teraz masz 105 w swojej bazie danych i w Redis, i możesz dostać się user:Simon:lingotsbez zapytania do bazy danych.

Drugi przypadek to aktualizacja informacji zależnych. Załóżmy, że generujesz fragmenty strony i buforujesz ich wyniki. Nagłówek pokazuje doświadczenie, poziom i ilość pieniędzy gracza; strona profilu gracza ma blok, który pokazuje jego statystyki; i tak dalej. Gracz zdobywa trochę doświadczenia. Cóż, teraz masz kilka templates:Header:Simon, templates:StatsBox:Simon, templates:GrowthGraph:Simon, i tak dalej dziedzinach, w których już buforowane wyjście bazy pół tuzina zapytań prowadzonym przez silnik szablonu. Zwykle podczas wyświetlania tych stron mówisz:

$t = GetStringFromRedis("templates:StatsBox:" + $playerName);
if ($t == null) {
  $t = BuildTemplate("StatsBox.tmpl",
                     GetStatsFromDatabase($playerName));
  SetStringInRedis("Templates:StatsBox:" + $playerName, $t);
}
print $t;

Ponieważ właśnie zaktualizowałeś wyniki GetStatsFromDatabase("Simon"), musisz zrezygnować templates:*:Simonz pamięci podręcznej klucz-wartość. Podczas próby renderowania któregokolwiek z tych szablonów aplikacja odpali pobieranie danych z bazy danych (PostgreSQL, MongoDB) i wstawia je do szablonu; następnie zapisze wynik w Redis i, miejmy nadzieję, nie będzie zawracał sobie głowy tworzeniem zapytań do bazy danych i renderowaniem szablonów przy następnym wyświetleniu tego bloku danych wyjściowych.

Redis umożliwia także tworzenie kolejek wiadomości subskrybowanych przez wydawcę i tym podobnych. To zupełnie inny temat. Chodzi o to, że Redis to pamięć podręczna klucz-wartość, która różni się od relacyjnej bazy danych lub magazynu dokumentów.

Wniosek

Wybierz narzędzia w zależności od potrzeb. Największą potrzebą jest zazwyczaj model danych, który określa stopień złożoności i podatności na błędy w kodzie. Specjalistyczne aplikacje będą opierać się na wydajności - miejscach, w których piszesz wszystko w mieszance C i asemblera; większość aplikacji po prostu obsłuży uogólnioną skrzynkę i użyje systemu buforowania, takiego jak Redis lub Memcached, który jest o wiele szybszy niż wysokowydajna baza danych SQL lub magazyn dokumentów.


2
„Unieważnienie pamięci podręcznej jest jednym z trudnych problemów informatycznych; drugim jest nadawanie nazw”. Tak prawdziwe!
Arel

2

I nie powinieneś używać żadnego z nich, jeśli masz dużo pamięci RAM. Redis i MongoDB są w cenie narzędzia ogólnego zastosowania. Wprowadza to wiele kosztów ogólnych.

Było powiedzenie, że Redis jest 10 razy szybszy niż Mongo. To może już nie być tak prawdziwe. MongoDB (jeśli dobrze pamiętam) twierdził, że pobił memcache do przechowywania i buforowania dokumentów, o ile konfiguracje pamięci są takie same.

W każdym razie. Redis dobrze, MongoDB jest dobre. Jeśli zależy Ci na podkonstrukcjach i potrzebujesz agregacji, wybierz MongoDB. Jeśli przechowywanie kluczy i wartości jest Twoim głównym zmartwieniem, to wszystko dotyczy Redis. (lub jakikolwiek inny magazyn wartości klucza).


2

Redis i MongoDB są nierelacyjnymi bazami danych, ale należą do różnych kategorii.

Redis jest bazą danych Key / Value i korzysta z pamięci In-memory, co czyni ją superszybką. Jest dobrym kandydatem do buforowania rzeczy i tymczasowego przechowywania danych (w pamięci), a ponieważ obsługuje je większość platform chmurowych (takich jak Azure, AWS), wykorzystanie pamięci jest skalowalne, ale jeśli będziesz używać go na swoich komputerach z ograniczone zasoby, rozważ użycie pamięci.

Z drugiej strony MongoDB to baza danych dokumentów. Jest to dobra opcja do przechowywania dużych tekstów, obrazów, filmów itp. I prawie wszystkiego, co robisz z bazami danych, z wyjątkiem transakcji. Na przykład, jeśli chcesz stworzyć blog lub sieć społecznościową, MongoDB jest właściwym wyborem. Jest skalowalny dzięki skalowalnej strategii. Używa dysku jako nośnika pamięci, więc dane zostaną utrwalone.


1

Jeśli twój projekt pozwala na posiadanie wystarczającej ilości pamięci RAM w twoim środowisku - odpowiedź brzmi Redis. Zwłaszcza biorąc pod uwagę nowy Redis 3.2 z funkcjonalnością klastra.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.